!
! 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;