!
! 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:
[email protected]
!
! 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:
[email protected]
!
!!!!!!!!
!
! 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;