// write.js
(function () {
/* global adventurejs A */
/**
* @augments {adventurejs.Verb}
* @class write
* @ajsnode game.dictionary.verbs.write
* @ajsconstruct MyGame.createVerb({ "name": "write", [...] });
* @ajsconstructedby adventurejs.Dictionary#createVerb
* @hideconstructor
* @ajsinstanceof Verb
* @ajsnavheading CompositionVerbs
* @summary Verb that means write, as in "write on blackboard".
* @tutorial Scripting_VerbSubscriptions
* @tutorial Verbs_VerbAnatomy
* @tutorial Verbs_VerbProcess
* @tutorial Verbs_ModifyVerbs
* @tutorial Verbs_WriteVerbs
* @ajsdemo WritingDemo, Office, Playroom, Classroom, Library, Scorecard
* @ajscss Styles
* @classdesc
* <pre class="display border outline">
* <span class="ajs-player-input">> write on blackboard with chalk</span>
* You scribble a hasty formula on the blackboard with the chalk.
* Hmm, no, that doesn't look right.
* </pre>
* <p>
* <strong>Write</strong> on a {@link adventurejs.Tangible|Tangible}
* {@link adventurejs.Asset|Asset}.
* Requires that the writing Asset has
* <code>asset.iov.write.enabled</code>
* set to true and that the target has
* <code>asset.dov.write.enabled</code>
* set to true.
* <strong>Write</strong> saves a record
* of things written on an Asset to its
* <a class="code" href="/doc/adventurejs.Tangible.html#property_written_strings">written_strings</a>
* property, which can be appended to the Asset's description.
* </p>
* @ajsverbreactions
* @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
*/
A.Preverbs.write = {
name: "write",
prettyname: "write on",
past_tense: "wrote",
synonyms: ["write"],
gerund: "writing",
allow_iov_on_iov: true,
// write on x
//verb_prep_noun: [ "write on" ],
// write on x with y
//verb_prep_noun_prep_noun: [ "write on with" ],
/**
* @ajsverbstructures
* @memberof write
*/
accepts_structures: [
"verb noun",
"verb preposition noun",
"verb noun preposition noun",
"verb preposition noun preposition noun",
"verb noun preposition noun preposition noun",
],
/**
* @memberof write
* @ajsverbphrase
* phrase1:
* {
* accepts_noun: true,
* requires_noun: true,
* noun_must_be:
* {
* known: true,
* present: true,
* visible: true,
* reachable: true,
* },
* accepts_preposition: true,
* preposition_must_be: ["on", "with"],
* },
*/
phrase1: {
accepts_noun: true,
requires_noun: true,
noun_must_be: {
known: true,
present: true,
visible: true,
reachable: true,
},
accepts_preposition: true,
preposition_must_be: ["on", "with"],
},
/**
* @memberof write
* @ajsverbphrase
* phrase2:
* {
* accepts_noun: true,
* requires_noun: true,
* noun_must_be:
* {
* known: true,
* present: true,
* visible: true,
* reachable: true,
* prefer_carried_if_ambiguous: true,
* },
* accepts_preposition: true,
* requires_preposition: true,
* preposition_must_be: ["on", "with"],
* },
*/
phrase2: {
accepts_noun: true,
requires_noun: true,
noun_must_be: {
known: true,
present: true,
visible: true,
reachable: true,
prefer_carried_if_ambiguous: true,
},
accepts_preposition: true,
requires_preposition: true,
preposition_must_be: ["in", "on", "with"],
},
/**
* @memberof write
* @ajsverbphrase
* phrase3:
* {
* accepts_noun: true,
* requires_noun: true,
* noun_must_be:
* {
* known: true,
* present: true,
* visible: true,
* reachable: true,
* prefer_carried_if_ambiguous: true,
* },
* accepts_preposition: true,
* requires_preposition: true,
* preposition_must_be: ["on", "with"],
* },
*/
phrase3: {
accepts_noun: true,
requires_noun: true,
noun_must_be: {
known: true,
present: true,
visible: true,
reachable: true,
prefer_carried_if_ambiguous: true,
},
accepts_preposition: true,
requires_preposition: true,
preposition_must_be: ["in", "on", "with"],
},
/**
* Write breaks our usual direct / indirect pattern
* because technically, the information being written is
* the direct object, even if no information is specified.
* So for example "write 'foo' on paper" treats 'foo', aka the
* information object, as the direct object, and the paper
* as the direct object. Whereas "write on paper", even though
* no information object has been provided, must infer one,
* leading to the same result.
*
* "write 'foo' on paper with pencil" treats both the paper
* and the pencil as indirect objects. So, though it may
* seem that "write on paper" should treat paper as the
* direct object, in fact it does not.
*
* Usually, we can use isDOV() and isIOV() to determine which
* asset acts on another - as with lock and key - but here,
* in order to distinguish direct from indirect object, we add
* a custom verb param to make the relationship explicit.
* @memberof write
* @ajsverbparams
* with_params: {
* // tool is a writing implement
* tool: false,
* // target is a writing surface
* target: false,
* },
*/
with_params: {
// tool is a writing implement
tool: false,
// target is a writing surface
target: false,
},
doTry: function () {
var input = this.game.getInput();
var subject = input.getSubject();
var msg = "";
var info_asset,
tool_asset,
tool_preposition,
tool_asset_inferred,
target_asset,
target_preposition;
var results;
/**
* Write doesn't follow our usual direct / indirect pattern.
* Player can input "write 'foo'" or "write 'foo' on board"
* or "write 'foo' on board with chalk"
* or "write with chalk" or "write on board"
* We need to do some extra work to figure out what's what
* and save the results in the verb_params object for doSuccess to use.
* All we're saving is the phrase number, not the actual phrase.
*/
let count = input.getPhraseCount();
for (let i = count; i > 0; i--) {
const asset = input.getAsset(i);
const preposition = input.getPreposition(i);
// is asset information?
if (asset?.is.information) {
if (info_asset) {
this.game.debug(
`D1221 | ${this.name}.js | can't handle multiple information assets`
);
msg += `{We} don't know how to ${input.input}. `;
this.handleFailure(msg);
return null;
}
// we've done minimal noun/preposition gates in phrases
// to allow for input flexibility, but information asset
// shouldn't have a preposition, so check that now
if (preposition) {
this.game.debug(
`D1093 | ${this.name}.js | no handling for "${preposition} ${asset.id}" `
);
msg += `{We} don't know how to ${this.name} ${preposition} ${asset.name}. `;
this.handleFailure(msg);
return false;
}
// need to expand this to handle abtractions
// like "type password" or "write name"
info_asset = input.verb_params.info_asset = asset;
continue;
}
// is asset a tool?
// if (preposition === "with") {
if (asset?.isIOV(this.name) && asset.iov[this.name].with_params.tool) {
if (tool_asset) {
this.game.debug(
`D1243 | ${this.name}.js | can't handle multiple tool assets`
);
msg += `{We} don't know how to ${input.input}. `;
this.handleFailure(msg);
return null;
}
if (preposition !== "with") {
this.game.debug(
`D1243 | ${this.name}.js | can't handle preposition`
);
msg += `{We} don't know how to ${this.name} ${preposition} ${asset.articlename}. `;
this.handleFailure(msg);
return null;
}
// it's our writing implement
tool_asset = input.verb_params.tool_asset = asset;
tool_preposition = input.verb_params.tool_preposition = preposition;
continue;
}
// is asset a target?
// else if (preposition === "on" || preposition === "in") {
if (
asset?.isIOV(this.name) &&
asset.iov[this.name].with_params.target
) {
if (target_asset) {
this.game.debug(
`D1301 | ${this.name}.js | can't handle multiple target assets`
);
msg += `{We} don't know how to ${input.input}. `;
this.handleFailure(msg);
return null;
}
if (!["in", "on"].includes(preposition)) {
this.game.debug(
`D1343 | ${this.name}.js | can't handle preposition`
);
msg += `{We} don't know how to ${this.name} ${preposition} ${asset.articlename}. `;
this.handleFailure(msg);
return null;
}
// it's our writing surface
// write on paper is different from write in book
// maybe need to add a "quirks.write_on_means_write_in" property?
target_asset = input.verb_params.target_asset = asset;
target_preposition = input.verb_params.target_preposition =
preposition;
continue;
}
// is asset none of these things?
this.game.debug(
`D1203 | ${this.name}.js | unusable asset ${asset.id} found`
);
msg += `{We} don't know how to ${this.name} ${preposition ? preposition : ""} ${asset ? asset.articlename : ""}. `;
this.handleFailure(msg);
return null;
}
// if we didn't receive one of these three things
// then we got nothin' to work with
if (!info_asset && !target_asset && !tool_asset) {
this.game.debug(`D1356 | ${this.name}.js | no relevant assets found`);
msg += `{We} don't know how to ${input.input}. `;
this.handleFailure(msg);
return null;
}
// if no info_asset we need to make one for consistency with dov/iov write
if (!info_asset) {
info_asset = input.verb_params.info_asset =
this.game.getAsset("global_string");
input.shiftPhrase({ asset: info_asset });
}
// @TODO how to limit input to alpha or numeric
if (!target_asset) {
var index = input.getPhraseCount() + 1;
input.setPreposition(index, "on");
input.setSoftPrompt({
index: index,
type: "noun",
[`noun${index}`]: true,
// [`preposition${index}`]: "on",
// verb: 'write'
});
this.game.debug(
`D1525 | ${this.name}.js | soft prompt for target asset `
);
msg += `What would {we} like to write on? `;
this.handleFailure(msg);
return null;
}
// is subject holding the writing implement?
// in most verbs would be handled by NounMustBe,
// but write's noun_must_be is a little more complicated
// because the order of words can vary
if (
tool_asset &&
!this.game.parser.selectInHands([tool_asset.id]).length
) {
this.game.debug(
`D1549 | ${this.name}.js | ${tool_asset.id}.place is not ${subject.id} `
);
msg += `{We're} not holding ${tool_asset.articlename}. `;
this.handleFailure(msg);
return null;
}
// can writing object write on surface object?
if (
tool_asset &&
!tool_asset.allowVerbWithAsset({
verb: this.name,
asset: target_asset,
ov: "iov",
}) &&
!tool_asset.allowVerbWithAnything(this.name, "iov")
) {
this.game.debug(
`D1517 | ${this.name}.js | neither ${tool_asset.id} nor ${target_asset.id} lists the other in dov/iov.${this.name}.with_assets or dov/iov.${this.name}.with_classes `
);
msg += `${target_asset.Articlename} doesn't present a good writing surface for ${tool_asset.articlename}. `;
this.handleFailure(msg);
return null;
}
// can we infer a writing object?
if (!tool_asset && this.game.settings.infer_objects) {
results = this.tryToInferIndirectObject({
direct_object: target_asset,
context: subject,
handle_input: false,
infer_first_use: true,
});
if (results.fail) {
/* do nothing */
} else if (results.prompt) {
/* do nothing here, we'll handle later */
} else if (results.success) {
// tool_asset = results.indirect_object;
tool_asset = input.verb_params.tool_asset = results.indirect_object;
tool_preposition = input.verb_params.tool_preposition = "with";
var index = input.pushPhrase({
asset: tool_asset,
preposition: "with",
});
tool_asset_inferred = true;
this.game.printInferred(`with ${tool_asset.articlename}`);
}
}
// no writing object after all
if (!tool_asset) {
var index = input.hasPhrase(2) ? 3 : 2;
// we're inferring an indirect object so restructure sentence for next turn
input.setSoftPrompt({
index: index,
type: "noun",
["noun" + index]: true,
structure: input.getStructure() + " preposition noun",
});
input.setPreposition(index, "with");
this.game.debug(`D1759 | ${this.name}.js | soft prompt for noun2 `);
msg += `What would {we} like to write with? `;
this.handleFailure(msg);
return false;
}
// reorder input if needed
// (1) info_asset (2) tool_asset (3) target_asset
let phrase_count = input.getPhraseCount();
let asset_in_info_slot = input.getAsset(1);
if (
info_asset &&
asset_in_info_slot &&
info_asset.id !== asset_in_info_slot.id
) {
// move info_asset to slot 1
let info_found_in_slot;
for (let i = 1; i <= phrase_count; i++) {
if (input.getAsset(i).id === info_asset.id) {
info_found_in_slot = i;
break;
}
}
input.swapPhrases(info_found_in_slot, 1);
}
let asset_in_target_slot = input.getAsset(2);
if (
target_asset &&
asset_in_target_slot &&
target_asset.id !== asset_in_target_slot.id
) {
// move target_asset to slot 2
let target_found_in_slot;
for (let i = 1; i <= phrase_count; i++) {
if (input.getAsset(i).id === target_asset.id) {
target_found_in_slot = i;
break;
}
}
if (target_found_in_slot) input.swapPhrases(target_found_in_slot, 2);
}
let asset_in_tool_slot = input.getAsset(phrase_count);
if (
tool_asset &&
asset_in_tool_slot &&
tool_asset.id !== asset_in_tool_slot.id
) {
// move tool_asset to last slot [phraseCount]
let tool_found_in_slot;
for (let i = 1; i <= phrase_count; i++) {
let asset = input.getAsset(i);
if (asset && asset.id === tool_asset.id) {
tool_found_in_slot = i;
break;
}
}
if (tool_found_in_slot)
input.swapPhrases(tool_found_in_slot, phrase_count);
}
return true;
},
doSuccess: function () {
var input = this.game.getInput();
var results;
var msg = "";
let {
info_asset,
info_preposition,
target_asset,
target_preposition,
tool_asset,
tool_preposition,
} = input.verb_params;
// if (
// input.verb_params.tool_phrase &&
// input.getInferred(input.verb_params.tool_phrase)
// ) {
// msg += `<span class='ajs-inferred'>(with ${tool_asset.articlename})</span>`;
// }
msg += "{We} write ";
if (target_asset) {
if (info_asset && (info_asset.value || info_asset.values[0])) {
let data = info_asset.value || info_asset.values[0] || "";
data.replace(/^"(.*)"$/, "$1");
msg += `<em class="string ${tool_asset.class.toLowerCase()} ${
this.game.settings.apply_color_classes_to_written_strings
? tool_asset.appearance.color
: ""
}">${data}</em> `;
target_asset.written_strings.push({
class: tool_asset.class,
erasable: tool_asset.is.erasable,
data,
color: tool_asset.appearance.color || "",
});
} else {
msg += " a few scribbles ";
target_asset.appearance.scribbles = true;
}
msg += target_asset.hasQuirk("write_on_means_write_in") ? "in " : "on ";
msg += target_asset.articlename;
} else {
msg += " a few scribbles in the air";
}
if (tool_asset) {
msg += " with " + tool_asset.articlename;
}
msg += ". ";
// --------------------------------------------------
// print output
// --------------------------------------------------
return this.handleSuccess(msg, target_asset);
},
};
})(); // write