I'm running this script in the Firefox Web Developer Web Console.
(For those who are unfamiliar with this: I pushed <F12> then Ctrl+B with Firefox open, entered the code in the field on the left, then pressed Ctrl+ENTER to run the code.)
The GiveTo command only has one regular expression set up: /^(?:give) (.+) (?:to) (.+)$/
VIEW THE REFERENCED LINE OF CODE ON GITHUB
So, the player must enter: GIVE [object(s)] TO [npc].
I want to add this optional format (or syntax, or whatever): GIVE [npc] [object(s)].
Well . . .
Long story a little shorter, I had to learn how to use regular expression capture groups to pull this off.
clear()
var log = console.log
var regexes = [
/^(?:give) (?<obj>.+) (?:to) (?<npc>.+)$/,
/^(?:give) (?<npc>\w.?(?!= to)) (?<obj>.+)$/
]
var arrS = ["give the sword to rp","give rp the sword","give all to rp",
"give rp all", "give mask and hat to rp", "give rp mask and hat",
"give mask, hat, and sword to rp", "give rp mask, hat, and sword"]
var res = []
arrS.forEach((s,i) => {
log(`INPUT: ${s}`)
regexes.forEach((rgx,j) => {
log(`rgx[${j}]: ${rgx}`)
log(rgx.exec(s))
if(rgx.exec(s)) {
res.push(rgx.exec(s))
}
})
})
log(`==============RESULTS==============\nres:`)
log(res)
log(`======res breakdown=========`)
var obj,npc
res.forEach((cand,i) => {
log(`----res.${i}----------------\nINPUT: ${cand.input}`)
npc = cand.groups.npc
log(`npc: ${npc}`)
obj = cand.groups.obj
log(`obj: ${obj}`)
log(`----==end of res.${i}==-----`)
})
So, after lots of learning and coding, I made it work (I think).
Now, I only need to learn how to feed that to the QuestJS parser. The parser is not at all set up to work with regex capture groups.
So . . .
I wrote some (sloppy) code to modify "GiveTo".
//CHANGE GIVE TO FUNCTIONALITY (to add 'GIVE [npc] [object(s)]
findCmd("GiveTo").regexes = [/^(?:give) (?<obj>.+) (?:to) (?<npc>.+)$/,
/^(?:give) (?<npc>\w.?(?!= to)) (?<obj>.+)$/]
findCmd("GiveTo").multiple = true
findCmd("GiveTo").script = function(objects) {
//parser.debug = true
parser.msg("Running giveTo function.")
var objArr = []
findCmd("GiveTo").regexes.forEach(rgx => {
if (rgx.exec(parser.currentCommand.cmdString)) {
objArr = rgx.exec(parser.currentCommand.cmdString)
}
})
//game.objArr = objArr
var npc = objArr.groups.npc
if (parser.debug) {console.log("npc");console.log(npc)}
var obj = objArr.groups.obj
var isAll = false;
if (obj == "all") {
isAll = true;
if(parser.debug){console.log("Attempting ALL")}
obj = game.player.getContents()
if(parser.debug){console.log("Changing obj to game.player.getContents() . . . :");console.log(obj)}
}
//TODO: if object is multiple objects!!!
var newObj1Arr = []
var mult = false
var multObjs
if (!isAll && (obj.includes(', ')||obj.includes(' and '))){
mult = true
if(parser.debug){console.log("I'm running!")}
multObjs = obj.replace(/\, and /g, '|').replace(/ and /g, '|').replace(/\, /g, '|').split('|')
if(parser.debug){console.log(`multObjs`);console.log(multObjs)}
}
if(mult && multObjs.length>1) {
multObjs.forEach(o => {
if(parser.debug){console.log("Finding: "+o)}
var fnd = findObjByParser(o)
if(parser.debug){console.log(fnd)}
newObj1Arr.push(fnd)
})
obj = newObj1Arr
}
if(parser.debug){console.log("mult?");console.log(mult);console.log("npc");console.log(npc);console.log("objArr");console.log(objArr)}
objects[1][0] = findObjByParser(npc) //This should be the npc
if (!isAll && !mult) {
objects[0] = [findObjByParser(obj)] //This should be the item(s)(?) given
}else{
objects[0] = obj
}
if(parser.debug){console.log('objects[1][0] (the npc)');console.log(objects[1][0]);console.log('objects[0][0] (the item(s) given)');console.log(objects[0][0])}
return handleGiveToNpc(game.player, objects);
}
//END OF CHANGE GIVE TO FUNCTIONALITY
Now, this is where I'm hitting a snag.
I recently added an OOPS command, and I modified parser.parse
and parser.matchItemsToCmd
to do so. Then, The Pixie updated QuestJS with his own code to add OOPS (which I honestly haven't perused because I've been busy messing with this code).
So, I'm not even certain that I'm editing the same parser.matchItemsToCmd
which is now in the QuestJS code, but these are the two blocks of code from the current version of parser.matchItemsToCmd
that mess up my "GiveTo" hack:
Lines 295 - 299
https://github.com/ThePix/QuestJS/blob/master/lib/_parser.js#L295
if (list.length > 1 && !cmd.objects[i].multiple) {
res.error = no_multiples_msg;
res.score = -1;
return res;
}
...and lines 313 - 317:
https://github.com/ThePix/QuestJS/blob/master/lib/_parser.js#L313
if (objectNames.length > 1 && !cmd.objects[i].multiple) {
res.error = no_multiples_msg;
res.score = -1;
return res;
}
I cannot find any object with a multiple
attribute set on it. So, I don't understand why that code is there (which definitely doesn't mean that code serves no purpose (it may deal with containers or something)). I commented out both of those blocks of code, and my stuff (mostly) works now! (I just hope I didn't break something else in the game world!)
Here are some screenshots of the gameplay:
I just realized there are (at least) two issues whilst writing this.
I probably just need to change one bit of code to fix the comma issue, and there's probably a parser function that removes an object's articles (a, an, the, etc.) before trying to match the text to objects.
After a nap, I'll try to fix those two things and come back and edit the code.
As always, comments and suggestions are welcomed!
Happy gaming!
PS
I had to add something to this post to edit the title.
UPDATE
I solved the issue with the articles quite easily.
I mostly (technically) fixed the issue with commas, but I just realized I'm using the version of QuestJS that didn't have the Oxford comma coded. Therefore, I can do give this, that and the other to ralph
, but not give this, that, and the other to ralph
.
That Oxford comma has been indirectly kicking my butt for two hours. I just now planted enough logs to the console to figure out that it never makes it to my "GiveTo" script at all.
More on this as it comes in. Plus, I'll update the code.
For the record, this is for QuestJS v0.3.
It seems to be in working order now.
Adding this line of code (which familiarizes the parser with the Oxford comma) fixed my main issue:
lang.joiner_regex = /\b\, and\b|\,|\band\b/
I forgot to include this code in the original post, but the function needed to be updated (meaning fixed) anyway:
function findObjByParser(s){
console.log("Running findObjByParser")
console.log(s)
s = lang.article_filter_regex.exec(s)[1]
console.log(s)
var scr = parser.findInList(s,allObjects(),{})
if (scr.length>1) {
scr = findObjByParser(s,scr)
}
scr = scr[0]
console.log(scr)
return scr
}
My final (?) "GiveTo" script:
//CHANGE GIVE TO FUNCTIONALITY (to add 'GIVE [npc] [object(s)]
findCmd("GiveTo").regexes = [/^(?:give) (?<obj>.+) (?:to) (?<npc>.+)$/,
/^(?:give) (?<npc>\w.?(?!= to)) (?<obj>.+)$/]
//findCmd("GiveTo").multiple = true
findCmd("GiveTo").script = function(objects) {
//parser.debug = true
//console.log("Running giveTo function.")
var objArr = []
findCmd("GiveTo").regexes.forEach(rgx => {
if (rgx.exec(parser.currentCommand.cmdString)) {
objArr = rgx.exec(parser.currentCommand.cmdString)
}
})
//game.objArr = objArr
var npc = objArr.groups.npc
if (parser.debug) {console.log("npc");console.log(npc)}
var obj = objArr.groups.obj
if (parser.debug) {console.log("obj");console.log(obj)}
var isAll = false;
var newObj1Arr = []
var mult = false
var multObjs
if (obj == "all") {
isAll = true;
if(parser.debug){console.log("Attempting ALL")}
obj = game.player.getContents()
if(parser.debug){console.log("Changing obj to game.player.getContents() . . . :");console.log(obj)}
}else{
//console.log("Not ALL")
if (obj.includes(',')||obj.includes(' and ')){
mult = true
if(parser.debug){console.log("I'm running!")}
//console.log(obj)
multObjs = obj.replace(/\, and /g, '|').replace(/\, /g, '|').replace(/ and /g, '|').trim().split('|')
//console.log(multObjs)
multObjs.forEach((o,i) => {
multObjs[i] = o.trim()
})
if(parser.debug){console.log(`multObjs`);console.log(multObjs)}
}
if(mult && multObjs.length>1) {
multObjs.forEach(o => {
o = o.trim()
if(parser.debug){console.log("Finding: "+o)}
var fnd = findObjByParser(o)
if(parser.debug){console.log(fnd)}
newObj1Arr.push(fnd)
})
obj = newObj1Arr
}
}
if(parser.debug){console.log("mult?");console.log(mult);console.log("npc");console.log(npc);console.log("objArr");console.log(objArr)}
objects[1][0] = findObjByParser(npc) //This should be the npc
if (!isAll && !mult) {
objects[0] = [findObjByParser(obj)] //This should be the item(s)(?) given
}else{
objects[0] = obj
}
if(parser.debug){console.log('objects[1][0] (the npc)');console.log(objects[1][0]);console.log('objects[0][0] (the item(s) given)');console.log(objects[0][0])}
return handleGiveToNpc(game.player, objects);
}
//END OF CHANGE GIVE TO FUNCTIONALITY
Also, I still have to modify these IF blocks in parser.matchItemsToCmd
:
list = list.filter(function(el) { return !exclude.includes(el); });
//KV removed next block because this seems to want the NPC to have multiple setup by default?
//TODO maybe this affects CONTAINERS ???
//if (list.length > 1 && !cmd.objects[i].multiple) {
//console.log(list)
//console.log(cmd)
//console.log(cmd.objects[i]) //This is the npc.
//res.error = no_multiples_msg;
//res.score = -1;
//return res;
//}
score = 2;
res.objects.push(list.map(function(el) { return [el]; }));
res.matches.push(arr[i]);
res.all = true;
}
else {
objectNames = arr[i].split(lang.joiner_regex).map(function(el){ return el.trim() });
//KV removed next block because this seems to want the NPC to have multiple setup by default?
//TODO maybe this affects CONTAINERS ???
//if (objectNames.length > 1 && !cmd.objects[i].multiple) {
//console.log(cmd)
//console.log(cmd.objects[i]) //This is the npc
//res.error = no_multiples_msg;
//res.score = -1;
//return res;
//}
Here's the original code on lines 295 - 313: https://github.com/ThePix/QuestJS/blob/master/lib/_parser.js#L295
It throws an error when I "give ralph all", but not when I "give rp all" or "give all to ralph".
Still a mystery to me, and I haven't tested out other objects after changing that.
It's probably a setting on the Ralph object.
Here's Ralph:
createItem("Ralph", NPC(false), {
loc:"cellar",
examine:"Your trusty sidekick.",
regex:/^(ralph|rp)$/,
shadowingPlayer:true,
properName:true,
talkto:"Ralph waves you off.",
getAgreementGo:function(dir) {
msg("Ralph never voluntarily leaves your side in this game.");
return false;
},
hug:"\"Whoa, whoa, whoa,\" says Ralph. \"Social distancing! Remember?!?\"",
love:"This is not that kind of game!",
});
Will update after a break.
I have yet to read this fully, this is just some initial thoughts.
First, an excellent place to test regular expressions is this web page (ideally set to ECMAScript, though it does not make much difference):
https://regex101.com/
So, the player must enter: GIVE [object(s)] TO [npc].
I want to add this optional format (or syntax, or whatever): GIVE [npc] [object(s)].
The big problem is how the parser will decide where to break the text. Consider:
GIVE BIG MIKE HAT COAT
Do you want to give Mike, the hat and the coat to Big (which could be short for Big Jim)? Or give the hat and coat to Big Mike?
The parser is not at all set up to work with regex capture groups.
Not sure what that means. The parser does just that!
Then, The Pixie updated QuestJS with his own code to add OOPS (which I honestly haven't perused because I've been busy messing with this code).
Should not be an issue; I do not think I edited the parser itself, just added the command to _commands.js and modified _io.js. The only change to _parser.js in the last month was your fix for DROP ALL.
I cannot find any object with a multiple attribute set on it. So, I don't understand why that code is there (which definitely doesn't mean that code serves no purpose (it may deal with containers or something)).
There are commands with "multiple" set, indicating they can be used with multiple items. If you set your command to multiple:true
, that may fix it - though the parser may expect the first item to be multiple rather than the last. Thinking about it, that could be an issue in some languages.
I mostly (technically) fixed the issue with commas, but I just realized I'm using the version of QuestJS that didn't have the Oxford comma coded.
It is only coded in the output, i.e., formatList
, so not an issue. I am not sure how the input handles commas... I will have to check.
ETA: The commas in the input are handled with a regex called "joiner_regex", and the Oxford comma can be accomodated by changing that:
joiner_regex:/\band\b|\, ?and\b|\,/,
The commas in the input are handled with a regex called "joiner_regex", and the Oxford comma can be accomodated by changing that
I found that just before my last post last night. My regex works but I like your regex better.
I also found that I had already transferred my game into a QuestJS v0.3 folder. So, I did already have the updated formatList
. It took a while to find joiner_regex
, but changing that immediately solved that issue.
The big problem is how the parser will decide where to break the text. Consider:
GIVE BIG MIKE HAT COAT
Do you want to give Mike, the hat and the coat to Big (which could be short for Big Jim)? Or give the hat and coat to Big Mike?
Very good point. I had not considered object names with more than one word.
I need to test that with my current code.
The parser is not at all set up to work with regex capture groups.
Not sure what that means. The parser does just that!
Well, I'm probably not using the proper terminology.
I figured out how to add what I believe is called regex templates in Quest 5.
I changed the regex on GiveTo:
//CHANGE GIVE TO FUNCTIONALITY (to add 'GIVE [npc] [object(s)]
findCmd("GiveTo").regexes = [/^(?:give) (?<obj>.+) (?:to) (?<npc>.+)$/,
/^(?:give) (?<npc>\w.?(?!= to)) (?<obj>.+)$/]
And I easily pull the matched text for <obj>
and <npc>
like so:
findCmd("GiveTo").script = function(objects) {
var objArr = []
findCmd("GiveTo").regexes.forEach(rgx => {
if (rgx.exec(parser.currentCommand.cmdString)) {
objArr = rgx.exec(parser.currentCommand.cmdString)
}
})
var npc = objArr.groups.npc //This is the magic!
var obj = objArr.groups.obj //This is the magic!
//...the code continues...
Does the parser already check for anything like <object>
or <object1>
and <object2>
in the regex?
I (think I) have to reset objects[1][0]
(which is the npc in GiveTo) and objects[0]
manually after my script finds the JS objects that match <npc>
and <obj>
from my regex capture groups.
Right now, everything seems to work except the one way I enter GIVE RALPH ALL.
I get: I don't even know where to begin with that.
But everything else works: GIVE RP ALL or GIVE ALL TO RALPH or GIVE ALL TO RP.
For some reason, the parser doesn't like it when I say RALPH before I say ALL.
ACTUAL TEXT FROM THE GAME:
> give ralph cig
I don't even know where to begin with that.
> give rp cig
Done.
> give mask to rp
Done.
> give lighter to ralph
Done.
>
Like I say, I'm sure I've done something to the code that makes it ignore Ralph's actual name. I shall research that and return here.
Does the parser already check for anything like <object> or <object1> and <object2>in the regex?
It does... But not by name. JavaScript support for named capture groups is pretty recent - 2018, when I started this, and browser support was patching.
It assumes you capture groups are in order. What is passed to the command script is an array of objects in the order of the capture groups.
This would mean two commands for GIVE. But both could use the same function, so not much of a deal.
Does the parser already check for anything like <object> or <object1> and <object2>in the regex?
Yes, if I understand right. The parser will match what is thinks are suitable objects before passing the results to the command script.
I don't even know where to begin with that.
You can use the PARSER command to get some idea of how the parser is comparing different options for commands and then fir objects to work with them.
I am miscommunicating. I should be saying "named capture groups" rather than "capture groups".
As far as I can tell, nothing in the existing code is checking for named capture groups.
Oh, I see. You just said "not by name" because it's relatively new.
Hrmm. I wonder how hard it would be to have the actual parser try to match named groups and move on to do what is already scripted if none exist . . .
That site to which you linked to test regex is freakin' awesome!
You can use the PARSER command
I can't get that command to work. I've tried with the game set on "dev" and on "beta".
I have to manually do parser.debug = true
to get the parser messages.
Does it have to be all caps? (Or am I just being silly?) If it does need to be all caps, does this line in parser.js affect that?: let cmdString = inputText.toLowerCase().trim().replace(/\s+/g, ' ');
This would mean two commands for GIVE.
Hey . . .
I just looked, and that's how Inform 7 handles it, too. I added a regex to the GiveTo. Perhaps creating a GiveToToo with a separate regex (leaving GiveTo's regex alone) would do the trick.
That's a marvelous idea!
[MANIACAL LAUGH]
SIDETRACK
I can't find the PARSER command.
I listed out all the commands in the console, but I can't see it. (I also printed out the regexes, just to make sure I wasn't overlooking anything.
commands.forEach(n => {console.log(n.name)})
MetaHelp
MetaHint
MetaCredits
MetaDarkMode
MetaSilent
MetaWarnings
MetaSpoken
MetaIntro
MetaBrief
MetaTerse
MetaVerbose
MetaTranscript
MetaTranscriptOn
MetaTranscriptOff
MetaTranscriptClear
MetaTranscriptShow
MetaTranscriptShowWithOptions
MetaTranscriptToWalkthrough
MetaPlayerComment
MetaSave
MetaSaveGame
MetaLoad
MetaLoadGame
MetaDir
MetaDeleteGame
MetaUndo
MetaAgain
Look
Exits
Wait
TopicsNote
Inv
Map
Smell
Listen
PurchaseFromList
Examine
LookAt
LookOut
LookBehind
LookUnder
LookInside
Search
Take
Drop
Wear2
Wear
Remove
Remove2
Read
Eat
Purchase
Sell
Smash
SwitchOn
SwitchOn2
SwitchOff2
SwitchOff
Open
Close
Lock
Unlock
Push
Pull
Fill
Empty
SmellItem
ListenToItem
Eat
Drink
Ingest
SitOn
StandOn
ReclineOn
GetOff
Use
TalkTo
Topics
Say
Stand
NpcStand
FillWith
NpcFillWith
PutIn
NpcPutIn
TakeOut
NpcTakeOut
GiveTo
NpcGiveTo
PushExit
NpcPushExit
TieTo
NpcTieTo
Untie
NpcUntie
UntieFrom
NpcUntieFrom
UseWith
AskAbout
TellAbout
Oops
Love
Hug
Light
Enjoy
Smoke
MetaRestart
MetaQuit
NpcExamine
NpcLookAt
NpcLookOut
NpcLookBehind
NpcLookUnder
NpcLookInside
NpcSearch
NpcTake
NpcDrop
NpcWear2
NpcWear
NpcRemove
NpcRemove2
NpcRead
NpcEat
NpcPurchase
NpcSell
NpcSmash
NpcSwitchOn
NpcSwitchOn2
NpcSwitchOff2
NpcSwitchOff
NpcOpen
NpcClose
NpcLock
NpcUnlock
NpcPush
NpcPull
NpcFill
NpcEmpty
NpcSmellItem
NpcListenToItem
NpcEat
NpcDrink
NpcIngest
NpcSitOn
NpcStandOn
NpcReclineOn
NpcGetOff
NpcUse
GoNorthwest
NpcGoNorthwest2
GoNorth
NpcGoNorth2
GoNortheast
NpcGoNortheast2
GoIn
NpcGoIn2
GoUp
NpcGoUp2
GoWest
NpcGoWest2
GoEast
NpcGoEast2
GoOut
NpcGoOut2
GoDown
NpcGoDown2
GoSouthwest
NpcGoSouthwest2
GoSouth
NpcGoSouth2
GoSoutheast
NpcGoSoutheast2
It is a debugging command, so only present if:
settings.playMode = 'dev'
(EDIT: I see more posts while I was typing this; so it's probably redundant. But may as well share anyway)
(EDIT 3: changed handling of commas in the pseudocode so that objects with commas in their names don't cause issues)
/^(?:give) (?<npc>\w.?(?!= to)) (?<obj>.+)$/
So that matches (taking the parts one by one)…
/(?:give) /
- the literal word "give". Exactly the same behaviour as /give /
so not sure why the parentheses are there/(?<npc>\w.?(?!= to))/
- the named group npc
, which matches either one or two characters, the first of which must be a letter or underscore, which are not followed by the string = to
./ /
- a space (I'm including this as a separate part because it isn't captured)/(?<obj>.+)$/
- the named capture group obj
, which matches any one or more characters until the end of the stringI've only skimmed this post because there's so much of it. But do you really want to limit the npc name to 2 letters?
I suspect you want something more like:
/^give(?!.*\bto\b) (?<npc>\w+?) (?<obj>.+)$/i
which is slightly slower, and still forces the NPC name to be a single word, but allows it to be longer than 2 letters.
If you want the NPC name and the object to both have spaces in, you need a lexer rather than a pattern engine. (as in, you need to interpret the line in the context of the available objects). You'd probably want to match everything as a single string and then interpret it later.
An algorithm for this could be (pseudocode):
It basically parses a list of objects where the comma and/or "and" between objects is optional; by checking each word to see if it could be part of the name of the previous object, and assuming a new object otherwise. Then assume the first object is the NPC. Making a regex do this would require the regex to be modified based on the objects in scope, and would be horrific.
(EDIT2: fixed the random parser pseudocode so that it works for constructs and errors like "give statue to barry", "give ralph cheese and pickles", "give john smith bell book and candle", "give ham,eggs,key to cellar, and biscuits to mike", and "give dave the key to the cellar")
It is a debugging command, so only present if:
settings.playMode = 'dev'
I have it set that way. I still can't get it to work.
I'll try it in a new game (with none of my added code) later today.
settings.playMode
was set to 'play'
on one of the first few lines. I had other code in settings.setup
which called a function which sometimes set settings.playMode
to 'dev'
, but this was after the code already set it to 'play'
. I'm assuming the initial setting is what decides whether or not the PARSER command will exist in the commands
array. I've only skimmed this post because there's so much of it. But do you really want to limit the npc name to 2 letters?
Ha! No. That has to be most of my problem, too.
I'll read your post in full and try your code out later today.
Thanks, everyone!
Looking at the pseudocode again, it might be worth adding a line at the end of the outermost while loop:
if (current_word.match(/^(?:and|to)\W*$/i)) {words.unshift(current_word);}
for the edge case where you have an object named "key to cellar" and the player types "give key to dave".
This would mean that it doesn't accept "key to" as a shortened form of "key to cellar"; it matches "key" and uses the "to" to indicate which object is the NPC
(depending on the function you use for "does this object's name contain this word", the algorithm could easily be changed to allow, for example, the player entering the words of an object name in the wrong order (treating "book about snails" and "snail book" as equivalent); or to handle pronouns, articles, and other weirdnesses)
I'm pretty sure some variant of this could be included as a parser function, for cases where the arguments to a command could be treated as a list of objects; from which the first and/or last might be treated as a separate argument, allowing for both commas, words like "and", prepositions which indicate the variable of the following word ("to", "in", "with"), and work correctly with object names that contain those words.
Working out a data structure so that commands can indicate what parameters/prepositions they understand would be time consuming, but I think that it would be worthwhile, if only because plain regexes can't tell if words like "to" or "and" are part of an object name or not.
Heck, using this for a "put" command might mean that commands like:
==> give letter to gordon to man who is eager to punch someone
can be parsed correctly :p
I have to look at mrangel's code later. I'm sure it parses better then mine. For now, all I'm using of his is the regex, and it works well with my current code.
Here's what I've got now, and everything seems to work:
GiveNpcStuff
command ]commands.push(new Cmd('GiveNpcStuff', {
regex:/^give(?!.*\bto\b) (?<npc>\w+?) (?<obj>.+)$/i ,
rules:[cmdRules.canManipulate, cmdRules.isHeld],
objects:[
{scope:parser.isPresent, attName: "npc"}, //This one has to go first, or it throws the multiple error. I don't really know why.
{scope:parser.isHeld, multiple:true},
],
defmsg:(char,item)=>{return "Char: "+char+" / item: "+item},
script: function(objects) {
return handleGiveNpcStuff(objects)
},
default:function(item, isMultiple, char) {
if (typeof this.defmsg === "string") {
failedmsg(prefix(item, isMultiple) + this.defmsg, {char:char, item:item});
}
else if (typeof this.defmsg === "function") {
failedmsg(prefix(item, isMultiple) + this.defmsg(char, item), {char:char, item:item});
}
else {
errormsg("No default set for command '" + this.name + "'.");
}
return false;
},
}))
handleGiveNpcStuff
function ]function handleGiveNpcStuff(objects){
//parser.debug = true
if(parser.debug){
alert("RUNNING GiveNpcStuff function!")
console.log("Running GiveNpcSomething_RH function.")
}
var objArr = []
findCmd("GiveNpcStuff").regexes.forEach(rgx => {
if (rgx.exec(parser.currentCommand.cmdString)) {
objArr = rgx.exec(parser.currentCommand.cmdString)
}
})
var npc = objArr.groups.npc
if (parser.debug) {console.log("npc");console.log(npc)}
var obj = objArr.groups.obj
if (parser.debug) {console.log("obj");console.log(obj)}
var isAll = false;
var newObj1Arr = []
var mult = false
var multObjs
if (obj == "all") {
isAll = true;
if(parser.debug){console.log("Attempting ALL")}
obj = scopeHeldBy(game.player)
if(parser.debug){console.log("Changing obj to game.player.getContents() . . . :");console.log(obj)}
}else{
//console.log("Not ALL")
if (obj.includes(',')||obj.includes(' and ')){
mult = true
if(parser.debug){console.log("I'm running!")}
//console.log(obj)
multObjs = obj.replace(/\, and /g, '|').replace(/\, /g, '|').replace(/ and /g, '|').trim().split('|')
//console.log(multObjs)
multObjs.forEach((o,i) => {
multObjs[i] = o.trim()
})
if(parser.debug){console.log(`multObjs`);console.log(multObjs)}
}
if(mult && multObjs.length>1) {
multObjs.forEach(o => {
o = o.trim()
if(parser.debug){console.log("Finding: "+o)}
var fnd = findObjByParser(o)
if(parser.debug){console.log(fnd)}
newObj1Arr.push(fnd)
})
obj = newObj1Arr
}
}
if(parser.debug){console.log("mult?");console.log(mult);console.log("npc");console.log(npc);console.log("objArr");console.log(objArr)}
objects[1][0] = findObjByParser(npc) //This should be the npc
if (!isAll && !mult) {
objects[0] = [findObjByParser(obj)] //This should be the item given
}else{
objects[0] = obj //The items given have already been put into this list
}
if(parser.debug){console.log('objects[1][0] (the npc)');console.log(objects[1][0]);console.log('objects[0][0] (the item(s) given)');console.log(objects[0][0])}
return handleGiveToNpc(game.player, objects);
}
findObjByParser
function ]function findObjByParser(s){
if(parser.debug) {
console.log("Running findObjByParser")
console.log("DATA: "+s)
}
s = lang.article_filter_regex.exec(s)[1]
if(parser.debug){console.log("After article filter regexp exec:");console.log(s);}
var scr = parser.findInList(s,allObjects(),{})
if(parser.debug){console.log("scr: ");console.log(scr)}
if (scr.length>1) {
if(parser.debug){console.log("scr.length is > 1!");console.log(scr)}
scr = findObjByParser(s,scr)
}
scr = scr[0]
if(parser.debug) console.log(scr)
return scr
}
TRANSCRIPT
You can go up.
> give rp all
Cigarette: Done.
Purple lighter: Done.
Mask: Done.
> undo
Undoing...
The cellar
The cellar is small, dimly lit, and dingy.
You can see Ralph and a TV here.
You can go up.
> give all to rp
Cigarette: Done.
Purple lighter: Done.
Mask: Done.
> undo
Undoing...
The cellar
The cellar is small, dimly lit, and dingy.
You can see Ralph and a TV here.
You can go up.
> give mask, cig, and light to rp
Mask: Done.
Cigarette: Done.
Purple lighter: Done.
> undo
Undoing...
The cellar
The cellar is small, dimly lit, and dingy.
You can see Ralph and a TV here.
You can go up.
> give ralph mask, cig, and light
Mask: Done.
Cigarette: Done.
Purple lighter: Done.
> undo
Undoing...
The cellar
The cellar is small, dimly lit, and dingy.
You can see Ralph and a TV here.
You can go up.
> give all to ralph
Cigarette: Done.
Purple lighter: Done.
Mask: Done.
>
Here is a link to this "test" game's title page, if anyone feels like trying to break something:
PS
If anyone gives this a play, you could also test this out (just for kicks):
Turn on the TV in the first room. (If on a mobile browser, I believe you have to start the video manually, as autoplay usually won't work with the volume on.)
Leave it on and go UP. (The YouTube video should disappear and the volume should lower, as if the TV were a room away.)
Go UP once more and the TV's sound should disappear.
Go back DOWN, and the muffled sound should return.
Going DOWN once more brings you back to the first room, and the video should pop back up on full volume.
The PARSER command works. I had some invalid code.
Sorry, Pixie, if I made you waste time on that.
That is really cool.
Could you write up how you did the video in a form that can be added to the documentation? It is something that has been on the list of outstanding features for over year.
I would also be interested how you did the landing page.
I would also be interested how you did the landing page.
I just created a landing page ('index.html') and added it to my main directory.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta charset="UTF-8">
<title>Quest 6 Adventure 1 by Richard Headkid</title>
<style >
body{
background-color:lightgoldenrodyellow;
font-family: courier, monospace;
font-weight:bold;
line-height:16px;
font-size:16px;
}
#game-desc{
margin-right:15%;
margin-left:15%;
text-align: justify;
text-justify: inter-word;
border:5px solid black;
background-color:silver;
padding:12px;
}
button {
background-color: Green;
border: 2px solid black;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 22px;
font-family: courier, cursive;
font-weight:bold;
width: 34%;
}
button:hover {
background-color: blue;
color: white;
border: 2px solid green;
}
buttonalt {
background-color: green;
border: 2px solid black;
color: silver;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-family: 'Rye', cursive;
width: 33%;
}
buttonalt:hover {
background-color: white;
color: green;
border: 2px solid saddlebrown;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
</head>
<body>
<center id="thetopofthepage">
<!--<audio controls loop autoplay controlsList="nodownload">
<source src="" type="audio/ogg">
Your browser does not support the audio element.
</audio>-->
</center>
<!--<br /><br />
<img src="" id="base64image" style="width:100%;"><br /><br />-->
<center><h1>Quest 6 Adventure 1</h1><h3>by Richard Headkid</h3></center>
<hr>
<center><u><p id='game-desc-div'>
<big><b>GAME DESCRIPTION</b></big></u><br/>
<p id='game-desc'></p>
</p></center>
<hr>
<center><div class="button">
<button onclick="location.href='page.html'">Play Now!</button>
<!--<button onclick="location.href='src/hints.html'" target="_blank">Hints & Clues</button>-->
</div></center>
<center><div class="button">
<button onclick="location.href='quest6game1.zip'">Download the website (zip file)</button>
</div></center>
<hr />
<center>Comments or suggestions?</center><br />
<center>Please contact me at:<br /> <small><a href="http://textadventures.co.uk/user/view/gudejtsx9komagj-tffyfg/richard-headkid" target="_blank">RICHARD HEADKID @ TextAdventures </a></small></center>
<hr /><center><br /><br />
<p style="font-family: courier, monospace;font-weight:bold;color:black;line-height:18px; font-size:16px;" id="demo" onclick="myFunction()"><big><b><u><big>CREDITS</big></u></b></big></p>
</center>
<script>
var desc = "In this small text adventure, you and your trusty sidekick, Ralph, "+
"need to look around and interact with things (and XanMag) until you figure out what to do."+
"<br/><br/>It's pretty straightforward. It's really just to test out the new version of Quest."+
"<br/><br/><center>(Click on CREDITS at the bottom of this page for more info!)</center>";
$("#game-desc").html(desc);
function myFunction() {
var s = "<big><b>CREDITS</b></big><br /><br />"+
"The main game was created with <a href='https://github.com/ThePix/QuestJS/wiki' target='_blank'>"+
"Quest 6 AKA QuestJS</a>."+
//"<br /><br />The HINTS & CLUES menu was "+
//"created with <a href='https://twinery.org' target='_blank'>Twine</a>."+
"<br />I learned how to code this "+
"page by studying up at <a href='https://www.w3schools.com' target='_blank'>www.w3schools.com</a>."+
"<br /><br/>"+
"Don't forget those who make these games available to the public AT NO CHARGE:<br />"+
"<a href='http://textadventures.co.uk/' target='_blank'>TEXTADVENTURES.CO.UK</a><br />"+
"<a href='http://ifdb.tads.org/' target='_blank'>IFDB</a><br />"+
"<a href='http://ifarchive.org/' target='_blank'>IFArchive</a><br /><br />"+
"This game is a work of fiction.<br />Any names, characters, businesses, places, events,"+
" incidents, products, spells, weapons, talking animals, magikal things, and non-magikal things"+
" are purely fictitious. Any resemblance to actual persons, living or dead, or actual businesses,"+
" places, events, incidents, products, spells, weapons, talking animals, magikal things, or non-magikal"+
" things is purely coincidental.<br /><br />Ralph the penguin was created by an author friend of mine,"+
" and appears in this game with his consent.<br /><br />"+
"<strong>SPECIAL THANKS TO:</strong><br /><br />LUKE A. JONES, ANDREW PLOTKIN (AND ASSOCIATES),"+
" EMILY SHORT (AND ASSOCIATES), THE PIXIE, MRANGEL, XANMAG, AND EVERYONE AT TEXTADVENTURES.CO.UK "+
"(EXTRA SPECIAL THANKS TO LUIS FELIPE MORALES), CHRIS KLIMAS (AND ASSOCIATES), "+
"STEVE MERETZKY (AND ASSOCIATES, AND EVERYONE WHO EVER WORKED FOR INFOCOM), EVERYONE AT IFDB, "+
"EVERYONE AT IFARCHIVE (EXTRA SPECIAL THANKS TO DOUG),<br /><br />"+
"<big>AND AN ESPECIALLY BIG THANKS TO: PLAYERS LIKE YOU!</big></p>"+
"<a href='#thetopofthepage'><br />BACK TO TOP</a>";
$("#demo").html(s);
}
</script>
</body>
</html>
As far as I understand it, when we upload a ZIP file as a game, this site has default ways of choosing which page is the "home" page. First, it checks for 'index.html'. If that doesn't exist, it checks for 'play.html'. If neither exist, and there is only one HTML file, it just loads that page. If there are numerous HTML files in the main directory, and none are named "play" or "index", I have no clue how it works. (Alphabetical order maybe?)
That is really cool.
Well, thank ya'. Thank ya' very much!
EDIT
I started a new thread about YouTube API.
Could you write up how you did the video in a form that can be added to the documentation?
Certainly, but I'd better make sure my code doesn't need any updating before I do that. (It most assuredly does.)
Just to include all the YouTube stuff here (in case I lose it somehow), see this thread. (It's the same one to which I linked above.)
(If on a mobile browser, I believe you have to start the video manually, as autoplay usually won't work with the volume on.)
This isn't necessarily the case. If, rather than using autoplay, you use the API to start the video as a response to a click or keypress on the same page, it would be allowed.
On Quest 5.x, you couldn't autoplay videos because clicking a command link or typing a command has the sole result of sending a string to the server. When you execute a command, the stuff that comes up on screen is in response to a message back from the server, and responses to external events can't trigger autoplay.
But with a more JS-based engine, I think it should be possible to have the video load and initialise when you enter the room, and use the YouTupe API to press play and unhide it in response to the player clicking/typing "turn on tv". Not sure if some workaround would be necessary; but if the command script is called (through however many layers of function calls) from an 'click' or 'keyup' event within the same page as the video, it should work.
This isn't necessarily the case. If, rather than using autoplay, you use the API to start the video as a response to a click or keypress on the same page, it would be allowed.
That fits with what I found when looking at sound.
This isn't necessarily the case. If, rather than using autoplay, you use the API to start the video as a response to a click or keypress on the same page, it would be allowed.
Hrmm... I shall add the call to play the video the first time the TV is turned on. As it is now, it loads the video the first time, and that made it play on my PC. So, I never coded it to play in that code block. After testing on my Android, I just assumed it wouldn't autoplay due to the mobile restrictions. But, like I say, I'm going to add the call to play after loading it initially and see how that works out on mobile.
Will update.
Nope. (Unless it's operator error on my behalf.)
Even having the API call the play function doesn't automatically play it in my Andoid's Chrome browser.
...nor does it work in its Firefox browser.
I have to manually click on it to start the video before the rest of the YouTube functions work. (The phone is a Galaxy S6, so it is rather old.)
Not sure if there could be some indirection somewhere between the click handler and the actual function.
Or if loading the video (but having it hidden) when the room is first entered could make a difference; so that the video is already in existence when the click event is delivered.
I heard that some ads are using a workaround where they have autoplay but are muted (which is allowed for some reason), and then using a timeout to turn the volume up after a second. But I wouldn't try that, as it's likely to be fixed soon if it hasn't already.
UPDATE
I got sidetracked with YouTube.
I removed my changes to the GiveTo command, and I created a GiveNpcStuff command with this regex:
(?!.* to )^(?:give) (?<npc>.+?) (?<obj>.+)$
The (?!.* to )^
tells it NOT to match any string that includes the word "to". (I call this overkill, as it seemed to work already with mrangel's code. I'm just trying to learn stuff. #KnowingIsHalfTheBattle)
Not sure if there could be some indirection somewhere between the click handler and the actual function.
I just added the call to play the video to onPlayerReady
function. Now, I have to upload the game again and check with my phone to see if it makes the video start by itself.
More on this as it comes in . . .
PS
(I might create a new thread for this YouTube API stuff. The regex is crazy enough on its own.)
EDIT
http://textadventures.co.uk/forum/questkit/topic/vqas_xivue_l3jugeo7t5a/quest-6-adding-youtube-api#a6228584-2e1c-498e-8ffb-74fd74d17929
Back to RegExp name capture groups . . .
I broke out my Windows 7 machine. When I tried GIVE RALPH MASK, it got an error. The game still did everything else, even after that, but it definitely didn't work. If this were an important thing to do to win the game, that would be bad.
But Firefox constantly badgered me to update. So, I don't know. After updating Firefox, it worked fine in Windows 7. I guess if someone has a machine that's older than Windows 7, they couldn't update to a recent browser version maybe?
I don't know . . .
Either way, Quest 6 is Pixie's baby. If Pixie decides name capture groups are no bueno, then I shall post code without them.
giveNpcStuff
Pattern: GIVE [npc] [objects]
commands.push(new Cmd('GiveNpcStuff', {
regex:/(?!.* to )^give (\w+?) (.+)$/i,
rules:[cmdRules.canManipulate, cmdRules.isHeld],
objects:[
{scope:parser.isPresent, attName: "npc"}, //This one has to go first, or it throws the multiple error. I don't really know why.
{scope:parser.isHeld, multiple:true},
],
defmsg:(char,item)=>{return "Char: "+char+" / item: "+item},
script: function(objects) {
return handleGiveNpcStuff(game.player, objects)
},
default:function(item, isMultiple, char) {
if (typeof this.defmsg === "string") {
failedmsg(prefix(item, isMultiple) + this.defmsg, {char:char, item:item});
}
else if (typeof this.defmsg === "function") {
failedmsg(prefix(item, isMultiple) + this.defmsg(char, item), {char:char, item:item});
}
else {
errormsg("No default set for command '" + this.name + "'.");
}
return false;
},
}))
function handleGiveNpcStuff(char, objects) {
let success = false;
const npc = objects[0][0];
const multiple = objects[1].length > 1 || parser.currentCommand.all;
if (!npc.npc && npc !== game.player) {
failedmsg(lang.not_npc_for_give, {char:char, item:npc});
return world.FAILED;
}
for (let obj of objects[1]) {
let flag = true;
if (!char.getAgreement("Give", obj)) {
// The getAgreement should give the response
}
if (npc.testRestrictions) {
flag = npc.testRestrictions(obj);
}
if (!npc.canManipulate(obj, "give")) {
return world.FAILED;
}
if (flag) {
if (!obj.isAtLoc(char.name)) {
failedmsg(prefix(obj, multiple) + lang.not_carrying, {char:char, item:obj});
}
else {
if (npc.giveReaction) {
npc.giveReaction(obj, multiple, char);
}
else {
msg(prefix(obj, multiple) + lang.done_msg);
obj.moveToFrom(npc.name, char.name);
}
success = true;
}
}
}
if (success === world.SUCCESS) char.pause();
return success ? world.SUCCESS : world.FAILED;
}
NOTE
I combined mrangel's regex with mine, and this works perfectly (as far as I've tested it).
See here:
https://github.com/ThePix/QuestJS/wiki/Implementation-notes#no-named-capture-groups
If I was doing this in 2022, it would be different, and it might be worthwhile changing at some point.
If I was doing this in 2022, it would be different
I wasn't trying to be negative (if it seemed that way).
it might be worthwhile changing at some point.
Maybe, but rewriting all that parser stuff sounds like an awful lot of work. If I got this pattern to work without the named capture groups, I bet most other patterns can be matched without it, too.
All in all, I'm having a marvelous time learning all this JS.
And I get to help some people out along the way?
Oh yeah. This is fun.
I didn't realise support was so recent. Javascript regular expressions must have lagged a bit behind other languages.
I remember the first time I was working on a project and got told not to use named groups because not everyone had upgraded to Perl 5.10 yet; that was in 2008.