#charset "us-ascii"
/* include the library header */
#include "advlite.h"
/*---------------------------------------------------------------------------*/
/*
* cmdHelp Extension
*. by Eric Eve
*
* Experimental extension to give help to players who enter a empty command.
*/
/* Modifications to Parser for CMDHELP EXTENSION */
modify Parser
/*
* Overridden for CMDHELP EXTENSION. If our autoHelp property is true then
* respond to an empty command by displaying a brief menu of command
* options.
*/
emptyCommand()
{
if(autoHelp)
CmdMenu.showOptions();
else
inherited();
}
/*
* Flag: Do we want to show a menu of command options in response to an
* empty command? By default we do since that's the purpose of this
* CMDHELP EXTENSION.
*/
autoHelp = true
;
/ *[CMDHELP EXTENSION] */
DefineSystemAction(CmdMenu)
showOptions()
{
DMsg(cmdhelp show options,
'What would you like to do?\b
<<aHref('1','1')>>. Go to another location\n
<<aHref('2','2')>>. Investigate your surroundings\n
<<aHref('3','3')>>. Relocate something\n
<<aHref('4','4')>>. Manipulate something\n');
if(defined(Actor))
{
for(local a = firstObj(Actor); a != nil ; a = nextObj(a, Actor))
{
if(a != gPlayerChar && Q.canTalkTo(gPlayerChar, a))
{
DMsg(cmdhelp talk to someone, '<<aHref('5','5')>>.
Talk to someone\n');
break;
}
}
}
// "6. Something else\b";
"<.p>";
}
/*
* Show a list of possible actions, where cmd_str is the name of the
* action and lst a list of objects on which it might be tried.
* [CMDHELP EXTENSION]
*/
showList(lst, cmd_str)
{
local cmdstr;
/*
* Replace any commonly abbreviated commands with their common
* abbreviations in the command we send to the command line, so that
* players get used to the common abbreviations.
*/
switch(cmd_str)
{
case 'examine ':
cmdstr = 'x ';
break;
default:
cmdstr = cmd_str;
break;
}
/*
* Output a hyperlinked list of commands for each object in the list,
* but stop after at most maxObj objects, so we don't overwhelm the
* player with choices.
*/
for(local cur in lst, local i = 1; i <= maxObjs; i++)
{
local str = cmd_str + cur.name;
local str1 = cmdstr + cur.name;
"<<aHref(str1, str)>>\ \ \ ";
}
}
/*
* Carry out this action. This is the response to the player entering a
* number at the command prompt.
* [CMDHELP EXTENSION]
*/
execAction(cmd)
{
/* Get the number the player typed .*/
local num = cmd.dobj.numVal;
/* Note the player character's current location. */
local loc = gPlayerChar.getOutermostRoom();
/*
* If the player typed a number out of range, re-display the options
* and end there.
*/
if(num < 1 || num > 6)
{
showOptions();
return;
}
/* Option 1: The Player wants to go to another location. */
if(num == 1)
{
/* Ask the player where s/he wants to go. */
DMsg(cmdhelp where go, 'Where would you like to go?\n
The possible exits are: ');
/*
* If we have an exit lister, use it to display the list of exits.
*/
if(gExitLister)
gExitLister.showExits(gPlayerChar);
/* Otherwise create our own list of exits. */
else
{
/* Set up a flag to record if we actually found any exits. */
local dirFound = nil;
foreach(local dir in Direction.allDirections)
{
/*
* Assume an exit exists if the corresponding direction
* property points to a method or an object.
*/
if(loc.propType(dir.dirProp) is in (TypeCode, TypeObject))
{
"<<aHref(dir.name, dir.name)>>\ \ ";
dirFound = true;
}
}
/* If we didn't find any exits, say so. */
if(dirFound == nil)
DMsg(cmdhelp no exit, 'None ');
}
"<.p>";
/*
* If the routeFinder is present we can also offer the player of
* travelling to another location via the GO TO command.
*/
if(defined(pcRouteFinder))
{
/* Get a list of rooms the player knows about. */
local rmList = Q.knownScopeList.subset({o: o.ofKind(Room)});
/* Remove the player's current location from the list. */
rmList -= loc;
/* Sort the list in alphabetical order. */
rmList = rmList.sort(SortAsc, {a, b:
a.name.compareIgnoreCase(b.name) });
/*
* Don't offer the option unless there are any rooms to offer.
*/
if(rmList.length > 0)
{
/* Introduce the list. */
DMsg(cmdhelp go to, 'Or you could: ');
/*
* Display a hyperlinked GO TO ROOM command for every room
* in the list.
*/
foreach(local rm in rmList)
{
local str = 'go to ' + rm.name;
"<<aHref(str, str)>>\ \ ";
}
}
}
/* We're done. */
return;
}
/*
* If we reach this point the player may want to deal with specific
* objects, so we need to get a list of those in scope.
*/
local scope_lst = World.scope.toList().subset({o:
Q.canSee(gPlayerChar, o)});
/* Remove Unthings and Rooms from the list. */
scope_lst = scope_lst.subset({o: !o.ofKind(Unthing)
&& !o.ofKind(Room) });
/* Option 2: the Player wants to investigate their environment. */
if(num == 2)
{
/* First offer the basic set of intransitive commands. */
DMsg(cmdhelp investigate, 'Here are some suggestions (other
actions may also be possible):\n');
"<<aHref('look', 'look', 'Look around the room')>>\ \
<<aHref('listen', 'listen')>>\ \
<<aHref('smell', 'smell')>>\ \
<<aHref('I', 'inventory', 'See what you\'re carrying')>>\b";
/*
* Get a list of things that could be examined. In the first
* instance, sort them alphabetically.
*/
local exa_lst = scope_lst.sort(SortAsc,
{a, b: a.name.compareIgnoreCase(b.name)});
/*
* If the list of things we could examine has too many items, sort
* the list in the order of the turn number on which the items
* were last examined, so that those that were examined longest
* ago come to the front of the list.
*/
if(exa_lst.length() > maxObjs)
exa_lst = exa_lst.sort(SortAsc,{a, b: a.turnLastExamined -
b.turnLastExamined});
/* Show the list of EXAMINE suggestions. */
showList(exa_lst, 'examine ');
"<.p>";
/*
* Get a list of READ suggestions by taking the subset of examine
* suggestions for items that have a non-nil readDesc.
*/
local read_lst = exa_lst.subset({o: o.propType(&readDesc) !=
TypeNil });
/* Show the list of READ suggestions. */
showList(read_lst, 'read ');
"<.p>";
/* Get a list of thing we could look inside. */
local li_lst = exa_lst.subset(
{o: (o.contType == In && !o.isLocked)
|| (o.remapIn != nil && !o.remapIn.isLocked)});
/*
* Show a list of LOOK IN suggestions. Note we don't do the same
* for LOOK UNDER or LOOK BEHIND since these are much rarer, and
* handling them would either flood the player with useless
* suggestions (if we took a maximalist view of what to include)
* or create potential spoilers (if we took a minimalist view)
*/
showList(li_lst,'look in ');
"<.p>";
/*
* Get a list of things we could listen to; these are objects with
* a non-nil listenDesc.
*/
local listen_lst = scope_lst.subset({o: o.propType(&listenDesc) !=
TypeNil});
/* Display the list of LISTEN TO suggestions. */
showList(listen_lst, 'listen to ');
/*
* Get a list of things we could smell; these are objects with a
* non-nil smellDesc.
*/
local smell_lst = scope_lst.subset({o: o.propType(&smellDesc) !=
TypeNil});
/* Display the list of SMELL suggestions. */
showList(smell_lst, 'smell ');
/* We're done. */
return;
}
/*
* From this point on we're suggesting commands to manipulate objects
* in various ways, so we'll reduce the scope list to things the
* player character can actually touch.
*/
scope_lst = scope_lst.subset({o: Q.canReach(gPlayerChar, o)});
/* Things can be moved if they're not fixed in place. */
local move_lst = scope_lst.subset({o: !o.isFixed });
/* Option 3: The player wants to move things around. */
if(num == 3)
{
/* Display an introductory message. */
DMsg(cmdhelp relocate, 'Here are some suggestions (there may well
be several other possibilities):\n');
/*
* First deal with things the player can TAKE. We need to set
* gAction since verify() routines will assume it has been set
* correctly.
*/
gAction = Take;
/*
* Get a list of things than can be taken. This will be the subset
* of potentially moveable objects that pass verifyDobjTake().
*/
local take_lst = move_lst.subset({o: Take.verify(o,
DirectObject).allowAction});
/*
* Also exlude any objects that would fail the TAKE action at the
* check() stage, if the excludeCheckFailures option is set.
*/
if(excludeCheckFailures)
take_lst = take_lst.subset({o: passCheck(Take, o) });
/*
* If we have too many objects, sort them in the order in which they
* were last moved.
*/
if(take_lst.length > maxObjs)
take_lst = take_lst.sort(SortAsc, {a, b: a.turnLastMoved -
b.turnLastMoved});
/* Display a list of TAKE suggestions */
showList(take_lst, 'take ');
/*
* If there's more than one thing that can be taken, also offer
* the TAKE ALL command.
*/
if(take_lst.length > 1)
"<<aHref('take all', 'take all')>>";
"<.p>";
/*
* Now suggest things that can be DROPped. Again we must set
* gAction for the sake of the verify method.
*/
gAction = Drop;
/*
* Get a list of moveable objects directly located in the player
* character.
*/
local drop_lst = move_lst.subset({o: o.isDirectlyIn(gPlayerChar)});
/*
* If we have too many objects, sort them in ascending order of
* the turn on which they were last moved.
*/
if(drop_lst.length > maxObjs)
drop_lst = drop_lst.sort(SortAsc, {a, b: a.turnLastMoved -
b.turnLastMoved});
/* Display a list of DROP suggestions. */
showList(drop_lst, 'drop ');
/*
* If more than one object could be dropped, offer the DROP ALL
* option.
*/
if(drop_lst.length > 1)
"<<aHref('drop all', 'drop all')>>";
"<.p>";
/*
* Now suggest PUT IN, PUT ON, PUT UNDER, PUT BEHIND. First set
* the list of objects that can be put anywhere to the list of
* moveable objects.
*/
local put_lst = move_lst;
/*
* If there are too many objects, sort them in ascending order of
* the turn on which they were last moved.
*/
if(put_lst.length > maxObjs)
put_lst = drop_lst.sort(SortAsc, {a, b: a.turnLastMoved -
b.turnLastMoved});
/*
* Set up PUT IN suggestions. First set gAction to PutIn so that
* any routines we call that depend on the value of gAction will
* work as expected.
*/
gAction = PutIn;
/*
* Get the list of items into which things can be out. This is the
* list of unlocked objects with a contType of In plus the list of
* objects with an unlocked remapIn object.
*/
local put_in_lst = scope_lst.subset({o: (o.contType == In &&
!o.isLocked) || (o.remapIn != nil && !o.isLocked)});
/*
* Sort the resulting list into ascending order of the turn on
* which items last had anything inserted into them.
*/
put_in_lst = put_in_lst.sort(SortAsc, {a, b: a.turnLastMovedInto -
b.turnLastMovedInto });
/*
* Set up a counter to keep track of how many suggestions we've
* made.
*/
local i = 1;
in_loop:
/*
* Go through every item in our put_lst (potential direct objects
* of a PUT IN COMMAND).
*/
for(local cur in put_lst)
{
/*
* For each potential direct object of a PUT IN command, go
* through all the potential indirect objects of a PUT IN
* command.
*/
foreach(local dest in put_in_lst)
{
/*
* If the potential direct object is neither in the
* potential indirect object nor identical to the
* potential direct object, and if inserting the direct
* object into the indirect object would pass the
* checkInsert() test, then suggest putting this direct
* object into this indirect object.
*/
if(!cur.isOrIsIn(dest)
&& (!excludeCheckFailures || checkInsert(cur, dest,
PutIn)))
{
/*
* Create the relevant string version of the command
* and output a hyperlinked version of it.
*/
local str = 'put ' + cur.name + ' in ' + dest.name;
"<<aHref(str, str)>>\ \ ";
/*
* Increment our number-of-suggestions counter. If it
* exceeds maxObjs, break out of both loops.
*/
if(++i > maxObjs)
break in_loop;
}
}
}
"<.p>";
/*
* Now handle the PUT ON suggestions. The logic is the same as for
* PUT IN.
*/
gAction = PutOn;
put_in_lst = scope_lst.subset({o: o.contType == On ||
o.remapOn != nil});
put_in_lst = put_in_lst.sort(SortAsc, {a, b: a.turnLastMovedInto -
b.turnLastMovedInto });
i = i;
on_loop:
for(local cur in put_lst)
{
foreach(local dest in put_in_lst)
{
if(!cur.isOrIsIn(dest)
&& (!excludeCheckFailures || checkInsert(cur, dest,
PutOn)))
{
local str = 'put ' + cur.name + ' on ' + dest.name;
"<<aHref(str, str)>>\ \ ";
if(++i > maxObjs)
break on_loop;
}
}
}
"<.p>";
/*
* Now handle the PUT UNDER suggestions. The logic is the same as
* for PUT IN.
*/
gAction = PutUnder;
put_in_lst = scope_lst.subset({o: o.contType.canPutUnderMe ||
(o.remapUnder != nil && o.remapUnder.canPutUnderMe) });
put_in_lst = put_in_lst.sort(SortAsc, {a, b: a.turnLastMovedInto -
b.turnLastMovedInto });
i = 1;
under_loop:
foreach(local cur in put_lst)
{
foreach(local dest in put_in_lst)
{
if(!cur.isOrIsIn(dest)
&& (!excludeCheckFailures || checkInsert(cur, dest,
PutUnder)))
{
local str = 'put ' + cur.name + ' under ' + dest.name;
"<<aHref(str, str)>>\ \ ";
if(++i > maxObjs)
break under_loop;
}
}
}
"<.p>";
/*
* Now handle the PUT BEHIND suggestions. The logic is the same as
* for PUT IN.
*/
gAction = PutBehind;
put_in_lst = scope_lst.subset({o: o.contType.canPutBehindMe ||
(o.remapBehind != nil && o.remapBehind.canPutBehindMe) });
put_in_lst = put_in_lst.sort(SortAsc, {a, b: a.turnLastMovedInto -
b.turnLastMovedInto });
i = 1;
behind_loop:
foreach(local cur in put_lst)
{
foreach(local dest in put_in_lst)
{
if(!cur.isOrIsIn(dest)
&& (!excludeCheckFailures || checkInsert(cur, dest,
PutBehind)))
{
local str = 'put ' + cur.name + ' behind ' + dest.name;
"<<aHref(str, str)>>\ \ ";
if(++i > maxObjs)
break behind_loop;
}
}
}
"<.p>";
}
/*
* OPTION 4: The player wants to manipulate objects in their
* surroundings, which is a catch-all term for actions not covered
* above.
*/
if(num == 4)
{
/* Display an introductory message. */
DMsg(cmdhelp manipulate, 'Some things you could try include (there
may be many other possibilities):\b');
/* Go through every action in out manipulationActions list */
foreach(local act in manipulationActions)
{
/*
* Set gAction to the current action, so that routines such as
* verify that depend on its value will work properly.
*/
gAction = act;
/* Set gActor to the player character for the same reason. */
gActor = gPlayerChar;
/*
* Get the list of objects in scope that would pass the verify
* stage for this action.
*/
local lst = scope_lst.subset({o: act.verify(o,
DirectObject).allowAction});
/*
* If we want to exclude objects that would fail at the check
* stage, reduce our list to those objects that would pass
* this action at the check stage.
*/
if(excludeCheckFailures)
lst = lst.subset({o: passCheck(act, o) });
/*
* Get the name of the action from the grammarTemplates
* property of the current action. This will be a string in
* the form 'clean (dobj)', so we want to remove '(dobj)' from
* the string to just leave the command name.
*/
local str = act.grammarTemplates[1].findReplace('(dobj)', '');
/* Display a list of commands for this action. */
showList(lst, str);
}
/*
* If the gadgets module is present the player could also try
* pushing and pulling buttons and levers. PUSH and PULL aren't
* included in manipulationActions as they would result in the
* suggestion of too many pointless commands.
*/
if(defined(Button))
{
"<.p>";
/* Get a list of all the buttons and levers in scope. */
local lst = scope_lst.subset({o: o.ofKind(Button) ||
o.ofKind(Lever)});
/* Set gAction and gActor for the PUSH action. */
gAction = Push;
gActor = gPlayerChar;
/*
* Restrict our list to actions that would pass the verify
* stage of a PUSH action.
*/
lst = lst.subset({o: Push.verify(o, DirectObject).allowAction});
/* Display a list of suggested actions. */
showList(lst, 'push ');
/* Get a list of levers in scope. */
lst = scope_lst.subset({o: o.ofKind(Lever) });
/* Set gAction and gActor for the PULL action. */
gAction = Pull;
gActor = gPlayerChar;
/*
* Get a subset of levers that would pass the verify stage for
* PULL.
*/
lst = lst.subset({o: Pull.verify(o, DirectObject).allowAction});
/* Show a list of suggested PULL commands. */
showList(lst, 'pull ');
}
/* LockWith and UnlockWith actions on lockableWithKey items */
/*
* Get a list of objects that can be locked or unlocked with a
* key, remembering to include those with lockable remapIn
* objects.
*/
local lockLst = scope_lst.subset({o: o.lockability == lockableWithKey
|| (o.remapIn &&
o.remapIn.lockability ==
lockableWithKey)});
/* Go through every object in our list. */
foreach(local lock in lockLst)
{
/*
* Get a list of keys that might lock/unlock the current
* object. These will be Keys in scope for which the current
* object appears in the plausibleLockList.
*/
local key_lst = scope_lst.subset(
{o: o.ofKind(Key) &&
o.plausibleLockList.indexOf(lock) });
/*
* Set the description of the suggested action to 'unlock' or
* 'lock' depending on whether the current object is locked or
* unlocked.
*/
local actstr = lock.isLocked ? 'unlock ' : 'lock ';
/* Go through every possibly usable key we found */
foreach(local key in key_lst)
{
/*
* Suggest locking or unlocking the current object with
* that key.
*/
local str = actstr + lock.name + ' with ' + key.name;
"<<aHref(str, str)>>\ \ ";
}
}
}
/* OPTION 5: The player wants to talk with an NPC. */
if(num == 5)
{
/* Set up a list of available actors. */
local actor_lst = [];
/*
* We can only populate the list if the actor.t module is present.
*/
if(defined(Actor))
{
/*
* Get a list of all the actors in scope who aren't the player
* character.
*/
actor_lst = scope_lst.subset({o: o.ofKind(Actor) && o !=
gPlayerChar});
}
/* If we didn't find any, say so. */
if(actor_lst.length == 0)
DMsg(cmdhelp no one to talk to, 'Sorry, but there\'s no one here
to talk to right now.\b');
/*
* Otherwise, if we only found one and the player character is
* already talking to him or her, suggest the use of the TOPICS
* command.
*/
else if(actor_lst.length == 1 && actor_lst[1] ==
gPlayerChar.currentInterlocutor)
"<<aHref('topics', 'topics')>> ";
/* Otherwise display a list of actors to talk to. */
else
showList(actor_lst, 'talk to ');
}
/* At the moment OPTION 6 isn't included in the suggestions menu */
if(num == 6)
{
"Some other actions you could try include:\b
<<aHref('JUMP', 'JUMP')>>\ \ <<aHref('THINK', 'THINK')>>
\ \ <<aHref('WAIT', 'WAIT')>>, <<aHref('YELL', 'YELL')>>
\ \ <<aHref('SLEEP', 'SLEEP')>>\b";
}
gAction = self;
}
/*
* A list of the actions we'll potentially suggest for option 4,
* "Manipulate thing". Note that these must all be TActions.
*/
manipulationActions = [Open, Close, Lock, Unlock, Break, Cut,
SwitchOn, SwitchOff, Burn, Wear, Doff, Climb, ClimbUp, ClimbDown,
Board, Enter, GetOff, GetOutOf, Light, Extinguish, Eat, Drink,
Clean, Dig, Attach, Detach, Fasten, Unfasten, Unplug, JumpOff,
JumpOver, Pour, Screw, Unscrew, GoThrough]
/*
* We in any case rule out combinations of actions and objects that would
* fail at the verify stage; flag - should we also rule out combinations
* that fail at the check stage? By default we do.
*/
excludeCheckFailures = true
/* Determine whether obj would pass the check stage of the act action. */
passCheck(act, obj)
{
/* Set obj to the current direct object of act/ */
act.curDobj = obj;
/* See if running the check routine would output any text. */
local str = gOutStream.captureOutput(
{: act.check(obj, act.checkDobjProp)});
/*
* If any text would have been output, we failed the check stage;
* otherwise we passed.
*/
return str == '';
}
/*
* Check whether obj can be inserted in cont with the action act (which
* will be one of PutIn, PutOn, PutUnder or PutBehind).
*/
checkInsert(obj, cont, act)
{
/* If the container has a remapIn object, use that instead */
if(cont.remapIn != nil)
cont = cont.remapIn;
/* Set gAction to the appropriate action */
gAction = act;
gDobj = obj;
/*
* Get the property for running the check() method on the indirect
* object of prop,
*/
local prop = act.checkIobjProp;
/* Capture the output from running this method. */
local str = gOutStream.captureOutput( {: cont.(prop) });
/*
* If there was any output, we failed the check stage; otherwise we
* passed.
*/
return str == '';
}
/*
* The maximum number of objects in a list before we try to reduce that
* list.
*/
maxObjs = 10
;
/* [CMDHELP EXTENSION] */
VerbRule(CmdMenu)
numericDobj
: VerbProduction
action = CmdMenu
isActive = Parser.autoHelp
;
/* Modifications to Thing for the CMDHELP EXTENSION */
modify Thing
turnLastMoved = 0
turnLastMovedInto = 0
/*
* Modified for CMDHELP EXTENSION. Note the last turns on which this
* object was moved and on which something was moved into this object.
*/
actionMoveInto(dest)
{
inherited(dest);
turnLastMoved = gTurns;
dest.turnLastMovedInto = gTurns;
if(dest.ofKind(SubComponent) && dest.lexicalParent)
dest.lexicalParent.turnLastMovedInto = gTurns;
}
turnLastExamined = 0
/*
* Modified for CMDHELP EXTENSION. Note the last turn on which this
* object was examined.
*/
dobjFor(Examine)
{
action()
{
inherited();
turnLastExamined = gTurns;
}
}
;
/* Modify suggestedTopicLister to hyperlink suggestions for the CMDHELP EXTENSION */
modify suggestedTopicLister
/*
* Turn on the topic suggestion hyperlinking if the Parser autoHelp is
* enabled.
*/
hyperlinkSuggestions = Parser.autoHelp
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1