! ! OKBScript, Release 0 - An Inform library extension to allow branching, menu-based ! (2000/08/23) - conversations. Object-oriented, with various features. ! ! By Brendan Barnwell (aka BrenBarn) Email: BrenBarn@aol.com ! ! WARNING -- The documentation for this library is very incomplete. It should work all right (unless ! I made some crippling modifications and don't remember doing it), but you'll have to figure out ! exactly HOW it works yourself (or you can always ask me for help). The code itself is pretty clear, I ! think, except for my non-standard indenting, but comments are few and far between. You may want ! to get ahold of bigmama.inf (the source code for The Big Mama, the game for which I wrote this ! library) to get an idea for how to go about using OKBScript. ! ! I welcome your emails. Nay, I ENCOURAGE you to email me. Nay, nay, I WANT you to email me ! and tell me what you think of this, or ask me questions about how to use it, or whatever. My email ! address (as noted above) is: BrenBarn@aol.com ! !!!!!!!! ! ! A preliminary note: ! !!! ! ! This documentation is in three sections: "What it does (a player's view)", "How to use it ! (a programmer's view)", and "Technical notes". ! This library is Tolerant. In other words, it can make use of the Tolerance library-hack ! by Brendan Barnwell (yours truly). If you have a Tolerant version of the Inform parser, this ! library should work its Tolerance magic without you having to do anything. For more in-depth ! information about this, see the "Technical notes" section. ! !!!!!!!! ! ! What it does (a player's view) ! !!! ! ! OKBScript is yet another conversation engine, designed to let the player talk to NPCs in ! the game. It is of the infamous "menu-based" family of such engines, in which the ! conversation takes place via a menu from which the player chooses one of several possible ! things to say. It is also of the (perhaps even more infamous) "branching" family of ! conversation engines, in which one choice from such a menu can spawn an entirely new set of ! choices; this "branching" can continue ad infinitum (or until you run out of memory). ! In short, OKBScript is a library to allow you to create games whose partial transcript ! might look something like this: ! ! [Begin fake transcript] ! Big Room ! You are in this giant room. It is super-huge. Man, this room is big. ! You can see Joe Schmo here. ! !>TALK TO JOE ! ! 0: Say nothing. ! 1: "Hey, Joe, whaddaya know?" ! 2: "Joe, you're a thief and a liar." ! 3: "Joe Schmo? I'm your biggest fan." ! What do you want to say to Joe Schmo? 1 ! ! "Hey, Joe," you say. "Whaddaya know?" ! "I know that the sum of the squares of the two shorter sides of a right triangle is equal ! to the square of the largest side," Joe says. ! ! 0: Say nothing. ! 1: "Wow, that's really something!" ! 2: "Duh, everyone knows that." ! 3: "No it doesn't."; ! What do you want to say to Joe Schmo? 3 ! ! "No it doesn't," you say rudely and incorrectly. ! "Liar!" yells Joe. He beats the bejeezus out of you. ! ! *** You have died *** ! [End fake transcript] ! ! Get the idea? That's what OKBScript does for the player. As for the author. . . ! !!!!!!!! ! ! How to use it (an author's view) ! !!! ! ! To make this library work, you just have to include "OKBScrpt" after including "Parser". ! (Note that the filename is "OKBScrpt", with no "i".) ! !!! ! ! Note: In this documentation, the word "dialogue" refers to the amount of conversation the ! player reads between choosing an option from the menu, and being shown the menu again. The ! word "line" refers to the words the player sees on the menu. (In the example transcript ! given above, there are 2 dialogues and 8 lines, for example.) The entire bunch of dialogues ! that the player sees between when he starts talking to the NPC and when he stops is called a ! "conversation". The entire collection of all possible conversations with a given NPC is ! called a "conversation tree". A segment of the conversation tree is called a "branch". ! !!! ! ! All NPCs who are going to engage in conversation must be of class Talker. All dialogues ! must be of class Line. This library also provides the classes NLine, PLine, and SLine, all ! of which are subclasses of Line, and the class Holder, which is not. I recommend that you do not use ! the Line class directly; use the other classes instead. The differences among the various classes will ! be discussed in a bit. ! The easiest way to explain OKBScript is with an example. Below is the source code for ! the sample transcript given above. ! ! [Begin source code] ! Room BigRoom "Big Room" ! has light ! with description " You are in this giant room. It is super-huge. Man, this room is big."; ! ! Talker -> JoeSchmo "Joe Schmo" ! with name 'joe' 'schmo', ! description "This is Joe Schmo."; ! ! NLine ->-> J1 "Hey, Joe, whaddaya know?" ! with description "~Hey, Joe,~ you say. ~Whaddaya know?~^ ! ~I know that the sum of the squares of the two shorter sides of a right triangle is equal ! to the square of the largest side,~ Joe says.^"; ! ! NLine ->->-> J1x0 "Silence" ! has general edible ! with description "You say nothing. Irritated by your silence, Joe kills you.^", ! after [; deadflag=1; ]; ! ! NLine ->->-> J1x1 "Wow, that's really something!" ! has edible ! with description "~Wow,~ you exclaim, ~that's really something!~^ ! ~I'm glad you like me,~ says Joe. ~Let's be friends.~^", ! after [; deadflag=2; ]; ! ! NLine ->->-> J1x2 "Duh, everyone knows that." ! has edible ! with description "~Duh,~ you say, ~everyone knows that.~^ ! ~You're mean!~ Joe says. ~I hate you!~ Joe kills you.^", ! after [; deadflag=1; ]; ! ! NLine ->->-> J1x3 "No it doesn't" ! has edible ! with description "~No it doesn't,~ you say rudely and incorrectly.^ ! ~Liar!~ yells Joe. He beats the bejeezus out of you.^", ! after [; deadflag=1; ]; ! ! NLine ->-> J2 "Joe, you're a thief and a liar." ! has edible ! with description "~Joe,~ you say, ~you're a thief and a liar.~^ ! ~It's true!~ Joe sobs. Joe kills himself. Overcome with remorse, you kill yourself too.^", ! after [; deadflag=1; ]; ! ! NLine ->-> J3 "Joe Schmo? I'm your biggest fan!" ! has edible ! with description "~Joe Schmo?~ you say, unable to believe your luck. ~I'm your biggest fan.~^ ! ~No paparazzi,~ Joe says disdainfully, dismissing you with wave of his hand.^"; ! ! [End source code] ! ! The first thing to notice here is that each object (in this case, all of class NLine) represent a ! single dialogue choice on the menu. Also notice that the object tree mirrors the conversation tree -- ! in other words, each dialogue object contains the objects that will be on the NEXT menu if the player ! chooses that line. For example, the objects representing "that's really something", "everyone knows ! that", and "no it doesn't" are all children of the object representing "whaddaya know?". So if the ! player chooses "whaddaya know?", these sub-options will be displayed. ! ! Also notice that each object's short name is the line of dialogue as it appears on the menu, and ! each object's description is the text the player sees if he chooses that option. ! ! Slightly mysterious is that some of the objects have the edible attribute. Does this mean that ! you can literally eat your words? No. Since the likelihood is very small that this attribute will need ! to be applied to OKBScript objects in its normal capacity, I have recycled it and used it for a totally ! different purpose. If an OKBScript has the edible attribute, it means that if the player chooses it, the ! conversation will end (after the object's description is printed, of course). ! ! That is the basic structure of an OKBScript. You could probably write a dialogue tree right now. ! One thing that is of paramount importance is to not forget to include the edible attribute for choices ! which should end the conversation. If you forget to do this, the conversation will never end! ! ! Aside from advanced cases (which will be discussed later), the player can only see a line ! on the menu if it is turned on AND is a child of the current Topic. All lines are turned on ! by default. When the player types "TALK TO NPC", where NPC is the name of an object of class ! Talker, the Topic initially is that object. Thus, if the player types "TALK TO JOE", the Topic will ! initially be set to JoeSchmo. ! ! The different classes ! ! INCOMPLETE DOCUMENTATION! System_file; ! This is necessary to compile with Graham's current Inform 6.21 compiler. #ifndef WORDSIZE; Constant TARGET_ZCODE; Constant WORDSIZE 2; #endif; #IfNDef NoScriptColor; Global Color; #IfNDef fg; Global fg; #EndIf; #IfNDef bg; Global bg; #EndIf; #IfNDef SetColor; [SetColor foreground background; fg=foreground; bg=background; @set_colour foreground background; ]; #EndIf; #EndIf; Array Script --> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; [OKBScriptSub a b c topic original x undo oldfore oldback; if (noun hasnt animate && ~~(noun ofclass Holder)) {"@11It's really only worthwhile to talk to living things.";} if (noun==player) {"@11Consulting with yourself is something you do on a regular basis, but not verbally.";} topic=noun; original=topic; .StartDialogue; b=0; #IfNDef NoScriptColor; oldfore=fg; oldback=bg; if (Color==1) {SetColor(noun.forecolor,noun.backcolor);} #EndIf; font off; print "^"; spaces 18; print "0: Say nothing.^"; .PrintChoices; objectloop (a in topic) { if (a ofclass Line && a has on && a hasnt general) {b++; Script-->b=a; spaces 18; print b, ": ~",(name) a,"~^";} if (a ofclass Line && a has on && a has general) {Script-->0=a;} } for (c=0: c<((topic.#share)/2): c++) { objectloop (a in topic.&share-->c) { if (a ofclass Line && a has on && a hasnt general) {b++; Script-->b=a; spaces 18; print b, ": ~",(name) a,"~^";} if (a ofclass Line && a has on && a has general && Script-->0==0) {Script-->0=a;} } } for (c=0: c<((topic.#adopt)/2): c++) { if (~~(topic.&adopt-->c ofclass Line)) continue; if (topic.&adopt-->c has on && topic.&adopt-->c hasnt general) {b++; Script-->b=topic.&adopt-->c; spaces 18; print b,": ~",(name) topic.&adopt-->c,"~^";} if (topic.&adopt-->c has on && topic.&adopt-->c has general && Script-->0==0) {Script-->0=topic.&adopt-->c;} } if (b==0 && topic~=original && Script-->0~=0) {topic=parent(topic); jump PrintChoices;} if (b==0 && topic==original && Script-->0==0 && topic.goto~=0) {topic=topic.goto; jump PrintChoices;} print "What do you want to say to ",(the) noun,"? "; font on; #IfNDef NoScriptColor; if (Color==1) {SetColor(oldfore,oldback);} #EndIf; .GetInput; @read_char 1 0 NULL x; if (x=='u' or 'U') { print "^"; if (just_undone==1) { L__M(##Miscellany, 12); jump GetInput; } @restore_undo undo; if (undo==0) {L__M(##Miscellany, 7); } jump GetInput; } @save_undo undo; just_undone=0; undo_flag=2; if (undo==-1) {undo_flag=0;} if (undo==0) {undo_flag=1;} if (undo==2) { ! Thanks to Gunther Schmidl for this code, which may or may not work #IFDEF UsesColor; ! new SetColor(fg,bg); ! new ! Gunther had: @erase_window NULL; --but this messed me up. #ENDIF; ! new ! End of Gunther's code print "[Undone.]^@00"; just_undone=1; PrintOrRun(topic, before); PrintOrRun(topic, description); PrintOrRun(topic, after); jump StartDialogue; } x=x-48; ! To convert from ASCII code to actual number .ActOnInput; turns++; if (x>b || x<0) {print "^Enter a number between 0 and ",b,". "; jump GetInput;} print "^@00"; if (x==0 && Script-->0==0) "You say nothing. ",(CHeSheIt) noun, " says nothing. Needless to say, the conversation lapses."; PrintOrRun(Script-->x, before); PrintOrRun(Script-->x, description); PrintOrRun(Script-->x, after); give Script-->x visited; objectloop (a in Script-->x) give a ~visited; if ((Script-->x).goto~=0) {topic=(Script-->x).goto;} else {topic=Script-->x;} b=Script-->x; for (a=0:a<9:a++) {Script-->a=0;} a=0; x=0; if (b hasnt edible) jump StartDialogue; ! EDIBLE means "EnD thIs okB diaLoguE now." ]; Verb "talk" * "to"/"with" noun -> OKBScript; Class Talker ! Someone (or. . . some-THING! :-) who can speak lines has animate static with leavechance 0, talkchance 0, goto 0, share 0, adopt 0, forecolor 0, backcolor 0; Class Line ! Line of dialogue has on ! Active with leavem 0, talkm 0, goto 0, share 0, adopt 0, after [a; a=Speaker(self); a.leavechance=a.leavechance+self.leavem; ! Modify speaker's tendencies a.talkchance=a.talkchance+self.talkm; ]; Class NLine ! Normal line of dialogue (can only be chosen once) class Line with after [; give self ~on; self.Line::after(); ]; ! Make inactive after use Class PLine ! Persistent line of dialogue (can be chosen indefinitely) class Line with after [; self.Line::after(); ]; Class SLine ! Semi-persistent line of dialogue (can be chosen until none of its class Line ! sub-choices can be chosen) with after [a flag; objectloop(a in self) {if (a ofclass Line && a has on) flag=1;} if (flag==0) give self ~on; self.Line::after(); ]; Class Holder ! Dialogue container (to allow one character to initiate multiple with goto 0, share 0, ! dialogues. It doesn't do anything, just holds the dialogue. adopt 0; [Begone; remove Speaker(self); ]; ! Removes the speaker of the calling Line [Speaker a; ! Returns the person who speaks the given Line while (a hasnt animate) a=parent(a); return a; ]; [HowMany a b c d; ! Returns the number of lines that would be on the c=1; ! menu if the given object were the Topic objectloop (b in a) { if (b ofclass Line && b hasnt general && b has on) {c++;} } for (d=0: d<((a.#share)/2): d++) { if ((a.&share-->d)~=0) {objectloop (b in (a.&share-->d)) { if (b ofclass Line && b hasnt general && b has on) {c++;} }} } for (d=0: d<((a.#adopt)/2): d++) { b=a.&adopt-->d; if (b ofclass Line && b hasnt general && b has on) {c++;} } return c; ]; [CHeSheIt a; ! Prints the appropriate pronoun, capitalized if (a has female) print "She"; if (a has neuter || a hasnt animate) print "It"; if (a hasnt female && a hasnt neuter) print "He"; ]; [HeSheIt a; ! Prints the appropriate pronoun, uncapitalized if (a has female) print "she"; if (a has neuter || a hasnt animate) print "it"; if (a hasnt female && a hasnt neuter) print "he"; ]; #IfDef Tolerray; Tolerator OKBScript "OKBScript" with needed 4, entries talkm PTNum leavem PTNum; #IfNot; Message warning "Your copy of the Inform library does not have Brendan Barnwell's Tolerance hack. Using the ;xo or showobj verbs may crash your interpreter."; #EndIf;