! -------------------------------------------------------------------- ! 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. #ifdef TARGET_ZCODE; [ Qlist num a1 a2 a3 a4 a5 a6; switch (num) { 0: return a1; 1: return a2; 2: return a3; 3: return a4; 4: return a5; 5: return a6; } return 0; ]; #ifnot; [ 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--; } ], qoff [ _vararg_count qp; while (_vararg_count > 0) { @copy sp qp; qset_(self, qp, 0); _vararg_count--; } ] ; #endif; #ifdef AutoDeactivate; ! 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); ]; #endif;