[SOLVED] Find the Item Being Examined (Messing Around with EXAMINE (Trying to Learn JS))

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);
	},

The Output

NOTE
  • First I examine him, then I order another NPC to examine him.

image


TODO

  1. 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.)

  2. 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 . . .

image


Bah!

Well . . . Grep it is!

[[email protected] QuestJS_v03]$ grep -nr NpcExamine* *
[[email protected] QuestJS_v03]$ 

Huh?


image


[image
(https://user-images.githubusercontent.com/30656341/103139562-2b008500-46a3-11eb-83ec-4f4588db7c45.png)


Okay . . .

[[email protected]  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

A Mystery

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!


Click here to view this extremely long post.
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.

image


Again, that is after the command has finished running.

This is what is passed to the verb script, though:

image


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).

image


When I assume, though, it makes an ASS out of U and ME.

image


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:

image


...and this is parser.currentCommand.objects after the command has been run:
image


...and this is parser.currentCommand.matches after the command has been run:
image


parser.currentCommand.matches

An array of string arrays representing matches made by the parser.

> RP, X XAN

After the command has successfully executed:

image


X XM

After the command has successfully executed:

image


CONCLUSION

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?

image

  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});
	},

image


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums