! --------------------------------------------------------------------
! GTALK.H : A Library File for Conversation Menus
! Version 3.00
! by Greg Boettcher, Krister Fundin, and Mark J. Tilford
! This library is freeware. You may use it for any purpose without
! permission, but please credit us.
! --------------------------------------------------------------------
!
! -----------
! Description
! -----------
!
! This is an Inform library file designed to handle Photopia-style
! conversation menus, sort of like Phtalkoo.h, but it has certain
! features Phtalkoo.h lacks:
! * Can do looping/repeating conversations, a la "Being Andrew
! Plotkin" or "The Cabal."
! * Can do submenus, menu mazes, etc. Compile Gtalk.inf to see what
! I mean.
! * Allows more readable code than Phtalkoo.h, in my humble opinion,
! since, unlike Phtalkoo.h, it allows quip/response pairs to be
! right next to each other in your code.
! * Allows you to turn on/off the "0 to say nothing" option.
! * Allows you to turn on/off the "What would you like to say?"
! message.
!
! ---------------
! Version History
! ---------------
!
! 3.00: 2006-07-29 Numerous changes, detailed below, the work of
! Krister Fundin. E.g., lots of comments have been
! added, and lots of code reformatted. Also, the
! speed of performance has been increased, reportedly
! producing a measurable improvement on slower
! computers.
! 2.04: 2006-05-06 It's now possible to modify a conversation menu
! with a character before you've initiated a
! conversation with that character, based on work by
! Victor Gijsbers. In gtalk.inf, a new bug fix
! prevents things like, "The weeds doesn't reply."
! 2.03: 2005-09-06 New features for non-English authors: modifications
! by Victor Gijsbers.
! 2.02: 2005-02-08 Optimized a few things for speed and memory
! efficiency. Fixed a couple of minor flaws in the
! documentation. Rewrote Note #5.
! 2.01: 2005-02-02 Added documentation for the new changes.
! 2.00: 2005-01-29 Major improvements added, as specified below,
! mostly the work of Mark J. Tilford. (Not a public
! release.)
! 1.01: 2004-09-23 Original release by Greg Boettcher.
!
! ---------------
! Acknowledgments
! ---------------
!
! * This library owes quite a bit to Phtalkoo.h, which was written by
! David Glasser and was based on Adam Cadre's source code for
! Photopia. (I pulled the bitwise stuff from there and got my basic
! start from there, although most of the code here is original.
! * Special thanks to Roger Firth, Andrew Plotkin, and Cedric Knight
! for helping me with some complex array issues for a previous,
! unreleased version of this library.
! * And to J. Robinson Wheeler, for showing me his Phtalkoo.h
! modifications.
! * And to Victor Gijsbers, who made international improvements for
! version 2.03, and also helped improve 2.04 so that it is now
! possible to modify a conversation menu with a character before
! you've initiated a conversation with that character.
! * Big thanks to Mark J. Tilford, who made the improvements resulting
! in version 2.00, making the syntax more flexible, adding Glulx
! support, and making other improvements.
! * And big thanks to Krister Fundin, who made the improvements
! resulting in version 3.00, adding comments, optimizing things,
! improving performance speed, etc. Krister also previously made
! some suggestions to improve memory efficiency.
!
! ---------------------------
! Before You Use This Library
! ---------------------------
!
! First download Gtalk.inf if you don't already have it, and make sure
! it's the same version as this file, Gtalk.h! Gtalk.inf is a demo
! game using this library (Gtalk.h), and this library is meant to be
! learned in conjunction with that demo. You should be able to get the
! demo from the same place where you got this file.
!
! -----------------------
! How to Use This Library
! -----------------------
!
! 1. Insert the line:
! Include "Gtalk";
! somewhere early in your file. (I included it right after
! VerbLib.)
! 2. Each "talk-to-able" NPC in your game must be of class Character,
! and must contain a "quip" routine.
! 3. The "quip" routine consists of one big "switch" clause, like so:
! Character NPC_ID "Name" someroom
! with
! ...
! quip [a b;
! switch (a) {
! ...
! ! Quip #20
! 201: "~Could I have that apple?~";
! 202: move apple to player;
! "~Sure, why not. Say, you're not from
! around here, are you?~";
! 203: qtype = [MainMenu or SubMenu];
! qqon = true;
! killz = true;
! killq = true;
! qtransfer = [quip number];
! 204: ! Conversation options to follow
! return Qlist(b, 2, ! 2 quips in
! ! this "array"
! 21, ! Quip #21
! 22); ! Quip #22
! ...
! }
! rfalse;
! ];
! This sequence -- 201, 202, 203, and 204 -- collectively
! represents Quip #20, a sample conversation option. (Well, not
! really. This quip contains every possible element that a quip can
! have. No real quip would ever do that.)
! 4. Each conversation menu option -- each quip -- can contain any of
! the following elements:
! ### = The quip's ID number. Mandatory. Note: Do *not* use a
! Quip #0! See Note #3 below.
! ###1: Name: The quip's name, as shown among the list of
! conversation menu options.
! ###2: Reply: The reply, including a textual response, and
! maybe also additional programming statements.
! ###3: Additional options. Choose from among the following:
! qtype = MainMenu; If the quip is a main menu.
! = SubMenu; If the quip is a submenu.
! qqon = true; Quip is turned on from the beginning of
! the game. (See also Note #6 below.)
! = false; Quip is turned off from the beginning of
! the game.
! killz = true; To get rid of the "0 to say nothing"
! option for any quip. (killz = "kill
! zero")
! killq = true; To get rid of "What would you like to
! say?" for any quip. (killq = "kill
! question")
! qtransfer = To "go back to the main menu" or any
! [a quip ID]; other previously defined set of options.
! Give the ID of the quip whose
! conversation menu options list (that is,
! whose ###4 clause) you want to use.
! ###4: A list of conversation menu options that can be chosen
! *after* this quip. See Step 5 below.
! Note that none of these four elements is mandatory. For examples,
! consult Gtalk.inf.
! 5. With version 2.01 and up of Gtalk.h, the syntax for defining
! conversation options is considerably easier than it was before.
! However, it still requires some learning. For example, suppose
! you have a Quip 50, and you want it to provide five options --
! namely, Quips 51, 52, 53, 54, and 55. You'd do something like
! this:
! Character NPC_ID "Name" someroom
! with
! ...
! quip [a b;
! switch (a) {
! ...
! ! Quip #50
! ...
! 504: return Qlist(b, 5, ! 5 values in
! ! this "array"
! 51, ! Quip #51
! 52, ! Quip #52
! 53, ! Quip #53
! 54, ! Quip #54
! 55); ! Quip #55
! ...
! }
! rfalse;
! ];
! As you can see, in ###4 you simply call the Qlist routine, where
! the first argument is b, the second argument is the total number
! of conversation options, and the remaining arguments are the ID
! numbers of the quips in your options list.
! Note the format here; Quip #55 should be specified as 55, not
! as 551 or whatever.
! Also note that your options list should include all the
! options that will ever be available at the given juncture. If you
! want a quip's options to change over time, then make sure its
! ###4 makes reference to all the quip options that it will ever
! provide; then turn off any quips in the list that aren't valid
! choices at the beginning of the game.
! But what if you have more than 5 options? This could be a
! problem, because conventional Z-machine Inform allows no more
! than 7 arguments when calling a routine.
! Unless you're writing for Glulx, here's the best way to define
! more than 5 menu options:
! 504: return Qlist(b, 11, 51,52,53,54,55) |
! Qlist(b-6, 56,57,58,59,60,61);
! (The | is a "bitwise or" operator and basically serves to make
! sure that the right value is returned from the right Qlist()
! call. Just trust me, it works. Or rather, trust Mark, who devised
! this clever bit of code.)
! Here's the pattern for when you have more than 11 options:
! 504: return Qlist(b, 40, 51,52,53,54,55) |
! Qlist(b-6, 56,57,58,59,60,61) |
! Qlist(b-12, 62,63,64,65,66,67) |
! Qlist(b-18, 68,69,70,71,72,73) |
! Qlist(b-24, 74,75,76,77,78,79) |
! Qlist(b-30, 80,81,82,83,84,85) |
! Qlist(b-36, 86,87,88,89,90);
! See also Gtalk.inf, Quip #70.
! On the other hand, if you're writing for Glulx, the syntax is
! much simpler:
! 504: return Qlist(b, 40, 51,52,53,54,55,56,57,58,59,60,
! 61,62,63,64,65,66,67,68,69,70,
! 71,72,73,74,75,76,77,78,79,80,
! 81,82,83,84,85,86,87,88,89,90);
! I have been informed that Glulx has the capacity for any number
! of arguments.
! In case anybody's interested, there is also another option:
! 504: switch (b) {
! 0: return 11; ! 11 options
! 1: return 51; ! Quip #51
! 2: return 52; ! Quip #52
! 3: return 53; ! Quip #53
! 4: return 54; ! Quip #54
! 5: return 55; ! Quip #55
! 6: return 56; ! Quip #56
! 7: return 57; ! Quip #57
! 8: return 58; ! Quip #58
! 9: return 59; ! Quip #59
! 10: return 60; ! Quip #60
! 11: return 61; ! Quip #61
! }
! If you find this way easier, then feel free to use it, but it
! doesn't seem easier to me. This is the old, clumsy way, which is
! no longer necessary with Gtalk.h 2.x.
! 6. Each character must have (at least) one conversation menu option
! that represents the "main menu." It is this that's called into
! action first. (To find out how, read Step 8 below.) This main
! menu need not (should not) have a ###1 (option name) or a ###2
! (reply), but it must have a ###4 menu options list, plus a ###3
! with "qtype = MainMenu;". (To find out why, read Note #2 below.)
! 7. If you have any submenus, your submenu quips should have a ###3
! with "qtype = SubMenu;". To find out why, read Note #2 below.
! 8. Each character has a "select" property routine. This is what you
! must call to set the conversation menu in motion. Call
! "self.select(X);" or "CharacterID.select(X);", where X is the
! Quip ID of your main menu.
! 9. It's unlikely that you'll want the choices in your conversation
! menus to always remain the same.
! - To turn Quip #22 on: self.qon(22);
! - To turn Quip #22 off: self.qoff(22);
! - To turn on a series self.qon(1,2,3,4,5);
! of quips (up to 5):
! - To turn off a series self.qoff(1,2,3,4,5);
! of quips (up to 5):
! - To deal with a series self.qset(1,0, 2,1);
! of quips, turning ! Quip #1 on
! some on, others off. ! Quip #2 off
! (Only up to 2 quips
! with Z-code Inform;
! unlimited with Glulx)
! - To test a quip:
! self.qtest(22) is true if Quip #22 is on
! is false if Quip #22 is off
! - Or, if you're ObjectID.qon(22);
! calling from ObjectID.qoff(22);
! a different part ObjectID.qon(1,2,3,4,5);
! of your code: ObjectID.qoff(1,2,3,4,5);
! flag = ObjectID.qtest(22);
! And remember to use "###3: qqon = true;" for quips that are to be
! turned on from the beginning. Otherwise, all quips are initially
! turned off.
!10. Gtalk.h does not deal with any verb issues; you must do that on
! your own. Here are some tips.
! Verb Tip 1:
! Typically, games with conversation menus utilize the TALK
! verb rather than ASK and TELL. So you may wish to define the
! verb Talk. Then, for each of your NPCs, use the "before"
! routine to intercept this verb (or whatever verb(s) you
! choose), and then call the NPC's "select" routine as
! described in Step 8.
! Verb Tip 2:
! You may also wish to disable the standard conversation verbs.
! * You can do this the hard way, as Gtalk.inf does, by
! disabling the conversation verbs one by one -- namely:
! Answer/Say/Shout/Speak
! Ask
! No
! Sorry
! Tell
! Yes/Y
! You may also wish to intercept "Order" and "Answer" in
! the "life" property of each of your NPCs, disabling that
! as well. ("Order" handles things like "HARRY, GO NORTH";
! "Answer" handles things like "HARRY, HELLO" or "HARRY,
! ONIONS".) For more info, consult Gtalk.inf.
! * Just before releasing this library I thought of an easier
! way to do this. Between Parser and VerbLib you could
! include:
! Include "Parser";
! Object LibraryMessages
! with before [;
! Answer, Ask, No, Orders,
! Sorry, Tell, Yes:
! "To talk to someone,
! please type TALK TO PERSON.";
! ];
! Include "VerbLib";
! However, I haven't tested this, so I leave this for you
! to experiment with.
! Verb Tip 3:
! I also recommend one more finishing touch: do an
! "Extend only 'speak' replace", making it so that it so that
! 'speak' (but not 'answer', 'say', or or 'shout') becomes
! synonymous with 'talk'. Consult Gtalk.inf to see what I mean.
!11. See also the following notes.
!
! Note #1: Gtalk.h has an optional feature where it can routinely look
! for any quip whose options are all turned off, and turn that quip
! off for you. To turn on this feature, simply define the constant
! AutoDeactivate just before you include Gtalk.h. See Gtalk.inf, which
! does this. (This feature is not on by default because it might be
! unwanted, and could theoretically slow games down, although this is
! probably not a big factor.)
!
! Note #2: What, really, is the difference between MainMenu quips,
! SubMenu quips, and normal quips?
! * MainMenu quips (unlike SubMenu quips and normal quips) will
! print "You can't think of anything to say" when all of their
! options are turned off.
! * Having reached a MainMenu or SubMenu quip (as opposed to a
! normal quip) does not constitute actually having said anything.
! Gtalk.h takes this into account when deciding whether it makes
! sense to print the message "You decide not to say anything after
! all."
!
! Note #3: Do NOT use a Quip #0. Just don't. Many of the routines in
! gtalk.h (qon, qoff, qset, and qtransfer) would fail if they
! attempted to deal with a Quip #0, and beginning with version 3.x
! gtalk.h doesn't even attempt to process any quips numbered 0.
!
! Note #4: Also, don't use negative numbers for Quip IDs. They won't
! work as you'd expect. Just use Quip IDs from 1 to 65535, rather
! than -32768 to 32767. It's a lot easier.
!
! Note #5: The library is presently set up so that you can have quips
! numbered 1 to 95 when writing for the Z-machine, or 1 to 191 quips
! per NPC when writing for Glulx. This can be changed, and sometimes
! should be changed, because it affects both your game's memory
! consumption and the speed at which Gtalk.h runs.
! Let's see how this works by observing the following bit of code:
! Class Character
! has animate,
! with qflag 0 0 0 0 0 0,
! ! For the For
! ! Z-machine: Glulx:
! ! 6 6 elements in the "qflag" word array
! ! * 2 * 4 bytes per word
! ! * 8 * 8 bits per byte
! ! = 96 = 192
! ! - 1 - 1 because it's 0-95 or 0-191
! ! = 95 = 191 highest quip value for each NPC
! maxquip [;
! return self.#qflag * 8 - 1;
! ],
! As you can see, the highest quip value for each NPC, and thus the
! maximum number of quips for each NPC, depends on the Character.qflag
! array. There are normally six elements (six 0's) in the array.
! Multiply by 16 as indicated above (or 32 if you're writing for
! Glulx) and subtract 1, and you get a maximum of 95 quips per NPC
! with the Z-machine (or 191 with Glulx).
! You can, of course, override the Character class's qflag array.
! For any of your NPCs, simply put a qflag array into the NPC's object
! definition, and put in as many elements as you want to produce the
! desired maximum number of quips, as follows:
! Number of
! elements
! in qflag Highest quip value for each NPC:
! array: (Z-machine) (Glulx)
! 1 *16-1= 15 *32-1= 31
! 2 *16-1= 31 *32-1= 63
! 3 *16-1= 47 *32-1= 95
! 4 *16-1= 63 *32-1= 127
! 5 *16-1= 79 *32-1= 159
! 6 *16-1= 95 *32-1= 191
! 7 *16-1= 111 *32-1= 223
! 8 *16-1= 127 *32-1= 255
! 9 *16-1= 143 *32-1= 287
! 10 *16-1= 159 *32-1= 319
! etc.
! The only restriction is, if you're writing for the Z-machine, you
! can't use more than 6553 quips for any NPC. (Why 6553? Inform's
! integer limits of -32768 to 32767 can also be represented as 0 to
! 65535. Divide by ten, and you get 6553.) It's doubtful that anyone
! would need more than 6553 quips for any one NPC.
!
! Note #6: Gtalk.h now has an optional feature where you can make it
! so all the quips are automatically turned on from the beginning of
! the game unless their ###3 clause specifically says "qqon = false;".
! To do this, simply define the constant QuipsOnByDefault, like so...
! Constant QuipsOnByDefault;
! ...just before you include Gtalk.h. Gtalk.inf doesn't do this, but I
! think you can figure out how to do this on your own.
!
! Note #7: Thanks to Victor Gijsbers, Gtalk now makes things easier
! for authors not writing in English. If your game uses a language
! other than English, make sure to define the constants GT_SELECT,
! GT_ZEROEXIT, GT_WOULDLIKE, GT_NOQUIP, and GT_NOSAY, setting them to
! strings that are appropriate translations of the English expressions
! of those constants (see below). If you are writing in English, all
! you have to do is make sure that the constant EnglishNaturalLanguage
! is defined, as English.h does automatically. Thanks again to Victor
! Gijsbers for this modification.
!
! -----------------------------------------------
! Differences Between Versions 1.x and 2.x and 3.x
! -----------------------------------------------
!
! It is not necessary to read this section in order to use gtalk.h. It
! is mostly here for my own reference, and also for anyone else who is
! interested in knowing what modifications have been done to this
! library. For a more concise version history, see above.
!
! Changes new to version 2.x (mostly due to the work of Mark J.
! Tilford):
! * You no longer have to define your conversation menu options with
! a convoluted array-like routine, but can use a much easier
! syntax. This eliminates what was formerly Gtalk.h's biggest
! disadvantage, so I am grateful to Mark for helping with this.
! * Glulx support has been added.
! * Gtalk.h is now more efficient and faster, perhaps even faster
! enough for some people to notice the difference.
! * You can now declare a constant QuipsOnByDefault, to make your
! conversation options on by default.
! * The Character.qset() routine is now more flexible.
! * Starting with version 2.03, Victor Gijsbers has made some
! modifications to make things easier for authors not writing
! games in English. See Note #7.
!
! Changes new to version 3.x (mostly due to the work of
! Krister Fundin):
!
! * Reformatted code, minor tweaks, some optimizations, lots of
! comments for the benefit of anyone who wants to mess around with
! the library and add more features.
! * Added System_file and made the string constants into #Defaults,
! so that they can be individually replaced even in English games.
! * Added two new strings (GT_OPTIONPREFIX and GT_OPTIONSUFFIX),
! which can be used to change the style of the option listing,
! e.g. into:
! 1. Say this
! or
! #2 Say that
! or
! Type 3 to say something else
! etc.
! * Some Character methods that were only used internally have been
! made into functions instead, since that means fewer properties
! and less dynamic memory used.
! * Moved AutoDeactivate behaviour to a separate function in order
! to reduce clutter a bit
! * Calculate maxquip only once, in qinitial (now called initquips).
! * Made warnings for too high quip numbers a bit more elaborate,
! but they are only compiled when the DEBUG switch is set.
! * Ditched warnings for odd number of arguments to qset, since that
! doesn't seem like a mistake that should happen very easily.
! * Changed the PowersOfTwo function into a table, for efficiency.
! * Used qset directly as much as possible.
! * Quip 0 is not allowed, so skip looping through it
! * In Gtalk.inf, an "unweary" verb has been added, to test the
! consequences of changing the on/off status of quips prior to
! initiating a conversation.
System_file;
! Define default versions of all strings that this extension uses, so
! that they can be replaced, E.G. when developing a game in a language
! other than English.
#default GT_SELECT = "Select an option ";
#default GT_ZEROEXIT = "or 0 to exit ";
#default GT_WOULDLIKE = "What would you like to say?^";
#default GT_NOQUIP = "You can't think of anything to say.^";
#default GT_NOSAY = "You decide not to say anything after all.^";
#default GT_OPTIONPREFIX = "(";
#default GT_OPTIONSUFFIX = ") ";
#ifndef WORDSIZE;
Constant TARGET_ZCODE;
Constant WORDSIZE = 2;
#endif;
! This array of two-powers is used in order to save some time when
! processing quips.
Array GT_Powers
-> $$000000001
$$000000010
$$000000100
$$000001000
$$000010000
$$000100000
$$001000000
$$010000000;
! The five global variables that can be set in a quip's ###3 clause.
Global qtype;
Global qqon;
Global qtransfer;
Global killz;
Global killq;
! These values can be used for qtype in a quip's ###3 clause.
Constant MainMenu = 1;
Constant SubMenu = 2;
! The Qlist function is used as a shorthand to defining options in a
! quip's ###4 clause. We define separate Z-code and Glulx versions,
! since the latter benefits from being able to accept any number of
! arguments.
[ Qlist _vararg_count n t;
@copy sp n;
_vararg_count--;
if (n < 0 || n >= _vararg_count)
{
return 0;
}
while (n > 0)
{
n--;
@copy sp t;
}
@copy sp t;
return t;
];
#endif;
! The Character class. All NPCs with conversation menus should belong to
! this class.
Class Character
has animate,
! This is the array in which we store the on/off status of all our quips.
! The number of entries determines how many quips we can have at most,
! and this can be calculated like so:
!
! For the For
! Z-machine: Glulx:
! 6 6 elements in the default "qflag" word array
! * 2 * 4 bytes per word
! * 8 * 8 bits per byte
! = 96 = 192
! - 1 - 1 because it's 0-95 or 0-191
! = 95 = 191 highest quip value for each NPC
!
! If more quips are needed, override this property and add more zeroes
! to the array.
with qflag 0 0 0 0 0 0,
! the number of our highest quip will be stored here, so that we only
! have to calculate it once
maxquip 0,
! This method carries out a conversation with our character. Call it with
! the first argument set to a quip which represents a main menu.
select
[ curquip times quipnum numoptions onoptions o selected spoken;
! Check if we need to initialize. This process is, among other things,
! responsible for calculating our maxquip value, so we can see if it
! has been done by checking if maxquip is still set to zero.
if (self.maxquip == 0)
initquips(self);
! do a big loop, because this quip could be only the first of many in
! an extended conversation
for (times = 1 : : times++)
{
! turn off quips with no options on, if desired
#ifdef AutoDeactivate;
AutoDeactivateQuips(self);
#endif;
! Reset global variables. Note that we don't care about qqon, since
! it's only used during the initquips() stage.
qtype = 0;
qtransfer = curquip;
killz = 0;
killq = 0;
! ask the current quip to set the five global variables (qtype,
! qqon, qtransfer, killz and killq) to their intended values
self.quip(curquip * 10 + 3);
! if this quip is not a menu of some kind, note that that player
! has said something
if (qtype ~= MainMenu && qtype ~= SubMenu)
spoken = true;
! print the reply for this quip
self.quip(curquip * 10 + 2);
! The next step is to see if we have any options to display. Assume
! that we don't.
onoptions = false;
! Get the quip argument for the option list. Note that qtransfer
! will be set either to the current quip or to a different quip,
! but always to SOME quip
quipnum = qtransfer * 10 + 4;
! get the total number of options, whether on or off
numoptions = self.quip(quipnum, 0);
! if we have any options at all, see if any of them are on
for (o = 1 : o <= numoptions : o++)
{
! we don't care about how many options there are right now, so
! leave this loop as soon as we find at least one option that
! is on
if (self.qtest(self.quip(quipnum, o)))
{
onoptions = true;
break;
}
}
if (~~onoptions)
{
! There were no options available, hence nothing for the player
! to say, so this conversation is finished. If this was a main
! menu, report that the player can't think of anything to say,
! otherwise assume that something appropriate has already been
! printed in our reply.
if (qtype == MainMenu)
print (string) GT_NOQUIP;
return;
}
! We have options. Display an extra prompt before printing them, if
! desired
if (~~killq)
{
! unless this is the first time through, print an extra line
! feed so that we're separated from the previous reply
if (times > 1)
new_line;
print (string) GT_WOULDLIKE;
}
! Go through the options again, now printing a list of those that
! are on. Note that we can reuse the variables quipnum and
! numoptions from the previous iteration.
for (onoptions = 0, o = 1 : o <= numoptions : o++)
{
! get this quip
curquip = self.quip(quipnum, o);
! see if it's on
if (self.qtest(curquip))
{
! print the number of this option (not the internal number,
! of course, but one that starts at 1 and increases for every
! option that we display)
print (string) GT_OPTIONPREFIX,
++onoptions,
(string) GT_OPTIONSUFFIX;
! print the option text
self.quip(curquip * 10 + 1);
}
}
! separate the options from the prompt with an empty line
new_line;
! Now get the response from the player. Keep on asking until we get
! an acceptable answer.
do
{
! print the basic prompt
print (string) GT_SELECT;
! if the player can exit the conversation by typing 0, mention
! this as well
if (~~killz)
print (string) GT_ZEROEXIT;
print ">> ";
! read a line of input
KeyboardPrimitive(buffer, parse);
! Check for empty input, otherwise for a number. In any case,
! invalid input will set selected to something < 0.
if (parse->(WORDSIZE - 1) == 0)
selected = -1;
else
selected = TryNumber(1);
! disallow 0, if desired
if (killz && selected == 0)
selected = -1;
} until (selected >= 0 && selected <= onoptions);
! check for 0, which means that we're leaving this conversation
if (selected == 0)
{
! print a notice saying that the PC decided not to say anything
! after all, but only if nothing has actually been said so far
if (~~spoken)
print (string) GT_NOSAY;
! return from this method
return;
}
! go through the options once again, this time to retrieve the
! option that the player selected, and thus the quip that we want
! to process next
for (onoptions = 0, o = 1 : o <= numoptions : o++)
{
! get this quip
curquip = self.quip(quipnum, o);
! see if it's on
if (self.qtest(curquip))
{
! it's on, but is it the one the player wants?
if (selected == ++onoptions)
{
! It's the right one. Just break out of this loop, since
! we'll then jump back to the beginning with curquip set
! to the quip that the player selected.
break;
}
}
}
}
],
! The following three methods are a bit limited in their use, since the
! Z-machine has a fixed limit on the number of arguments that can be
! given to a method. If we are compiling for Glulx, these will be
! replaced (at run-time) with better versions defined further on.
! turn at most two quips either on or off
qset
[ a b c d;
qset_(self, a, b);
qset_(self, c, d);
],
! Turn on at most five quips. The funky syntax here just means that if an
! argument is empty, then we don't bother checking those that follow
qon
[ a b c d e;
if (a) { qset_(self, a, 1);
if (b) { qset_(self, b, 1);
if (c) { qset_(self, c, 1);
if (d) { qset_(self, d, 1);
if (e) { qset_(self, e, 1); }}}}}
],
! turn off at most five quips
qoff
[ a b c d e;
if (a) { qset_(self, a, 0);
if (b) { qset_(self, b, 0);
if (c) { qset_(self, c, 0);
if (d) { qset_(self, d, 0);
if (e) { qset_(self, e, 0); }}}}}
],
! test a given quip to see if it is on or off
qtest
[ qp byte bits;
! check for too high quips, but only if we're compiling for debugging
#ifdef DEBUG;
if (qp > self.maxquip)
{
print "Gtalk: tried to test quip ", qp, " of ", (name) self,
", but the highest available quip is ", self.maxquip,
". Refer to the documentation on how to enlarge the qflag
array.^";
rfalse;
}
#endif;
! Break down the quip number into bytes and bits. We can't optimize
! away the division, since the Z-machine lacks a rotate operator, but
! we can at least substitute the modulo for a bit-wise AND.
byte = qp / 8;
bits = GT_Powers->(qp & $$111);
! return true if the quip is on
return (self.&qflag->byte & bits == bits);
]
;
! Internal function: set a character's quip to a given on/off state.
[ qset_ char qp state byte bits;
! In most cases the quips will already be initialized
! at this point. But, just in case an author wants to
! turn quips on/off before the conversation menu is
! called up, initialize the quips here if necessary.
if (char.maxquip == 0)
initquips(char);
! check for too high quips, but only if we're compiling for debugging
#ifdef DEBUG;
if (qp > char.maxquip)
{
print "Gtalk: tried to set quip ", qp, " of ", (name) char,
", but the highest available quip is ", char.maxquip,
". Refer to the documentation on how to enlarge the qflag
array.^";
return;
}
#endif;
! break down the quip number into bytes and bits
byte = qp / 8;
bits = GT_Powers->(qp & $$111);
! turn the bit in question on or off
if (state)
char.&qflag->byte = char.&qflag->byte | bits;
else
char.&qflag->byte = char.&qflag->byte & ~bits;
];
! Internal function: do some initialization for a given character.
[ initquips char qp;
! use glulx versions of some methods if we can
#ifdef TARGET_GLULX;
char.qon = CharacterGlulx.qon;
char.qoff = CharacterGlulx.qoff;
char.qset = CharacterGlulx.qset;
#endif;
! initialize the maxquip value
char.maxquip = char.#qflag * 8 - 1;
! go through all the quips
for (qp = 1 : qp <= char.maxquip : qp++)
{
! see if we want quips to be on off by default
#ifdef QuipsOnByDefault;
qqon = 1;
#ifnot;
qqon = 0;
#endif;
! Obtain the desired qqon value for this quip. We'll use the default
! unless the quip says otherwise.
char.quip(qp * 10 + 3);
if (qqon)
char.qon(qp);
}
];
#ifdef TARGET_GLULX;
! Glulx versions of a couple of methods.
Object CharacterGlulx
with
qset
[ _vararg_count qp state;
while (_vararg_count > 0)
{
@copy sp qp;
@copy sp state;
qset_(self, qp, state);
_vararg_count = _vararg_count - 2;
}
],
qon
[ _vararg_count qp;
while (_vararg_count > 0)
{
@copy sp qp;
qset_(self, qp, 1);
_vararg_count--;
}
],
! Internal function: turn off all quips that have options that are all off.
! We only compile and make use of this if requested to.
[ AutoDeactivateQuips char keepgoing qp o quipnum numoptions;
do
{
! assume that we won't have to repeat this
keepgoing = false;
! go through all quips
for (qp = 1 : qp <= char.maxquip : qp++)
{
! we don't need to check quips that are already off
if (char.qtest(qp))
{
! get the option list and the number of options for this quip
quipnum = qp * 10 + 4;
numoptions = char.quip(quipnum, 0);
! see if we have any options at all
if (numoptions)
{
! go through the options
for (o = 1 : o <= numoptions : o++)
{
! If the quip for this option is on, then we can skip the
! current quip and move on to the next one. (Sorry for the
! spaghetti.)
if (char.qtest(char.quip(quipnum, o)))
jump nextquip;
}
! We found no options that were on, so turn off this quip.
! Since this could affect other quips that have this quips as
! one of their options, also note that we have to go through
! this loop at least once more.
qset_(char, qp, 0);
keepgoing = true;
}
}
nextquip;
}
} until (~~keepgoing);
];