// tie.js
(function () {
/*global adventurejs A*/
/**
* @augments {adventurejs.Verb}
* @class tie
* @ajsnode game.dictionary.verbs.tie
* @ajsconstruct MyGame.createVerb({ "name": "tie", [...] });
* @ajsconstructedby adventurejs.Dictionary#createVerb
* @hideconstructor
* @ajsinstanceof Verb
* @ajsnavheading ManipulationVerbs
* @summary Summary.
* @tutorial Scripting_VerbSubscriptions
* @tutorial Verbs_VerbAnatomy
* @tutorial Verbs_VerbProcess
* @tutorial Verbs_ModifyVerbs
* @tutorial Verbs_WriteVerbs
* @classdesc
* <pre class="display border outline">
* <span class="input">> tie boat to bollard with rope</span>
* You tie the rope to the boat and the bollard.
* </pre>
* <p>
* <strong>Tie</strong> one
* {@link adventurejs.Tangible|Tangible}
* {@link adventurejs.Asset|Asset} to one or more others.
* In order to be tied, the asset must have its
* <code class="property">asset.dov.tie.enabled</code> property
* set to true.
* </p>
* <p>
* To apply tie to a non-rope asset such as a shoe
* or a bowtie, set its
* <code class="property">asset.dov.tie.with_params.with_nothing</code>
* to true.
* </p>
* <h3 class="example">Example</h3>
* <pre class="display"><code class="language-javascript">MyGame.createAsset({
* class: "Clothing",
* name: "shoe"
* dov: {
* tie: { with_nothing: true },
* },
* is: { tied: false },
* });
* </code></pre>
* <p>
* If a rope is being tied to another asset, the other asset must have its
* <code class="property">asset.iov.tie.enabled</code> set to true.
* This is true even if a player inputs the other asset as a direct object.
* For example, "tie rope to zebra" and "tie zebra with rope" will both be
* parsed with the rope as the direct object and the zebra as the indirect object.
* (Instances of the {@link adventurejs.Rope|Rope} class will inherit
* a direct object subscription, though you may redefine it in order
* to set custom parameters.)
* </p>
* <pre class="display"><code class="language-javascript">MyGame.createAsset({
* class: "Rope",
* name: "sisal rope"
* dov: {
* tie: {
* with_assets: [ "balustrade" ],
* },
* },
* });
* MyGame.createAsset({
* class: "Thing",
* name: "balustrade",
* dov: { climb: true },
* iov: {
* tie: {
* with_assets: [ "sisal rope" ],
* },
* },
* });
* </code></pre>
* @ajsverbreactions doRemoveThisFromThat, doRemoveThatFromThis, doMoveThisToThat, doMoveThatToThis
* @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
* @TODO tie knot in rope
*/
A.Preverbs.tie = {
name: "tie",
prettyname: "tie",
synonyms: ["tie"],
past_tense: "tied",
state: "tied",
state_string: "tied",
unstate_string: "untied",
gerund: "tying",
verb_prep_noun: ["tie up"],
verb_noun_prep: ["tie up"],
makes_connections: true,
/**
* @ajsverbstructures
* @memberof tie
*/
accepts_structures: [
"verb noun", // tie bowtie, tie shoelace
"verb noun preposition noun", // tie thief to chair (with rope), tie thief with rope
// tie knot in rope, tie rope in knots
"verb noun preposition noun preposition noun", // tie thief to chair with rope
// tie rope to thief and chair
],
/**
* @memberof tie
* @ajsverbphrase
* phrase1:
* {
* accepts_noun: true,
* accepts_plural_noun: true, // tie A and B with rope
* requires_noun: true,
* noun_must_be:
* {
* known: true,
* tangible: true,
* present: true,
* visible: true,
* reachable: true,
* },
* },
*/
phrase1: {
accepts_noun: true,
accepts_plural_noun: true,
requires_noun: true,
noun_must_be: {
known: true,
tangible: true,
present: true,
visible: true,
reachable: true,
},
},
/**
* @memberof tie
* @ajsverbphrase
* phrase2:
* {
* accepts_noun: true,
* accepts_plural_noun: true, // tie rope to A and B
* noun_must_be:
* {
* known: true,
* tangible: true,
* present: true,
* visible: true,
* reachable: true,
* },
* accepts_preposition: true,
* requires_preposition: true,
* accepts_these_prepositions: [ "to", "with", "in" ], // "in" only for "tie knot in rope"
* },
*/
phrase2: {
accepts_noun: true,
accepts_plural_noun: true,
noun_must_be: {
known: true,
tangible: true,
present: true,
visible: true,
reachable: true,
},
accepts_preposition: true,
requires_preposition: true,
accepts_these_prepositions: ["to", "with", "in"],
},
/**
* @memberof tie
* @ajsverbphrase
* phrase3:
* {
* accepts_noun: true,
* requires_noun: true,
* noun_must_be:
* {
* known: true,
* tangible: true,
* present: true,
* visible: true,
* reachable: true,
* },
* accepts_preposition: true,
* requires_preposition: true,
* accepts_these_prepositions: [ 'to', 'with' ],
* },
*/
phrase3: {
accepts_noun: true,
requires_noun: true,
noun_must_be: {
known: true,
tangible: true,
present: true,
visible: true,
reachable: true,
},
accepts_preposition: true,
requires_preposition: true,
accepts_these_prepositions: ["to", "with"],
},
/**
* @memberof tie
* @ajsverbparams
* with_params: {
*
* // If true, player can tie knots in this asset.
* // (@TODO)
* can_knot: true,
*
* // The maximum number of connections that this
* // asset can make with this verb.
* max_connections: 2,
*
* // If true, player must be holding this asset
* // to apply this verb.
* must_hold_to_use: false,
*
* // If true, player will drop rope on tying it to the
* // maximum number of assets.
* on_max_connections_drop_rope: true,
*
* // If true, taking this asset while tied will
* // untie it from any other asset(s) it is tied to.
* on_take_break_connections: false,
*
* // If true, taking this asset while tied will
* // also take any other asset(s) it is tied to.
* on_take_take_connections: false,
* },
*/
with_params: {
can_knot: true,
max_connections: 2,
must_hold_to_use: false,
on_max_connections_drop_rope: true,
on_take_break_connections: false,
on_take_take_connections: false,
},
doTry: function () {
var input = this.game.getInput();
var verb_phrase = input.verb_phrase;
var direct_object = input.getAsset(1);
var direct_preposition = input.getPreposition(1);
var indirect_object1 = input.getAsset(2);
var indirect_preposition1 = input.getPreposition(2);
var indirect_object2 = input.getAsset(3);
var indirect_preposition2 = input.getPreposition(3);
var player = this.game.getPlayer();
var results;
var inferred_rope;
var msg = "";
var rope;
var targets = [];
if (verb_phrase === "tie up") {
//
}
// did player input something like
// "tie x with y with z" or "tie x to y to z" ?
if (
indirect_preposition1 &&
indirect_preposition2 &&
indirect_preposition1 === indirect_preposition2
) {
this.game.debug(
`D1594 | ${this.name}.js | can't handle "${indirect_preposition1}" and "${indirect_preposition2}"`
);
msg += `That doesn't even make sense. `;
this.handleFailure(msg);
return false;
}
// sentence structure: verb noun
// ie tie shoe
if (input.hasStructure("verb noun")) {
// no indirect object required?
if (
direct_object.isDOV(this.name) &&
direct_object.allowVerbWithNothing(this.name, "dov")
) {
if (this.hasState() && direct_object.isVerbState(this.name)) {
this.game.debug(
`D2165 | ${this.name}.js | ${
direct_object.id
}.is.${this.getState()} is ${direct_object.isVerbState(
this.name
)}`
);
msg += `${
direct_object.Articlename_is
} already ${this.getState()}. `;
this.handleFailure(msg);
return false;
}
return true;
}
// this.game.debug(
// `D2161 | ${this.name}.js | ${direct_object.id}.dov.${this.name} or ${direct_object.id}.dov.${this.name}.with_nothing is unset`,
// );
if (direct_object.isDOV(this.name)) {
msg += `To what would $(we) like to ${this.name} ${direct_object.articlename}? `;
input.setPreposition(2, "to");
input.setSoftPrompt({
index: 2,
type: "noun",
noun2: true,
preposition2: true,
structure: "verb noun preposition noun",
});
this.handleFailure(msg);
return false;
} else if (direct_object.isIOV(this.name)) {
this.game.debug(`Fxxxx | ${this.name}.js | direct_object.isIOV `);
// can we infer a rope?
results = this.tryToInferRope([direct_object]);
if (results.prompt) {
let index = 2;
input.setPreposition(index, "with");
input.setSoftPrompt({
index: index,
type: "noun",
["noun" + index]: true,
["preposition" + index]: true,
structure: "verb noun preposition noun",
});
this.game.debug(
`D2166 | ${this.name}.js | soft prompt for noun${index} `
);
msg += `With what would $(we) like to ${this.name} ${direct_object.articlename}? `;
this.handleFailure(msg);
return false;
} else if (results.ropes.length) {
this.game.debug(`Fxxxx | ${this.name}.js | found a rope `);
// found a rope
inferred_rope = true;
rope = results.ropes[0];
input.setPreposition(2, "with");
input.setAsset(2, rope);
input.setStructure("verb noun preposition noun");
}
} else {
msg += `$(We) don't know how to ${this.name} ${direct_object.articlename}. `;
this.handleFailure(msg);
return false;
}
}
// we accept "tie rope to thing" or "tie thing with rope"
// which means direct object could be rope or target
// so look for uses of "to" and "with" to help identify
// make lists of ropes and targets
for (let i = 1; i <= 3; i++) {
let asset = input.getAsset(i);
if (!asset) continue;
let preposition = input.getPreposition(i);
// did player input a substance?
// (which would give us a tangible substance container)
if (input.getSubstance(i)) {
this.game.debug(
`D2164 | ${this.name}.js | substances can't be tied `
);
msg += `${input.getSubstance(i).Name} can't be tied. `;
this.handleFailure(msg);
return false;
}
// can asset be tied at all?
if (!asset.isOV(this.name)) {
this.game.debug(
`D1590 | ${this.name}.js | neither ${asset.id}.dov.${this.name} nor ${asset.id}.iov.${this.name} is set `
);
msg += `${asset.Articlename} can't be tied. `;
this.handleFailure(msg);
return false;
}
// direct object is the only asset without a preposition
// is that what we're looking at?
// @TODO this doesn't handle knots
if (!preposition) {
if (asset.isDOV(this.name)) {
rope = asset;
continue;
} else if (asset.isIOV(this.name)) {
targets.push(asset);
continue;
}
}
// "tie with" assumes it's a rope
if (preposition && preposition === "with") {
if (!asset.isDOV(this.name)) {
this.game.debug(
`D2162 | ${this.name}.js | ${asset.id}.iov.${this.name} is unset`
);
msg += `$(We) can't ${this.name} anything with ${asset.articlename}. `;
this.handleFailure(msg);
return false;
}
rope = asset;
continue;
}
// "tie to" assumes it's a target
if (preposition && preposition === "to") {
if (asset.isIOV(this.name)) {
targets.push(asset);
continue;
} else if (asset.isDOV(this.name)) {
if (!rope) {
rope = asset;
continue;
}
this.game.debug(
`D2163 | ${this.name}.js | ${asset.id}.dov.${this.name} is set but already found ${rope.id}`
);
msg += `$(We) can't ${this.name} anything to ${asset.articlename}. `;
this.handleFailure(msg);
return false;
}
}
}
// did player input something like
// "tie boat to dock" without specifying a rope?
// try to infer a rope
if (targets.length && !rope) {
// infer indirect object?
results = this.tryToInferRope();
let index = targets.length + 1;
if (results.prompt) {
this.game.debug(
`D1593 | ${this.name}.js | soft prompt for noun${index} `
);
input.setPreposition(index, "with");
input.setSoftPrompt({
index: index,
type: "noun",
["noun" + index]: true,
structure:
"verb noun preposition noun" +
(index === "3" ? " preposition noun" : ""),
});
msg += `With what would $(we) like to ${this.name} ${
targets.length > 0 ? targets[0].articlename : ""
} ${targets.length > 1 ? "to " + targets[1].articlename : ""}? `;
this.handleFailure(msg);
return false;
}
// found a rope
inferred_rope = true;
rope = results.ropes[0];
this.game.printInferred(`with ${rope.articlename}`);
}
// no rope found
if (!rope) {
this.game.debug(`D2158 | ${this.name}.js | no ropes `);
// ignore rope/targets and fall back to the original vars
msg += `$(We) don't know how to ${this.name} ${
direct_preposition ? direct_preposition : ""
} ${direct_object.articlename}${
indirect_preposition1 ? " " + indirect_preposition1 : ""
}${indirect_object1 ? " " + indirect_object1.articlename : ""}${
indirect_preposition2 ? " " + indirect_preposition2 : ""
}${indirect_object2 ? " " + indirect_object2.articlename : ""}. `;
this.handleFailure(msg);
return false;
}
// can't tie if not holding rope
if (
rope.dov[this.name].with_params.must_hold_to_use &&
!player.$has(rope)
) {
this.game.debug(
`D1591 | ${this.name}.js | ${rope.id}.dov.${this.name}.with_params.must_hold_to_use is true`
);
msg += `$(We)'re not holding ${rope.articlename}. `;
this.handleFailure(msg);
return false;
}
// rope already tied to both targets
if (
targets.length > 1 &&
rope.isConnectedToAsset("tie", targets[0], "to_iov") &&
rope.isConnectedToAsset("tie", targets[1], "to_iov")
) {
msg += `${rope.Articlename} is already tied to both ${targets[0].articlename} and ${targets[1].articlename}. `;
this.handleFailure(msg);
return false;
}
for (let i = 0; i < targets.length; i++) {
// can't tie to target? - redundant?
if (targets[i] && !targets[i].isIOV(this.name)) {
this.game.debug(
`D1649 | ${this.name}.js | ${targets[i].id}.iov.${this.name} is unset`
);
msg += `$(We) can't tie anything to ${targets[i].articlename}. `;
this.handleFailure(msg);
return false;
}
// can't tie this specific rope to this specific target?
if (
!rope.allowVerbWithAnything(this.name, "dov") &&
!rope.allowVerbWithAsset("tie", targets[i].id, "dov")
) {
this.game.debug(
`D2167 | ${this.name}.js | ${rope.id}.dov.${this.name}.with_anything is false and neither .with_assets / .with_classes includes ${targets[i].id} / ${targets[i].class} `
);
msg += `${rope.Articlename} can't be ${this.past_tense} to ${targets[i].articlename}. `;
this.handleFailure(msg);
return false;
}
// can't tie if not holding target?
if (
targets[i] &&
targets[i].iov[this.name].with_params.must_hold_to_use &&
!player.$has(targets[i])
) {
this.game.debug(
`D2159 | ${this.name}.js | ${targets[i].id}.iov.${this.name}.with_params.must_hold_to_use is true`
);
msg += `$(We)'re not holding ${targets[i].articlename}. `;
this.handleFailure(msg);
return false;
}
// rope already tied to target?
if (rope.isConnectedToAsset("tie", targets[i], "to_iov")) {
this.game.debug(
`D1483 | ${this.name}.js | ${rope.id}.is.connected_by.${this.name}.to_iov contains ${targets[i].id}`
);
msg += `${rope.Articlename} is already tied to ${targets[i].articlename}. `;
this.handleFailure(msg);
return false;
}
// target is maxed out?
if (
targets[i].getVerbConnectionCount("tie", "to_dov") >=
targets[i].getVerbMaxConnections("tie", "iov")
) {
this.game.debug(
`D2169 | ${this.name}.js | ${targets[i].id}.is.connected_by.${this.name}.to_dov has the maximum number of connections specified at ${targets[i].id}.iov.${this.name}.with_params.max_connections`
);
msg += `${targets[i].Articlename} can't have any more things tied to it. `;
this.handleFailure(msg);
return false;
}
}
// rope is maxed out?
if (
rope.getVerbConnectionCount("tie", "to_iov") >=
rope.getVerbMaxConnections("tie", "dov")
) {
this.game.debug(
`D2170 | ${this.name}.js | ${rope.id}.is.connected_by.${this.name}.to_iov has the maximum number of connections specified at ${rope.id}.dov.${this.name}.with_params.max_connections`
);
msg += `${rope.Articlename} can't be tied to any more things. `;
this.handleFailure(msg);
return false;
}
input.verb_params.rope = rope;
input.verb_params.targets = targets;
return true;
},
doSuccess: function () {
var input = this.game.getInput();
var verb_phrase = input.verb_phrase;
var direct_object = input.getAsset(1);
var indirect_object1 = input.getAsset(2);
var indirect_object2 = input.getAsset(3);
var player = this.game.getPlayer();
var results;
var msg = "";
var rope = input.verb_params.rope;
var targets = input.verb_params.targets;
if (input.hasStructure("verb noun")) {
// if we got here without an indirect object
// it means none is required
msg = `$(We) ${this.name} ${direct_object.articlename}. `;
// apply state changes
this.setState(direct_object, true);
// print output
this.handleSuccess(msg, direct_object);
return true;
}
let diff =
rope.getVerbMaxConnections("tie", "dov") -
rope.getVerbConnectionCount("tie", "to_iov");
// compose output
msg +=
`$(We) ${this.name}` +
`${diff <= 1 ? " the free" : " one"}` +
` end of ${rope.articlename}` +
`${targets[0] ? " to " + targets[0].articlename : ""}` +
`${
targets[1] ? ", and the other end to " + targets[1].articlename : ""
}`;
// no period yet because we don't end here...
// the following logic determines additional output
if (targets.length) {
for (let i = 0; i < targets.length; i++) {
// set verb connection
this.setVerbConnection(rope, targets[i]);
// If rope.dov.take.with_params.on_take_take_connections
// is true move takeable targets into player's inventory.
if (
rope.isWithin(player) &&
rope.dov[this.name].with_params.on_take_take_connections &&
!targets[i].isWithin(player) &&
targets[i].isDOV("take")
) {
results = player.onMoveThatToThis(targets[i], "in");
if ("undefined" !== typeof results) return results;
msg += ", and take " + targets[i].articlename;
}
}
}
// If tying rope to its max number of items, and
// the items are not in player's inventory, remove
// rope from inventory.
var tiedObjectsNotInInventory = 0;
var ropeIsTiedToMaxObjects =
rope.getVerbConnectionCount("tie", "to_iov") ===
rope.getVerbMaxConnections("tie", "dov");
if (ropeIsTiedToMaxObjects) {
let count = rope.getVerbConnectionCount("tie", "to_iov");
for (var i = 0; i < count; i++) {
var tiedObject = this.game.getAsset(
rope.getVerbConnections("tie", "to_iov")[i]
);
if (!tiedObject.isWithin(player)) {
tiedObjectsNotInInventory++;
}
}
}
var noTiedObjectsInInventory =
tiedObjectsNotInInventory === rope.getVerbMaxConnections("tie", "dov");
if (
rope.dov.tie.with_params.on_max_connections_drop_rope &&
rope.isWithin(player) &&
ropeIsTiedToMaxObjects &&
noTiedObjectsInInventory
) {
var currentRoom = this.game.getCurrentRoom();
results = player.onRemoveThatFromThis(rope);
if ("undefined" !== typeof results) return results;
// set thing's new location to player's location
results = currentRoom.onMoveThatToThis(rope, "in");
if ("undefined" !== typeof results) return results;
msg += ", and let go of " + rope.articlename;
}
msg += ". ";
if (
this.game.parser.isParsingMultiple() &&
-1 === input.output_class.indexOf("concatenate_output")
) {
input.output_class += " concatenate_output ";
}
this.handleSuccess(msg, rope);
return true;
},
tryToInferRope: function (targets) {
var result = { prompt: false, ropes: [] };
var player = this.game.getPlayer();
var ropes = player.findNestedAssetsWithClass("Rope");
// allowed to infer?
if (!this.game.settings.infer_objects) {
result.prompt = true;
}
if (!ropes || !ropes.length) {
// look for other carried objects that are dov tie
ropes = player.findNestedAssetsWithProperty("dov.tie");
}
// can this rope be tied to that object?
for (let i = ropes.length - 1; i > 0; i--) {
let rope = ropes[i];
if (rope.allowVerbWithNothing()) {
// it's a tieable non-rope, like a bowtie
ropes.splice(i, 1);
continue;
} else if (!rope.allowVerbWithAnything(this.name, "dov")) {
for (let t = 0; t < targets.length; t++) {
let target = targets[t];
if (!rope.allowVerbWithAsset("tie", target.id, "dov")) {
ropes.splice(i, 1);
continue;
}
}
}
}
// done everything we can
result.ropes = ropes;
// still no ropes?
if (!ropes || !ropes.length) {
result.prompt = true;
}
// more than one rope to choose from?
if (
ropes.length > 1 &&
!this.game.settings.infer_objects_automatically_picks
) {
result.prompt = true;
}
return result;
},
};
})(); // tie