| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library - miscellaneous definitions |
| | 7 | * |
| | 8 | * This module contains miscellaneous definitions that don't have a |
| | 9 | * natural grouping with any larger modules, and which aren't complex |
| | 10 | * enough to justify modules of their own. |
| | 11 | */ |
| | 12 | |
| | 13 | /* include the library header */ |
| | 14 | #include "adv3.h" |
| | 15 | |
| | 16 | |
| | 17 | /* ------------------------------------------------------------------------ */ |
| | 18 | /* |
| | 19 | * When a call is made to a property not defined or inherited by the |
| | 20 | * target object, the system will automatically invoke this method. The |
| | 21 | * method will be invoked with a property pointer as its first argument, |
| | 22 | * and the original arguments as the remaining arguments. The first |
| | 23 | * argument gives the property that was invoked and not defined by the |
| | 24 | * object. A typical definition in an object would look like this: |
| | 25 | * |
| | 26 | * propNotDefined(prop, [args]) { ... } |
| | 27 | * |
| | 28 | * If this method is not defined by the object, the system simply |
| | 29 | * returns nil as the value of the undefined property evaluation or |
| | 30 | * method invocation. |
| | 31 | */ |
| | 32 | property propNotDefined; |
| | 33 | export propNotDefined; |
| | 34 | |
| | 35 | |
| | 36 | /* ------------------------------------------------------------------------ */ |
| | 37 | /* |
| | 38 | * We refer to some properties defined primarily in score.t - that's an |
| | 39 | * optional module, though, so make sure the compiler has heard of these. |
| | 40 | */ |
| | 41 | property calcMaxScore, runScoreNotifier; |
| | 42 | |
| | 43 | |
| | 44 | /* ------------------------------------------------------------------------ */ |
| | 45 | /* |
| | 46 | * The library base class for the gameMain object. |
| | 47 | * |
| | 48 | * Each game MUST define an object called 'gameMain' to define how the |
| | 49 | * game starts up. You can use GameMainDef as the base class of your |
| | 50 | * 'gameMain' object, in which case the only thing you're required to |
| | 51 | * specify in your object is the 'initialPlayerChar' property - you can |
| | 52 | * inherit everything else from the GameMainDef class if you don't |
| | 53 | * require any further customizations. |
| | 54 | */ |
| | 55 | class GameMainDef: object |
| | 56 | /* |
| | 57 | * The initial player character. Each game's 'gameMain' object MUST |
| | 58 | * define this to refer to the Actor object that serves as the |
| | 59 | * initial player character. |
| | 60 | */ |
| | 61 | initialPlayerChar = nil |
| | 62 | |
| | 63 | /* |
| | 64 | * Show the game's introduction. This routine is called by the |
| | 65 | * default newGame() just before entering the main command loop. The |
| | 66 | * command loop starts off by showing the initial room description, |
| | 67 | * so there's no need to do that here. |
| | 68 | * |
| | 69 | * Most games will want to override this, to show a prologue message |
| | 70 | * setting up the game's initial situation for the player. We don't |
| | 71 | * show anything by default. |
| | 72 | */ |
| | 73 | showIntro() { } |
| | 74 | |
| | 75 | /* |
| | 76 | * Show the "goodbye" message. This is called after the main command |
| | 77 | * loop terminates. |
| | 78 | * |
| | 79 | * We don't show anything by default. If you want to show a "thanks |
| | 80 | * for playing" type of message as the game exits, override this |
| | 81 | * routine with the desired text. |
| | 82 | */ |
| | 83 | showGoodbye() { } |
| | 84 | |
| | 85 | /* |
| | 86 | * Begin a new game. This default implementation shows the |
| | 87 | * introductory message, calls the main command loop, and finally |
| | 88 | * shows the goodbye message. |
| | 89 | * |
| | 90 | * You can override this routine if you want to customize the startup |
| | 91 | * protocol. For example, if you want to create a pre-game options |
| | 92 | * menu, you could override this routine to show the list of options |
| | 93 | * and process the user's input. If you need only to customize the |
| | 94 | * introduction and goodbye messages, you can simply override |
| | 95 | * showIntro() and showGoodbye() instead. |
| | 96 | */ |
| | 97 | newGame() |
| | 98 | { |
| | 99 | /* try restoring the global defaults */ |
| | 100 | settingsManager.restoreSettings(); |
| | 101 | |
| | 102 | /* |
| | 103 | * Show the statusline before we display our introductory. This |
| | 104 | * will help minimize redrawing - if we waited until after |
| | 105 | * displaying some text, we might have to redraw some of the |
| | 106 | * screen to rearrange things for the new screen area taken up by |
| | 107 | * the status line, which could be visible to the user. By |
| | 108 | * setting up the status line first, we'll probably have less to |
| | 109 | * redraw because we won't have anything on the screen yet when |
| | 110 | * figuring the layout. |
| | 111 | */ |
| | 112 | statusLine.showStatusLine(); |
| | 113 | |
| | 114 | /* show the introduction */ |
| | 115 | showIntro(); |
| | 116 | |
| | 117 | /* run the game, showing the initial location's full description */ |
| | 118 | runGame(true); |
| | 119 | |
| | 120 | /* show the end-of-game message */ |
| | 121 | showGoodbye(); |
| | 122 | } |
| | 123 | |
| | 124 | /* |
| | 125 | * Restore a game and start it running. This is invoked when the |
| | 126 | * user launches the interpreter using a saved game file; for |
| | 127 | * example, on a Macintosh, this happens when the user double-clicks |
| | 128 | * on a saved game file on the desktop. |
| | 129 | * |
| | 130 | * This default implementation bypasses any normal introduction |
| | 131 | * messages: we simply restore the game file if possible, and |
| | 132 | * immediately start the game's main command loop. Most games won't |
| | 133 | * need to override this, but you can if you need some special effect |
| | 134 | * in the restore-at-startup case. |
| | 135 | */ |
| | 136 | restoreAndRunGame(filename) |
| | 137 | { |
| | 138 | local succ; |
| | 139 | |
| | 140 | /* mention that we're about to restore the saved position */ |
| | 141 | gLibMessages.noteMainRestore(); |
| | 142 | |
| | 143 | /* try restoring it */ |
| | 144 | succ = RestoreAction.startupRestore(filename); |
| | 145 | |
| | 146 | /* show a blank line after the restore result message */ |
| | 147 | "<.p>"; |
| | 148 | |
| | 149 | /* if we were successful, run the game */ |
| | 150 | if (succ) |
| | 151 | { |
| | 152 | /* |
| | 153 | * Run the command loop. There's no need to show the room |
| | 154 | * description, since the RESTORE action will have already |
| | 155 | * done so. |
| | 156 | */ |
| | 157 | runGame(nil); |
| | 158 | |
| | 159 | /* show the end-of-game message */ |
| | 160 | showGoodbye(); |
| | 161 | } |
| | 162 | } |
| | 163 | |
| | 164 | /* |
| | 165 | * Set the interpreter window title, if applicable to the local |
| | 166 | * platform. This simply displays a <TITLE> tag to set the title to |
| | 167 | * the string found in the versionInfo object. |
| | 168 | */ |
| | 169 | setGameTitle() |
| | 170 | { |
| | 171 | /* write the <TITLE> tag with the game's name */ |
| | 172 | "<title><<versionInfo.name>></title>"; |
| | 173 | } |
| | 174 | |
| | 175 | /* |
| | 176 | * Set up the HTML-mode about-box. By default, this does nothing. |
| | 177 | * Games can use this routine to show an <ABOUTBOX> tag, if desired, |
| | 178 | * to set up the contents of an about-box for HTML TADS platforms. |
| | 179 | * |
| | 180 | * Note that an <ABOUTBOX> tag must be re-initialized each time the |
| | 181 | * main game window is cleared, so this routine should be called |
| | 182 | * again after any call to clearScreen(). |
| | 183 | */ |
| | 184 | setAboutBox() |
| | 185 | { |
| | 186 | /* we don't show any about-box by default */ |
| | 187 | } |
| | 188 | |
| | 189 | /* |
| | 190 | * The gameMain object also specifies some settings that control |
| | 191 | * optional library behavior. If you want the standard library |
| | 192 | * behavior, you can just inherit the default settings from this |
| | 193 | * class. Some games might want to select non-default variations, |
| | 194 | * though. |
| | 195 | */ |
| | 196 | |
| | 197 | /* |
| | 198 | * The maximum number of points possible in the game. If the game |
| | 199 | * includes the scoring module at all, and this is non-nil, the SCORE |
| | 200 | * and FULL SCORE commands will display this value to the player as a |
| | 201 | * rough indication of how much farther there is to go in the game. |
| | 202 | * |
| | 203 | * By default, we initialize this on demand, by calculating the sum |
| | 204 | * of the point values of the Achievement objects in the game. The |
| | 205 | * game can override this if needed to specify a specific maximum |
| | 206 | * possible score, rather than relying on the automatic calculation. |
| | 207 | */ |
| | 208 | maxScore() |
| | 209 | { |
| | 210 | local m; |
| | 211 | |
| | 212 | /* ask the score module (if any) to compute the maximum score */ |
| | 213 | m = (libGlobal.scoreObj != nil |
| | 214 | ? libGlobal.scoreObj.calcMaxScore : nil); |
| | 215 | |
| | 216 | /* supersede this initializer with the calculated value */ |
| | 217 | maxScore = m; |
| | 218 | |
| | 219 | /* return the result */ |
| | 220 | return m; |
| | 221 | } |
| | 222 | |
| | 223 | /* |
| | 224 | * The score ranking list - this provides a list of names for |
| | 225 | * various score levels. If the game provides a non-nil list here, |
| | 226 | * the SCORE and FULL SCORE commands will show the rank along with |
| | 227 | * the score ("This makes you a Master Adventurer"). |
| | 228 | * |
| | 229 | * This is a list of score entries. Each score entry is itself a |
| | 230 | * list of two elements: the first element is the minimum score for |
| | 231 | * the rank, and the second is a string describing the rank. The |
| | 232 | * ranks should be given in ascending order, since we simply search |
| | 233 | * the list for the first item whose minimum score is greater than |
| | 234 | * our score, and use the preceding item. The first entry in the |
| | 235 | * list would normally have a minimum of zero points, since it |
| | 236 | * should give the initial, lowest rank. |
| | 237 | * |
| | 238 | * If this is set to nil, which it is by default, we'll simply skip |
| | 239 | * score ranks entirely. |
| | 240 | */ |
| | 241 | scoreRankTable = nil |
| | 242 | |
| | 243 | /* |
| | 244 | * Verbose mode. If this is on, the full room description is |
| | 245 | * displayed each time the player enters a room, regardless of |
| | 246 | * whether or not the player has seen the room before; if this is |
| | 247 | * nil, the full description is only displayed on the player's first |
| | 248 | * entry to a room, and only the short description on re-entry. Note |
| | 249 | * that the library provides VERBOSE and TERSE commands that let the |
| | 250 | * player change this setting dynamically. |
| | 251 | * |
| | 252 | * We use a BinarySettingsItem to store the current mode, so that |
| | 253 | * this setting's default will be taken from the user's global |
| | 254 | * cross-game preferences. |
| | 255 | */ |
| | 256 | verboseMode = verboseModeSettingsItem |
| | 257 | |
| | 258 | /* |
| | 259 | * Option flag: allow the player to use "you" and "me" |
| | 260 | * interchangeably in referring to the player character. We set this |
| | 261 | * true by default, so that the player can refer to the player |
| | 262 | * character in either the first or second person, regardless of how |
| | 263 | * the game refers to the PC. |
| | 264 | * |
| | 265 | * If desired, the game can set this flag to nil to force the player |
| | 266 | * to use the correct pronoun to refer to the player character. We |
| | 267 | * define "correct" in the case of first or second person as the |
| | 268 | * complement of what the game uses: if the game calls the PC "me", |
| | 269 | * the player must say "you", and vice versa. In a third-person |
| | 270 | * game, the player must also refer to the PC in the third person. |
| | 271 | * |
| | 272 | * We set the default to allow using "you" and "me" interchangeably |
| | 273 | * because (a) this will create no confusion in most games, and (b) |
| | 274 | * many players would be annoyed otherwise. For one thing, most |
| | 275 | * experienced IF players will be rather set in their ways; they'll |
| | 276 | * be accustomed to using either "me" or "you" (but usually "me") to |
| | 277 | * refer to the PC, and will tend out of habit to do so even in games |
| | 278 | * that don't use the traditional second-person narration format. |
| | 279 | * For another thing, different players have different ideas about |
| | 280 | * whether the PC is "you" or "me" in input, even in a conventional |
| | 281 | * second-person game. Some players think in terms of a conversation |
| | 282 | * with the narrator, in which case the narrator's "you" is the |
| | 283 | * player's "me", and vice versa; other players are rather more |
| | 284 | * literal-minded, assuming that if the game talks about "you" then |
| | 285 | * so should the player. |
| | 286 | * |
| | 287 | * Even in games that use first-person or third-person narration, it |
| | 288 | * seems unlikely that there will be a separate second-person element |
| | 289 | * to the narration, and as long as that's true, it should cause no |
| | 290 | * confusion for the game to accept "you" and "me" as equivalent in |
| | 291 | * commands. However, the library provides this option in case such |
| | 292 | * as situation does arise. |
| | 293 | */ |
| | 294 | allowYouMeMixing = true |
| | 295 | |
| | 296 | /* |
| | 297 | * Option flag: filter plural phrase matches exclude the most obvious |
| | 298 | * illogicalities, such as trying to TAKE an object that's already |
| | 299 | * being held, or trying to OPEN an object that's already open. |
| | 300 | * |
| | 301 | * This is set to true by default, which means that we exclude an |
| | 302 | * object from matching a plural phrase when the object's "verify" |
| | 303 | * routine for the verb has an "illogical-already" or an |
| | 304 | * "illogical-self" result. |
| | 305 | * |
| | 306 | * If you would prefer that plural words are simply matched to |
| | 307 | * everything present that matches the vocabulary, without any |
| | 308 | * filtering at all, override this and set it to nil. |
| | 309 | */ |
| | 310 | filterPluralMatches = true |
| | 311 | |
| | 312 | /* |
| | 313 | * Option flag: allow ALL to be used for every verb. This is true by |
| | 314 | * default, which means that players will be allowed to use ALL with |
| | 315 | * any command - OPEN ALL, EXAMINE ALL, etc. |
| | 316 | * |
| | 317 | * Some authors don't like to allow players to use ALL with so many |
| | 318 | * verbs, because they think it's a sort of "cheating" when players |
| | 319 | * try things like OPEN ALL. This option lets you disable ALL for |
| | 320 | * most verbs; if you set this to nil, only the basic inventory |
| | 321 | * management verbs (TAKE, TAKE FROM, DROP, PUT IN, PUT ON) will |
| | 322 | * allow ALL, and other verbs will simply respond with an error |
| | 323 | * ("'All' isn't allowed with that verb"). |
| | 324 | * |
| | 325 | * If you're writing an especially puzzle-oriented game, you might |
| | 326 | * want to set this to nil. It's a trade-off though, as some people |
| | 327 | * will think your game is less player-friendly if you disable ALL. |
| | 328 | */ |
| | 329 | allVerbsAllowAll = true |
| | 330 | |
| | 331 | /* |
| | 332 | * When a command fails, should we continue processing any remaining |
| | 333 | * commands on the same command line, or simply ignore them? The |
| | 334 | * reason we might want to ignore additional commands is that they |
| | 335 | * might not do what the player was expecting if an earlier command |
| | 336 | * failed; this can sometimes create confusing situations, because |
| | 337 | * the player expected one effect but got something quite different. |
| | 338 | * On the other hand, *not* executing all the commands on the command |
| | 339 | * line could be confusing in its own way, since the game's |
| | 340 | * assessment of what constitutes "failure" might not be clear to the |
| | 341 | * player; from the player's perspective, the game might appear to be |
| | 342 | * inexplicably skipping commands. |
| | 343 | * |
| | 344 | * There's no perfect solution. As always, the ideal is to |
| | 345 | * understand the player's intentions and act accordingly. But when |
| | 346 | * a command fails, it's usually because the player's idea of what's |
| | 347 | * going on is out of sync with the game's - in other words, if we're |
| | 348 | * in this situation to start with, it's probably because our best |
| | 349 | * effort to understand the player's intentions has already failed. |
| | 350 | * This isn't always the case; sometimes we understand the player's |
| | 351 | * intentions perfectly well, but the command fails anyway because of |
| | 352 | * some surprising new development. In these cases, aborting the |
| | 353 | * rest of the command is arguably the right approach, because the |
| | 354 | * player will need a chance to reconsider the pre-typed commands in |
| | 355 | * light of the new information. In other cases, though, it's not so |
| | 356 | * clear. For many players, the prime virtue for the parser is to be |
| | 357 | * predictable, and the most predictable thing to do is to simply |
| | 358 | * plow through the rest of the command line in all cases. |
| | 359 | * |
| | 360 | * Our traditional approach (from the early adv3 versions, and even |
| | 361 | * in tads 2) has been the simple-minded approach - just keep going |
| | 362 | * in all cases. So, we make this the default. You can abort |
| | 363 | * remaining commands on a command failure by setting this to true. |
| | 364 | */ |
| | 365 | cancelCmdLineOnFailure = nil |
| | 366 | |
| | 367 | /* |
| | 368 | * Should we use distinguishers when generating action object |
| | 369 | * announcement messages? If this is true, we'll be as specific as |
| | 370 | * possible when listing default objects, vaguely matched objects, |
| | 371 | * and multiple objects in action reports. By default, this is nil, |
| | 372 | * which means that we just use the object's base name in these |
| | 373 | * announcements. |
| | 374 | * |
| | 375 | * (This is optional because the current implementation is a bit |
| | 376 | * verbose for most people's taste. The problem is that it's a bit |
| | 377 | * *too* specific in many cases, showing more qualification than is |
| | 378 | * really necessary. We make this an option so that authors can |
| | 379 | * enable it and possibly tweak it a bit to meet their needs.) |
| | 380 | */ |
| | 381 | useDistinguishersInAnnouncements = nil |
| | 382 | |
| | 383 | /* |
| | 384 | * How should we handle object announcements when an object is |
| | 385 | * automatically disambiguated? This controls how an action is |
| | 386 | * described when the parser uses the logicalness rules to narrow |
| | 387 | * down the object for a noun phrase when the noun phrase could refer |
| | 388 | * to multiple in-scope objects. There are three options: |
| | 389 | * |
| | 390 | * AnnounceUnclear - Make a parenthetical announcement only when the |
| | 391 | * choice is *not* clear (as described below). This is the original |
| | 392 | * library behavior, from before this option was added. |
| | 393 | * |
| | 394 | * AnnounceClear - Make a parenthetical announcement (for example, |
| | 395 | * "(the red door)") for all disambiguated objects, whether clear or |
| | 396 | * unclear. We don't make an announcement when there's only one |
| | 397 | * in-scope object matching the noun phrase - the announcement is |
| | 398 | * only when multiple objects match the words. |
| | 399 | * |
| | 400 | * DescribeClear - For *unclear* disambiguation, make a parenthetical |
| | 401 | * announcement, to emphasize that the parser had to make a choice. |
| | 402 | * For *clear* disambiguation, skip the announcement, but *do* use a |
| | 403 | * verbose version of the library message in place of one of the |
| | 404 | * terse default replies. For example, for >TAKE BOX, instead of |
| | 405 | * "Taken", we would reply "You take the green box." The longer |
| | 406 | * reply in these cases always mentions the involved object by name, |
| | 407 | * to make it clear exactly which object we chose to use. |
| | 408 | * |
| | 409 | * The default setting is DescribeClear. |
| | 410 | * |
| | 411 | * This only applies when the disambiguation choice is clear - that |
| | 412 | * is, when there's exactly one in-scope object that passes the |
| | 413 | * logicalness tests. For example, if the current location contains |
| | 414 | * a red door that's open and a green door that's closed, CLOSE DOOR |
| | 415 | * clearly refers to the red door because the other one is already |
| | 416 | * closed - it's not logical. There are other cases where the |
| | 417 | * disambiguation is a best guess rather than a clear choice, such as |
| | 418 | * when there are multiple logical objects but there's one that's |
| | 419 | * more likely than the others due to the logicalRank results. In |
| | 420 | * those best-guess situations, the parser always announces its |
| | 421 | * decision, because it's entirely plausible that the player meant |
| | 422 | * one of the other logical, but less likely, choices. |
| | 423 | */ |
| | 424 | ambigAnnounceMode = DescribeClear |
| | 425 | |
| | 426 | /* |
| | 427 | * Should the "before" notifications (beforeAction, roomBeforeAction, |
| | 428 | * and actorAction) run before or after the "check" phase? |
| | 429 | * |
| | 430 | * The library traditionally ran the "before" notifiers first, so |
| | 431 | * this is the default. However, in many ways it's more logical and |
| | 432 | * useful to run "check" first. That way, you can consider the |
| | 433 | * action to be more or less committed by the time the "before" |
| | 434 | * notifiers are invoked. Of course, a command is never *truly* |
| | 435 | * committed until it's actually been executed, since a "before" |
| | 436 | * handler could always cancel it. But this is relatively rare - |
| | 437 | * "before" handlers usually carry out side effects, so it's very |
| | 438 | * useful to be able to know that the command has already passed all |
| | 439 | * of its own internal checks by the time "before" is invoked - that |
| | 440 | * way, you can invoke side effects without worrying that the command |
| | 441 | * will subsequently fail. |
| | 442 | */ |
| | 443 | beforeRunsBeforeCheck = true |
| | 444 | ; |
| | 445 | |
| | 446 | /* |
| | 447 | * The VERBOSE mode settings item. |
| | 448 | */ |
| | 449 | verboseModeSettingsItem: BinarySettingsItem |
| | 450 | /* VERBOSE mode is on by default */ |
| | 451 | isOn = true |
| | 452 | |
| | 453 | /* our configuration file variable ID */ |
| | 454 | settingID = 'adv3.verbose' |
| | 455 | |
| | 456 | /* show our description */ |
| | 457 | settingDesc = (gLibMessages.shortVerboseStatus(isOn)) |
| | 458 | ; |
| | 459 | |
| | 460 | /* ------------------------------------------------------------------------ */ |
| | 461 | /* |
| | 462 | * Clear the main game window. In most cases, you should call this |
| | 463 | * rather than calling the low-level clearScreen() function directly, |
| | 464 | * since this routine takes care of a couple of chores that should |
| | 465 | * usually be done at the same time. |
| | 466 | * |
| | 467 | * First, we flush the transcript to ensure that no left-over reports |
| | 468 | * that were displayed before we cleared the screen will show up on the |
| | 469 | * new screen. Second, we call the low-level clearScreen() function to |
| | 470 | * actually clear the display window. Finally, we re-display any |
| | 471 | * <ABOUTBOX> tag, to ensure that the about-box will still be around; |
| | 472 | * this is necessary because any existing <ABOUTBOX> tag is lost after |
| | 473 | * the screen is cleared. |
| | 474 | */ |
| | 475 | cls() |
| | 476 | { |
| | 477 | /* flush any captured transcript output */ |
| | 478 | if (gTranscript != nil) |
| | 479 | gTranscript.flushForInput(); |
| | 480 | |
| | 481 | /* clear the screen */ |
| | 482 | clearScreen(); |
| | 483 | |
| | 484 | /* re-initialize any <ABOUTBOX> tag */ |
| | 485 | gameMain.setAboutBox(); |
| | 486 | } |
| | 487 | |
| | 488 | /* ------------------------------------------------------------------------ */ |
| | 489 | /* |
| | 490 | * Run the game. We start by showing the description of the initial |
| | 491 | * location, if desired, and then we read and interpret commands until |
| | 492 | * the game ends (via a "quit" command, winning, death of the player |
| | 493 | * character, or any other way of terminating the game). |
| | 494 | * |
| | 495 | * This routine doesn't return until the game ends. |
| | 496 | * |
| | 497 | * Before calling this routine, the caller should already have set the |
| | 498 | * global variable gPlayerChar to the player character actor. |
| | 499 | * |
| | 500 | * 'look' is a flag indicating whether or not to look around; if this is |
| | 501 | * true, we'll show a full description of the player character's initial |
| | 502 | * location, as though the player were to type "look around" as the first |
| | 503 | * command. |
| | 504 | */ |
| | 505 | runGame(look) |
| | 506 | { |
| | 507 | /* show the starting location */ |
| | 508 | if (look) |
| | 509 | { |
| | 510 | /* run the initial "look around" in a dummy command context */ |
| | 511 | withActionEnv(EventAction, gPlayerChar, |
| | 512 | {: gPlayerChar.lookAround(true) }); |
| | 513 | } |
| | 514 | |
| | 515 | /* run the scheduling loop until the game ends */ |
| | 516 | runScheduler(); |
| | 517 | } |
| | 518 | |
| | 519 | /* ------------------------------------------------------------------------ */ |
| | 520 | /* |
| | 521 | * Main program entrypoint. The core run-time start-up code calls this |
| | 522 | * after running pre-initialization and load-time initialization. This |
| | 523 | * entrypoint is called when we're starting the game normally; when the |
| | 524 | * game is launched through a saved-position file, mainRestore() will be |
| | 525 | * invoked instead. |
| | 526 | */ |
| | 527 | main(args) |
| | 528 | { |
| | 529 | try |
| | 530 | { |
| | 531 | /* initialize the display */ |
| | 532 | initDisplay(); |
| | 533 | |
| | 534 | /* call the gameMain object to set up a new game */ |
| | 535 | gameMain.newGame(); |
| | 536 | } |
| | 537 | catch (QuittingException q) |
| | 538 | { |
| | 539 | /* |
| | 540 | * ignore this exception - it's just a signal to quit the game, |
| | 541 | * which we will now proceed to do by returning from this |
| | 542 | * function, which exits the program |
| | 543 | */ |
| | 544 | } |
| | 545 | } |
| | 546 | |
| | 547 | /* |
| | 548 | * Initialize the display. We call this when we first enter the |
| | 549 | * interpreter to set up the main game window. |
| | 550 | */ |
| | 551 | initDisplay() |
| | 552 | { |
| | 553 | /* set the interpreter window title */ |
| | 554 | gameMain.setGameTitle(); |
| | 555 | |
| | 556 | /* set up the ABOUT box */ |
| | 557 | gameMain.setAboutBox(); |
| | 558 | } |
| | 559 | |
| | 560 | /* |
| | 561 | * Main program entrypoint for restoring a saved-position file. This is |
| | 562 | * invoked from the core run-time start-up code when the game is launched |
| | 563 | * from the operating system via a saved-position file. For example, on |
| | 564 | * Windows, double-clicking on a saved-position file on the Windows |
| | 565 | * desktop launches the interpreter, which looks in the save file to find |
| | 566 | * the game executable to run, then starts the game and invokes this |
| | 567 | * entrypoint. |
| | 568 | */ |
| | 569 | mainRestore(args, restoreFile) |
| | 570 | { |
| | 571 | try |
| | 572 | { |
| | 573 | /* initialize the display */ |
| | 574 | initDisplay(); |
| | 575 | |
| | 576 | /* call the gameMain object to restore the specified saved game */ |
| | 577 | gameMain.restoreAndRunGame(restoreFile); |
| | 578 | } |
| | 579 | catch (QuittingException q) |
| | 580 | { |
| | 581 | /* |
| | 582 | * ignore this exception - it's just a signal to quit the game, |
| | 583 | * which we will now proceed to do by returning from this |
| | 584 | * function, which exits the program |
| | 585 | */ |
| | 586 | } |
| | 587 | } |
| | 588 | |
| | 589 | /* ------------------------------------------------------------------------ */ |
| | 590 | /* |
| | 591 | * Determine if the given object overrides the definition of the given |
| | 592 | * property inherited from the given base class. Returns true if the |
| | 593 | * object derives from the given base class, and the object's definition |
| | 594 | * of the property comes from a different place than the base class's |
| | 595 | * definition of the property. |
| | 596 | */ |
| | 597 | overrides(obj, base, prop) |
| | 598 | { |
| | 599 | return (obj.ofKind(base) |
| | 600 | && (obj.propDefined(prop, PropDefGetClass) |
| | 601 | != base.propDefined(prop, PropDefGetClass))); |
| | 602 | } |
| | 603 | |
| | 604 | /* ------------------------------------------------------------------------ */ |
| | 605 | /* |
| | 606 | * Library Pre-Initializer. This object performs the following |
| | 607 | * initialization operations immediately after compilation is completed: |
| | 608 | * |
| | 609 | * - adds each defined Thing to its container's contents list |
| | 610 | * |
| | 611 | * - adds each defined Sense to the global sense list |
| | 612 | * |
| | 613 | * This object is named so that other libraries and/or user code can |
| | 614 | * create initialization order dependencies upon it. |
| | 615 | */ |
| | 616 | adv3LibPreinit: PreinitObject |
| | 617 | execute() |
| | 618 | { |
| | 619 | /* save each SettingsItem's factory default settings */ |
| | 620 | forEachInstance(SettingsItem, |
| | 621 | {i: i.factoryDefault = i.settingToText()}); |
| | 622 | |
| | 623 | /* set the initial player character, as specified in gameMain */ |
| | 624 | gPlayerChar = gameMain.initialPlayerChar; |
| | 625 | |
| | 626 | /* |
| | 627 | * visit every VocabObject, and run its vocabulary initializer |
| | 628 | * (this routine will be defined in the language-specific part |
| | 629 | * of the library to enter each object's vocabulary words into |
| | 630 | * the dictionary) |
| | 631 | */ |
| | 632 | forEachInstance(VocabObject, { obj: obj.initializeVocab() }); |
| | 633 | |
| | 634 | /* visit every Thing, and run its general initializer */ |
| | 635 | forEachInstance(Thing, { obj: obj.initializeThing() }); |
| | 636 | |
| | 637 | /* initialize SpecialTopic objects */ |
| | 638 | forEachInstance(SpecialTopic, { obj: obj.initializeSpecialTopic() }); |
| | 639 | |
| | 640 | /* |
| | 641 | * Initialize each MultiInstance object. Do this after |
| | 642 | * initializing the Thing objects, because we'll be dynamically |
| | 643 | * constructing new Thing objects for the instances. Those new |
| | 644 | * Things will be initialized by their constructors, so we don't |
| | 645 | * want to initialize them redundantly with explicit |
| | 646 | * initializeThing calls. |
| | 647 | */ |
| | 648 | forEachInstance(MultiInstance, { obj: obj.initializeLocation() }); |
| | 649 | |
| | 650 | /* add every Sense to the global sense list */ |
| | 651 | forEachInstance(Sense, { obj: libGlobal.allSenses += obj }); |
| | 652 | |
| | 653 | /* |
| | 654 | * initialize each ActorState - do this before initializing |
| | 655 | * actors, since we want each actor's initial state to plug |
| | 656 | * itself into its actor before we initialize the actors |
| | 657 | */ |
| | 658 | forEachInstance(ActorState, { obj: obj.initializeActorState() }); |
| | 659 | |
| | 660 | /* initialize each Actor */ |
| | 661 | forEachInstance(Actor, { obj: obj.initializeActor() }); |
| | 662 | |
| | 663 | /* |
| | 664 | * initialize the AltTopics first, to set up their parents' lists |
| | 665 | * of their AltTopic children |
| | 666 | */ |
| | 667 | forEachInstance(AltTopic, { obj: obj.initializeAltTopic() }); |
| | 668 | |
| | 669 | /* initialize the topic database entries */ |
| | 670 | forEachInstance(TopicEntry, { obj: obj.initializeTopicEntry() }); |
| | 671 | |
| | 672 | /* initialize the suggested topics */ |
| | 673 | forEachInstance(SuggestedTopic, |
| | 674 | { obj: obj.initializeSuggestedTopic() }); |
| | 675 | |
| | 676 | /* initialize the master direction list */ |
| | 677 | Direction.initializeDirectionClass(); |
| | 678 | |
| | 679 | /* initialize the noise/odor notification daemon */ |
| | 680 | local d = new Daemon(SensoryEmanation, ¬eSenseChanges, 1); |
| | 681 | |
| | 682 | /* |
| | 683 | * give it a later-than-default event order, so that it runs |
| | 684 | * after most other daemons and fuses |
| | 685 | */ |
| | 686 | d.eventOrder = 500; |
| | 687 | |
| | 688 | /* set up a daemon for the current location */ |
| | 689 | new Daemon(BasicLocation, &dispatchRoomDaemon, 1); |
| | 690 | |
| | 691 | /* |
| | 692 | * Initialize the status line daemon. Set this daemon's event |
| | 693 | * order to a high value so that it runs last, after all other |
| | 694 | * daemons - we want this to be the last prompt daemon so that |
| | 695 | * the status line is updated after any other daemons have done |
| | 696 | * their jobs already, in case any of them move the player |
| | 697 | * character to a new location or affect the score, or make any |
| | 698 | * other changes that should be reflected on the status line. |
| | 699 | */ |
| | 700 | local sld = new PromptDaemon(statusLine, &showStatusLineDaemon); |
| | 701 | sld.eventOrder = 1000; |
| | 702 | |
| | 703 | /* |
| | 704 | * Attach the command sequencer output filter, the |
| | 705 | * language-specific message parameter substitution filter, the |
| | 706 | * style tag formatter filter, and the paragraph filter to the |
| | 707 | * main output stream. Stack them so that the paragraph manager |
| | 708 | * is at the bottom, since the library tag filter can produce |
| | 709 | * paragraph tags and thus needs to sit atop the paragraph |
| | 710 | * filter. Put the command sequencer above those, since it |
| | 711 | * might need to write style tags. Finally, put the sense |
| | 712 | * context filter on top of those. |
| | 713 | */ |
| | 714 | mainOutputStream.addOutputFilter(typographicalOutputFilter); |
| | 715 | mainOutputStream.addOutputFilter(mainParagraphManager); |
| | 716 | mainOutputStream.addOutputFilter(styleTagFilter); |
| | 717 | mainOutputStream.addOutputFilter(langMessageBuilder); |
| | 718 | mainOutputStream.addOutputFilter(commandSequencer); |
| | 719 | mainOutputStream.addOutputFilter(conversationManager); |
| | 720 | mainOutputStream.addOutputFilter(senseContext); |
| | 721 | |
| | 722 | /* |
| | 723 | * Attach our message parameter filter and style tag filter to |
| | 724 | * the status line streams. We don't need most of the main |
| | 725 | * window's filters in the status line. |
| | 726 | */ |
| | 727 | statusTagOutputStream.addOutputFilter(styleTagFilter); |
| | 728 | statusTagOutputStream.addOutputFilter(langMessageBuilder); |
| | 729 | |
| | 730 | statusLeftOutputStream.addOutputFilter(styleTagFilter); |
| | 731 | statusLeftOutputStream.addOutputFilter(langMessageBuilder); |
| | 732 | |
| | 733 | statusRightOutputStream.addOutputFilter(styleTagFilter); |
| | 734 | statusRightOutputStream.addOutputFilter(langMessageBuilder); |
| | 735 | } |
| | 736 | |
| | 737 | /* |
| | 738 | * Make sure the output streams we depend on are initialized before |
| | 739 | * me (so that they set up properly internally). Also, make sure |
| | 740 | * that the message builder object (langMessageBuilder) is set up |
| | 741 | * first, so that we can add entries to its parameter substitution |
| | 742 | * table. |
| | 743 | */ |
| | 744 | execBeforeMe = [mainOutputStream, statusTagOutputStream, |
| | 745 | statusLeftOutputStream, statusRightOutputStream, |
| | 746 | langMessageBuilder] |
| | 747 | ; |
| | 748 | |
| | 749 | /* ------------------------------------------------------------------------ */ |
| | 750 | /* |
| | 751 | * Library Initializer. This object performs the following |
| | 752 | * initialization operations each time the game is started: |
| | 753 | * |
| | 754 | * - sets up the library's default output function |
| | 755 | */ |
| | 756 | adv3LibInit: InitObject |
| | 757 | execute() |
| | 758 | { |
| | 759 | /* |
| | 760 | * Set up our default output function. Note that we must do |
| | 761 | * this during run-time initialization each time we start the |
| | 762 | * game, rather than during pre-initialization, because the |
| | 763 | * default output function state is not part of the load-image |
| | 764 | * configuration. |
| | 765 | */ |
| | 766 | t3SetSay(say); |
| | 767 | } |
| | 768 | ; |
| | 769 | |
| | 770 | |
| | 771 | /* ------------------------------------------------------------------------ */ |
| | 772 | /* |
| | 773 | * Generic script object. This class can be used to implement a simple |
| | 774 | * state machine. |
| | 775 | */ |
| | 776 | class Script: object |
| | 777 | /* |
| | 778 | * Get the current state. This returns a value that gives the |
| | 779 | * current state of the script, which is usually simply an integer. |
| | 780 | */ |
| | 781 | getScriptState() |
| | 782 | { |
| | 783 | /* by default, return our state property */ |
| | 784 | return curScriptState; |
| | 785 | } |
| | 786 | |
| | 787 | /* |
| | 788 | * Process the next step of the script. This routine must be |
| | 789 | * overridden to perform the action of the script. This routine's |
| | 790 | * action should call getScriptState() to get our current state, and |
| | 791 | * should update the internal state appropriately to take us to the |
| | 792 | * next step after the current one. |
| | 793 | * |
| | 794 | * By default, we don't do anything at all. |
| | 795 | */ |
| | 796 | doScript() |
| | 797 | { |
| | 798 | /* override to carry out the script */ |
| | 799 | } |
| | 800 | |
| | 801 | /* |
| | 802 | * Property giving our current state. This should never be used |
| | 803 | * directly; instead, getScriptState() should always be used, since |
| | 804 | * getScriptState() can be overridden so that the state depends on |
| | 805 | * something other than this internal state property. The meaning of |
| | 806 | * the state identifier is specific to each subclass. |
| | 807 | */ |
| | 808 | curScriptState = 0 |
| | 809 | ; |
| | 810 | |
| | 811 | /* |
| | 812 | * Random-Firing script add-in. This is a mix-in class that you can add |
| | 813 | * to the superclass list of any Script subclass to make the script |
| | 814 | * execute only a given percentage of the time it's invoked. Each time |
| | 815 | * doScript() is invoked on the script, we'll look at the probability |
| | 816 | * settings (see the properties below) to determine whether we really |
| | 817 | * want to execute the script this time; if so, we'll proceed with the |
| | 818 | * scripted event, otherwise we'll just return immediately, doing |
| | 819 | * nothing. |
| | 820 | * |
| | 821 | * Note that this must be used in the superclass list *before* the Script |
| | 822 | * subclass: |
| | 823 | * |
| | 824 | * myScript: RandomFiringScript, EventList |
| | 825 | *. // ...my definitions... |
| | 826 | *. ; |
| | 827 | * |
| | 828 | * This class is especially useful for random atmospheric events, because |
| | 829 | * it allows you to make the timing of scripted events random. Rather |
| | 830 | * than making a scripted event happen on every single turn, you can use |
| | 831 | * this to make events happen only sporadically. It can often feel too |
| | 832 | * predictable and repetitious when a random background event happens on |
| | 833 | * every single turn; firing events less frequently often makes them feel |
| | 834 | * more realistic. |
| | 835 | */ |
| | 836 | class RandomFiringScript: object |
| | 837 | /* |
| | 838 | * Percentage of the time an event occurs. By default, we execute an |
| | 839 | * event 100% of the time - meaning every time that doScript() is |
| | 840 | * invoked. If you set this to a lower percentage, then each time |
| | 841 | * doScript() is invoked, we'll randomly decide whether or not to |
| | 842 | * execute an event based on this percentage. For example, if you |
| | 843 | * want an event to execute on average about a third of the time, set |
| | 844 | * this to 33. |
| | 845 | * |
| | 846 | * Note that this is a probabilistic frequency. Setting this to 33 |
| | 847 | * does *not* mean that we'll execute exactly every third time. |
| | 848 | * Rather, it means that we'll randomly execute or not on each |
| | 849 | * invocation, and averaged over a large number of invocations, we'll |
| | 850 | * execute about a third of the time. |
| | 851 | */ |
| | 852 | eventPercent = 100 |
| | 853 | |
| | 854 | /* |
| | 855 | * Random atmospheric events can get repetitive after a while, so we |
| | 856 | * provide an easy way to reduce the frequency of our events after a |
| | 857 | * while. This way, we'll generate the events more frequently at |
| | 858 | * first, but once the player has seen them enough to get the idea, |
| | 859 | * we'll cut back. Sometimes, the player will spend a lot of time in |
| | 860 | * one place trying to solve a puzzle, so the same set of random |
| | 861 | * events can get stale. Set eventReduceAfter to the number of times |
| | 862 | * you want the events to be generated at full frequency; after we've |
| | 863 | * fired events that many times, we'll change eventPercent to |
| | 864 | * eventReduceTo. If eventReduceAfter is nil, we won't ever change |
| | 865 | * eventPercent. |
| | 866 | */ |
| | 867 | eventReduceAfter = nil |
| | 868 | eventReduceTo = nil |
| | 869 | |
| | 870 | /* |
| | 871 | * When doScript() is invoked, check the event probabilities before |
| | 872 | * proceeding. |
| | 873 | */ |
| | 874 | doScript() |
| | 875 | { |
| | 876 | /* process the script step only if the event odds allow it */ |
| | 877 | if (checkEventOdds()) |
| | 878 | inherited(); |
| | 879 | } |
| | 880 | |
| | 881 | /* |
| | 882 | * Check the event odds to see if we want to fire an event at all on |
| | 883 | * this invocation. |
| | 884 | */ |
| | 885 | checkEventOdds() |
| | 886 | { |
| | 887 | /* |
| | 888 | * check the event odds to see if we fire an event this time; if |
| | 889 | * not, we're done with the script invocation |
| | 890 | */ |
| | 891 | if (rand(100) >= eventPercent) |
| | 892 | return nil; |
| | 893 | |
| | 894 | /* |
| | 895 | * we're firing an event this time, so count this against the |
| | 896 | * reduction limit, if there is one |
| | 897 | */ |
| | 898 | if (eventReduceAfter != nil) |
| | 899 | { |
| | 900 | /* decrement the limit counter */ |
| | 901 | --eventReduceAfter; |
| | 902 | |
| | 903 | /* if it has reached zero, apply the reduced frequency */ |
| | 904 | if (eventReduceAfter == 0) |
| | 905 | { |
| | 906 | /* apply the reduced frequency */ |
| | 907 | eventPercent = eventReduceTo; |
| | 908 | |
| | 909 | /* we no longer have a limit to look for */ |
| | 910 | eventReduceAfter = nil; |
| | 911 | } |
| | 912 | } |
| | 913 | |
| | 914 | /* indicate that we do want to fire an event */ |
| | 915 | return true; |
| | 916 | } |
| | 917 | ; |
| | 918 | |
| | 919 | /* ------------------------------------------------------------------------ */ |
| | 920 | /* |
| | 921 | * An "event list." This is a general-purpose type of script that lets |
| | 922 | * you define the scripted events separately from the Script object. |
| | 923 | * |
| | 924 | * The script is driven by a list of values; each value represents one |
| | 925 | * step of the script. Each value can be a single-quoted string, in |
| | 926 | * which case the string is simply displayed; a function pointer, in |
| | 927 | * which case the function is invoked without arguments; another Script |
| | 928 | * object, in which case the object's doScript() method is invoked; a |
| | 929 | * property pointer, in which case the property of 'self' (the EventList |
| | 930 | * object) is invoked with no arguments; or nil, in which case nothing |
| | 931 | * happens. |
| | 932 | * |
| | 933 | * This base type of event list runs through the list once, in order, and |
| | 934 | * then simply stops doing anything once we pass the last event. |
| | 935 | */ |
| | 936 | class EventList: Script |
| | 937 | construct(lst) { eventList = lst; } |
| | 938 | |
| | 939 | /* the list of events */ |
| | 940 | eventList = [] |
| | 941 | |
| | 942 | /* advance to the next state */ |
| | 943 | advanceState() |
| | 944 | { |
| | 945 | /* increment our state index */ |
| | 946 | ++curScriptState; |
| | 947 | } |
| | 948 | |
| | 949 | /* by default, start at the first list element */ |
| | 950 | curScriptState = 1 |
| | 951 | |
| | 952 | /* process the next step of the script */ |
| | 953 | doScript() |
| | 954 | { |
| | 955 | local idx; |
| | 956 | |
| | 957 | /* get our current event state */ |
| | 958 | idx = getScriptState(); |
| | 959 | |
| | 960 | /* if it's a valid index in our list, fire the event */ |
| | 961 | if (idx >= 1 && idx <= eventList.length()) |
| | 962 | { |
| | 963 | /* carry out the event */ |
| | 964 | doScriptEvent(eventList[idx]); |
| | 965 | } |
| | 966 | |
| | 967 | /* perform any end-of-script processing */ |
| | 968 | scriptDone(); |
| | 969 | } |
| | 970 | |
| | 971 | /* carry out one script event */ |
| | 972 | doScriptEvent(evt) |
| | 973 | { |
| | 974 | /* check what kind of event we have */ |
| | 975 | switch (dataTypeXlat(evt)) |
| | 976 | { |
| | 977 | case TypeSString: |
| | 978 | /* it's a string - display it */ |
| | 979 | say(evt); |
| | 980 | break; |
| | 981 | |
| | 982 | case TypeObject: |
| | 983 | /* it must be a Script object - invoke its doScript() method */ |
| | 984 | evt.doScript(); |
| | 985 | break; |
| | 986 | |
| | 987 | case TypeFuncPtr: |
| | 988 | /* it's a function pointer - invoke it */ |
| | 989 | (evt)(); |
| | 990 | break; |
| | 991 | |
| | 992 | case TypeProp: |
| | 993 | /* it's a property of self - invoke it */ |
| | 994 | self.(evt)(); |
| | 995 | break; |
| | 996 | |
| | 997 | default: |
| | 998 | /* do nothing in other cases */ |
| | 999 | break; |
| | 1000 | } |
| | 1001 | } |
| | 1002 | |
| | 1003 | /* |
| | 1004 | * Perform any end-of-script processing. By default, we advance the |
| | 1005 | * script to the next state. |
| | 1006 | * |
| | 1007 | * Some scripts might want to override this. For example, a script |
| | 1008 | * could be driven entirely by some external timing; the state of a |
| | 1009 | * script could vary once per turn, for example, or could change each |
| | 1010 | * time an actor pushes a button. In these cases, invoking the |
| | 1011 | * script wouldn't affect the state of the event list, so the |
| | 1012 | * subclass would override scriptDone() so that it does nothing at |
| | 1013 | * all. |
| | 1014 | */ |
| | 1015 | scriptDone() |
| | 1016 | { |
| | 1017 | /* advance to the next state */ |
| | 1018 | advanceState(); |
| | 1019 | } |
| | 1020 | ; |
| | 1021 | |
| | 1022 | /* |
| | 1023 | * An "external" event list is one whose state is driven externally to |
| | 1024 | * the script. Specifically, the state is *not* advanced by invoking the |
| | 1025 | * script; the state is advanced exclusively by some external process |
| | 1026 | * (for example, by a daemon that invokes the event list's advanceState() |
| | 1027 | * method). |
| | 1028 | */ |
| | 1029 | class ExternalEventList: EventList |
| | 1030 | scriptDone() { } |
| | 1031 | ; |
| | 1032 | |
| | 1033 | /* |
| | 1034 | * A cyclical event list - this runs through the event list in order, |
| | 1035 | * returning to the first element when we pass the last element. |
| | 1036 | */ |
| | 1037 | class CyclicEventList: EventList |
| | 1038 | advanceState() |
| | 1039 | { |
| | 1040 | /* go to the next state */ |
| | 1041 | ++curScriptState; |
| | 1042 | |
| | 1043 | /* if we've passed the end of the list, loop back to the start */ |
| | 1044 | if (curScriptState > eventList.length()) |
| | 1045 | curScriptState = 1; |
| | 1046 | } |
| | 1047 | ; |
| | 1048 | |
| | 1049 | /* |
| | 1050 | * A stopping event list - this runs through the event list in order, |
| | 1051 | * then stops at the last item and repeats it each time the script is |
| | 1052 | * subsequently invoked. |
| | 1053 | * |
| | 1054 | * This is often useful for things like ASK ABOUT topics, where we reveal |
| | 1055 | * more information when asked repeatedly about a topic, but eventually |
| | 1056 | * reach a point where we've said everything: |
| | 1057 | * |
| | 1058 | *. >ask bob about black book |
| | 1059 | *. "What makes you think I know anything about it?" he says, his |
| | 1060 | * voice shaking. |
| | 1061 | * |
| | 1062 | * >again |
| | 1063 | *. "No! You can't make me tell you!" |
| | 1064 | * |
| | 1065 | * >again |
| | 1066 | *. "All right, I'll tell you what you want to know! But I warn you, |
| | 1067 | * these are things mortal men were never meant to know. Your life, your |
| | 1068 | * very soul will be in danger from the moment you hear these dark secrets!" |
| | 1069 | * |
| | 1070 | * >again |
| | 1071 | *. [scene missing] |
| | 1072 | * |
| | 1073 | * >again |
| | 1074 | *. "I've already told you all I know." |
| | 1075 | * |
| | 1076 | * >again |
| | 1077 | *. "I've already told you all I know." |
| | 1078 | */ |
| | 1079 | class StopEventList: EventList |
| | 1080 | advanceState() |
| | 1081 | { |
| | 1082 | /* if we haven't yet reached the last state, go to the next one */ |
| | 1083 | if (curScriptState < eventList.length()) |
| | 1084 | ++curScriptState; |
| | 1085 | } |
| | 1086 | ; |
| | 1087 | |
| | 1088 | /* |
| | 1089 | * A synchronized event list. This is an event list that takes its |
| | 1090 | * actions from a separate event list object. We get our current state |
| | 1091 | * from the other list, and advancing our state advances the other list's |
| | 1092 | * state in lock step. Set 'masterObject' to refer to the master list |
| | 1093 | * whose state we synchronize with. |
| | 1094 | * |
| | 1095 | * This can be useful, for example, when we have messages that reflect |
| | 1096 | * two different points of view on the same events: the messages for each |
| | 1097 | * point of view can be kept in a separate list, but the one list can be |
| | 1098 | * a slave of the other to ensure that the two lists are based on a |
| | 1099 | * common state. |
| | 1100 | */ |
| | 1101 | class SyncEventList: EventList |
| | 1102 | /* my master event list object */ |
| | 1103 | masterObject = nil |
| | 1104 | |
| | 1105 | /* my state is simply the master list's state */ |
| | 1106 | getScriptState() { return masterObject.getScriptState(); } |
| | 1107 | |
| | 1108 | /* to advance my state, advance the master list's state */ |
| | 1109 | advanceState() { masterObject.advanceState(); } |
| | 1110 | |
| | 1111 | /* let the master list take care of finishing a script step */ |
| | 1112 | scriptDone() { masterObject.scriptDone(); } |
| | 1113 | ; |
| | 1114 | |
| | 1115 | /* |
| | 1116 | * Randomized event list. This is similar to a regular event list, but |
| | 1117 | * chooses an event at random each time it's invoked. |
| | 1118 | */ |
| | 1119 | class RandomEventList: RandomFiringScript, EventList |
| | 1120 | /* process the next step of the script */ |
| | 1121 | doScript() |
| | 1122 | { |
| | 1123 | local idx; |
| | 1124 | |
| | 1125 | /* check the odds to see if we want to fire an event at all */ |
| | 1126 | if (!checkEventOdds()) |
| | 1127 | return; |
| | 1128 | |
| | 1129 | /* get our next random number */ |
| | 1130 | idx = getNextRandom(); |
| | 1131 | |
| | 1132 | /* run the event, if the index is valid */ |
| | 1133 | if (idx >= 1 && idx <= eventList.length()) |
| | 1134 | doScriptEvent(eventList[idx]); |
| | 1135 | } |
| | 1136 | |
| | 1137 | /* |
| | 1138 | * Get the next random state. By default, we simply return a number |
| | 1139 | * from 1 to the number of entries in our event list. This is a |
| | 1140 | * separate method to allow subclasses to customize the way the |
| | 1141 | * random number is selected. |
| | 1142 | */ |
| | 1143 | getNextRandom() |
| | 1144 | { |
| | 1145 | /* |
| | 1146 | * Note that rand(n) returns a number from 0 to n-1 inclusive; |
| | 1147 | * since list indices run from 1 to list.length, add one to the |
| | 1148 | * result of rand(list.length) to get a value in the proper range |
| | 1149 | * for a list index. |
| | 1150 | */ |
| | 1151 | return rand(eventList.length()) + 1; |
| | 1152 | } |
| | 1153 | ; |
| | 1154 | |
| | 1155 | /* |
| | 1156 | * Shuffled event list. This is similar to a random event list, except |
| | 1157 | * that we fire our events in a "shuffled" order rather than an |
| | 1158 | * independently random order. "Shuffled order" means that we fire the |
| | 1159 | * events in random order, but we don't re-fire an event until we've run |
| | 1160 | * through all of the other events. The effect is as though we were |
| | 1161 | * dealing from a deck of cards. |
| | 1162 | * |
| | 1163 | * For the first time through the main list, we normally shuffle the |
| | 1164 | * strings immediately at startup, but this is optional. If shuffleFirst |
| | 1165 | * is set to nil, we will NOT shuffle the list the first time through - |
| | 1166 | * we'll run through it once in the given order, then shuffle for the |
| | 1167 | * next time through, then shuffle again for the next, and so on. So, if |
| | 1168 | * you want a specific order for the first time through, just define the |
| | 1169 | * list in the desired order and set shuffleFirst to nil. |
| | 1170 | * |
| | 1171 | * You can optionally specify a separate list of one-time-only sequential |
| | 1172 | * strings in the property firstEvents. We'll run through these strings |
| | 1173 | * once. When we've exhausted them, we'll switch to the main eventList |
| | 1174 | * list, showing it one time through in its given order, then shuffling |
| | 1175 | * it and running through it again, and so on. The firstEvents list is |
| | 1176 | * never shuffled - it's always shown in exactly the order given. |
| | 1177 | */ |
| | 1178 | class ShuffledEventList: RandomFiringScript, EventList |
| | 1179 | /* |
| | 1180 | * a list of events to go through sequentially, in the exact order |
| | 1181 | * specified, before firing any events from the main list |
| | 1182 | */ |
| | 1183 | firstEvents = [] |
| | 1184 | |
| | 1185 | /* |
| | 1186 | * Flag: shuffle the eventList list before we show it for the first |
| | 1187 | * time. By default, this is set to true, so that the behavior is |
| | 1188 | * random on each independent run of the game. However, it might be |
| | 1189 | * desirable in some cases to always use the original ordering of the |
| | 1190 | * eventList list the first time through the list. If this is set to |
| | 1191 | * nil, we won't shuffle the list the first time through. |
| | 1192 | */ |
| | 1193 | shuffleFirst = true |
| | 1194 | |
| | 1195 | /* |
| | 1196 | * Flag: suppress repeats in the shuffle. If this is true, it |
| | 1197 | * prevents a given event from showing up twice in a row, which could |
| | 1198 | * otherwise happen right after a shuffle. This is ignored for lists |
| | 1199 | * with one or two events: it's impossible to prevent repeats in a |
| | 1200 | * one-element list, and doing so in a two-element list would produce |
| | 1201 | * a predictable A-B-A-B... pattern. |
| | 1202 | * |
| | 1203 | * You might want to set this to nil for lists of three or four |
| | 1204 | * elements, since such short lists can result in fairly |
| | 1205 | * un-random-looking sequences when repeats are suppressed, because |
| | 1206 | * the available number of permutations drops significantly. |
| | 1207 | */ |
| | 1208 | suppressRepeats = true |
| | 1209 | |
| | 1210 | /* process the next step of the script */ |
| | 1211 | doScript() |
| | 1212 | { |
| | 1213 | local evt; |
| | 1214 | local firstLen = firstEvents.length(); |
| | 1215 | local eventLen = eventList.length(); |
| | 1216 | |
| | 1217 | /* process the script step only if the event odds allow it */ |
| | 1218 | if (!checkEventOdds()) |
| | 1219 | return; |
| | 1220 | |
| | 1221 | /* |
| | 1222 | * States 1..N, where N is the number of elements in the |
| | 1223 | * firstEvents list, simply show the firstEvents elements in |
| | 1224 | * order. |
| | 1225 | * |
| | 1226 | * If we're set to shuffle the main eventList list initially, all |
| | 1227 | * states above N simply show elements from the eventList list in |
| | 1228 | * shuffled order. |
| | 1229 | * |
| | 1230 | * If we're NOT set to shuffle the main eventList list initially, |
| | 1231 | * the following apply: |
| | 1232 | * |
| | 1233 | * States N+1..N+M, where M is the number of elements in the |
| | 1234 | * eventList list, show the eventList elements in order. |
| | 1235 | * |
| | 1236 | * States above N+M show elements from the eventList list in |
| | 1237 | * shuffled order. |
| | 1238 | */ |
| | 1239 | if (curScriptState <= firstLen) |
| | 1240 | { |
| | 1241 | /* simply fetch the next string from firstEvents */ |
| | 1242 | evt = firstEvents[curScriptState++]; |
| | 1243 | } |
| | 1244 | else if (!shuffleFirst && curScriptState <= firstLen + eventLen) |
| | 1245 | { |
| | 1246 | /* fetch the next string from eventList */ |
| | 1247 | evt = eventList[curScriptState++ - firstLen]; |
| | 1248 | } |
| | 1249 | else |
| | 1250 | { |
| | 1251 | /* we're showing shuffled strings from the eventList list */ |
| | 1252 | evt = eventList[getNextRandom()]; |
| | 1253 | } |
| | 1254 | |
| | 1255 | /* execute the event */ |
| | 1256 | doScriptEvent(evt); |
| | 1257 | } |
| | 1258 | |
| | 1259 | |
| | 1260 | /* |
| | 1261 | * Get the next random event. We'll pick an event from our list of |
| | 1262 | * events using a ShuffledIntegerList to ensure we pick each value |
| | 1263 | * once before re-using any values. |
| | 1264 | */ |
| | 1265 | getNextRandom() |
| | 1266 | { |
| | 1267 | /* if we haven't created our shuffled list yet, do so now */ |
| | 1268 | if (shuffledList_ == nil) |
| | 1269 | { |
| | 1270 | /* |
| | 1271 | * create a shuffled integer list - we'll use these shuffled |
| | 1272 | * integers as indices into our event list |
| | 1273 | */ |
| | 1274 | shuffledList_ = new ShuffledIntegerList(1, eventList.length()); |
| | 1275 | |
| | 1276 | /* apply our suppressRepeats option to the shuffled list */ |
| | 1277 | shuffledList_.suppressRepeats = suppressRepeats; |
| | 1278 | } |
| | 1279 | |
| | 1280 | /* ask the shuffled list to pick an element */ |
| | 1281 | return shuffledList_.getNextValue(); |
| | 1282 | } |
| | 1283 | |
| | 1284 | /* our ShuffledList - we'll initialize this on demand */ |
| | 1285 | shuffledList_ = nil |
| | 1286 | ; |
| | 1287 | |
| | 1288 | /* ------------------------------------------------------------------------ */ |
| | 1289 | /* |
| | 1290 | * Shuffled List - this class keeps a list of values that can be returned |
| | 1291 | * in random order, but with the constraint that we never repeat a value |
| | 1292 | * until we've handed out every value. Think of a shuffled deck of |
| | 1293 | * cards: the order of the cards handed out is random, but once a card is |
| | 1294 | * dealt, it can't be dealt again until we put everything back into the |
| | 1295 | * deck and reshuffle. |
| | 1296 | */ |
| | 1297 | class ShuffledList: object |
| | 1298 | /* |
| | 1299 | * the list of values we want to shuffle - initialize this in each |
| | 1300 | * instance to the set of values we want to return in random order |
| | 1301 | */ |
| | 1302 | valueList = [] |
| | 1303 | |
| | 1304 | /* |
| | 1305 | * Flag: suppress repeated values. We mostly suppress repeats by our |
| | 1306 | * very design, since we run through the entire list before repeating |
| | 1307 | * anything in the list. However, there's one situation (in a list |
| | 1308 | * with more than one element) where a repeat can occur: immediately |
| | 1309 | * after a shuffle, we could select the last element from the |
| | 1310 | * previous shuffle as the first element of the new shuffle. If this |
| | 1311 | * flag is set, we'll suppress this type of repeat by choosing again |
| | 1312 | * any time we're about to choose a repeat. |
| | 1313 | * |
| | 1314 | * Note that we ignore this for a list of one element, since it's |
| | 1315 | * obviously impossible to avoid repeats in this case. We also |
| | 1316 | * ignore it for a two-element list, since this would produce the |
| | 1317 | * predictable pattern A-B-A-B..., defeating the purpose of the |
| | 1318 | * shuffle. |
| | 1319 | */ |
| | 1320 | suppressRepeats = nil |
| | 1321 | |
| | 1322 | /* create from a given list */ |
| | 1323 | construct(lst) |
| | 1324 | { |
| | 1325 | /* remember our list of values */ |
| | 1326 | valueList = lst; |
| | 1327 | } |
| | 1328 | |
| | 1329 | /* |
| | 1330 | * Get a random value. This will return a randomly-selected element |
| | 1331 | * from 'valueList', but we'll return every element of 'valueList' |
| | 1332 | * once before repeating any element. |
| | 1333 | * |
| | 1334 | * If we've returned every value on the current round, we'll |
| | 1335 | * automatically shuffle the values and start a new round. |
| | 1336 | */ |
| | 1337 | getNextValue() |
| | 1338 | { |
| | 1339 | local i; |
| | 1340 | local ret; |
| | 1341 | local justReshuffled = nil; |
| | 1342 | |
| | 1343 | /* if we haven't initialized our vector, do so now */ |
| | 1344 | if (valuesVec == nil) |
| | 1345 | { |
| | 1346 | /* create the vector */ |
| | 1347 | valuesVec = new Vector(valueList.length(), valueList); |
| | 1348 | |
| | 1349 | /* all values are initially available */ |
| | 1350 | valuesAvail = valuesVec.length(); |
| | 1351 | } |
| | 1352 | |
| | 1353 | /* if we've exhausted our values on this round, start over */ |
| | 1354 | if (valuesAvail == 0) |
| | 1355 | { |
| | 1356 | /* shuffle the elements */ |
| | 1357 | reshuffle(); |
| | 1358 | |
| | 1359 | /* note that we just did a shuffle */ |
| | 1360 | justReshuffled = true; |
| | 1361 | } |
| | 1362 | |
| | 1363 | /* pick a random element from the 'available' partition */ |
| | 1364 | i = rand(valuesAvail) + 1; |
| | 1365 | |
| | 1366 | /* |
| | 1367 | * If we just reshuffled, and we're configured to suppress a |
| | 1368 | * repeat immediately after a reshuffle, and we chose the first |
| | 1369 | * element of the vector, and we have at least three elements, |
| | 1370 | * choose a different element. The first element in the vector is |
| | 1371 | * always the last element we return from each run-through, since |
| | 1372 | * the 'available' partition is at the start of the list and thus |
| | 1373 | * shrinks down until it contains only the first element. |
| | 1374 | * |
| | 1375 | * If we have one element, there's obviously no point in trying to |
| | 1376 | * suppress repeats. If we have two elements, we *still* don't |
| | 1377 | * want to suppress repeats, because in this case we'd generate a |
| | 1378 | * predicatable A-B-A-B pattern (because we could never have two |
| | 1379 | * A's or two B's in a row). |
| | 1380 | */ |
| | 1381 | if (justReshuffled && suppressRepeats && valuesAvail > 2) |
| | 1382 | { |
| | 1383 | /* |
| | 1384 | * we don't want repeats, so choose anything besides the |
| | 1385 | * first element; keep choosing until we get another element |
| | 1386 | */ |
| | 1387 | while (i == 1) |
| | 1388 | i = rand(valuesAvail) + 1; |
| | 1389 | } |
| | 1390 | |
| | 1391 | /* remember the element we're returning */ |
| | 1392 | ret = valuesVec[i]; |
| | 1393 | |
| | 1394 | /* |
| | 1395 | * Move the value at the top of the 'available' partition down |
| | 1396 | * into the hole we're creating at 'i', since we're about to |
| | 1397 | * reduce the size of the 'available' partition to reflect the |
| | 1398 | * use of one more value; that would leave the element at the top |
| | 1399 | * of the partition homeless, so we need somewhere to put it. |
| | 1400 | * Luckily, we also need to delete element 'i', since we're using |
| | 1401 | * this element. Solve both problems at once by moving element |
| | 1402 | * we're rendering homeless into the hole we're creating. |
| | 1403 | */ |
| | 1404 | valuesVec[i] = valuesVec[valuesAvail]; |
| | 1405 | |
| | 1406 | /* move the value we're returning into the top slot */ |
| | 1407 | valuesVec[valuesAvail] = ret; |
| | 1408 | |
| | 1409 | /* reduce the 'available' partition by one */ |
| | 1410 | --valuesAvail; |
| | 1411 | |
| | 1412 | /* return the result */ |
| | 1413 | return ret; |
| | 1414 | } |
| | 1415 | |
| | 1416 | /* |
| | 1417 | * Shuffle the values. This puts all of the values back into the |
| | 1418 | * deck (as it were) for a new round. It's never required to call |
| | 1419 | * this, because getNextValue() automatically shuffles the deck and |
| | 1420 | * starts over each time it runs through the entire deck. This is |
| | 1421 | * provided in case the caller has a reason to want to put all the |
| | 1422 | * values back into play immediately, before every value has been |
| | 1423 | * dealt on the current round. |
| | 1424 | */ |
| | 1425 | reshuffle() |
| | 1426 | { |
| | 1427 | /* |
| | 1428 | * Simply reset the counter of available values. Go with the |
| | 1429 | * original source list's length, in case we haven't initialized |
| | 1430 | * our internal vector yet. |
| | 1431 | */ |
| | 1432 | valuesAvail = valueList.length(); |
| | 1433 | } |
| | 1434 | |
| | 1435 | /* |
| | 1436 | * Internal vector of available/used values. Elements from 1 to |
| | 1437 | * 'valuesAvail', inclusive, are still available for use on this |
| | 1438 | * round. Elements above 'valuesAvail' have already been used. |
| | 1439 | */ |
| | 1440 | valuesVec = nil |
| | 1441 | |
| | 1442 | /* number of values still available on this round */ |
| | 1443 | valuesAvail = 0 |
| | 1444 | ; |
| | 1445 | |
| | 1446 | /* |
| | 1447 | * A Shuffled Integer List is a special kind of Shuffled List that |
| | 1448 | * returns integers in a given range. Like an ordinary Shuffled List, |
| | 1449 | * we'll return integers in the given range in random order, but we'll |
| | 1450 | * only return each integer once during a given round; when we exhaust |
| | 1451 | * the supply, we'll reshuffle the set of integers and start over. |
| | 1452 | */ |
| | 1453 | class ShuffledIntegerList: ShuffledList |
| | 1454 | /* |
| | 1455 | * The minimum and maximum values for our range. Instances should |
| | 1456 | * define these to the range desired. |
| | 1457 | */ |
| | 1458 | rangeMin = 1 |
| | 1459 | rangeMax = 10 |
| | 1460 | |
| | 1461 | /* initialize the value list on demand */ |
| | 1462 | valueList = nil |
| | 1463 | |
| | 1464 | /* construct with the given range */ |
| | 1465 | construct(rmin, rmax) |
| | 1466 | { |
| | 1467 | rangeMin = rmin; |
| | 1468 | rangeMax = rmax; |
| | 1469 | } |
| | 1470 | |
| | 1471 | /* get the next value */ |
| | 1472 | getNextValue() |
| | 1473 | { |
| | 1474 | /* if we haven't set up our value list yet, do so now */ |
| | 1475 | if (valueList == nil) |
| | 1476 | { |
| | 1477 | local cnt; |
| | 1478 | local i; |
| | 1479 | |
| | 1480 | /* |
| | 1481 | * Set up a vector with the required number of elements. We |
| | 1482 | * have to add one to the difference between our max and min |
| | 1483 | * values, because we want the set to be inclusive of the |
| | 1484 | * endpoints. |
| | 1485 | */ |
| | 1486 | cnt = rangeMax - rangeMin + 1; |
| | 1487 | valueList = new Vector(cnt); |
| | 1488 | |
| | 1489 | /* put a nil value in each slot */ |
| | 1490 | valueList.fillValue(nil, 1, cnt); |
| | 1491 | |
| | 1492 | /* now populate the slots with the integers from our range */ |
| | 1493 | i = rangeMin; |
| | 1494 | valueList.applyAll({x: i++}); |
| | 1495 | } |
| | 1496 | |
| | 1497 | /* use the inherited handling to select from our value list */ |
| | 1498 | return inherited(); |
| | 1499 | } |
| | 1500 | ; |
| | 1501 | |
| | 1502 | |
| | 1503 | /* ------------------------------------------------------------------------ */ |
| | 1504 | /* |
| | 1505 | * Library global variables |
| | 1506 | */ |
| | 1507 | libGlobal: object |
| | 1508 | /* |
| | 1509 | * The current library messages object. This is the source object |
| | 1510 | * for messages that don't logically relate to the actor carrying out |
| | 1511 | * the comamand. It's mostly used for meta-command replies, and for |
| | 1512 | * text fragments that are used to construct descriptions. |
| | 1513 | * |
| | 1514 | * This message object isn't generally used for parser messages or |
| | 1515 | * action replies - most of those come from the objects given by the |
| | 1516 | * current actor's getParserMessageObj() or getActionMessageObj(), |
| | 1517 | * respectively. |
| | 1518 | * |
| | 1519 | * By default, this is set to libMessages. The library never changes |
| | 1520 | * this itself, but a game can change this if it wants to switch to a |
| | 1521 | * new set of messages during a game. (If you don't need to change |
| | 1522 | * messages during a game, but simply want to customize some of the |
| | 1523 | * default messages, you don't need to set this variable - you can |
| | 1524 | * simply use 'modify libMessages' instead. This variable is |
| | 1525 | * designed for cases where you want to *dynamically* change the |
| | 1526 | * standard messages during the game.) |
| | 1527 | */ |
| | 1528 | libMessageObj = libMessages |
| | 1529 | |
| | 1530 | /* |
| | 1531 | * Sense cache - we keep SenseInfo lists here, keyed by [pov,sense]; |
| | 1532 | * we normally discard the cached information at the start of each |
| | 1533 | * turn, and disable caching entirely at the start of the "action" |
| | 1534 | * phase of each turn. We leave caching disabled during each turn's |
| | 1535 | * action phase because this is the phase where simulation state |
| | 1536 | * changes are typically made, and hence it would be difficult to |
| | 1537 | * keep the cache coherent during this phase. |
| | 1538 | * |
| | 1539 | * When this is nil, it indicates that caching is disabled. We only |
| | 1540 | * allow caching during certain phases of execution, when game state |
| | 1541 | * is not conventionally altered, so that we don't have to do a lot |
| | 1542 | * of work to keep the cache up to date. |
| | 1543 | */ |
| | 1544 | senseCache = nil |
| | 1545 | |
| | 1546 | /* |
| | 1547 | * Can-Touch cache - we keep CanTouchInfo entires here, keyed by |
| | 1548 | * [from,to]. This cache is the touch-path equivalent of the sense |
| | 1549 | * cache, and is enabled and disabled |
| | 1550 | */ |
| | 1551 | canTouchCache = nil |
| | 1552 | |
| | 1553 | /* |
| | 1554 | * Connection list cache - this is a cache of all of the objects |
| | 1555 | * connected by containment to a given object. |
| | 1556 | */ |
| | 1557 | connectionCache = nil |
| | 1558 | |
| | 1559 | /* |
| | 1560 | * Actor visual ambient cache - this keeps track of the ambient light |
| | 1561 | * level at the given actor. |
| | 1562 | */ |
| | 1563 | actorVisualAmbientCache = nil |
| | 1564 | |
| | 1565 | /* enable the cache, clearing any old cached information */ |
| | 1566 | enableSenseCache() |
| | 1567 | { |
| | 1568 | /* create a new, empty lookup table for the sense cache */ |
| | 1569 | senseCache = new LookupTable(32, 64); |
| | 1570 | |
| | 1571 | /* create the can-touch cache */ |
| | 1572 | canTouchCache = new LookupTable(32, 64); |
| | 1573 | |
| | 1574 | /* create the actor visual ambient cache */ |
| | 1575 | actorVisualAmbientCache = new LookupTable(32, 64); |
| | 1576 | |
| | 1577 | /* create a connection list cache */ |
| | 1578 | connectionCache = new LookupTable(32, 64); |
| | 1579 | } |
| | 1580 | |
| | 1581 | /* disable the cache */ |
| | 1582 | disableSenseCache() |
| | 1583 | { |
| | 1584 | /* forget the cache tables */ |
| | 1585 | senseCache = nil; |
| | 1586 | canTouchCache = nil; |
| | 1587 | actorVisualAmbientCache = nil; |
| | 1588 | connectionCache = nil; |
| | 1589 | } |
| | 1590 | |
| | 1591 | /* |
| | 1592 | * Invalidate the sense cache. This can be called if something |
| | 1593 | * happens during noun resolution or verification that causes any |
| | 1594 | * cached sense information to become out of date. For example, if |
| | 1595 | * you have to create a new game-world object during noun-phrase |
| | 1596 | * resolution, this should be called to ensure that the new object's |
| | 1597 | * visibility is properly calculated and incorporated into the cached |
| | 1598 | * information. |
| | 1599 | */ |
| | 1600 | invalSenseCache() |
| | 1601 | { |
| | 1602 | /* remember whether or not caching is currently enabled */ |
| | 1603 | local wasEnabled = (senseCache != nil); |
| | 1604 | |
| | 1605 | /* clear the cache by disabling it */ |
| | 1606 | disableSenseCache(); |
| | 1607 | |
| | 1608 | /* if the cache was previously enabled, re-enable it */ |
| | 1609 | if (wasEnabled) |
| | 1610 | enableSenseCache(); |
| | 1611 | } |
| | 1612 | |
| | 1613 | /* |
| | 1614 | * List of all of the senses. The library pre-initializer will load |
| | 1615 | * this list with a reference to each instance of class Sense. |
| | 1616 | */ |
| | 1617 | allSenses = [] |
| | 1618 | |
| | 1619 | /* |
| | 1620 | * The current player character |
| | 1621 | */ |
| | 1622 | playerChar = nil |
| | 1623 | |
| | 1624 | /* |
| | 1625 | * The current perspective actor. This is the actor who's performing |
| | 1626 | * the action (LOOK AROUND, EXAMINE, SMELL, etc) that's generating |
| | 1627 | * the current description. |
| | 1628 | */ |
| | 1629 | pointOfViewActor = nil |
| | 1630 | |
| | 1631 | /* |
| | 1632 | * The current perspective object. This is *usually* the actor |
| | 1633 | * performing the current command, but can be a different object when |
| | 1634 | * the actor is viewing the location being described via an |
| | 1635 | * intermediary, such as through a closed-circuit TV camera. |
| | 1636 | */ |
| | 1637 | pointOfView = nil |
| | 1638 | |
| | 1639 | /* |
| | 1640 | * The stack of point of view objects. The last element of the |
| | 1641 | * vector is the most recent point of view after the current point |
| | 1642 | * of view. |
| | 1643 | */ |
| | 1644 | povStack = static new Vector(32) |
| | 1645 | |
| | 1646 | /* |
| | 1647 | * The global score object. We use a global for this, rather than |
| | 1648 | * referencing libScore directly, to allow the score module to be |
| | 1649 | * left out entirely if the game doesn't make use of scoring. The |
| | 1650 | * score module should set this during pre-initialization. |
| | 1651 | */ |
| | 1652 | scoreObj = nil |
| | 1653 | |
| | 1654 | /* |
| | 1655 | * The global Footnote class object. We use a global for this, |
| | 1656 | * rather than referencing Footnote directly, to allow the footnote |
| | 1657 | * module to be left out entirely if the game doesn't make use of |
| | 1658 | * footnotes. The footnote class should set this during |
| | 1659 | * pre-initialization. |
| | 1660 | */ |
| | 1661 | footnoteClass = nil |
| | 1662 | |
| | 1663 | /* the total number of turns so far */ |
| | 1664 | totalTurns = 0 |
| | 1665 | |
| | 1666 | /* |
| | 1667 | * flag: the parser is in 'debug' mode, in which it displays the |
| | 1668 | * parse tree for each command entered |
| | 1669 | */ |
| | 1670 | parserDebugMode = nil |
| | 1671 | |
| | 1672 | /* |
| | 1673 | * Most recent command, for 'undo' purposes. This is the last |
| | 1674 | * command the player character performed, or the last initial |
| | 1675 | * command a player directed to an NPC. |
| | 1676 | * |
| | 1677 | * Note that if the player directed a series of commands to an NPC |
| | 1678 | * with a single command line, only the first command on such a |
| | 1679 | * command line is retained here, because it is only the first such |
| | 1680 | * command that counts as a player's turn in terms of the game |
| | 1681 | * clock. Subsequent commands are executed by the NPC's on the |
| | 1682 | * NPC's own time, and do not count against the PC's game clock |
| | 1683 | * time. The first command counts against the PC's clock because of |
| | 1684 | * the time it takes the PC to give the command to the NPC. |
| | 1685 | */ |
| | 1686 | lastCommandForUndo = '' |
| | 1687 | |
| | 1688 | /* |
| | 1689 | * Most recent target actor phrase; this goes with |
| | 1690 | * lastCommandForUndo. This is nil if the last command did not |
| | 1691 | * specify an actor (i.e., was implicitly for the player character), |
| | 1692 | * otherwise is the string the player typed specifying a target |
| | 1693 | * actor. |
| | 1694 | */ |
| | 1695 | lastActorForUndo = '' |
| | 1696 | |
| | 1697 | /* |
| | 1698 | * Current command information. We keep track of the current |
| | 1699 | * command's actor and action here, as well as the verification |
| | 1700 | * result list and the command report list. |
| | 1701 | */ |
| | 1702 | curActor = nil |
| | 1703 | curIssuingActor = nil |
| | 1704 | curAction = nil |
| | 1705 | curVerifyResults = nil |
| | 1706 | |
| | 1707 | /* the exitLister object, if included in the build */ |
| | 1708 | exitListerObj = nil |
| | 1709 | |
| | 1710 | /* the hint manager, if included in the build */ |
| | 1711 | hintManagerObj = nil |
| | 1712 | ; |
| | 1713 | |
| | 1714 | /* ------------------------------------------------------------------------ */ |
| | 1715 | /* |
| | 1716 | * FinishType objects are used in finishGameMsg() to indicate what kind |
| | 1717 | * of game-over message to display. We provide a couple of standard |
| | 1718 | * objects for the most common cases. |
| | 1719 | */ |
| | 1720 | class FinishType: object |
| | 1721 | /* the finishing message, as a string or library message property */ |
| | 1722 | finishMsg = nil |
| | 1723 | ; |
| | 1724 | |
| | 1725 | /* 'death' - the game has ended due to the player character's demise */ |
| | 1726 | ftDeath: FinishType finishMsg = &finishDeathMsg; |
| | 1727 | |
| | 1728 | /* 'victory' - the player has won the game */ |
| | 1729 | ftVictory: FinishType finishMsg = &finishVictoryMsg; |
| | 1730 | |
| | 1731 | /* 'failure' - the game has ended in failure (but not necessarily death) */ |
| | 1732 | ftFailure: FinishType finishMsg = &finishFailureMsg; |
| | 1733 | |
| | 1734 | /* 'game over' - the game has simply ended */ |
| | 1735 | ftGameOver: FinishType finishMsg = &finishGameOverMsg; |
| | 1736 | |
| | 1737 | /* |
| | 1738 | * Finish the game, showing a message explaining why the game has ended. |
| | 1739 | * This can be called when an event occurs that ends the game, such as |
| | 1740 | * the player character's death, winning, or any other endpoint in the |
| | 1741 | * story. |
| | 1742 | * |
| | 1743 | * We'll show a message defined by 'msg', using a standard format. The |
| | 1744 | * format depends on the language, but in English, it's usually the |
| | 1745 | * message surrounded by asterisks: "*** You have won! ***". 'msg' can |
| | 1746 | * be: |
| | 1747 | * |
| | 1748 | *. - nil, in which case we display nothing |
| | 1749 | *. - a string, which we'll display as the message |
| | 1750 | *. - a FinishType object, from which we'll get the message |
| | 1751 | * |
| | 1752 | * After showing the message (if any), we'll prompt the user with |
| | 1753 | * options for how to proceed. We'll always show the QUIT, RESTART, and |
| | 1754 | * RESTORE options; other options can be offered by listing one or more |
| | 1755 | * FinishOption objects in the 'extra' parameter, which is given as a |
| | 1756 | * list of FinishOption objects. The library defines a few non-default |
| | 1757 | * finish options, such as finishOptionUndo and finishOptionCredits; in |
| | 1758 | * addition, the game can subclass FinishOption to create its own custom |
| | 1759 | * options, as desired. |
| | 1760 | */ |
| | 1761 | finishGameMsg(msg, extra) |
| | 1762 | { |
| | 1763 | local lst; |
| | 1764 | |
| | 1765 | /* |
| | 1766 | * Adjust the turn counter to take into account the action currently |
| | 1767 | * in progress, if any, and to reflect any turns that the player |
| | 1768 | * character has already completed and which aren't yet reflected in |
| | 1769 | * the turn counter. If we're processing a daemon, the PC's next |
| | 1770 | * schedulable run time will already reflect the last turn the PC |
| | 1771 | * completed, but the global turn counter won't be there yet, since |
| | 1772 | * we're still scheduling daemons that were ready to run on the same |
| | 1773 | * turn as the player's last action. |
| | 1774 | */ |
| | 1775 | libGlobal.totalTurns = gPlayerChar.nextRunTime + gAction.actionTime; |
| | 1776 | |
| | 1777 | /* |
| | 1778 | * Explicitly run any final score notification now. This will ensure |
| | 1779 | * that any points awarded in the course of the final command that |
| | 1780 | * brought us to this point will generate the usual notification, and |
| | 1781 | * that the notification will appear at a reasonable place, just |
| | 1782 | * before the termination message. |
| | 1783 | */ |
| | 1784 | if (libGlobal.scoreObj != nil) |
| | 1785 | libGlobal.scoreObj.runScoreNotifier(); |
| | 1786 | |
| | 1787 | /* translate the message, if specified */ |
| | 1788 | if (dataType(msg) == TypeObject) |
| | 1789 | { |
| | 1790 | /* it's a FinishType object - get its message property or string */ |
| | 1791 | msg = msg.finishMsg; |
| | 1792 | |
| | 1793 | /* if it's a library message property, look it up */ |
| | 1794 | if (dataType(msg) == TypeProp) |
| | 1795 | msg = gLibMessages.(msg); |
| | 1796 | } |
| | 1797 | |
| | 1798 | /* if we have a message, display it */ |
| | 1799 | if (msg != nil) |
| | 1800 | gLibMessages.showFinishMsg(msg); |
| | 1801 | |
| | 1802 | /* if the extra options include a scoring option, show the score */ |
| | 1803 | if (extra != nil && extra.indexWhich({x: x.showScoreInFinish}) != nil) |
| | 1804 | { |
| | 1805 | "<.p>"; |
| | 1806 | libGlobal.scoreObj.showScore(); |
| | 1807 | "<.p>"; |
| | 1808 | } |
| | 1809 | |
| | 1810 | /* |
| | 1811 | * Since we need to interact directly with the player, any sense |
| | 1812 | * context currently in effect is now irrelevant. Reset the sense |
| | 1813 | * context by setting the 'source' object to nil to indicate that we |
| | 1814 | * don't need any sense blocking at all. We can just set the context |
| | 1815 | * directly, since this routine will never return into the |
| | 1816 | * surrounding command processing - we always either terminate the |
| | 1817 | * program or proceed to a different game context (via undo, restore, |
| | 1818 | * restart, etc). By the same token, the actor we're talking to now |
| | 1819 | * is the player character. |
| | 1820 | */ |
| | 1821 | senseContext.setSenseContext(nil, sight); |
| | 1822 | gActor = gPlayerChar; |
| | 1823 | |
| | 1824 | /* start with the standard options */ |
| | 1825 | lst = [finishOptionRestore, finishOptionRestart]; |
| | 1826 | |
| | 1827 | /* add any additional options in the 'extra' parameter */ |
| | 1828 | if (extra != nil) |
| | 1829 | lst += extra; |
| | 1830 | |
| | 1831 | /* always add 'quit' as the last option */ |
| | 1832 | lst += finishOptionQuit; |
| | 1833 | |
| | 1834 | /* process the options */ |
| | 1835 | processOptions(lst); |
| | 1836 | } |
| | 1837 | |
| | 1838 | /* finish the game, offering the given extra options but no message */ |
| | 1839 | finishGame(extra) |
| | 1840 | { |
| | 1841 | finishGameMsg(nil, extra); |
| | 1842 | } |
| | 1843 | |
| | 1844 | /* |
| | 1845 | * Show failed startup restore options. If a restore operation fails at |
| | 1846 | * startup, we won't just proceed with the game, but ask the user what |
| | 1847 | * they want to do; we'll offer the options of restoring another game, |
| | 1848 | * quitting, or starting the game from the beginning. |
| | 1849 | */ |
| | 1850 | failedRestoreOptions() |
| | 1851 | { |
| | 1852 | /* process our set of options */ |
| | 1853 | processOptions([restoreOptionRestoreAnother, restoreOptionStartOver, |
| | 1854 | finishOptionQuit]); |
| | 1855 | } |
| | 1856 | |
| | 1857 | /* |
| | 1858 | * Process a list of finishing options. We'll loop, showing prompts and |
| | 1859 | * reading responses, until we get a response that terminates the loop. |
| | 1860 | */ |
| | 1861 | processOptions(lst) |
| | 1862 | { |
| | 1863 | /* keep going until we get a valid response */ |
| | 1864 | promptLoop: |
| | 1865 | for (;;) |
| | 1866 | { |
| | 1867 | local resp; |
| | 1868 | |
| | 1869 | /* show the options */ |
| | 1870 | finishOptionsLister.showListAll(lst, 0, 0); |
| | 1871 | |
| | 1872 | /* switch to before-command mode for reading the interactive input */ |
| | 1873 | "<.commandbefore>"; |
| | 1874 | |
| | 1875 | /* |
| | 1876 | * update the status line, in case the score or turn counter has |
| | 1877 | * changed (this is especially likely when we first enter this |
| | 1878 | * loop, since we might have just finished the game with our |
| | 1879 | * previous action, and that action might well have awarded us |
| | 1880 | * some points) |
| | 1881 | */ |
| | 1882 | statusLine.showStatusLine(); |
| | 1883 | |
| | 1884 | /* read a response */ |
| | 1885 | resp = inputManager.getInputLine(nil, nil); |
| | 1886 | |
| | 1887 | /* switch to command-after mode */ |
| | 1888 | "<.commandafter>"; |
| | 1889 | |
| | 1890 | /* check for a match to each of the options in our list */ |
| | 1891 | foreach (local cur in lst) |
| | 1892 | { |
| | 1893 | /* if this one matches, process the option */ |
| | 1894 | if (cur.responseMatches(resp)) |
| | 1895 | { |
| | 1896 | /* it matches - carry out the option */ |
| | 1897 | if (cur.doOption()) |
| | 1898 | { |
| | 1899 | /* |
| | 1900 | * they returned true - they want to continue asking |
| | 1901 | * for more options |
| | 1902 | */ |
| | 1903 | continue promptLoop; |
| | 1904 | } |
| | 1905 | else |
| | 1906 | { |
| | 1907 | /* |
| | 1908 | * they returned nil - they want us to stop asking |
| | 1909 | * for options and return to our caller |
| | 1910 | */ |
| | 1911 | return; |
| | 1912 | } |
| | 1913 | } |
| | 1914 | } |
| | 1915 | |
| | 1916 | /* |
| | 1917 | * If we got this far, it means that we didn't get a valid |
| | 1918 | * option. Display our "invalid option" message, and continue |
| | 1919 | * looping so that we show the prompt again and read a new |
| | 1920 | * option. |
| | 1921 | */ |
| | 1922 | gLibMessages.invalidFinishOption(resp); |
| | 1923 | } |
| | 1924 | } |
| | 1925 | |
| | 1926 | /* |
| | 1927 | * Finish Option class. This is the base class for the abstract objects |
| | 1928 | * representing options offered by finishGame. |
| | 1929 | */ |
| | 1930 | class FinishOption: object |
| | 1931 | /* |
| | 1932 | * The description, as displayed in the list of options. For the |
| | 1933 | * default English messages, this is expected to be a verb phrase in |
| | 1934 | * infinitive form, and should show the keyword accepted as a |
| | 1935 | * response in all capitals: "RESTART", "see some AMUSING things to |
| | 1936 | * do", "show CREDITS". |
| | 1937 | */ |
| | 1938 | desc = "" |
| | 1939 | |
| | 1940 | /* |
| | 1941 | * By default, the item is listed. If you want to create an |
| | 1942 | * invisible option that's accepted but which isn't listed in the |
| | 1943 | * prompt, just set this to nil. Invisible options are sometimes |
| | 1944 | * useful when the output of one option mentions another option; for |
| | 1945 | * example, the CREDITS message might mention a LICENSE command for |
| | 1946 | * displaying the license, so you want to make that command available |
| | 1947 | * without cluttering the prompt with it. |
| | 1948 | */ |
| | 1949 | isListed = true |
| | 1950 | |
| | 1951 | /* our response keyword */ |
| | 1952 | responseKeyword = '' |
| | 1953 | |
| | 1954 | /* |
| | 1955 | * a single character we accept as an alternative to our full |
| | 1956 | * response keyword, or nil if we don't accept a single-character |
| | 1957 | * response |
| | 1958 | */ |
| | 1959 | responseChar = nil |
| | 1960 | |
| | 1961 | /* |
| | 1962 | * Match a response string to this option. Returns true if the |
| | 1963 | * string matches our response, nil otherwise. By default, we'll |
| | 1964 | * return true if the string exactly matches responseKeyword or |
| | 1965 | * exactly matches our responseChar (if that's non-nil), but this |
| | 1966 | * can be overridden to match other strings if desired. By default, |
| | 1967 | * we'll match the response without regard to case. |
| | 1968 | */ |
| | 1969 | responseMatches(response) |
| | 1970 | { |
| | 1971 | /* do all of our work in lower-case */ |
| | 1972 | response = response.toLower(); |
| | 1973 | |
| | 1974 | /* |
| | 1975 | * check for a match the full response keyword or to the single |
| | 1976 | * response character |
| | 1977 | */ |
| | 1978 | return (response == responseKeyword.toLower() |
| | 1979 | || (responseChar != nil |
| | 1980 | && response == responseChar.toLower())); |
| | 1981 | } |
| | 1982 | |
| | 1983 | /* |
| | 1984 | * Carry out the option. This is called when the player enters a |
| | 1985 | * response that matches this option. This routine must perform the |
| | 1986 | * action of the option, then return true to indicate that we should |
| | 1987 | * ask for another option, or nil to indicate that the finishGame() |
| | 1988 | * routine should simply return. |
| | 1989 | */ |
| | 1990 | doOption() |
| | 1991 | { |
| | 1992 | /* tell finishGame() to ask for another option */ |
| | 1993 | return true; |
| | 1994 | } |
| | 1995 | |
| | 1996 | /* |
| | 1997 | * Flag: show the score with the end-of-game announcement. If any |
| | 1998 | * option in the list of finishing options has this flag set, we'll |
| | 1999 | * show the score using the same message that the SCORE command |
| | 2000 | * uses. |
| | 2001 | */ |
| | 2002 | showScoreInFinish = nil |
| | 2003 | ; |
| | 2004 | |
| | 2005 | /* |
| | 2006 | * QUIT option for finishGame. The language-specific code should modify |
| | 2007 | * this to specify the description and response keywords. |
| | 2008 | */ |
| | 2009 | finishOptionQuit: FinishOption |
| | 2010 | doOption() |
| | 2011 | { |
| | 2012 | /* |
| | 2013 | * carry out the Quit action - this will signal a |
| | 2014 | * QuittingException, so this call will never return |
| | 2015 | */ |
| | 2016 | QuitAction.terminateGame(); |
| | 2017 | } |
| | 2018 | ; |
| | 2019 | |
| | 2020 | /* |
| | 2021 | * RESTORE option for finishGame. |
| | 2022 | */ |
| | 2023 | finishOptionRestore: FinishOption |
| | 2024 | doOption() |
| | 2025 | { |
| | 2026 | /* |
| | 2027 | * Try restoring. If this succeeds (i.e., it returns true), tell |
| | 2028 | * the caller to stop looping and to proceed with the game by |
| | 2029 | * returning nil. If this fails, tell the caller to keep looping |
| | 2030 | * by returning true. |
| | 2031 | */ |
| | 2032 | if (RestoreAction.askAndRestore()) |
| | 2033 | { |
| | 2034 | /* |
| | 2035 | * we succeeded, so we're now restored to some prior game |
| | 2036 | * state - terminate any remaining processing in the command |
| | 2037 | * that triggered the end-of-game options |
| | 2038 | */ |
| | 2039 | throw new TerminateCommandException(); |
| | 2040 | } |
| | 2041 | else |
| | 2042 | { |
| | 2043 | /* it failed - tell the caller to keep looping */ |
| | 2044 | return true; |
| | 2045 | } |
| | 2046 | } |
| | 2047 | ; |
| | 2048 | |
| | 2049 | /* |
| | 2050 | * RESTART option for finishGame |
| | 2051 | */ |
| | 2052 | finishOptionRestart: FinishOption |
| | 2053 | doOption() |
| | 2054 | { |
| | 2055 | /* |
| | 2056 | * carry out the restart - this will not return, since we'll |
| | 2057 | * reset the game state and re-enter the game at the restart |
| | 2058 | * entrypoint |
| | 2059 | */ |
| | 2060 | RestartAction.doRestartGame(); |
| | 2061 | } |
| | 2062 | ; |
| | 2063 | |
| | 2064 | /* |
| | 2065 | * START FROM BEGINNING option for failed startup restore. This is just |
| | 2066 | * like finishOptionRestart, but shows a different option name. |
| | 2067 | */ |
| | 2068 | restoreOptionStartOver: finishOptionRestart |
| | 2069 | ; |
| | 2070 | |
| | 2071 | /* |
| | 2072 | * RESTORE ANOTHER GAME option for failed startup restore. This is just |
| | 2073 | * like finishOptionRestore, but shows a different option name. |
| | 2074 | */ |
| | 2075 | restoreOptionRestoreAnother: finishOptionRestore |
| | 2076 | ; |
| | 2077 | |
| | 2078 | /* |
| | 2079 | * UNDO option for finishGame |
| | 2080 | */ |
| | 2081 | finishOptionUndo: FinishOption |
| | 2082 | doOption() |
| | 2083 | { |
| | 2084 | /* try performing the undo */ |
| | 2085 | if (UndoAction.performUndo(nil)) |
| | 2086 | { |
| | 2087 | /* act as though UNDO were the last actual command, for AGAIN */ |
| | 2088 | AgainAction.saveForAgain(gPlayerChar, gPlayerChar, |
| | 2089 | nil, UndoAction); |
| | 2090 | |
| | 2091 | /* |
| | 2092 | * Success - terminate the current command with no further |
| | 2093 | * processing. |
| | 2094 | */ |
| | 2095 | throw new TerminateCommandException(); |
| | 2096 | } |
| | 2097 | else |
| | 2098 | { |
| | 2099 | /* |
| | 2100 | * failure - show a blank line and tell the caller to ask |
| | 2101 | * for another option, since we couldn't carry out this |
| | 2102 | * option |
| | 2103 | */ |
| | 2104 | "<.p>"; |
| | 2105 | return true; |
| | 2106 | } |
| | 2107 | } |
| | 2108 | ; |
| | 2109 | |
| | 2110 | /* |
| | 2111 | * FULL SCORE option for finishGame |
| | 2112 | */ |
| | 2113 | finishOptionFullScore: FinishOption |
| | 2114 | doOption() |
| | 2115 | { |
| | 2116 | /* show a blank line before the score display */ |
| | 2117 | "\b"; |
| | 2118 | |
| | 2119 | /* run the Full Score action */ |
| | 2120 | FullScoreAction.showFullScore(); |
| | 2121 | |
| | 2122 | /* show a paragraph break after the score display */ |
| | 2123 | "<.p>"; |
| | 2124 | |
| | 2125 | /* |
| | 2126 | * this option has now had its full effect, so tell the caller |
| | 2127 | * to go back and ask for a new option |
| | 2128 | */ |
| | 2129 | return true; |
| | 2130 | } |
| | 2131 | |
| | 2132 | /* |
| | 2133 | * by default, show the score with the end-of-game announcement when |
| | 2134 | * this option is included |
| | 2135 | */ |
| | 2136 | showScoreInFinish = true |
| | 2137 | ; |
| | 2138 | |
| | 2139 | /* |
| | 2140 | * Option to show the score in finishGame. This doesn't create a listed |
| | 2141 | * option in the set of offered options, but rather is simply a flag to |
| | 2142 | * finishGame() that the score should be announced along with the |
| | 2143 | * end-of-game announcement message. |
| | 2144 | */ |
| | 2145 | finishOptionScore: FinishOption |
| | 2146 | /* show the score in the end-of-game announcement */ |
| | 2147 | showScoreInFinish = true |
| | 2148 | |
| | 2149 | /* this is not a listed option */ |
| | 2150 | isListed = nil |
| | 2151 | |
| | 2152 | /* this option isn't selectable, so it has no effect */ |
| | 2153 | doOption() { } |
| | 2154 | ; |
| | 2155 | |
| | 2156 | /* |
| | 2157 | * CREDITS option for finishGame |
| | 2158 | */ |
| | 2159 | finishOptionCredits: FinishOption |
| | 2160 | doOption() |
| | 2161 | { |
| | 2162 | /* show a blank line before the credits */ |
| | 2163 | "\b"; |
| | 2164 | |
| | 2165 | /* run the Credits action */ |
| | 2166 | CreditsAction.execSystemAction(); |
| | 2167 | |
| | 2168 | /* show a paragraph break after the credits */ |
| | 2169 | "<.p>"; |
| | 2170 | |
| | 2171 | /* |
| | 2172 | * this option has now had its full effect, so tell the caller |
| | 2173 | * to go back and ask for a new option |
| | 2174 | */ |
| | 2175 | return true; |
| | 2176 | } |
| | 2177 | ; |
| | 2178 | |
| | 2179 | /* |
| | 2180 | * AMUSING option for finishGame |
| | 2181 | */ |
| | 2182 | finishOptionAmusing: FinishOption |
| | 2183 | /* |
| | 2184 | * The game must modify this object to define a doOption method. We |
| | 2185 | * have no built-in way to show a list of amusing things to try, so |
| | 2186 | * if a game wants to offer this option, it must provide a suitable |
| | 2187 | * definition here. (We never offer this option by default, so a |
| | 2188 | * game need not provide a definition if the game doesn't explicitly |
| | 2189 | * offer this option via the 'extra' argument to finishGame()). |
| | 2190 | */ |
| | 2191 | ; |
| | 2192 | |
| | 2193 | /* ------------------------------------------------------------------------ */ |
| | 2194 | /* |
| | 2195 | * The settings user interface. This is a subclass of the Settings |
| | 2196 | * Manager that adds a command-line user interface, particularly to allow |
| | 2197 | * the user to view, save, and load the default settings. |
| | 2198 | * |
| | 2199 | * Our user interface consists mainly of a pair of special commands: SAVE |
| | 2200 | * DEFAULTS and RESTORE DEFAULTS. The SAVE DEFAULTS command tells the |
| | 2201 | * library to write out all of the current settings (at least, all of |
| | 2202 | * those that participate in this framework) to a file. RESTORE DEFAULTS |
| | 2203 | * explicitly reads that same file and puts the stored settings into |
| | 2204 | * effect. Finally, we'll also read the file and activate its stored |
| | 2205 | * settings when we start (or RESTART) the game. |
| | 2206 | * |
| | 2207 | */ |
| | 2208 | settingsUI: settingsManager |
| | 2209 | /* display all of the current settings */ |
| | 2210 | showAll() |
| | 2211 | { |
| | 2212 | local first = true; |
| | 2213 | |
| | 2214 | /* loop over all SettingsItem instances */ |
| | 2215 | forEachInstance(SettingsItem, new function(item) |
| | 2216 | { |
| | 2217 | /* add a separator if this isn't the first one */ |
| | 2218 | if (!first) |
| | 2219 | gLibMessages.settingsItemSeparator; |
| | 2220 | |
| | 2221 | /* show this item's description */ |
| | 2222 | item.settingDesc; |
| | 2223 | |
| | 2224 | /* it's no longer the first */ |
| | 2225 | first = nil; |
| | 2226 | }); |
| | 2227 | } |
| | 2228 | |
| | 2229 | /* |
| | 2230 | * Save settings, and display an acknowledgment message (or an error |
| | 2231 | * message, if necessary) for the user's edification. |
| | 2232 | */ |
| | 2233 | saveSettingsMsg() |
| | 2234 | { |
| | 2235 | /* catch any errors */ |
| | 2236 | try |
| | 2237 | { |
| | 2238 | /* save the settings */ |
| | 2239 | saveSettings(); |
| | 2240 | |
| | 2241 | /* if we got this far, declare success */ |
| | 2242 | gLibMessages.savedDefaults(); |
| | 2243 | } |
| | 2244 | catch (FileCreationException fce) |
| | 2245 | { |
| | 2246 | /* we couldn't open the file */ |
| | 2247 | gLibMessages.defaultsFileWriteError; |
| | 2248 | } |
| | 2249 | } |
| | 2250 | |
| | 2251 | /* |
| | 2252 | * Restore settings, and display an acknowledgment or error message, |
| | 2253 | * as appropriate. |
| | 2254 | */ |
| | 2255 | restoreSettingsMsg() |
| | 2256 | { |
| | 2257 | /* catch any errors */ |
| | 2258 | try |
| | 2259 | { |
| | 2260 | /* restore the settings */ |
| | 2261 | restoreSettings(); |
| | 2262 | |
| | 2263 | /* if we got this far, declare success */ |
| | 2264 | gLibMessages.restoredDefaults(); |
| | 2265 | } |
| | 2266 | catch (SettingsNotSupportedException sns) |
| | 2267 | { |
| | 2268 | /* this interpreter doesn't support the settings file */ |
| | 2269 | gLibMessages.defaultsFileNotSupported; |
| | 2270 | } |
| | 2271 | } |
| | 2272 | ; |
| | 2273 | |
| | 2274 | /* ------------------------------------------------------------------------ */ |
| | 2275 | /* |
| | 2276 | * Utility functions |
| | 2277 | */ |
| | 2278 | |
| | 2279 | /* |
| | 2280 | * nilToList - convert a 'nil' value to an empty list. This can be |
| | 2281 | * useful for mix-in classes that will be used in different inheritance |
| | 2282 | * contexts, since the classes might or might not inherit a base class |
| | 2283 | * definition for list-valued methods such as preconditions. This |
| | 2284 | * provides a usable default for list-valued methods that return nothing |
| | 2285 | * from superclasses. |
| | 2286 | */ |
| | 2287 | nilToList(val) |
| | 2288 | { |
| | 2289 | return (val != nil ? val : []); |
| | 2290 | } |
| | 2291 | |
| | 2292 | /* |
| | 2293 | * partitionList - partition a list into a pair of two lists, the first |
| | 2294 | * containing items that match the predicate 'fn', the second containing |
| | 2295 | * items that don't match 'fn'. 'fn' is a function pointer (usually an |
| | 2296 | * anonymous function) that takes a single argument - a list element - |
| | 2297 | * and returns true or nil. |
| | 2298 | * |
| | 2299 | * The return value is a list with two elements. The first element is a |
| | 2300 | * list giving the elements of the original list for which 'fn' returns |
| | 2301 | * true, the second element is a list giving the elements for which 'fn' |
| | 2302 | * returns nil. |
| | 2303 | * |
| | 2304 | * (Contributed by Tommy Nordgren.) |
| | 2305 | */ |
| | 2306 | partitionList(lst, fn) |
| | 2307 | { |
| | 2308 | local lst1 = lst.subset(fn); |
| | 2309 | local lst2 = lst.subset({x : !fn(x)}); |
| | 2310 | |
| | 2311 | return [lst1, lst2]; |
| | 2312 | } |
| | 2313 | |
| | 2314 | /* |
| | 2315 | * Determine if list a is a subset of list b. a is a subset of b if |
| | 2316 | * every element of a is in b. |
| | 2317 | */ |
| | 2318 | isListSubset(a, b) |
| | 2319 | { |
| | 2320 | /* a can't be a subset if it has more elements than b */ |
| | 2321 | if (a.length() > b.length()) |
| | 2322 | return nil; |
| | 2323 | |
| | 2324 | /* check each element of a to see if it's also in b */ |
| | 2325 | foreach (local cur in a) |
| | 2326 | { |
| | 2327 | /* if this element of a is not in b, a is not a subset of b */ |
| | 2328 | if (b.indexOf(cur) == nil) |
| | 2329 | return nil; |
| | 2330 | } |
| | 2331 | |
| | 2332 | /* |
| | 2333 | * we didn't find any elements of a that are not also in b, so a is a |
| | 2334 | * subset of b |
| | 2335 | */ |
| | 2336 | return true; |
| | 2337 | } |
| | 2338 | |