The N/K verb response sytem
-To any potential users: I didn’t write this system/code in a clean, organized fashion as I would have if I’d planned to write a standalone module/extension; rather, it just sort of materialized in my game as I was trying to find ways of speeding up repetitive tasks. For now I am going to simply post the code in its raw form, and defer uploading it to an archive till such time as I can repackage it into something more consistent and refined.
-Note that the system is primarily intended for those situations where you just want to enliven the “supporting cast” of game world objects with text responses, or perhaps a few simple side effects like flipping a property or setting off a daemon. For your major puzzles and interactive objects, just stick with overriding dobjFor(Verb).
-You may hate the naming conventions I used, so feel free to replace them: I tend to go for really terse, since I’m just programming for myself.
USAGE GUIDE:
This text presupposes you’ve read my introduction of this system in the IntFiction.org thread I posted. Carrying on from there:
We’ll start with your custom game verbs – these are the most straightforward.
The system is macro-based, and you’ll have to know a few “codes”, to choose the kind of preconditions you want for the verb.
The only preconditions I’ve incorporated into the system (so far) are touchObj, objVisible, objHeld and an empty precondition set. For any others, including combinations, you’ll have to write your own precondition method in the usual way (or expand on the macro code yourself).
The macros are:
pnK(name) - for no preconditions
pTnK(name) - uses touchObj
pHnK(name) - uses objHeld
pVnK(name) - uses objVisible
I also use short macros for those property or class names that are so commonly used that it’s not too hard to remember their initials. I’ve included them in my list of #defines, and you can use or not use them as you like. A few you might see in this explanatory text include:
acT → DefineTAction acTI → DefineTIAction vR → VerbRule vP → verbPhrase
dF → dobjFor iF → iobjFor mT → modify Thing
tN → theName fC → failCheck rA → replaceAction mI → moveInto
Here’s a sample custom verb definition using the macros:
acT(JumpOn) ; vR(JumpOn) 'jump''on' singleDobj : JumpOnAction vP = 'jump/jumping (on what)' ;
mT dF(JumpOn) pTnK(JumpOn)
nJumpOn = 'You decide you\'ve got better ideas to entertain. '
;
Because all Things will initially have nJumpOn defined (and no kJumpOn defined), ‘jump on thing’ will be dismissed in the verify phase, without consideration to preconditions. However, if you define
puddle: Thing
kJumpOn = 'You jump in the puddle and make a big splash. ' ;
statue: Thing
nJumpOn = 'The statue is way too high to jump on. ' ;
the statue will simply use your new message, still disallowing the action in verify, while the library will make sure you can touch the puddle before printing your message, because you used the pTnK macro (touch). If you had used the pnK macro, there are no preconds and your message would be printed if the object is in scope at all.
-Important note: nVerb must be a SQstring, or a method that returns a SQstring (it will be passed to the library illogical macros). kVerb can be a SQstring, or it can simply be a method that triggers some side effects and either prints a DQstring OR returns a SQstring. e.g:
acT(Grab) ...
mT dF(Grab) pTnK(Grab)
kGrab { if(isPortable) rA(Take,self);
else "Grabbing hold of the immovable object has no significant result. "; } ;
-A word about naming. The name which you supply as an argument to the macro (in this case Grab) does not have to match the name of the corresponding Action exactly. It does have to match the n… and k… properties that are used in order to work. The macro looks for properties which are literally the argument name with an ‘n’ or ‘k’ pasted in front. This isn’t much of an issue for TActions, but could matter for TIActions, which we’ll look at next.
TIActions are almost invariably named after the pattern VerbPrep: for instance CutWith, PutUnder,etc. The naming convention I was using in my game followed this pattern: for any TI verb VerbPrep, the direct object macro would use nVerb/kVerb, while the indirect object macro would use nVerbPrep/kVerbPrep. Example:
acTI(PlantIn) ...
mT dF(PlantIn) pTnK(Plant)
iF(PlantIn) pTnK(PlantIn)
nPlant = 'There\'s no point burying <<thatObj>> in the dirt. '
nPlantIn = 'You probably won\'t reap any crops from <<tN>>. ' ;
class Plantable: Thing
kPlant { "You plant <<tN>> in <<gIobj.tN>>. "; moveInto(gIobj);
/*start a growing daemon etc.*/; } ;
seed: Plantable ;
soilPatch: Container, Fixture
kPlantIn { } // by defining this property, this object will pass verifyIobjPlantIn()
clayPot: Thing
nPlantIn = 'There\'s no soil in the pot, so you can\'t plant anything in it. '
nPlant = 'Why would you bury the clay pot? ' ;
This works fine for most things where you’ll always be using direct and indirect object for a given Action. For instance, in this PlantIn example, we’re assuming that if the player types ‘plant seed’, the library will prompt ‘what do you want to plant it in?’
But you may have situations where a TAction and a TIAction have some overlap. Say that you have a RubWith verb, but also have a separate Rub verb, where the PC’s hands are implied to do the rubbing. Then you have
acT(Rub) ...
mT dF(Rub) pTnK(Rub)
kRub = 'You rub <<tN>>. '
acTI(RubWith)
mT dF(RubWith) pTnK( ? )
iF(RubWith) pTnK(RubWith)
n? = 'msg'
nRubWith = 'msg'
In this situation I used RubXWith, because this was the exception and I was used to the pattern I’d already started with. However, then you have to keep a mental note or a cheatsheet of which verbs don’t follow the standard pattern exactly, when you’re defining responses on bunches of objects. You may prefer for any and all TI verbs, to always mark the macro as iobj or dobj, perhaps:
acT(Rub) ...
mT dF(Rub) pTnK(Rub)
acTI(RubWith)
mT dF(RubWith) pTnK(RubWithDj)
iF(RubWith) pTnK(RubWithIj)
In any case, as long as distinctions are made, the same object can define:
object: Thing
nRub = 'You don\'t want to rub <<tN>> with your hand. '
nRubWithDj = 'You should\'t rub this object with <<gIobj.tN>>. '
nRubWithIj = 'There\'s a reason you shouldn\'t rub <<gDobj.tN>> with this object. ' ;
-Note that most of the time, indirect objects will only use the macro to customize their dismissal message, or perhaps define an empty kVerb { } method just to clear verify. Any complicated handling on an indirect object should probably be handled in an ordinary iobjFor block.
-Using the macro doesn’t give direct access to the check() method. However, there’s no reason you can’t define:
kVerb { if(condition) fC('You can\'t do that while condition is true. '); // failCheck
else "You verb the object. "; }
This leaves dealing with library verbs, which is currently the most unrefined part of this system. Unfortunately at the time of writing I haven’t made this part all neat and tidy for public presentation… until I get around to that, you’ll just have to work with what I have and modify or add to it as you wish.
Certain library verbs do not participate in the macro system because I didn’t expect to override them frequently enough to make it worthwhile. Other very common verbs that almost always carry handling like Take, Open, etc. also do not participate in the system because of their handling, but I wrote simple name substitution macros so that messages can be defined in a consistent style. That is to say: nTake is simply another name for the cannotTakeMsg property, kTake stands in for okayTakeMsg. In this case, however, the macros aren’t defining when and where the messages get printed, they simply print when the library would otherwise print the corresponding message.
Currently, then, when it comes to library verbs, you will either need to extend my partial list to suit your needs, or else make sure you know which of the verbs actually participate in the macro system.
This is a list of participating library verbs with the corresponding macro name I used. If the macro starts with an ‘l’, it just means the Action name and the dobj macro name are the same. Note that if you want to use different naming conventions, different preconditions, whatever, you’ll have to alter the list below. The 50s and 20s have to do with logicalRank, I was mostly trying to mimic how the library handles the verbs.
modify Thing
lpTnK(Attack)
lpTnK(Break)
dF(BurnWith) pTnK(Burn)
dF(CleanWith) pTnK(Clean)
iF(CleanWith) pHnK(CleanWith) // hold something to clean with it
lpTnK(Climb)
lpTnK(ClimbDown)
lpTnK(ClimbUp)
dF(CutWith) pTnK50(Cut)
iF(CutWith) pHv nK20(CutWith) // we'll show an illogical nCutWith sooner than
// an illogical nCut
dF(DigWith) pTnK(Dig)
lpTnK(Drink)
lpTnK(Eat)
lpTnK(JumpOff)
lpTnK50(Kiss)
lpTnK(Lock)
lpTnK50(Move)
dF(MoveWith) pTnK50(MoveXWith)
lpTnK50(Push)
lpTnK50(Pull)
lpTnK50(Turn)
dF(TurnWith) pTnK(Turn)
iF(TurnWith) pHnK(TurnWith)
lpTnK(Unlock)
We have to define these properties so that the above verbs can make use of the nVerb/kVerb system. If you want to override library default response messages, you can replace the &cannot… with your own message, or you can just override the properties in playerActionMessages like normal. You can also change the n or k before any of these, if you want default dismiss/acknowledge to be different from the library’s defaults. Any lib verbs that you add to the system, you’ll need to define a corresponding nVerb or kVerb property of Thing for a default (and modify any differing subclasses as applicable).
nBreak = &shouldNotBreakMsg
nBurn = &cannotBurnMsg
nClean = &cannotCleanMsg
nCleanWith = &cannotCleanWithMsg
kCut = &cutNoEffectMsg
nCutWith = &cannotCutWithMsg
nClimb = &cannotClimbMsg
nClimbUp = &cannotClimbMsg
nClimbDown = &cannotClimbMsg
nDig = &cannotDigMsg
nDigWith = &cannotDigWithMsg
nDrink = &cannotDrinkMsg
nEat = &cannotEatMsg
nFollow = ¬FollowableMsg
nJumpOff = &cannotJumpOffMsg
kAttack = &uselessToAttackMsg
kKiss = &cannotKissMsg
nLock = &cannotLockMsg
kMove = &moveNoEffectMsg
kMoveXWith = &moveNoEffectMsg
kPull = &pullNoEffectMsg
kPush = &pushNoEffectMsg
nTurn = &cannotTurnMsg
nTurnWith = &cannotTurnWithMsg
nUnlock = &cannotUnlockMsg
;
modify Fixture
nTake = cannotTakeMsg
nMove = cannotMoveMsg
nMoveXTo = cannotMoveMsg
nMoveXWith = cannotMoveMsg
dF(Push) pTnK50(Push)
dF(Pull) pTnK50(Pull)
dF(Move) pTnK50(Move)
dF(MoveWith) pTnK50(MoveXWith)
dF(MoveTo) pTnK50(MoveXTo) ;
modify CustomFixture
nMove = nTake ;
modify Immovable
nTake = cannotTakeMsg
nMove = cannotMoveMsg
nMoveXTo = cannotMoveMsg
nMoveXWith = cannotMoveMsg
dF(Push) pTnK50(Push)
dF(Pull) pTnK50(Pull)
dF(Move) pTnK50(Move)
dF(MoveWith) pTnK50(MoveXWith)
dF(MoveTo) pTnK50(MoveXTo) ;
modify CustomImmovable
nMove = nTake ;
modify TravelPushable
nMove = &cannotMovePushableMsg ;
modify Food
dF(Eat) { action { moveInto(nil); defaultReport(&okayEatMsg); } }
kEat = &okayEatMsg ;
Below is a list of macros referring to message properties that I hadn’t incorporated into the macro system. You can use them, not use them, or modify them as you wish. They’re just typing shortcuts, but you may have to keep in mind what’s what. For instance you couldn’t define a kDetach property on an object and expect it to work: it would never be called by anything, unless you added the Detach verb to the macro system in the above list, or unless you #defined kDetach to be a substitute for okayDetachFromMsg. I didn’t add it to the system since attachment/detachment is fairly complex and is better handled with normal overrides.
Don’t paste this code into your files in this order… this is just for browsing purposes. The #defines need to appear in the right order to work. I’m planning to upload separate files with the code…
#define nAttach cannotAttachMsg
#define nAttachTo cannotAttachToMsg
#define nBoard cannotBoardMsg
#define nBurnWith cannotBurnWithMsg
#define nClose cannotCloseMsg
#define nDetach cannotDetachMsg
#define nDetachFrom cannotDetachFromMsg
#define nDoff notDoffableMsg
#define nEnter cannotEnterMsg
#define nGetOffOf cannotGetOffOfMsg
#define nGetOutOf cannotUnboardMsg
#define nGo cannotGoThatWayMsg
#define nGoThrough cannotGoThroughMsg
#define nAttackWith notAWeaponMsg
#define nKissA cannotKissActorMsg
#define nLI nothingInsideMsg // def. LookIn response
#define nLU nothingUnderMsg // LookUnder
#define nLB nothingBehindMsg //
#define nLT nothingThroughMsg //
#define nLTP nothingThroughPassageMsg
#define nLieOn cannotLieOnMsg
#define nMoveC cannotMoveComponentMsg
#define nMoveWith cannotMoveWithMsg
#define nOpen cannotOpenMsg
#define nOpenLocked cannotOpenLockedMsg
#define nPour cannotPourMsg
#define nPushTravel cannotPushTravelMsg
#define nPut cannotPutMsg
#define nPutIn notAContainerMsg
#define nPutInR cannotPutInMsg(obj)
#define nPutOn notASurfaceMsg
#define nPutInOn(str) nPutIn = iobjMsg(str) nPutOn = iobjMsg(str)
#define nPutOnR cannotPutOnMsg(obj)
#define nPutUnder cannotPutUnderMsg
#define nPutUnderR cannotPutUnderMsg(obj)
#define nPutBehind cannotPutBehindMsg
#define nPutBehindR cannotPutBehindMsg(obj)
#define nPutC cannotPutComponentMsg
#define nSitOn cannotSitOnMsg
#define nStandOn cannotStandOnMsg
#define nTake cannotTakeMsg
#define nTakeC cannotTakeComponentMsg
#define nUnlockWith cannotUnlockWithMsg
#define nWear notWearableMsg
#define kBurn okayBurnMsg
#define kClose okayCloseMsg
#define kDrop okayDropMsg
#define kOpen okayOpenMsg
#define kPutIn okayPutInMsg
#define kPutOn okayPutOnMsg
#define kTake okayTakeMsg
#define kOff okayNotStandingOnMsg
#define nIntang notWithIntangibleMsg
#define nHere notHereMsg
#define nImp notImportantMsg
#define tooFull tooFullMsg
#define tooLarge tooLargeForContainerMsg
// The rest of the necessary code and some extras:
/* To direct multiple verbs to the same response message:
obj: Thing
nBoard = 'Don\'t get on that. '
mapN(Board,StandOn,SitOn,LieOn,Climb)
//nStandOn,nClimb,etc. will all redirect to nBoard, the first argument of mapN macro
*/
#define mapN(to,from...) from#foreach: n##from = n##to : :
#define mapK(to,from...) from#foreach: k##from = k##to : :
#define mapdF(to,from...) from#foreach: dF(from) adF(to) : :
#define mapiF(to,from...) from#foreach: iF(from) aiF(to) : :
#define mapProp(obj,prop...) prop#foreach: prop = obj.prop : :
#define mapINH(vb...) vb#foreach: dF(vb) INH : :
#define mapINHI(vb...) vb#foreach: iF(vb) INH : :
#define maprT(obj,df...) df#foreach: dF(df) rT(df,obj) : :
#define mapPuts(str) nPutIn = iobjMsg(str) \
mapN(PutIn,PutOn,PutUnder,PutBehind)
#define mapMounts(str) nBoard = str \
mapN(Board,StandOn,SitOn,LieOn,Enter,Climb)
#define mapExits(to,from...) from#foreach: from aE(to) : :
#define pTnK(z) { preCond = [touchObj] verify() { nK(z)
#define pTnK50(z) { preCond = [touchObj] verify() { nK50(z)
#define pHnK(z) { preCond = [objHeld] verify() { nK(z)
#define pVnK(z) { preCond = [objVisible] verify() { nK(z)
#define pnK(z) { preCond = [] verify() { nK(z)
#define lpTnK(z) dF(z) { preCond = [touchObj] verify() { nK(z)
#define lpTnK50(z) dF(z) { preCond = [touchObj] verify() { nK50(z)
#define lpHnK(z) dF(z) { preCond = [objHeld] verify() { nK(z)
#define lpVnK(z) dF(z) { preCond = [objVisible] verify() { nK(z)
#define lpnK(z) dF(z) { preCond = [] verify() { nK(z)
#define nKWOA(z) if(!propDefined(&k##z)) { \
if(propDefined(&n##z)) illogical(n##z); \
else illogical('That\'s not happening. '); } \
else if(!overrides(self,Thing,&k##z) && propDefined(&n##z)) \
illogical(n##z)
#define nK(z) nKWOA(z); } \
action() { say(k##z); } }
#define nK20(z) if(!propDefined(&k##z)) { \
if(propDefined(&n##z)) illogical(rank:20,n##z); \
else illogical(rank:20,'That\'s not happening. '); } \
else if(!overrides(self,Thing,&k##z) && propDefined(&n##z)) \
illogical(rank:20,n##z); } \
action() { say(k##z); } }
#define nK50(z) nKWOA(z); else LR(50,''); } action { say(k##z); } }
#define acI DefineIAction
#define acLit DefineLiteralAction
#define acLitT DefineLiteralTAction
#define acT DefineTAction
#define acTI DefineTIAction
#define acTop DefineTopicAction
#define acTopT DefineTopicTAction
#define vP verbPhrase
#define vR VerbRule
#define INH { preCond = (inherited) remap { inherited; } verify() { inherited(); } \
check() { inherited(); } action() { inherited(); } }
#define IL illogical
#define ILA illogicalAlready
#define ILN illogicalNow
#define ILS illogicalSelf
#define LR logicalRank
#define fC failCheck
#define rA replaceAction
#define mI moveInto
#define rT remapTo
#define dF dobjFor
#define iF iobjFor
#define adF asDobjFor
#define aiF asIobjFor
#define aE asExit
modify IllogicalVerifyResult // * so iobj ILs can trump dobj ILs when desired by rank:20 *
construct(rank:=30, msg, [params]) { if (dataType(msg) == TypeObject && msg.oK(MessageResult))
{ messageText_ = msg.messageText_;
messageProp_ = msg.messageProp_;
return; }
if (dataType(msg) == TypeProp)
messageProp_ = msg;
messageText_ = resolveMessageText(gAction.getCurrentObjects(), msg, params);
resultRank = rank; }
;