Should "it" be the last object mentioned or the last object(s) you interacted with?
> open door
You open it.
> enter it
You are on your front porch.
You can see a newspaper.
> get it
You can't take it.
> x it
Nothing out of the ordinary.
*NOTE: Found a bug!!! Can't take the newspaper, and it has no description?!? Watch. I'll try again.
> get newspaper
You pick it up.
> x it
The big story on the front page is titled How Pronouns Work in Interactive Fiction.
*NOTE: I have no clue why it worked the second time.
PS
I know how Quest works as far as "it" is concerned. That was just a hypothetical transcript.
I'm wondering if everyone thinks the way it works is the best way.
I would say it should be the last object mentioned should be it, except when it's the player (unless otherwise specified).
I agree.
Well, I think it needs to be the last object mentioned or the last object interacted with, whichever came last.
The game has a list attribute called lastobjects
. When you interact with something, it's added to the list. The last thing on the list is "it". (A list item may contain two objects, in a scenario such as 'put egg in basket.')
Also, I just noticed that checking inventory clears the list.
Anyway...
I wonder if it would hurt anything to make ObjectLink
add the object to lastobjects
.
These changes seem to make it behave as I would expect (as long as hyperlinks are enabled):
<function name="GetDisplayNameLink" parameters="obj, type" type="string"><![CDATA[
verbs = GetDisplayVerbs(obj)
if (verbs <> null) {
verbCount = ListCount(verbs)
}
else {
verbCount = 0
}
if (type = "exit" and verbCount = 1) {
if (not game.enablehyperlinks) {
result = GetDisplayAlias(obj)
}
else {
result = "{exit:" + obj.name + "}"
}
}
else if (type = "") {
result = GetDisplayAlias(obj)
if (not HasAttribute(game,"lastobjects")) {
game.lastobjects = NewObjectList()
}
if (not obj = game.pov.parent) {
list add (game.lastobjects, obj)
}
}
else {
result = "{object:" + obj.name + "}"
if (not HasAttribute(game,"lastobjects")) {
game.lastobjects = NewObjectList()
}
list add (game.lastobjects, obj)
}
if (not GetBoolean(obj, "usedefaultprefix")) {
if (obj.prefix = null) {
prefix = ""
}
else {
prefix = obj.prefix
}
}
else if (type = "exit") {
prefix = ""
}
else {
prefix = GetDefaultPrefix(obj)
}
if (LengthOf(prefix) > 0) {
prefix = prefix + " "
}
result = prefix + result
if (not GetBoolean(obj, "usedefaultprefix") and HasString(obj, "suffix")) {
if (LengthOf(obj.suffix) > 0) {
result = result + " " + obj.suffix
}
}
return (result)
]]></function>
<function name="ObjectLink" parameters="obj" type="string">
game.lastobjects = NewObjectList()
list add (game.lastobjects, obj)
return ("{object:" + obj.name + "}")
</function>
HEAD KNIGHT: He said the word!
ARTHUR: Surely you've not given up your quest for the Holy Grail?
MINSTREL (singing): He is sneaking away and buggering off...
ROBIN: Shut up! No, no no-- far from it.
HEAD KNIGHT: He said the word again!
ROBIN: I was looking for it.
KNIGHTS: Aaaaugh!
ROBIN: Uh, here, here in this forest.
ARTHUR: No, it is far from...
KNIGHTS: Aaaaugh!
HEAD KNIGHT: Aaaaugh! Stop saying the word!
ARTHUR: Oh, stop it!
KNIGHTS: Aaaaugh!
HEAD KNIGHT: Oh! He said it again!
ARTHUR: Patsy!
HEAD KNIGHT: Wait! I said it! I said it! Ooh! I said it again!
KNIGHTS: Aaaaugh!
You are in a room.
You can see an apple.
You can go south.
>eat it
You eat it.
> eat it
I can't see that. (it)
If there is no object in the room, Quest interprets "it" as an object
If there is no object in the room, Quest interprets "it" as an object
That would give some bizarre behaviour if you did have an object called "it"
Let's compare apples to oranges (using no modified code).
<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="it">
<gameid>e5a6d9d3-0847-4c28-bb20-9444bd8dcf75</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
</game>
<object name="room">
<inherit name="editor_room" />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="orange">
<inherit name="editor_object" />
<inherit name="edible" />
<take />
</object>
</object>
</asl>
Three scenarios:
Let's add another room which contains an apple, just for fun:
<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="it">
<gameid>e5a6d9d3-0847-4c28-bb20-9444bd8dcf75</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
</game>
<object name="room">
<inherit name="editor_room" />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="orange">
<inherit name="editor_object" />
<inherit name="edible" />
<take />
</object>
<exit alias="north" to="second room">
<inherit name="northdirection" />
</exit>
</object>
<object name="second room">
<inherit name="editor_room" />
<exit alias="south" to="room">
<inherit name="southdirection" />
</exit>
<object name="apple">
<inherit name="editor_object" />
<inherit name="edible" />
<take />
</object>
</object>
</asl>
I just noticed that an object is destroyed (not removed) once eaten, hence clearing the list: game.lastobjects
.
It makes sense to me that Quest can't see "it" after you eat something. The object was, after all, the last object mentioned as well as the last object with which the player interacted. In real life, if I told you to eat an apple, then told you to examine it, you'd probably say something along the lines of, "I can't see that."
==> Open drawer
It contains a spanner, a newspaper, a screwdriver, a ball of twine, and a leaking pen.
==> Close it
The pen is already closed.
I think that if the drawer contained exactly one object, it would be logical to assume "it" means the item. More than one, it's probably the drawer. But either could be ambiguous.
I'd think that "it" could mean either an object mentioned in the last command response, or the last object I interacted with. Possibly both should be retained; and the parser can assume that if one of them has gone out of scope, I meant the other. Or if I'm using a command which corresponds to a verb, see if only one of the available objects has that verb. Or if neither of those is true, try a disambiguation menu.
I wonder if the script I wrote last night handles that as you describe...
Yep! (It pretty much does, anyway.)
<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="it">
<gameid>e5a6d9d3-0847-4c28-bb20-9444bd8dcf75</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
</game>
<object name="room">
<inherit name="editor_room" />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="orange">
<inherit name="editor_object" />
<inherit name="edible" />
<take />
</object>
<exit alias="north" to="second room">
<inherit name="northdirection" />
</exit>
</object>
<object name="second room">
<inherit name="editor_room" />
<exit alias="south" to="room">
<inherit name="southdirection" />
</exit>
<object name="apple">
<inherit name="editor_object" />
<inherit name="edible" />
<take />
</object>
<object name="drawer">
<inherit name="editor_object" />
<inherit name="container_closed" />
<feature_container />
<listchildren />
<look><![CDATA[It is {either drawer.isopen:open|closed}.<br/>]]></look>
<object name="spanner">
<inherit name="editor_object" />
<take />
</object>
<object name="newspaper">
<inherit name="editor_object" />
<take />
</object>
<object name="screwdriver">
<inherit name="editor_object" />
<take />
</object>
<object name="ball of twine">
<inherit name="editor_object" />
<take />
</object>
<object name="leaking pen">
<inherit name="editor_object" />
<take />
</object>
</object>
</object>
<function name="GetDisplayNameLink" parameters="obj, type" type="string"><![CDATA[
verbs = GetDisplayVerbs(obj)
if (verbs <> null) {
verbCount = ListCount(verbs)
}
else {
verbCount = 0
}
if (type = "exit" and verbCount = 1) {
if (not game.enablehyperlinks) {
result = GetDisplayAlias(obj)
}
else {
result = "{exit:" + obj.name + "}"
}
}
else if (type = "") {
result = GetDisplayAlias(obj)
if (not HasAttribute(game,"lastobjects")) {
game.lastobjects = NewObjectList()
}
if (not obj = game.pov.parent) {
list add (game.lastobjects, obj)
}
}
else {
result = "{object:" + obj.name + "}"
if (not HasAttribute(game,"lastobjects")) {
game.lastobjects = NewObjectList()
}
list add (game.lastobjects, obj)
}
if (not GetBoolean(obj, "usedefaultprefix")) {
if (obj.prefix = null) {
prefix = ""
}
else {
prefix = obj.prefix
}
}
else if (type = "exit") {
prefix = ""
}
else {
prefix = GetDefaultPrefix(obj)
}
if (LengthOf(prefix) > 0) {
prefix = prefix + " "
}
result = prefix + result
if (not GetBoolean(obj, "usedefaultprefix") and HasString(obj, "suffix")) {
if (LengthOf(obj.suffix) > 0) {
result = result + " " + obj.suffix
}
}
return (result)
]]></function>
<function name="ObjectLink" parameters="obj" type="string">
game.lastobjects = NewObjectList()
list add (game.lastobjects, obj)
return ("{object:" + obj.name + "}")
</function>
</asl>
I hate bringing up Inform, but they have a neat little trick: Does the player mean
.
Let's say we have a read command (or verb) set up, which we do.
The default response for READ OBJECT is "Trying to read "+GetDisplayName(this)+" would accomplish nothing."
If we only had one object which the player could actually read in the game, we could add this to the game's code:
Does the player mean reading the newspaper when the newspaper is carried by the player or the newspaper is in the location of the player: it is very likely.
Then, we could just enter READ.
If we wanted something to be held before being allowing the player to read it, well... here's an example Inform game:
"Newspaper" by KV
The Quest Forum is a room.
The newspaper is in the forum. Understand "news/paper" as newspaper.
Reading is an action applying to one thing. Understand "read [something preferably held]" as reading.
Carry out reading something:
say "Trying to read [the noun] would accomplish nothing.[paragraph break]".
The stick is in the forum.
Does the player mean reading the newspaper when the newspaper is carried by the player or the newspaper is in the location of the player: it is very likely.
Before trying reading something when the noun is not carried by the player:
say "(first taking [the noun])";
try silently taking the noun;
if the noun is not carried by the player, stop the action.
Instead of reading the newspaper, say "You peruse the paper. A story about scope catches your eye. Very interesting.[paragraph break]".
Note that this is not as good as the code I posted here for Quest, because it assumes "it" is the last object mentioned, where my Quest code would ask if you meant the stick or the newspaper.
Yes KV, this is another dark corner of Quest that it would be useful to tidy up. Presumably any satisfactory solution to 'it' will also take account of possible references to 'him', 'her' and 'them' in the same location?
Presumably any satisfactory solution to 'it' will also take account of possible references to 'him', 'her' and 'them' in the same location?
I've been assuming this is the case, but I've never actually thought to test it.
If so, I wonder if it would be like:
> x John
He is looking right back at you.
> hit it
I can't see that. (it)
> hit him
You punch John in the face.
UPDATE
Yep:
...also what about scenery objects? Presumably 'the room' can also be referred to as 'it'?
I actually prefer it not allowing me to refer to John as "it", but I'm admittedly crazy.
Scenery objects just don't print in the room description. Besides that, they are the same as non-scenery objects.
(Also note that taking an object that is scenery sets its scenery
attribute to false, which I believe to be a good thing.)
You can't really refer to the parent object (the room the player is in). It isn't in scope unless you do some extra scripting (or actually add an object called "room", which I just realized is probably what you meant).
I actually prefer it not allowing me to refer to John as "it", but I'm admittedly crazy.
I assumed 'take it' would select the orange?
Scenery objects just don't print in the room description. Besides that, they are the same as non-scenery objects.
Depending on how a game is presented, players may not be able to distinguish between scenery and non-scenery objects so perhaps both need to be treated in the same way? e.g.
You enter a room that is full of dust.
x dust
Just what you would expect.
take it
Aitchoo!
...
I think the logic I'd use is to examine several lists:
If any of those lists (filtered by gender and being still in scope) has exactly one member, use that.
Otherwise (for verb-flavoured commands) if one of those lists has exactly one member that has the right verb, use that.
Otherwise display a disambiguation menu.
… but it's a pretty tough problem to figure out what a person is likely to have meant. I wouldn't be surprised if it varies with language, too. Is "When I nod my head, hit it" still funny in other languages? Or do other languages have pronouns that resolve the ambiguity somehow? (I'm reminded of a couple of double-negative ambiguities that I'm told can't be translated into Irish)
I assumed 'take it' would select the orange?
John was the last object mentioned and interacted with in this case.
Depending on how a game is presented, players may not be able to distinguish between scenery and non-scenery objects so perhaps both need to be treated in the same way?
They shouldn't even think about distinguishing. Scenery should be expected to there, thus there is no reason to mention it outright. And Quest already does treat both in the same way, except for in the room description.
When in an object is scenery
, that only means Quest ignores it when printing the room description. It's just scenery. It isn't mentioned outright by the game. It doesn't appear in the pane. Everything else works as it normally would.
Scenery can be interacted with. That is its purpose. It's not mentioned, but it's there if you try to interact with it.
EDIT
It appears that TAKE ALL ignores scenery.
Who in a TA says ‘take it’? If someone types that, I would consider making a global command that responds to ‘take it’ with ‘I know this is 2018, but this is a text adventure and OLD SCHOOL rules. Please indicate what object you’d like to take. For example, ‘take a time machine and travel back to 1985, you filthy millenial.’
There do seem to be issues with the 'it/him/her/them' facility in Quest. Perhaps if we can spell them out precisely, solutions will be more obvious? The first step is probably to define the current facility. Here is my attempt:
During game play, the last object referenced by the player is remembered, and can be referred to as 'it', 'him', 'her' or 'them' as appropriate. On entry to a location this reference is 'undefined'. It also becomes undefined after any command that doesn't involve an object, such as 'help' of 'z'. Any reference to the player ('me' or 'myself') is not remembered.
The main issues seem to be:
1: the error message given when the last reference is undefined is misleading
Currently this is "I can't see that". Would "Sorry, what do you mean?" be better?
2: why treat the player differently from other objects?
3: why forget the last reference when commands such as 'help' are used?
I have seen games where objects change over time, meaning the sequence "x object; z; x it" might be used.
There also seems to be an opportunity to remember objects of different type separately. i.e. last 'it', last 'him', last 'her' and last 'them' but that is probably not that helpful!
why treat the player differently from other objects?
Because the first person pronoun is "me", not "it" or "him".
There also seems to be an opportunity to remember objects of different type separately. i.e. last 'it', last 'him', last 'her' and last 'them' but that is probably not that helpful!
The algorithm I suggested above would keep a list, and filter it by the appropriate pronoun when needed.
Also, I would suggest filtering the list by objects currently in scope when a command is used, rather than clearing it when entering a room. Because commands can have a scope that doesn't necessarily mean the room. If, for example, your last command was follow John
, then talk to him
should be parsed even though you're now in a different room.
Currently this is "I can't see that". Would "Sorry, what do you mean?" be better?
"I can't see that."
This pretty much handles all situations sensibly.
Let's say we had an apple, then ate it, then entered X APPLE.
"I can't see that," makes sense.
"Sorry, what do you mean?" kind of makes sense, but "I can't see that," covers all the bases without being misleading (most of the time).
This is easy to change.
Just modify your UnresolvedObject
template.
On entry to a location this reference is 'undefined'. It also becomes undefined after any command that doesn't involve an object,
I'm pretty sure game.lastobjects
is reset every time a command is successful, but the code I posted fixes this. "It" will be the last object mentioned or interacted with, whichever comes last.
Also, I would suggest filtering the list by objects currently in scope when a command is used, rather than clearing it when entering a room. Because commands can have a scope that doesn't necessarily mean the room.
mrangel already has the best solution worked out in his head. I'm considering this one solved with a pending solution.
Personally, I think "it" should refer to the last thing the player refered to.
I would be interested, KV, what Hitchhikers does if you type:
X GOWN
LOOK
GET IT
Do you pick up the gown or the toothbrush?
A problem with doing it from the last described is that that may be difficult to work out. Quest allows the object list to be before the room derscription, and there may be no objects listed, so should the player expect "it" to refer to the last object in the room description? Or even in an item description? I note that Hitchhikers does not consider "it" to refer to the collar of the gown despite that being the last thing mentioned.
That would be tricky for authors to implement, as the last object in every description would need to be flagged. And then you might have dynamic descriptions, descriptions with male, female and plural objects. Should we expect this to work?
You are in a small room, where you can see a man sat on a chair.
>X HIM
If you do, how are you, the author, going to tell Quest that "him" should refer to the man, and "it" to the chair?
KV has a solution, but you would need to add hyperlinks to every mention of an object in a description. What if you turn hyperlinks off for your game?
An alternative approach would be to have a dictionary of last objects. When the player does something, the objects are added to the dictionary twice, with their gender and article as the key, overwriting any previous values. When resolving names, the keys are then checked against. The allows the values to persist.
> get teapot
You pick it up.
> talk to joanna
'I like your hair.'
'Do you like it this colour?' she asks.
> drop it
You drop it.
> get it
You pick it up.
> x her
Joanna is quite pretty; she has green hair.
> x it
It is blue.
It is quite a simple change to two core functions. You also need to add an object dictionary called "lastobjects" to the game object (but should it be on game.pov?).
<function name="ResolveNextName"><![CDATA[
resolvedall = false
queuetype = TypeOf(game.pov, "currentcommandvarlistqueue")
if (queuetype = "stringlist") {
queuelength = ListCount(game.pov.currentcommandvarlistqueue)
if (queuelength > 0) {
// Pop next variable off the queue
var = StringListItem(game.pov.currentcommandvarlistqueue, 0)
if (queuelength = 1) {
game.pov.currentcommandvarlistqueue = null
}
else {
newqueue = NewStringList()
for (i, 1, queuelength - 1) {
list add (newqueue, StringListItem(game.pov.currentcommandvarlistqueue, i))
}
game.pov.currentcommandvarlistqueue = newqueue
}
// Resolve variable
value = StringDictionaryItem(game.pov.currentcommandvarlist, var)
if (value <> "") {
result = null
resolvinglist = false
// This is to resolve issue 626
if (StartsWith(var, "objectexit")) {
result = ResolveName(var, value, "exit")
}
if (result = null) {
if (StartsWith(var, "object")) {
if (HasScript(game.pov.currentcommandpattern, "multipleobjects")) {
game.pov.currentcommandpendingobjectlist = NewObjectList()
game.pov.currentcommandpendingvariable = var
do (game.pov.currentcommandpattern, "multipleobjects")
ResolveNameList (value, "object")
resolvinglist = true
}
else {
result = ResolveName(var, value, "object")
}
}
else if (StartsWith(var, "exit")) {
result = ResolveName(var, value, "exit")
}
else if (StartsWith(var, "text")) {
result = StringDictionaryItem(game.pov.currentcommandvarlist, var)
}
else {
error ("Unhandled command variable '" + var + "' - command variable names must begin with 'object', 'exit' or 'text'")
}
}
// at this point, ResolveName has returned - either an object name, unresolved, or pending
if (result = null) {
if ((not resolvinglist) and LengthOf(GetString(game.pov, "currentcommandpendingvariable")) = 0) {
UnresolvedCommand (value, var)
}
}
else {
AddToResolvedNames (var, result)
}
}
else {
ResolveNextName
}
}
else {
resolvedall = true
}
}
else if (queuetype = "null") {
resolvedall = true
}
else {
error ("Invalid queue type")
}
if (resolvedall) {
// All the objects have been resolved, so now we can actually do the command
// TO DO: game.lastobjects should be game.pov.lastobjects
foreach (obj, game.pov.currentcommandresolvedobjects) {
if (obj.gender in game.lastobjects) {
dictionary remove (game.lastobjects, obj.gender)
}
dictionary add (game.lastobjects, obj.gender, obj)
if (obj.article in game.lastobjects) {
dictionary remove (game.lastobjects, obj.article)
}
dictionary add (game.lastobjects, obj.article, obj)
}
if (not DictionaryContains(game.pov.currentcommandresolvedelements, "multiple")) {
dictionary add (game.pov.currentcommandresolvedelements, "multiple", false)
}
if (not GetBoolean(game.pov.currentcommandpattern, "isundo")) {
if (LengthOf(game.pov.currentcommand) > 0) {
start transaction (game.pov.currentcommand)
}
}
if (not GetBoolean(game.pov.currentcommandpattern, "isoops")) {
// TO DO: game.unresolved* should be game.pov.unresolved*
game.unresolvedcommand = null
game.unresolvedcommandvarlist = null
game.unresolvedcommandkey = null
}
if (HasScript(game.pov.currentcommandpattern, "script")) {
// This is the bit that actually runs the commands
do (game.pov.currentcommandpattern, "script", game.pov.currentcommandresolvedelements)
}
HandleNextCommandQueueItem
}
]]></function>
<function name="ResolveNameFromList" parameters="variable, value, objtype, scope, secondaryscope" type="object"><![CDATA[
value = Trim(LCase(value))
fullmatches = NewObjectList()
partialmatches = NewObjectList()
foreach (obj, scope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
// allow referring to objects from the previous command by gender or article
if (objtype = "object") {
foreach (key, game.lastobjects) {
CompareNames (key, value, ObjectDictionaryItem(game.lastobjects, key), fullmatches, partialmatches)
}
}
// Also check the secondary scope, but only if we have not found anything yet
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0 and not secondaryscope = null) {
foreach (obj, secondaryscope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
}
if (ListCount(fullmatches) = 1) {
return (ListItem(fullmatches, 0))
}
else if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 1) {
return (ListItem(partialmatches, 0))
}
else if (ListCount(fullmatches) + ListCount(partialmatches) = 0) {
return (null)
}
else {
candidates = ListCompact(fullmatches + partialmatches)
if (LengthOf(variable) > 0) {
// single object command, so after showing the menu, add the object to game.pov.currentcommandresolvedelements
game.pov.currentcommandpendingvariable = variable
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
varname = game.pov.currentcommandpendingvariable
game.pov.currentcommandpendingvariable = null
if (result <> null) {
AddToResolvedNames (varname, GetObject(result))
}
}
}
else {
// multi-object command, so after showing the menu, add the object to the list
game.pov.currentcommandmultiobjectpending = true
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
if (result <> null) {
list add (game.pov.currentcommandpendingobjectlist, GetObject(result))
ResolveNextNameListItem
}
}
}
return (null)
}
]]></function>
KV has a solution, but you would need to add hyperlinks to every mention of an object in a description. What if you turn hyperlinks off for your game?
I just tested this last night. If you turn off hyperlinks, my code no longer does anything at all.
I would be interested, KV, what Hitchhikers does if you type:
X GOWN
LOOK
GET ITDo you pick up the gown or the toothbrush?
And just to be thorough:
how are you, the author, going to tell Quest that "him" should refer to the man, and "it" to the chair?
You are in a small room, where you can see a man sat on a chair.
X HIM
Quest seems to do that by itself. Here's an example using this code:
You are in a room.
You can see Jack, Jill and a robot.
> x it
It is an old Radio Shack robot, made in 1987.
> l
You are in a room.
You can see Jack, Jill and a robot.
> x him
Jack looks like an average guy.
> l
You are in a room.
You can see Jack, Jill and a robot.
> x her
Jill looks like an average girl.
I do agree that my way is not that good, since it only works with hyperlinks.
...but I do dig the last object(s) mentioned in the text being added to lastobjects
.
When entering a room and the text is:
"On the doormat, you see a pile of junk mail."
I think it makes sense that GET IT would take that mail.
It does not in Hitchhiker's, but I do like the response:
Example game using Pixie's code:
<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="It (Pixie's Code)">
<gameid>cb9e4b3b-763a-40f6-a98f-f0260be056ae</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
<lastobjects type="objectdictionary" />
</game>
<object name="room">
<inherit name="editor_room" />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="John">
<inherit name="editor_object" />
<inherit name="namedmale" />
<look>He looks like a man.</look>
</object>
<object name="Mary">
<inherit name="editor_object" />
<inherit name="namedfemale" />
<look>She looks like a woman.</look>
</object>
<object name="lamp">
<inherit name="editor_object" />
<inherit name="switchable" />
<take />
<look>It is {either lamp.switchedon:on|off}.</look>
<feature_switchable />
</object>
<exit alias="north" to="second room">
<inherit name="northdirection" />
</exit>
</object>
<object name="second room">
<inherit name="editor_room" />
<exit alias="south" to="room">
<inherit name="southdirection" />
</exit>
</object>
<function name="ResolveNextName"><![CDATA[
resolvedall = false
queuetype = TypeOf(game.pov, "currentcommandvarlistqueue")
if (queuetype = "stringlist") {
queuelength = ListCount(game.pov.currentcommandvarlistqueue)
if (queuelength > 0) {
// Pop next variable off the queue
var = StringListItem(game.pov.currentcommandvarlistqueue, 0)
if (queuelength = 1) {
game.pov.currentcommandvarlistqueue = null
}
else {
newqueue = NewStringList()
for (i, 1, queuelength - 1) {
list add (newqueue, StringListItem(game.pov.currentcommandvarlistqueue, i))
}
game.pov.currentcommandvarlistqueue = newqueue
}
// Resolve variable
value = StringDictionaryItem(game.pov.currentcommandvarlist, var)
if (value <> "") {
result = null
resolvinglist = false
// This is to resolve issue 626
if (StartsWith(var, "objectexit")) {
result = ResolveName(var, value, "exit")
}
if (result = null) {
if (StartsWith(var, "object")) {
if (HasScript(game.pov.currentcommandpattern, "multipleobjects")) {
game.pov.currentcommandpendingobjectlist = NewObjectList()
game.pov.currentcommandpendingvariable = var
do (game.pov.currentcommandpattern, "multipleobjects")
ResolveNameList (value, "object")
resolvinglist = true
}
else {
result = ResolveName(var, value, "object")
}
}
else if (StartsWith(var, "exit")) {
result = ResolveName(var, value, "exit")
}
else if (StartsWith(var, "text")) {
result = StringDictionaryItem(game.pov.currentcommandvarlist, var)
}
else {
error ("Unhandled command variable '" + var + "' - command variable names must begin with 'object', 'exit' or 'text'")
}
}
// at this point, ResolveName has returned - either an object name, unresolved, or pending
if (result = null) {
if ((not resolvinglist) and LengthOf(GetString(game.pov, "currentcommandpendingvariable")) = 0) {
UnresolvedCommand (value, var)
}
}
else {
AddToResolvedNames (var, result)
}
}
else {
ResolveNextName
}
}
else {
resolvedall = true
}
}
else if (queuetype = "null") {
resolvedall = true
}
else {
error ("Invalid queue type")
}
if (resolvedall) {
// All the objects have been resolved, so now we can actually do the command
// TO DO: game.lastobjects should be game.pov.lastobjects
foreach (obj, game.pov.currentcommandresolvedobjects) {
if (obj.gender in game.lastobjects) {
dictionary remove (game.lastobjects, obj.gender)
}
dictionary add (game.lastobjects, obj.gender, obj)
if (obj.article in game.lastobjects) {
dictionary remove (game.lastobjects, obj.article)
}
dictionary add (game.lastobjects, obj.article, obj)
}
if (not DictionaryContains(game.pov.currentcommandresolvedelements, "multiple")) {
dictionary add (game.pov.currentcommandresolvedelements, "multiple", false)
}
if (not GetBoolean(game.pov.currentcommandpattern, "isundo")) {
if (LengthOf(game.pov.currentcommand) > 0) {
start transaction (game.pov.currentcommand)
}
}
if (not GetBoolean(game.pov.currentcommandpattern, "isoops")) {
// TO DO: game.unresolved* should be game.pov.unresolved*
game.unresolvedcommand = null
game.unresolvedcommandvarlist = null
game.unresolvedcommandkey = null
}
if (HasScript(game.pov.currentcommandpattern, "script")) {
// This is the bit that actually runs the commands
do (game.pov.currentcommandpattern, "script", game.pov.currentcommandresolvedelements)
}
HandleNextCommandQueueItem
}
]]></function>
<function name="ResolveNameFromList" parameters="variable, value, objtype, scope, secondaryscope" type="object"><![CDATA[
value = Trim(LCase(value))
fullmatches = NewObjectList()
partialmatches = NewObjectList()
foreach (obj, scope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
// allow referring to objects from the previous command by gender or article
if (objtype = "object") {
foreach (key, game.lastobjects) {
CompareNames (key, value, ObjectDictionaryItem(game.lastobjects, key), fullmatches, partialmatches)
}
}
// Also check the secondary scope, but only if we have not found anything yet
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0 and not secondaryscope = null) {
foreach (obj, secondaryscope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
}
if (ListCount(fullmatches) = 1) {
return (ListItem(fullmatches, 0))
}
else if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 1) {
return (ListItem(partialmatches, 0))
}
else if (ListCount(fullmatches) + ListCount(partialmatches) = 0) {
return (null)
}
else {
candidates = ListCompact(fullmatches + partialmatches)
if (LengthOf(variable) > 0) {
// single object command, so after showing the menu, add the object to game.pov.currentcommandresolvedelements
game.pov.currentcommandpendingvariable = variable
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
varname = game.pov.currentcommandpendingvariable
game.pov.currentcommandpendingvariable = null
if (result <> null) {
AddToResolvedNames (varname, GetObject(result))
}
}
}
else {
// multi-object command, so after showing the menu, add the object to the list
game.pov.currentcommandmultiobjectpending = true
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
if (result <> null) {
list add (game.pov.currentcommandpendingobjectlist, GetObject(result))
ResolveNextNameListItem
}
}
}
return (null)
}
]]></function>
</asl>
You are in a room.
You can see John, Mary and a lamp.
You can go north.
> x john
He looks like a man.
> x mary
She looks like a woman.
> x lamp
It is off.
> get it
You pick it up.
> n
You are in a second room.
You can go south.
> x him
He looks like a man.
> x her
She looks like a woman.
What if this was the only change made?
THIS CODE WAS NO GOOD
It allowed you to ALWAYS refer to objects in scope by their gender
or article
.
That part worked, but it completely bypassed lastobjects
.
Example:
You can see a stick and a ball.
>x it
Which do you mean?
1. stick
2. ball
>1
It's a stick.
>get it
Which do you mean?
1. stick
2. ball
Okay...
What if THIS was the only change made?
<function name="ResolveNameFromList" parameters="variable, value, objtype, scope, secondaryscope" type="object"><![CDATA[
value = Trim(LCase(value))
fullmatches = NewObjectList()
partialmatches = NewObjectList()
foreach (obj, scope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
// allow referring to objects from the previous command by gender or article
if (objtype = "object" and game.lastobjects <> null) {
foreach (obj, game.lastobjects) {
CompareNames (LCase(obj.article), value, obj, fullmatches, partialmatches)
CompareNames (LCase(obj.gender), value, obj, fullmatches, partialmatches)
}
}
// Also check the secondary scope, but only if we have not found anything yet
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0 and not secondaryscope = null) {
foreach (obj, secondaryscope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
}
// ADDED BY KV
// Also allow referring to objects IN SCOPE by gender or article
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0) {
if (objtype = "object") {
foreach (obj, scope) {
CompareNames (obj.gender, value, obj, fullmatches, partialmatches)
CompareNames (obj.article, value, obj, fullmatches, partialmatches)
}
}
}
// END OF ADDITION BY KV
if (ListCount(fullmatches) = 1) {
return (ListItem(fullmatches, 0))
}
else if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 1) {
return (ListItem(partialmatches, 0))
}
else if (ListCount(fullmatches) + ListCount(partialmatches) = 0) {
return (null)
}
else {
candidates = ListCompact(fullmatches + partialmatches)
if (LengthOf(variable) > 0) {
// single object command, so after showing the menu, add the object to game.pov.currentcommandresolvedelements
game.pov.currentcommandpendingvariable = variable
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
varname = game.pov.currentcommandpendingvariable
game.pov.currentcommandpendingvariable = null
if (result <> null) {
AddToResolvedNames (varname, GetObject(result))
}
}
}
else {
// multi-object command, so after showing the menu, add the object to the list
game.pov.currentcommandmultiobjectpending = true
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
if (result <> null) {
list add (game.pov.currentcommandpendingobjectlist, GetObject(result))
ResolveNextNameListItem
}
}
}
return (null)
}
]]></function>
Example game:
<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="It REV3">
<gameid>9f053cbe-65e3-4589-b95f-295682497bc3</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
</game>
<object name="room">
<inherit name="editor_room" />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="John">
<inherit name="editor_object" />
<inherit name="namedmale" />
<look>He looks like a man.</look>
</object>
<object name="Mary">
<inherit name="editor_object" />
<inherit name="namedfemale" />
<look>She looks like a woman.</look>
</object>
<object name="stick">
<inherit name="editor_object" />
<take />
<look>A small stick.</look>
<ondrop type="script">
</ondrop>
</object>
<object name="lamp">
<inherit name="editor_object" />
<inherit name="switchable" />
<take />
<look>It is {either lamp.switchedon:on|off}.</look>
<feature_switchable />
</object>
<exit alias="north" to="second room">
<inherit name="northdirection" />
</exit>
</object>
<object name="second room">
<inherit name="editor_room" />
<exit alias="south" to="room">
<inherit name="southdirection" />
</exit>
<object name="Ralph">
<inherit name="editor_object" />
<inherit name="namedmale" />
<look>He looks like a penguin.</look>
</object>
<object name="ball">
<inherit name="editor_object" />
<take />
<look>A red ball.</look>
</object>
</object>
<function name="ResolveNameFromList" parameters="variable, value, objtype, scope, secondaryscope" type="object"><![CDATA[
value = Trim(LCase(value))
fullmatches = NewObjectList()
partialmatches = NewObjectList()
foreach (obj, scope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
// allow referring to objects from the previous command by gender or article
if (objtype = "object" and game.lastobjects <> null) {
foreach (obj, game.lastobjects) {
CompareNames (LCase(obj.article), value, obj, fullmatches, partialmatches)
CompareNames (LCase(obj.gender), value, obj, fullmatches, partialmatches)
}
}
// Also check the secondary scope, but only if we have not found anything yet
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0 and not secondaryscope = null) {
foreach (obj, secondaryscope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
}
// ADDED BY KV
// Also allow referring to objects IN SCOPE by gender or article
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0) {
if (objtype = "object") {
foreach (obj, scope) {
CompareNames (obj.gender, value, obj, fullmatches, partialmatches)
CompareNames (obj.article, value, obj, fullmatches, partialmatches)
}
}
}
// END OF ADDITION BY KV
if (ListCount(fullmatches) = 1) {
return (ListItem(fullmatches, 0))
}
else if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 1) {
return (ListItem(partialmatches, 0))
}
else if (ListCount(fullmatches) + ListCount(partialmatches) = 0) {
return (null)
}
else {
candidates = ListCompact(fullmatches + partialmatches)
if (LengthOf(variable) > 0) {
// single object command, so after showing the menu, add the object to game.pov.currentcommandresolvedelements
game.pov.currentcommandpendingvariable = variable
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
varname = game.pov.currentcommandpendingvariable
game.pov.currentcommandpendingvariable = null
if (result <> null) {
AddToResolvedNames (varname, GetObject(result))
}
}
}
else {
// multi-object command, so after showing the menu, add the object to the list
game.pov.currentcommandmultiobjectpending = true
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
if (result <> null) {
list add (game.pov.currentcommandpendingobjectlist, GetObject(result))
ResolveNextNameListItem
}
}
}
return (null)
}
]]></function>
</asl>
OFF-TOPIC, BUT CONCERNING THIS CODE
It seems that I could use some code from this function to compare input to objects during ASK NPC ABOUT TEXT when the text isn't matched to a topic.
I looks like ASK checks against each entire word in each of an object's keys. So it doesn't parse like everything else does... If I see a defibrillator, I can enter X DEF to examine it, but I can't ASK BOB ABOUT DEF. I have to ASK ABOUT ABOUT DEFIBRILLATOR.
I also can't X DEF, then ASK BOB ABOUT IT.
I'm thinking I can use something from ResolveNameFromList
to check the text against all objects before going with the default reply if there is no match found.
UPDATE
I got it so you can ASK OBJECT1 ABOUT TEXT/#OBJECT2#. You just can't ask about IT/HIM/HER/THEM.
<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="Ask About an Object">
<gameid>124f1873-d66e-460f-b41e-33d5e26bfe60</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
<feature_asktell />
</game>
<object name="room">
<inherit name="editor_room" />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="Bob">
<inherit name="editor_object" />
<inherit name="namedmale" />
<ask type="scriptdictionary">
<item key="defibrillator">
msg ("\"I like to keep one handy,\" says Bob.")
</item>
<item key="game">
msg ("\"It's just a test game,\" says Bob.")
</item>
</ask>
</object>
<object name="defibrillator">
<inherit name="editor_object" />
<take />
<look>A heart defibrillator can magically revive a dead person, if all those hospital dramas are to be believed.</look>
</object>
</object>
<command name="askaboutstuff">
<pattern>ask #object1# about #text#</pattern>
<script>
pronouns = Split("it;he;him;her;she;them;those",";")
foundpronoun = false
foreach (p, pronouns) {
if (Trim(LCase(text)) = p) {
foundpronoun = true
}
}
if (foundpronoun) {
msg ("Try asking about things without using pronouns.")
}
else {
DoAskTell (object1, text, "ask", "askdefault", "DefaultAsk")
}
</script>
</command>
<function name="DoAskTell" parameters="object, text, property, defaultscript, defaulttemplate"><![CDATA[
handled = false
maxstrength = 0
match = null
text = LCase(text)
if (TypeOf(object, property) = "scriptdictionary") {
dictionary = GetAttribute(object, property)
foreach (keywords, dictionary) {
strength = GetKeywordsMatchStrength(LCase(keywords), text)
if (strength >= maxstrength and strength>0) {
match = ScriptDictionaryItem(dictionary, keywords)
maxstrength = strength
}
}
if (match <> null) {
parameters = NewObjectDictionary()
dictionary add (parameters, "this", object)
invoke (match, parameters)
handled = true
}
}
if (not handled) {
partialmatches = NewStringList()
foreach (obj, AllObjects()) {
oda = GetDisplayAlias(obj)
// check if input matches the start of any word in the name
if (HasAttribute(obj,"alt")) {
foreach (value, obj.alt) {
if (Instr(value,text) > 0) {
if (not ListContains(partialmatches, oda)) {
list add (partialmatches, oda)
}
}
}
}
else {
if (Instr(oda,text) > 0) {
list add (partialmatches, oda)
}
}
}
if (ListCount(partialmatches)>0) {
if (ListCount(partialmatches)=1) {
text = partialmatches[0]
}
else {
ShowMenu (DynamicTemplate("DisambiguateMenu", "object"), partialmatches, true) {
if (result <> null) {
text = result
}
}
}
}
}
if (not handled) {
if (ListCount(partialmatches)=0) {
if (ListCount(partialmatches)>0) {
if (ListCount(partialmatches)=1) {
text = partialmatches[0]
}
else {
ShowMenu (DynamicTemplate("DisambiguateMenu", "object"), partialmatches, true) {
if (result <> null) {
text = result
}
}
}
}
}
}
if (not handled) {
if (TypeOf(object, property) = "scriptdictionary") {
dictionary = GetAttribute(object, property)
foreach (keywords, dictionary) {
strength = GetKeywordsMatchStrength(LCase(keywords), text)
if (strength >= maxstrength and strength>0) {
match = ScriptDictionaryItem(dictionary, keywords)
maxstrength = strength
}
}
if (match <> null) {
parameters = NewObjectDictionary()
dictionary add (parameters, "this", object)
invoke (match, parameters)
handled = true
}
}
}
if (not handled) {
if (HasScript(object, defaultscript)) {
d = NewDictionary()
dictionary add (d, "text", text)
do (object, defaultscript, d)
}
else {
msg (DynamicTemplate(defaulttemplate, object))
}
}
]]></function>
</asl>
Pixie can probably turn that into much less code. It's a very dirty hack.
Pixie can probably turn that into much less code. It's a very dirty hack.
I did on the thread about using "it" with ask/tell, but now I cannot find the thread. I did not imagine that thread, did I?
It is just some extra code for DoAskTell
; try to match the text against pronouns, ifthey match, recursively call DoAskTell
.
if (DictionaryContains(game.lastobjects, text)) {
DoAskTell (object, GetDisplayAlias(ObjectDictionaryItem(game.lastobjects, text)), property, defaultscript, defaulttemplate)
}
else {
handled = false
maxstrength = 0
match = null
text = LCase(text)
if (TypeOf(object, property) = "scriptdictionary") {
dictionary = GetAttribute(object, property)
foreach (keywords, dictionary) {
strength = GetKeywordsMatchStrength(LCase(keywords), text)
if (strength >= maxstrength and strength>0) {
match = ScriptDictionaryItem(dictionary, keywords)
maxstrength = strength
}
}
if (match <> null) {
parameters = NewObjectDictionary()
dictionary add (parameters, "this", object)
invoke (match, parameters)
handled = true
}
}
if (not handled) {
if (HasScript(object, defaultscript)) {
d = NewDictionary()
dictionary add (d, "text", text)
do (object, defaultscript, d)
}
else {
msg (DynamicTemplate(defaulttemplate, object))
}
}
}
With regards to things going out of scope, one approach would be to go through the list every turn and remove any objects that are no longer there (whether because they were eaten, or the player is in a different room). This would allow them to persist when changing rooms, if appropriate.
Here we are. This only adds the text if lastobjects was used (I think).
A room
You can see a stick and Phil.
> x stick
A small stick.
> x it
(stick)
A small stick.
> x stick
A small stick.
> x st
A small stick.
> x ph
He looks like a "Phil".
> x phil
He looks like a "Phil".
> x him
(Phil)
He looks like a "Phil".
> x ph
He looks like a "Phil".
> x it
I can't see that.
> x stick
A small stick.
> x it
(stick)
A small stick.
The functions:
<function name="ResolveNameFromList" parameters="variable, value, objtype, scope, secondaryscope" type="object"><![CDATA[
value = Trim(LCase(value))
fullmatches = NewObjectList()
partialmatches = NewObjectList()
foreach (obj, scope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
// allow referring to objects from the previous command by gender or article
// This attribute added by KV to print the alias when using lastobjects in a command.
game.usedlastobjects = false
if (objtype = "object" and game.lastobjects <> null) {
foreach (obj, game.lastobjects) {
CompareNames (LCase(obj.article), value, obj, fullmatches, partialmatches)
CompareNames (LCase(obj.gender), value, obj, fullmatches, partialmatches)
// Check if we are using an object from lastobjects.
mentioned = DictionaryItem(game.pov.currentcommandvarlist, "object")
if (ListCount(fullmatches) = 1 and ListContains(fullmatches, obj) and (mentioned = obj.gender or mentioned = obj.article)) {
// This attribute added by KV to print the alias when using lastobjects in a command.
game.usedlastobjects = true
}
}
}
// Also check the secondary scope, but only if we have not found anything yet
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0 and not secondaryscope = null) {
foreach (obj, secondaryscope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
}
if (ListCount(fullmatches) = 1) {
return (ListItem(fullmatches, 0))
}
else if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 1) {
return (ListItem(partialmatches, 0))
}
else if (ListCount(fullmatches) + ListCount(partialmatches) = 0) {
return (null)
}
else {
// Added this line to resolve issue with new FinishTurn setup in 580
if (HasAttribute (game, "runturnscripts") or GetAttribute(game, "aslversion") = "580") {
game.disambiguating = true
}
game.disambiguating = true
candidates = ListCompact(ListCombine(fullmatches, partialmatches))
if (LengthOf(variable) > 0) {
// single object command, so after showing the menu, add the object to game.pov.currentcommandresolvedelements
game.pov.currentcommandpendingvariable = variable
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
varname = game.pov.currentcommandpendingvariable
game.pov.currentcommandpendingvariable = null
if (result <> null) {
AddToResolvedNames (varname, GetObject(result))
}
}
}
else {
// multi-object command, so after showing the menu, add the object to the list
game.pov.currentcommandmultiobjectpending = true
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
if (result <> null) {
list add (game.pov.currentcommandpendingobjectlist, GetObject(result))
ResolveNextNameListItem
}
}
}
return (null)
}
]]></function>
<function name="ResolveNextName"><![CDATA[
resolvedall = false
queuetype = TypeOf(game.pov, "currentcommandvarlistqueue")
if (queuetype = "stringlist") {
queuelength = ListCount(game.pov.currentcommandvarlistqueue)
if (queuelength > 0) {
// Pop next variable off the queue
var = StringListItem(game.pov.currentcommandvarlistqueue, 0)
if (queuelength = 1) {
game.pov.currentcommandvarlistqueue = null
}
else {
newqueue = NewStringList()
for (i, 1, queuelength - 1) {
list add (newqueue, StringListItem(game.pov.currentcommandvarlistqueue, i))
}
game.pov.currentcommandvarlistqueue = newqueue
}
// Resolve variable
value = StringDictionaryItem(game.pov.currentcommandvarlist, var)
if (value <> "") {
result = null
resolvinglist = false
// This is to resolve issue 626
if (StartsWith(var, "objectexit")) {
result = ResolveName(var, value, "exit")
}
if (result = null) {
if (StartsWith(var, "object")) {
if (GetBoolean(game.pov.currentcommandpattern, "allow_all")) {
scope = FilterByAttribute(GetScope("object", "", "object"), "scenery", false)
game.pov.currentcommandpendingobjectscope = ListExclude(scope, FilterByAttribute(scope, "not_all", true))
game.pov.currentcommandpendingvariable = var
ResolveNameList (value, "object")
resolvinglist = true
}
else if (HasScript(game.pov.currentcommandpattern, "multipleobjects")) {
game.pov.currentcommandpendingobjectlist = NewObjectList()
game.pov.currentcommandpendingvariable = var
do (game.pov.currentcommandpattern, "multipleobjects")
ResolveNameList (value, "object")
resolvinglist = true
}
else {
result = ResolveName(var, value, "object")
}
}
else if (StartsWith(var, "exit")) {
result = ResolveName(var, value, "exit")
}
else if (StartsWith(var, "text")) {
result = StringDictionaryItem(game.pov.currentcommandvarlist, var)
}
else {
error ("Unhandled command variable '" + var + "' - command variable names must begin with 'object', 'exit' or 'text'")
}
}
// at this point, ResolveName has returned - either an object name, unresolved, or pending
if (result = null) {
if ((not resolvinglist) and LengthOf(GetString(game.pov, "currentcommandpendingvariable")) = 0) {
UnresolvedCommand (value, var)
}
}
else {
AddToResolvedNames (var, result)
}
}
else {
ResolveNextName
}
}
else {
resolvedall = true
}
}
else if (queuetype = "null") {
resolvedall = true
}
else {
error ("Invalid queue type")
}
if (resolvedall) {
// All the objects have been resolved, so now we can actually do the command
// TO DO: game.lastobjects should be game.pov.lastobjects
// msg (ListCount(game.pov.currentcommandresolvedobjects))
game.lastobjects = game.pov.currentcommandresolvedobjects
if (not DictionaryContains(game.pov.currentcommandresolvedelements, "multiple")) {
dictionary add (game.pov.currentcommandresolvedelements, "multiple", false)
}
if (not GetBoolean(game.pov.currentcommandpattern, "isundo")) {
if (LengthOf(game.pov.currentcommand) > 0) {
start transaction (game.pov.currentcommand)
}
}
if (not GetBoolean(game.pov.currentcommandpattern, "isoops")) {
// TO DO: game.unresolved* should be game.pov.unresolved*
game.unresolvedcommand = null
game.unresolvedcommandvarlist = null
game.unresolvedcommandkey = null
}
if (HasScript(game.pov.currentcommandpattern, "script")) {
// KV added this bit to show which object we're interacting with if partialmatches was used.
if (GetBoolean (game, "usedlastobjects")) {
mentioned = DictionaryItem(game.pov.currentcommandvarlist, "object")
if (GetObject(mentioned) = null) {
it = game.pov.currentcommandresolvedobjects[0]
msg ("("+ GetDisplayAlias(it) +")")
}
}
// END OF BIT ADDED BY KV
// This is the bit that actually runs the commands
do (game.pov.currentcommandpattern, "script", game.pov.currentcommandresolvedelements)
}
//
// Setting game.runturnscripts to true to run turn scripts after ShowMenu , show menu, ask, or Ask.
// This works in conjuction with FinishTurn, which has also been modified as of Quest 5.8.
// The game.aslversion attribute exists only in my mod of Quest.
// - KV
if (HasAttribute (game, "runturnscripts") or GetAttribute(game, "aslversion") = "580" or GetBoolean(game, "multiplecommands")) {
game.runturnscripts = true
FinishTurn
}
HandleNextCommandQueueItem
}
]]></function>
Example game:
<!--Saved by Quest 5.7.6735.33664-->
<asl version="550">
<include ref="English.aslx" />
<include ref="Core.aslx" />
<game name="What is it?">
<gameid>ab721896-e3b1-439d-83d4-715c2162d748</gameid>
<version>1.0</version>
<firstpublished>2018</firstpublished>
</game>
<object name="room">
<inherit name="editor_room" />
<isroom />
<object name="player">
<inherit name="editor_object" />
<inherit name="editor_player" />
</object>
<object name="stick">
<inherit name="editor_object" />
<take />
<look>A small stick.</look>
</object>
<object name="Phil">
<inherit name="editor_object" />
<inherit name="namedmale" />
<look>He looks like a "Phil".</look>
<speak>{once:Phil nods.}{notfirst:Phil waves you away.}</speak>
</object>
</object>
<function name="ResolveNameFromList" parameters="variable, value, objtype, scope, secondaryscope" type="object"><![CDATA[
value = Trim(LCase(value))
fullmatches = NewObjectList()
partialmatches = NewObjectList()
foreach (obj, scope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
// allow referring to objects from the previous command by gender or article
// This attribute added by KV to print the alias when using lastobjects in a command.
game.usedlastobjects = false
if (objtype = "object" and game.lastobjects <> null) {
foreach (obj, game.lastobjects) {
CompareNames (LCase(obj.article), value, obj, fullmatches, partialmatches)
CompareNames (LCase(obj.gender), value, obj, fullmatches, partialmatches)
// Check if we are using an object from lastobjects.
mentioned = DictionaryItem(game.pov.currentcommandvarlist, "object")
if (ListCount(fullmatches) = 1 and ListContains(fullmatches, obj) and (mentioned = obj.gender or mentioned = obj.article)) {
// This attribute added by KV to print the alias when using lastobjects in a command.
game.usedlastobjects = true
}
}
}
// Also check the secondary scope, but only if we have not found anything yet
if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 0 and not secondaryscope = null) {
foreach (obj, secondaryscope) {
name = LCase(GetDisplayAlias(obj))
CompareNames (name, value, obj, fullmatches, partialmatches)
if (obj.alt <> null) {
foreach (altname, obj.alt) {
CompareNames (LCase(altname), value, obj, fullmatches, partialmatches)
}
}
}
}
if (ListCount(fullmatches) = 1) {
return (ListItem(fullmatches, 0))
}
else if (ListCount(fullmatches) = 0 and ListCount(partialmatches) = 1) {
return (ListItem(partialmatches, 0))
}
else if (ListCount(fullmatches) + ListCount(partialmatches) = 0) {
return (null)
}
else {
// Added this line to resolve issue with new FinishTurn setup in 580
if (HasAttribute (game, "runturnscripts") or GetAttribute(game, "aslversion") = "580") {
game.disambiguating = true
}
game.disambiguating = true
candidates = ListCompact(ListCombine(fullmatches, partialmatches))
if (LengthOf(variable) > 0) {
// single object command, so after showing the menu, add the object to game.pov.currentcommandresolvedelements
game.pov.currentcommandpendingvariable = variable
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
varname = game.pov.currentcommandpendingvariable
game.pov.currentcommandpendingvariable = null
if (result <> null) {
AddToResolvedNames (varname, GetObject(result))
}
}
}
else {
// multi-object command, so after showing the menu, add the object to the list
game.pov.currentcommandmultiobjectpending = true
ShowMenu (DynamicTemplate("DisambiguateMenu", value), candidates, true) {
if (result <> null) {
list add (game.pov.currentcommandpendingobjectlist, GetObject(result))
ResolveNextNameListItem
}
}
}
return (null)
}
]]></function>
<function name="ResolveNextName"><![CDATA[
resolvedall = false
queuetype = TypeOf(game.pov, "currentcommandvarlistqueue")
if (queuetype = "stringlist") {
queuelength = ListCount(game.pov.currentcommandvarlistqueue)
if (queuelength > 0) {
// Pop next variable off the queue
var = StringListItem(game.pov.currentcommandvarlistqueue, 0)
if (queuelength = 1) {
game.pov.currentcommandvarlistqueue = null
}
else {
newqueue = NewStringList()
for (i, 1, queuelength - 1) {
list add (newqueue, StringListItem(game.pov.currentcommandvarlistqueue, i))
}
game.pov.currentcommandvarlistqueue = newqueue
}
// Resolve variable
value = StringDictionaryItem(game.pov.currentcommandvarlist, var)
if (value <> "") {
result = null
resolvinglist = false
// This is to resolve issue 626
if (StartsWith(var, "objectexit")) {
result = ResolveName(var, value, "exit")
}
if (result = null) {
if (StartsWith(var, "object")) {
if (GetBoolean(game.pov.currentcommandpattern, "allow_all")) {
scope = FilterByAttribute(GetScope("object", "", "object"), "scenery", false)
game.pov.currentcommandpendingobjectscope = ListExclude(scope, FilterByAttribute(scope, "not_all", true))
game.pov.currentcommandpendingvariable = var
ResolveNameList (value, "object")
resolvinglist = true
}
else if (HasScript(game.pov.currentcommandpattern, "multipleobjects")) {
game.pov.currentcommandpendingobjectlist = NewObjectList()
game.pov.currentcommandpendingvariable = var
do (game.pov.currentcommandpattern, "multipleobjects")
ResolveNameList (value, "object")
resolvinglist = true
}
else {
result = ResolveName(var, value, "object")
}
}
else if (StartsWith(var, "exit")) {
result = ResolveName(var, value, "exit")
}
else if (StartsWith(var, "text")) {
result = StringDictionaryItem(game.pov.currentcommandvarlist, var)
}
else {
error ("Unhandled command variable '" + var + "' - command variable names must begin with 'object', 'exit' or 'text'")
}
}
// at this point, ResolveName has returned - either an object name, unresolved, or pending
if (result = null) {
if ((not resolvinglist) and LengthOf(GetString(game.pov, "currentcommandpendingvariable")) = 0) {
UnresolvedCommand (value, var)
}
}
else {
AddToResolvedNames (var, result)
}
}
else {
ResolveNextName
}
}
else {
resolvedall = true
}
}
else if (queuetype = "null") {
resolvedall = true
}
else {
error ("Invalid queue type")
}
if (resolvedall) {
// All the objects have been resolved, so now we can actually do the command
// TO DO: game.lastobjects should be game.pov.lastobjects
// msg (ListCount(game.pov.currentcommandresolvedobjects))
game.lastobjects = game.pov.currentcommandresolvedobjects
if (not DictionaryContains(game.pov.currentcommandresolvedelements, "multiple")) {
dictionary add (game.pov.currentcommandresolvedelements, "multiple", false)
}
if (not GetBoolean(game.pov.currentcommandpattern, "isundo")) {
if (LengthOf(game.pov.currentcommand) > 0) {
start transaction (game.pov.currentcommand)
}
}
if (not GetBoolean(game.pov.currentcommandpattern, "isoops")) {
// TO DO: game.unresolved* should be game.pov.unresolved*
game.unresolvedcommand = null
game.unresolvedcommandvarlist = null
game.unresolvedcommandkey = null
}
if (HasScript(game.pov.currentcommandpattern, "script")) {
// KV added this bit to show which object we're interacting with if partialmatches was used.
if (GetBoolean (game, "usedlastobjects")) {
mentioned = DictionaryItem(game.pov.currentcommandvarlist, "object")
if (GetObject(mentioned) = null) {
it = game.pov.currentcommandresolvedobjects[0]
msg ("("+ GetDisplayAlias(it) +")")
}
}
// END OF BIT ADDED BY KV
// This is the bit that actually runs the commands
do (game.pov.currentcommandpattern, "script", game.pov.currentcommandresolvedelements)
}
//
// Setting game.runturnscripts to true to run turn scripts after ShowMenu , show menu, ask, or Ask.
// This works in conjuction with FinishTurn, which has also been modified as of Quest 5.8.
// The game.aslversion attribute exists only in my mod of Quest.
// - KV
if (HasAttribute (game, "runturnscripts") or GetAttribute(game, "aslversion") = "580" or GetBoolean(game, "multiplecommands")) {
game.runturnscripts = true
FinishTurn
}
HandleNextCommandQueueItem
}
]]></function>
</asl>
Knight: "Suffice to say, is one of the words we cannot say."
King Arthur: "What is it?"
Knight: "Ahhh! He said the word!"
King Arthur: "What is it?"
Knight: "Ahhh! He said it again!"
Knight: "Oh no! I said it!"
Knight: "Oh no! I said it again!"
Knight: "Ahhh! I keep saying it!"