// goTo.js
(function () {
/* global adventurejs A */
/**
* @augments {adventurejs.Verb}
* @class goTo
* @ajsnode game.dictionary.verbs.goTo
* @ajsconstruct MyGame.createVerb({ "name": "goTo", [...] });
* @ajsconstructedby adventurejs.Dictionary#createVerb
* @hideconstructor
* @ajsinstanceof Verb
* @ajsnavheading LocomotionVerbs
* @summary Verb meaning go to location, or location of asset.
* @tutorial Scripting_VerbSubscriptions
* @tutorial Verbs_VerbAnatomy
* @tutorial Verbs_VerbProcess
* @tutorial Verbs_ModifyVerbs
* @tutorial Verbs_WriteVerbs
* @classdesc
* <pre class="display border outline">
* <span class="ajs-player-input">> go to grand ballroom</span>
* You go up to the mezzanine. You go south to the Grand Ballroom.
*
* <strong>Grand Ballroom</strong>
* Balls everywhere. Literally. More of a ball pit, really.
* </pre>
* <p>
* <strong>goTo</strong> is not called directly by the parser.
* When a player enters <string>"go to"</string>, the verb
* <code>go</code> is called, and forwards to <code>goTo</code>
* if it's contextually suitable.
* <code>goTo</code> is a special verb that will attempt
* to map a path from the subject's location to the specified
* {@link adventurejs.Room|Room},
* or the Room containing a specified
* {@link adventurejs.Tangible|Tangible}
* {@link adventurejs.Asset|Asset}.
* The target asset must be known by subject.
* </p>
* <p>
* <code>goTo</code> will not open or unlock doors that the
* subject has not already opened or unlocked. It will open
* doors the subject has already opened, and it will unlock
* doors the subject has already unlocked providing the subject
* is carrying a key.
* </p>
* @ajsverbreactions doRemoveThisFromThat, doRemoveThatFromThis, doMoveThisToThat, doMoveThatToThis
* @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
* @TODO consider vehicles
*/
A.Preverbs.goTo = {
name: "goTo",
prettyname: "go to",
//synonyms: ["goto"],
// verb_prep_noun: ["go to"],
type: { locomotion: true, travel: true },
subject_must_be: { not_constrained: true, not_nested_elsewhere: true },
/**
* @ajsverbstructures
* @memberof goTo
*/
accepts_structures: ["verb noun"],
/**
* @memberof goTo
* @ajsverbphrase
* phrase1:
* {
* not_global: true,
* accepts_noun: true,
* requires_noun: true,
* noun_must_be:
* {
* known: true,
* tangible: true,
* },
* },
*/
phrase1: {
accepts_noun: true,
requires_noun: true,
noun_must_be: { known: true, tangible: true },
},
/**
* @memberof goTo
* @ajsverbparams
* with_params: {},
*/
with_params: {},
msgNoObject: "Where did you want to go?",
doTry: function () {
var input = this.game.getInput();
var direct_object = input.getAsset(1);
var destination = input.getNoun(1);
var current_room = this.game.world._room;
var msg = "";
if (!(direct_object instanceof adventurejs.Room)) {
destination = direct_object.getRoomId();
}
if (destination === current_room) {
this.game.debug(
`D1296 | ${this.name}.js | destination is current room `
);
msg += `Look around. `;
this.game.print(msg);
return false;
}
return true;
},
doSuccess: function () {
var input = this.game.getInput();
var subject = input.getSubject();
var direct_object = input.getAsset(1);
var destination = input.getNoun(1);
var current_room = this.game.world._room;
var msg = "";
const print_room = this.game.settings.goto_prints_room_descriptions;
const output_class = print_room ? "" : "concatenate_output";
const exclude_unvisited =
this.game.settings.goto_excludes_unvisited_locations;
const exclude_locked = this.game.settings.goto_excludes_locked_doors;
if (!(direct_object instanceof adventurejs.Room)) {
destination = direct_object.getRoomId();
}
// if (destination === current_room) {
// this.game.debug(
// `D1296 | ${this.name}.js | destination is current room `,
// );
// msg += `Look around. `;
// this.game.print(msg);
// return false;
// }
/**
* We use layout to build a list of rooms and their connections.
*/
var layout = {};
for (var i = 0; i < this.game.room_lookup.length; i++) {
var room = this.game.getAsset(this.game.room_lookup[i]);
if (!room) {
continue;
}
if (!subject.hasVisitedRoom(room)) {
// exclude rooms that subject hasn't visited yet
this.game.log(
"L1343",
"log",
"high",
`[${this.name}.js] ${room.id} excluded from goTo because ${subject.id} hasn't been`,
"verbs"
);
continue;
}
var nodes = [];
var exits = room.exits;
var keys = Object.keys(exits);
for (var k = 0; k < keys.length; k++) {
var exit = exits[keys[k]];
exit = this.game.getAsset(exit);
if (!exit) {
continue;
}
var destination_room = this.game.getAsset(exit.destinationID);
if (!destination_room) {
continue;
}
var aperture = exit.aperture;
// exclude exits that subject hasn't used
// if (!exit.is.used) {
// this.game.log(
// "L1344",
// "log",
// "high",
// `[${this.name}.js] excluded ${exit.id}.is.used is false`,
// "verbs"
// );
// continue;
// }
// exclude exits that have no destination
if (!exit.destinationID) {
continue;
}
// exclude destinations that subject hasn't visited
if (!subject.hasVisitedRoom(destination_room) && exclude_unvisited) {
this.game.log(
"L1345",
"log",
"high",
`[${this.name}.js] ${subject.id}.has_been[${destination_room.id}] is unset`,
"verbs"
);
continue;
}
// test exits for locked doors
if (aperture && exclude_locked) {
var key_assets;
aperture = this.game.getAsset(aperture);
//
// LOCKED
//
if (aperture.isDOV("unlock") && aperture.is.locked) {
key_assets = subject.findNestedIndirectObjects(
"unlock",
aperture
);
// if door is locked and subject hasn't got key, pass
if (
!aperture.allowVerbWithNothing("unlock", "dov") &&
!key_assets.length
) {
this.game.log(
"L1346",
"log",
"high",
`[${this.name}.js] ${aperture.id}.is.locked and subject has no key`,
"verbs"
);
continue;
}
if (!aperture.canDoVerbAutomatically("unlock")) {
continue;
}
}
//
// SEALED
//
if (aperture.isDOV("unseal") && aperture.is.sealed) {
key_assets = subject.findNestedIndirectObjects(
"unseal",
aperture
);
// has subject got a key?
if (
!aperture.allowVerbWithNothing("unseal", "dov") &&
!key_assets.length
) {
this.game.log(
"L1347",
"log",
"high",
`[${this.name}.js] ${aperture.id}.is.sealed and subject has no key`,
"verbs"
);
continue;
}
if (!aperture.canDoVerbAutomatically("unseal")) {
continue;
}
}
//
// CLOSED
//
if (aperture.isDOV("open") && aperture.is.closed) {
key_assets = subject.findNestedIndirectObjects("open", aperture);
// has subject got a key?
if (
!aperture.allowVerbWithNothing("open", "dov") &&
!key_assets.length
) {
this.game.log(
"L1348",
"log",
"high",
`[${this.name}.js] ${aperture.id}.is.closed and subject has no key`,
"verbs"
);
continue;
}
if (!aperture.canDoVerbAutomatically("unlock")) {
continue;
}
}
if (aperture.is.hidden) {
this.game.log(
"L1349",
"log",
"high",
`[${this.name}.js] ${aperture.id}.is.hidden`,
"verbs"
);
continue;
}
} // if aperture
// good to go!
nodes.push(exit.destinationID);
}
layout[room.id] = nodes;
}
this.game.log("L1350", "log", "low", ["layout:", layout], "verbs");
/**
* We use graph to convert uni-directional to bi-directional.
* needs to look like: where: { a: { b: cost of a->b }
*/
var graph = {};
for (var id in layout) {
if (!graph[id]) {
graph[id] = {};
}
layout[id].forEach(function (aid) {
graph[id][aid] = 1;
if (!graph[aid]) {
graph[aid] = {};
}
graph[aid][id] = 1;
});
}
this.game.log("L1351", "log", "low", ["graph:", graph], "verbs");
//choose start node
var start = this.game.world._room;
//get all solutions
var solutions = A.dijkstra(graph, start);
this.game.log(
"L1352",
"log",
"low",
["solutions[destination]:", solutions[destination]],
"verbs"
);
if (
"undefined" === typeof solutions[destination] ||
0 === solutions[destination].length
) {
this.game.debug(
`D1297 | ${this.name}.js |
No path was found.
Blockers may include locked/sealed/closed doors which
subject has not opened or does not have a key for,
or destination unknown to / unvisited by subject. `
);
msg += "{We} {don't} know of a route between here and there";
if (!destination?.is?.known && !subject.knowsAbout(destination)) {
msg += ", or even if there is a there there";
}
msg += ".";
this.handleFailure(msg);
return false;
} else {
var len = solutions[destination].length;
for (var i = 0; i < len; i++) {
var thisRoom, nextRoom;
if (0 === i) {
thisRoom = start;
} else {
thisRoom = solutions[destination][i - 1];
}
nextRoom = solutions[destination][i];
thisRoom = this.game.world[thisRoom];
nextRoom = this.game.world[nextRoom];
// cycle through the room's exits
for (var direction in thisRoom.exits) {
var exit = this.game.world[thisRoom.id + "_" + direction];
var dest = exit.destinationID;
// no destination? probably just a description
if ("undefined" === typeof dest) {
continue;
}
//
if (nextRoom.id === dest) {
this.game.log(
"L1353",
"log",
"low",
["nextRoom.id", nextRoom.id, "dest", dest],
"verbs"
);
var output = "";
var aperture = exit.aperture;
if (aperture) aperture = this.game.getAsset(aperture);
if (aperture && (aperture.is.locked || aperture.is.closed)) {
// INVOKE OPEN VERB
this.game.parser.input_queue.push({
input: "open " + aperture.id,
printInput: false,
excludeRoomDescriptions: true,
output_class: output_class,
linefeed: linefeed,
});
}
// printing this output results in double output with tryTravel ie
// You go west to the Eastern Room. {We} move west.
// You go west to the East Room. {We} move west.
// You go west to the Standing Room. {We} move west.
output =
"{We} go " +
direction +
" to " +
(nextRoom.use_definite_article_in_lists
? nextRoom.definite_article + " "
: "") +
nextRoom.name +
". ";
// print a linefeed after the last item only
var linefeed = i === len - 1 ? true : undefined;
this.game.parser.input_queue.push({
// print output: output,
// output was doubling "you move etc" messages
// because tryTravel prints them too
// leaving this here for now as an option
// for future dev
input: direction,
printInput: false,
excludeRoomDescriptions: !print_room,
output_class: output_class,
linefeed: linefeed,
});
continue;
}
}
}
}
//display solutions
// console.log("From '"+start+"' to");
// for(var s in solutions) {
// if(!solutions[s]) continue;
// if( s !== destination ) continue;
// console.log(" -> " + s + ": [" + solutions[s].join(", ") + "] (dist:" + solutions[s].dist + ")");
// }
return true;
},
};
})(); // goTo