// tryTravel.js
(function () {
/*global adventurejs A*/
var p = adventurejs.Game.prototype;
/**
* Tries to move the player in the specified direction.
* @memberOf adventurejs.Game
* @method adventurejs.Game#tryTravel
* @param {String} direction
* @param {Object} params
*/
p.tryTravel = function Game_tryTravel(direction, params = {}) {
this.game.log(
"L1078",
"log",
"high",
`tryTravel.js > ${direction.direction || direction}`,
"Travel"
);
if (direction.direction) direction = direction.direction;
var input = this.game.getInput();
var currentRoom = this.getCurrentRoom();
var player = this.game.getPlayer();
var nest_asset = player.getNestAsset();
var nest_preposition = player.getNestPreposition();
var nest_aspect = player.getNestAspect();
var exit_id = currentRoom.exits[direction]; // may be no exit
var exit, aperture;
var msg = "";
var results;
input.did_tryTravel = true;
// does this room have an exit in the specified direction?
if (!exit_id) {
this.game.debug(`D1115 | tryTravel.js |exit_id is undefined`);
var global_object = this.game.world["global_" + direction];
var global_description;
var currentZone = this.game.world[currentRoom.zone];
// try to get current room's scenery settings
// description can be left undefined
if (
currentRoom.room_scenery[global_object.id]?.enabled &&
currentRoom.room_scenery[global_object.id]?.description
) {
// console.warn( "USE ROOM SCENERY OBJECT DESCRIPTION" );
this.game.log(
"L1491",
"log",
"high",
`tryTravel.js > use ${currentRoom.id}.room_scenery.${global_object.id} for description`,
"Travel"
);
global_description =
currentRoom.room_scenery[global_object.id].description;
}
// otherwise try to get current room's zone scenery settings
else if (
currentZone?.zone_scenery[global_object.id]?.enabled &&
currentZone?.zone_scenery[global_object.id]?.description
) {
this.game.log(
"L1491",
"log",
"high",
`tryTravel.js > use ${currentZone.id}.zone_scenery.${global_object.id} for description`,
"Travel"
);
// console.warn( "USE ZONE SCENERY OBJECT DESCRIPTION" );
global_description =
currentZone.zone_scenery[global_object.id].description;
}
// otherwise if room enables object,
// try to get object's native (aka global) description
else if (
currentRoom.room_scenery[global_object.id]?.enabled &&
global_object.description
) {
this.game.log(
"L1493",
"log",
"high",
`tryTravel.js > use ${global_object.id}.description for description`,
"Travel"
);
// console.warn( "USE GLOBAL OBJECT DESCRIPTION" );
global_description = global_object.description;
}
if (global_description) {
msg += A.getSAF.call(this.game, global_description);
}
if (
this.settings.when_travel_fails_list_exits ||
this.world[this.world._currentRoom].when_travel_fails_list_exits
) {
this.game.debug(
`D1120 | tryTravel.js |
${
this.world[this.world._currentRoom].when_travel_fails_list_exits
? this.world._currentRoom + ".when_travel_fails_list_exits"
: this.game_name + ".settings.when_travel_fails_list_exits"
}`
);
var exits = this.getCurrentRoomExits();
if (exits) msg += " " + exits;
}
this.print(msg, input.output_class);
return null;
}
// ok we think there's an exit
exit = this.game.getAsset(exit_id);
aperture = this.game.getAsset(exit.aperture);
this.game.log(
"L1079",
"log",
"high",
`tryTravel.js > found exit: ${exit.id} ${aperture ? "with aperture: " + aperture.id : ""} `,
"Travel"
);
// is there actually an exit object?
if (!exit) {
this.game.debug(
`D1104 | tryTravel.js | no exit found matching ${exit_id}`
);
msg += `That doesn't appear to be an exit. `;
this.print(msg, input.output_class);
return false;
}
// is player constrained?
if (player.is.constrained) {
this.game.debug(`D1103 | tryTravel.js |player.is.constrained`);
msg +=
A.getSAF.call(this.game, player.constrained_msg) ||
"$(We) can't go anywhere. ";
if (msg) this.game.print(msg, input.output_class);
return null;
}
// is player holding something like a rope or a railing?
if (player.getVerbConnectionCount("hold", "to_dov") > 0) {
// @TODO automatically let go
this.game.debug(
`D1133 | tryTravel.js | ${player.id}.is.connected_by.hold.to_dov contains ${player.is.connected_by.hold.to_dov}`
);
msg += `$(We'll) have to let go of ${this.game.getPrintableObjectList({
objects: player.getVerbConnections("hold", "to_dov"),
})}. `;
if (msg) this.game.print(msg, input.output_class);
return null;
}
// is player in/on a vehicle?
if (
nest_asset &&
(nest_asset.hasClass("Vehicle") || nest_asset.isDOV("ride"))
) {
var allowed = true;
// does the exit allow vehicles?
// does it allow THIS vehicle?
if (
(!exit.allow_vehicles &&
!exit.allow_these_vehicles.includes(nest_asset.id)) ||
exit.deny_these_vehicles.includes(nest_asset.id)
) {
allowed = false;
}
if (!allowed) {
this.game.debug(
`D1323 | tryTravel.js | ${exit.id}.allow_vehicles is false or ${exit.id}.deny_these_vehicles contains ${nest_asset.id} `
);
msg += `$(We) can't ${input.input_verb} ${direction} ${exit.aperture ? this.game.getAsset(exit.aperture).articlename : ""} while ${nest_aspect.id} ${nest_asset.articlename}. `;
if (msg) this.game.print(msg, input.output_class);
return null;
}
}
// is player nested in something?
else if (nest_asset) {
// by default, can't reach an exit while nested
// but there are exceptions
// @TODO add logic for vertical reachability of exit from nest
var reachable = false;
var reachable_assets;
// @TODO this is no longer valid
// is player nested behind/in/on/under something
// that can reach this direction?
if (nest_aspect) {
reachable_assets = nest_aspect.things_player_can_reach_from_this_aspect;
// @todo this no longer works - need to call canPlayerReach( direction )
if (-1 < reachable_assets.indexOf(direction)) {
reachable = true;
}
}
// is player nested on top of something that can reach this direction?
if (!reachable && player.isNestedOnTop()) {
// try the convenience prop first
reachable_assets = nest_asset.things_player_can_reach_from_top_of_this;
if (true === A.isIdInMixedArray(direction, reachable_assets))
reachable = true;
// if not, try the aspect positions
if (!reachable) {
reachable_assets =
nest_asset.aspects[nest_preposition]
.things_player_can_reach_from_positions_of_this_aspect.top;
if (true === A.isIdInMixedArray(direction, reachable_assets))
reachable = true;
}
}
// is player nested on bottom of something that can reach this direction?
if (!reachable && player.isNestedOnBottom()) {
// try the convenience prop first
reachable_assets =
nest_asset.things_player_can_reach_from_bottom_of_this;
if (A.isIdInMixedArray(direction, reachable_assets)) reachable = true;
// if not, try the aspect positions
if (!reachable) {
reachable_assets =
nest_asset.aspects[nest_preposition]
.things_player_can_reach_from_positions_of_this_aspect.bottom;
if (A.isIdInMixedArray(direction, reachable_assets)) reachable = true;
}
}
if (!reachable) {
this.game.debug(
`D1134 | tryTravel.js | ${direction} is unreachable from ${nest_preposition} ${nest_asset.id}`
);
msg += `$(We) can't ${
this.game.dictionary.getDirection(input.input_verb)
? "go"
: input.input_verb
} ${direction} from ${
nest_preposition === "under" || nest_preposition === "behind"
? "$(our) position " + nest_preposition
: ""
} ${nest_asset.articlename}. `;
if (msg) this.game.print(msg, input.output_class);
return null;
}
}
if (aperture) {
var key_assets;
var locked_msg = `${aperture.Articlename_is} closed and locked. `;
var sealed_msg = `${aperture.Articlename_is} sealed shut. `;
var closed_msg = `${aperture.Articlename_is} closed. `;
this.game.log(
"L1080",
"log",
"high",
"tryTravel.js > found aperture " + aperture.id,
"Travel"
);
//
// LOCKED
//
if (aperture.isDOV("unlock") && aperture.is.locked) {
key_assets = player.findNestedIndirectObjects("unlock", aperture);
// if door is locked and player hasn't got key, pass
if (
!aperture.allowVerbWithNothing("unlock", "dov") &&
!key_assets.length
) {
this.game.debug(
`D1105 | tryTravel.js | ${aperture.id}.is.locked and player hasn't got a key`
);
this.print(msg + locked_msg);
return false;
}
if (!aperture.canDoVerbAutomatically("unlock")) {
this.game.debug(
`D1106 | tryTravel.js | ${aperture.id}.is.locked and either ${aperture.id}.dov.unlock.automatically is false or ${aperture.id}.dov.unlock.automatically_after_use is true and player has not previously unlocked ${aperture.id}`
);
this.print(msg + locked_msg);
return false;
}
}
//
// SEALED
//
if (aperture.isDOV("unseal") && aperture.is.sealed) {
key_assets = player.findNestedIndirectObjects("unseal", aperture);
// has player got a key?
if (
!aperture.allowVerbWithNothing("unseal", "dov") &&
!key_assets.length
) {
this.game.debug(
`D1108 | tryTravel.js | ${aperture.id}.is.sealed and player hasn't got a key`
);
this.print(msg + sealed_msg);
return false;
}
if (!aperture.canDoVerbAutomatically("unseal")) {
this.game.debug(
`D1111 | tryTravel.js | ${aperture.id}.is.sealed and either ${aperture.id}.dov.unseal.automatically is false or ${aperture.id}.dov.unseal.automatically_after_use is true and player has not previously unsealed ${aperture.id}`
);
this.print(msg + sealed_msg);
return false;
}
}
//
// CLOSED
//
if (aperture.isDOV("open") && aperture.is.closed) {
key_assets = player.findNestedIndirectObjects("open", aperture);
// has player got a key?
if (
!aperture.allowVerbWithNothing("open", "dov") &&
!key_assets.length
) {
this.game.debug(
`D1108 | tryTravel.js | ${aperture.id}.is.closed and player hasn't got a key`
);
this.print(msg + closed_msg);
return false;
}
if (!aperture.canDoVerbAutomatically("open")) {
this.game.debug(
`D1719 | tryTravel.js | ${aperture.id}.is.closed and either ${aperture.id}.dov.open.automatically is false or ${aperture.id}.dov.open.automatically_after_use is true and player has not previously opened ${aperture.id}`
);
this.print(msg + closed_msg);
return false;
}
}
// check if aperture is hidden
if (aperture.is.hidden) {
this.game.debug(`D1049 | tryTravel.js | ${aperture.id}.is.hidden`);
msg += `Hmm. You don't see any exit ${exit.direction}. `;
this.print(msg, input.output_class);
return false;
}
} // if aperture
// direction is valid but has no destination, try to print description
if (
"undefined" === typeof exit.destinationID &&
(exit.descriptions.travel || exit.descriptions.look)
) {
this.game.log(
"L1081",
"log",
"high",
"tryTravel.js > " +
exit.direction +
" has no destination. Printing description.",
"Travel"
);
this.game.debug(
`D1112 | tryTravel.js | ${exit.id}.destinationID is unset`
);
msg += A.getSAF.call(
this,
exit.descriptions.travel
? exit.descriptions.travel
: exit.descriptions.look
);
if (
this.settings.when_travel_fails_list_exits ||
this.world[this.world._currentRoom].when_travel_fails_list_exits
) {
this.game.debug(
`D1124 | tryTravel.js |
${
this.world[this.world._currentRoom].when_travel_fails_list_exits
? this.world._currentRoom + ".when_travel_fails_list_exits"
: this.game_name + ".settings.when_travel_fails_list_exits"
}`
);
var exits = this.getCurrentRoomExits();
if (exits) msg += " " + exits;
}
this.print(msg);
return null;
}
// is player holding a rope that is tied to something in the room?
// @TODO expand this to include other attachment types
// such as things that are plugged into each other
if (player.hasRopesThatBlockTravel()) {
var ropes = player.getRopesThatBlockTravel();
var rope = this.game.getAsset(ropes[0]);
var object_rope_is_tied_to;
for (var i = 0; i < rope.is.connected_by.tie.to_iov.length; i++) {
if (player.id !== rope.is.connected_by.tie.to_iov[i]) {
object_rope_is_tied_to = this.game.getAsset(
rope.is.connected_by.tie.to_iov[i]
);
}
}
this.game.debug(`D1113 | tryTravel.js |player.hasRopesThatBlockTravel`);
msg = `$(We're) preventing from leaving by ${rope.articlename}`;
if (object_rope_is_tied_to) {
msg += ` tied to ${object_rope_is_tied_to.articlename}`;
}
msg += `. `;
if (msg) this.game.print(msg, input.output_class);
return null;
}
if (player.isNested() && !nest_asset.isDOV("ride")) {
results = player.onUnnestThisFromThat(nest_asset);
if ("undefined" !== typeof results) return results;
}
// doSuccess
// direction is valid and destination exists
if (exit.destinationID) {
this.game.log(
"L1082",
"log",
"high",
`tryTravel.js > doSuccess, travel to ${exit.destinationID}.`,
"Travel"
);
// TODO add logic for hidden exits
exit.setIs("used", true);
var addPeriod = false;
console.warn({ aperture });
if (aperture) {
if (aperture.is.locked && aperture.canDoVerbAutomatically("unlock")) {
msg += `$(We) unlock ${aperture.articlename}`;
addPeriod = true;
aperture.setIs("locked", false);
aperture.incrementDoVerbCount("unlock", "dov");
}
if (
aperture.is.sealed &&
aperture.can.canDoVerbAutomatically("unseal")
) {
msg += `$(We) unseal ${aperture.articlename}`;
addPeriod = true;
aperture.setIs("sealed", false);
aperture.incrementDoVerbCount("unseal", "dov");
}
if (aperture.is.closed && aperture.canDoVerbAutomatically("open")) {
if (!msg) {
msg += `$(We) open ${aperture.articlename}`;
} else {
msg += ", and then open it";
}
//+ ". ";
addPeriod = true;
aperture.setIs("closed", false);
aperture.incrementDoVerbCount("open", "dov");
}
if (addPeriod) msg += ". ";
aperture.setIs("used", true);
if (aperture.linked_asset) {
this.game.getAsset(aperture.linked_asset).setIs("used", true);
}
}
var newRoomID = exit.destinationID;
var newRoomObj = this.world[newRoomID];
for (var exitProp in newRoomObj.exits) {
var newRoomExitID = newRoomObj.exits[exitProp];
var newRoomExitObj = this.world[newRoomExitID];
if (newRoomExitObj.destinationID === currentRoom.id) {
newRoomExitObj.setIs("used", true);
}
}
if (player.isNested() && nest_asset.isDOV("ride")) {
// results = newRoom.onMoveThatToThis( nest_asset, "in" ); // string
// if( false === results ) { return false; }
// else if ( null === results ) { return null; }
msg += `$(We) ride ${nest_asset.articlename} `;
if (this.game.dictionary.verbs[direction].is_relative_direction) {
//msg += "to ";
} else if (this.game.dictionary.verbs[direction].is_compass_direction) {
msg += `to the `;
}
msg += `${direction}. `;
} else if (params.with) {
msg += `$(We) push `;
msg += this.game.getPrintableObjectList({
objects: params.with,
article: "definite",
});
msg += ` `;
if (this.game.dictionary.verbs[direction].is_relative_direction) {
//msg += "to ";
} else if (this.game.dictionary.verbs[direction].is_compass_direction) {
msg += `to the `;
}
msg += `${direction}. `;
if ("string" === typeof params.with) {
params.with = [params.with];
}
for (var i = 0; i < params.with.length; i++) {
var object = this.game.getAsset(params.with[i]);
object.moveFrom(object.getPlaceAsset());
object.moveTo("in", newRoomObj);
}
} else if (exit.descriptions && exit.descriptions.travel) {
msg += A.getSAF.call(this.game, exit.descriptions.travel);
} else {
msg +=
this.game.dictionary.verbs[input.input_verb] &&
this.game.dictionary.verbs[input.input_verb].type.travel
? `$(We) ${input.input_verb} `
: `$(We) ${player.getPostureVerb()} `;
if (this.game.dictionary.verbs[direction].is_relative_direction) {
// relative directions include "port" and "starboard",
// "left" and "right", etc
//msg += "to ";
}
// else if(this.game.dictionary.verbs[direction].is_compass_direction)
// {
// msg += "to the ";
// }
msg +=
direction +
" to " +
(newRoomObj.use_definite_article_in_lists
? newRoomObj.definite_article + " "
: "") +
newRoomObj.name +
". ";
}
this.game.print(msg, input.output_class);
this.setPlayerRoom(this.world[newRoomID], params);
return true;
}
// final fallback
this.game.debug(`D1114 | tryTravel.js |no ${direction} exit found`);
msg += `There doesn't appear to be an exit ${direction}. `;
if (
this.settings.when_travel_fails_list_exits ||
this.world[this.world._currentRoom].when_travel_fails_list_exits
) {
this.game.debug(
`D1119 | tryTravel.js | ${this.game_name}.settings.when_travel_fails_list_exits is true`
);
var exits = this.getCurrentRoomExits();
if (exits) msg += " " + exits;
}
this.print(msg, input.output_class);
return false;
};
})();