| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Sample game using the TADS 3 library. |
| | 5 | * |
| | 6 | * This is an extremely contrived game, and is not intended to be the |
| | 7 | * least bit interesting to a player but rather serves as a test of |
| | 8 | * various library features during library development. |
| | 9 | */ |
| | 10 | |
| | 11 | #include "adv3.h" |
| | 12 | #include "tok.h" |
| | 13 | #include "en_us.h" |
| | 14 | |
| | 15 | /* |
| | 16 | * This is how we'd change a library grammar definition for a verb, if |
| | 17 | * we wanted to replace library grammar with our own. This doesn't |
| | 18 | * change the rest of the VerbRule definition; it merely changes the |
| | 19 | * grammar. If we wanted to change the rest of the VerbRule, we'd use |
| | 20 | * 'replace' rather than 'modify'. Note that 'replace' and' modify' |
| | 21 | * both replace the grammar rule itself, though; they differ in how they |
| | 22 | * handle the *rest* of the properties of the VerbRule object. |
| | 23 | * |
| | 24 | * To delete a library verb rule grammar, modify the rule and use an |
| | 25 | * unmatchable token as the replacement grammar rule (' ' will work, |
| | 26 | * since the standard tokenizer won't return a space character as a |
| | 27 | * token). |
| | 28 | */ |
| | 29 | //modify VerbRule(Examine) |
| | 30 | // ('ex' | 'lkat') DobjList : |
| | 31 | //; |
| | 32 | |
| | 33 | |
| | 34 | /* ------------------------------------------------------------------------ */ |
| | 35 | /* |
| | 36 | * Game credits and version information |
| | 37 | */ |
| | 38 | versionInfo: GameID |
| | 39 | IFID = 'fa555579-c107-f533-d2a8-d2c93ca87ea0' |
| | 40 | name = 'TADS 3 Library Sampler' |
| | 41 | version = '1.0' |
| | 42 | byline = 'by M.J.Roberts' |
| | 43 | htmlByline = |
| | 44 | 'by <a href="mailto:mjr_@hotmail.com">M. J. Roberts</a>' |
| | 45 | authorEmail = 'M.J. Roberts <mjr_@hotmail.com>' |
| | 46 | desc = 'A random sample and test of features of |
| | 47 | the tads 3 library.' |
| | 48 | htmlDesc = 'A random sample and test of features of the |
| | 49 | <b><font size=-1>TADS</font> 3 library</b>.' |
| | 50 | |
| | 51 | /* add some special text of our own for the credits */ |
| | 52 | showCredit() |
| | 53 | { |
| | 54 | "<b><<name>></b>\n |
| | 55 | <<htmlByline>>\b |
| | 56 | Thanks to everyone who has participated in the TADS 3 library |
| | 57 | design process for contributing so many great ideas. |
| | 58 | \b |
| | 59 | <center> |
| | 60 | * * * |
| | 61 | \n |
| | 62 | </center> |
| | 63 | \b"; |
| | 64 | } |
| | 65 | |
| | 66 | /* show "about" information */ |
| | 67 | showAbout() |
| | 68 | { |
| | 69 | "This is just a boring sample game to test features of |
| | 70 | the library under construction. Everything in this game |
| | 71 | is contrived to exercise a particular set of library |
| | 72 | features, and we make no attempt to make a coherent |
| | 73 | game out of this hodge-podge of tests. A more interesting |
| | 74 | demonstration game will have to wait until the library |
| | 75 | is further along. "; |
| | 76 | } |
| | 77 | ; |
| | 78 | |
| | 79 | /* ------------------------------------------------------------------------ */ |
| | 80 | /* |
| | 81 | * Set up a barrier object so that we can conveniently prevent the |
| | 82 | * tricycle from going past certain points. This is just a generic |
| | 83 | * vehicle barrier, but we customize the failure message. |
| | 84 | */ |
| | 85 | tricycleBarrier: VehicleBarrier |
| | 86 | construct(msg) { msg_ = msg; } |
| | 87 | explainTravelBarrier(traveler) |
| | 88 | { |
| | 89 | if (traveler == tricycle) |
| | 90 | reportFailure(msg_); |
| | 91 | else |
| | 92 | inherited(traveler); |
| | 93 | } |
| | 94 | msg_ = '{You/he}\'d best stay indoors with the tricycle; it would |
| | 95 | probably break if you rode it outside. ' |
| | 96 | ; |
| | 97 | |
| | 98 | /* set up a barrier against pushing the television out of the house */ |
| | 99 | tvBarrier: PushTravelBarrier |
| | 100 | construct(msg) { msg_ = msg; } |
| | 101 | canPushedObjectPass(obj) { return obj != television; } |
| | 102 | explainTravelBarrier(traveler) { reportFailure(msg_); } |
| | 103 | msg_ = 'The television\'s casters are too small to |
| | 104 | allow the TV to go beyond this point. ' |
| | 105 | ; |
| | 106 | |
| | 107 | /* ------------------------------------------------------------------------ */ |
| | 108 | /* |
| | 109 | * Trivial class for rooms in the house. We use this mostly for |
| | 110 | * identification of house rooms. |
| | 111 | */ |
| | 112 | class HouseRoom: Room |
| | 113 | ; |
| | 114 | |
| | 115 | class DarkHouseRoom: DarkRoom, HouseRoom |
| | 116 | ; |
| | 117 | |
| | 118 | /* ------------------------------------------------------------------------ */ |
| | 119 | /* |
| | 120 | * Basic Coin class |
| | 121 | */ |
| | 122 | class Coin: Thing |
| | 123 | vocabWords = 'coin*coins' |
| | 124 | listWith = [coinGroup] |
| | 125 | |
| | 126 | /* |
| | 127 | * The base name for the coin as it appears in groups. We leave off |
| | 128 | * the word "coin" because the coin group prefix uses it. |
| | 129 | */ |
| | 130 | coinGroupBaseName = '' |
| | 131 | |
| | 132 | /* use the coinCollective for selected actions on coins */ |
| | 133 | collectiveGroups = [coinCollective] |
| | 134 | |
| | 135 | /* |
| | 136 | * coin group name and counted name - we synthesize these from the |
| | 137 | * group base name |
| | 138 | */ |
| | 139 | coinGroupName = ('one ' + coinGroupBaseName) |
| | 140 | countedCoinGroupName(cnt) |
| | 141 | { return spellIntBelow(cnt, 100) + ' ' + coinGroupBaseName; } |
| | 142 | ; |
| | 143 | |
| | 144 | /* |
| | 145 | * copper coins |
| | 146 | */ |
| | 147 | class CopperCoin: Coin 'copper -' 'copper coin' @livingRoom |
| | 148 | isEquivalent = true |
| | 149 | coinValue = 1 |
| | 150 | coinGroupBaseName = 'copper' |
| | 151 | ; |
| | 152 | |
| | 153 | /* |
| | 154 | * silver coins |
| | 155 | */ |
| | 156 | class SilverCoin: Coin 'silver -' 'silver coin' @livingRoom |
| | 157 | isEquivalent = true |
| | 158 | coinValue = 5 |
| | 159 | coinGroupBaseName = 'silver' |
| | 160 | ; |
| | 161 | |
| | 162 | /* |
| | 163 | * gold coins |
| | 164 | */ |
| | 165 | class GoldCoin: Coin 'gold -' 'gold coin' @livingRoom |
| | 166 | isEquivalent = true |
| | 167 | coinValue = 10 |
| | 168 | coinGroupBaseName = 'gold' |
| | 169 | |
| | 170 | /* flag: I've awarded points for being taken by the player character */ |
| | 171 | takeAwarded = nil |
| | 172 | |
| | 173 | /* award points when the item is first taken */ |
| | 174 | afterAction() |
| | 175 | { |
| | 176 | /* inherit the default handling */ |
| | 177 | inherited(); |
| | 178 | |
| | 179 | /* |
| | 180 | * if I'm now being held directly or indirectly, and I haven't |
| | 181 | * awarded my points for being taken before, award points now |
| | 182 | */ |
| | 183 | if (!takeAwarded && isIn(libGlobal.playerChar)) |
| | 184 | { |
| | 185 | /* award my points */ |
| | 186 | goldCoinAchievement.awardPoints(); |
| | 187 | |
| | 188 | /* only award this achievement once per coin */ |
| | 189 | takeAwarded = true; |
| | 190 | } |
| | 191 | } |
| | 192 | ; |
| | 193 | |
| | 194 | /* |
| | 195 | * Coin group - this is a listing group we use to group coins in room |
| | 196 | * contents lists, object contents lists, and inventory lists. |
| | 197 | */ |
| | 198 | coinGroup: ListGroupParen |
| | 199 | showGroupCountName(lst) |
| | 200 | { |
| | 201 | "<<spellIntBelowExt(lst.length(), 100, 0, |
| | 202 | DigitFormatGroupSep)>> coins"; |
| | 203 | } |
| | 204 | |
| | 205 | /* |
| | 206 | * Just for the heck of it, order coins in a group in descending |
| | 207 | * order of monetary value. Since we want descending order, return |
| | 208 | * the negative of the value comparison. |
| | 209 | */ |
| | 210 | compareGroupItems(a, b) { return b.coinValue - a.coinValue; } |
| | 211 | |
| | 212 | /* |
| | 213 | * Customize the display of the individual coins listed in a coin |
| | 214 | * group, so that we leave off the word "coin" from the name. This |
| | 215 | * will make our group list look like so: |
| | 216 | * |
| | 217 | * five coins (two gold, two silver, one copper) |
| | 218 | */ |
| | 219 | showGroupItem(lister, obj, options, pov, info) |
| | 220 | { say(obj.coinGroupName); } |
| | 221 | showGroupItemCounted(lister, lst, options, pov, infoTab) |
| | 222 | { say(lst[1].countedCoinGroupName(lst.length())); } |
| | 223 | ; |
| | 224 | |
| | 225 | /* |
| | 226 | * Coin Collective - this is a collective group object that we use to |
| | 227 | * perform certain actions on coins collectively, rather than iteratively |
| | 228 | * on the individuals. This is an "itemizing" collective group, because |
| | 229 | * we want "examine coins" to show a message listing the individual |
| | 230 | * coins. |
| | 231 | */ |
| | 232 | coinCollective: ItemizingCollectiveGroup '*coins' 'coins' |
| | 233 | ; |
| | 234 | |
| | 235 | |
| | 236 | /* ------------------------------------------------------------------------ */ |
| | 237 | /* |
| | 238 | * Balloons |
| | 239 | */ |
| | 240 | class Balloon: Thing 'inflated uninflated popped balloon' |
| | 241 | /* balloons are by default equivalent to one another */ |
| | 242 | isEquivalent = true |
| | 243 | |
| | 244 | /* list balloons in one place as a group */ |
| | 245 | listWith = [balloonGroup] |
| | 246 | |
| | 247 | /* our state index - this is an index into our allStates list */ |
| | 248 | state = nil |
| | 249 | |
| | 250 | /* get our current state - translates our index to a state */ |
| | 251 | getState = (allStates[state]) |
| | 252 | |
| | 253 | /* our list of all of our possible states */ |
| | 254 | allStates = [balloonStateInflated, |
| | 255 | balloonStateUninflated, |
| | 256 | balloonStatePopped] |
| | 257 | ; |
| | 258 | |
| | 259 | class RedBalloon: Balloon 'red -' 'red balloon'; |
| | 260 | class BlueBalloon: Balloon 'blue -' 'blue balloon'; |
| | 261 | class GreenBalloon: Balloon 'green -' 'green balloon'; |
| | 262 | |
| | 263 | /* use a simple unadorned grouper to keep all the balloons together */ |
| | 264 | balloonGroup: ListGroupSorted; |
| | 265 | |
| | 266 | /* balloon state objects */ |
| | 267 | balloonStateInflated: ThingState 'inflated' +1 |
| | 268 | stateTokens = ['inflated'] |
| | 269 | ; |
| | 270 | balloonStateUninflated: ThingState 'uninflated' +2 |
| | 271 | stateTokens = ['uninflated'] |
| | 272 | ; |
| | 273 | balloonStatePopped: ThingState 'popped' +3 |
| | 274 | stateTokens = ['popped'] |
| | 275 | ; |
| | 276 | |
| | 277 | |
| | 278 | sun: MultiFaceted |
| | 279 | initialLocationClass = OutdoorRoom |
| | 280 | instanceObject: Distant { 'sun' 'sun' |
| | 281 | "It's the yellow star around which the Earth orbits. " |
| | 282 | } |
| | 283 | ; |
| | 284 | |
| | 285 | |
| | 286 | /* ------------------------------------------------------------------------ */ |
| | 287 | /* |
| | 288 | * Living Room |
| | 289 | */ |
| | 290 | |
| | 291 | redBook: Thing 'red book*books' 'red book' @livingRoom; |
| | 292 | blueBook: Thing 'blue test book/booklet*books booklets' |
| | 293 | 'blue test booklet' @livingRoom; |
| | 294 | bigRedBall: Thing 'big large red ball*balls' 'large red ball' @livingRoom; |
| | 295 | smallRedBall: Thing 'small little red ball*balls' 'small red ball' @livingRoom; |
| | 296 | greenBall: Thing 'green ball*balls' 'green ball' @livingRoom; |
| | 297 | box: Container 'brown cardboard box' 'brown box' @livingRoom; |
| | 298 | poBox: Thing 'p. o. p.o. po post office # box' 'PO Box' @livingRoom; |
| | 299 | eightball: Thing '8 ball/8-ball*balls' '8-ball' @livingRoom; |
| | 300 | |
| | 301 | greenJar: OpenableContainer 'green glass jar*jars' 'green jar' @livingRoom |
| | 302 | "It's made of transparent green glass. " |
| | 303 | material = glass |
| | 304 | initiallyOpen = nil |
| | 305 | ; |
| | 306 | |
| | 307 | clearJar: OpenableContainer 'clear glass jar*jars' 'clear jar' @livingRoom |
| | 308 | "It's made of clear glass. " |
| | 309 | material = glass |
| | 310 | initiallyOpen = true |
| | 311 | isEquivalent = true |
| | 312 | ; |
| | 313 | |
| | 314 | CopperCoin location=greenJar; |
| | 315 | CopperCoin location=greenJar; |
| | 316 | |
| | 317 | SilverCoin; |
| | 318 | SilverCoin; |
| | 319 | SilverCoin; |
| | 320 | |
| | 321 | GoldCoin; |
| | 322 | GoldCoin; |
| | 323 | GoldCoin; |
| | 324 | GoldCoin; |
| | 325 | GoldCoin; |
| | 326 | |
| | 327 | /* achievement for obtaining a gold coin */ |
| | 328 | goldCoinAchievement: Achievement |
| | 329 | "obtaining <<spellInt(scoreCount)>> gold coin<< |
| | 330 | scoreCount > 1 ? 's' : ''>>" |
| | 331 | |
| | 332 | /* |
| | 333 | * we're worth two points, and we can be awarded up to five times |
| | 334 | * (one time per gold coin), for a total of ten points |
| | 335 | */ |
| | 336 | points = 2 |
| | 337 | maxPoints = 10 |
| | 338 | ; |
| | 339 | |
| | 340 | ironKey: Key 'iron key*keys' 'iron key' @livingRoom |
| | 341 | "It's an ordinary iron key. " |
| | 342 | ; |
| | 343 | brassKey: Key 'brass key*keys' 'brass key' @livingRoom |
| | 344 | "It's an ordinary brass key. " |
| | 345 | ; |
| | 346 | rustyKey: Key 'rusty key*keys' 'rusty key' @livingRoom |
| | 347 | "It's an ordinary rusty key. " |
| | 348 | ; |
| | 349 | |
| | 350 | /* |
| | 351 | * Note that we don't have to define any vocabulary for the player |
| | 352 | * character, since the library automatically defines the appropriate |
| | 353 | * pronouns for any actor being used as the player character. The |
| | 354 | * pronoun selection is automatic and dynamic, so if we change to |
| | 355 | * another player character later, the pronouns will all switch |
| | 356 | * automatically at the same time. |
| | 357 | */ |
| | 358 | me: Person |
| | 359 | location = livingRoom |
| | 360 | bulkCapacity = 5 // to force lots of bag-of-holding shuffling, for testing |
| | 361 | // referralPerson = firstPerson |
| | 362 | |
| | 363 | /* |
| | 364 | * When we issue a series of commands to another actor, we can wait |
| | 365 | * until the entire series finishes before we take another turn. |
| | 366 | * Set this flag to true to wait, or nil to allow us to take turns |
| | 367 | * while the other actor carries out our orders. |
| | 368 | */ |
| | 369 | issueCommandsSynchronously = true |
| | 370 | ; |
| | 371 | |
| | 372 | + duffel: BagOfHolding, Openable, Container 'duffel bag' 'duffel bag' |
| | 373 | "It's a really capacious bag. " |
| | 374 | ; |
| | 375 | |
| | 376 | + keyring: Keyring 'key ring/keyring' 'keyring' |
| | 377 | ; |
| | 378 | |
| | 379 | bob: Person 'bob' 'Bob' @livingRoom |
| | 380 | isProperName = true |
| | 381 | isHim = true |
| | 382 | |
| | 383 | /* obey any command from any other character */ |
| | 384 | obeyCommand(issuer, action) { return true; } |
| | 385 | |
| | 386 | /* |
| | 387 | * We want to be able to follow other actors if requested, so keep |
| | 388 | * track of departure data. NPC's don't track departure information |
| | 389 | * by default, because most NPC's never need it, and tracking it |
| | 390 | * consumes a little extra time and memory. |
| | 391 | */ |
| | 392 | wantsFollowInfo(obj) { return true; } |
| | 393 | ; |
| | 394 | |
| | 395 | /* |
| | 396 | * Respond to any ASK ABOUT topic with a generic message incorporating |
| | 397 | * the tokens from the topic. |
| | 398 | */ |
| | 399 | + DefaultAskTopic |
| | 400 | "<q>Ah, yes, <<gTopic.getTopicText()>>, very interesting...</q> " |
| | 401 | ; |
| | 402 | |
| | 403 | + GiveTopic |
| | 404 | matchTopic(fromActor, obj) |
| | 405 | { |
| | 406 | /* we match any coin */ |
| | 407 | return obj.ofKind(Coin) ? matchScore : nil; |
| | 408 | } |
| | 409 | |
| | 410 | handleTopic(fromActor, obj) |
| | 411 | { |
| | 412 | /* accept the coin into my actor's inventory */ |
| | 413 | obj.moveInto(getActor()); |
| | 414 | |
| | 415 | /* add our special report */ |
| | 416 | gTranscript.addReport(new AcceptCoinReport(obj)); |
| | 417 | |
| | 418 | /* register for collective handling at the end of the command */ |
| | 419 | gAction.callAfterActionMain(self); |
| | 420 | } |
| | 421 | |
| | 422 | afterActionMain() |
| | 423 | { |
| | 424 | /* |
| | 425 | * adjust the transcript by summarizing consecutive coin |
| | 426 | * acceptance reports |
| | 427 | */ |
| | 428 | gTranscript.summarizeAction( |
| | 429 | {x: x.ofKind(AcceptCoinReport)}, |
| | 430 | {vec: '\^' + getActor().theName + ' accepts the ' |
| | 431 | + spellInt(vec.length()) + ' coins. ' }); |
| | 432 | } |
| | 433 | ; |
| | 434 | |
| | 435 | + AskForTopic [ironKey, brassKey, rustyKey] |
| | 436 | handleTopic(fromActor, topic) |
| | 437 | { |
| | 438 | "<q>So, you want a key, do you? Let's see... "; |
| | 439 | SimpleLister.showSimpleList(topic.inScopeList + topic.likelyList); |
| | 440 | "...</q>"; |
| | 441 | } |
| | 442 | ; |
| | 443 | |
| | 444 | class AcceptCoinReport: MainCommandReport |
| | 445 | construct(obj) |
| | 446 | { |
| | 447 | /* remember the coin we accepted */ |
| | 448 | coinObj = obj; |
| | 449 | |
| | 450 | /* inherit the default handling */ |
| | 451 | gMessageParams(obj); |
| | 452 | inherited('Bob accepts {the obj/him}. '); |
| | 453 | } |
| | 454 | |
| | 455 | /* my coin object */ |
| | 456 | coinObj = nil |
| | 457 | ; |
| | 458 | |
| | 459 | bill: Person 'bill' 'Bill' @livingRoom |
| | 460 | isProperName = true |
| | 461 | isHim = true |
| | 462 | obeyCommand(issuer, action) { return true; } |
| | 463 | wantsFollowInfo(obj) { return true; } |
| | 464 | |
| | 465 | /* |
| | 466 | * Make Bill go after Bob. This normally wouldn't be important, |
| | 467 | * since the relative order of actor turns is usually arbitrary, but |
| | 468 | * we want our test scripts to be stable so we establish a fixed |
| | 469 | * order. We accomplish the order adjustment simply by giving Bill a |
| | 470 | * slightly higher schedule order than the default. |
| | 471 | */ |
| | 472 | calcScheduleOrder() |
| | 473 | { |
| | 474 | inherited(); |
| | 475 | scheduleOrder += 1; |
| | 476 | } |
| | 477 | ; |
| | 478 | |
| | 479 | livingRoom: HouseRoom |
| | 480 | roomName = 'Living Room' |
| | 481 | destName = 'the living room' |
| | 482 | desc = "It's a nice, big room. A potted plant is the only attempt |
| | 483 | at decoration. Passages lead north, east, and west. |
| | 484 | To the south, the front door of the house |
| | 485 | leads outside. " |
| | 486 | north = diningRoom |
| | 487 | east = den |
| | 488 | south = frontDoor |
| | 489 | out asExit(south) |
| | 490 | west = platformRoom |
| | 491 | ; |
| | 492 | |
| | 493 | + tricycle: Vehicle, BasicChair 'tricycle/trike' 'tricycle' |
| | 494 | "It's a child's three-wheeled bike. " |
| | 495 | |
| | 496 | gaveInstructions = nil |
| | 497 | |
| | 498 | allowedPostures = [sitting] |
| | 499 | |
| | 500 | /* |
| | 501 | * Customize the travel arrival/departure messages for certain types |
| | 502 | * of connectors. The default message is of the form "The tricycle |
| | 503 | * (carrying whoever) arrives from the east." This isn't great for |
| | 504 | * the tricycle, which only carries one rider, and which only goes |
| | 505 | * when the rider makes it go; a better message would be "Bob rides |
| | 506 | * the tricycle in from the east," which better captures that the |
| | 507 | * rider is the one making the tricycle go somewhere. |
| | 508 | */ |
| | 509 | sayArrivingDir(dir, conn) |
| | 510 | { |
| | 511 | local actor = getTravelerActors()[1]; |
| | 512 | |
| | 513 | "<<actor.name>> ride<<actor.verbEndingS>> in from the |
| | 514 | <<dir.name>> on a tricycle. "; |
| | 515 | } |
| | 516 | sayArrivingThroughPassage(conn) |
| | 517 | { |
| | 518 | local actor = getTravelerActors()[1]; |
| | 519 | |
| | 520 | "<<actor.name>> ride<<actor.verbEndingS>> in through |
| | 521 | <<conn.theName>> on a tricycle. "; |
| | 522 | } |
| | 523 | sayDepartingDir(dir, conn) |
| | 524 | { |
| | 525 | local actor = getTravelerActors()[1]; |
| | 526 | |
| | 527 | "<<actor.name>> ride<<actor.verbEndingS>> the tricycle off |
| | 528 | to the <<dir.name>>. "; |
| | 529 | } |
| | 530 | sayDepartingThroughPassage(conn) |
| | 531 | { |
| | 532 | local actor = getTravelerActors()[1]; |
| | 533 | |
| | 534 | "<<actor.name>> ride<<actor.verbEndingS>> off |
| | 535 | through <<conn.theName>> on the tricycle. "; |
| | 536 | } |
| | 537 | |
| | 538 | dobjFor(Ride) asDobjFor(SitOn) |
| | 539 | |
| | 540 | dobjFor(SitOn) |
| | 541 | { |
| | 542 | check() |
| | 543 | { |
| | 544 | /* don't allow riding the tricycle except in the house */ |
| | 545 | if (!gActor.location.getOutermostRoom().ofKind(HouseRoom)) |
| | 546 | { |
| | 547 | reportFailure('{You/he} shouldn\'t ride {the dobj/him} |
| | 548 | except in the house; {it dobj/he} might break. '); |
| | 549 | exit; |
| | 550 | } |
| | 551 | } |
| | 552 | |
| | 553 | action() |
| | 554 | { |
| | 555 | /* inherit the default handling */ |
| | 556 | inherited(); |
| | 557 | |
| | 558 | /* if we haven't given instructions previously, do so now */ |
| | 559 | if (!gaveInstructions) |
| | 560 | { |
| | 561 | mainReport('Okay, {you\'re} now on the tricycle. {You/he} |
| | 562 | can probably manage to ride around on it if |
| | 563 | {it/he} tr{ies}; just say the direction you\'d like ' |
| | 564 | + (gActor.isPlayerChar ? '' : '{it/him}') + ' to go. '); |
| | 565 | gaveInstructions = true; |
| | 566 | } |
| | 567 | } |
| | 568 | } |
| | 569 | |
| | 570 | dobjFor(Eat) { verify() { } action() { "{You/he} eat{s} {your} tricycle. "; } } |
| | 571 | ; |
| | 572 | |
| | 573 | + frontDoor: Door 'front door' 'front door' |
| | 574 | "It leads outside to the south. " |
| | 575 | travelBarrier = [tricycleBarrier, tvBarrier] |
| | 576 | ; |
| | 577 | |
| | 578 | + Decoration |
| | 579 | 'potted plant small green clay plant/tree/pot/leaf/leaves/dirt/soil' |
| | 580 | 'potted plant' |
| | 581 | "It's a tree with small green leaves, about six feet tall and |
| | 582 | growing in a clay pot filled with soil. " |
| | 583 | ; |
| | 584 | |
| | 585 | + television: TravelPushable |
| | 586 | 'old huge boxy model/unit/television/tv' 'television' |
| | 587 | "It's an old, huge, boxy model, certainly black-and-white. The |
| | 588 | bulbous picture tube shows no sign of life. Two dials control |
| | 589 | the channel selection (it looks like it's currently <<curChannel>>), |
| | 590 | but the channel setting hardly matters when the set doesn't work |
| | 591 | in the first place. It seems to be on small casters, which is |
| | 592 | a good thing given how big it is and how heavy it looks. " |
| | 593 | |
| | 594 | specialDesc = "An old television, a huge, boxy, massive-looking thing, |
| | 595 | sits on the floor. " |
| | 596 | specialNominalRoomPartLocation = defaultFloor |
| | 597 | |
| | 598 | curChannel() |
| | 599 | { |
| | 600 | if (upperDial.curSetting == 'UHF') |
| | 601 | return lowerDial.curSetting; |
| | 602 | else |
| | 603 | return upperDial.curSetting; |
| | 604 | } |
| | 605 | ; |
| | 606 | |
| | 607 | ++ Decoration 'casters' 'casters' |
| | 608 | "Small wheels, intended to make it easier to move the unit's |
| | 609 | huge bulk around the house. " |
| | 610 | ; |
| | 611 | |
| | 612 | ++ Decoration 'bulbous picture tube/screen' 'picture tube' |
| | 613 | "It shows no pictures. "; |
| | 614 | |
| | 615 | ++ upperDial: NumberedDial, Component |
| | 616 | 'upper tv television channel dial*dials' 'upper dial' |
| | 617 | "This is the VHF selector dial; it can be set to a channel from 2 |
| | 618 | through 13, or to the additional stop marked \"UHF\". It's |
| | 619 | currently set to <<curSetting>>. " |
| | 620 | |
| | 621 | minSetting = 2 |
| | 622 | maxSetting = 13 |
| | 623 | curSetting = 8 |
| | 624 | isValidSetting(val) |
| | 625 | { |
| | 626 | /* allow the special 'uhf' setting in addition to the numbers */ |
| | 627 | if (val.toLower() == 'uhf') |
| | 628 | return true; |
| | 629 | |
| | 630 | /* inherit the default handling to check the numbered settings */ |
| | 631 | return inherited(val); |
| | 632 | } |
| | 633 | makeSetting(val) |
| | 634 | { |
| | 635 | /* if the setting is 'uhf', change it to all caps */ |
| | 636 | if (val.toLower() == 'uhf') |
| | 637 | val = 'UHF'; |
| | 638 | |
| | 639 | /* inherit default handling with the (possibly) adjusted value */ |
| | 640 | inherited(val); |
| | 641 | } |
| | 642 | ; |
| | 643 | |
| | 644 | ++ lowerDial: NumberedDial, Component |
| | 645 | 'lower tv television channel dial*dials' 'lower dial' |
| | 646 | "This is the UHF selector dial; it can be set to a channel from |
| | 647 | 14 through 82. It's currently set to <<curSetting>>. You probably |
| | 648 | have to turn the upper dial to \"UHF\" for this dial's setting |
| | 649 | to have any effect. " |
| | 650 | minSetting = 14 |
| | 651 | maxSetting = 82 |
| | 652 | curSetting = 44 |
| | 653 | ; |
| | 654 | |
| | 655 | |
| | 656 | + Consultable 'phone book' 'phone book' |
| | 657 | "You can probably look up people you know in here. " |
| | 658 | ; |
| | 659 | ++ ConsultTopic @bob "Bob's number is (415)555-1212. "; |
| | 660 | ++ ConsultTopic @bill "Bill's number is (650)555-1212. "; |
| | 661 | ++ ConsultTopic @salesman "Ron's number is (510)555-1212. "; |
| | 662 | ++ ConsultTopic @insCompany |
| | 663 | "<q>Actuarial Life Insurance Company! Call for best rates! |
| | 664 | (800)555-1212!</q> " |
| | 665 | ; |
| | 666 | ++ DefaultConsultTopic "You can't find any such listing. "; |
| | 667 | |
| | 668 | /* ------------------------------------------------------------------------ */ |
| | 669 | /* |
| | 670 | * Platform room |
| | 671 | */ |
| | 672 | platformRoom: HouseRoom 'Platform Room' 'the platform room' |
| | 673 | "This room is obviously highly contrived. Filling one end of the |
| | 674 | room is a platform, almost like a theater stage, raised a couple of |
| | 675 | feet above the floor. At opposite ends of the |
| | 676 | platform are two smaller platforms, one red and one blue; |
| | 677 | atop each smaller platform is a chair of the same color as its platform. |
| | 678 | In the center of the main platform is a raised dais, upon which |
| | 679 | <<stool.isDirectlyIn(dais) ? "are a wooden stool and" : "is">> a |
| | 680 | leather armchair. |
| | 681 | <.p>The only exit is east. " |
| | 682 | |
| | 683 | east = livingRoom |
| | 684 | down = (whiteBox.isOpen ? whiteBox.down : nil) |
| | 685 | ; |
| | 686 | |
| | 687 | + Fixture, Platform |
| | 688 | 'main theater theatre platform/stage*platforms' 'main platform' |
| | 689 | ; |
| | 690 | |
| | 691 | ++whiteBox: Openable, Booth 'large white cardboard box' 'white box' |
| | 692 | useInitSpecialDesc() |
| | 693 | { |
| | 694 | /* show our initial description only when the actor isn't in me */ |
| | 695 | return inherited() && !gActor.isIn(self); |
| | 696 | } |
| | 697 | initSpecialDesc = "On the main platform is a large white box, |
| | 698 | about waist high and quite roomy. " |
| | 699 | |
| | 700 | /* don't mention our contents if we're using our initial description */ |
| | 701 | contentsListed = (!useSpecialDesc()) |
| | 702 | |
| | 703 | interiorDesc = "The box is roomy, but not enough that you can |
| | 704 | stand up in it when it's closed. " |
| | 705 | |
| | 706 | down = whiteBoxTrapDoor |
| | 707 | |
| | 708 | /* make it paper, so we can hear through the box */ |
| | 709 | material = paper |
| | 710 | |
| | 711 | /* |
| | 712 | * we can't stand up in the box when it's closed, so make the |
| | 713 | * default posture sitting |
| | 714 | */ |
| | 715 | defaultPosture = (isOpen ? standing : sitting) |
| | 716 | |
| | 717 | /* we can't close the box when someone's standing in it */ |
| | 718 | makeOpen(stat) |
| | 719 | { |
| | 720 | /* if we're closing the box, check to make sure we're allowed to */ |
| | 721 | if (!stat) |
| | 722 | { |
| | 723 | /* |
| | 724 | * We're trying to close the box; if anyone's standing in |
| | 725 | * the box, don't allow it. |
| | 726 | */ |
| | 727 | foreach (local cur in allContents()) |
| | 728 | { |
| | 729 | /* if this is a standing actor, disallow closure */ |
| | 730 | if (cur.isActor && cur.posture == standing) |
| | 731 | { |
| | 732 | /* |
| | 733 | * we can't close - issue a failure report and |
| | 734 | * terminate the command |
| | 735 | */ |
| | 736 | reportFailure('{You/he} cannot close the box while |
| | 737 | anyone is standing in it. '); |
| | 738 | exit; |
| | 739 | } |
| | 740 | } |
| | 741 | } |
| | 742 | |
| | 743 | /* no problems - inherit default handling */ |
| | 744 | inherited(stat); |
| | 745 | } |
| | 746 | |
| | 747 | /* |
| | 748 | * if they try to stand within a nested room within us, mention that |
| | 749 | * they can only sit |
| | 750 | */ |
| | 751 | roomBeforeAction() |
| | 752 | { |
| | 753 | /* |
| | 754 | * If we're closed, and the action is 'stand', and they're |
| | 755 | * something nested within me, mention after the fact that |
| | 756 | * there's no room to stand up. We need to mention this, but we |
| | 757 | * do not need to enforce the constraint here because the nested |
| | 758 | * room will set our default sitting posture when moving an |
| | 759 | * actor from an interior location. |
| | 760 | */ |
| | 761 | if (!isOpen |
| | 762 | && gActionIs(Stand) |
| | 763 | && gActor.isIn(self) |
| | 764 | && !gActor.isDirectlyIn(self)) |
| | 765 | reportAfter('There isn\'t room to stand up in here, so |
| | 766 | {you/he} sit{s} in the box. '); |
| | 767 | } |
| | 768 | |
| | 769 | /* enforce the low headroom when the box is closed */ |
| | 770 | makeStandingUp() |
| | 771 | { |
| | 772 | if (isOpen) |
| | 773 | { |
| | 774 | /* we're open, so proceed as normal */ |
| | 775 | inherited(); |
| | 776 | } |
| | 777 | else |
| | 778 | { |
| | 779 | /* the box is closed, so they can't stand up */ |
| | 780 | reportFailure('There\'s not enough room to stand up in |
| | 781 | the box while it\'s closed. '); |
| | 782 | } |
| | 783 | } |
| | 784 | ; |
| | 785 | |
| | 786 | +++ whiteBoxTrapDoor: Fixture, Door -> tunnelTrapDoor |
| | 787 | 'small trap door' 'trap door' |
| | 788 | "It's set into the floor of the box; it looks just large enough |
| | 789 | for you to fit through. " |
| | 790 | |
| | 791 | /* |
| | 792 | * list me *after* the room's contents, since the box is sometimes |
| | 793 | * listed with the room's contents |
| | 794 | */ |
| | 795 | specialDescBeforeContents = nil |
| | 796 | |
| | 797 | initSpecialDesc = "A small trap door is set into the floor of the box. " |
| | 798 | |
| | 799 | /* don't require standing up before entering the trap door */ |
| | 800 | actorTravelPreCond(actor) { return []; } |
| | 801 | |
| | 802 | enteredBefore = nil |
| | 803 | dobjFor(TravelVia) |
| | 804 | { |
| | 805 | action() |
| | 806 | { |
| | 807 | /* mention the ladder below */ |
| | 808 | if (enteredBefore) |
| | 809 | "You find the ladder below and climb down. "; |
| | 810 | else |
| | 811 | { |
| | 812 | "You tentatively lower yourself through the door, and |
| | 813 | manage to find what feels like a ladder below, so you |
| | 814 | carefully climb down. "; |
| | 815 | enteredBefore = true; |
| | 816 | } |
| | 817 | |
| | 818 | /* inherit the default handling */ |
| | 819 | inherited(); |
| | 820 | } |
| | 821 | } |
| | 822 | ; |
| | 823 | |
| | 824 | +++ Switch 'small black plastic beeper pager box/beeper/pager/switch' 'beeper' |
| | 825 | "It's a small black plastic box with a single red light |
| | 826 | (which is currently <<isOn ? 'lit' : 'off'>>) and a |
| | 827 | switch (currently <<isOn ? 'on' : 'off'>>). " |
| | 828 | |
| | 829 | /* |
| | 830 | * if they know about us, they'll recognize our beeping, so they'll |
| | 831 | * want to be able to refer to the beeper itself; thus, give the |
| | 832 | * beeper a sound presence once it's been seen |
| | 833 | */ |
| | 834 | soundPresence = (isOn && seen) |
| | 835 | |
| | 836 | isOn = true |
| | 837 | makeOn(val) |
| | 838 | { |
| | 839 | /* inherit the default handling */ |
| | 840 | inherited(val); |
| | 841 | |
| | 842 | /* add/remove our beeping sound, as appropriate */ |
| | 843 | beeperNoise.moveInto(val ? self : nil); |
| | 844 | } |
| | 845 | ; |
| | 846 | |
| | 847 | ++++ Component 'tiny small red beeper light' 'beeper light' |
| | 848 | "It's a tiny red light, currently <<location.isOn ? 'lit' : 'off'>>. " |
| | 849 | ; |
| | 850 | |
| | 851 | ++++ beeperNoise: Noise |
| | 852 | 'piercing high-pitched high pitched beeping sound/noise/beep' |
| | 853 | 'beeping sound' |
| | 854 | sourceDesc = "The beeper is making a piercing, high-pitched beeping |
| | 855 | noise. " |
| | 856 | descWithSource = "The beeper is beeping, which is what they do. " |
| | 857 | descWithoutSource = "It's a piercing, high-pitched beeping noise. " |
| | 858 | |
| | 859 | hereWithSource = "The beeper is making a piercing beeping noise. " |
| | 860 | hereWithoutSource = "You can hear a high-pitched beeping noise. " |
| | 861 | |
| | 862 | displaySchedule = [2, 4] |
| | 863 | ; |
| | 864 | |
| | 865 | ++ Fixture, Platform 'small smaller red platform*platforms' 'red platform' |
| | 866 | ; |
| | 867 | |
| | 868 | class PlatformChair: Chair |
| | 869 | dobjFor(SitOn) |
| | 870 | { |
| | 871 | verify() |
| | 872 | { |
| | 873 | /* inherit default */ |
| | 874 | inherited(); |
| | 875 | |
| | 876 | /* |
| | 877 | * if they're directly in my location, and my location is a |
| | 878 | * nested room of some kind, boost likelihood that this is |
| | 879 | * where they want to sit |
| | 880 | */ |
| | 881 | if (gActor.isDirectlyIn(location) && location.ofKind(NestedRoom)) |
| | 882 | logicalRank(150, 'adjacent'); |
| | 883 | } |
| | 884 | } |
| | 885 | ; |
| | 886 | |
| | 887 | +++ Fixture, PlatformChair 'red chair' 'red chair' |
| | 888 | ; |
| | 889 | |
| | 890 | ++ Fixture, Platform 'small smaller blue platform*platforms' 'blue platform' |
| | 891 | ; |
| | 892 | |
| | 893 | +++ Fixture, PlatformChair 'blue chair' 'blue chair' |
| | 894 | ; |
| | 895 | |
| | 896 | ++ dais: Fixture, Platform 'raised dais' 'dais' |
| | 897 | ; |
| | 898 | |
| | 899 | +++ stool: PlatformChair 'wooden wood stool' 'stool' |
| | 900 | /* |
| | 901 | * since we're part of the main room description if we're in our |
| | 902 | * original location, don't list me if I'm on the dais |
| | 903 | */ |
| | 904 | isListed = (location == dais ? nil : inherited) |
| | 905 | ; |
| | 906 | |
| | 907 | +++ Fixture, PlatformChair 'leather arm chair/armchair' 'armchair' |
| | 908 | actorInPrep = 'in' |
| | 909 | ; |
| | 910 | |
| | 911 | #if 0 // disabled by default |
| | 912 | /* |
| | 913 | * enable this to test multi-location sense connections - this is a bit |
| | 914 | * too contrived an example, so we disable it by default |
| | 915 | */ |
| | 916 | whiteBoxHole: SenseConnector, Fixture 'small hole' 'small hole' |
| | 917 | "It's a small hole in the box. " |
| | 918 | locationList = [whiteBox, backYard] |
| | 919 | connectorMaterial = glass |
| | 920 | ; |
| | 921 | #endif |
| | 922 | |
| | 923 | |
| | 924 | /* ------------------------------------------------------------------------ */ |
| | 925 | /* |
| | 926 | * Front yard |
| | 927 | */ |
| | 928 | frontYard: OutdoorRoom 'Front Yard' 'the front yard' |
| | 929 | "This is a small yard in front of a modest house. The front |
| | 930 | door to the house leads in to the north. To the south is |
| | 931 | the street. " |
| | 932 | in asExit(north) |
| | 933 | north = frontDoorOutside |
| | 934 | south: NoTravelMessage { "You'd rather not venture beyond the |
| | 935 | immediate vicinity of the house right now. " } |
| | 936 | atmosphereList: RandomEventList { |
| | 937 | eventList = [ |
| | 938 | nil, |
| | 939 | 'A car drives past. ', |
| | 940 | 'Someone honks a horn in the distance. ', |
| | 941 | nil, |
| | 942 | 'A few birds pass overhead. ', |
| | 943 | 'A couple of bicyclists pedal past on the street. ', |
| | 944 | nil, |
| | 945 | nil ] |
| | 946 | } |
| | 947 | ; |
| | 948 | |
| | 949 | + frontDoorOutside: Door ->frontDoor 'front door' 'front door' |
| | 950 | "It leads north, into the house. " |
| | 951 | ; |
| | 952 | |
| | 953 | + Enterable, Decoration 'narrow residential street/road' 'street' |
| | 954 | "It's a narrow residential street. " |
| | 955 | connector = (frontYard.south) |
| | 956 | ; |
| | 957 | |
| | 958 | + Enterable, Decoration ->frontDoorOutside 'house' 'house' |
| | 959 | "It's a modest house. A door leads in to the north. " |
| | 960 | ; |
| | 961 | |
| | 962 | + RedBalloon state = 3; |
| | 963 | + RedBalloon state = 3; |
| | 964 | + RedBalloon state = 3; |
| | 965 | + RedBalloon state = 3; |
| | 966 | + RedBalloon state = 1; |
| | 967 | + RedBalloon state = 1; |
| | 968 | + RedBalloon state = 2; |
| | 969 | |
| | 970 | + BlueBalloon state = 2; |
| | 971 | + BlueBalloon state = 1; |
| | 972 | + BlueBalloon state = 1; |
| | 973 | |
| | 974 | + GreenBalloon state = 3; |
| | 975 | + GreenBalloon state = 3; |
| | 976 | + GreenBalloon state = 3; |
| | 977 | + GreenBalloon state = 3; |
| | 978 | + GreenBalloon state = 3; |
| | 979 | |
| | 980 | + salesman: Person 'salesman/man/ron' 'salesman' |
| | 981 | "He's a thin, youngish man in a garish suit that's rather comically |
| | 982 | oversized on him. " |
| | 983 | isHim = true |
| | 984 | ; |
| | 985 | ++ Wearable 'oversized garish suit' 'suit' |
| | 986 | "It's an oddly bright shade of blue, and is way too large for its |
| | 987 | present wearer. " |
| | 988 | wornBy = salesman |
| | 989 | ; |
| | 990 | ++ Thing 'huge black briefcase' 'briefcase' |
| | 991 | "It's a huge black briefcase. " |
| | 992 | ; |
| | 993 | |
| | 994 | /* the salesman's initial state */ |
| | 995 | ++ ActorState |
| | 996 | isInitState = true |
| | 997 | specialDesc = "A youngish man in a garish suit is standing in |
| | 998 | the yard near the front door. " |
| | 999 | |
| | 1000 | takeTurn() |
| | 1001 | { |
| | 1002 | if (gPlayerChar.location == frontYard) |
| | 1003 | salesman.initiateConversation(salesConv, 'sales-hello'); |
| | 1004 | inherited(); |
| | 1005 | } |
| | 1006 | ; |
| | 1007 | |
| | 1008 | ++ salesConv: InConversationState |
| | 1009 | attentionSpan = 10000 |
| | 1010 | nextState = salesWaiting |
| | 1011 | stateDesc = "He's smiling eagerly at you. " |
| | 1012 | specialDesc = "The salesman is standing a little too close, |
| | 1013 | smiling a bit too much. " |
| | 1014 | ; |
| | 1015 | |
| | 1016 | ++ salesWaiting: ConversationReadyState |
| | 1017 | specialDesc = "The salesman is standing in the yard. " |
| | 1018 | |
| | 1019 | inConvState = salesConv |
| | 1020 | |
| | 1021 | takeTurn() |
| | 1022 | { |
| | 1023 | /* re-arm for conversation only when the PC is gone */ |
| | 1024 | if (gPlayerChar.location != frontYard) |
| | 1025 | regreet = true; |
| | 1026 | |
| | 1027 | /* greet again randomly if we're re-armed and the PC is present */ |
| | 1028 | if (regreet && gPlayerChar.location == frontYard && rand(100) < 33) |
| | 1029 | salesman.initiateConversation(salesConv, 'sales-1'); |
| | 1030 | |
| | 1031 | inherited(); |
| | 1032 | } |
| | 1033 | |
| | 1034 | /* flag: we're ready to re-greet the player */ |
| | 1035 | regreet = nil |
| | 1036 | |
| | 1037 | /* on activation, set the re-greet flag to nil by default */ |
| | 1038 | activateState(actor, oldState) |
| | 1039 | { |
| | 1040 | inherited(actor, oldState); |
| | 1041 | regreet = nil; |
| | 1042 | } |
| | 1043 | ; |
| | 1044 | +++ HelloTopic |
| | 1045 | "You tap the salesman on the shoulder. <q>Oh, hi!</q> he says. |
| | 1046 | <q>I\'d sure like to talk to you about life insurance.</q> |
| | 1047 | <.convnode sales-1> " |
| | 1048 | ; |
| | 1049 | +++ ByeTopic, ShuffledEventList |
| | 1050 | ['<q>I have to go,</q> you say. |
| | 1051 | <.p><q>Thanks for your time!</q> the salesman says. ', |
| | 1052 | |
| | 1053 | '<q>Thanks, but not right now,</q> you say. |
| | 1054 | <.p><q>I\'ll be here when you\'re ready!</q> the salesman says. '] |
| | 1055 | ; |
| | 1056 | +++ ImpByeTopic, ShuffledEventList |
| | 1057 | ['The salesman waves. <q>Okay, I\'ll wait here!</q> he says. ', |
| | 1058 | '<q>Thanks for your time!</q> the salesman says. ', |
| | 1059 | 'The salesman calls after you, <q>I\'ll be here if you need me!</q> '] |
| | 1060 | ; |
| | 1061 | |
| | 1062 | ++ ConvNode 'sales-hello' |
| | 1063 | npcGreetingMsg = "<.p>The man in the bad suit walks up to you and |
| | 1064 | extends his hand; by habit you shake his hand. |
| | 1065 | <q>Hello, friend! My name is Ron, and I represent the |
| | 1066 | Acturial Life Insurance Company.</q> He releases |
| | 1067 | your hand after a vigorous shaking. <q>You know, a lot |
| | 1068 | of people I talk to don't know just how important life |
| | 1069 | insurance is to proper financial planning. Let me |
| | 1070 | ask you this: do you have all the life insurance |
| | 1071 | coverage you and your family need?</q> " |
| | 1072 | |
| | 1073 | npcContinueList: CyclicEventList { |
| | 1074 | [ |
| | 1075 | 'The salesman says eagerly, <q>Really, do you have enough |
| | 1076 | insurance?</q> ', |
| | 1077 | |
| | 1078 | 'Ron says, <q>The sad fact is, most people <i>don\'t know</i> |
| | 1079 | how much insurance they really need. I\'d really like to |
| | 1080 | tell you about our policies...</q><.convnode sales-1> ' |
| | 1081 | ]} |
| | 1082 | ; |
| | 1083 | +++ YesTopic |
| | 1084 | "<q>I'm covered,</q> you say. |
| | 1085 | <.p>Ron smiles and nods. <q>You know, a lot of people <i>think</i> |
| | 1086 | they're covered. But have you really <i>read</i> your insurance |
| | 1087 | policy? Most insurance policies have so many exclusions and |
| | 1088 | limitations that you just can't rely on them. That's why |
| | 1089 | Acturial Life created a new kind of insurance policy. Let me |
| | 1090 | tell you about it...</q><.convnode sales-1> " |
| | 1091 | ; |
| | 1092 | +++ NoTopic |
| | 1093 | "<q>Why, no,</q> you say. |
| | 1094 | <.p>Ron smiles eagerly. <q>Well, then it's a good thing I was in |
| | 1095 | your neighborhood today! Let me tell you about our policies...</q> |
| | 1096 | <.convnode sales-1> " |
| | 1097 | ; |
| | 1098 | |
| | 1099 | ++ ConvNode 'sales-1' |
| | 1100 | npcGreetingMsg = "<.p>Ron walks up to you. <q>Hello again, friend! |
| | 1101 | It would be my pleasure to help you with your |
| | 1102 | insurance needs.</q> " |
| | 1103 | |
| | 1104 | npcContinueList: ShuffledEventList { |
| | 1105 | ['The salesman looks serious for a moment. <q>Most people don\'t know |
| | 1106 | this, but death is our number one killer,</q> he says earnestly. |
| | 1107 | He goes back to smiling. <q>Fortunately, death is covered under |
| | 1108 | our policy\'s loss-of-life section.</q> ', |
| | 1109 | |
| | 1110 | 'Ron says, <q>You know, life insurance is a lot more interesting |
| | 1111 | than most people think.</q> ', |
| | 1112 | |
| | 1113 | 'The salesman says, <q>I\'m really glad I was in the neighborhood |
| | 1114 | today. Everyone needs more insurance than they think.</q> ' |
| | 1115 | ]} |
| | 1116 | ; |
| | 1117 | |
| | 1118 | ++ AskTellTopic, StopEventList @insPolicy |
| | 1119 | [ |
| | 1120 | '<q>Okay, tell me about your policy,</q> you say. |
| | 1121 | <.p><q>Our policy is the best in the business,</q> Ron says. |
| | 1122 | <q>For starters, it\'s guaranteed go pay, which most policies |
| | 1123 | aren\'t. There\'s so much more I can tell you.</q> ', |
| | 1124 | |
| | 1125 | '<q>Tell me more about your policy,</q> you say. |
| | 1126 | <.p><q>I could go on all day,</q> the salesman says. ' |
| | 1127 | ] |
| | 1128 | ; |
| | 1129 | |
| | 1130 | ++ AskTellTopic, StopEventList @insCompany |
| | 1131 | ['<q>I\'ve never heard of your company,</q> you say. |
| | 1132 | <.p><q>Most people haven\'t,</q> Ron says. <q>We\'re the best-kept |
| | 1133 | secret in the business.</q>', |
| | 1134 | |
| | 1135 | '<q>Why would you want to keep your company a secret?</q> |
| | 1136 | <.p><q>One word: savings. We save on marketing expenses, and |
| | 1137 | pass the low cost on to you.</q> ', |
| | 1138 | |
| | 1139 | '<q>What else can you tell me about your company?</q> |
| | 1140 | <.p><q>I\'d rather tell you about the policy.</q> '] |
| | 1141 | ; |
| | 1142 | |
| | 1143 | /* some topics for the salesman to talk about */ |
| | 1144 | insPolicy: Topic 'life insurance policy/policies'; |
| | 1145 | insCompany: Topic 'acturial life insurance company'; |
| | 1146 | |
| | 1147 | /* ------------------------------------------------------------------------ */ |
| | 1148 | /* |
| | 1149 | * Den |
| | 1150 | */ |
| | 1151 | den: HouseRoom |
| | 1152 | roomName = 'Den' |
| | 1153 | destName = 'the den' |
| | 1154 | desc = "This small room is dominated by a desk, a massive steel |
| | 1155 | edifice painted a drab gray, something that would be |
| | 1156 | more at home in a government office during the cold war. |
| | 1157 | Behind the desk, built in to the north wall, is a |
| | 1158 | bookcase. A passage leads west. " |
| | 1159 | west = livingRoom |
| | 1160 | north = bookcasePassage |
| | 1161 | roomParts = static (inherited() - defaultNorthWall) |
| | 1162 | ; |
| | 1163 | |
| | 1164 | + Decoration 'north wall*walls' 'north wall' |
| | 1165 | "A floor-to ceiling bookcase<< |
| | 1166 | bookcasePassage.isOpen ? " (which has swung aside to expose |
| | 1167 | a passage to the north)" : "">> |
| | 1168 | is built into the wall. " |
| | 1169 | ; |
| | 1170 | |
| | 1171 | + bookcase: Fixture 'book case/bookcase' 'bookcase' |
| | 1172 | desc |
| | 1173 | { |
| | 1174 | "It's a floor-to-ceiling bookcase built in to the north |
| | 1175 | wall, behind the desk, filled with hundreds of dusty tomes. "; |
| | 1176 | if (bookcasePassage.isOpen) |
| | 1177 | "The bookcase is evidently a secret door, because it |
| | 1178 | has moved sideways to expose a passage to the north. "; |
| | 1179 | } |
| | 1180 | ; |
| | 1181 | |
| | 1182 | + bookcasePassage: BasicOpenable, HiddenDoor |
| | 1183 | 'passage' 'passage' "It leads north. " |
| | 1184 | initSpecialDesc |
| | 1185 | { |
| | 1186 | if (isOpen) |
| | 1187 | "The bookcase has moved aside to reveal a passage |
| | 1188 | to the north. "; |
| | 1189 | } |
| | 1190 | ; |
| | 1191 | |
| | 1192 | + Decoration 'dusty book/books/tome/tomes' 'books' |
| | 1193 | "There must be hundreds of books, all with incomprehensible |
| | 1194 | titles and authors you've never heard of. " |
| | 1195 | isPlural = true |
| | 1196 | dobjFor(Read) |
| | 1197 | { |
| | 1198 | verify() { } |
| | 1199 | action() |
| | 1200 | { |
| | 1201 | "Much as you enjoy reading, you can't find any book |
| | 1202 | whose title even means anything to you, let alone |
| | 1203 | holds any interest. "; |
| | 1204 | } |
| | 1205 | } |
| | 1206 | ; |
| | 1207 | |
| | 1208 | + desk: Immovable, Surface |
| | 1209 | 'massive big huge edifice drab gray grey steel metal desk' 'desk' |
| | 1210 | desc |
| | 1211 | { |
| | 1212 | "It's a big desk with a single drawer (currently |
| | 1213 | <<deskDrawer.openDesc>>). A small button is set inconspicuously |
| | 1214 | into the edge of the desktop"; |
| | 1215 | if (hiddenPanel.isOpen) |
| | 1216 | ", and in the side is a small compartment containing a lever"; |
| | 1217 | ". "; |
| | 1218 | } |
| | 1219 | |
| | 1220 | dobjFor(Open) remapTo(Open, deskDrawer) |
| | 1221 | dobjFor(Close) remapTo(Close, deskDrawer) |
| | 1222 | dobjFor(LookIn) remapTo(LookIn, deskDrawer) |
| | 1223 | iobjFor(PutIn) remapTo(PutIn, DirectObject, deskDrawer) |
| | 1224 | ; |
| | 1225 | |
| | 1226 | ++ Thing 'bob\'s brand premium fish cleaner jar/cleaner' |
| | 1227 | 'jar of Bob\'s Brand Premium Fish Cleaner' |
| | 1228 | "It presumably once held fish cleaner (whatever that is), |
| | 1229 | but it's just an empty jar now. " |
| | 1230 | ; |
| | 1231 | |
| | 1232 | ++penCup: Container 'pen cup' 'pen cup' |
| | 1233 | "It's a uneven clay cup, currently employed as a pen holder. " |
| | 1234 | |
| | 1235 | /* just for fun, show my contents out-of-line in listings I'm in */ |
| | 1236 | contentsListedSeparately = true |
| | 1237 | ; |
| | 1238 | |
| | 1239 | class pen: Thing 'pen*pens' 'pen' |
| | 1240 | "A simple Bic. " |
| | 1241 | isEquivalent = true |
| | 1242 | ; |
| | 1243 | |
| | 1244 | +++pen; |
| | 1245 | +++pen; |
| | 1246 | +++pen; |
| | 1247 | +++pen; |
| | 1248 | |
| | 1249 | ++ deskDrawer: Component, OpenableContainer 'desk drawer' 'desk drawer' |
| | 1250 | ; |
| | 1251 | |
| | 1252 | ++ Immovable 'old-fashioned rotary phone/telephone/dial/receiver/handset' |
| | 1253 | 'phone' |
| | 1254 | "It's an old-fashioned rotary phone, very sturdy-looking but |
| | 1255 | rather scuffed from long use. " |
| | 1256 | |
| | 1257 | dobjFor(Take) |
| | 1258 | { |
| | 1259 | verify() { } |
| | 1260 | check() { } |
| | 1261 | action() |
| | 1262 | { |
| | 1263 | "You pick up the handset, but there's nothing but static |
| | 1264 | on the line. At least it stops ringing. You put the |
| | 1265 | handset back, and the phone starts ringing again. "; |
| | 1266 | } |
| | 1267 | } |
| | 1268 | |
| | 1269 | dobjFor(Answer) asDobjFor(Take) |
| | 1270 | ; |
| | 1271 | |
| | 1272 | +++ Noise 'ring/ringing' 'ringing/bell' |
| | 1273 | sourceDesc = "The phone is ringing loudly. " |
| | 1274 | descWithSource = "It's an actual mechanical bell, not the ubiquitous |
| | 1275 | electronic warble of modern phones. " |
| | 1276 | hereWithSource() |
| | 1277 | { |
| | 1278 | switch (displayCount) |
| | 1279 | { |
| | 1280 | case 1: |
| | 1281 | "The phone is ringing. "; |
| | 1282 | break; |
| | 1283 | |
| | 1284 | default: |
| | 1285 | "The phone is still ringing. "; |
| | 1286 | break; |
| | 1287 | } |
| | 1288 | } |
| | 1289 | |
| | 1290 | displaySchedule = [2, 4, 8] |
| | 1291 | ; |
| | 1292 | |
| | 1293 | |
| | 1294 | ++ Button, Component 'small button' 'small button' |
| | 1295 | dobjFor(Push) |
| | 1296 | { |
| | 1297 | action() |
| | 1298 | { |
| | 1299 | /* open/close the hidden panel */ |
| | 1300 | hiddenPanel.makeOpen(!hiddenPanel.isOpen); |
| | 1301 | |
| | 1302 | /* show the appropriate message */ |
| | 1303 | if (hiddenPanel.isOpen) |
| | 1304 | "A previously hidden panel in the side of the desk opens, |
| | 1305 | revealing a small compartment containing a lever. "; |
| | 1306 | else |
| | 1307 | "The panel in the desk closes, hiding the lever. Now |
| | 1308 | that the panel is closed, it's completely seamless - |
| | 1309 | you can't see any sign of it. "; |
| | 1310 | } |
| | 1311 | } |
| | 1312 | ; |
| | 1313 | |
| | 1314 | ++ hiddenPanel: BasicOpenable, Component, Container |
| | 1315 | 'hidden panel/compartment' 'compartment' |
| | 1316 | "It's a small compartment, just large enough to contain the |
| | 1317 | lever it enclosed. " |
| | 1318 | |
| | 1319 | bulkCapacity = 0 |
| | 1320 | initiallyOpen = nil |
| | 1321 | |
| | 1322 | /* the panel is only visible when it's open */ |
| | 1323 | sightPresence = (self.isOpen) |
| | 1324 | ; |
| | 1325 | |
| | 1326 | +++ SpringLever, Component 'lever' 'lever' |
| | 1327 | "It's mounted in a small compartment in the desk. " |
| | 1328 | dobjFor(Pull) |
| | 1329 | { |
| | 1330 | action() |
| | 1331 | { |
| | 1332 | /* open/close the secret door */ |
| | 1333 | bookcasePassage.makeOpen(!bookcasePassage.isOpen); |
| | 1334 | |
| | 1335 | /* show the appropriate message */ |
| | 1336 | if (bookcasePassage.isOpen) |
| | 1337 | "The lever is surprisingly heavy, but you manage to |
| | 1338 | pull it all the way out. At the end of its travel, |
| | 1339 | something under the floor clicks, and the bookcase |
| | 1340 | behind the desk slides sideways far enough to open |
| | 1341 | a passage to the north. As soon as you release the |
| | 1342 | lever, it springs back to its original position. "; |
| | 1343 | else |
| | 1344 | "You pull the lever out all the way. Something |
| | 1345 | under the floor clicks, and the bookcase slides |
| | 1346 | sideways until it covers the passage, leaving |
| | 1347 | no trace of a doorway. "; |
| | 1348 | } |
| | 1349 | } |
| | 1350 | ; |
| | 1351 | |
| | 1352 | ++ typewriter: Immovable |
| | 1353 | 'big old old-fashioned black manual typewriter' 'typewriter' |
| | 1354 | initSpecialDesc = "A big, old-fashioned manual typewriter is sitting |
| | 1355 | on the desk. " |
| | 1356 | desc = "It's big, black, manual typewriter, like something out |
| | 1357 | of an old black-and-white movie about newspapermen or private |
| | 1358 | detectives. There's a piece of paper in it. " |
| | 1359 | dobjFor(TypeOn) { verify() { } } |
| | 1360 | dobjFor(TypeLiteralOn) |
| | 1361 | { |
| | 1362 | verify() { } |
| | 1363 | action() |
| | 1364 | { |
| | 1365 | /* add the literal text to the paper */ |
| | 1366 | typewriterPaper.addText(gAction.getLiteral()); |
| | 1367 | mainReport('With some effort, {you/he} work{s} the keys of the |
| | 1368 | ancient machine, noisily impressing <q>' |
| | 1369 | + gAction.text_ + '</q> on the paper. '); |
| | 1370 | } |
| | 1371 | } |
| | 1372 | ; |
| | 1373 | |
| | 1374 | +++ typewriterPaper: Thing 'piece/paper' 'piece of paper' |
| | 1375 | isListedInContents = nil |
| | 1376 | desc |
| | 1377 | { |
| | 1378 | if (textList.length() == 0) |
| | 1379 | "The paper is blank. "; |
| | 1380 | else |
| | 1381 | { |
| | 1382 | "The typewritten letters on the page have that uneven |
| | 1383 | darkness and wandering alignment characteristic of |
| | 1384 | the work a well-worn manual typewriter:<tt>\b"; |
| | 1385 | |
| | 1386 | foreach (local cur in textList) |
| | 1387 | "\t<<cur>>\n"; |
| | 1388 | |
| | 1389 | "</tt>"; |
| | 1390 | } |
| | 1391 | } |
| | 1392 | addText(txt) { textList.append(txt); } |
| | 1393 | textList = static new Vector(10) |
| | 1394 | moveInto(obj) |
| | 1395 | { |
| | 1396 | reportFailure('On second thought, {you/he}\'d rather leave the |
| | 1397 | paper in the typewriter in case someone needs to do |
| | 1398 | some typing. '); |
| | 1399 | exit; |
| | 1400 | } |
| | 1401 | dobjFor(TypeOn) { verify() { } } |
| | 1402 | dobjFor(TypeLiteralOn) |
| | 1403 | { |
| | 1404 | verify() |
| | 1405 | { |
| | 1406 | /* |
| | 1407 | * allow it but reduce the likelihood - we want the |
| | 1408 | * typewriter to win in a default case |
| | 1409 | */ |
| | 1410 | logicalRank(50, 'nondefault'); |
| | 1411 | } |
| | 1412 | action() |
| | 1413 | { |
| | 1414 | /* treat 'type on paper' as 'type on typewriter' */ |
| | 1415 | replaceAction(TypeLiteralOn, typewriter, gAction.text_); |
| | 1416 | } |
| | 1417 | } |
| | 1418 | ; |
| | 1419 | |
| | 1420 | ++ dagger: Thing 'dagger' 'dagger' |
| | 1421 | initSpecialDesc = "Someone has jabbed a dagger into the top of |
| | 1422 | the desk, leaving the dagger sticking up almost vertically. " |
| | 1423 | initExamineDesc = "Its point is stuck into the top of the desk. " |
| | 1424 | ; |
| | 1425 | |
| | 1426 | ++ watch: Wearable 'watch' 'watch' "It has a minute hand and an hour hand. "; |
| | 1427 | +++ Component 'minute big hand' 'minute hand'; |
| | 1428 | +++ Component 'hour little hand' 'hour hand'; |
| | 1429 | |
| | 1430 | ++ Container 'glass bottle' 'bottle' |
| | 1431 | bulkCapacity = 2 |
| | 1432 | canFitObjThruOpening(obj) |
| | 1433 | { |
| | 1434 | /* we can only fit small objects through the opening */ |
| | 1435 | return obj.bulk < 2; |
| | 1436 | } |
| | 1437 | ; |
| | 1438 | |
| | 1439 | +++ Thing 'minature model sailing ship/boat' 'model ship' |
| | 1440 | "It's a miniature model of a sailing ship. " |
| | 1441 | bulk = 2 |
| | 1442 | ; |
| | 1443 | |
| | 1444 | #if 0 // for testing distant viewing of sightSize=large objects |
| | 1445 | viewScreen: SenseConnector, Fixture 'view screen/viewscreen' 'viewscreen' |
| | 1446 | "It's a viewscreen, displaying a close-up shot of a floating sphere. " |
| | 1447 | locationList = [den, backYard] |
| | 1448 | connectorMaterial: Material |
| | 1449 | { |
| | 1450 | seeThru = distant |
| | 1451 | hearThru = distant |
| | 1452 | smellThru = opaque |
| | 1453 | touchThru = opaque |
| | 1454 | } |
| | 1455 | ; |
| | 1456 | #endif |
| | 1457 | |
| | 1458 | /* ------------------------------------------------------------------------ */ |
| | 1459 | /* |
| | 1460 | * Top of stairs |
| | 1461 | */ |
| | 1462 | class SpiralStairway: Fixture |
| | 1463 | 'perforated central black metal/stair/stairs/stairway/pole' 'stairs' |
| | 1464 | "The stairs are narrow triangles of perforated black metal arranged |
| | 1465 | in a helix around a central pole. " |
| | 1466 | isPlural = true |
| | 1467 | ; |
| | 1468 | |
| | 1469 | topOfStairs: HouseRoom |
| | 1470 | roomName = 'Top of Stairs' |
| | 1471 | destName = 'the top of the stairs' |
| | 1472 | desc = "This is the top of a narrow spiral staircase that |
| | 1473 | leads down a dark shaft walled in rough brown bricks. |
| | 1474 | A passage leads south. " |
| | 1475 | south = stairPassage |
| | 1476 | down = spiralStairsTop |
| | 1477 | ; |
| | 1478 | |
| | 1479 | + Decoration 'dark rough brown shaft/brick/bricks' 'dark shaft' |
| | 1480 | "The shaft is just large enough for the staircase. " |
| | 1481 | ; |
| | 1482 | |
| | 1483 | + stairPassage: ThroughPassage ->bookcasePassage 'south passage' 'passage' |
| | 1484 | ; |
| | 1485 | |
| | 1486 | + spiralStairsTop: StairwayDown, SpiralStairway |
| | 1487 | moveActor(actor) |
| | 1488 | { |
| | 1489 | if (actor.isPlayerChar) |
| | 1490 | "{You/he} carefully descend{s} the steep, narrow stairs. "; |
| | 1491 | inherited(actor); |
| | 1492 | } |
| | 1493 | |
| | 1494 | travelBarrier = static [ |
| | 1495 | new tvBarrier('The television is far too heavy to take down |
| | 1496 | the stairs. '), |
| | 1497 | new tricycleBarrier('It would make a nice stunt, but it\'s far |
| | 1498 | too dangerous to ride the tricycle down |
| | 1499 | the stairs. ') |
| | 1500 | ] |
| | 1501 | ; |
| | 1502 | |
| | 1503 | /* ------------------------------------------------------------------------ */ |
| | 1504 | /* |
| | 1505 | * Bottom of stairs |
| | 1506 | */ |
| | 1507 | bottomOfStairs: DarkHouseRoom |
| | 1508 | roomName = 'Bottom of Stairs' |
| | 1509 | destName = 'the bottom of the stairs' |
| | 1510 | desc = "This is the bottom of a narrow spiral staircase. |
| | 1511 | Apart from the stairs, the only exit is the door |
| | 1512 | to the south. " |
| | 1513 | up = spiralStairsBottom |
| | 1514 | south = stairDoor |
| | 1515 | |
| | 1516 | /* no illumination here */ |
| | 1517 | brightness = 0 |
| | 1518 | |
| | 1519 | /* |
| | 1520 | * even in the dark, keep the stairs in scope, because we're |
| | 1521 | * standing on them |
| | 1522 | */ |
| | 1523 | getExtraScopeItems(actor) |
| | 1524 | { |
| | 1525 | return inherited(actor) + spiralStairsBottom; |
| | 1526 | } |
| | 1527 | ; |
| | 1528 | |
| | 1529 | class IronDoor: LockableWithKey, Door |
| | 1530 | 'iron door/rivet/rivets/plate/plates' 'iron door' |
| | 1531 | "It's a formidable-looking mass of iron plate and rivets. " |
| | 1532 | |
| | 1533 | keyList = [ironKey] |
| | 1534 | ; |
| | 1535 | |
| | 1536 | + stairDoor: IronDoor; |
| | 1537 | |
| | 1538 | + spiralStairsBottom: StairwayUp, SpiralStairway -> spiralStairsTop |
| | 1539 | travelBarrier: tricycleBarrier |
| | 1540 | { |
| | 1541 | msg_ = 'Riding the tricycle up the stairs is |
| | 1542 | simply out of the question. ' |
| | 1543 | } |
| | 1544 | ; |
| | 1545 | |
| | 1546 | /* |
| | 1547 | * make the washing machine a complex containe so that we can have |
| | 1548 | * components as well as contents within |
| | 1549 | */ |
| | 1550 | + ComplexContainer 'washing machine/washer' 'washing machine' |
| | 1551 | "It's a beat-up old washing machine. A big dial is the only |
| | 1552 | obvious control. " |
| | 1553 | |
| | 1554 | subContainer: ComplexComponent, OpenableContainer { } |
| | 1555 | ; |
| | 1556 | |
| | 1557 | ++ Component, LabeledDial |
| | 1558 | 'big washer washing machine control dial' 'control dial' |
| | 1559 | "The dial has stops labeled Wash, Rinse, Spin, and Stop. It's currently |
| | 1560 | set to <<curSetting>>. " |
| | 1561 | |
| | 1562 | curSetting = 'Wash' |
| | 1563 | validSettings = ['Wash', 'Rinse', 'Spin', 'Stop'] |
| | 1564 | ; |
| | 1565 | |
| | 1566 | /* ------------------------------------------------------------------------ */ |
| | 1567 | /* |
| | 1568 | * Dining Room |
| | 1569 | */ |
| | 1570 | diningRoom: HouseRoom |
| | 1571 | roomName = 'Dining Room' |
| | 1572 | destName = 'the dining room' |
| | 1573 | desc = "This medium-sized room<<myNote.noteRef>> has a dining table |
| | 1574 | and a couple of chairs, one upholstered in dark fabric and |
| | 1575 | other in light fabric. |
| | 1576 | A door (<<diningDoor.openDesc>>) leads north. " |
| | 1577 | south = livingRoom |
| | 1578 | north = diningDoor |
| | 1579 | out asExit(north) |
| | 1580 | myNote: Footnote { "This is just a sample note for the dining room. " } |
| | 1581 | ; |
| | 1582 | |
| | 1583 | + alcove: OutOfReach, Fixture, Container 'arched small alcove' 'small alcove' |
| | 1584 | "It's a small, arched alcove, set high up on the wall, near the |
| | 1585 | ceiling. " |
| | 1586 | |
| | 1587 | useSpecialDesc = (contents.length() != 1 || contents[1] != trophy) |
| | 1588 | specialDesc = "A small alcove is set near the top of one wall, |
| | 1589 | up near the ceiling. " |
| | 1590 | |
| | 1591 | canObjReachContents(obj) |
| | 1592 | { |
| | 1593 | /* if the actor is standing on a chair, allow it */ |
| | 1594 | if (obj.posture == standing && obj.location == diningTable) |
| | 1595 | return true; |
| | 1596 | |
| | 1597 | /* inherit the default */ |
| | 1598 | return inherited(obj); |
| | 1599 | } |
| | 1600 | |
| | 1601 | cannotReachFromOutsideMsg(obj) |
| | 1602 | { |
| | 1603 | return '{You/he} can\'t reach ' + obj.theNameObj |
| | 1604 | + ' from here; the alcove is too high up. {You/he} might |
| | 1605 | be able to reach the alcove if {you/he} were standing |
| | 1606 | on something. '; |
| | 1607 | } |
| | 1608 | ; |
| | 1609 | |
| | 1610 | ++ trophy: Thing 'silver big trophy/award/cup' 'trophy' |
| | 1611 | "It's shaped like a big cup, and looks to be made of silver. " |
| | 1612 | |
| | 1613 | useInitSpecialDesc = (location == alcove) |
| | 1614 | initSpecialDesc = "A trophy is visible in a small alcove set |
| | 1615 | into the top of one wall. " |
| | 1616 | ; |
| | 1617 | |
| | 1618 | + diningDoor: Door 'door' 'door' |
| | 1619 | "It leads north. " |
| | 1620 | travelBarrier = [tricycleBarrier, tvBarrier] |
| | 1621 | ; |
| | 1622 | |
| | 1623 | class DiningChair: Chair, Immovable 'chair*chairs' 'chair' |
| | 1624 | "Its style matches that of the table. " |
| | 1625 | ; |
| | 1626 | |
| | 1627 | + DiningChair 'light lighter fabric' 'lighter chair'; |
| | 1628 | + DiningChair 'dark darker fabric' 'darker chair'; |
| | 1629 | |
| | 1630 | + diningTable: Heavy, Platform 'dining large oval table' 'table' |
| | 1631 | "It's a large<<myNote.noteRef>> oval table. " |
| | 1632 | myNote: Footnote { "Okay, it's not all that large. Medium-sized |
| | 1633 | sounds so weak, though. " } |
| | 1634 | ; |
| | 1635 | |
| | 1636 | class MyCandle: FireSource, Candle |
| | 1637 | fuelLevel = 32 |
| | 1638 | desc() |
| | 1639 | { |
| | 1640 | /* show a description based on how far we've burned down */ |
| | 1641 | if (fuelLevel > 30) |
| | 1642 | "The candle has barely been used. "; |
| | 1643 | else if (fuelLevel > 24) |
| | 1644 | "The candle looks only slightly burned down. "; |
| | 1645 | else if (fuelLevel > 16) |
| | 1646 | "The candle looks about half burned down. "; |
| | 1647 | else if (fuelLevel > 8) |
| | 1648 | "The candle is considerably burned down. "; |
| | 1649 | else if (fuelLevel > 0) |
| | 1650 | "The candle is mostly burned down. "; |
| | 1651 | else |
| | 1652 | "The candle is completely burned down. "; |
| | 1653 | |
| | 1654 | /* if we're burning, mention it */ |
| | 1655 | if (isLit) |
| | 1656 | "It's lit. "; |
| | 1657 | } |
| | 1658 | ; |
| | 1659 | |
| | 1660 | ++ MyCandle 'red candle' 'red candle'; |
| | 1661 | ++ MyCandle 'white candle' 'white candle'; |
| | 1662 | |
| | 1663 | ++ vase: Container 'vase' 'vase' |
| | 1664 | "It's a simple glass cylinder, open at the top. " |
| | 1665 | bulkCapacity = 1 |
| | 1666 | initSpecialDesc = "A bouquet of flowers is arranged in a vase |
| | 1667 | in the center of the table. " |
| | 1668 | useInitSpecialDesc() |
| | 1669 | { |
| | 1670 | /* don't use the initial description if the flowers have been moved */ |
| | 1671 | return flowers.isIn(self) ? inherited() : nil; |
| | 1672 | } |
| | 1673 | verifyInsert(obj, newCont) |
| | 1674 | { |
| | 1675 | if (obj != flowers) |
| | 1676 | illogical('The vase is only designed to hold flowers. '); |
| | 1677 | } |
| | 1678 | |
| | 1679 | /* |
| | 1680 | * in room and inventory lists, show the vase with its flowers, if |
| | 1681 | * it has the flowers |
| | 1682 | */ |
| | 1683 | listName = (flowers.isIn(self) |
| | 1684 | ? 'a bouquet of flowers in a vase' |
| | 1685 | : inherited()) |
| | 1686 | ; |
| | 1687 | |
| | 1688 | +++ flowers: Thing 'flower/flowers/arrangement/bouquet' 'bouquet of flowers' |
| | 1689 | "It's a nice arrangement of flowers. " |
| | 1690 | |
| | 1691 | /* |
| | 1692 | * don't separately list the flowers if they're in the vase, because |
| | 1693 | * the vase shows the flowers as part of its list name; but don't |
| | 1694 | * hide the flowers from an explicit "look in vase" |
| | 1695 | */ |
| | 1696 | isListed = (!isIn(vase)) |
| | 1697 | isListedIn = true |
| | 1698 | ; |
| | 1699 | |
| | 1700 | ++++ Odor 'floral flowery pleasant fragrance/odor/smell' 'floral fragrance' |
| | 1701 | /* the description when they smell the flowers directly */ |
| | 1702 | sourceDesc = "The flowers have a strong, well, flowery fragrance. " |
| | 1703 | |
| | 1704 | /* the description when they smell us and the flowers are visible */ |
| | 1705 | descWithSource = "The pleasant fragrance is coming from the flowers. " |
| | 1706 | |
| | 1707 | /* the description when they smell us and the flowers aren't seen */ |
| | 1708 | descWithoutSource = "It's a pleasant floral fragrance. " |
| | 1709 | |
| | 1710 | /* room description with and without being able to see the flowers */ |
| | 1711 | hereWithSource = "The flowers have a pleasant fragrance. " |
| | 1712 | hereWithoutSource = "A flowery fragrance is in the air. " |
| | 1713 | ; |
| | 1714 | |
| | 1715 | ++ StretchyContainer 'stretchy bag' 'stretchy bag' |
| | 1716 | "It's made of some very elastic material. It seems |
| | 1717 | to take on the size and shape of whatever's inside. " |
| | 1718 | |
| | 1719 | /* |
| | 1720 | * we have no bulk contribution of our own when we have any contents, |
| | 1721 | * but we do have a minimum empty bulk of 1 |
| | 1722 | */ |
| | 1723 | bulk = 0 |
| | 1724 | minBulk = 1 |
| | 1725 | ; |
| | 1726 | |
| | 1727 | ++ Container 'cookie tin' 'cookie tin' |
| | 1728 | "It's a round metal cylinder about three inches tall, with |
| | 1729 | no lid, decorated with pictures of cookies. " |
| | 1730 | |
| | 1731 | bulkCapacity = 3 |
| | 1732 | ; |
| | 1733 | |
| | 1734 | ++ Thing 'yellow pile inflatable rubber raft' 'inflatable rubber raft' |
| | 1735 | desc |
| | 1736 | { |
| | 1737 | if (inflated) |
| | 1738 | "It's fully inflated. "; |
| | 1739 | else |
| | 1740 | "In its current deflated state, it's just a pile of yellow |
| | 1741 | rubber, bunched up messily. It's large, but you could |
| | 1742 | probably inflate it if you had to. "; |
| | 1743 | } |
| | 1744 | |
| | 1745 | inflated = nil |
| | 1746 | bulk { return inflated ? 10 : 2; } |
| | 1747 | |
| | 1748 | makeInflated(flag) |
| | 1749 | { |
| | 1750 | /* check the effect of the inflation on my bulk */ |
| | 1751 | whatIf({: checkBulkChange()}, &inflated, flag); |
| | 1752 | |
| | 1753 | /* set the new status */ |
| | 1754 | inflated = flag; |
| | 1755 | } |
| | 1756 | |
| | 1757 | dobjFor(Inflate) |
| | 1758 | { |
| | 1759 | verify() |
| | 1760 | { |
| | 1761 | if (inflated) |
| | 1762 | illogicalAlready('It\'s already fully inflated. '); |
| | 1763 | } |
| | 1764 | action() |
| | 1765 | { |
| | 1766 | makeInflated(true); |
| | 1767 | mainReport('It takes all your strength, but you manage to |
| | 1768 | blow up the raft. You might feel light-headed for |
| | 1769 | a bit. '); |
| | 1770 | } |
| | 1771 | } |
| | 1772 | |
| | 1773 | dobjFor(Deflate) |
| | 1774 | { |
| | 1775 | verify() |
| | 1776 | { |
| | 1777 | if (!inflated) |
| | 1778 | illogicalAlready('It\'s already fully deflated. '); |
| | 1779 | } |
| | 1780 | action() |
| | 1781 | { |
| | 1782 | makeInflated(nil); |
| | 1783 | mainReport('You let the air out of the raft, which flattens |
| | 1784 | into a bunched-up pile of yellow rubber. '); |
| | 1785 | } |
| | 1786 | } |
| | 1787 | ; |
| | 1788 | |
| | 1789 | /* ------------------------------------------------------------------------ */ |
| | 1790 | /* |
| | 1791 | * Back Yard |
| | 1792 | */ |
| | 1793 | backYard: OutdoorRoom |
| | 1794 | roomName = 'Back Yard' |
| | 1795 | destName = 'the back yard' |
| | 1796 | vocabWords = 'back yard' |
| | 1797 | desc = "This small yard has a well-kept lawn. The edges of the yard |
| | 1798 | are defined by thick, tall hedges. An old garden shed occupies |
| | 1799 | the northwest corner of the yard. A door into the house |
| | 1800 | is to the south. " |
| | 1801 | south = yardDoor |
| | 1802 | in asExit(south) |
| | 1803 | northwest = shedOuterDoor |
| | 1804 | ; |
| | 1805 | + yardDoor: Door ->diningDoor 'house back door' 'back door of the house' |
| | 1806 | "It leads south, into the house. " |
| | 1807 | ; |
| | 1808 | |
| | 1809 | + Decoration 'shrub/shrubs/hedge/hedges/plant/plants' 'hedges' |
| | 1810 | "The hedges are tall and thick, and completely close in the |
| | 1811 | boundaries of the yard. " |
| | 1812 | isPlural = true |
| | 1813 | dobjFor(LookOver) |
| | 1814 | { |
| | 1815 | verify() { } |
| | 1816 | action() { "The hedges are too tall. "; } |
| | 1817 | } |
| | 1818 | ; |
| | 1819 | |
| | 1820 | /* |
| | 1821 | * This object tests and demonstrates using '*' and matchName to parse |
| | 1822 | * non-dictionary words as object names. |
| | 1823 | * |
| | 1824 | * This isn't the kind of place you'd really use '*' in practice; for |
| | 1825 | * this case, you'd be much better off defining all of the possible |
| | 1826 | * color names as adjectives for the object and then using matchName() |
| | 1827 | * to ignore matches to anything but the current one. This works better |
| | 1828 | * because the color names would then exist as dictionary words, and |
| | 1829 | * hence the parser would not complain about them being unknown when |
| | 1830 | * this object is not in scope. |
| | 1831 | */ |
| | 1832 | + Sphere: Immovable |
| | 1833 | noun = '*' 'sphere' |
| | 1834 | name = (colors[curColor] + ' sphere') |
| | 1835 | desc = "The <<colors[curColor]>> sphere floats unnervingly in mid-air, |
| | 1836 | with no apparent mechanical means of suspension. It does not |
| | 1837 | hover like a balloon, but seems absolutely still, as though |
| | 1838 | firmly attached to an invisible pole. " |
| | 1839 | cannotTakeMsg = 'The sphere is strangely fixed in place; it resists |
| | 1840 | your attempts to move it. It does seem to be able to |
| | 1841 | rotate freely on its vertical axis, though, so you |
| | 1842 | could probably turn it if you wanted to. ' |
| | 1843 | cannotMoveMsg = (cannotTakeMsg) |
| | 1844 | cannotPutInMsg = (cannotTakeMsg) |
| | 1845 | |
| | 1846 | initSpecialDesc = "In the center of the yard, floating in |
| | 1847 | mid-air, is a <<colors[curColor]>> sphere. " |
| | 1848 | |
| | 1849 | matchNameCommon(origTokens, adjustedTokens) |
| | 1850 | { |
| | 1851 | /* check each token */ |
| | 1852 | foreach (local cur in origTokens) |
| | 1853 | { |
| | 1854 | /* |
| | 1855 | * if it's 'sphere' or our current color name, match it; |
| | 1856 | * otherwise, we don't have a match |
| | 1857 | */ |
| | 1858 | if (getTokVal(cur) not in ('sphere', colors[curColor])) |
| | 1859 | return nil; |
| | 1860 | } |
| | 1861 | |
| | 1862 | /* all of our tokens match */ |
| | 1863 | return self; |
| | 1864 | } |
| | 1865 | |
| | 1866 | dobjFor(Turn) |
| | 1867 | { |
| | 1868 | verify() { } |
| | 1869 | action() |
| | 1870 | { |
| | 1871 | ++curColor; |
| | 1872 | if (curColor > colors.length()) |
| | 1873 | curColor = 1; |
| | 1874 | |
| | 1875 | "Although the sphere is immovably fixed in space, it rotates |
| | 1876 | so freely that it feels weightless. You give it the lightest |
| | 1877 | touch, and it starts spinning wildly, so fast that it becomes |
| | 1878 | a featureless, glowing blur. After a few moments, it abruptly |
| | 1879 | comes to a stop, and you see that its color has changed |
| | 1880 | to <<colors[curColor]>>. "; |
| | 1881 | } |
| | 1882 | } |
| | 1883 | |
| | 1884 | /* current index in color list */ |
| | 1885 | curColor = 1 |
| | 1886 | |
| | 1887 | /* list of possible colors */ |
| | 1888 | colors = ['red', 'blue', 'green', 'orange', 'yellow', 'white'] |
| | 1889 | ; |
| | 1890 | |
| | 1891 | + Enterable, Decoration ->yardDoor 'house' 'house' |
| | 1892 | "It's a modest one-story house. A door leads in to the south. " |
| | 1893 | ; |
| | 1894 | |
| | 1895 | + Enterable ->shedOuterDoor 'old garden shed' 'shed' |
| | 1896 | "The shed's wood siding looks well weathered; only the thinnest |
| | 1897 | layer of white paint remains. Its flimsy-looking door |
| | 1898 | is <<shedOuterDoor.openDesc>>. " |
| | 1899 | ; |
| | 1900 | |
| | 1901 | + Decoration 'shed\'s wood siding' 'wood siding' |
| | 1902 | "It looks weathered. " |
| | 1903 | ; |
| | 1904 | |
| | 1905 | + shedOuterDoor: Door 'shed door' 'door of the shed' |
| | 1906 | "It leads into the shed. " |
| | 1907 | ; |
| | 1908 | |
| | 1909 | /* ------------------------------------------------------------------------ */ |
| | 1910 | /* |
| | 1911 | * Shed |
| | 1912 | */ |
| | 1913 | shed: Room 'Shed' 'the shed' |
| | 1914 | "There's barely room to stand up or turn around in this tiny |
| | 1915 | storage space. The north wall has a few deep shelves, but |
| | 1916 | otherwise the structure is featureless. A door leads out |
| | 1917 | to the southeast. " |
| | 1918 | southeast = shedInnerDoor |
| | 1919 | out asExit(southeast) |
| | 1920 | ; |
| | 1921 | |
| | 1922 | + shedInnerDoor: Door ->shedOuterDoor 'shed door' 'door' |
| | 1923 | "It leads out of the shed. " |
| | 1924 | ; |
| | 1925 | |
| | 1926 | + shedShelves: Fixture, Surface 'deep shelf/shelves' 'shelf' |
| | 1927 | "The shelves are about a yard deep and look sturdy. " |
| | 1928 | ; |
| | 1929 | |
| | 1930 | ++ Flashlight 'flash light/flashlight' 'flashlight' |
| | 1931 | "It's a big lantern-type flashlight. It's currently <<onDesc>>. " |
| | 1932 | ; |
| | 1933 | |
| | 1934 | ++ Thing 'rat poison/box' 'box of rat poison' |
| | 1935 | "It's a large box, missing its top, full of white powder. The |
| | 1936 | box is labeled RAT POISON and is marked with a prominent |
| | 1937 | skull-and-crossbones symbol, along with a legend in tiny |
| | 1938 | type reading <q>Use of this product in a manner inconsistent |
| | 1939 | with its labeling is a violation of Adventure Game Law.</q> " |
| | 1940 | |
| | 1941 | dobjFor(Eat) remapTo(Eat, ratPoison) |
| | 1942 | lookInDesc = "It's full of white powder. " |
| | 1943 | ; |
| | 1944 | |
| | 1945 | +++ ratPoison: Component, Food 'white powder' 'white powder' |
| | 1946 | "The box is full of white powder. " |
| | 1947 | cannotTakeMsg = 'You\'d best leave the poison in the box. ' |
| | 1948 | cannotMoveMsg = 'You\'d best leave the poison in the box. ' |
| | 1949 | cannotPutMsg = 'Best to leave the poison in the box. ' |
| | 1950 | |
| | 1951 | dobjFor(Pour) |
| | 1952 | { |
| | 1953 | verify() { } |
| | 1954 | action() { "You'd best leave the poison in its box. "; } |
| | 1955 | } |
| | 1956 | dobjFor(PourInto) asDobjFor(Pour) |
| | 1957 | dobjFor(PourOnto) asDobjFor(Pour) |
| | 1958 | |
| | 1959 | dobjFor(Eat) |
| | 1960 | { |
| | 1961 | preCond = [touchObj] |
| | 1962 | verify() { dangerous; } |
| | 1963 | action() |
| | 1964 | { |
| | 1965 | "It's not very tasty, but it sure is deadly. "; |
| | 1966 | |
| | 1967 | /* terminate the game */ |
| | 1968 | finishGameMsg(ftDeath, [finishOptionUndo, finishOptionCredits]); |
| | 1969 | } |
| | 1970 | } |
| | 1971 | ; |
| | 1972 | |
| | 1973 | class MyMatch: Matchstick 'match*matches' 'match' |
| | 1974 | ; |
| | 1975 | |
| | 1976 | ++ Matchbook 'match book/matches/matchbook*matches' 'book of matches' |
| | 1977 | matchName(origTokens, adjustedTokens) |
| | 1978 | { |
| | 1979 | /* |
| | 1980 | * if the name is just 'match', ignore it - we want to allow |
| | 1981 | * 'match' as an adjective because we want to match 'match |
| | 1982 | * book', but just plain 'match' is very unlikely to mean us |
| | 1983 | */ |
| | 1984 | if (origTokens.length() == 1 |
| | 1985 | && getTokVal(origTokens[1]) == 'match') |
| | 1986 | return nil; |
| | 1987 | |
| | 1988 | /* inherit default handling */ |
| | 1989 | return inherited(origTokens, adjustedTokens); |
| | 1990 | } |
| | 1991 | |
| | 1992 | /* put this after the individual matches when we match a plural */ |
| | 1993 | pluralOrder = 110 |
| | 1994 | ; |
| | 1995 | |
| | 1996 | +++ MyMatch; |
| | 1997 | +++ MyMatch; |
| | 1998 | +++ MyMatch; |
| | 1999 | +++ MyMatch; |
| | 2000 | +++ MyMatch; |
| | 2001 | |
| | 2002 | /* ------------------------------------------------------------------------ */ |
| | 2003 | /* |
| | 2004 | * Additional verbs |
| | 2005 | */ |
| | 2006 | |
| | 2007 | DefineTAction(Inflate); |
| | 2008 | VerbRule(Inflate) |
| | 2009 | ('inflate' | 'blow' 'up') dobjList |
| | 2010 | | 'blow' dobjList 'up' |
| | 2011 | : InflateAction |
| | 2012 | verbPhrase = 'inflate/inflating (what)' |
| | 2013 | ; |
| | 2014 | |
| | 2015 | DefineTAction(Deflate); |
| | 2016 | VerbRule(Deflate) |
| | 2017 | 'deflate' dobjList |
| | 2018 | : DeflateAction |
| | 2019 | verbPhrase = 'deflate/deflating (what)' |
| | 2020 | ; |
| | 2021 | |
| | 2022 | DefineTAction(LookOver); |
| | 2023 | VerbRule(LookOver) |
| | 2024 | 'look' 'over' dobjList |
| | 2025 | : LookOverAction |
| | 2026 | verbPhrase = 'look/looking (over what)' |
| | 2027 | ; |
| | 2028 | |
| | 2029 | DefineTAction(Ride); |
| | 2030 | VerbRule(Ride) |
| | 2031 | 'ride' singleDobj : RideAction |
| | 2032 | verbPhrase = 'ride/riding (what)' |
| | 2033 | ; |
| | 2034 | |
| | 2035 | DefineTAction(Answer); |
| | 2036 | VerbRule(Answer) |
| | 2037 | 'answer' dobjList |
| | 2038 | : AnswerAction |
| | 2039 | verbPhrase = 'answer/answering (what)' |
| | 2040 | ; |
| | 2041 | |
| | 2042 | modify Thing |
| | 2043 | dobjFor(Inflate) |
| | 2044 | { |
| | 2045 | preCond = [touchObj] |
| | 2046 | verify() |
| | 2047 | { |
| | 2048 | illogical('{That dobj/he} {is}n\'t something {you/he} can |
| | 2049 | inflate. '); |
| | 2050 | } |
| | 2051 | } |
| | 2052 | dobjFor(Deflate) |
| | 2053 | { |
| | 2054 | preCond = [touchObj] |
| | 2055 | verify() |
| | 2056 | { |
| | 2057 | illogical('{That dobj/he} {is}n\'t something {you/he} can |
| | 2058 | deflate. '); |
| | 2059 | } |
| | 2060 | } |
| | 2061 | dobjFor(LookOver) |
| | 2062 | { |
| | 2063 | preCond = [objVisible] |
| | 2064 | verify() { illogical('{You/he} see{s} nothing unusual. '); } |
| | 2065 | } |
| | 2066 | dobjFor(Ride) |
| | 2067 | { |
| | 2068 | verify() |
| | 2069 | { |
| | 2070 | illogical('{That dobj/he} {is}n\'t something {you/he} |
| | 2071 | can ride. '); |
| | 2072 | } |
| | 2073 | } |
| | 2074 | dobjFor(Answer) |
| | 2075 | { |
| | 2076 | preCond = [objVisible] |
| | 2077 | verify() |
| | 2078 | { |
| | 2079 | illogical('{The dobj/he} didn\'t ask {you/him} anything. '); |
| | 2080 | } |
| | 2081 | } |
| | 2082 | ; |
| | 2083 | |
| | 2084 | DefineTopicAction(ThinkAbout) |
| | 2085 | execAction() |
| | 2086 | { |
| | 2087 | "You think about <<gTopic.getTopicText()>>. "; |
| | 2088 | } |
| | 2089 | ; |
| | 2090 | VerbRule(ThinkAbout) 'think' 'about' singleTopic : ThinkAboutAction |
| | 2091 | verbPhrase = 'think/thinking (about what)' |
| | 2092 | ; |
| | 2093 | |
| | 2094 | /* ------------------------------------------------------------------------ */ |
| | 2095 | /* |
| | 2096 | * Basement tunnel |
| | 2097 | */ |
| | 2098 | tunnel: DarkRoom 'Tunnel' 'the tunnel' |
| | 2099 | "This low, narrow tunnel is walled in old, dark gray concrete |
| | 2100 | or something similar. The walls curve inward at shoulder level to |
| | 2101 | form an arched ceiling just barely high enough at the center |
| | 2102 | to allow walking without bending your neck. The passage ends |
| | 2103 | to the north at an iron door, and ends at the south in an |
| | 2104 | earthen wall. A narrow side passage leads west. " |
| | 2105 | |
| | 2106 | north = tunnelDoor |
| | 2107 | west = sideTunnel |
| | 2108 | |
| | 2109 | /* |
| | 2110 | * since we have special walls and ceiling that are specifically |
| | 2111 | * included as normal decoration objects in this location, keep only |
| | 2112 | * the default floor among our room parts |
| | 2113 | */ |
| | 2114 | roomParts = [defaultFloor] |
| | 2115 | ; |
| | 2116 | |
| | 2117 | + tunnelCeiling: Decoration |
| | 2118 | 'arched concrete cement ceiling roof' 'ceiling' |
| | 2119 | "It's very low, and arches in from the walls. " |
| | 2120 | ; |
| | 2121 | |
| | 2122 | + tunnelWalls: Decoration |
| | 2123 | 'east west tunnel concrete cement wall/walls' 'tunnel wall' |
| | 2124 | "The walls are made of some kind of dark gray concrete. " |
| | 2125 | ; |
| | 2126 | |
| | 2127 | + earthenWall: Decoration |
| | 2128 | 'earthen dirt south wall' 'earthen wall' |
| | 2129 | "It's just a wall of dirt, as though construction on the |
| | 2130 | tunnel had been abruptly halted here. " |
| | 2131 | ; |
| | 2132 | |
| | 2133 | + tunnelDoor: IronDoor ->stairDoor; |
| | 2134 | |
| | 2135 | /* ------------------------------------------------------------------------ */ |
| | 2136 | /* |
| | 2137 | * Side tunnel |
| | 2138 | */ |
| | 2139 | sideTunnel: DarkRoom 'Jagged Tunnel' 'the jagged tunnel' |
| | 2140 | "This is an extremely narrow tunnel; it looks like construction |
| | 2141 | was abandoned before it was completed. The passage is jagged, |
| | 2142 | but continues a little way to the east. A rough ladder has |
| | 2143 | been improvised from wood scraps and fastened to the wall, |
| | 2144 | leading up to a small trap door above. " |
| | 2145 | |
| | 2146 | up = tunnelTrapDoor |
| | 2147 | east = tunnel |
| | 2148 | |
| | 2149 | roomParts = [defaultFloor] |
| | 2150 | |
| | 2151 | getExtraScopeItems(actor) |
| | 2152 | { |
| | 2153 | /* |
| | 2154 | * if they arrived from above, make sure the trap door and |
| | 2155 | * ladder are in scope |
| | 2156 | */ |
| | 2157 | if (lastArrivedVia[actor] == tunnelTrapDoor) |
| | 2158 | return inherited(actor) + tunnelTrapDoor + tunnelLadder; |
| | 2159 | else |
| | 2160 | return inherited(actor); |
| | 2161 | } |
| | 2162 | |
| | 2163 | /* table giving last arrival connector for each actor present */ |
| | 2164 | lastArrivedVia = static new LookupTable(4, 4) |
| | 2165 | |
| | 2166 | travelerArriving(traveler, origin, connector, backConnector) |
| | 2167 | { |
| | 2168 | /* note the connector by which they're arriving */ |
| | 2169 | foreach (local actor in traveler.getTravelerActors()) |
| | 2170 | lastArrivedVia[actor] = backConnector; |
| | 2171 | |
| | 2172 | /* inherit default handling */ |
| | 2173 | inherited(traveler, origin, connector, backConnector); |
| | 2174 | } |
| | 2175 | ; |
| | 2176 | |
| | 2177 | + tunnelTrapDoor: Fixture, Door |
| | 2178 | 'small trap door' 'trap door' |
| | 2179 | ; |
| | 2180 | |
| | 2181 | + tunnelLadder: StairwayUp 'rough improvised wood ladder/scraps' 'ladder' |
| | 2182 | dobjFor(TravelVia) remapTo(TravelVia, tunnelTrapDoor) |
| | 2183 | ; |
| | 2184 | |
| | 2185 | + Decoration |
| | 2186 | 'west north south tunnel dirt jagged wall/walls' 'tunnel wall' |
| | 2187 | "The walls are just bare dirt. " |
| | 2188 | ; |
| | 2189 | |
| | 2190 | /* ------------------------------------------------------------------------ */ |
| | 2191 | /* |
| | 2192 | * A generic USE verb of two objects. We'll remap this to a more |
| | 2193 | * specific verb when possible, based on the direct object, using the |
| | 2194 | * direct object's remapDobjUse() method. |
| | 2195 | */ |
| | 2196 | DefineTIAction(UseWith) |
| | 2197 | /* we want to remap on the direct object, so we must resolve it first */ |
| | 2198 | resolveFirst = DirectObject |
| | 2199 | ; |
| | 2200 | VerbRule(UseWith) 'use' singleDobj ('on' | 'with') singleIobj: UseWithAction |
| | 2201 | verbPhrase = 'use/using (what) (on what)' |
| | 2202 | ; |
| | 2203 | VerbRule(UseWithWhat) 'use' singleDobj: UseWithAction |
| | 2204 | verbPhrase = 'use/using (what) (on what)' |
| | 2205 | construct() |
| | 2206 | { |
| | 2207 | /* set up the empty indirect object phrase */ |
| | 2208 | iobjMatch = new EmptyNounPhraseProd(); |
| | 2209 | iobjMatch.responseProd = withSingleNoun; |
| | 2210 | } |
| | 2211 | ; |
| | 2212 | |
| | 2213 | modify Thing |
| | 2214 | dobjFor(UseWith) { verify() { } } |
| | 2215 | iobjFor(UseWith) { verify() { illogical('Please be more specific. '); }} |
| | 2216 | ; |
| | 2217 | |
| | 2218 | /* |
| | 2219 | * USE <key> WITH <obj> maps to LOCK/UNLOCK <obj> WITH <key> |
| | 2220 | */ |
| | 2221 | modify Key |
| | 2222 | dobjFor(UseWith) |
| | 2223 | { |
| | 2224 | verify() { verifyIobjLockWith(); } |
| | 2225 | preCond() { return preCondIobjLockWith(); } |
| | 2226 | remap() |
| | 2227 | { |
| | 2228 | /* |
| | 2229 | * If we have a single tentative indirect object, check to |
| | 2230 | * see if it's locked or unlocked, and use the appropriate |
| | 2231 | * verb (LOCK or UNLOCK) to reverse its state. If we can't |
| | 2232 | * yet identify the indirect object, guess that it's UNLOCK. |
| | 2233 | */ |
| | 2234 | if (gTentativeIobj.length() == 1) |
| | 2235 | { |
| | 2236 | /* |
| | 2237 | * If the object is locked, unlock it; otherwise lock |
| | 2238 | * it. Note that we must reverse the dobj/iobj roles in |
| | 2239 | * the remapped verb, because the key is the direct |
| | 2240 | * object of USE but the indirect object of LOCK/UNLOCK. |
| | 2241 | */ |
| | 2242 | if (gTentativeIobj[1].obj_.isLocked) |
| | 2243 | return [UnlockWithAction, OtherObject, self]; |
| | 2244 | else |
| | 2245 | return [LockWithAction, OtherObject, self]; |
| | 2246 | } |
| | 2247 | else |
| | 2248 | { |
| | 2249 | /* guess that it's UNLOCK */ |
| | 2250 | return [UnlockWithAction, OtherObject, self]; |
| | 2251 | } |
| | 2252 | } |
| | 2253 | } |
| | 2254 | ; |
| | 2255 | |
| | 2256 | /* ------------------------------------------------------------------------ */ |
| | 2257 | /* |
| | 2258 | * "fill x with y" - treat this as "put y in x". This verb isn't really |
| | 2259 | * useful in this sample game; it's here only as a demonstration of |
| | 2260 | * using the TIAction remapping macros. |
| | 2261 | */ |
| | 2262 | DefineTIAction(FillWith) |
| | 2263 | resolveFirst = DirectObject |
| | 2264 | ; |
| | 2265 | |
| | 2266 | VerbRule(FillWith) |
| | 2267 | 'fill' singleDobj 'with' iobjList |
| | 2268 | : FillWithAction |
| | 2269 | verbPhrase = 'fill/filling (what) (with what)' |
| | 2270 | ; |
| | 2271 | |
| | 2272 | modify Thing |
| | 2273 | dobjFor(FillWith) remapTo(PutIn, IndirectObject, self); |
| | 2274 | ; |
| | 2275 | |
| | 2276 | /* ------------------------------------------------------------------------ */ |
| | 2277 | /* |
| | 2278 | * Main startup object. This lets us customize the introductory text, |
| | 2279 | * set the player character object, and control the game's startup |
| | 2280 | * procedure. |
| | 2281 | */ |
| | 2282 | gameMain: GameMainDef |
| | 2283 | /* the initial (and only) player character is 'me' */ |
| | 2284 | initialPlayerChar = me |
| | 2285 | |
| | 2286 | /* show our introduction message */ |
| | 2287 | showIntro() |
| | 2288 | { |
| | 2289 | /* show our simple introduction message */ |
| | 2290 | "Welcome to the TADS 3 Library Sample Game!\b"; |
| | 2291 | } |
| | 2292 | |
| | 2293 | /* |
| | 2294 | * Create an "about box". |
| | 2295 | * |
| | 2296 | * The <ABOUTBOX> tag has to be re-displayed each time we clear the |
| | 2297 | * screen, since the tag is part of the main text window's output |
| | 2298 | * stream and thus only lasts as long as the rest of the text on the |
| | 2299 | * main game screen. Fortunately, the library will take care of this |
| | 2300 | * for us automatically, as long as we do two things. First, we have |
| | 2301 | * to put our <ABOUTBOX> tag here, in this method, so that the |
| | 2302 | * library knows how to re-display the tag when necessary. Second, |
| | 2303 | * we must always use the cls() function (NOT the low-level |
| | 2304 | * clearScreen() function) whenever we want to clear the game window. |
| | 2305 | */ |
| | 2306 | setAboutBox() |
| | 2307 | { |
| | 2308 | "<aboutbox><body bgcolor=black text=white> |
| | 2309 | <table height='100%' width='100%' border=0> |
| | 2310 | <tr valign=middle align=center><td> |
| | 2311 | <font size=5 face=tads-sans>T3 Library Sampler</font><br><br> |
| | 2312 | <font face=tads-sans><b>This is the T3 Library Sample Game. It's |
| | 2313 | just an example of how to create a game using the T3 standard |
| | 2314 | library (also known as <q>adv3</q>).</b></font> |
| | 2315 | </table></aboutbox>"; |
| | 2316 | } |
| | 2317 | |
| | 2318 | /* show our end-of-game message */ |
| | 2319 | showGoodbye() |
| | 2320 | { |
| | 2321 | "<.p>Thanks for playing the Library Sample Game!\b"; |
| | 2322 | } |
| | 2323 | ; |
| | 2324 | |
| | 2325 | /* ------------------------------------------------------------------------ */ |
| | 2326 | /* |
| | 2327 | * random daemon/fuse tests |
| | 2328 | */ |
| | 2329 | |
| | 2330 | VerbRule(rtfuse) 'test-rtfuse' : IAction |
| | 2331 | execAction() |
| | 2332 | { |
| | 2333 | "Starting a real-time fuse for 10 seconds... "; |
| | 2334 | new RealTimeFuse(testFuse, &execEvent, 10000); |
| | 2335 | } |
| | 2336 | ; |
| | 2337 | |
| | 2338 | VerbRule(rtdaemon) 'test-rtdaemon' : IAction |
| | 2339 | execAction() |
| | 2340 | { |
| | 2341 | "Starting a real-time daemon for 10 second... "; |
| | 2342 | new RealTimeDaemon(testDaemon, &execEvent, 10000); |
| | 2343 | } |
| | 2344 | ; |
| | 2345 | |
| | 2346 | VerbRule(moreprompt) 'moreprompt' : IAction |
| | 2347 | execAction() |
| | 2348 | { |
| | 2349 | "Pausing for a MORE prompt, leaving the real-time clock running.\n"; |
| | 2350 | inputManager.pauseForMore(nil); |
| | 2351 | } |
| | 2352 | ; |
| | 2353 | |
| | 2354 | VerbRule(morefreeze) 'morefreeze' : IAction |
| | 2355 | execAction() |
| | 2356 | { |
| | 2357 | "Pausing for a MORE prompt, freezing the real-time clock.\n"; |
| | 2358 | inputManager.pauseForMore(true); |
| | 2359 | } |
| | 2360 | ; |
| | 2361 | |
| | 2362 | VerbRule(fuse) 'test-fuse' : IAction |
| | 2363 | execAction() |
| | 2364 | { |
| | 2365 | "Starting a fuse for three turns... "; |
| | 2366 | new Fuse(testFuse, &execEvent, 3); |
| | 2367 | } |
| | 2368 | ; |
| | 2369 | |
| | 2370 | testFuse: object |
| | 2371 | execEvent() |
| | 2372 | { |
| | 2373 | "<.commandsep>This is the test fuse!"; |
| | 2374 | } |
| | 2375 | ; |
| | 2376 | |
| | 2377 | VerbRule(daemon) 'test-daemon': IAction |
| | 2378 | execAction() |
| | 2379 | { |
| | 2380 | "Starting daemon that will run three times, every other turn... "; |
| | 2381 | new Daemon(testDaemon, &execEvent, 2); |
| | 2382 | testDaemon.counter = 0; |
| | 2383 | } |
| | 2384 | ; |
| | 2385 | |
| | 2386 | VerbRule(getkey) 'getkey': IAction |
| | 2387 | execAction() |
| | 2388 | { |
| | 2389 | local key; |
| | 2390 | |
| | 2391 | /* get a key */ |
| | 2392 | key = inputManager.getKey(true, {: "<.p>Enter a key:\ " }); |
| | 2393 | |
| | 2394 | /* show the key */ |
| | 2395 | "\nThanks! You typed '<<key>>'!\n"; |
| | 2396 | |
| | 2397 | nestedActorAction(bob, Take, bigRedBall); |
| | 2398 | } |
| | 2399 | ; |
| | 2400 | |
| | 2401 | VerbRule(showlinks) 'showlinks': IAction |
| | 2402 | execAction() |
| | 2403 | { |
| | 2404 | "Here are some links: |
| | 2405 | <ul> |
| | 2406 | <li><font color=green>color <a href='outside'>outside</a> |
| | 2407 | the link</font> |
| | 2408 | <li><font color=purple>color <a href='inside'><font color=red> |
| | 2409 | inside</font></a> the link</font> |
| | 2410 | <li><font color=navy>a mix of <a href='mix'>outside |
| | 2411 | <font color=#ff8000>and inside</font> the link</a> for |
| | 2412 | extra completeness</font> |
| | 2413 | </ul> "; |
| | 2414 | } |
| | 2415 | ; |
| | 2416 | |
| | 2417 | testDaemon: object |
| | 2418 | execEvent() |
| | 2419 | { |
| | 2420 | "\bThis is the test daemon! "; |
| | 2421 | |
| | 2422 | #if 0 |
| | 2423 | "<.p>*** You have died ***\n"; |
| | 2424 | finishGame([finishOptionUndo, finishOptionCredits, |
| | 2425 | finishOptionFullScore]); |
| | 2426 | #endif |
| | 2427 | |
| | 2428 | /* if I've run three times now, cancel myself */ |
| | 2429 | if (++counter == 3) |
| | 2430 | eventManager.removeCurrentEvent(); |
| | 2431 | } |
| | 2432 | counter = 0 |
| | 2433 | ; |
| | 2434 | |
| | 2435 | VerbRule(nbspTest) 'nbsptest' : IAction |
| | 2436 | execAction() |
| | 2437 | { |
| | 2438 | "(This test is designed for 80-column displays.) |
| | 2439 | \b |
| | 2440 | This is a long line intended to wrap after a bit. The trick is that |
| | 2441 | here to here (i.e., the parts between the first and second 'here') |
| | 2442 | should be non-breaking: 'here to here' should all be on one line, |
| | 2443 | even though there are spaces between the words. Anyway, so much for |
| | 2444 | the test. "; |
| | 2445 | } |
| | 2446 | ; |
| | 2447 | |
| | 2448 | VerbRule(wrapTest) 'wraptest' : IAction |
| | 2449 | execAction() |
| | 2450 | { |
| | 2451 | "(This test is designed for 80-column displays.) |
| | 2452 | \b |
| | 2453 | This is a long line intended to wrap after.\u2002First, let's test soft |
| | 2454 | hy­phen­ation. |
| | 2455 | Okay, we should have hyphenated somewhere in there. |
| | 2456 | \b |
| | 2457 | Next, let us make sure that regular hyphen wrapping still works. What we need |
| | 2458 | is for this line to wrap <b>after</b> 'need'.\n |
| | 2459 | Next, let us make sure that regular hyphen wrapping still works. What we |
| | 2460 | need -- and this is key -- is to make sure this line wraps |
| | 2461 | <b>before</b> 'need'.\n |
| | 2462 | Next, let us make sure that regular hyphens still work. This time use |
| | 2463 | hard-hyphen-wrapping, where we break at a hyphen in the middle of a word. |
| | 2464 | \b |
| | 2465 | Now we can test some new stuff. First, test the explicit break flag: |
| | 2466 | We\u200bCan\u200bBreak\u200bAt\u200bAny\u200bOf\u200bThese"; |
| | 2467 | "\u200bInter\u200bCaps\u200bCharacters\u200bAnd\u200bWe\u200bCan\u200bGo"; |
| | 2468 | "\u200bOn\u200bLike\u200bThis\u200bFor\u200bQuite\u200bSome\u200bTime"; |
| | 2469 | "\u200bEven\u200bTo\u200bThe\u200bExtent\u200bOf\u200bWrapping\u200bAnother"; |
| | 2470 | "\u200bLine\u200bOf\u200bText! |
| | 2471 | \b |
| | 2472 | Next, let's test that explicit non-break points are working. We'll want |
| | 2473 | to\ufeff \ufeffbreak just before the previous word 'break', but we have a |
| | 2474 | non-breaking zero-width space after the 'to' and another before the 'break', |
| | 2475 | which should prevent breaking there.\n |
| | 2476 | Likewise, we'll want to break this line at a hyphen, coming up right |
| | 2477 | here-\ufeffabsolutely. But we have a non-break right after the hyphen, |
| | 2478 | which should tell us not to break there.\n |
| | 2479 | For comparison, this line we do want to break at the hyphen, which is |
| | 2480 | here-absolutely once again. This time we should break after the hyphen. |
| | 2481 | \b |
| | 2482 | The next test is for East Asian language wrapping.<wrap char>WeAreNowIn"; |
| | 2483 | "CharacterWrapMode,WhichMeansThatWeCanWrapBetweenAnyTwoCharactersExcept"; |
| | 2484 | "WhereOtherwiseNotedWithAnExplicitNonBreakingIndicator\ufeff.<wrap word>This |
| | 2485 | is back in English-style text, which means we should wrap on word |
| | 2486 | boundaries.<wrap char>AndNowWe'reBackInCharacterWrappingMode.TheTrickIsThat"; |
| | 2487 | "WeWantToCheckTransitionsBetweenTheTwoModes.<wrap word>Such as here, where |
| | 2488 | we are back in word mode.<wrap char>OrHereWithCharInTheMiddleOfALine."; |
| | 2489 | "<wrap word>We're back in word-wrap mode here.\u2002The remaining test is |
| | 2490 | to put word-wrap mode in the middle of a line of<wrap char>CharacterWrapped"; |
| | 2491 | "Text,SuchAsThis<wrap word>(here is some word-wrapped text)<wrap char>AndNow"; |
| | 2492 | "WeAreBackToCharacterWrappedTextToTheEndOfTheLine.ATestWeHaveYetToTryWith"; |
| | 2493 | "CharWrappedTextIs\ufeff.\ufeff.\ufeff.ExplicitNonBreakingIndicators. |
| | 2494 | <wrap word>(That ellipsis should be all together, not broken across lines, |
| | 2495 | and should furthermore stick to the letter just before it, so the ellipsis |
| | 2496 | doesn't start a new line.)\n |
| | 2497 | \b |
| | 2498 | This is just a test of <u>underlining some typographical spaces</u>: |
| | 2499 | let's try <u>an en space:\u2002an em space:\u2003a thin space:\u2009a |
| | 2500 | punctuation space:\u2008a figure space:\u2007a hair space:\u200a...and |
| | 2501 | that</u> should do it.\n |
| | 2502 | <font color=black bgcolor=yellow> |
| | 2503 | And now let's try something similar:\u2002 |
| | 2504 | ending the line with an en plus an em:\u2002\u2003This is to demonstrate |
| | 2505 | that we trim trailing whitespace, even if the whitespace consists of |
| | 2506 | typographical spaces.</font>\n |
| | 2507 | <font color=black bgcolor=aqua> |
| | 2508 | Another similar test:\u2002This time, some non-breaking |
| | 2509 | spaces:\uFEFF\u2002\uFEFF\u2003\uFEFF\n |
| | 2510 | That ended with an en plus em again, but these were quoted with FEFF's. |
| | 2511 | </font>\n |
| | 2512 | \b |
| | 2513 | <p><table border width=50% align=center> |
| | 2514 | <tr><td><wrap char>ThisIsSomeTextInATableInCharacterWrapMode.TheIdeaIsTo"; |
| | 2515 | "SeeHowTablesDealWithThisSortOfThing.TablesUseTheBasicWrappingMechanism"; |
| | 2516 | "LikeEverythingElse,ButHaveSomeSpecialCodeToMeasureTheSizeOfTheTextThat"; |
| | 2517 | "WillGoInThem.<wrap word> |
| | 2518 | <td>This is a second column that uses character-based wrapping instead |
| | 2519 | of word-based wrapping. The two columns should more or less balance |
| | 2520 | out, although the word-based column will want a bigger share because |
| | 2521 | of its larger minimum width. |
| | 2522 | </table>\n"; |
| | 2523 | } |
| | 2524 | ; |
| | 2525 | |
| | 2526 | |
| | 2527 | VerbRule(logAbout) 'logabout' : IAction |
| | 2528 | execAction() |
| | 2529 | { |
| | 2530 | "Logging the ABOUT command to About.txt...\n"; |
| | 2531 | LogConsole.captureToFile('About.txt', |
| | 2532 | getLocalCharSet(CharsetDisplay), 80, |
| | 2533 | {: AboutAction.execSystemAction() }); |
| | 2534 | } |
| | 2535 | ; |
| | 2536 | |
| | 2537 | /* ------------------------------------------------------------------------ */ |
| | 2538 | /* |
| | 2539 | * A rather contrived preparser. |
| | 2540 | * |
| | 2541 | * This preparser allows the player to set "aliases" using syntax like |
| | 2542 | * this: |
| | 2543 | * |
| | 2544 | * alias $xxx yyy |
| | 2545 | * |
| | 2546 | * where xxx is the name of an alias variable, and yyy is the |
| | 2547 | * replacement text for the variable. Once an alias has been created, |
| | 2548 | * it can be used in any command simply by naming it with the '$ prefix. |
| | 2549 | */ |
| | 2550 | StringPreParser |
| | 2551 | /* pre-compile our search patterns */ |
| | 2552 | patDefAlias = static new RexPattern( |
| | 2553 | '<space>*alias<space>+<dollar>(<alphanum>+)<space>+(.+)') |
| | 2554 | patUseAlias = static new RexPattern( |
| | 2555 | '(<^dollar>*)<dollar>(<alphanum>+)(.*)') |
| | 2556 | |
| | 2557 | doParsing(str, which) |
| | 2558 | { |
| | 2559 | local ret; |
| | 2560 | local aliasName; |
| | 2561 | local aliasText; |
| | 2562 | |
| | 2563 | /* check for the alias definition syntax */ |
| | 2564 | if (rexMatch(patDefAlias, str) != nil) |
| | 2565 | { |
| | 2566 | /* extract the alias name and the replacement text */ |
| | 2567 | aliasName = rexGroup(1)[3]; |
| | 2568 | aliasText = rexGroup(2)[3]; |
| | 2569 | |
| | 2570 | /* add the alias to our table */ |
| | 2571 | aliasTable[aliasName] = aliasText; |
| | 2572 | |
| | 2573 | /* mention that the alias has been defined */ |
| | 2574 | "Okay, $<<aliasName>> has been defined. "; |
| | 2575 | |
| | 2576 | /* this command has been fully handled */ |
| | 2577 | return nil; |
| | 2578 | } |
| | 2579 | |
| | 2580 | /* we're not defining an alias, so look for aliases to replace */ |
| | 2581 | ret = nil; |
| | 2582 | for (;;) |
| | 2583 | { |
| | 2584 | /* look for a '$' in the balance of the input string */ |
| | 2585 | if (rexMatch(patUseAlias, str) != nil) |
| | 2586 | { |
| | 2587 | local prv; |
| | 2588 | local nxt; |
| | 2589 | |
| | 2590 | /* |
| | 2591 | * extract the text before the alias, the name of the |
| | 2592 | * alias, and the rest of the text after the alias |
| | 2593 | */ |
| | 2594 | prv = rexGroup(1)[3]; |
| | 2595 | aliasName = rexGroup(2)[3]; |
| | 2596 | nxt = rexGroup(3)[3]; |
| | 2597 | |
| | 2598 | /* add the previous text to the output string so far */ |
| | 2599 | if (ret != nil) |
| | 2600 | ret += prv; |
| | 2601 | else |
| | 2602 | ret = prv; |
| | 2603 | |
| | 2604 | /* |
| | 2605 | * translate the alias; if it has no translation, then |
| | 2606 | * use the original text of the alias, including the |
| | 2607 | * '$', unchanged |
| | 2608 | */ |
| | 2609 | aliasText = aliasTable[aliasName]; |
| | 2610 | if (aliasText == nil) |
| | 2611 | aliasText = '$' + aliasName; |
| | 2612 | |
| | 2613 | /* add the translated alias to the output string so far */ |
| | 2614 | ret += aliasText; |
| | 2615 | |
| | 2616 | /* continue parsing the balance of the input string */ |
| | 2617 | str = nxt; |
| | 2618 | } |
| | 2619 | else |
| | 2620 | { |
| | 2621 | /* |
| | 2622 | * We have no more aliases in the string - the balance |
| | 2623 | * of the string is simply to be returned unchanged. If |
| | 2624 | * we've already built some output text, add the balance |
| | 2625 | * of the input to the output so far; otherwise, the |
| | 2626 | * balance of the input is the entirety of the output. |
| | 2627 | */ |
| | 2628 | if (ret != nil) |
| | 2629 | ret += str; |
| | 2630 | else |
| | 2631 | ret = str; |
| | 2632 | |
| | 2633 | /* we're done with the text */ |
| | 2634 | break; |
| | 2635 | } |
| | 2636 | } |
| | 2637 | |
| | 2638 | /* return the output string */ |
| | 2639 | return ret; |
| | 2640 | } |
| | 2641 | |
| | 2642 | /* our lookup table of defined aliases */ |
| | 2643 | aliasTable = static new LookupTable() |
| | 2644 | ; |
| | 2645 | |