This is a long post.
Click on this to view it:
This is XanMag's examine
property, and this code is in the createItem()
function:
examine:(...params)=>{
/* ^ spread operator */
// Print XM's description
msg(processText("{once:"+w.XanMag.examineFirst+"}{notOnce:"+w.XanMag.examineDefault+"}"));
// Call the function to list this item's contents (if any).
// - (Passing 'params' for no particular reason at this time, but it looks like I can \
// use this to customize the response based upon who is doing the examining (the player or an NPC).)
handleExamineHolder(params);
// Check out what 'params' actually is
log("params:", params);
// Destructure the 'params' array
let [ /*undefined*/, examiner, { match:examinee } ] = params;
/* ^ skip this , ^ define examiner, ^ define 'examinee' as the 'match' value */
// This works every time (so far).
log("examiner: ", examiner.name);
// When examined by an NPC, this is undefined because there is no 'match' key.
log("examinee: ", examinee);
// 'this' targets main window object, presumably because this function is within a function, \
// and it is being invoked (or called?) by another function (or other functions).
log("this:", this);
},
Figure out how to pass the object being examined to handleExamineHolder
in a way this can be used with clones. (This may need to be approached differently, as I have no clue how to pull it off.)
Research and learn how the parser works. E.g., why isn't there a match
key when an NPC is examining the item? I know that (somehow) this is being assigned a command then running that command's script:
const outcome = parser.currentCommand.cmd.script(parser.currentCommand.objects, parser.currentCommand.matches)
In this case, the command is either "Examine" or "NpcExamine". So, let's see what the latter's script is all about . . .
Bah!
Well . . . Grep it is!
[rh@yourmomshouse QuestJS_v03]$ grep -nr NpcExamine* *
[rh@yourmomshouse QuestJS_v03]$
Huh?
[
(https://user-images.githubusercontent.com/30656341/103139562-2b008500-46a3-11eb-83ec-4f4588db7c45.png)
Okay . . .
[rh@yourmomshouse QuestJS_v03]$ grep -nr Npc* *
grep: architecture.png: binary file matches
grep: game-alt-map/small_scale.xcf: binary file matches
grep: game-alt-map/Untitled.xcf: binary file matches
game-alt-map/code.js:38: for (let el of scopeAllNpcHere()) {
grep: game-alt-map/small_scale.png: binary file matches
grep: game-alt-map/map.png: binary file matches
game-banks/commands.js:119:/*commands.push(new Cmd('NpcPressurise1', {
game-banks/commands.js:136:commands.push(new Cmd('NpcPressurise2', {
game-banks/commands.js:153:commands.push(new Cmd('NpcDepressurise1', {
game-banks/commands.js:171:commands.push(new Cmd('NpcDepressurise2', {
game-banks/code.js:410:function reviveNpc(npc, object) {
grep: game-deeper/deeper-title.png: binary file matches
grep: game-eg/hrn06.wav: binary file matches
grep: game-map/paper.jpg: binary file matches
grep: images/spaceship.png: binary file matches
grep: images/icon_man_green.png: binary file matches
grep: images/icon-ice-shard.png: binary file matches
lang/lang-en.js:101: NpcStand:[/^(.+), ?(?:stand|stand up|get up)$/, /^tell (.+) to (?:stand|stand up|get up)$/],
lang/lang-en.js:103: NpcFillWith:[/^(.+), ?(?:fill) (.+) (?:with) (.+)$/, /^tell (.+) to (?:fill) (.+) (?:with) (.+)$/],
lang/lang-en.js:105: NpcPutIn:[/^(.+), ?(?:put|place|drop) (.+) (?:in to|into|in|on to|onto|on) (.+)$/, /^tell (.+) to (?:put|place|drop) (.+) (?:in to|into|in|on to|onto|on) (.+)$/],
lang/lang-en.js:107: NpcTakeOut:[/^(.+), ?(?:take|get|remove) (.+) (?:from|out of|out|off of|off) (.+)$/, /^tell (.+) to (?:take|get|remove) (.+) (?:from|out of|out|off of|off) (.+)$/],
lang/lang-en.js:109: NpcGiveTo:[/^(.+), ?(?:give) (.+) (?:to) (.+)$/, /^tell (.+) to ?(?:give) (.+) (?:to) (.+)$/],
lang/lang-en.js:112: NpcTieTo:[/^(.+), ?(?:tie|fasten|attach) (.+) (?:to) (.+)$/, /^tell (.+) to ?(?:tie|fasten|attach) (.+) (?:to) (.+)$/],
lang/lang-en.js:114: NpcUntie:[/^(.+), ?(?:untie|unfasten|detach) (.+)$/, /^tell (.+) to ?(?:untie|unfasten|detach) (.+)$/],
lang/lang-en.js:116: NpcUntieFrom:[/^(.+), ?(?:untie|unfasten|detach) (.+) (?:frm) (.+)$/, /^tell (.+) to ?(?:untie|unfasten|detach) (.+) (?:from) (.+)$/],
lang/lang-en.js:121: NpcPushExit:[
lib/_templates.js:382: return char === game.player ? 'Wear' : 'NpcWear'
lib/_templates.js:758: return char === game.player ? cmd : 'Npc' + cmd
lib/_commands.js:738: {scope:parser.isNpcAndHere},
lib/_commands.js:748: {scope:parser.isNpcAndHere},
lib/_commands.js:797: new Cmd('NpcStand', {
lib/_commands.js:819: new Cmd('NpcFillWith', {
lib/_commands.js:850: new Cmd('NpcPutIn', {
lib/_commands.js:855: {scope:parser.isHeldByNpc, multiple:true},
lib/_commands.js:880: new Cmd('NpcTakeOut', {
lib/_commands.js:906: return handleGiveToNpc(game.player, objects);
lib/_commands.js:909: new Cmd('NpcGiveTo', {
lib/_commands.js:914: {scope:parser.isHeldByNpc, multiple:true},
lib/_commands.js:924: return handleGiveToNpc(npc, objects);
lib/_commands.js:942: new Cmd('NpcPushExit', {
lib/_commands.js:975: new Cmd('NpcTieTo', {
lib/_commands.js:1003: new Cmd('NpcUntie', {
lib/_commands.js:1032: new Cmd('NpcUntieFrom', {
lib/_commands.js:1117: {scope:parser.isNpcAndHere},
lib/_commands.js:1131: {scope:parser.isNpcAndHere},
lib/_commands.js:1251: const npcCmd = commands.find(el => el.name === "Npc" + cmd.name + "2")
lib/_commands.js:1401:function handleGiveToNpc(char, objects) {
lib/_command.js:93:function NpcCmd(name, hash) {
lib/_command.js:176:function NpcExitCmd(name, dir, hash) {
lib/_command.js:237: forNpc:true,
lib/_command.js:240: const cmd = new NpcCmd("Npc" + el.name, data)
lib/_command.js:245: if (el.useThisScriptForNpcs) cmd.script = el.script
lib/_command.js:248: cmd.scope.push(el2 === parser.isHeld ? parser.isHeldByNpc : el2)
lib/_command.js:249: cmd.scope.push(el2 === parser.isWorn ? parser.isWornByNpc : el2)
lib/_command.js:268: commands.push(new NpcExitCmd("NpcGo" + sentenceCase(el.name) + "2", el.name, { regexes:regexes }))
lib/_command.js:277: if (cmd.forNpc) {
lib/_parser.js:553:parser.isHeldByNpc = function(item) {
lib/_parser.js:563:parser.isWornByNpc = function(item) {
lib/_parser.js:571:parser.isNpcOrHere = function(item) {
lib/_parser.js:574:parser.isNpcAndHere = function(item) {
lib/_util.js:658:function scopeNpcHere(ignoreDark) {
lib/_util.js:672:function scopeAllNpcHere(ignoreDark) {
grep: q6interface.png: binary file matches
Where is the "NpcExamine" code???
Where is the "NpcExamine" code???
initCommands
in _commands.js generates additional commands for any command that has "npcCmd" set to true
.
generates additional commands for any command that has "npcCmd" set
Ah! Thanks!
const outcome = parser.currentCommand.cmd.script(parser.currentCommand.objects, parser.currentCommand.matches)
Alright. Let's see if I'm understanding this line of code.
outcome
This will be whatever is returned by the command's script
.
This should be something like world.FAILED
, world.SUCCESS
, world.SUCCESS_NO_TURNSCRIPTS
, etc.
parser.currentCommand
This is a JS object representing the command the player just entered. (It contains lots of evolving data.)
parser.currentCommand.cmd
This is the command the parser matched to the player's input. In this case, this is "Examine" when the player examines XanMag (or "LookAt" if the command entered is LOOK AT XM rather than X XM), and it is "NpcExamine" when the player orders an NPC to examine XanMag (or "NpcLookAt" if the command entered is RP, LOOK AT XM rather than RP, X XM).
parser.currentCommand.cmd.script
This is the script
of cmd
, which is w.XanMag.examine
in this case.
It doesn't matter if the command entered is X XM, LOOK AT XM, RP, X XM, or RP, LOOK AT XM. This is the script that will run. (In Quest-ese, it's called a verb script.)
parser.currentCommand.objects
> RP, X XAN
This will end up being an array containing two objects after the command has successfully executed.
Again, that is after the command has finished running.
This is what is passed to the verb script, though:
I am (incorrectly) assuming that params[0]
and params[1]
are parser.currentCommand.objects
at this point in the parsing process.
I also (incorrectly) assume that params[2]
is parser.currentCommand.matches
.
I assume these things because in this example parser.currentCommand.cmd.script(parser.currentCommand.objects, parser.currentCommand.matches)
is the same thing as w.XanMag.examine(parser.currentCommand.objects, parser.currentCommand.matches)
.
I can run w.XanMag.examine(undefined,w.Ralph,{multi:undefined, match:undefined, verb:undefined})
from the browser's console, and it yields the same results in the game and in the console log as it does when I enter RP, X XM (except it doesn't echo the player's command, of course).
When I assume, though, it makes an ASS out of U and ME.
This proves that I am not fully grasping what is happening.
Anyway . . .
Alternatively, if this is the entered command:
> X XM
...the data is a little different. (This is how it should be, of course, since w.me
is now the examiner, but another difference is that match
is not undefined
when the player is the examiner. This is the bit that confuse me.)
This is what is passed to the verb script:
...and this is parser.currentCommand.objects
after the command has been run:
...and this is parser.currentCommand.matches
after the command has been run:
parser.currentCommand.matches
An array of string arrays representing matches made by the parser.
> RP, X XAN
After the command has successfully executed:
X XM
After the command has successfully executed:
I think I understand most of this process.
I think parser.currentCommand.objects
and parser.currentCommand.matches
might both get updated once after as soon as the parser calls the verb script, and that's why params
is different?
Another long post.
Click this to view it:
After hacking parser.execute
, I've decided that I must be missing a step in this process.
...or I'm simply not understanding something.
If the parameters parser.currentCommand.objects
and parser.currentCommand.matches
are passed to w.XanMag.examine
, why is params
different?
parser.execute = function() {
parser.inspect();
let inEndTurnFlag = false
try {
if (parser.currentCommand.objects.length > 0 && typeof parser.currentCommand.objects[0] === "object") {
for (let obj of parser.currentCommand.objects[0]) {
parser.pronouns[obj.pronouns.objective] = obj
}
}
log("parser.currentCommand.objects:", parser.currentCommand.objects);
log("parser.currentCommand.matches", parser.currentCommand.matches);
const outcome = parser.currentCommand.cmd.script(parser.currentCommand.objects, parser.currentCommand.matches)
inEndTurnFlag = true
world.endTurn(outcome)
} catch (err) {
if (inEndTurnFlag) {
console.error("Hit a coding error trying to process world.endTurn after that command.")
}
else {
console.error("Hit a coding error trying to process the command `" + parser.currentCommand.cmdString + "'.")
}
console.log('Look through the trace below to find the offending code. It is probably the first entry in the list, but may not be.')
console.log(err)
io.print({tag:'p', cssClass:"error", text:lang.error})
}
};
XanMag's examine
:
examine:(...params)=>{
msg(processText("{once:"+w.XanMag.examineFirst+"}{notOnce:"+w.XanMag.examineDefault+"}"));
handleExamineHolder(params);
let [ /*undefined*/, examiner, { match:examinee } ] = params;
log("params:", params);
log("parser.currentCommand.objects:", parser.currentCommand.objects);
log("parser.currentCommand.matches", parser.currentCommand.matches);
log("examiner: ", examiner.name);
log("examinee: ", examinee);
log("this:", this);
},
Let's delve deeper . . .
>> findCmd("Examine").script.toString()
function(objects, matches) {
let success = false;
let suppressEndturn = false
let verb
if (objects.length > 1) verb = objects.shift()
const multi = objects[0] && (objects[0].length > 1 || parser.currentCommand.all);
for (let i = 0; i < objects[0].length; i++) {
if (!objects[0][i][this.attName]) {
this.default(objects[0][i], multi, game.player);
}
else {
let result = this.processCommand(game.player, objects[0][i], multi, matches[0][i], verb);
if (result === world.SUCCESS_NO_TURNSCRIPTS) {
suppressEndturn = true;
result = true;
}
success = result || success;
}
}
if (success) {
return (this.noTurnscripts || suppressEndturn ? world.SUCCESS_NO_TURNSCRIPTS : world.SUCCESS);
}
else {
return world.FAILED;
}
}
O-ho! What's this?
I see a verb = objects.shift()
and a for (let i = 0; i < objects[0].length; i++) {
!
Okay.
Also . . .
>> findCmd("NpcExamine").script.toString()
function(objects) {
const npc = objects[0][0];
if (!npc.npc) {
failedmsg(lang.not_npc, {char:game.player, item:npc});
return world.FAILED;
}
let success = false, handled;
if (objects.length !== 2) {
errormsg("The command " + name + " is trying to use a facility for NPCs to do it, but there is no object list; this facility is only for commands in the form verb-object.");
return world.FAILED;
}
const multi = (objects[1].length > 1 || parser.currentCommand.all);
for (let obj of objects[1]) {
if (npc["getAgreement" + this.cmdCategory] && !npc["getAgreement" + this.cmdCategory](obj, this.name)) {
// The getAgreement should give the response
continue;
}
if (!npc["getAgreement" + this.cmdCategory] && npc.getAgreement && !npc.getAgreement(this.cmdCategory, obj)) {
continue;
}
if (!obj[this.attName]) {
this.default(obj, multi, npc);
}
else {
let result = this.processCommand(npc, obj, multi);
if (result === world.SUCCESS_NO_TURNSCRIPTS) {
result = true;
}
success = result || success;
}
}
if (success) {
npc.pause();
return (this.noTurnscripts ? world.SUCCESS_NO_TURNSCRIPTS : world.SUCCESS);
}
else {
return world.FAILED;
}
}
Okay. Now I see the lines of code which were eluding me.
The parser is passing parser.currentCommand.objects
and parser.currentCommand.matches
, but the command's script is dissecting that before passing its own arguments to the default script (or the verb script) on the item.
Got it!
function handleExamineHolder(params){
let s;
let {examiner, examinee: obj, cmdString } = params;
if (!obj) return;
if (!obj.container && !obj.npc) return;
if (obj.container) {
if (!obj.closed || obj.transparent) {
let contents = obj.getContents();
contents = contents.filter(o => !o.scenery)
if (contents.length <= 0){
return;
}
let pre = obj.contentsType === 'surface' ? lang.on_top : lang.inside;
pre = sentenceCase(pre);
let subjVerb = processText("{pv:pov:see}", {pov:game.player});
pre += `, ${subjVerb} `;
contents = settings.linksEnabled ? getContentsLink(obj) : contents;
s = `${pre}${contents}`;
}
} else {
let contents = getAllChildrenLinks(obj)
if (contents == 'nothing') return;
let pre = processText('{pv:char:be:true} ' + lang.carrying, {char:obj});
s = `${pre} ${contents}`;
}
s = examiner.npc ? getDisplayAliasLink(examiner,{capital:true, article:DEFINITE}) + ' examines ' + getDisplayAliasLink(obj, {article:DEFINITE}) + ' more closely, then continues. "' + s + '."' : s + '.';
msg(s);
}
XanMag (the pertinent bits):
createItem("XanMag", NPC(false), {
loc:"living_room",
examineFirst:"XanMag is a pretty normal fellow upon inspection. \
His shirt is old and adorned with two Bells - apparently a beer logo - \
and his jeans are old and snug around his frumpy waist. He is bouncing between \
activities as you would expect an over-caffeinated teenager with A.D.D. would despite \
the fact that he is not over-caffeinated, not a teenager, and has never been diagnosed \
as having attention deficit disorder. Currently he \
{random:is scouring through the forum posts:is fiddling with his iPhone"+
//":is taking a long 'sip' from his fancy beer."+
":is flipping between browser tabs.:is listening to music \
streaming out of his laptop:appears to be daydreaming}",
examineDefault:"XanMag is easily distracted by all the stimuli around him. \
Currently his focus is on \
{random:scouring through the forum posts:fiddling with his iPhone:flipping between browser tabs\
:listening to music streaming out of his laptop:the enthralling daydream he is having}",
examine:(...params)=>{
let cmd = parser.currentCommand;
let examinee = cmd.cmd.name.startsWith("Npc") ?cmd.objects[1][0] : cmd.objects[0][0];
let [ /*undefined*/, examiner ] = params;
let cmdString = cmd.cmdString;
let s = processText("{once:" + examinee.examineFirst + "}{notOnce:" + examinee.examineDefault + "}");
if (examiner.npc) {
let pre = processText("{nv:pov:examine}", {pov:examiner}) + ' ' + getDisplayAliasLink(examinee, {article:DEFINITE}) + '.';
msg(pre);
s = `"${s}," ` + processText("{nv:pov:say}.", {pov:examiner});
}
msg(s);
handleExamineHolder({examiner:examiner, examinee:examinee, cmdString:cmdString});
},