| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | *. Portions based on work by Kevin Forchione, used by permission. |
| | 6 | * |
| | 7 | * TADS 3 Library - objects |
| | 8 | * |
| | 9 | * This module defines the basic physical simulation objects (apart from |
| | 10 | * Thing, the base class for most game objects, which is so large that |
| | 11 | * it's defined in its own separate module for convenience). We define |
| | 12 | * such basic classes as containers, surfaces, fixed-in-place objects, |
| | 13 | * openables, and lockables. |
| | 14 | */ |
| | 15 | |
| | 16 | /* include the library header */ |
| | 17 | #include "adv3.h" |
| | 18 | |
| | 19 | |
| | 20 | /* ------------------------------------------------------------------------ */ |
| | 21 | /* |
| | 22 | * LocateInParent - this is a mix-in superclass that defines the location |
| | 23 | * of the object as the object's lexical parent. This is useful for |
| | 24 | * nested object definitions where the next object should be located |
| | 25 | * within the enclosing object. |
| | 26 | * |
| | 27 | * When this class is mixed with Thing or its subclasses, LocateInParent |
| | 28 | * should go first, so that the location we define here takes precedence. |
| | 29 | */ |
| | 30 | class LocateInParent: object |
| | 31 | location = (lexicalParent) |
| | 32 | ; |
| | 33 | |
| | 34 | /* ------------------------------------------------------------------------ */ |
| | 35 | /* |
| | 36 | * Intangible - this is an object that represents something that can be |
| | 37 | * sensed but which has no tangible existence, such as a ray of light, a |
| | 38 | * sound, or an odor. |
| | 39 | */ |
| | 40 | class Intangible: Thing |
| | 41 | /* |
| | 42 | * The base intangible object has no presence in any sense, |
| | 43 | * including sight. Subclasses should override these as appropriate |
| | 44 | * for the senses in which the object can be sensed. |
| | 45 | */ |
| | 46 | sightPresence = nil |
| | 47 | soundPresence = nil |
| | 48 | smellPresence = nil |
| | 49 | touchPresence = nil |
| | 50 | |
| | 51 | /* intangibles aren't included in regular room/inventory/contents lists */ |
| | 52 | isListed = nil |
| | 53 | isListedInInventory = nil |
| | 54 | isListedInContents = nil |
| | 55 | |
| | 56 | /* hide intangibles from 'all' for all actions by default */ |
| | 57 | hideFromAll(action) { return true; } |
| | 58 | |
| | 59 | /* don't hide from defaults, though */ |
| | 60 | hideFromDefault(action) { return nil; } |
| | 61 | |
| | 62 | /* |
| | 63 | * Essentially all verbs are meaningless on intangibles. Each |
| | 64 | * subclass should re-enable verbs that are meaningful for that |
| | 65 | * specific type of intangible; to re-enable an action, just define |
| | 66 | * a verify() handler for the action. |
| | 67 | * |
| | 68 | * Note that the verbs we handle via the Default handlers have no |
| | 69 | * preconditions; since these verbs don't do anything anyway, |
| | 70 | * there's no need to apply any preconditions to them. |
| | 71 | */ |
| | 72 | dobjFor(Default) |
| | 73 | { |
| | 74 | preCond = [] |
| | 75 | verify() { illogical(¬WithIntangibleMsg, self); } |
| | 76 | } |
| | 77 | iobjFor(Default) |
| | 78 | { |
| | 79 | preCond = [] |
| | 80 | verify() { illogical(¬WithIntangibleMsg, self); } |
| | 81 | } |
| | 82 | ; |
| | 83 | |
| | 84 | /* |
| | 85 | * A "vaporous" object is a visible but intangible object: something |
| | 86 | * visible, and possibly with an odor and a sound, but not something that |
| | 87 | * can be touched or otherwise physically manipulated. Fire, smoke, and |
| | 88 | * fog are examples of this kind of object. |
| | 89 | */ |
| | 90 | class Vaporous: Intangible |
| | 91 | /* we have a sight presence */ |
| | 92 | sightPresence = true |
| | 93 | |
| | 94 | /* |
| | 95 | * EXAMINE ALL, LISTEN TO ALL, and SMELL ALL apply to us, but hide |
| | 96 | * from ALL for other actions, as not much else makes sense on us |
| | 97 | */ |
| | 98 | hideFromAll(action) |
| | 99 | { |
| | 100 | return !(action.ofKind(ExamineAction) |
| | 101 | || action.ofKind(ListenToAction) |
| | 102 | || action.ofKind(SmellAction)); |
| | 103 | } |
| | 104 | |
| | 105 | /* |
| | 106 | * We can examine, smell, and listen to these objects, as normal for |
| | 107 | * any Thing. To make these verbs work as normal for Thing, we need |
| | 108 | * to explicitly override the corresponding verifiers, so that we |
| | 109 | * bypass the dobjFor(Default) verifier in Intangible. We don't need |
| | 110 | * to do anything special in the overrides, so just inherit the |
| | 111 | * default handling; what's important is that we do override the |
| | 112 | * methods at all. |
| | 113 | */ |
| | 114 | dobjFor(Examine) { verify() { inherited(); } } |
| | 115 | dobjFor(Smell) { verify() { inherited(); } } |
| | 116 | dobjFor(ListenTo) { verify() { inherited(); } } |
| | 117 | |
| | 118 | /* |
| | 119 | * look in, look through, look behind, look under, search: since |
| | 120 | * vaporous objects are usually essentially transparent, these |
| | 121 | * commands reveal nothing interesting |
| | 122 | */ |
| | 123 | lookInDesc { mainReport(&lookInVaporousMsg, self); } |
| | 124 | |
| | 125 | /* |
| | 126 | * downgrade the likelihood of these slightly, and map everything to |
| | 127 | * LOOK IN |
| | 128 | */ |
| | 129 | dobjFor(LookIn) { verify() { logicalRank(70, 'look in vaporous'); } } |
| | 130 | dobjFor(LookThrough) asDobjFor(LookIn) |
| | 131 | dobjFor(LookBehind) asDobjFor(LookIn) |
| | 132 | dobjFor(LookUnder) asDobjFor(LookIn) |
| | 133 | dobjFor(Search) asDobjFor(LookIn) |
| | 134 | |
| | 135 | /* the message we display for commands we disallow */ |
| | 136 | notWithIntangibleMsg = ¬WithVaporousMsg |
| | 137 | ; |
| | 138 | |
| | 139 | |
| | 140 | /* |
| | 141 | * A sensory emanation. This is an intangible object that represents a |
| | 142 | * sound, odor, or the like. |
| | 143 | */ |
| | 144 | class SensoryEmanation: Intangible |
| | 145 | /* |
| | 146 | * Are we currently emanating our sensory information? This can be |
| | 147 | * used as an on/off switch to control when we're active. |
| | 148 | */ |
| | 149 | isEmanating = true |
| | 150 | |
| | 151 | /* |
| | 152 | * The description shown when the *source* is examined (with "listen |
| | 153 | * to", "smell", or whatever verb is appropriate to the type of sense |
| | 154 | * the subclass involves). This will also be appended to the regular |
| | 155 | * "examine" description, if we're not marked as ambient. |
| | 156 | */ |
| | 157 | sourceDesc = "" |
| | 158 | |
| | 159 | /* our description, with and without being able to see the source */ |
| | 160 | descWithSource = "" |
| | 161 | descWithoutSource = "" |
| | 162 | |
| | 163 | /* |
| | 164 | * Our "I am here" message, with and without being able to see the |
| | 165 | * source. These are displayed in room descriptions, inventory |
| | 166 | * descriptions, and by the daemon that schedules background messages |
| | 167 | * for sensory emanations. |
| | 168 | * |
| | 169 | * If different messages are desired as the emanation is mentioned |
| | 170 | * repeatedly while the emanation remains continuously within sense |
| | 171 | * range of the player character ("A phone is ringing", "The phone is |
| | 172 | * still ringing", etc), you can do one of two things. The easier |
| | 173 | * way is to use a Script object; each time we need to show a |
| | 174 | * message, we'll invoke the script. The other way, which is more |
| | 175 | * manual but gives you greater control, is to write a method that |
| | 176 | * checks the displayCount property of self to determine which |
| | 177 | * iteration of the message is being shown. displayCount is set to 1 |
| | 178 | * the first time a message is displayed for the object when the |
| | 179 | * object can first be sensed, and is incremented each we invoke one |
| | 180 | * of these display routines. Note that displayCount resets to nil |
| | 181 | * when the object leaves sense scope, so the sequence of messages |
| | 182 | * will automatically start over each time the object comes back into |
| | 183 | * scope. |
| | 184 | * |
| | 185 | * The manual way (writing a method that checks the displayCount) |
| | 186 | * might be desirable if you want the emanation to fade into the |
| | 187 | * background gradually as the player character stays in the same |
| | 188 | * location repeatedly. This mimics human perception: we notice a |
| | 189 | * noise or odor most when we first hear it, but if it continues for |
| | 190 | * an extended period without changing, we'll eventually stop |
| | 191 | * noticing it. |
| | 192 | */ |
| | 193 | hereWithSource = "" |
| | 194 | hereWithoutSource = "" |
| | 195 | |
| | 196 | /* |
| | 197 | * A message to display when the emanation ceases to be within sense |
| | 198 | * range. In most cases, this displays nothing at all, but some |
| | 199 | * emanations might want to note explicitly when the noise/etc |
| | 200 | * stops. |
| | 201 | */ |
| | 202 | noLongerHere = "" |
| | 203 | |
| | 204 | /* |
| | 205 | * Flag: I'm an "ambient" emanation. This means we essentially are |
| | 206 | * part of the background, and are not worth mentioning in our own |
| | 207 | * right. If this is set to true, then we won't mention this |
| | 208 | * emanation at all when it first becomes reachable in its sense. |
| | 209 | * This should be used for background noises and the like: we won't |
| | 210 | * ever make an unsolicited mention of them, but they'll still show |
| | 211 | * up in explicit 'listen' commands and so on. |
| | 212 | */ |
| | 213 | isAmbient = nil |
| | 214 | |
| | 215 | /* |
| | 216 | * The schedule for displaying messages about the emanation. This |
| | 217 | * is a list of intervals between messages, in game clock times. |
| | 218 | * When the player character can repeatedly sense this emanation for |
| | 219 | * multiple consecutive turns, we'll use this schedule to display |
| | 220 | * messages periodically about the noise/odor/etc. |
| | 221 | * |
| | 222 | * Human sensory perception tends to be "edge-sensitive," which |
| | 223 | * means that we tend to perceive sensory input most acutely when |
| | 224 | * something changes. When a sound or odor is continually present |
| | 225 | * without variation for an extended period, it tends to fade into |
| | 226 | * the background of our awareness, so that even though it remains |
| | 227 | * audible, we gradually stop noticing it. This message display |
| | 228 | * schedule mechanism is meant to approximate this perceptual model |
| | 229 | * by allowing the sensory emanation to specify how noticeable the |
| | 230 | * emanation remains during continuous exposure. Typically, a |
| | 231 | * continuous emanation would have relatively frequent messages |
| | 232 | * (every two turns, say) for a couple of iterations, then would |
| | 233 | * switch to infrequent messages. Emanations that are analogous to |
| | 234 | * white noise would probably not be mentioned at all after the |
| | 235 | * first couple of messages, because the human senses are especially |
| | 236 | * given to treating such input as background. |
| | 237 | * |
| | 238 | * We use this list by applying each interval in the list once and |
| | 239 | * then moving to the next entry in the list. The first entry in |
| | 240 | * the list is the interval between first sensing the emanation and |
| | 241 | * displaying the first "still here" message. When we reach the end |
| | 242 | * of the list, we simply repeat the last interval in the list |
| | 243 | * indefinitely. If the last entry in the list is nil, though, we |
| | 244 | * simply never produce another message. |
| | 245 | */ |
| | 246 | displaySchedule = [nil] |
| | 247 | |
| | 248 | /* |
| | 249 | * Show our "I am here" description. This is the description shown |
| | 250 | * as part of our room's description. We show our hereWithSource or |
| | 251 | * hereWithoutSource message, according to whether or not we can see |
| | 252 | * the source object. |
| | 253 | */ |
| | 254 | emanationHereDesc() |
| | 255 | { |
| | 256 | local actor; |
| | 257 | local prop; |
| | 258 | |
| | 259 | /* if we're not currently emanating, there's nothing to do */ |
| | 260 | if (!isEmanating) |
| | 261 | return; |
| | 262 | |
| | 263 | /* note that we're mentioning the emanation */ |
| | 264 | noteDisplay(); |
| | 265 | |
| | 266 | /* |
| | 267 | * get the actor driving the description - if there's a command |
| | 268 | * active, use the command's actor; otherwise use the player |
| | 269 | * character |
| | 270 | */ |
| | 271 | if ((actor = gActor) == nil) |
| | 272 | actor = gPlayerChar; |
| | 273 | |
| | 274 | /* our display varies according to our source's visibility */ |
| | 275 | prop = (canSeeSource(actor) ? &hereWithSource : &hereWithoutSource); |
| | 276 | |
| | 277 | /* |
| | 278 | * if it's a Script object, invoke the script; otherwise, just |
| | 279 | * invoke the property |
| | 280 | */ |
| | 281 | if (propType(prop) == TypeObject && self.(prop).ofKind(Script)) |
| | 282 | self.(prop).doScript(); |
| | 283 | else |
| | 284 | self.(prop); |
| | 285 | } |
| | 286 | |
| | 287 | /* |
| | 288 | * Show a message describing that we cannot see the source of this |
| | 289 | * emanation because the given obstructor is in the way. This |
| | 290 | * should be overridden for each subclass. |
| | 291 | */ |
| | 292 | cannotSeeSource(obs) { } |
| | 293 | |
| | 294 | /* |
| | 295 | * Get the source of the noise/odor/whatever, as perceived by the |
| | 296 | * current actor. This is the object we appear to be coming from. |
| | 297 | * By default, an emanation is generated by its direct container, |
| | 298 | * and by default this is apparent to actors, so we'll simply return |
| | 299 | * our direct container. |
| | 300 | * |
| | 301 | * If the source is not apparent, this should simply return nil. |
| | 302 | */ |
| | 303 | getSource() { return location; } |
| | 304 | |
| | 305 | /* determine if our source is apparent and visible */ |
| | 306 | canSeeSource(actor) |
| | 307 | { |
| | 308 | local src; |
| | 309 | |
| | 310 | /* get our source */ |
| | 311 | src = getSource(); |
| | 312 | |
| | 313 | /* |
| | 314 | * return true if we have an apparent source, and the apparent |
| | 315 | * source is visible to the current actor |
| | 316 | */ |
| | 317 | return src != nil && actor.canSee(src); |
| | 318 | } |
| | 319 | |
| | 320 | /* |
| | 321 | * Note that we're displaying a message about the emanation. This |
| | 322 | * method should be called any time a message about the emanation is |
| | 323 | * displayed, either by an explicit action or by our background |
| | 324 | * daemon. |
| | 325 | * |
| | 326 | * We'll adjust our next display time so that we wait the full |
| | 327 | * interval at the current point in the display schedule before we |
| | 328 | * show any background message about this object. Note we do not |
| | 329 | * advance through the schedule list; instead, we merely delay any |
| | 330 | * further message by the interval at the current point in the |
| | 331 | * schedule list. |
| | 332 | */ |
| | 333 | noteDisplay() |
| | 334 | { |
| | 335 | /* calculate our next display time */ |
| | 336 | calcNextDisplayTime(); |
| | 337 | |
| | 338 | /* count the display */ |
| | 339 | if (displayCount == nil) |
| | 340 | displayCount = 1; |
| | 341 | else |
| | 342 | ++displayCount; |
| | 343 | } |
| | 344 | |
| | 345 | /* |
| | 346 | * Note an indirect message about the emanation. This can be used |
| | 347 | * when we don't actually display a message ourselves, but another |
| | 348 | * object (usually our source object) describes the emanation; for |
| | 349 | * example, if our source object mentions the noise it's making when |
| | 350 | * it is examined, it should call this method to let us know we have |
| | 351 | * been described indirectly. This method advances our next display |
| | 352 | * time, just as noteDisplay() does, but this method doesn't count |
| | 353 | * the display as a direct display. |
| | 354 | */ |
| | 355 | noteIndirectDisplay() |
| | 356 | { |
| | 357 | /* calculate our next display time */ |
| | 358 | calcNextDisplayTime(); |
| | 359 | } |
| | 360 | |
| | 361 | /* |
| | 362 | * Begin the emanation. This is called from the sense change daemon |
| | 363 | * when the item first becomes noticeable to the player character - |
| | 364 | * for example, when the player character first enters the room |
| | 365 | * containing the emanation, or when the emanation is first |
| | 366 | * activated. |
| | 367 | */ |
| | 368 | startEmanation() |
| | 369 | { |
| | 370 | /* if we're an ambient emanation only, don't mention it */ |
| | 371 | if (isAmbient) |
| | 372 | return; |
| | 373 | |
| | 374 | /* |
| | 375 | * if we've already initialized our scheduling, we must have |
| | 376 | * been explicitly mentioned, such as by a room description - in |
| | 377 | * this case, act as though we're continuing our emanation |
| | 378 | */ |
| | 379 | if (scheduleIndex != nil) |
| | 380 | { |
| | 381 | continueEmanation(); |
| | 382 | return; |
| | 383 | } |
| | 384 | |
| | 385 | /* show our message */ |
| | 386 | emanationHereDesc; |
| | 387 | } |
| | 388 | |
| | 389 | /* |
| | 390 | * Continue the emanation. This is called on each turn in which the |
| | 391 | * emanation remains continuously within sense range of the player |
| | 392 | * character. |
| | 393 | */ |
| | 394 | continueEmanation() |
| | 395 | { |
| | 396 | /* |
| | 397 | * if we are not to run again, our next display time will be set |
| | 398 | * to zero - do nothing in this case |
| | 399 | */ |
| | 400 | if (nextDisplayTime == 0 || nextDisplayTime == nil) |
| | 401 | return; |
| | 402 | |
| | 403 | /* if we haven't yet reached our next display time, do nothing */ |
| | 404 | if (Schedulable.gameClockTime < nextDisplayTime) |
| | 405 | return; |
| | 406 | |
| | 407 | /* |
| | 408 | * Advance to the next schedule interval, if we have one. If |
| | 409 | * we're already on the last schedule entry, simply repeat it |
| | 410 | * forever. |
| | 411 | */ |
| | 412 | if (scheduleIndex < displaySchedule.length()) |
| | 413 | ++scheduleIndex; |
| | 414 | |
| | 415 | /* show our description */ |
| | 416 | emanationHereDesc; |
| | 417 | } |
| | 418 | |
| | 419 | /* |
| | 420 | * End the emanation. This is called when the player character can |
| | 421 | * no longer sense the emanation. |
| | 422 | */ |
| | 423 | endEmanation() |
| | 424 | { |
| | 425 | /* show our "no longer here" message */ |
| | 426 | noLongerHere; |
| | 427 | |
| | 428 | /* uninitialize the display scheduling */ |
| | 429 | scheduleIndex = nil; |
| | 430 | nextDisplayTime = nil; |
| | 431 | |
| | 432 | /* reset the display count */ |
| | 433 | displayCount = nil; |
| | 434 | } |
| | 435 | |
| | 436 | /* |
| | 437 | * Calculate our next display time. The caller must set our |
| | 438 | * scheduleIndex to the correct index prior to calling this. |
| | 439 | */ |
| | 440 | calcNextDisplayTime() |
| | 441 | { |
| | 442 | local delta; |
| | 443 | |
| | 444 | /* if our scheduling isn't initialized, set it up now */ |
| | 445 | if (scheduleIndex == nil) |
| | 446 | { |
| | 447 | /* start at the first display schedule interval */ |
| | 448 | scheduleIndex = 1; |
| | 449 | } |
| | 450 | |
| | 451 | /* get the next display interval from the schedule list */ |
| | 452 | delta = displaySchedule[scheduleIndex]; |
| | 453 | |
| | 454 | /* |
| | 455 | * if the current display interval is nil, it means that we're |
| | 456 | * never to display another message |
| | 457 | */ |
| | 458 | if (delta == nil) |
| | 459 | { |
| | 460 | /* |
| | 461 | * we're not to display again - simply set the next display |
| | 462 | * time to zero and return |
| | 463 | */ |
| | 464 | nextDisplayTime = 0; |
| | 465 | return; |
| | 466 | } |
| | 467 | |
| | 468 | /* |
| | 469 | * our next display time is the current game clock time plus the |
| | 470 | * interval |
| | 471 | */ |
| | 472 | nextDisplayTime = Schedulable.gameClockTime + delta; |
| | 473 | } |
| | 474 | |
| | 475 | /* |
| | 476 | * Internal counters that keep track of our display scheduling. |
| | 477 | * scheduleIndex is the index in the displaySchedule list of the |
| | 478 | * interval we're waiting to expire; nextDisplayTime is the game |
| | 479 | * clock time of our next display. noiseList and odorList are lists |
| | 480 | * of senseInfo entries for the sound and smell senses, |
| | 481 | * respectively, indicating which objects were within sense range on |
| | 482 | * the last turn. displayCount is the number of times in a row |
| | 483 | * we've displayed a message already. |
| | 484 | */ |
| | 485 | scheduleIndex = nil |
| | 486 | nextDisplayTime = nil |
| | 487 | noiseList = nil |
| | 488 | odorList = nil |
| | 489 | displayCount = nil |
| | 490 | |
| | 491 | /* |
| | 492 | * Class method implementing the sensory change daemon. This runs |
| | 493 | * on each turn to check for changes in the set of objects the |
| | 494 | * player can hear and smell, and to generate "still here" messages |
| | 495 | * for objects continuously within sense range for multiple turns. |
| | 496 | */ |
| | 497 | noteSenseChanges() |
| | 498 | { |
| | 499 | /* emanations don't change anything, so turn on caching */ |
| | 500 | libGlobal.enableSenseCache(); |
| | 501 | |
| | 502 | /* note sound changes */ |
| | 503 | noteSenseChangesFor(sound, &noiseList, Noise); |
| | 504 | |
| | 505 | /* note odor changes */ |
| | 506 | noteSenseChangesFor(smell, &odorList, Odor); |
| | 507 | |
| | 508 | /* done with sense caching */ |
| | 509 | libGlobal.disableSenseCache(); |
| | 510 | } |
| | 511 | |
| | 512 | /* |
| | 513 | * Note sense changes for a particular sense. 'listProp' is the |
| | 514 | * property of SensoryEmanation giving the list of SenseInfo entries |
| | 515 | * for the sense on the previous turn. 'sub' is a subclass of ours |
| | 516 | * (such as Noise) giving the type of sensory emanation used for |
| | 517 | * this sense. |
| | 518 | */ |
| | 519 | noteSenseChangesFor(sense, listProp, sub) |
| | 520 | { |
| | 521 | local newInfo; |
| | 522 | local oldInfo; |
| | 523 | |
| | 524 | /* get the old table of SenseInfo entries for the sense */ |
| | 525 | oldInfo = self.(listProp); |
| | 526 | |
| | 527 | /* |
| | 528 | * Get the new table of items we can reach in the given sense, |
| | 529 | * and reduce it to include only emanations of the subclass of |
| | 530 | * interest. |
| | 531 | */ |
| | 532 | newInfo = gPlayerChar.senseInfoTable(sense); |
| | 533 | newInfo.forEachAssoc(new function(obj, info) |
| | 534 | { |
| | 535 | /* |
| | 536 | * remove this item if it's not of the subclass of interest, |
| | 537 | * or if it's not currently emanating |
| | 538 | */ |
| | 539 | if (!obj.ofKind(sub) || !obj.isEmanating) |
| | 540 | newInfo.removeElement(obj); |
| | 541 | }); |
| | 542 | |
| | 543 | /* run through the new list and note each change */ |
| | 544 | newInfo.forEachAssoc(new function(obj, info) |
| | 545 | { |
| | 546 | /* treat this as a new command visually */ |
| | 547 | "<.commandsep>"; |
| | 548 | |
| | 549 | /* |
| | 550 | * Check to see whether the item is starting anew or was |
| | 551 | * already here on the last turn. If the item was in our |
| | 552 | * list from the previous turn, it was already here. |
| | 553 | */ |
| | 554 | if (oldInfo == nil || oldInfo[obj] == nil) |
| | 555 | { |
| | 556 | /* |
| | 557 | * the item wasn't in sense range on the last turn, so |
| | 558 | * it is becoming newly noticeable |
| | 559 | */ |
| | 560 | obj.startEmanation(); |
| | 561 | } |
| | 562 | else |
| | 563 | { |
| | 564 | /* the item was already here - continue its emanation */ |
| | 565 | obj.continueEmanation(); |
| | 566 | } |
| | 567 | }); |
| | 568 | |
| | 569 | /* run through the old list and note each item no longer sensed */ |
| | 570 | if (oldInfo != nil) |
| | 571 | { |
| | 572 | oldInfo.forEachAssoc(new function(obj, info) |
| | 573 | { |
| | 574 | /* if this item isn't in the new list, note its departure */ |
| | 575 | if (newInfo[obj] == nil) |
| | 576 | { |
| | 577 | /* treat this as a new command visually */ |
| | 578 | "<.commandsep>"; |
| | 579 | |
| | 580 | /* note the departure */ |
| | 581 | obj.endEmanation(); |
| | 582 | } |
| | 583 | }); |
| | 584 | } |
| | 585 | |
| | 586 | /* store the current list for comparison the next time we run */ |
| | 587 | self.(listProp) = newInfo; |
| | 588 | } |
| | 589 | |
| | 590 | /* |
| | 591 | * Examine the sensory emanation. We'll show our descWithSource or |
| | 592 | * descWithoutSource, according to whether or not we can see the |
| | 593 | * source object. |
| | 594 | */ |
| | 595 | dobjFor(Examine) |
| | 596 | { |
| | 597 | verify() { inherited(); } |
| | 598 | action() |
| | 599 | { |
| | 600 | /* note that we're displaying a message about us */ |
| | 601 | noteDisplay(); |
| | 602 | |
| | 603 | /* display our sound description */ |
| | 604 | if (canSeeSource(gActor)) |
| | 605 | { |
| | 606 | /* we can see the source */ |
| | 607 | descWithSource; |
| | 608 | } |
| | 609 | else |
| | 610 | { |
| | 611 | local src; |
| | 612 | |
| | 613 | /* show the unseen-source version of the description */ |
| | 614 | descWithoutSource; |
| | 615 | |
| | 616 | /* |
| | 617 | * If we have a source, find out what's keeping us from |
| | 618 | * seeing the source; in other words, find the opaque |
| | 619 | * visual obstructor on the sense path to the source. |
| | 620 | */ |
| | 621 | if ((src = getSource()) != nil) |
| | 622 | { |
| | 623 | local obs; |
| | 624 | |
| | 625 | /* get the visual obstructor */ |
| | 626 | obs = gActor.findVisualObstructor(src); |
| | 627 | |
| | 628 | /* |
| | 629 | * If we found an obstructor, and we can see it, add |
| | 630 | * a message describing the obstruction. If we |
| | 631 | * can't see the obstructor, we can't localize the |
| | 632 | * sensory emanation at all. |
| | 633 | */ |
| | 634 | if (obs != nil && gActor.canSee(obs)) |
| | 635 | cannotSeeSource(obs); |
| | 636 | } |
| | 637 | } |
| | 638 | } |
| | 639 | } |
| | 640 | ; |
| | 641 | |
| | 642 | /* |
| | 643 | * Noise - this is an intangible object representing a sound. |
| | 644 | * |
| | 645 | * A Noise object is generally placed directly within the object that is |
| | 646 | * generating the noise. This will ensure that the noise is |
| | 647 | * automatically in scope whenever the object is in scope (or, more |
| | 648 | * precisely, whenever the object's contents are in scope) and with the |
| | 649 | * same sense attributes. |
| | 650 | * |
| | 651 | * By default, when a noise is specifically examined via "listen to", |
| | 652 | * and the container is visible, we'll mention that the noise is coming |
| | 653 | * from the container. |
| | 654 | */ |
| | 655 | class Noise: SensoryEmanation |
| | 656 | /* |
| | 657 | * by default, we have a definite presence in the sound sense if |
| | 658 | * we're emanating our noise |
| | 659 | */ |
| | 660 | soundPresence = (isEmanating) |
| | 661 | |
| | 662 | /* |
| | 663 | * By default, a noise is listed in a room description (i.e., on LOOK |
| | 664 | * or entry to a room) unless it's an ambient background noise.. Set |
| | 665 | * this to nil to omit the noise from the room description, while |
| | 666 | * still allowing it to be heard in an explicit LISTEN command. |
| | 667 | */ |
| | 668 | isSoundListedInRoom = (!isAmbient && isEmanating) |
| | 669 | |
| | 670 | /* show our description as part of a room description */ |
| | 671 | soundHereDesc() { emanationHereDesc(); } |
| | 672 | |
| | 673 | /* explain that we can't see the source because of the obstructor */ |
| | 674 | cannotSeeSource(obs) { obs.cannotSeeSoundSource(self); } |
| | 675 | |
| | 676 | /* treat "listen to" the same as "examine" */ |
| | 677 | dobjFor(ListenTo) asDobjFor(Examine) |
| | 678 | |
| | 679 | /* "examine" requires that the object is audible */ |
| | 680 | dobjFor(Examine) |
| | 681 | { |
| | 682 | preCond = [objAudible] |
| | 683 | } |
| | 684 | ; |
| | 685 | |
| | 686 | /* |
| | 687 | * Odor - this is an intangible object representing an odor. |
| | 688 | */ |
| | 689 | class Odor: SensoryEmanation |
| | 690 | /* |
| | 691 | * by default, we have a definite presence in the smell sense if |
| | 692 | * we're currently emanating our odor |
| | 693 | */ |
| | 694 | smellPresence = (isEmanating) |
| | 695 | |
| | 696 | /* |
| | 697 | * By default, an odor is listed in a room description (i.e., on LOOK |
| | 698 | * or entry to a room) unless it's an ambient background odor. Set |
| | 699 | * this to nil to omit the odor from the room description, while |
| | 700 | * still allowing it to be listed in an explicit SMELL command. |
| | 701 | */ |
| | 702 | isSmellListedInRoom = (!isAmbient && isEmanating) |
| | 703 | |
| | 704 | /* mention the odor as part of a room description */ |
| | 705 | smellHereDesc() { emanationHereDesc(); } |
| | 706 | |
| | 707 | /* explain that we can't see the source because of the obstructor */ |
| | 708 | cannotSeeSource(obs) { obs.cannotSeeSmellSource(self); } |
| | 709 | |
| | 710 | /* handle "smell" using our "examine" handler */ |
| | 711 | dobjFor(Smell) asDobjFor(Examine) |
| | 712 | |
| | 713 | /* "examine" requires that the object is smellable */ |
| | 714 | dobjFor(Examine) |
| | 715 | { |
| | 716 | preCond = [objSmellable] |
| | 717 | } |
| | 718 | ; |
| | 719 | |
| | 720 | /* |
| | 721 | * SimpleNoise is for cases where a noise is an ongoing part of a |
| | 722 | * location, so (1) it's not necessary to distinguish source and |
| | 723 | * sourceless versions of the description, and (2) there are no |
| | 724 | * scheduled reports for the noise. For these cases, all of the |
| | 725 | * messages default to the basic 'desc' property. Note that we make |
| | 726 | * this type of noise "ambient" by default, which means that we won't |
| | 727 | * automatically include it in room descriptions. |
| | 728 | */ |
| | 729 | class SimpleNoise: Noise |
| | 730 | isAmbient = true |
| | 731 | sourceDesc { desc; } |
| | 732 | descWithSource { desc; } |
| | 733 | descWithoutSource { desc; } |
| | 734 | hereWithSource { desc; } |
| | 735 | hereWithoutSource { desc; } |
| | 736 | ; |
| | 737 | |
| | 738 | /* SimpleOdor is the olfactory equivalent of SimpleNoise */ |
| | 739 | class SimpleOdor: Odor |
| | 740 | isAmbient = true |
| | 741 | sourceDesc { desc; } |
| | 742 | descWithSource { desc; } |
| | 743 | descWithoutSource { desc; } |
| | 744 | hereWithSource { desc; } |
| | 745 | hereWithoutSource { desc; } |
| | 746 | ; |
| | 747 | |
| | 748 | /* ------------------------------------------------------------------------ */ |
| | 749 | /* |
| | 750 | * Sensory Event. This is an object representing a transient event, |
| | 751 | * such as a sound, visual display, or odor, to which some objects |
| | 752 | * observing the event might react. |
| | 753 | * |
| | 754 | * A sensory event differs from a sensory emanation in that an emanation |
| | 755 | * is ongoing and passive, while an event is isolated in time and |
| | 756 | * actively notifies observers. |
| | 757 | */ |
| | 758 | class SensoryEvent: object |
| | 759 | /* |
| | 760 | * Trigger the event. This routine must be called at the time when |
| | 761 | * the event is to occur. We'll notify every interested observer |
| | 762 | * capable of sensing the event that the event is occurring, so |
| | 763 | * observers can take appropriate action in response to the event. |
| | 764 | * |
| | 765 | * 'source' is the source object - this is the physical object in |
| | 766 | * the simulation that is causing the event. For example, if the |
| | 767 | * event is the sound of a phone ringing, the phone would probably |
| | 768 | * be the source object. The source is used to determine which |
| | 769 | * observers are capable of detecting the event: an observer must be |
| | 770 | * able to sense the source object in the appropriate sense to be |
| | 771 | * notified of the event. |
| | 772 | */ |
| | 773 | triggerEvent(source) |
| | 774 | { |
| | 775 | /* |
| | 776 | * Run through all objects connected to the source object by |
| | 777 | * containment, and notify any that are interested and can |
| | 778 | * detect the event. Containment is the only way sense |
| | 779 | * information can propagate, so we can limit our search |
| | 780 | * accordingly. |
| | 781 | * |
| | 782 | * Connection by containment is no guarantee of a sense |
| | 783 | * connection: it's a necessary, but not sufficient, condition. |
| | 784 | * Because it's a necessary condition, though, we can use it to |
| | 785 | * limit the number of objects we have to test with a more |
| | 786 | * expensive sense path calculation. |
| | 787 | */ |
| | 788 | source.connectionTable().forEachAssoc(new function(cur, val) |
| | 789 | { |
| | 790 | /* |
| | 791 | * If this object defines the observer notification method, |
| | 792 | * then it might be interested in the event. If the object |
| | 793 | * doesn't define this method, then there's no way it could |
| | 794 | * be interested. (We make this test before checking the |
| | 795 | * sense path because checking to see if an object defines a |
| | 796 | * property is fast and simple, while the sense path |
| | 797 | * calculation could be expensive.) |
| | 798 | */ |
| | 799 | if (cur.propDefined(notifyProp, PropDefAny)) |
| | 800 | { |
| | 801 | local info; |
| | 802 | |
| | 803 | /* |
| | 804 | * This object might be interested in the event, so |
| | 805 | * check to see if the object can sense the event. If |
| | 806 | * this object can sense the source object at all (i.e., |
| | 807 | * the sense path isn't 'opaque'), then notify the |
| | 808 | * object of the event. |
| | 809 | */ |
| | 810 | info = cur.senseObj(sense, source); |
| | 811 | if (info.trans != opaque) |
| | 812 | { |
| | 813 | /* |
| | 814 | * this observer object can sense the source of the |
| | 815 | * event, so notify it of the event |
| | 816 | */ |
| | 817 | cur.(notifyProp)(self, source, info); |
| | 818 | } |
| | 819 | } |
| | 820 | }); |
| | 821 | } |
| | 822 | |
| | 823 | /* the sense in which the event is observable */ |
| | 824 | sense = nil |
| | 825 | |
| | 826 | /* |
| | 827 | * the notification property - this is the property we'll invoke on |
| | 828 | * each observer to notify it of the event |
| | 829 | */ |
| | 830 | notifyProp = nil |
| | 831 | ; |
| | 832 | |
| | 833 | /* |
| | 834 | * Visual event |
| | 835 | */ |
| | 836 | class SightEvent: SensoryEvent |
| | 837 | sense = sight |
| | 838 | notifyProp = ¬ifySightEvent |
| | 839 | ; |
| | 840 | |
| | 841 | /* |
| | 842 | * Visual event observer. This is a mix-in that can be added to any |
| | 843 | * other classes. |
| | 844 | */ |
| | 845 | class SightObserver: object |
| | 846 | /* |
| | 847 | * Receive notification of a sight event. This routine is called |
| | 848 | * whenever a SightEvent occurs within view of this object. |
| | 849 | * |
| | 850 | * 'event' is the SightEvent object; 'source' is the physical |
| | 851 | * simulation object that is making the visual display; and 'info' |
| | 852 | * is a SenseInfo object describing the viewing conditions from this |
| | 853 | * object to the source object. |
| | 854 | */ |
| | 855 | notifySightEvent(event, source, info) { } |
| | 856 | ; |
| | 857 | |
| | 858 | /* |
| | 859 | * Sound event |
| | 860 | */ |
| | 861 | class SoundEvent: SensoryEvent |
| | 862 | sense = sound |
| | 863 | notifyProp = ¬ifySoundEvent |
| | 864 | ; |
| | 865 | |
| | 866 | /* |
| | 867 | * Sound event observer. This is a mix-in that can be added to any |
| | 868 | * other classes. |
| | 869 | */ |
| | 870 | class SoundObserver: object |
| | 871 | /* |
| | 872 | * Receive notification of a sound event. This routine is called |
| | 873 | * whenever a SoundEvent occurs within hearing range of this object. |
| | 874 | */ |
| | 875 | notifySoundEvent(event, source, info) { } |
| | 876 | ; |
| | 877 | |
| | 878 | /* |
| | 879 | * Smell event |
| | 880 | */ |
| | 881 | class SmellEvent: SensoryEvent |
| | 882 | sense = smell |
| | 883 | notifyProp = ¬ifySmellEvent |
| | 884 | ; |
| | 885 | |
| | 886 | /* |
| | 887 | * Smell event observer. This is a mix-in that can be added to any |
| | 888 | * other classes. |
| | 889 | */ |
| | 890 | class SmellObserver: object |
| | 891 | /* |
| | 892 | * Receive notification of a smell event. This routine is called |
| | 893 | * whenever a SmellEvent occurs within smelling range of this |
| | 894 | * object. |
| | 895 | */ |
| | 896 | notifySmellEvent(event, source, info) { } |
| | 897 | ; |
| | 898 | |
| | 899 | |
| | 900 | /* ------------------------------------------------------------------------ */ |
| | 901 | /* |
| | 902 | * Hidden - this is an object that's present but not visible to any |
| | 903 | * actors. The object will simply not be visible in the 'sight' sense |
| | 904 | * until discovered. |
| | 905 | */ |
| | 906 | class Hidden: Thing |
| | 907 | /* we can't be seen until discovered */ |
| | 908 | canBeSensed(sense, trans, ambient) |
| | 909 | { |
| | 910 | /* |
| | 911 | * If the sense is sight, and we haven't been discovered yet, we |
| | 912 | * cannot be sensed. Otherwise, inherit the normal handling. |
| | 913 | */ |
| | 914 | if (sense == sight && !discovered) |
| | 915 | return nil; |
| | 916 | else |
| | 917 | return inherited(sense, trans, ambient); |
| | 918 | } |
| | 919 | |
| | 920 | /* |
| | 921 | * Have we been discovered yet? |
| | 922 | * |
| | 923 | * Note that this should be a simple property value, not a method. |
| | 924 | * It's risky to make this a method because it's evaluated from |
| | 925 | * within some of the low-level scope/sense calculations, and those |
| | 926 | * calculations depend upon certain global variables. If you make |
| | 927 | * this property into a method, you could indirectly call another |
| | 928 | * method that changes some of the same globals, which could disrupt |
| | 929 | * the main scope/sense calculations and cause other, seemingly |
| | 930 | * unrelated objects to mysteriously appear or disappear at the wrong |
| | 931 | * times. If you need to calculate this value dynamically, you could |
| | 932 | * explicitly assign the property a new value in something like a |
| | 933 | * daemon or an afterAction() method. |
| | 934 | * |
| | 935 | * (The warning above is a bit more conservative than is strictly |
| | 936 | * necessary. It actually is safe to make 'discovered' a method, |
| | 937 | * *provided* that the method doesn't ever call anything that's |
| | 938 | * involved in the scope/sense calculations. For example, never call |
| | 939 | * methods like senseObj(), senseAmbientMax(), or |
| | 940 | * sensePresenceList(), or anything that calls those. In most cases, |
| | 941 | * it's safe to call non-sense-related methods, like isOpen() or |
| | 942 | * isIn().) |
| | 943 | */ |
| | 944 | discovered = nil |
| | 945 | |
| | 946 | /* mark the object as discovered */ |
| | 947 | discover() |
| | 948 | { |
| | 949 | local pc; |
| | 950 | |
| | 951 | /* note that we've been discovered */ |
| | 952 | discovered = true; |
| | 953 | |
| | 954 | /* mark me and my contents as having been seen */ |
| | 955 | if ((pc = gPlayerChar).canSee(self)) |
| | 956 | { |
| | 957 | /* mark me as seen */ |
| | 958 | pc.setHasSeen(self); |
| | 959 | |
| | 960 | /* mark my visible contents as see */ |
| | 961 | setContentsSeenBy(pc.visibleInfoTable(), pc); |
| | 962 | } |
| | 963 | } |
| | 964 | ; |
| | 965 | |
| | 966 | |
| | 967 | /* ------------------------------------------------------------------------ */ |
| | 968 | /* |
| | 969 | * Collective - this is an object that can be used to refer to a group of |
| | 970 | * other (usually equivalent) objects collectively. In most cases, this |
| | 971 | * object will be a separate game object that contains or can contain the |
| | 972 | * individuals: a bag of marbles can be a collective for the marbles, or |
| | 973 | * a book of matches can be a collective for the matchsticks. |
| | 974 | * |
| | 975 | * A collective object is usually given the same plural vocabulary as its |
| | 976 | * individuals. When we use that plural vocabulary, we will filter for |
| | 977 | * or against the collective, as determined by the noun phrase |
| | 978 | * production, when the player uses the collective term. |
| | 979 | * |
| | 980 | * This is a mix-in class, intended to be used along with other (usually |
| | 981 | * Thing-derived) superclasses. |
| | 982 | */ |
| | 983 | class Collective: object |
| | 984 | filterResolveList(lst, action, whichObj, np, requiredNum) |
| | 985 | { |
| | 986 | /* scan for my matching individuals */ |
| | 987 | foreach (local cur in lst) |
| | 988 | { |
| | 989 | /* if this one's a matching individual, decide what to do */ |
| | 990 | if (isCollectiveFor(cur.obj_)) |
| | 991 | { |
| | 992 | /* |
| | 993 | * We're a collective for this object. If the noun |
| | 994 | * phrase production wants us to filter for collectives, |
| | 995 | * remove the individual and keep me (the collective); |
| | 996 | * otherwise, keep the individual and remove me. |
| | 997 | */ |
| | 998 | if (np.filterForCollectives) |
| | 999 | { |
| | 1000 | /* |
| | 1001 | * we want to keep the collective, so remove this |
| | 1002 | * individual item |
| | 1003 | */ |
| | 1004 | lst -= cur; |
| | 1005 | } |
| | 1006 | else |
| | 1007 | { |
| | 1008 | /* |
| | 1009 | * we want to keep individuals, so remove the |
| | 1010 | * collective (i.e., myself) |
| | 1011 | */ |
| | 1012 | lst -= lst.valWhich({x: x.obj_ == self}); |
| | 1013 | |
| | 1014 | /* |
| | 1015 | * we can only be in the list once, so there's no |
| | 1016 | * need to keep looking - if we found another item |
| | 1017 | * for which we're a collective, all we'd do is try |
| | 1018 | * to remove myself again, which would be pointless |
| | 1019 | * since I'm already gone |
| | 1020 | */ |
| | 1021 | break; |
| | 1022 | } |
| | 1023 | } |
| | 1024 | } |
| | 1025 | |
| | 1026 | /* return the result */ |
| | 1027 | return lst; |
| | 1028 | } |
| | 1029 | |
| | 1030 | /* |
| | 1031 | * Determine if I'm a collective object for the given object. |
| | 1032 | * |
| | 1033 | * In order to be a collective for some objects, an object must have |
| | 1034 | * vocubulary for the plural name, and must return true from this |
| | 1035 | * method for the collected objects. |
| | 1036 | */ |
| | 1037 | isCollectiveFor(obj) { return nil; } |
| | 1038 | ; |
| | 1039 | |
| | 1040 | /* |
| | 1041 | * A "collective group" object. This is an abstract object: the player |
| | 1042 | * doesn't think of this as a physically separate object, but rather as a |
| | 1043 | * collection of a bunch of individual objects. For example, if you had |
| | 1044 | * a group of floor-number buttons in an elevator, you might create a |
| | 1045 | * CollectiveGroup to represent the buttons as a collection - from the |
| | 1046 | * player's perspective, there's not a separate physical object called |
| | 1047 | * "the buttons," but it might nonetheless be handy to refer to "the |
| | 1048 | * buttons" collectively as a single entity in commands. CollectiveGroup |
| | 1049 | * is designed for such situations. |
| | 1050 | * |
| | 1051 | * There are two ways to use CollectiveGroup: as a non-physical, |
| | 1052 | * non-simulation object whose only purpose is to field a few specific |
| | 1053 | * commands; or as a physical simulation object that shows up separately |
| | 1054 | * as an object in its own right. |
| | 1055 | * |
| | 1056 | * First: you can use a CollectiveGroup as a non-physical object, which |
| | 1057 | * essentially means it has a nil 'location'. The group object doesn't |
| | 1058 | * actually appear in any location. Instead, it'll be brought into the |
| | 1059 | * sensory system automatically by its individuals, and it'll have the |
| | 1060 | * same effective sensory status as the most visible/audible/etc of its |
| | 1061 | * individuals. This choice is appropriate when the individuals are |
| | 1062 | * mobile, so they might be scattered around the game map, hence the |
| | 1063 | * group object might need to be invoked anywhere. With this option, you |
| | 1064 | * normally won't want to make the CollectiveGroup handle very many |
| | 1065 | * commands, because you'll have to completely customize each command you |
| | 1066 | * want it to handle, in order to properly account for the possible |
| | 1067 | * scattering of the individuals. For example, if you want the group |
| | 1068 | * object to handle the TAKE command, you'll have to figure out which |
| | 1069 | * individuals are in reach, and specially program the procedure for |
| | 1070 | * taking each of the available individuals. |
| | 1071 | * |
| | 1072 | * Second: you can use CollectiveGroup as a simulation object, and |
| | 1073 | * actually set its 'location' to the location of its individuals. The |
| | 1074 | * group object in this case shows up in the simulation alongside its |
| | 1075 | * individuals. This is a good choice if the individuals are fixed in |
| | 1076 | * place, all in one place, because you can simply put the group object |
| | 1077 | * in the same location as the individuals without worrying that the |
| | 1078 | * individuals will move around the game later on. This is much easier |
| | 1079 | * to handle than the first case above, mostly because commands that |
| | 1080 | * physically manipulate the individuals (such as TAKE) aren't a factor. |
| | 1081 | * In this set-up, you can easily let the group object handle many |
| | 1082 | * actions, since it won't have to do much apart from showing the default |
| | 1083 | * failure messages that a Fixed would generate in any other situation. |
| | 1084 | * Note that if you use this approach, the CollectiveGroup should *also* |
| | 1085 | * inherit from Fixture or the like, so that the group object is fixed in |
| | 1086 | * place just like its corresponding individuals. |
| | 1087 | * |
| | 1088 | * The parser will substitute a CollectiveGroup object for its |
| | 1089 | * individuals when (1) any of the individuals are in scope, (2) the |
| | 1090 | * CollectiveGroup has vocabulary that matches a noun phrase in the |
| | 1091 | * player's input, and (3) the conditions for substitution, defined by |
| | 1092 | * isCollectiveQuant and isCollectiveAction, are met. |
| | 1093 | * |
| | 1094 | * (The substitution itself is handled in two steps. First, an |
| | 1095 | * individual will add the group object to the sense connection list |
| | 1096 | * whenever the individual is in the connection list, which will bring |
| | 1097 | * the object into scope, so the parser will be able to match the |
| | 1098 | * vocabulary from the group object any time an individual is in scope. |
| | 1099 | * Once the group object is matched, its filterResolveList method will |
| | 1100 | * throw out either the group object or all of the individuals, depending |
| | 1101 | * on whether or not the isCollectiveQuant and isCollectiveAction tests |
| | 1102 | * are met.) |
| | 1103 | * |
| | 1104 | * For example, we might have a bunch of coins and paper bills in a game, |
| | 1105 | * and give them all a plural word 'money'. We then also create a |
| | 1106 | * collective group object with plural word 'money'. We set the |
| | 1107 | * collectiveGroup property of each coin and bill object to refer to the |
| | 1108 | * collective group object. Whenever the player uses 'money' in a |
| | 1109 | * command, the individual coins and bills will initially match, and the |
| | 1110 | * group object will also match. The group object will then either throw |
| | 1111 | * itself out, keeping only the individuals, or will throw out the |
| | 1112 | * individuals. If the group object decides to field the command, it |
| | 1113 | * will be the only matching object, so a command like "examine money" |
| | 1114 | * will be directed to the single collective group object, rather than |
| | 1115 | * directed to the matching individuals one at a time. This allows the |
| | 1116 | * game to present simpler, more elegant responses to commands on the |
| | 1117 | * individuals as a group. |
| | 1118 | * |
| | 1119 | * By default, the only action we handle is Examine. Each instance must |
| | 1120 | * provide a suitable description so that when the collective is |
| | 1121 | * examined, we describe the group of individuals appropriately. |
| | 1122 | */ |
| | 1123 | class CollectiveGroup: Thing |
| | 1124 | /* collective group objects are usually named in plural terms */ |
| | 1125 | isPlural = true |
| | 1126 | |
| | 1127 | /* |
| | 1128 | * Filter a noun phrase resolution list. |
| | 1129 | * |
| | 1130 | * If there are any objects in the resolution list for which we're a |
| | 1131 | * collective, we'll check to see whether we want to the collective |
| | 1132 | * or keep the individuals. We want to keep the collective if the |
| | 1133 | * action is one we can handle collectively; otherwise, we want to |
| | 1134 | * drop the collective and let the individuals handle the action |
| | 1135 | * instead. |
| | 1136 | * |
| | 1137 | * Note that, when any of our individuals are in scope, we're in |
| | 1138 | * scope. This means that the collective is always in the |
| | 1139 | * resolution list, along with the individuals, if (1) any |
| | 1140 | * individuals are in scope, and (2) the vocabulary used in the noun |
| | 1141 | * phrase matches the collective object. If the vocabulary doesn't |
| | 1142 | * match the collective, the parser simply won't include the |
| | 1143 | * collective in the resolution list by virtue of the normal |
| | 1144 | * vocabulary selection mechanism, so we'll never reach this point. |
| | 1145 | * |
| | 1146 | * By default, the collective object will be ignored if a specific |
| | 1147 | * number of objects is required. When the player explicitly |
| | 1148 | * specifies a quantity (by a phrase like "the five coins" or "both |
| | 1149 | * coins"), we'll assume they want to iterate over individuals |
| | 1150 | * rather than operate on the collection. |
| | 1151 | */ |
| | 1152 | filterResolveList(lst, action, whichObj, np, requiredNum) |
| | 1153 | { |
| | 1154 | /* |
| | 1155 | * If we want to use the collective for the current action and |
| | 1156 | * the required quantity, keep the collective; otherwise, if |
| | 1157 | * there are any individuals, keep the individuals and filter |
| | 1158 | * out the collective group. If there are no matching |
| | 1159 | * individuals, keep the collective group object, since there's |
| | 1160 | * nothing to replace it. |
| | 1161 | */ |
| | 1162 | if (isCollectiveQuant(np, requiredNum) |
| | 1163 | && isCollectiveAction(action, whichObj)) |
| | 1164 | { |
| | 1165 | /* |
| | 1166 | * We can handle the action collectively, so keep myself, and |
| | 1167 | * get rid of the individuals. We want to discard the |
| | 1168 | * individuals because we want the entire action to be |
| | 1169 | * handled by the collective object, rather than iterating |
| | 1170 | * over the individuals. So, discard each object that has |
| | 1171 | * 'self' as a collectiveGroup (which is to say, keep each |
| | 1172 | * object that *doesn't* have collectiveGroup 'self'). |
| | 1173 | */ |
| | 1174 | lst = lst.subset({x: !x.obj_.hasCollectiveGroup(self)}); |
| | 1175 | } |
| | 1176 | else if (lst.indexWhich({x: x.obj_.hasCollectiveGroup(self)}) != nil) |
| | 1177 | { |
| | 1178 | /* |
| | 1179 | * We can't handle the action collectively, and the list |
| | 1180 | * includes at least one of our individuals, so let the |
| | 1181 | * individuals handle it. Simply remove myself from the |
| | 1182 | * list. |
| | 1183 | */ |
| | 1184 | lst = lst.removeElementAt(lst.indexWhich({x: x.obj_ == self})); |
| | 1185 | } |
| | 1186 | |
| | 1187 | /* return the updated list */ |
| | 1188 | return lst; |
| | 1189 | } |
| | 1190 | |
| | 1191 | /* |
| | 1192 | * "Unfilter" a pronoun antecedent list. We'll restore the |
| | 1193 | * individuals to the list so that we can choose anew, for the new |
| | 1194 | * command, whether to select the group object or the individuals. |
| | 1195 | * |
| | 1196 | * For example, suppose there's a CollectiveGroup for a set of |
| | 1197 | * elevator buttons that handles the Examine command, but no other |
| | 1198 | * commands. Now suppose the player types in these commands: |
| | 1199 | * |
| | 1200 | *. >examine buttons |
| | 1201 | *. >push them |
| | 1202 | * |
| | 1203 | * On the first command, the CollectiveGroup object will filter out |
| | 1204 | * the individual buttons in filterResolveList, because the group |
| | 1205 | * object handles the Examine command on behalf of the individuals. |
| | 1206 | * This will set the pronoun antecedent for IT and THEM to the group |
| | 1207 | * object, because that's the program object that handled the |
| | 1208 | * action. On the second command, if the player had typed simply |
| | 1209 | * PUSH BUTTONS, the collective group object would have filtered |
| | 1210 | * *itself* out, keeping the individuals. However, the raw pronoun |
| | 1211 | * binding for THEM is the group object; if we did nothing to change |
| | 1212 | * this, we'd get a different response for PUSH THEM than we'd get |
| | 1213 | * for PUSH BUTTONS. That's where this routine comes in: by |
| | 1214 | * restoring the individuals, we let filterResolveList() make the |
| | 1215 | * decision about what to keep anew for the pronoun. |
| | 1216 | */ |
| | 1217 | expandPronounList(typ, lst) |
| | 1218 | { |
| | 1219 | /* restore our individuals to the list */ |
| | 1220 | forEachInstance(Thing, new function(obj) { |
| | 1221 | if (obj.hasCollectiveGroup(self)) |
| | 1222 | lst += obj; |
| | 1223 | }); |
| | 1224 | |
| | 1225 | /* return the list */ |
| | 1226 | return lst; |
| | 1227 | } |
| | 1228 | |
| | 1229 | /* |
| | 1230 | * Check the action to determine if it's one that we want to handle |
| | 1231 | * collectively. If so, return true; if not, return nil. |
| | 1232 | */ |
| | 1233 | isCollectiveAction(action, whichObj) |
| | 1234 | { |
| | 1235 | /* we handle 'Examine' */ |
| | 1236 | if (action.ofKind(ExamineAction)) |
| | 1237 | return true; |
| | 1238 | |
| | 1239 | /* it's not one of ours */ |
| | 1240 | return nil; |
| | 1241 | } |
| | 1242 | |
| | 1243 | /* |
| | 1244 | * Check to see if we're a collective for the given quantity. By |
| | 1245 | * default, we return true only when no quantity is specified. |
| | 1246 | */ |
| | 1247 | isCollectiveQuant(np, requiredNum) |
| | 1248 | { |
| | 1249 | /* if no quantity was specified, use the collective */ |
| | 1250 | return (requiredNum == nil); |
| | 1251 | } |
| | 1252 | |
| | 1253 | /* |
| | 1254 | * Get a list of the individuals that can be sensed, given the |
| | 1255 | * information table for the desired sense (for visible items, this |
| | 1256 | * can be obtained by calling gActor.visibleInfoTable()). This is a |
| | 1257 | * service routine that can be useful for purposes such as writing a |
| | 1258 | * description routine for the collective. For example, a "money" |
| | 1259 | * collective object might want to count up the sum of money visible |
| | 1260 | * and show that. |
| | 1261 | * |
| | 1262 | * Note that it's possible for this to return an empty list. The |
| | 1263 | * caller can deal with this in a description, for example, by |
| | 1264 | * indicating that the collection cannot be seen. |
| | 1265 | */ |
| | 1266 | getVisibleIndividuals(tab) |
| | 1267 | { |
| | 1268 | /* keep only those items that are individuals of this collective */ |
| | 1269 | tab.forEachAssoc(new function(key, val) |
| | 1270 | { |
| | 1271 | /* remove this item if it's not an individual of mine */ |
| | 1272 | if (!key.hasCollectiveGroup(self)) |
| | 1273 | tab.removeElement(key); |
| | 1274 | }); |
| | 1275 | |
| | 1276 | /* return a list of the objects (i.e., the table's keys) */ |
| | 1277 | return tab.keysToList(); |
| | 1278 | } |
| | 1279 | |
| | 1280 | /* |
| | 1281 | * When we have no location, we're an abstract object without any |
| | 1282 | * physical presence in the game world. However, we still want to |
| | 1283 | * show up in the senses to the same extent our individuals do. To |
| | 1284 | * do this, we override this method so that we use the same sense |
| | 1285 | * data as the most visible (or whatever) of our individuals. |
| | 1286 | */ |
| | 1287 | addToSenseInfoTable(sense, tab) |
| | 1288 | { |
| | 1289 | /* if we have no location, mimic our best individual */ |
| | 1290 | if (location == nil && !ofKind(BaseMultiLoc)) |
| | 1291 | { |
| | 1292 | /* check everything in the connection table */ |
| | 1293 | tab.forEachAssoc(new function(cur, val) { |
| | 1294 | /* if this is one of our individuals, check it */ |
| | 1295 | if (cur.hasCollectiveGroup(self)) |
| | 1296 | { |
| | 1297 | local t; |
| | 1298 | |
| | 1299 | /* |
| | 1300 | * If it's the best or only one so far, adopt its |
| | 1301 | * sense status. Consider it the best if it has a |
| | 1302 | * more transparent transparency than the best so |
| | 1303 | * far, or its transparency is the same and it has a |
| | 1304 | * high ambient level. |
| | 1305 | */ |
| | 1306 | t = transparencyCompare(cur.tmpTrans_, tmpTrans_); |
| | 1307 | if (t > 0 || (t == 0 && cur.tmpAmbient_ > tmpAmbient_)) |
| | 1308 | { |
| | 1309 | /* it's better than our settings; mimic it */ |
| | 1310 | tmpTrans_ = cur.tmpTrans_; |
| | 1311 | tmpAmbient_ = cur.tmpAmbient_; |
| | 1312 | tmpObstructor_ = cur.tmpObstructor_; |
| | 1313 | } |
| | 1314 | } |
| | 1315 | }); |
| | 1316 | } |
| | 1317 | |
| | 1318 | /* inherit the standard handling */ |
| | 1319 | inherited(sense, tab); |
| | 1320 | } |
| | 1321 | |
| | 1322 | /* |
| | 1323 | * When we have no location, we want to create our own special |
| | 1324 | * containment path, just as we create our own special SenseInfo. |
| | 1325 | */ |
| | 1326 | specialPathFrom(src, vec) |
| | 1327 | { |
| | 1328 | /* if we have a location, use the normal handling */ |
| | 1329 | if (location != nil || ofKind(BaseMultiLoc)) |
| | 1330 | inherited(src, vec); |
| | 1331 | |
| | 1332 | /* look for an individual among the source object's connections */ |
| | 1333 | src.connectionTable().forEachAssoc(new function(cur, val) { |
| | 1334 | /* if this is one of our individuals, check it */ |
| | 1335 | if (cur.hasCollectiveGroup(self)) |
| | 1336 | { |
| | 1337 | /* add this individual's paths to the vector */ |
| | 1338 | vec.appendAll(src.getAllPathsTo(cur)); |
| | 1339 | } |
| | 1340 | }); |
| | 1341 | } |
| | 1342 | |
| | 1343 | /* |
| | 1344 | * CollectiveGroup objects are not normally listable in any |
| | 1345 | * situations. Since a collective group is merely a parser stand-in |
| | 1346 | * for its individuals, we don't want it to appear as a separate |
| | 1347 | * object in the game. |
| | 1348 | */ |
| | 1349 | isListedInContents = nil |
| | 1350 | isListedInInventory = nil |
| | 1351 | ; |
| | 1352 | |
| | 1353 | /* |
| | 1354 | * An "itemizing" collective group is like a regular collective group, |
| | 1355 | * but the Examine action itemizes the individual visible items making up |
| | 1356 | * the group. We itemize the individuals instead of showing the 'desc' |
| | 1357 | * for the overall group object, as the basic collective group class |
| | 1358 | * does. |
| | 1359 | */ |
| | 1360 | class ItemizingCollectiveGroup: CollectiveGroup |
| | 1361 | /* |
| | 1362 | * Override the main Examine handling. By default, we'll list the |
| | 1363 | * individuals that are visible, and separately list those that are |
| | 1364 | * being carried by the actor. If none of our individuals are |
| | 1365 | * visible, simply say so. |
| | 1366 | */ |
| | 1367 | mainExamine() |
| | 1368 | { |
| | 1369 | local info; |
| | 1370 | local vis; |
| | 1371 | local carried, here; |
| | 1372 | |
| | 1373 | /* get the visible info table */ |
| | 1374 | info = gActor.visibleInfoTable(); |
| | 1375 | |
| | 1376 | /* get the list of visible individuals */ |
| | 1377 | vis = getVisibleIndividuals(info); |
| | 1378 | |
| | 1379 | /* if any individuals are visible, list them */ |
| | 1380 | if (vis.length() != 0) |
| | 1381 | { |
| | 1382 | /* separate out the individuals being carried */ |
| | 1383 | carried = vis.subset({x: x.isIn(gActor)}); |
| | 1384 | here = vis - carried; |
| | 1385 | |
| | 1386 | /* show the items that are here but not being carried, if any */ |
| | 1387 | if (here.length() != 0) |
| | 1388 | { |
| | 1389 | /* get the room contents lister */ |
| | 1390 | local lister = gActor.location.roomContentsLister; |
| | 1391 | |
| | 1392 | /* get the subset that the room contents lister won't list */ |
| | 1393 | local xlist = here.subset({x: !lister.isListed(x)}); |
| | 1394 | |
| | 1395 | /* show the list through the room contents lister */ |
| | 1396 | lister.showList(gActor, nil, here, 0, 0, info, nil); |
| | 1397 | |
| | 1398 | /* Examine any objects not part of the room description */ |
| | 1399 | foreach (local x in xlist) |
| | 1400 | examineUnlisted(x); |
| | 1401 | |
| | 1402 | /* |
| | 1403 | * if that showed anything, add a paragraph break before |
| | 1404 | * the carried list |
| | 1405 | */ |
| | 1406 | if (xlist.length() != 0 && carried.length() != 0) |
| | 1407 | "<.p>"; |
| | 1408 | } |
| | 1409 | |
| | 1410 | /* separately, show the items being carried, if any */ |
| | 1411 | if (carried.length() != 0) |
| | 1412 | gActor.inventoryLister.showList( |
| | 1413 | gActor, gActor, carried, 0, 0, info, nil); |
| | 1414 | } |
| | 1415 | else |
| | 1416 | { |
| | 1417 | /* |
| | 1418 | * None are visible. If it's dark in the location, simply |
| | 1419 | * say so; otherwise, say that we can't see any of me. |
| | 1420 | */ |
| | 1421 | if (!gActor.isLocationLit()) |
| | 1422 | reportFailure(&tooDarkMsg); |
| | 1423 | else |
| | 1424 | reportFailure(&mustBeVisibleMsg, self); |
| | 1425 | } |
| | 1426 | } |
| | 1427 | |
| | 1428 | /* |
| | 1429 | * Examine an unlisted individual object. This will be called for |
| | 1430 | * each object in the room that's not listable via the room contents |
| | 1431 | * lister. |
| | 1432 | */ |
| | 1433 | examineUnlisted(x) |
| | 1434 | { |
| | 1435 | "<.p>"; |
| | 1436 | nestedAction(Examine, x); |
| | 1437 | } |
| | 1438 | ; |
| | 1439 | |
| | 1440 | /* ------------------------------------------------------------------------ */ |
| | 1441 | /* |
| | 1442 | * A readable object. Any ordinary object will show its normal full |
| | 1443 | * description when read, but an object that is explicitly readable will |
| | 1444 | * have elevated logicalness for the "read" action, and can optionally |
| | 1445 | * show a separate description when read. |
| | 1446 | */ |
| | 1447 | class Readable: Thing |
| | 1448 | /* |
| | 1449 | * Show my special reading desription. By default, we set this to |
| | 1450 | * nil to indicate that we should use our default "examine" |
| | 1451 | * description; objects can override this to show a special message |
| | 1452 | * for reading the object as desired. |
| | 1453 | */ |
| | 1454 | readDesc = nil |
| | 1455 | |
| | 1456 | /* our reading description when obscured */ |
| | 1457 | obscuredReadDesc() { gLibMessages.obscuredReadDesc(self); } |
| | 1458 | |
| | 1459 | /* our reading description in dim light */ |
| | 1460 | dimReadDesc() { gLibMessages.dimReadDesc(self); } |
| | 1461 | |
| | 1462 | /* "Read" action */ |
| | 1463 | dobjFor(Read) |
| | 1464 | { |
| | 1465 | verify() |
| | 1466 | { |
| | 1467 | /* give slight preference to an object being held */ |
| | 1468 | if (!isIn(gActor)) |
| | 1469 | logicalRank(80, 'not held'); |
| | 1470 | } |
| | 1471 | action() |
| | 1472 | { |
| | 1473 | /* |
| | 1474 | * if we have a special reading description defined, show |
| | 1475 | * it; otherwise, use the same handling as "examine" |
| | 1476 | */ |
| | 1477 | if (propType(&readDesc) != TypeNil) |
| | 1478 | { |
| | 1479 | local info; |
| | 1480 | |
| | 1481 | /* |
| | 1482 | * Reading requires a transparent sight path and plenty |
| | 1483 | * of light; in the absence of either of these, we can't |
| | 1484 | * make out the details. |
| | 1485 | */ |
| | 1486 | info = gActor.bestVisualInfo(self); |
| | 1487 | if (info.trans != transparent) |
| | 1488 | obscuredReadDesc; |
| | 1489 | else if (info.ambient < 3) |
| | 1490 | dimReadDesc; |
| | 1491 | else |
| | 1492 | readDesc; |
| | 1493 | } |
| | 1494 | else |
| | 1495 | { |
| | 1496 | /* |
| | 1497 | * we have no special reading description, so use the |
| | 1498 | * default "examine" handling |
| | 1499 | */ |
| | 1500 | actionDobjExamine(); |
| | 1501 | } |
| | 1502 | } |
| | 1503 | } |
| | 1504 | ; |
| | 1505 | |
| | 1506 | |
| | 1507 | /* ------------------------------------------------------------------------ */ |
| | 1508 | /* |
| | 1509 | * A "consultable" object. This is an inanimate object that can be |
| | 1510 | * consulted about various topics, almost the way an actor can be asked |
| | 1511 | * about topics. Examples include individual objects that contain |
| | 1512 | * voluminous information, such as books, phone directories, and maps, as |
| | 1513 | * well as collections of individual information-carrying objects, such |
| | 1514 | * as file cabinets or bookcases. |
| | 1515 | * |
| | 1516 | * A consultable keeps a database of TopicEntry objects; this works in |
| | 1517 | * much the same way as the topic database system that actors use. |
| | 1518 | * Create one or more ConsultTopic objects and place them inside the |
| | 1519 | * Consultable (using the 'location' property, or using the '+' syntax). |
| | 1520 | * When an actor consults the object about a topic, we'll search our |
| | 1521 | * database for a ConsultTopic object that matches the topic and is |
| | 1522 | * currently active, and show the response for the best one we can find. |
| | 1523 | * |
| | 1524 | * From an IF design perspective, consultables have two nice properties. |
| | 1525 | * |
| | 1526 | * First, they hide the boundaries of implementation, by letting the game |
| | 1527 | * *suggest* that there's an untold wealth of information in a particular |
| | 1528 | * book (or whatever) without the need to actually implement all of it. |
| | 1529 | * We only have to show the entries the player specifically asks for, so |
| | 1530 | * the game never has to admit when it's run out of things to show, and |
| | 1531 | * the player can never know for sure that there's not more to find. Be |
| | 1532 | * careful, though, because this is a double-edge sword, design-wise; |
| | 1533 | * it's easy to abuse this property to hide information gratuitously from |
| | 1534 | * the player. |
| | 1535 | * |
| | 1536 | * Second, consultables help "match impedances" between the narrative |
| | 1537 | * level of detail and the underlying world model. At the narrative |
| | 1538 | * level, we paint in fairly broad strokes: when we visit a new location, |
| | 1539 | * we describe the *important* features of the setting, not every last |
| | 1540 | * detail. If the player wants to examine something in closer detail, we |
| | 1541 | * zoom in on that detail, assuming we've implemented it, but it's up to |
| | 1542 | * the player to determine where the attention is focused. Consultable |
| | 1543 | * objects give us the same capability for books and the like. With a |
| | 1544 | * consultable, we can describe the way a book looks without immediately |
| | 1545 | * dumping the literal contents of the book onto the screen; but when the |
| | 1546 | * player chooses some aspect of the book to read in detail, we can zoom |
| | 1547 | * in on that page or chapter and show that literal content, if we |
| | 1548 | * choose. |
| | 1549 | * |
| | 1550 | * Also, note that we assume that consultables convey their information |
| | 1551 | * through visual information, such as printed text or a display screen. |
| | 1552 | * Because of this, we by default require that the object be visible to |
| | 1553 | * be consulted. This might not be appropriate in some cases, such as |
| | 1554 | * Braille books or talking PDA's; to remove the visual condition, |
| | 1555 | * override the pre-condition for the Consult action. |
| | 1556 | */ |
| | 1557 | class Consultable: Thing, TopicDatabase |
| | 1558 | /* |
| | 1559 | * If they consult us without a topic, just ask for a topic. Treat |
| | 1560 | * it as logical, but rank it as improbable, in case there's |
| | 1561 | * anything else around that can be consulted without any topic |
| | 1562 | * specified. |
| | 1563 | */ |
| | 1564 | dobjFor(Consult) |
| | 1565 | { |
| | 1566 | preCond = [touchObj, objVisible] |
| | 1567 | verify() { logicalRank(50, 'need a topic'); } |
| | 1568 | action() { askForTopic(ConsultAbout); } |
| | 1569 | } |
| | 1570 | |
| | 1571 | /* consult about a topic */ |
| | 1572 | dobjFor(ConsultAbout) |
| | 1573 | { |
| | 1574 | verify() { } |
| | 1575 | action() |
| | 1576 | { |
| | 1577 | /* remember that we're the last object the actor consulted */ |
| | 1578 | gActor.noteConsultation(self); |
| | 1579 | |
| | 1580 | /* try handling the topic through our topic database */ |
| | 1581 | if (!handleTopic(gActor, gTopic, consultConvType, nil)) |
| | 1582 | topicNotFound(); |
| | 1583 | } |
| | 1584 | } |
| | 1585 | |
| | 1586 | /* show the default response for a topic we couldn't find */ |
| | 1587 | topicNotFound() |
| | 1588 | { |
| | 1589 | /* |
| | 1590 | * Report the absence of the topic. Note that we use an |
| | 1591 | * ordinary, successful report, not a failure report, because |
| | 1592 | * the consultation really did succeed in the sense of the |
| | 1593 | * physical action of consulting: we successfully flipped |
| | 1594 | * through the book, scanned the file cabinet, or whatever. We |
| | 1595 | * didn't find what we were looking for, but in terms of the |
| | 1596 | * physical action undertaken, we successfully did exactly what |
| | 1597 | * we were asked to do. |
| | 1598 | */ |
| | 1599 | mainReport(&cannotFindTopicMsg); |
| | 1600 | } |
| | 1601 | |
| | 1602 | /* |
| | 1603 | * Resolve the topic phrase for a CONSULT ABOUT command. The CONSULT |
| | 1604 | * ABOUT action refers this to the direct object of the action, so |
| | 1605 | * that the direct object can filter the topic match according to |
| | 1606 | * what makes sense for the consultable. |
| | 1607 | * |
| | 1608 | * By default, we resolve the topic phrase a little differently than |
| | 1609 | * we would for conversational commands, such as ASK ABOUT. By |
| | 1610 | * default, we don't differentiate objects at all based on physical |
| | 1611 | * scope or actor knowledge when deciding on a match for a topic |
| | 1612 | * phrase. For example, if you create a Consultable representing a |
| | 1613 | * phone book, and the player enters a command like FIND BOB IN PHONE |
| | 1614 | * BOOK, the topic BOB will be found even if the 'bob' object isn't |
| | 1615 | * known to the player character. The reason for this difference |
| | 1616 | * from ASK ABOUT et al is that consultables are generally the kinds |
| | 1617 | * of objects where, in real life, a person could browse through the |
| | 1618 | * object and come across entries whether or not the person knew |
| | 1619 | * enough to look for them. For example, you could go through a |
| | 1620 | * phone book and find an entry for "Bob" even if you didn't know |
| | 1621 | * anyone named Bob. |
| | 1622 | * |
| | 1623 | * 'lst' is the list of ResolveInfo objects giving the full set of |
| | 1624 | * matches for the vocabulary words; 'np' is the grammar production |
| | 1625 | * object for the topic phrase; and 'resolver' is the TopicResolver |
| | 1626 | * that's resolving the topic phrase. Note that 'lst' contains |
| | 1627 | * ResolveInfo objects, so to get the game-world object for a given |
| | 1628 | * list entry, use lst[i].obj_. |
| | 1629 | * |
| | 1630 | * We return a ResolvedTopic object that encapsulates the matching |
| | 1631 | * objects. |
| | 1632 | * |
| | 1633 | * Note that the resolver object can be used to get certain useful |
| | 1634 | * information. The resolver's getAction() method returns the action |
| | 1635 | * (which you should use instead of gAction, since this routine is |
| | 1636 | * called during the resolution process, not during command |
| | 1637 | * execution); its getTargetActor() method returns the actor |
| | 1638 | * performing the action; and its objInPhysicalScope(obj) method lets |
| | 1639 | * you determine if an object is in physical scope for the actor. |
| | 1640 | */ |
| | 1641 | resolveConsultTopic(lst, np, resolver) |
| | 1642 | { |
| | 1643 | /* |
| | 1644 | * by default, simply return an undifferentiated list with |
| | 1645 | * everything given equal weight, whether known or not, and |
| | 1646 | * whether in scope or not |
| | 1647 | */ |
| | 1648 | return new ResolvedTopic(lst, [], [], np); |
| | 1649 | } |
| | 1650 | |
| | 1651 | /* |
| | 1652 | * Our topic entry database for consultatation topics. This will be |
| | 1653 | * automatically built during initialization from the set of |
| | 1654 | * ConsultTopic objects located within me, so there's usually no |
| | 1655 | * need to initialize this manually. |
| | 1656 | */ |
| | 1657 | consultTopics = nil |
| | 1658 | ; |
| | 1659 | |
| | 1660 | /* |
| | 1661 | * A consultation topic. You can place one or more of these inside a |
| | 1662 | * Consultable object (using the 'location' property, or the '+' |
| | 1663 | * notation), to create a database of topics that can be looked up in |
| | 1664 | * the consultable. |
| | 1665 | */ |
| | 1666 | class ConsultTopic: TopicMatchTopic |
| | 1667 | /* include in the consultation list */ |
| | 1668 | includeInList = [&consultTopics] |
| | 1669 | |
| | 1670 | /* |
| | 1671 | * don't set any pronouns for the topic - the consultable itself |
| | 1672 | * should be the pronoun antecedent |
| | 1673 | */ |
| | 1674 | setTopicPronouns(fromActor, obj) { } |
| | 1675 | ; |
| | 1676 | |
| | 1677 | /* |
| | 1678 | * A default topic entry for a consultable. You can include one (or |
| | 1679 | * more) of these in a consultable's database to provide a topic of last |
| | 1680 | * resort that answers to any topics that aren't in the database |
| | 1681 | * themselves. |
| | 1682 | */ |
| | 1683 | class DefaultConsultTopic: DefaultTopic |
| | 1684 | includeInList = [&consultTopics] |
| | 1685 | setTopicPronouns(fromActor, obj) { } |
| | 1686 | ; |
| | 1687 | |
| | 1688 | |
| | 1689 | /* ------------------------------------------------------------------------ */ |
| | 1690 | /* |
| | 1691 | * A common, abstract base class for things that cannot be moved. You |
| | 1692 | * shouldn't use this class to create game objects directly; you should |
| | 1693 | * always use one of the concrete subclasses, such as Fixture or |
| | 1694 | * Immovable. This base class doesn't provide the full behavior |
| | 1695 | * necessary to make an object immovable; it's just here as a |
| | 1696 | * programming abstraction for the common elements of all immovable |
| | 1697 | * objects. |
| | 1698 | * |
| | 1699 | * This class has two purposes. First, it defines some behavior common |
| | 1700 | * to all non-portable objects. Second, you can test an object to see |
| | 1701 | * if it's based on this class to determine whether it's a portable or |
| | 1702 | * unportable type of Thing. |
| | 1703 | */ |
| | 1704 | class NonPortable: Thing |
| | 1705 | /* |
| | 1706 | * An immovable objects is not listed in room or container contents |
| | 1707 | * listings. Since the object is immovable, it's in effect a |
| | 1708 | * permanent feature of its location, so it should be described as |
| | 1709 | * such: either directly as part of its location's description text, |
| | 1710 | * or via its own specialDesc. |
| | 1711 | */ |
| | 1712 | isListed = nil |
| | 1713 | isListedInContents = nil |
| | 1714 | isListedInInventory = nil |
| | 1715 | |
| | 1716 | /* |
| | 1717 | * By default, if the object's contents would be listed in a direct |
| | 1718 | * examination, then also list them when showing an inventory list, |
| | 1719 | * or describing the enclosing room or an enclosing object. |
| | 1720 | */ |
| | 1721 | contentsListed = (contentsListedInExamine) |
| | 1722 | |
| | 1723 | /* |
| | 1724 | * Are my contents within a fixed item that is within the given |
| | 1725 | * location? Since we're fixed in place, our contents are certainly |
| | 1726 | * within a fixed item, so we merely need to check if we're fixed in |
| | 1727 | * place within the given location. We are if we're in the given |
| | 1728 | * location or we ourselves are fixed in place in the given location. |
| | 1729 | */ |
| | 1730 | contentsInFixedIn(loc) |
| | 1731 | { |
| | 1732 | return isDirectlyIn(loc) || isInFixedIn(loc); |
| | 1733 | } |
| | 1734 | |
| | 1735 | /* |
| | 1736 | * Since non-portables aren't carried, their weight and bulk are |
| | 1737 | * largely irrelevant. Even so, when a non-portable is a component |
| | 1738 | * of another object, or otherwise contained in another object, its |
| | 1739 | * weight and/or bulk can affect the behavior of the parent object. |
| | 1740 | * So, it's simplest to use a default of zero for these so that there |
| | 1741 | * are no surprises about the parent's behavior. |
| | 1742 | */ |
| | 1743 | weight = 0 |
| | 1744 | bulk = 0 |
| | 1745 | |
| | 1746 | /* |
| | 1747 | * Non-portable objects can't be held, since they can't be carried. |
| | 1748 | * However, in some cases, it's useful to include non-portable |
| | 1749 | * objects within an actor, such as when creating component parts of |
| | 1750 | * an actor (hands, say). In these cases, the non-portables aren't |
| | 1751 | * held, but rather are components or similar. |
| | 1752 | */ |
| | 1753 | isHeldBy(actor) { return nil; } |
| | 1754 | |
| | 1755 | /* |
| | 1756 | * We're not being held, but if our location is an actor, then we're |
| | 1757 | * as good as held because we're effectively part of the actor. |
| | 1758 | */ |
| | 1759 | meetsObjHeld(actor) { return actor == location; } |
| | 1760 | |
| | 1761 | /* |
| | 1762 | * showing an immovable to someone simply requires that it be in |
| | 1763 | * sight: we're not holding it up to show it, we're simply pointing |
| | 1764 | * it out |
| | 1765 | */ |
| | 1766 | dobjFor(ShowTo) { preCond = [objVisible] } |
| | 1767 | |
| | 1768 | /* |
| | 1769 | * Thing decreases the likelihood that we want to examine an object |
| | 1770 | * when the object isn't being held. That's fine for portable |
| | 1771 | * objects, but nonportables can never be held, so we don't want that |
| | 1772 | * decrease in logicalness. |
| | 1773 | */ |
| | 1774 | dobjFor(Examine) |
| | 1775 | { |
| | 1776 | /* override Thing's likelihood downgrade for un-held items */ |
| | 1777 | verify() { } |
| | 1778 | } |
| | 1779 | ; |
| | 1780 | |
| | 1781 | |
| | 1782 | /* ------------------------------------------------------------------------ */ |
| | 1783 | /* |
| | 1784 | * A "fixture," which is something that's obviously a part of the room. |
| | 1785 | * These objects cannot be removed from their containers. This class is |
| | 1786 | * meant for permanent features of rooms that obviously cannot be moved |
| | 1787 | * to a new container, such as walls, floors, doors, built-in bookcases, |
| | 1788 | * light switches, buildings, and the like. |
| | 1789 | * |
| | 1790 | * The important feature of a Fixture is that it's *obvious* that it's |
| | 1791 | * part of its container, so it should be safe to assume that a character |
| | 1792 | * normally wouldn't even try to take it or move it. For objects that |
| | 1793 | * might appear portable but turn out to be immovable, other classes are |
| | 1794 | * more appropriate: use Heavy for objects that are immovable simply |
| | 1795 | * because they're very heavy, for example, or Immovable for objects that |
| | 1796 | * are immovable for some non-obvious reason. |
| | 1797 | */ |
| | 1798 | class Fixture: NonPortable |
| | 1799 | /* |
| | 1800 | * Hide fixtures from "all" for certain commands. Fixtures are |
| | 1801 | * obviously part of the location, so a reaonable person wouldn't |
| | 1802 | * even consider trying to do things like take them or move them. |
| | 1803 | */ |
| | 1804 | hideFromAll(action) |
| | 1805 | { |
| | 1806 | return (action.ofKind(TakeAction) |
| | 1807 | || action.ofKind(DropAction) |
| | 1808 | || action.ofKind(PutInAction) |
| | 1809 | || action.ofKind(PutOnAction)); |
| | 1810 | } |
| | 1811 | |
| | 1812 | /* don't hide from defaults, though */ |
| | 1813 | hideFromDefault(action) { return nil; } |
| | 1814 | |
| | 1815 | /* a fixed item can't be moved by an actor action */ |
| | 1816 | verifyMoveTo(newLoc) |
| | 1817 | { |
| | 1818 | /* it's never possible to do this */ |
| | 1819 | illogical(cannotMoveMsg); |
| | 1820 | } |
| | 1821 | |
| | 1822 | /* |
| | 1823 | * a fixed item can't be taken - this would be caught by |
| | 1824 | * verifyMoveTo anyway, but provide a more explicit message when a |
| | 1825 | * fixed item is explicitly taken |
| | 1826 | */ |
| | 1827 | dobjFor(Take) { verify() { illogical(cannotTakeMsg); }} |
| | 1828 | dobjFor(TakeFrom) { verify() { illogical(cannotTakeMsg); }} |
| | 1829 | |
| | 1830 | /* fixed objects can't be put anywhere */ |
| | 1831 | dobjFor(PutIn) { verify() { illogical(cannotPutMsg); }} |
| | 1832 | dobjFor(PutOn) { verify() { illogical(cannotPutMsg); }} |
| | 1833 | dobjFor(PutUnder) { verify() { illogical(cannotPutMsg); }} |
| | 1834 | dobjFor(PutBehind) { verify() { illogical(cannotPutMsg); }} |
| | 1835 | |
| | 1836 | /* fixed objects can't be pushed, pulled, or moved */ |
| | 1837 | dobjFor(Push) { verify() { illogical(cannotMoveMsg); }} |
| | 1838 | dobjFor(Pull) { verify() { illogical(cannotMoveMsg); }} |
| | 1839 | dobjFor(Move) { verify() { illogical(cannotMoveMsg); }} |
| | 1840 | dobjFor(MoveWith) { verify() { illogical(cannotMoveMsg); }} |
| | 1841 | dobjFor(MoveTo) { verify() { illogical(cannotMoveMsg); }} |
| | 1842 | dobjFor(PushTravel) { verify() { illogical(cannotMoveMsg); }} |
| | 1843 | dobjFor(ThrowAt) { verify() { illogical(cannotMoveMsg); }} |
| | 1844 | dobjFor(ThrowDir) { verify() { illogical(cannotMoveMsg); }} |
| | 1845 | |
| | 1846 | /* |
| | 1847 | * The messages to use for illogical messages. These can be |
| | 1848 | * overridden with new properties (of playerActionMessages and the |
| | 1849 | * like), or simply with single-quoted strings to display. |
| | 1850 | */ |
| | 1851 | cannotTakeMsg = &cannotTakeFixtureMsg |
| | 1852 | cannotMoveMsg = &cannotMoveFixtureMsg |
| | 1853 | cannotPutMsg = &cannotPutFixtureMsg |
| | 1854 | |
| | 1855 | /* |
| | 1856 | * A component can be said to be owned by its location's owner or by |
| | 1857 | * its location. |
| | 1858 | */ |
| | 1859 | isOwnedBy(obj) |
| | 1860 | { |
| | 1861 | /* |
| | 1862 | * if I'm owned by the object under the normal rules, then we |
| | 1863 | * won't say otherwise |
| | 1864 | */ |
| | 1865 | if (inherited(obj)) |
| | 1866 | return true; |
| | 1867 | |
| | 1868 | /* |
| | 1869 | * we can be said to be owned by our location, since we're a |
| | 1870 | * direct and permanent part of the location |
| | 1871 | */ |
| | 1872 | if (obj == location) |
| | 1873 | return true; |
| | 1874 | |
| | 1875 | /* |
| | 1876 | * if my location is owned by the given object, consider |
| | 1877 | * ourselves owned by it as well, as we're an extension of our |
| | 1878 | * location |
| | 1879 | */ |
| | 1880 | if (location != nil && location.isOwnedBy(obj)) |
| | 1881 | return true; |
| | 1882 | |
| | 1883 | /* we didn't find anything that establishes ownership */ |
| | 1884 | return nil; |
| | 1885 | } |
| | 1886 | ; |
| | 1887 | |
| | 1888 | /* |
| | 1889 | * A component object. These objects cannot be removed from their |
| | 1890 | * containers because they are permanent features of other objects, which |
| | 1891 | * may themselves be portable: the hands of a watch, a tuning dial on a |
| | 1892 | * radio. This class behaves essentially the same way as Fixture, but |
| | 1893 | * its messages are more suitable for objects that are component parts of |
| | 1894 | * other objects rather than fixed features of rooms. |
| | 1895 | */ |
| | 1896 | class Component: Fixture |
| | 1897 | /* a component cannot be removed from its container by an actor action */ |
| | 1898 | verifyMoveTo(newLoc) |
| | 1899 | { |
| | 1900 | /* it's never possible to do this */ |
| | 1901 | illogical(&cannotMoveComponentMsg, location); |
| | 1902 | } |
| | 1903 | |
| | 1904 | /* |
| | 1905 | * Hide components from EXAMINE ALL, as well as any commands hidden |
| | 1906 | * from ALL for ordinary fixtures. Components are small parts of |
| | 1907 | * larger objects, so when we EXAMINE ALL, it's enough to examine the |
| | 1908 | * larger objects of which we're a part; we don't want components to |
| | 1909 | * show up separately in these cases. |
| | 1910 | */ |
| | 1911 | hideFromAll(action) |
| | 1912 | { |
| | 1913 | /* hide from EXAMINE ALL, plus anything the base class hides */ |
| | 1914 | return (action.ofKind(ExamineAction) |
| | 1915 | || inherited(action)); |
| | 1916 | } |
| | 1917 | |
| | 1918 | /* |
| | 1919 | * We are a component of our direct cotnainer, and we're indirectly a |
| | 1920 | * component of anything that it's a component of. |
| | 1921 | */ |
| | 1922 | isComponentOf(obj) |
| | 1923 | { |
| | 1924 | return (obj == location |
| | 1925 | || (location != nil && location.isComponentOf(obj))); |
| | 1926 | } |
| | 1927 | |
| | 1928 | /* |
| | 1929 | * Consider ourself to be held by the given actor if we're a |
| | 1930 | * component of the actor. |
| | 1931 | */ |
| | 1932 | meetsObjHeld(actor) { return isComponentOf(actor); } |
| | 1933 | |
| | 1934 | /* a component cannot be taken separately */ |
| | 1935 | dobjFor(Take) |
| | 1936 | { verify() { illogical(&cannotTakeComponentMsg, location); }} |
| | 1937 | dobjFor(TakeFrom) |
| | 1938 | { verify() { illogical(&cannotTakeComponentMsg, location); }} |
| | 1939 | |
| | 1940 | /* a component cannot be separately put somewhere */ |
| | 1941 | dobjFor(PutIn) |
| | 1942 | { verify() { illogical(&cannotPutComponentMsg, location); }} |
| | 1943 | dobjFor(PutOn) |
| | 1944 | { verify() { illogical(&cannotPutComponentMsg, location); }} |
| | 1945 | dobjFor(PutUnder) |
| | 1946 | { verify() { illogical(&cannotPutComponentMsg, location); }} |
| | 1947 | dobjFor(PutBehind) |
| | 1948 | { verify() { illogical(&cannotPutComponentMsg, location); }} |
| | 1949 | ; |
| | 1950 | |
| | 1951 | /* |
| | 1952 | * A "secret fixture" is a kind of fixture that we use for internal |
| | 1953 | * implementation purposes, and which we don't intend to be visible to |
| | 1954 | * the player. Objects of this type usually have no vocabulary, since we |
| | 1955 | * don't want the player to be able to refer to them. |
| | 1956 | */ |
| | 1957 | class SecretFixture: Fixture |
| | 1958 | /* |
| | 1959 | * this kind of object is internal to the game's implementation, so |
| | 1960 | * we don't want it to show up in "all" lists |
| | 1961 | */ |
| | 1962 | hideFromAll(action) { return true; } |
| | 1963 | ; |
| | 1964 | |
| | 1965 | /* |
| | 1966 | * A fixture that uses the same custom message for taking, moving, and |
| | 1967 | * putting. In many cases, it's useful to customize the message for a |
| | 1968 | * fixture, using the same custom message for all sorts of moving. Just |
| | 1969 | * override cannotTakeMsg, and the other messages will copy it. |
| | 1970 | */ |
| | 1971 | class CustomFixture: Fixture |
| | 1972 | cannotMoveMsg = (cannotTakeMsg) |
| | 1973 | cannotPutMsg = (cannotTakeMsg) |
| | 1974 | ; |
| | 1975 | |
| | 1976 | /* ------------------------------------------------------------------------ */ |
| | 1977 | /* |
| | 1978 | * An Immovable is an object that can't be moved, but not because it's |
| | 1979 | * obviously a fixture or component of another object. This class is |
| | 1980 | * suitable for things like furniture, which are in principle portable |
| | 1981 | * but which actors aren't actually allowed to pick up or move around. |
| | 1982 | * |
| | 1983 | * Note that Immovable is a lot like Fixture. The difference is that |
| | 1984 | * Fixture is for objects that are *obviously* fixed in place by their |
| | 1985 | * very nature, whereas Immovable is for objects that common sense would |
| | 1986 | * tell us are portable, but which the game doesn't in fact allow the |
| | 1987 | * player to move. |
| | 1988 | * |
| | 1989 | * The practical difference between Immovable and Fixture is that Fixture |
| | 1990 | * considers taking or moving to be illogical actions, whereas Immovable |
| | 1991 | * considers these actions logical but simply doesn't allow them. To be |
| | 1992 | * more specific, Fixture disallows taking and moving in the verify() |
| | 1993 | * methods for those actions, while Immovable disallows the actions in |
| | 1994 | * the check() methods. This means, for example, that Fixture objects |
| | 1995 | * will be removed from consideration during the noun resolution phase |
| | 1996 | * when there are more logical choices. |
| | 1997 | */ |
| | 1998 | class Immovable: NonPortable |
| | 1999 | /* an Immovable can't be taken */ |
| | 2000 | dobjFor(Take) { check() { failCheck(cannotTakeMsg); }} |
| | 2001 | |
| | 2002 | /* Immovables can't be put anywhere */ |
| | 2003 | dobjFor(PutIn) { check() { failCheck(cannotPutMsg); }} |
| | 2004 | dobjFor(PutOn) { check() { failCheck(cannotPutMsg); }} |
| | 2005 | dobjFor(PutUnder) { check() { failCheck(cannotPutMsg); }} |
| | 2006 | dobjFor(PutBehind) { check() { failCheck(cannotPutMsg); }} |
| | 2007 | |
| | 2008 | /* Immovables can't be pushed, pulled, or otherwise moved */ |
| | 2009 | dobjFor(Drop) { action() { reportFailure(cannotMoveMsg); }} |
| | 2010 | dobjFor(Push) { action() { reportFailure(cannotMoveMsg); }} |
| | 2011 | dobjFor(Pull) { action() { reportFailure(cannotMoveMsg); }} |
| | 2012 | dobjFor(Move) { action() { reportFailure(cannotMoveMsg); }} |
| | 2013 | dobjFor(MoveWith) { check() { failCheck(cannotMoveMsg); }} |
| | 2014 | dobjFor(MoveTo) { check() { failCheck(cannotMoveMsg); }} |
| | 2015 | dobjFor(PushTravel) { action() { reportFailure(cannotMoveMsg); }} |
| | 2016 | dobjFor(ThrowAt) { verify() { illogical(cannotMoveMsg); }} |
| | 2017 | dobjFor(ThrowDir) { verify() { illogical(cannotMoveMsg); }} |
| | 2018 | dobjFor(Turn) |
| | 2019 | { |
| | 2020 | verify() { logicalRank(50, 'turn heavy'); } |
| | 2021 | action() { reportFailure(cannotMoveMsg); } |
| | 2022 | } |
| | 2023 | |
| | 2024 | /* |
| | 2025 | * The messages to use for the failure messages. These can be |
| | 2026 | * overridden with new properties (of playerActionMessages and the |
| | 2027 | * like), or simply with single-quoted strings to display. |
| | 2028 | */ |
| | 2029 | cannotTakeMsg = &cannotTakeImmovableMsg |
| | 2030 | cannotMoveMsg = &cannotMoveImmovableMsg |
| | 2031 | cannotPutMsg = &cannotPutImmovableMsg |
| | 2032 | ; |
| | 2033 | |
| | 2034 | /* |
| | 2035 | * An immovable that uses the same custom message for taking, moving, and |
| | 2036 | * putting. In many cases, it's useful to customize the message for an |
| | 2037 | * immovable, using the same custom message for all sorts of moving. |
| | 2038 | * Just override cannotTakeMsg, and the other messages will copy it. |
| | 2039 | */ |
| | 2040 | class CustomImmovable: Immovable |
| | 2041 | cannotMoveMsg = (cannotTakeMsg) |
| | 2042 | cannotPutMsg = (cannotTakeMsg) |
| | 2043 | ; |
| | 2044 | |
| | 2045 | /* |
| | 2046 | * Heavy: an object that's immovable because it's very heavy. This is |
| | 2047 | * suitable for things like large boulders, heavy furniture, or the like: |
| | 2048 | * things that aren't nailed down, but nonetheless are too heavy to be |
| | 2049 | * carried or otherwise move. |
| | 2050 | * |
| | 2051 | * This is a simple specialization of Immovable; the only thing we change |
| | 2052 | * is the messages we use to describe why the object can't be moved. |
| | 2053 | */ |
| | 2054 | class Heavy: Immovable |
| | 2055 | cannotTakeMsg = &cannotTakeHeavyMsg |
| | 2056 | cannotMoveMsg = &cannotMoveHeavyMsg |
| | 2057 | cannotPutMsg = &cannotPutHeavyMsg |
| | 2058 | ; |
| | 2059 | |
| | 2060 | |
| | 2061 | /* ------------------------------------------------------------------------ */ |
| | 2062 | /* |
| | 2063 | * Decoration. This is an object that is included for scenery value but |
| | 2064 | * which has no other purpose, and which the author wants to make clear |
| | 2065 | * is not important. We use the catch-all action routine to respond to |
| | 2066 | * any command on this object with a flat "that's not important" |
| | 2067 | * message, so that the player can plainly see that there's no point |
| | 2068 | * wasting any time trying to manipulate this object. |
| | 2069 | * |
| | 2070 | * We use the "default" catch-all verb verify handling to report our |
| | 2071 | * "that's not important" message, so a decoration can be made |
| | 2072 | * responsive to specific verbs simply by defining an action handler for |
| | 2073 | * those verbs. |
| | 2074 | */ |
| | 2075 | class Decoration: Fixture |
| | 2076 | /* don't include decorations in 'all' */ |
| | 2077 | hideFromAll(action) { return true; } |
| | 2078 | |
| | 2079 | /* don't hide from defaults */ |
| | 2080 | hideFromDefault(action) { return nil; } |
| | 2081 | |
| | 2082 | /* |
| | 2083 | * use the default response "this object isn't important" when we're |
| | 2084 | * used as either a direct or indirect object |
| | 2085 | */ |
| | 2086 | dobjFor(Default) |
| | 2087 | { |
| | 2088 | verify() { illogical(notImportantMsg, self); } |
| | 2089 | } |
| | 2090 | iobjFor(Default) |
| | 2091 | { |
| | 2092 | verify() { illogical(notImportantMsg, self); } |
| | 2093 | } |
| | 2094 | |
| | 2095 | /* use the standard not-important message for decorations */ |
| | 2096 | notImportantMsg = &decorationNotImportantMsg |
| | 2097 | |
| | 2098 | /* |
| | 2099 | * The catch-all Default verifier makes all actions illogical, but we |
| | 2100 | * can override this to allow specific actions by explicitly defining |
| | 2101 | * them here so that they hide the Default verify handlers. In |
| | 2102 | * addition, give decorations a reduced logical rank, so that any |
| | 2103 | * in-scope non-decoration object with similar vocabulary will be |
| | 2104 | * matched for an Examine command ahead of a decoration. |
| | 2105 | */ |
| | 2106 | dobjFor(Examine) |
| | 2107 | { verify() { inherited(); logicalRank(70, 'decoration'); } } |
| | 2108 | |
| | 2109 | /* |
| | 2110 | * likewise for LISTEN TO and SMELL, which are the auditory and |
| | 2111 | * olfactory equivalents of EXAMINE |
| | 2112 | */ |
| | 2113 | dobjFor(ListenTo) |
| | 2114 | { verify() { inherited(); logicalRank(70, 'decoration'); } } |
| | 2115 | dobjFor(Smell) |
| | 2116 | { verify() { inherited(); logicalRank(70, 'decoration'); } } |
| | 2117 | |
| | 2118 | /* likewise for READ */ |
| | 2119 | dobjFor(Read) |
| | 2120 | { verify() { inherited(); logicalRank(70, 'decoration'); } } |
| | 2121 | |
| | 2122 | /* likewise for LOOK IN and SEARCH */ |
| | 2123 | dobjFor(LookIn) |
| | 2124 | { verify() { inherited(); logicalRank(70, 'decoration'); } } |
| | 2125 | dobjFor(Search) |
| | 2126 | { verify() { inherited(); logicalRank(70, 'decoration'); } } |
| | 2127 | |
| | 2128 | /* the default LOOK IN response is our standard "that's not important" */ |
| | 2129 | lookInDesc { mainReport(¬ImportantMsg, self); } |
| | 2130 | ; |
| | 2131 | |
| | 2132 | /* ------------------------------------------------------------------------ */ |
| | 2133 | /* |
| | 2134 | * An "unthing" is an object that represents the *absence* of an object. |
| | 2135 | * It's occasionally useful to respond specially when the player mentions |
| | 2136 | * an object that isn't present, especially when the player is likely to |
| | 2137 | * assume that something is present. |
| | 2138 | * |
| | 2139 | * An unthing is essentially a decoration, but we use a customized |
| | 2140 | * message that says "that isn't here" rather than "that isn't |
| | 2141 | * important". |
| | 2142 | */ |
| | 2143 | class Unthing: Decoration |
| | 2144 | /* |
| | 2145 | * The message to display when the player refers to this object. |
| | 2146 | * This can be a library message property, or a single-quoted string. |
| | 2147 | * This message will probably always be overridden in practice, since |
| | 2148 | * the point of this class is to provide a more specific explanation |
| | 2149 | * of why the object isn't here. |
| | 2150 | */ |
| | 2151 | notHereMsg = &unthingNotHereMsg |
| | 2152 | |
| | 2153 | /* an Unthing shouldn't be picked as a default */ |
| | 2154 | hideFromDefault(action) { return true; } |
| | 2155 | |
| | 2156 | /* |
| | 2157 | * by default, use our 'not here' message for our descriptions (in |
| | 2158 | * all of the standard senses) |
| | 2159 | */ |
| | 2160 | basicExamine() { mainReport(notHereMsg, self); } |
| | 2161 | basicExamineListen(explicit) |
| | 2162 | { |
| | 2163 | if (explicit) |
| | 2164 | mainReport(notHereMsg, self); |
| | 2165 | } |
| | 2166 | basicExamineSmell(explicit) |
| | 2167 | { |
| | 2168 | if (explicit) |
| | 2169 | mainReport(notHereMsg, self); |
| | 2170 | } |
| | 2171 | |
| | 2172 | /* use our custom message for the inherited Decoration responses */ |
| | 2173 | notImportantMsg = (notHereMsg) |
| | 2174 | |
| | 2175 | /* |
| | 2176 | * Because we're not actually here, use custom error messages when |
| | 2177 | * we're used as a possessive or locational qualifier. The standard |
| | 2178 | * messages say things like "Bob doesn't appear to have that" or "You |
| | 2179 | * don't see that in the box," but these don't make sense for an |
| | 2180 | * Unthing - we're not actually here, so we can't "appear" or "seem" |
| | 2181 | * to own or contain anything. Instead, we need to indicate that the |
| | 2182 | * qualifying object itself (i.e., 'self') isn't here at all. |
| | 2183 | */ |
| | 2184 | throwNoMatchForPossessive(txt) { throwUnthingAsQualifier(); } |
| | 2185 | throwNoMatchForLocation(txt) { throwUnthingAsQualifier(); } |
| | 2186 | throwNothingInLocation() { throwUnthingAsQualifier(); } |
| | 2187 | |
| | 2188 | /* |
| | 2189 | * throw a generic message when we're used as a qualifier - we'll |
| | 2190 | * simply get our "not here" message and display that |
| | 2191 | */ |
| | 2192 | throwUnthingAsQualifier() |
| | 2193 | { |
| | 2194 | local msg; |
| | 2195 | |
| | 2196 | /* |
| | 2197 | * resolve our "not here" message to a string - we need to do |
| | 2198 | * this here, since we're too early in the parsing sequence for |
| | 2199 | * the normal "mainResult" type of processing |
| | 2200 | */ |
| | 2201 | msg = MessageResult.resolveMessageText([self], ¬HereMsg, [self]); |
| | 2202 | |
| | 2203 | /* throw a parser exception that will display this literal text */ |
| | 2204 | throw new ParseFailureException(&parserErrorString, msg); |
| | 2205 | } |
| | 2206 | |
| | 2207 | /* |
| | 2208 | * if there's anything at all in a resolve list other than me, always |
| | 2209 | * remove me |
| | 2210 | */ |
| | 2211 | filterResolveList(lst, action, whichObj, np, requiredNum) |
| | 2212 | { |
| | 2213 | /* if the list has anything else in it, remove myself */ |
| | 2214 | if (lst.length() != 1) |
| | 2215 | lst = lst.removeElementAt(lst.indexWhich({x: x.obj_ == self})); |
| | 2216 | |
| | 2217 | /* return the list */ |
| | 2218 | return lst; |
| | 2219 | } |
| | 2220 | |
| | 2221 | /* |
| | 2222 | * trying to given an order to an Unthing acts the same way as any |
| | 2223 | * other kind of interaction |
| | 2224 | */ |
| | 2225 | acceptCommand(issuingActor) { mainReport(notHereMsg, self); } |
| | 2226 | ; |
| | 2227 | |
| | 2228 | |
| | 2229 | /* ------------------------------------------------------------------------ */ |
| | 2230 | /* |
| | 2231 | * Distant item. This is an object that's too far away to manipulate, |
| | 2232 | * but can be seen. This is useful for scenery objects that are at a |
| | 2233 | * great distance within a large location. |
| | 2234 | * |
| | 2235 | * A Distant item is essentially just like a decoration, but the default |
| | 2236 | * message is different. Note that this class is based on Fixture, which |
| | 2237 | * means that it should be *obvious* that the object is too far away to |
| | 2238 | * take or move. |
| | 2239 | */ |
| | 2240 | class Distant: Fixture |
| | 2241 | /* don't include in 'all' */ |
| | 2242 | hideFromAll(action) { return true; } |
| | 2243 | |
| | 2244 | dobjFor(Default) |
| | 2245 | { |
| | 2246 | verify() { illogical(&tooDistantMsg, self); } |
| | 2247 | } |
| | 2248 | iobjFor(Default) |
| | 2249 | { |
| | 2250 | verify() { illogical(&tooDistantMsg, self); } |
| | 2251 | } |
| | 2252 | |
| | 2253 | /* |
| | 2254 | * Explicitly allow examining and listening to a Distant item. To |
| | 2255 | * do this, override the 'verify' methods explicitly; we only need |
| | 2256 | * to inherit the base class handling, but we need to explicitly do |
| | 2257 | * so to 'override' the catch-all default handlers. |
| | 2258 | */ |
| | 2259 | dobjFor(Examine) { verify { inherited() ; } } |
| | 2260 | dobjFor(ListenTo) { verify() { inherited(); } } |
| | 2261 | |
| | 2262 | /* similarly, allow showing a distant item */ |
| | 2263 | dobjFor(ShowTo) { verify() { inherited(); } } |
| | 2264 | ; |
| | 2265 | |
| | 2266 | /* |
| | 2267 | * Out Of Reach - this is a special mix-in that can be used to create an |
| | 2268 | * object that places its *contents* out of reach under customizable |
| | 2269 | * conditions, and can optionally place itself out of reach as well. |
| | 2270 | */ |
| | 2271 | class OutOfReach: object |
| | 2272 | checkTouchViaPath(obj, dest, op) |
| | 2273 | { |
| | 2274 | /* check how we're traversing the object */ |
| | 2275 | if (op == PathTo) |
| | 2276 | { |
| | 2277 | /* |
| | 2278 | * we're reaching from outside for this object itself - |
| | 2279 | * check to see if the source can reach me |
| | 2280 | */ |
| | 2281 | if (!canObjReachSelf(obj)) |
| | 2282 | return new CheckStatusFailure( |
| | 2283 | cannotReachFromOutsideMsg(dest), dest); |
| | 2284 | } |
| | 2285 | else if (op == PathIn) |
| | 2286 | { |
| | 2287 | /* |
| | 2288 | * we're reaching in to touch one of my contents - check to |
| | 2289 | * see if the source object is within reach of my contents |
| | 2290 | */ |
| | 2291 | if (!canObjReachContents(obj)) |
| | 2292 | return new CheckStatusFailure( |
| | 2293 | cannotReachFromOutsideMsg(dest), dest); |
| | 2294 | } |
| | 2295 | else if (op == PathOut) |
| | 2296 | { |
| | 2297 | local ok; |
| | 2298 | |
| | 2299 | /* |
| | 2300 | * We're reaching out. If we're reaching for the object |
| | 2301 | * itself, check to see if we're reachable from within; |
| | 2302 | * otherwise, check to see if we can reach objects outside |
| | 2303 | * us from within. |
| | 2304 | */ |
| | 2305 | if (dest == self) |
| | 2306 | ok = canReachSelfFromInside(obj); |
| | 2307 | else |
| | 2308 | ok = canReachFromInside(obj, dest); |
| | 2309 | |
| | 2310 | /* if we can't reach the object, say so */ |
| | 2311 | if (!ok) |
| | 2312 | return new CheckStatusFailure( |
| | 2313 | cannotReachFromInsideMsg(dest), dest); |
| | 2314 | } |
| | 2315 | |
| | 2316 | /* if we didn't find a problem, allow the operation */ |
| | 2317 | return checkStatusSuccess; |
| | 2318 | } |
| | 2319 | |
| | 2320 | /* |
| | 2321 | * The message to use to indicate that we can't reach an object, |
| | 2322 | * because the actor is outside me and the target is inside, or vice |
| | 2323 | * versa. Each of these can return a property ID giving an actor |
| | 2324 | * action message property, or can simply return a string with the |
| | 2325 | * message text. |
| | 2326 | */ |
| | 2327 | cannotReachFromOutsideMsg(dest) { return &tooDistantMsg; } |
| | 2328 | cannotReachFromInsideMsg(dest) { return &tooDistantMsg; } |
| | 2329 | |
| | 2330 | /* |
| | 2331 | * Determine if the given object can reach my contents. 'obj' is |
| | 2332 | * the object (usually an actor) attempting to reach my contents |
| | 2333 | * from outside of me. |
| | 2334 | * |
| | 2335 | * By default, we'll return nil, so that nothing within me can be |
| | 2336 | * reached from anyone outside. This can be overridden to allow my |
| | 2337 | * contents to become reachable from some external locations but not |
| | 2338 | * others; for example, a high shelf could allow an actor standing |
| | 2339 | * on a chair to reach my contents. |
| | 2340 | */ |
| | 2341 | canObjReachContents(obj) { return nil; } |
| | 2342 | |
| | 2343 | /* |
| | 2344 | * Determine if the given object can reach me. 'obj' is the object |
| | 2345 | * (usually an actor) attempting to reach this object. |
| | 2346 | * |
| | 2347 | * By default, make this object subject to the same rules as its |
| | 2348 | * contents. |
| | 2349 | */ |
| | 2350 | canObjReachSelf(obj) { return canObjReachContents(obj); } |
| | 2351 | |
| | 2352 | /* |
| | 2353 | * Determine if the given object outside of me is reachable from |
| | 2354 | * within me. 'obj' (usually an actor) is attempting to reach |
| | 2355 | * 'dest'. |
| | 2356 | * |
| | 2357 | * By default, we return nil, so nothing outside of me is reachable |
| | 2358 | * from within me. This can be overridden as needed. This should |
| | 2359 | * usually behave symmetrically with canObjReachContents(). |
| | 2360 | */ |
| | 2361 | canReachFromInside(obj, dest) { return nil; } |
| | 2362 | |
| | 2363 | /* |
| | 2364 | * Determine if we can reach this object itself from within. This |
| | 2365 | * is used when 'obj' tries to touch this object when 'obj' is |
| | 2366 | * located within this object. |
| | 2367 | * |
| | 2368 | * By default, we we use the same rules as we use to reach an |
| | 2369 | * external object from within. |
| | 2370 | */ |
| | 2371 | canReachSelfFromInside(obj) { return canReachFromInside(obj, self); } |
| | 2372 | |
| | 2373 | /* |
| | 2374 | * We cannot implicitly remove this obstruction, so simply return |
| | 2375 | * nil when asked. |
| | 2376 | */ |
| | 2377 | tryImplicitRemoveObstructor(sense, obj) { return nil; } |
| | 2378 | ; |
| | 2379 | |
| | 2380 | /* ------------------------------------------------------------------------ */ |
| | 2381 | /* |
| | 2382 | * A Fill Medium - this is the class of object returned from |
| | 2383 | * Thing.fillMedium(). |
| | 2384 | */ |
| | 2385 | class FillMedium: Thing |
| | 2386 | /* |
| | 2387 | * Get the transparency sensing through this medium. |
| | 2388 | */ |
| | 2389 | senseThru(sense) |
| | 2390 | { |
| | 2391 | /* |
| | 2392 | * if I have a meterial, use its transparency; otherwise, we're |
| | 2393 | * transparent |
| | 2394 | */ |
| | 2395 | return (material != nil ? material.senseThru(sense) : transparent); |
| | 2396 | } |
| | 2397 | |
| | 2398 | /* my material */ |
| | 2399 | material = nil |
| | 2400 | ; |
| | 2401 | |
| | 2402 | /* ------------------------------------------------------------------------ */ |
| | 2403 | /* |
| | 2404 | * Base multi-location item with automatic initialization. This is the |
| | 2405 | * base class for various multi-located object classes. |
| | 2406 | * |
| | 2407 | * We provide four ways of initializing a multi-located object's set of |
| | 2408 | * locations. |
| | 2409 | * |
| | 2410 | * First, the object can simply enumerate the locations explicitly, by |
| | 2411 | * setting the 'locationList' property to the list of locations. |
| | 2412 | * |
| | 2413 | * Second, the object can indicate that it's located in every object of a |
| | 2414 | * given class, by setting the 'initialLocationClass' property to the |
| | 2415 | * desired class. |
| | 2416 | * |
| | 2417 | * Third, the object can define a rule that specifies which objects are |
| | 2418 | * its initial locations, by defining the 'isInitiallyIn(obj)' method to |
| | 2419 | * return true if 'obj' is an initial location, nil if not. This can be |
| | 2420 | * combined with the 'initialLocationClass' mechanism: if |
| | 2421 | * 'initialLocationClass' is non-nil, then only objects of the given |
| | 2422 | * class will be tested with 'isInitiallyIn()'; if 'initialLocationClass' |
| | 2423 | * is nil, then every object in the entire game will be tested. |
| | 2424 | * |
| | 2425 | * Fourth, you can override the method buildLocationList() to build an |
| | 2426 | * return the initial list of locations. You can use this approach if |
| | 2427 | * you have a complex set of rules for determining the initial location |
| | 2428 | * list, and none of the above approaches are flexible enough to |
| | 2429 | * implement it. If you override buildLocationList(), simply compute and |
| | 2430 | * return the list of initial locations; the library will automatically |
| | 2431 | * call the method during pre-initialization. |
| | 2432 | * |
| | 2433 | * If you don't define any of these, then the object simply has no |
| | 2434 | * initial locations by default. |
| | 2435 | */ |
| | 2436 | class BaseMultiLoc: object |
| | 2437 | /* |
| | 2438 | * The location list. Instances can override this to manually |
| | 2439 | * enumerate our initial locations. By default, we'll call |
| | 2440 | * buildLocationList() the first time this is invoked, and store the |
| | 2441 | * result. |
| | 2442 | */ |
| | 2443 | locationList = perInstance(buildLocationList()) |
| | 2444 | |
| | 2445 | /* |
| | 2446 | * The class of our initial locations. If this is nil, then our |
| | 2447 | * default buildLocationList() method will test every object in the |
| | 2448 | * entire game with our isInitiallyIn() method; otherwise, we'll test |
| | 2449 | * only objects of the given class. |
| | 2450 | */ |
| | 2451 | initialLocationClass = nil |
| | 2452 | |
| | 2453 | /* |
| | 2454 | * Test an object for inclusion in our initial location list. By |
| | 2455 | * default, we'll simply return true to include every object. We |
| | 2456 | * return true by default so that an instance can merely specify a |
| | 2457 | * value for initialLocationClass in order to place this object in |
| | 2458 | * every instance of the given class. |
| | 2459 | */ |
| | 2460 | isInitiallyIn(obj) { return true; } |
| | 2461 | |
| | 2462 | /* |
| | 2463 | * Build my list of locations, and return the list. This default |
| | 2464 | * implementation looks for an 'initialLocationClass' property value, |
| | 2465 | * and if one is found, looks at every object of that class; |
| | 2466 | * otherwise, it looks at every object in the entire game. In either |
| | 2467 | * case, each object is then passed to our isInitiallyIn() method, |
| | 2468 | * and is included in our result list if isInitiallyIn() returns |
| | 2469 | * true. |
| | 2470 | */ |
| | 2471 | buildLocationList() |
| | 2472 | { |
| | 2473 | local lst; |
| | 2474 | |
| | 2475 | /* |
| | 2476 | * If the object doesn't define any of the standard rules, which |
| | 2477 | * it would do by overriding initialLocationClass and/or |
| | 2478 | * isInitiallyIn(), then simply return an empty list. We take |
| | 2479 | * the absence of overrides for any of the rules to mean that the |
| | 2480 | * object simply has no initial locations. |
| | 2481 | */ |
| | 2482 | if (initialLocationClass == nil |
| | 2483 | && !overrides(self, BaseMultiLoc, &isInitiallyIn)) |
| | 2484 | return []; |
| | 2485 | |
| | 2486 | /* we have nothing in our list yet */ |
| | 2487 | lst = new Vector(16); |
| | 2488 | |
| | 2489 | /* |
| | 2490 | * if initialLocationClass is defined, loop over all objects of |
| | 2491 | * that class; otherwise, loop over all objects |
| | 2492 | */ |
| | 2493 | if (initialLocationClass != nil) |
| | 2494 | { |
| | 2495 | /* loop over all instances of the given class */ |
| | 2496 | for (local obj = firstObj(initialLocationClass) ; obj != nil ; |
| | 2497 | obj = nextObj(obj, initialLocationClass)) |
| | 2498 | { |
| | 2499 | /* if the object passes the test, include it */ |
| | 2500 | if (isInitiallyIn(obj)) |
| | 2501 | lst.append(obj); |
| | 2502 | } |
| | 2503 | } |
| | 2504 | else |
| | 2505 | { |
| | 2506 | /* loop over all objects */ |
| | 2507 | for (local obj = firstObj() ; obj != nil ; obj = nextObj(obj)) |
| | 2508 | { |
| | 2509 | /* if the object passes the test, include it */ |
| | 2510 | if (isInitiallyIn(obj)) |
| | 2511 | lst.append(obj); |
| | 2512 | } |
| | 2513 | } |
| | 2514 | |
| | 2515 | /* return the list of locations */ |
| | 2516 | return lst.toList(); |
| | 2517 | } |
| | 2518 | |
| | 2519 | /* determine if I'm in a given object, directly or indirectly */ |
| | 2520 | isIn(obj) |
| | 2521 | { |
| | 2522 | /* first, check to see if I'm directly in the given object */ |
| | 2523 | if (isDirectlyIn(obj)) |
| | 2524 | return true; |
| | 2525 | |
| | 2526 | /* |
| | 2527 | * Look at each object in my location list. For each location |
| | 2528 | * object, if the location is within the object, I'm within the |
| | 2529 | * object. |
| | 2530 | */ |
| | 2531 | return locationList.indexWhich({loc: loc.isIn(obj)}) != nil; |
| | 2532 | } |
| | 2533 | |
| | 2534 | /* determine if I'm directly in the given object */ |
| | 2535 | isDirectlyIn(obj) |
| | 2536 | { |
| | 2537 | /* |
| | 2538 | * we're directly in the given object only if the object is in |
| | 2539 | * my list of immediate locations |
| | 2540 | */ |
| | 2541 | return (locationList.indexOf(obj) != nil); |
| | 2542 | } |
| | 2543 | |
| | 2544 | /* Am I either inside 'obj', or equal to 'obj'? */ |
| | 2545 | isOrIsIn(obj) { return self == obj || isIn(obj); } |
| | 2546 | ; |
| | 2547 | |
| | 2548 | /* ------------------------------------------------------------------------ */ |
| | 2549 | /* |
| | 2550 | * MultiLoc: this class can be multiply inherited by any object that |
| | 2551 | * must exist in more than one place at a time. To use this class, put |
| | 2552 | * it BEFORE Thing (or any subclass of Thing) in the object's superclass |
| | 2553 | * list, to ensure that we override the default containment |
| | 2554 | * implementation for the object. |
| | 2555 | * |
| | 2556 | * Note that a MultiLoc object appears *in its entirety* in each of its |
| | 2557 | * locations. This means that MultiLoc is most suitable for a couple of |
| | 2558 | * specific situations: |
| | 2559 | * |
| | 2560 | * - several locations overlap slightly so that they include a common |
| | 2561 | * object: a large statue at the center of a public square, for example; |
| | 2562 | * |
| | 2563 | * - an object forms a sense connection among its location: a window; |
| | 2564 | * |
| | 2565 | * - a distant object that is seen in its entirety from several |
| | 2566 | * locations: the moon, say, or a mountain range. |
| | 2567 | * |
| | 2568 | * Note that MultiLoc is NOT suitable for cases where an object spans |
| | 2569 | * several locations but isn't contained entirely in any one of them: |
| | 2570 | * it's not good for something like a rope or a river, for example. |
| | 2571 | * MultiLoc also isn't good for cases where you simply want to avoid |
| | 2572 | * creating a bunch of repeated decorations in different locations. |
| | 2573 | * MultiLoc isn't good for these cases because a MultiLoc is treated as |
| | 2574 | * though it exists ENTIRELY and SIMULTANEOUSLY in all of its locations, |
| | 2575 | * which means that all of its sense information and internal state is |
| | 2576 | * shared among all of its locations. |
| | 2577 | * |
| | 2578 | * MultiInstance is better than MultiLoc for cases where you want to |
| | 2579 | * share a decoration object across several locations. MultiInstance is |
| | 2580 | * better because it creates individual copies of the object in the |
| | 2581 | * different locations, so each copy has its own separate sense |
| | 2582 | * information and its own separate identity. |
| | 2583 | * |
| | 2584 | * MultiFaceted is better for objects that span several locations, such |
| | 2585 | * as a river or a long rope. Like MultiInstance, MultiFaceted creates |
| | 2586 | * a separate copy in each location; in addition, MultiFaceted relates |
| | 2587 | * the copies together as "facets" of the same object, so that the |
| | 2588 | * parser knows they're all actually parts of one larger object. |
| | 2589 | */ |
| | 2590 | class MultiLoc: BaseMultiLoc |
| | 2591 | /* |
| | 2592 | * Initialize my location's contents list - add myself to my |
| | 2593 | * container during initialization |
| | 2594 | */ |
| | 2595 | initializeLocation() |
| | 2596 | { |
| | 2597 | /* add myself to each of my container's contents lists */ |
| | 2598 | locationList.forEach({loc: loc.addToContents(self)}); |
| | 2599 | } |
| | 2600 | |
| | 2601 | /* |
| | 2602 | * Re-initialize the location list. This calls buildLocationList() |
| | 2603 | * to re-evaluate the location rules, then updates the locationList |
| | 2604 | * to match the new results. We'll remove the MultiLoc from any old |
| | 2605 | * locations that are no longer part of the location list, and we'll |
| | 2606 | * add it to any new locations that weren't previously in the |
| | 2607 | * location list. You can call this at any time to update the |
| | 2608 | * MutliLoc's presence to reflect applying our location rules to the |
| | 2609 | * current game state. |
| | 2610 | * |
| | 2611 | * Note that this doesn't trigger any moveInto notifications. This |
| | 2612 | * routine is a re-initialization rather than an in-game action, so |
| | 2613 | * it's not meant to behave as though an actor in the game were |
| | 2614 | * walking around moving the MultiLoc around; thus no notifications |
| | 2615 | * are sent. Note also that we attempt to minimize our work by |
| | 2616 | * computing the "delta" from the old state - hence we only move the |
| | 2617 | * MultiLoc into containers it wasn't in previously, and we only |
| | 2618 | * remove it from existing containers that it's no longer in. |
| | 2619 | */ |
| | 2620 | reInitializeLocation() |
| | 2621 | { |
| | 2622 | local newList; |
| | 2623 | |
| | 2624 | /* build the new location list */ |
| | 2625 | newList = buildLocationList(); |
| | 2626 | |
| | 2627 | /* |
| | 2628 | * Update any containers that are not in the intersection of the |
| | 2629 | * two lists. Note that we don't simply move ourselves out of |
| | 2630 | * the old list and into the new list, because the two lists |
| | 2631 | * could have common members; to avoid unnecessary work that |
| | 2632 | * might result from removing ourselves from a container and |
| | 2633 | * then adding ourselves right back in to the same container, we |
| | 2634 | * only notify containers when we're actually moving out or |
| | 2635 | * moving in. |
| | 2636 | */ |
| | 2637 | |
| | 2638 | /* |
| | 2639 | * For each item in the old list, if it's not in the new list, |
| | 2640 | * notify the old container that we're being removed. |
| | 2641 | */ |
| | 2642 | foreach (local loc in locationList) |
| | 2643 | { |
| | 2644 | /* if it's not in the new list, remove me from the container */ |
| | 2645 | if (newList.indexOf(loc) == nil) |
| | 2646 | loc.removeFromContents(self); |
| | 2647 | } |
| | 2648 | |
| | 2649 | /* |
| | 2650 | * for each item in the new list, if we weren't already in this |
| | 2651 | * location, add ourselves to the location |
| | 2652 | */ |
| | 2653 | foreach (local loc in newList) |
| | 2654 | { |
| | 2655 | /* if it's not in the old list, add me to the new container */ |
| | 2656 | if (!isDirectlyIn(loc) == nil) |
| | 2657 | loc.addToContents(self); |
| | 2658 | } |
| | 2659 | |
| | 2660 | /* make the new location list current */ |
| | 2661 | locationList = newList; |
| | 2662 | } |
| | 2663 | |
| | 2664 | /* |
| | 2665 | * Note that we don't need to override any of the contents |
| | 2666 | * management methods, since we provide special handling for our |
| | 2667 | * location relationships, not for our contents relationships. |
| | 2668 | */ |
| | 2669 | |
| | 2670 | /* save my location for later restoration */ |
| | 2671 | saveLocation() |
| | 2672 | { |
| | 2673 | /* return my list of locations */ |
| | 2674 | return locationList; |
| | 2675 | } |
| | 2676 | |
| | 2677 | /* restore a previously saved location */ |
| | 2678 | restoreLocation(oldLoc) |
| | 2679 | { |
| | 2680 | /* remove myself from each current location not in the saved list */ |
| | 2681 | foreach (local cur in locationList) |
| | 2682 | { |
| | 2683 | /* |
| | 2684 | * if this present location isn't in the saved list, remove |
| | 2685 | * myself from the location |
| | 2686 | */ |
| | 2687 | if (oldLoc.indexOf(cur) == nil) |
| | 2688 | cur.removeFromContents(self); |
| | 2689 | } |
| | 2690 | |
| | 2691 | /* add myself to each saved location not in the current list */ |
| | 2692 | foreach (local cur in oldLoc) |
| | 2693 | { |
| | 2694 | /* if I'm not already in this location, add me to it */ |
| | 2695 | if (locationList.indexOf(cur) == nil) |
| | 2696 | cur.addToContents(self); |
| | 2697 | } |
| | 2698 | |
| | 2699 | /* set my own list to the original list */ |
| | 2700 | locationList = oldLoc; |
| | 2701 | } |
| | 2702 | |
| | 2703 | /* |
| | 2704 | * Basic routine to move this object into a given single container. |
| | 2705 | * Removes the object from all of its other containers. Performs no |
| | 2706 | * notifications. |
| | 2707 | */ |
| | 2708 | baseMoveInto(newContainer) |
| | 2709 | { |
| | 2710 | /* remove myself from all of my current contents */ |
| | 2711 | locationList.forEach({loc: loc.removeFromContents(self)}); |
| | 2712 | |
| | 2713 | /* set my location list to include only the new location */ |
| | 2714 | if (newContainer != nil) |
| | 2715 | { |
| | 2716 | /* set my new location */ |
| | 2717 | locationList = [newContainer]; |
| | 2718 | |
| | 2719 | /* add myself to my new container's contents */ |
| | 2720 | newContainer.addToContents(self); |
| | 2721 | } |
| | 2722 | else |
| | 2723 | { |
| | 2724 | /* we have no new locations */ |
| | 2725 | locationList = []; |
| | 2726 | } |
| | 2727 | } |
| | 2728 | |
| | 2729 | /* |
| | 2730 | * Add this object to a new location - base version that performs no |
| | 2731 | * notifications. |
| | 2732 | */ |
| | 2733 | baseMoveIntoAdd(newContainer) |
| | 2734 | { |
| | 2735 | /* add the new container to my list of locations */ |
| | 2736 | locationList += newContainer; |
| | 2737 | |
| | 2738 | /* add myself to my new container's contents */ |
| | 2739 | newContainer.addToContents(self); |
| | 2740 | } |
| | 2741 | |
| | 2742 | /* |
| | 2743 | * Add this object to a new location. |
| | 2744 | */ |
| | 2745 | moveIntoAdd(newContainer) |
| | 2746 | { |
| | 2747 | /* notify my new container that I'm about to be added */ |
| | 2748 | if (newContainer != nil) |
| | 2749 | newContainer.sendNotifyInsert(self, newContainer, ¬ifyInsert); |
| | 2750 | |
| | 2751 | /* perform base move-into-add operation */ |
| | 2752 | baseMoveIntoAdd(newContainer); |
| | 2753 | |
| | 2754 | /* note that I've been moved */ |
| | 2755 | moved = true; |
| | 2756 | } |
| | 2757 | |
| | 2758 | /* |
| | 2759 | * Base routine to move myself out of a given container. Performs |
| | 2760 | * no notifications. |
| | 2761 | */ |
| | 2762 | baseMoveOutOf(cont) |
| | 2763 | { |
| | 2764 | /* remove myself from this container's contents list */ |
| | 2765 | cont.removeFromContents(self); |
| | 2766 | |
| | 2767 | /* remove this container from my location list */ |
| | 2768 | locationList -= cont; |
| | 2769 | } |
| | 2770 | |
| | 2771 | /* |
| | 2772 | * Remove myself from a given container, leaving myself in any other |
| | 2773 | * containers. |
| | 2774 | */ |
| | 2775 | moveOutOf(cont) |
| | 2776 | { |
| | 2777 | /* if I'm not actually directly in this container, do nothing */ |
| | 2778 | if (!isDirectlyIn(cont)) |
| | 2779 | return; |
| | 2780 | |
| | 2781 | /* |
| | 2782 | * notify this container (and only this container) that we're |
| | 2783 | * being removed from it |
| | 2784 | */ |
| | 2785 | cont.sendNotifyRemove(obj, nil, ¬ifyRemove); |
| | 2786 | |
| | 2787 | /* perform base operation */ |
| | 2788 | baseMoveOutOf(cont); |
| | 2789 | |
| | 2790 | /* note that I've been moved */ |
| | 2791 | moved = true; |
| | 2792 | } |
| | 2793 | |
| | 2794 | /* |
| | 2795 | * Call a function on each container. We'll invoke the function as |
| | 2796 | * follows for each container 'cont': |
| | 2797 | * |
| | 2798 | * (func)(cont, args...) |
| | 2799 | */ |
| | 2800 | forEachContainer(func, [args]) |
| | 2801 | { |
| | 2802 | /* call the function for each location in our list */ |
| | 2803 | foreach(local cur in locationList) |
| | 2804 | (func)(cur, args...); |
| | 2805 | } |
| | 2806 | |
| | 2807 | /* |
| | 2808 | * Call a function on each connected container. By default, we |
| | 2809 | * don't connect our containers for sense purposes, so we do nothing |
| | 2810 | * here. |
| | 2811 | */ |
| | 2812 | forEachConnectedContainer(func, ...) { } |
| | 2813 | |
| | 2814 | /* |
| | 2815 | * get a list of my connected containers; by default, we don't |
| | 2816 | * connect our containers, so this is an empty list |
| | 2817 | */ |
| | 2818 | getConnectedContainers = [] |
| | 2819 | |
| | 2820 | /* |
| | 2821 | * Clone this object's contents for inclusion in a MultiInstance's |
| | 2822 | * contents tree. A MultiLoc is capable of being in multiple places |
| | 2823 | * at once, so we can just use our original contents tree as is. |
| | 2824 | */ |
| | 2825 | cloneMultiInstanceContents(loc) { } |
| | 2826 | |
| | 2827 | /* |
| | 2828 | * Create a clone of this object for inclusion in a MultiInstance's |
| | 2829 | * contents tree. We don't actually need to make a copy of the |
| | 2830 | * object, because a MultiLoc can be in several locations |
| | 2831 | * simultaneously; all we need to do is add ourselves to the new |
| | 2832 | * location. |
| | 2833 | */ |
| | 2834 | cloneForMultiInstanceContents(loc) |
| | 2835 | { |
| | 2836 | /* add myself into the new container */ |
| | 2837 | baseMoveIntoAdd(loc); |
| | 2838 | } |
| | 2839 | |
| | 2840 | /* |
| | 2841 | * Add the direct containment connections for this item to a lookup |
| | 2842 | * table. |
| | 2843 | * |
| | 2844 | * A MultiLoc does not, by default, connect its multiple locations |
| | 2845 | * together. This means that if we're traversing in from a point of |
| | 2846 | * view outside the MultiLoc object, we don't add any of our other |
| | 2847 | * containers to the connection table. However, the MultiLoc |
| | 2848 | * itself, and its contents, *can* see out to all of its locations; |
| | 2849 | * so if we're traversing from a point of view inside self, we will |
| | 2850 | * add all of our containers to the connection list. |
| | 2851 | */ |
| | 2852 | addDirectConnections(tab) |
| | 2853 | { |
| | 2854 | /* add myself */ |
| | 2855 | tab[self] = true; |
| | 2856 | |
| | 2857 | /* add my CollectiveGroup objects */ |
| | 2858 | foreach (local cur in collectiveGroups) |
| | 2859 | tab[cur] = true; |
| | 2860 | |
| | 2861 | /* add my contents */ |
| | 2862 | foreach (local cur in contents) |
| | 2863 | { |
| | 2864 | if (tab[cur] == nil) |
| | 2865 | cur.addDirectConnections(tab); |
| | 2866 | } |
| | 2867 | |
| | 2868 | /* |
| | 2869 | * If we're traversing from the outside in, don't connect any of |
| | 2870 | * our other containers. However, if we're traversing from our |
| | 2871 | * own point of view, or from a point of view inside us, we do |
| | 2872 | * get to see out to all of our containers. |
| | 2873 | */ |
| | 2874 | if (senseTmp.pointOfView == self || senseTmp.pointOfView.isIn(self)) |
| | 2875 | { |
| | 2876 | /* add my locations */ |
| | 2877 | foreach (local cur in locationList) |
| | 2878 | { |
| | 2879 | if (tab[cur] == nil) |
| | 2880 | cur.addDirectConnections(tab); |
| | 2881 | } |
| | 2882 | } |
| | 2883 | } |
| | 2884 | |
| | 2885 | /* |
| | 2886 | * Transmit ambient energy to my location or locations. Note that |
| | 2887 | * even though we don't by default shine light from one of our |
| | 2888 | * containers to another, we still shine light from within me to |
| | 2889 | * each of our containers. |
| | 2890 | */ |
| | 2891 | shineOnLoc(sense, ambient, fill) |
| | 2892 | { |
| | 2893 | /* shine on each of my containers and their immediate children */ |
| | 2894 | foreach (local cur in locationList) |
| | 2895 | cur.shineFromWithin(self, sense, ambient, fill); |
| | 2896 | } |
| | 2897 | |
| | 2898 | |
| | 2899 | /* |
| | 2900 | * Build a sense path to my location or locations. Note that even |
| | 2901 | * though we don't by default connect our different containers |
| | 2902 | * together, we still build a sense path from within to outside, |
| | 2903 | * because we can see from within out to all of our containers. |
| | 2904 | */ |
| | 2905 | sensePathToLoc(sense, trans, obs, fill) |
| | 2906 | { |
| | 2907 | /* build a path to each of my containers and their children */ |
| | 2908 | foreach (local cur in locationList) |
| | 2909 | cur.sensePathFromWithin(self, sense, trans, obs, fill); |
| | 2910 | } |
| | 2911 | |
| | 2912 | |
| | 2913 | /* |
| | 2914 | * Get the drop destination. The default implementation in Thing |
| | 2915 | * won't work for us, because it delegates to its location to find |
| | 2916 | * the drop destination; we can't do that because we could have |
| | 2917 | * several locations. To figure out which of our multiple locations |
| | 2918 | * to delegate to, we'll look for 'self' in the supplied sense path; |
| | 2919 | * if we can find it, and the previous path element is a container or |
| | 2920 | * peer of ours, then we'll delegate to that container, because it's |
| | 2921 | * the "side" we approached from. If there's no path, or if we're |
| | 2922 | * not preceded in the path by a container of ours, we'll arbitrarily |
| | 2923 | * delegate to our first container. |
| | 2924 | * |
| | 2925 | * Note that when we don't have a path, or there's no container of |
| | 2926 | * ours preceding us in the path, the object being dropped must be |
| | 2927 | * starting inside us. It would be highly unusual for this to happen |
| | 2928 | * with a multi-location object, because MutliLoc isn't designed for |
| | 2929 | * use as a "nested room" or the like. However, it's not an |
| | 2930 | * impossible situation; if the game does want to create such a |
| | 2931 | * scenario, then the game simply needs to override this routine so |
| | 2932 | * that it does whatever makes sense in the game scenario. There's |
| | 2933 | * no general way to handle such situations, but it should be |
| | 2934 | * possible to determine the correct handling for specific scenarios. |
| | 2935 | */ |
| | 2936 | getDropDestination(obj, path) |
| | 2937 | { |
| | 2938 | local idx; |
| | 2939 | |
| | 2940 | /* |
| | 2941 | * if there's no path, get the ordinary "touch" path from the |
| | 2942 | * current actor to us, since this is how the actor would reach |
| | 2943 | * out and touch this object |
| | 2944 | */ |
| | 2945 | if (path == nil) |
| | 2946 | path = gActor.getTouchPathTo(self); |
| | 2947 | |
| | 2948 | /* |
| | 2949 | * if there's a path, check to see if we're in it; if so, and |
| | 2950 | * we're not the first element, and the preceding element is a |
| | 2951 | * container or peer of ours, delegate to the preceding element |
| | 2952 | */ |
| | 2953 | if (path != nil |
| | 2954 | && (idx = path.indexOf(self)) != nil |
| | 2955 | && idx >= 3 |
| | 2956 | && path[idx - 1] is in (PathIn, PathPeer)) |
| | 2957 | { |
| | 2958 | /* |
| | 2959 | * we're preceded in the path by a container or peer of ours, |
| | 2960 | * so we know that we're approaching from that "side" - |
| | 2961 | * delegate to that container, since we're coming from that |
| | 2962 | * direction |
| | 2963 | */ |
| | 2964 | return path[idx - 2].getDropDestination(obj, path); |
| | 2965 | } |
| | 2966 | |
| | 2967 | /* |
| | 2968 | * We either don't have a path, or we're not preceded in the path |
| | 2969 | * by one of our containers or peers, so we don't have any idea |
| | 2970 | * which "side" we're approaching from. This means we have no |
| | 2971 | * good basis for deciding where the object being dropped will |
| | 2972 | * fall. Arbitrarily delegate to our first container, if we have |
| | 2973 | * one. |
| | 2974 | */ |
| | 2975 | return locationList.length() > 0 |
| | 2976 | ? locationList[1].getDropDestination(obj, path) |
| | 2977 | : nil; |
| | 2978 | } |
| | 2979 | ; |
| | 2980 | |
| | 2981 | /* ------------------------------------------------------------------------ */ |
| | 2982 | /* |
| | 2983 | * A "multi-instance" object is a simple way of creating copies of an |
| | 2984 | * object in several places. This is often useful for decorations and |
| | 2985 | * other features that recur in a whole group of rooms. |
| | 2986 | * |
| | 2987 | * You define a multi-instance object in two parts. |
| | 2988 | * |
| | 2989 | * First, you define a MultiInstance object, which is just a hollow |
| | 2990 | * shell of an object that sets up the location relationships. This |
| | 2991 | * shell object doesn't have any presence in the game world; it's just a |
| | 2992 | * programming abstraction. |
| | 2993 | * |
| | 2994 | * Second, as part of the shell object, you define an example of the |
| | 2995 | * object that will actually show up in the game in each of the multiple |
| | 2996 | * locations. You do this by defining a nested object under the |
| | 2997 | * 'instanceObject' property of the shell object. This is otherwise a |
| | 2998 | * perfectly ordinary object. In most cases, you'll want to make this a |
| | 2999 | * Decoration, Fixture, or some other non-portable object class, since |
| | 3000 | * the "cloned" nature of these objects means that you usually won't |
| | 3001 | * want them moving around (if they did, you might run into situations |
| | 3002 | * where you had several of them in the same place, leading to |
| | 3003 | * disambiguation headaches for the player). |
| | 3004 | * |
| | 3005 | * Here's an example of how you set up a multi-instance object: |
| | 3006 | * |
| | 3007 | * trees: MultiInstance |
| | 3008 | *. locationList = [forest1, forest2, forest3] |
| | 3009 | *. instanceObject: Fixture { 'tree/trees' 'trees' |
| | 3010 | *. "Many tall, old trees grow here. " |
| | 3011 | *. isPlural = true |
| | 3012 | *. } |
| | 3013 | *. ; |
| | 3014 | * |
| | 3015 | * Note that the instanceObject itself has no location, because it |
| | 3016 | * doesn't appear in the game-world model itself - it's just a template |
| | 3017 | * for the real objects. |
| | 3018 | * |
| | 3019 | * During initialization, the library will automatically create several |
| | 3020 | * instances (i.e., subclasses) of the example object - one instance per |
| | 3021 | * location, to be exact. These instances are the real objects that |
| | 3022 | * show up in the game world. |
| | 3023 | * |
| | 3024 | * MultiInstance has one more helpful feature: it lets you dynamically |
| | 3025 | * change the set of locations where the instances appear. You do this |
| | 3026 | * using the same interface that you use to move around MultiLoc objects |
| | 3027 | * - moveInto(), moveIntoAdd(), moveOutOf(). When you call these |
| | 3028 | * routines on the MultiInstance shell object, it will add and remove |
| | 3029 | * object instances as needed to keep everything consistent. Thanks to |
| | 3030 | * a little manipulation we do on the instance objects when we set them up, |
| | 3031 | * you can also move the instance objects around directly using |
| | 3032 | * moveInto(), and they'll update the MultiInstance parent to keep its |
| | 3033 | * location list consistent. |
| | 3034 | */ |
| | 3035 | class MultiInstance: BaseMultiLoc |
| | 3036 | /* the template object */ |
| | 3037 | instanceObject = nil |
| | 3038 | |
| | 3039 | /* initialize my locations */ |
| | 3040 | initializeLocation() |
| | 3041 | { |
| | 3042 | /* create a copy of our template object for each of our locations */ |
| | 3043 | locationList.forEach({loc: addInstance(loc)}); |
| | 3044 | } |
| | 3045 | |
| | 3046 | /* |
| | 3047 | * Move the MultiInstance into the given location. This removes us |
| | 3048 | * from any other existing locations and adds us (if we're not |
| | 3049 | * already there) to the given location. |
| | 3050 | */ |
| | 3051 | moveInto(loc) |
| | 3052 | { |
| | 3053 | /* remove all instances that aren't in the new location */ |
| | 3054 | foreach (local cur in instanceList) |
| | 3055 | { |
| | 3056 | /* if this instance isn't directly in 'loc', remove it */ |
| | 3057 | if (!cur.isDirectlyIn(loc)) |
| | 3058 | cur.moveInto(nil); |
| | 3059 | } |
| | 3060 | |
| | 3061 | /* |
| | 3062 | * If I don't have an instance object in the new location, add |
| | 3063 | * one. Since I've dropped every other instance already, we |
| | 3064 | * either have exactly one location now, which is in the new |
| | 3065 | * location, or we have no locations at all; so we need only |
| | 3066 | * check to see if we have any instances and add one in the new |
| | 3067 | * location if not. |
| | 3068 | */ |
| | 3069 | if (loc != nil && locationList.length() == 0) |
| | 3070 | addInstance(loc); |
| | 3071 | } |
| | 3072 | |
| | 3073 | /* |
| | 3074 | * Add the new location to our set of locations. Any existing |
| | 3075 | * locations are unaffected. |
| | 3076 | */ |
| | 3077 | moveIntoAdd(loc) |
| | 3078 | { |
| | 3079 | /* if I'm not already in the location, add an instance there */ |
| | 3080 | if (locationList.indexOf(loc) == nil) |
| | 3081 | addInstance(loc); |
| | 3082 | } |
| | 3083 | |
| | 3084 | /* |
| | 3085 | * Remove me from the given location. Other locations are |
| | 3086 | * unaffected. |
| | 3087 | */ |
| | 3088 | moveOutOf(loc) |
| | 3089 | { |
| | 3090 | local inst; |
| | 3091 | |
| | 3092 | /* find our instance that's in the given location */ |
| | 3093 | inst = getInstanceIn(loc); |
| | 3094 | |
| | 3095 | /* if we found it, remove this instance from its location */ |
| | 3096 | if (inst != nil) |
| | 3097 | inst.moveInto(nil); |
| | 3098 | } |
| | 3099 | |
| | 3100 | /* get our instance object (if any) that's in the given location */ |
| | 3101 | getInstanceIn(loc) |
| | 3102 | { return instanceList.valWhich({x: x.isDirectlyIn(loc)}); } |
| | 3103 | |
| | 3104 | /* internal service routine - add an instance for a given location */ |
| | 3105 | addInstance(loc) |
| | 3106 | { |
| | 3107 | local inst; |
| | 3108 | |
| | 3109 | /* |
| | 3110 | * Create an instance of the template object, mixing in our |
| | 3111 | * special instance superclass using multiple inheritance. The |
| | 3112 | * MultiInstanceInstance superclass overrides the location |
| | 3113 | * manipulation methods so that we keep the MultiInstance parent |
| | 3114 | * (i.e., us) synchronized if we move around the instance object |
| | 3115 | * directly (by calling its moveInto() method directly, for |
| | 3116 | * example). |
| | 3117 | */ |
| | 3118 | inst = TadsObject.createInstanceOf( |
| | 3119 | [instanceMixIn, self], [instanceObject]); |
| | 3120 | |
| | 3121 | /* add it to our list of active instances */ |
| | 3122 | instanceList.append(inst); |
| | 3123 | |
| | 3124 | /* move the instance into its new location */ |
| | 3125 | inst.moveInto(loc); |
| | 3126 | } |
| | 3127 | |
| | 3128 | /* |
| | 3129 | * If any contents are added to the MultiInstance object, they must |
| | 3130 | * be contents of the template object, so add them to the template |
| | 3131 | * object instead of the MultiInstance parent. |
| | 3132 | */ |
| | 3133 | addToContents(obj) { instanceObject.addToContents(obj); } |
| | 3134 | |
| | 3135 | /* |
| | 3136 | * remove an object from our contents - we'll delegate this to our |
| | 3137 | * template object just like we delegate addToContents |
| | 3138 | */ |
| | 3139 | removeFromContents(obj) { instanceObject.removeFromContents(obj); } |
| | 3140 | |
| | 3141 | /* the mix-in superclass for our instance objects */ |
| | 3142 | instanceMixIn = MultiInstanceInstance |
| | 3143 | |
| | 3144 | /* our vector of active instance objects */ |
| | 3145 | instanceList = perInstance(new Vector(5)) |
| | 3146 | ; |
| | 3147 | |
| | 3148 | /* |
| | 3149 | * An instance of a MultiInstance object. This is a mix-in class that |
| | 3150 | * we add (using mutiple inheritance) to each instance. This overrides |
| | 3151 | * the location manipulation methods, to ensure that we keep the |
| | 3152 | * MultiInstance parent object in sync with any changes made directly to |
| | 3153 | * the instance objects. |
| | 3154 | * |
| | 3155 | * IMPORTANT - the library adds this class to each instance object |
| | 3156 | * *automatically*. Game code shouldn't ever have to use this class |
| | 3157 | * directly. |
| | 3158 | */ |
| | 3159 | class MultiInstanceInstance: object |
| | 3160 | construct(parent) |
| | 3161 | { |
| | 3162 | /* remember our MultiInstance parent object */ |
| | 3163 | miParent = parent; |
| | 3164 | |
| | 3165 | /* |
| | 3166 | * clone my contents tree for the new instance, so that we have a |
| | 3167 | * private copy of any components within the instance |
| | 3168 | */ |
| | 3169 | cloneMultiInstanceContents(); |
| | 3170 | } |
| | 3171 | |
| | 3172 | /* move to a new location */ |
| | 3173 | baseMoveInto(newCont) |
| | 3174 | { |
| | 3175 | /* |
| | 3176 | * if we currently have a location, take the location out of our |
| | 3177 | * MultiInstance parent's location list |
| | 3178 | */ |
| | 3179 | if (location != nil) |
| | 3180 | miParent.locationList -= location; |
| | 3181 | |
| | 3182 | /* inherit the standard behavior */ |
| | 3183 | inherited(newCont); |
| | 3184 | |
| | 3185 | /* |
| | 3186 | * if we have a new location, add the new location to our |
| | 3187 | * MultiInstance parent's location list; otherwise, drop out of |
| | 3188 | * the parent's instance list |
| | 3189 | */ |
| | 3190 | if (newCont != nil) |
| | 3191 | { |
| | 3192 | /* |
| | 3193 | * add the new location to the parent's location list, if |
| | 3194 | * we're not already there |
| | 3195 | */ |
| | 3196 | if (miParent.locationList.indexOf(newCont) == nil) |
| | 3197 | miParent.locationList += newCont; |
| | 3198 | } |
| | 3199 | else |
| | 3200 | { |
| | 3201 | /* |
| | 3202 | * we're being removed from the game world, so remove this |
| | 3203 | * instance from the parent's instance list |
| | 3204 | */ |
| | 3205 | miParent.instanceList.removeElement(self); |
| | 3206 | } |
| | 3207 | } |
| | 3208 | |
| | 3209 | /* |
| | 3210 | * All instances of a given MultiInstance are equivalent to one |
| | 3211 | * another, for parsing purposes. |
| | 3212 | */ |
| | 3213 | isEquivalent = true |
| | 3214 | |
| | 3215 | /* our MultiInstance parent */ |
| | 3216 | miParent = nil |
| | 3217 | ; |
| | 3218 | |
| | 3219 | |
| | 3220 | /* ------------------------------------------------------------------------ */ |
| | 3221 | /* |
| | 3222 | * A "multi-faceted" object is similar to a MultiInstance object, with |
| | 3223 | * the addition that the instance objects are "facets" of one another. |
| | 3224 | * This means that they have the same identity, from the perspective of |
| | 3225 | * a character in the scenario: all of the instance objects are part of |
| | 3226 | * the same conceptual object, not separate objects. |
| | 3227 | * |
| | 3228 | * This is especially useful for large objects that span multiple |
| | 3229 | * locations, such as a river or a long rope. |
| | 3230 | * |
| | 3231 | * You define a multi-faceted object the same way you set up a |
| | 3232 | * MultiInstance: definfe a MultiFaceted shell object, and as part of |
| | 3233 | * the shell, define the facet object using the instanceObject property. |
| | 3234 | * Here's an example: |
| | 3235 | * |
| | 3236 | * river: MultiFaceted |
| | 3237 | *. locationList = [riverBank, meadow, canyon] |
| | 3238 | *. instanceObject: Fixture { 'river' 'river' |
| | 3239 | *. "The river meanders by. " |
| | 3240 | *. } |
| | 3241 | *. ; |
| | 3242 | * |
| | 3243 | * The main difference between MultiInstance and MultiFaceted is that |
| | 3244 | * the "facet" objects of a MultiFaceted are related as facets of a |
| | 3245 | * common object from the parser's perspective. For example, if a |
| | 3246 | * player refers to one facet, then travels to another location that |
| | 3247 | * contains a different facet, then refers to "it", the parser will |
| | 3248 | * realize that the pronoun refers to the new facet in the new location. |
| | 3249 | */ |
| | 3250 | class MultiFaceted: MultiInstance |
| | 3251 | /* our instance objects represent our facets for parsing purposes */ |
| | 3252 | getFacets() { return instanceList; } |
| | 3253 | |
| | 3254 | /* the mix-in superclass for our instance objects */ |
| | 3255 | instanceMixIn = MultiFacetedFacet |
| | 3256 | ; |
| | 3257 | |
| | 3258 | /* |
| | 3259 | * The mix-in superclass for MultiFaceted facet instances. |
| | 3260 | * |
| | 3261 | * IMPORTANT - the library adds this class to each instance object |
| | 3262 | * *automatically*. Game code shouldn't ever have to use this class |
| | 3263 | * directly. |
| | 3264 | */ |
| | 3265 | class MultiFacetedFacet: MultiInstanceInstance |
| | 3266 | /* |
| | 3267 | * Get our other facets for parsing purposes - our parent maintains |
| | 3268 | * the list of all of its facets, so simply return that list. (Note |
| | 3269 | * that we'll be in the list as well, but that's harmless, so don't |
| | 3270 | * bother removing us.) |
| | 3271 | */ |
| | 3272 | getFacets() { return miParent.getFacets(); } |
| | 3273 | ; |
| | 3274 | |
| | 3275 | /* ------------------------------------------------------------------------ */ |
| | 3276 | /* |
| | 3277 | * A "linkable" object is one that can participate in a master/slave |
| | 3278 | * relationship. This kind of relationship means that the state of both |
| | 3279 | * objects in the pair is controlled by one of the objects, called the |
| | 3280 | * master; the other object defers to the other to get and set all of |
| | 3281 | * its linkable state. |
| | 3282 | * |
| | 3283 | * Note that this base class doesn't provide for the management of any |
| | 3284 | * of the actual linked state. Subclasses are responsible for doing |
| | 3285 | * this. The general pattern is to create a getter/setter method pair |
| | 3286 | * for each bit of linked state, and in these methods refer to |
| | 3287 | * masterObject.xxx rather than just self.xxx. |
| | 3288 | * |
| | 3289 | * This is useful for objects such as doors that have two separate |
| | 3290 | * objects representing the two sides of the door. The two sides are |
| | 3291 | * always linked for things like open/closed and locked/unlocked state; |
| | 3292 | * this can be handled by linking the two sides, and managing all state |
| | 3293 | * of both sides in one side designated as the master. |
| | 3294 | */ |
| | 3295 | class Linkable: object |
| | 3296 | /* |
| | 3297 | * Get the master object, which holds our state. By default, this |
| | 3298 | * is simply 'self', but some objects might want to override this. |
| | 3299 | * For example, doors are usually implemented with two separate |
| | 3300 | * objects, representing the two sides of the door, which share |
| | 3301 | * common state; in such cases, one of the pair can be designated as |
| | 3302 | * the master, which holds the common state of the door, and this |
| | 3303 | * method can be overridden so that all state operations on the lock |
| | 3304 | * are performed on the master side of the door. |
| | 3305 | * |
| | 3306 | * We return self by default so that a linkable object can stand |
| | 3307 | * alone if desired. That is, a linkable object doesn't have to be |
| | 3308 | * part of a pair; it can just as well be a single object. |
| | 3309 | */ |
| | 3310 | masterObject() |
| | 3311 | { |
| | 3312 | /* |
| | 3313 | * inherit from the next superclass, if possible; otherwise, use |
| | 3314 | * 'self' as the default master object |
| | 3315 | */ |
| | 3316 | if (canInherit()) |
| | 3317 | return inherited(); |
| | 3318 | else |
| | 3319 | return self; |
| | 3320 | } |
| | 3321 | |
| | 3322 | /* |
| | 3323 | * We're normally mixed into a Thing; do some extra work in |
| | 3324 | * initialization. |
| | 3325 | */ |
| | 3326 | initializeThing() |
| | 3327 | { |
| | 3328 | /* inherit the default handling */ |
| | 3329 | inherited(); |
| | 3330 | |
| | 3331 | /* |
| | 3332 | * If we're tied to a separate master object, check the master |
| | 3333 | * object to see if it's tied back to us as its master object. |
| | 3334 | * Only one can be the master; if each says the other is the |
| | 3335 | * master, we'll get stuck in infinite loops as each tries to |
| | 3336 | * defer to the other. To avoid this, break the loop by |
| | 3337 | * arbitrarily choosing one or the other as the master. Note |
| | 3338 | * that we don't have to worry about the other object making a |
| | 3339 | * different decision and breaking the relationship, because if |
| | 3340 | * we detect the loop, it means we're going first - if the other |
| | 3341 | * object had gone first then it would have detected and broken |
| | 3342 | * the loop itself, and we wouldn't be finding the loop now. |
| | 3343 | */ |
| | 3344 | if (masterObject != self && masterObject.masterObject == self) |
| | 3345 | { |
| | 3346 | /* |
| | 3347 | * We're tied together in a loop - break the loop by |
| | 3348 | * arbitrarily electing myself as the master object. |
| | 3349 | * Because these relationships are symmetric, it shouldn't |
| | 3350 | * matter which we choose. |
| | 3351 | */ |
| | 3352 | masterObject = self; |
| | 3353 | } |
| | 3354 | } |
| | 3355 | ; |
| | 3356 | |
| | 3357 | |
| | 3358 | /* ------------------------------------------------------------------------ */ |
| | 3359 | /* |
| | 3360 | * A "basic openable" is an object that keeps open/closed status, and |
| | 3361 | * which can be linked to another object to maintain that status. This |
| | 3362 | * basic class doesn't handle any special commands; it's purely for |
| | 3363 | * keeping track of internal open/closed state. |
| | 3364 | */ |
| | 3365 | class BasicOpenable: Linkable |
| | 3366 | /* |
| | 3367 | * Initial open/closed setting. Set this to true to make the object |
| | 3368 | * open initially. If this object is linked to another object (as |
| | 3369 | * in the two sides of a door), you only need to set this property |
| | 3370 | * in the *master* object - the other side will automatically link |
| | 3371 | * up to the master object during initialization. |
| | 3372 | */ |
| | 3373 | initiallyOpen = nil |
| | 3374 | |
| | 3375 | /* |
| | 3376 | * Flag: door is open. Travel is only possible when the door is |
| | 3377 | * open. Return the master's status. |
| | 3378 | */ |
| | 3379 | isOpen() |
| | 3380 | { |
| | 3381 | /* |
| | 3382 | * If we're the master, simply use our isOpen_ property; |
| | 3383 | * otherwise, call our master's isOpen method. This way, if the |
| | 3384 | * master has a different way of calculating isOpen, we'll defer |
| | 3385 | * to its different handling. |
| | 3386 | */ |
| | 3387 | if (masterObject == self) |
| | 3388 | return isOpen_; |
| | 3389 | else |
| | 3390 | return masterObject.isOpen(); |
| | 3391 | } |
| | 3392 | |
| | 3393 | /* |
| | 3394 | * Make the object open or closed. By default, we'll simply set the |
| | 3395 | * isOpen flag to the new status. Objects can override this to |
| | 3396 | * apply side effects of opening or closing the object. |
| | 3397 | */ |
| | 3398 | makeOpen(stat) |
| | 3399 | { |
| | 3400 | /* |
| | 3401 | * if we're the master, simply set our isOpen_ property; |
| | 3402 | * otherwise, defer to the master |
| | 3403 | */ |
| | 3404 | if (masterObject == self) |
| | 3405 | isOpen_ = stat; |
| | 3406 | else |
| | 3407 | masterObject.makeOpen(stat); |
| | 3408 | |
| | 3409 | /* inherit the next superclass's handling */ |
| | 3410 | inherited(stat); |
| | 3411 | } |
| | 3412 | |
| | 3413 | /* |
| | 3414 | * Open status name. This is an adjective describing whether the |
| | 3415 | * object is opened or closed. In English, this will return "open" |
| | 3416 | * or "closed." |
| | 3417 | */ |
| | 3418 | openDesc = (isOpen ? gLibMessages.openMsg(self) |
| | 3419 | : gLibMessages.closedMsg(self)) |
| | 3420 | |
| | 3421 | /* initialization */ |
| | 3422 | initializeThing() |
| | 3423 | { |
| | 3424 | /* inherit the default handling */ |
| | 3425 | inherited(); |
| | 3426 | |
| | 3427 | /* if we're the master, set our initial open/closed state */ |
| | 3428 | if (masterObject == self) |
| | 3429 | isOpen_ = initiallyOpen; |
| | 3430 | } |
| | 3431 | |
| | 3432 | /* |
| | 3433 | * If we're obstructing a sense path, it must be because we're |
| | 3434 | * closed. Try implicitly opening. |
| | 3435 | */ |
| | 3436 | tryImplicitRemoveObstructor(sense, obj) |
| | 3437 | { |
| | 3438 | /* |
| | 3439 | * If I'm not already open, try opening me. As usual for 'try' |
| | 3440 | * routines, we return true if we attempt a command, nil if not. |
| | 3441 | * |
| | 3442 | * Note that we might be creating an obstruction despite already |
| | 3443 | * being open; in this case, we don't want to do anything, since |
| | 3444 | * an implied 'open' won't help when we're already open. |
| | 3445 | */ |
| | 3446 | return isOpen ? nil : tryImplicitAction(Open, self); |
| | 3447 | } |
| | 3448 | |
| | 3449 | /* |
| | 3450 | * if we can't reach or move something through the container, it |
| | 3451 | * must be because we're closed |
| | 3452 | */ |
| | 3453 | cannotTouchThroughMsg = &cannotTouchThroughClosedMsg |
| | 3454 | cannotMoveThroughMsg = &cannotMoveThroughClosedMsg |
| | 3455 | |
| | 3456 | /* |
| | 3457 | * Internal open/closed status. Do not use this for initialization |
| | 3458 | * - set initiallyOpen in the master object instead. |
| | 3459 | */ |
| | 3460 | isOpen_ = nil |
| | 3461 | ; |
| | 3462 | |
| | 3463 | /* ------------------------------------------------------------------------ */ |
| | 3464 | /* |
| | 3465 | * Openable: a mix-in class that can be combined with an object's other |
| | 3466 | * superclasses to make the object respond to the verbs "open" and |
| | 3467 | * "close." We also add some extra features for other related verbs, |
| | 3468 | * such as a must-be-open precondition "look in" and "board". |
| | 3469 | */ |
| | 3470 | class Openable: BasicOpenable |
| | 3471 | /* |
| | 3472 | * Describe our contents using a special version of the contents |
| | 3473 | * lister, so that we add our open/closed status to the listing. The |
| | 3474 | * message we add is given by our openStatus method, so if all you |
| | 3475 | * want to change is the "it's open" status message, you can just |
| | 3476 | * override openStatus rather than providing a whole new lister. |
| | 3477 | */ |
| | 3478 | descContentsLister = openableContentsLister |
| | 3479 | |
| | 3480 | /* |
| | 3481 | * Contents lister to use when we're opening the object. This |
| | 3482 | * lister shows the items that are newly revealed when the object is |
| | 3483 | * opened. |
| | 3484 | */ |
| | 3485 | openingLister = openableOpeningLister |
| | 3486 | |
| | 3487 | /* |
| | 3488 | * Get our "open status" message - this is a complete sentence saying |
| | 3489 | * that we're open or closed. By default, in English, we just say |
| | 3490 | * "it's open" (adjusted for number and gender, of course). |
| | 3491 | * |
| | 3492 | * Note that this message has to be a stand-alone independent clause. |
| | 3493 | * In particular note that we don't put any spacing after it, since |
| | 3494 | * we need to be able to add sentence-ending or clause-ending |
| | 3495 | * punctuation immediately after it. |
| | 3496 | */ |
| | 3497 | openStatus() { return gLibMessages.openStatusMsg(self); } |
| | 3498 | |
| | 3499 | /* |
| | 3500 | * By default, an Openable that's also a Lockable must be closed to |
| | 3501 | * be locked. This means that when it's open, the object is |
| | 3502 | * implicitly unlocked, in which case "It's unlocked" isn't worth |
| | 3503 | * mentioning when the description says "It's open." |
| | 3504 | */ |
| | 3505 | lockStatusReportable = (!isOpen) |
| | 3506 | |
| | 3507 | /* |
| | 3508 | * Action handlers |
| | 3509 | */ |
| | 3510 | dobjFor(Open) |
| | 3511 | { |
| | 3512 | verify() |
| | 3513 | { |
| | 3514 | /* it makes no sense to open something that's already open */ |
| | 3515 | if (isOpen) |
| | 3516 | illogicalAlready(&alreadyOpenMsg); |
| | 3517 | } |
| | 3518 | action() |
| | 3519 | { |
| | 3520 | local trans; |
| | 3521 | |
| | 3522 | /* |
| | 3523 | * note the effect we have currently, while still closed, on |
| | 3524 | * sensing from outside into our contents |
| | 3525 | */ |
| | 3526 | trans = transSensingIn(sight); |
| | 3527 | |
| | 3528 | /* make it open */ |
| | 3529 | makeOpen(true); |
| | 3530 | |
| | 3531 | /* |
| | 3532 | * make the default report - if we make a non-default |
| | 3533 | * report, the default will be ignored, so we don't need to |
| | 3534 | * worry about whether or not we'll make a non-default |
| | 3535 | * report now |
| | 3536 | */ |
| | 3537 | defaultReport(&okayOpenMsg); |
| | 3538 | |
| | 3539 | /* |
| | 3540 | * If the actor is outside me, and we have any listable |
| | 3541 | * contents, and our sight transparency is now better than it |
| | 3542 | * was before we were open, reveal the new contents. |
| | 3543 | * Otherwise, just show our default 'opened' message. |
| | 3544 | * |
| | 3545 | * As a special case, if we're running as an implied command |
| | 3546 | * within a LookIn or Search action on this same object, |
| | 3547 | * don't bother showing this result. Doing so would be |
| | 3548 | * redundant with the explicit examination of the contents |
| | 3549 | * that we'll be doing anyway with the main action. |
| | 3550 | */ |
| | 3551 | if (!gActor.isIn(self) |
| | 3552 | && transparencyCompare(transSensingIn(sight), trans) > 0 |
| | 3553 | && !(gAction.isImplicit |
| | 3554 | && (gAction.parentAction.ofKind(LookInAction) |
| | 3555 | || gAction.parentAction.ofKind(SearchAction)) |
| | 3556 | && gAction.parentAction.getDobj() == self)) |
| | 3557 | { |
| | 3558 | local tab; |
| | 3559 | |
| | 3560 | /* get the table of visible objects */ |
| | 3561 | tab = gActor.visibleInfoTable(); |
| | 3562 | |
| | 3563 | /* show my contents list, if I have any */ |
| | 3564 | openingLister.showList(gActor, self, contents, ListRecurse, |
| | 3565 | 0, tab, nil); |
| | 3566 | |
| | 3567 | /* mark my contents as having been seen */ |
| | 3568 | setContentsSeenBy(tab, gActor); |
| | 3569 | |
| | 3570 | /* show any special contents as well */ |
| | 3571 | examineSpecialContents(); |
| | 3572 | } |
| | 3573 | } |
| | 3574 | } |
| | 3575 | |
| | 3576 | dobjFor(Close) |
| | 3577 | { |
| | 3578 | verify() |
| | 3579 | { |
| | 3580 | /* it makes no sense to close something that's already closed */ |
| | 3581 | if (!isOpen) |
| | 3582 | illogicalAlready(&alreadyClosedMsg); |
| | 3583 | } |
| | 3584 | action() |
| | 3585 | { |
| | 3586 | /* make it closed */ |
| | 3587 | makeOpen(nil); |
| | 3588 | |
| | 3589 | /* show the default report */ |
| | 3590 | defaultReport(&okayCloseMsg); |
| | 3591 | } |
| | 3592 | } |
| | 3593 | |
| | 3594 | dobjFor(LookIn) |
| | 3595 | { |
| | 3596 | /* |
| | 3597 | * to look in an openable object, we must be open, unless the |
| | 3598 | * object is transparent or the actor is inside us |
| | 3599 | */ |
| | 3600 | preCond |
| | 3601 | { |
| | 3602 | local lst; |
| | 3603 | |
| | 3604 | /* get the inherited preconditions */ |
| | 3605 | lst = nilToList(inherited()); |
| | 3606 | |
| | 3607 | /* |
| | 3608 | * if I'm not transparent looking in, and the actor isn't |
| | 3609 | * already inside me, try opening me |
| | 3610 | */ |
| | 3611 | if (transSensingIn(sight) != transparent && !gActor.isIn(self)) |
| | 3612 | lst += objOpen; |
| | 3613 | |
| | 3614 | /* return the result */ |
| | 3615 | return lst; |
| | 3616 | } |
| | 3617 | } |
| | 3618 | |
| | 3619 | dobjFor(Search) |
| | 3620 | { |
| | 3621 | /* |
| | 3622 | * To search an openable object, we must be open - unlike LOOK |
| | 3623 | * IN, this applies even if the object is transparent, since |
| | 3624 | * SEARCH is inherently more aggressive than LOOK IN, and implies |
| | 3625 | * physically picking through the contents. This doesn't apply |
| | 3626 | * if the actor is already inside me. |
| | 3627 | */ |
| | 3628 | preCond |
| | 3629 | { |
| | 3630 | /* get the inherited preconditions */ |
| | 3631 | local lst = nilToList(inherited()); |
| | 3632 | |
| | 3633 | /* if the actor isn't in me, make sure I'm open */ |
| | 3634 | if (!gActor.isIn(self)) |
| | 3635 | lst += objOpen; |
| | 3636 | |
| | 3637 | /* |
| | 3638 | * searching implies physically sifting through the contents, |
| | 3639 | * so we need to be able to touch the object |
| | 3640 | */ |
| | 3641 | lst += touchObj; |
| | 3642 | |
| | 3643 | /* return the updated list */ |
| | 3644 | return lst; |
| | 3645 | } |
| | 3646 | } |
| | 3647 | |
| | 3648 | /* |
| | 3649 | * Generate a precondition to make sure gActor can reach the interior |
| | 3650 | * of the container. We consider the inside reachable if either the |
| | 3651 | * actor is located inside the container, or the actor is outside and |
| | 3652 | * the container is open. |
| | 3653 | */ |
| | 3654 | addInteriorReachableCond(lst) |
| | 3655 | { |
| | 3656 | /* |
| | 3657 | * If the actor's inside us, they can reach our interior whether |
| | 3658 | * we're open or not, so there's no need for any additional |
| | 3659 | * condition. If not, we need to be open for the actor to be |
| | 3660 | * able to reach our interior. |
| | 3661 | */ |
| | 3662 | if (!gActor.isIn(self)) |
| | 3663 | lst = nilToList(lst) + objOpen; |
| | 3664 | |
| | 3665 | /* return the result */ |
| | 3666 | return lst; |
| | 3667 | } |
| | 3668 | |
| | 3669 | iobjFor(PutIn) |
| | 3670 | { |
| | 3671 | /* make sure that our interior is reachable */ |
| | 3672 | preCond { return addInteriorReachableCond(inherited()); } |
| | 3673 | } |
| | 3674 | |
| | 3675 | iobjFor(PourInto) |
| | 3676 | { |
| | 3677 | /* make sure that our interior is reachable */ |
| | 3678 | preCond { return addInteriorReachableCond(inherited()); } |
| | 3679 | } |
| | 3680 | |
| | 3681 | /* can't lock an openable that isn't closed */ |
| | 3682 | dobjFor(Lock) |
| | 3683 | { |
| | 3684 | preCond { return nilToList(inherited()) + objClosed; } |
| | 3685 | } |
| | 3686 | dobjFor(LockWith) |
| | 3687 | { |
| | 3688 | preCond { return nilToList(inherited()) + objClosed; } |
| | 3689 | } |
| | 3690 | |
| | 3691 | /* must be open to get out of a nested room */ |
| | 3692 | dobjFor(GetOutOf) |
| | 3693 | { |
| | 3694 | preCond() |
| | 3695 | { |
| | 3696 | return nilToList(inherited()) |
| | 3697 | + new ObjectPreCondition(self, objOpen); |
| | 3698 | } |
| | 3699 | } |
| | 3700 | |
| | 3701 | /* must be open to get into a nested room */ |
| | 3702 | dobjFor(Board) |
| | 3703 | { |
| | 3704 | preCond() |
| | 3705 | { |
| | 3706 | return nilToList(inherited()) |
| | 3707 | + new ObjectPreCondition(self, objOpen); |
| | 3708 | } |
| | 3709 | } |
| | 3710 | ; |
| | 3711 | |
| | 3712 | /* ------------------------------------------------------------------------ */ |
| | 3713 | /* |
| | 3714 | * Lockable: a mix-in class that can be combined with an object's other |
| | 3715 | * superclasses to make the object respond to the verbs "lock" and |
| | 3716 | * "unlock." A Lockable requires no key. |
| | 3717 | * |
| | 3718 | * Note that Lockable should usually go BEFORE a Thing-derived class in |
| | 3719 | * the superclass list. |
| | 3720 | */ |
| | 3721 | class Lockable: Linkable |
| | 3722 | /* |
| | 3723 | * Our initial locked state (i.e., at the start of the game). By |
| | 3724 | * default, we start out locked. |
| | 3725 | */ |
| | 3726 | initiallyLocked = true |
| | 3727 | |
| | 3728 | /* |
| | 3729 | * Current locked state. Use our isLocked_ status if we're the |
| | 3730 | * master, otherwise defer to the master. |
| | 3731 | */ |
| | 3732 | isLocked() |
| | 3733 | { |
| | 3734 | if (masterObject == self) |
| | 3735 | return isLocked_; |
| | 3736 | else |
| | 3737 | return masterObject.isLocked(); |
| | 3738 | } |
| | 3739 | |
| | 3740 | /* |
| | 3741 | * Make the object locked or unlocked. Objects can override this to |
| | 3742 | * apply side effects of locking or unlocking. By default, if we're |
| | 3743 | * the master, we'll simply set our isLocked_ property to the new |
| | 3744 | * status, and otherwise defer to the master object. |
| | 3745 | */ |
| | 3746 | makeLocked(stat) |
| | 3747 | { |
| | 3748 | /* apply to self or the master object, as appropriate */ |
| | 3749 | if (masterObject == self) |
| | 3750 | isLocked_ = stat; |
| | 3751 | else |
| | 3752 | masterObject.makeLocked(stat); |
| | 3753 | |
| | 3754 | /* inherit the next superclass's handling */ |
| | 3755 | inherited(stat); |
| | 3756 | } |
| | 3757 | |
| | 3758 | /* show our status */ |
| | 3759 | examineStatus() |
| | 3760 | { |
| | 3761 | /* inherit the default handling */ |
| | 3762 | inherited(); |
| | 3763 | |
| | 3764 | /* |
| | 3765 | * if our lock status is visually apparent, and we want to |
| | 3766 | * mention the lock status in our current state, show the lock |
| | 3767 | * status |
| | 3768 | */ |
| | 3769 | if (lockStatusObvious && lockStatusReportable) |
| | 3770 | say(isLocked ? gLibMessages.currentlyLocked |
| | 3771 | : gLibMessages.currentlyUnlocked); |
| | 3772 | } |
| | 3773 | |
| | 3774 | /* description of the object as locked or unlocked */ |
| | 3775 | lockedDesc = (isLocked() ? gLibMessages.lockedMsg(self) |
| | 3776 | : gLibMessages.unlockedMsg(self)) |
| | 3777 | |
| | 3778 | /* |
| | 3779 | * Is our 'locked' status obvious? This should be set to true for an |
| | 3780 | * object whose locked/unlocked status can be visually observed, nil |
| | 3781 | * for an object whose status is not visuall apparent. For example, |
| | 3782 | * you can usually tell from the inside that a door is locked by |
| | 3783 | * looking at the position of the lock's paddle, but on the outside |
| | 3784 | * of a door there's usually no way to see the status. |
| | 3785 | * |
| | 3786 | * By default, since we can be locked and unlocked with simple LOCK |
| | 3787 | * and UNLOCK commands, we assume the status is as obvious as the |
| | 3788 | * mechanism must be to allow such simple commands. |
| | 3789 | */ |
| | 3790 | lockStatusObvious = true |
| | 3791 | |
| | 3792 | /* |
| | 3793 | * Is our 'locked' status reportable in our current state? This is |
| | 3794 | * similar to lockStatusObvious, but serves a separate purpose: this |
| | 3795 | * tells us if we wish to report the lock status for aesthetic |
| | 3796 | * reasons. |
| | 3797 | * |
| | 3798 | * This property is primarily of interest to mix-ins. To allow |
| | 3799 | * mix-ins to get a say, regardless of the order of superclasses, |
| | 3800 | * we'll by default defer to any inherited value if there is in fact |
| | 3801 | * an inherited value. If there's no inherited value, we'll simply |
| | 3802 | * return true. |
| | 3803 | * |
| | 3804 | * We use this in the library for one case in particular: when we're |
| | 3805 | * mixed with Openable, we don't want to report the lock status for |
| | 3806 | * an open object because an Openable must by default be closed to be |
| | 3807 | * locked. That is, when an Openable is open, it's always unlocked, |
| | 3808 | * so reporting that it's unlocked is essentially redundant |
| | 3809 | * information. |
| | 3810 | */ |
| | 3811 | lockStatusReportable = (canInherit() ? inherited() : true) |
| | 3812 | |
| | 3813 | /* |
| | 3814 | * Internal locked state. Do not use this to set the initial state |
| | 3815 | * - set initiallyLocked in the master object instead. |
| | 3816 | */ |
| | 3817 | isLocked_ = nil |
| | 3818 | |
| | 3819 | /* initialization */ |
| | 3820 | initializeThing() |
| | 3821 | { |
| | 3822 | /* inherit the default handling */ |
| | 3823 | inherited(); |
| | 3824 | |
| | 3825 | /* if we're the master, set our initial state */ |
| | 3826 | if (masterObject == self) |
| | 3827 | isLocked_ = initiallyLocked; |
| | 3828 | } |
| | 3829 | |
| | 3830 | /* |
| | 3831 | * Action handling |
| | 3832 | */ |
| | 3833 | |
| | 3834 | /* "lock" */ |
| | 3835 | dobjFor(Lock) |
| | 3836 | { |
| | 3837 | preCond = (nilToList(inherited()) + [touchObj]) |
| | 3838 | verify() |
| | 3839 | { |
| | 3840 | /* if we're already locked, there's no point in locking us */ |
| | 3841 | if (isLocked) |
| | 3842 | illogicalAlready(&alreadyLockedMsg); |
| | 3843 | } |
| | 3844 | action() |
| | 3845 | { |
| | 3846 | /* make it locked */ |
| | 3847 | makeLocked(true); |
| | 3848 | |
| | 3849 | /* make the default report */ |
| | 3850 | defaultReport(&okayLockMsg); |
| | 3851 | } |
| | 3852 | } |
| | 3853 | |
| | 3854 | /* "unlock" */ |
| | 3855 | dobjFor(Unlock) |
| | 3856 | { |
| | 3857 | preCond = (nilToList(inherited()) + [touchObj]) |
| | 3858 | verify() |
| | 3859 | { |
| | 3860 | /* if we're already unlocked, there's no point in doing this */ |
| | 3861 | if (!isLocked) |
| | 3862 | illogicalAlready(&alreadyUnlockedMsg); |
| | 3863 | } |
| | 3864 | action() |
| | 3865 | { |
| | 3866 | /* make it unlocked */ |
| | 3867 | makeLocked(nil); |
| | 3868 | |
| | 3869 | /* make the default report */ |
| | 3870 | defaultReport(&okayUnlockMsg); |
| | 3871 | } |
| | 3872 | } |
| | 3873 | |
| | 3874 | /* "lock with" */ |
| | 3875 | dobjFor(LockWith) |
| | 3876 | { |
| | 3877 | preCond = (nilToList(inherited()) + [touchObj]) |
| | 3878 | verify() { illogical(&noKeyNeededMsg); } |
| | 3879 | } |
| | 3880 | |
| | 3881 | /* "unlock with" */ |
| | 3882 | dobjFor(UnlockWith) |
| | 3883 | { |
| | 3884 | preCond = (nilToList(inherited()) + [touchObj]) |
| | 3885 | verify() { illogical(&noKeyNeededMsg); } |
| | 3886 | } |
| | 3887 | |
| | 3888 | /* |
| | 3889 | * Should we automatically unlock this door on OPEN? By default, we |
| | 3890 | * do this only if the lock status is obvious. |
| | 3891 | */ |
| | 3892 | autoUnlockOnOpen = (lockStatusObvious) |
| | 3893 | |
| | 3894 | /* |
| | 3895 | * A locked object can't be opened - apply a precondition and a check |
| | 3896 | * for "open" that ensures that we unlock this object before we can |
| | 3897 | * open it. |
| | 3898 | * |
| | 3899 | * If the lock status isn't obvious, don't try to unlock the object |
| | 3900 | * as a precondition. Instead, test to make sure it's unlocked in |
| | 3901 | * the 'check' routine, and fail. |
| | 3902 | */ |
| | 3903 | dobjFor(Open) |
| | 3904 | { |
| | 3905 | preCond() |
| | 3906 | { |
| | 3907 | /* start with the inherited preconditions */ |
| | 3908 | local ret = nilToList(inherited()); |
| | 3909 | |
| | 3910 | /* automatically unlock on open, if appropriate */ |
| | 3911 | if (autoUnlockOnOpen) |
| | 3912 | ret += objUnlocked; |
| | 3913 | |
| | 3914 | /* return the result */ |
| | 3915 | return ret; |
| | 3916 | } |
| | 3917 | |
| | 3918 | check() |
| | 3919 | { |
| | 3920 | /* make sure we're unlocked */ |
| | 3921 | if (isLocked) |
| | 3922 | { |
| | 3923 | /* let them know we're locked */ |
| | 3924 | reportFailure(&cannotOpenLockedMsg); |
| | 3925 | |
| | 3926 | /* set 'it' to me, so UNLOCK IT works */ |
| | 3927 | gActor.setPronounObj(self); |
| | 3928 | |
| | 3929 | /* we cannot proceed */ |
| | 3930 | exit; |
| | 3931 | } |
| | 3932 | |
| | 3933 | /* inherit the default handling */ |
| | 3934 | inherited(); |
| | 3935 | } |
| | 3936 | } |
| | 3937 | ; |
| | 3938 | |
| | 3939 | /* ------------------------------------------------------------------------ */ |
| | 3940 | /* |
| | 3941 | * A lockable that can't be locked and unlocked by direct action. The |
| | 3942 | * LOCK and UNLOCK commands cannot be used with this kind of lockable. |
| | 3943 | * |
| | 3944 | * This is useful for a couple of situations. First, it's useful when we |
| | 3945 | * want to create a locked object that simply can't be unlocked, such as |
| | 3946 | * a locked door that forms a permanent boundary of the map. Second, |
| | 3947 | * it's useful for locked objects that must be unlocked by some other |
| | 3948 | * means, such as manipulating an external mechanism (pulling a lever, |
| | 3949 | * say). In these cases, the trick is to figure out the separate means |
| | 3950 | * of unlocking the door, so we don't want the LOCK and UNLOCK commands |
| | 3951 | * to work directly. |
| | 3952 | */ |
| | 3953 | class IndirectLockable: Lockable |
| | 3954 | dobjFor(Lock) |
| | 3955 | { |
| | 3956 | check() |
| | 3957 | { |
| | 3958 | reportFailure(cannotLockMsg); |
| | 3959 | exit; |
| | 3960 | } |
| | 3961 | } |
| | 3962 | dobjFor(LockWith) asDobjFor(Lock) |
| | 3963 | dobjFor(Unlock) |
| | 3964 | { |
| | 3965 | check() |
| | 3966 | { |
| | 3967 | reportFailure(cannotUnlockMsg); |
| | 3968 | exit; |
| | 3969 | } |
| | 3970 | } |
| | 3971 | dobjFor(UnlockWith) asDobjFor(Unlock) |
| | 3972 | |
| | 3973 | /* |
| | 3974 | * Since we can't be locked and unlocked with simple LOCK and UNLOCK |
| | 3975 | * commands, presume that the lock status isn't obvious. If the |
| | 3976 | * alternative mechanism that locks and unlocks the object makes the |
| | 3977 | * current status readily apparent, this should be overridden and set |
| | 3978 | * to true. |
| | 3979 | */ |
| | 3980 | lockStatusObvious = nil |
| | 3981 | |
| | 3982 | /* the message we display in response to LOCK/UNLOCK */ |
| | 3983 | cannotLockMsg = &unknownHowToLockMsg |
| | 3984 | cannotUnlockMsg = &unknownHowToUnlockMsg |
| | 3985 | ; |
| | 3986 | |
| | 3987 | |
| | 3988 | /* ------------------------------------------------------------------------ */ |
| | 3989 | /* |
| | 3990 | * LockableWithKey: a mix-in class that can be combined with an object's |
| | 3991 | * other superclasses to make the object respond to the verbs "lock" and |
| | 3992 | * "unlock," with a key as an indirect object. A LockableWithKey cannot |
| | 3993 | * be locked or unlocked except with the keys listed in the keyList |
| | 3994 | * property. |
| | 3995 | * |
| | 3996 | * Note that LockableWithKey should usually go BEFORE a Thing-derived |
| | 3997 | * class in the superclass list. |
| | 3998 | */ |
| | 3999 | class LockableWithKey: Lockable |
| | 4000 | /* |
| | 4001 | * Determine if the key fits this lock. Returns true if so, nil if |
| | 4002 | * not. By default, we'll return true if the key is in my keyList. |
| | 4003 | * This can be overridden to use other key selection criteria. |
| | 4004 | */ |
| | 4005 | keyFitsLock(key) { return keyList.indexOf(key) != nil; } |
| | 4006 | |
| | 4007 | /* |
| | 4008 | * Determine if the key is plausibly of the right type for this |
| | 4009 | * lock. This doesn't check to see if the key actually fits the |
| | 4010 | * lock - rather, this checks to see if the key is generally the |
| | 4011 | * kind of object that might plausibly be used with this lock. |
| | 4012 | * |
| | 4013 | * The point of this routine is to make this class concerned only |
| | 4014 | * with the abstract notion of objects that serve to lock and unlock |
| | 4015 | * other objects, without requiring that the key objects resemble |
| | 4016 | * little notched metal sticks or that the lock objects resemble |
| | 4017 | * cylinders with pins - or, more specifically, without requiring |
| | 4018 | * that all of the kinds of keys in a game remotely resemble one |
| | 4019 | * another. |
| | 4020 | * |
| | 4021 | * For example, one kind of "key" in a game might be a plastic card |
| | 4022 | * with a magnetic stripe, and the corresponding lock would be a |
| | 4023 | * card slot; another kind of key might the traditional notched |
| | 4024 | * metal stick. Clearly, no one would ever think to use a plastic |
| | 4025 | * card with a conventional door lock, nor would one try to put a |
| | 4026 | * house key into a card slot (not with the expectation that it |
| | 4027 | * would actually work, anyway). This routine is meant to |
| | 4028 | * facilitate this kind of distinction: the card slot can use this |
| | 4029 | * routine to indicate that only plastic card objects are plausible |
| | 4030 | * as keys, and door locks can indicate that only metal keys are |
| | 4031 | * plausible. |
| | 4032 | * |
| | 4033 | * This routine can be used for disambiguation and other purposes |
| | 4034 | * when we must programmatically select a key that is not specified |
| | 4035 | * or is only vaguely specified. For example, the keyring searcher |
| | 4036 | * uses it so that, when we're searching for a key on a keyring to |
| | 4037 | * open this lock, we implicitly try only the kinds of keys that |
| | 4038 | * would be plausibly useful for this kind of lock. |
| | 4039 | * |
| | 4040 | * By default, we'll simply return true. Subclasses specific to a |
| | 4041 | * game (such as the "card reader" base class or the "door lock" |
| | 4042 | * base class) can override this to discriminate among the |
| | 4043 | * game-specific key classes. |
| | 4044 | */ |
| | 4045 | keyIsPlausible(key) { return true; } |
| | 4046 | |
| | 4047 | /* the list of objects that can serve as keys for this object */ |
| | 4048 | keyList = [] |
| | 4049 | |
| | 4050 | /* |
| | 4051 | * The list of keys which the player knows will fit this lock. This |
| | 4052 | * is used to make key disambiguation automatic once the player |
| | 4053 | * knows the correct key for a lock. |
| | 4054 | */ |
| | 4055 | knownKeyList = [] |
| | 4056 | |
| | 4057 | /* |
| | 4058 | * Get my known key list. This simply returns the known key list |
| | 4059 | * from the known key owner. |
| | 4060 | */ |
| | 4061 | getKnownKeyList() { return getKnownKeyOwner().knownKeyList; } |
| | 4062 | |
| | 4063 | /* |
| | 4064 | * Get the object that own our known key list. If we explicitly have |
| | 4065 | * our own non-empty known key list, we own the key list; otherwise, |
| | 4066 | * our master object owns the list, as long as it has a non-nil key |
| | 4067 | * list at all. |
| | 4068 | */ |
| | 4069 | getKnownKeyOwner() |
| | 4070 | { |
| | 4071 | /* |
| | 4072 | * if we have a non-empty key list, or our master object doesn't |
| | 4073 | * have a key list at all, use our list; otherwise, use our |
| | 4074 | * master object's list so use our list |
| | 4075 | */ |
| | 4076 | if (knownKeyList.length() != 0 || masterObject.knownKeyList == nil) |
| | 4077 | return self; |
| | 4078 | else |
| | 4079 | return masterObject; |
| | 4080 | } |
| | 4081 | |
| | 4082 | /* |
| | 4083 | * Flag: remember my keys after they're successfully used. If this |
| | 4084 | * is true, whenever a key is successfully used to lock or unlock |
| | 4085 | * this object, we'll add the key to our known key list; |
| | 4086 | * subsequently, whenever we try to use a key in this lock, we will |
| | 4087 | * automatically disambiguate the key based on the keys known to |
| | 4088 | * work previously. |
| | 4089 | * |
| | 4090 | * Some authors might prefer not to assume that the player should |
| | 4091 | * remember which keys operate which locks, so this property can be |
| | 4092 | * changed to nil to eliminate this memory feature. By default we |
| | 4093 | * set this to true, since it shouldn't generally give away any |
| | 4094 | * secrets or puzzles for the game to assume that a key that was |
| | 4095 | * used successfully once with a given lock is the one to be used |
| | 4096 | * subsequently with the same lock. |
| | 4097 | */ |
| | 4098 | rememberKnownKeys = true |
| | 4099 | |
| | 4100 | /* |
| | 4101 | * Determine if the player knows that the given key operates this |
| | 4102 | * lock. Returns true if the key is in our known key list, nil if |
| | 4103 | * not. |
| | 4104 | */ |
| | 4105 | isKeyKnown(key) { return getKnownKeyList().indexOf(key) != nil; } |
| | 4106 | |
| | 4107 | /* |
| | 4108 | * By default, the locked/unlocked status of a keyed lockable is nil. |
| | 4109 | * In most cases, an object that's locked and unlocked using a key |
| | 4110 | * doesn't have a visible indication of the status; for example, you |
| | 4111 | * usually can't tell just by looking at it from the outside whether |
| | 4112 | * or not an exterior door to a building is locked. Usually, the |
| | 4113 | * only way to tell from the outside that an exterior door is locked |
| | 4114 | * is to try opening it and see if it opens. |
| | 4115 | */ |
| | 4116 | lockStatusObvious = nil |
| | 4117 | |
| | 4118 | /* |
| | 4119 | * Should we automatically unlock on OPEN? We will if our inherited |
| | 4120 | * handling says so, OR if the current actor is carrying a key |
| | 4121 | * that's known to work with this object. We automatically unlock |
| | 4122 | * when a known key is present as a convenience: if we have a known |
| | 4123 | * key, then there's no mystery in unlocking this object, and thus |
| | 4124 | * for playability we want to make its operation fully automatic. |
| | 4125 | */ |
| | 4126 | autoUnlockOnOpen() |
| | 4127 | { |
| | 4128 | return (inherited() |
| | 4129 | || getKnownKeyList.indexWhich({x: x.isIn(gActor)}) != nil); |
| | 4130 | } |
| | 4131 | |
| | 4132 | /* |
| | 4133 | * Action handling |
| | 4134 | */ |
| | 4135 | |
| | 4136 | dobjFor(Lock) |
| | 4137 | { |
| | 4138 | preCond |
| | 4139 | { |
| | 4140 | /* |
| | 4141 | * remove any objClosed from our precondition - since we |
| | 4142 | * won't actually do any locking but will instead merely ask |
| | 4143 | * for an indirect object, we don't want to apply the normal |
| | 4144 | * closed precondition here |
| | 4145 | */ |
| | 4146 | return inherited() - objClosed; |
| | 4147 | } |
| | 4148 | verify() |
| | 4149 | { |
| | 4150 | /* if we're already locked, there's no point in locking us */ |
| | 4151 | if (isLocked) |
| | 4152 | illogicalAlready(&alreadyLockedMsg); |
| | 4153 | } |
| | 4154 | action() |
| | 4155 | { |
| | 4156 | /* ask for an indirect object to use as the key */ |
| | 4157 | askForIobj(LockWith); |
| | 4158 | } |
| | 4159 | } |
| | 4160 | |
| | 4161 | /* "unlock" */ |
| | 4162 | dobjFor(Unlock) |
| | 4163 | { |
| | 4164 | verify() |
| | 4165 | { |
| | 4166 | /* if we're not locked, there's no point in unlocking us */ |
| | 4167 | if (!isLocked) |
| | 4168 | illogicalAlready(&alreadyUnlockedMsg); |
| | 4169 | } |
| | 4170 | action() |
| | 4171 | { |
| | 4172 | /* |
| | 4173 | * We need a key. If we're running as an implied action, the |
| | 4174 | * player hasn't specifically proposed unlocking the object, |
| | 4175 | * so it's a little weird to ask a follow-up question about |
| | 4176 | * what key to use. So, if the action is implicit and |
| | 4177 | * there's no default key, don't proceed; simply fail with an |
| | 4178 | * explanation. |
| | 4179 | */ |
| | 4180 | if (gAction.isImplicit |
| | 4181 | && !UnlockWithAction.testRetryDefaultIobj(gAction)) |
| | 4182 | { |
| | 4183 | /* explain that we need a key, and we're done */ |
| | 4184 | reportFailure(&unlockRequiresKeyMsg); |
| | 4185 | return; |
| | 4186 | } |
| | 4187 | |
| | 4188 | /* ask for a key */ |
| | 4189 | askForIobj(UnlockWith); |
| | 4190 | } |
| | 4191 | } |
| | 4192 | |
| | 4193 | /* |
| | 4194 | * perform the action processing for LockWith or UnlockWith - these |
| | 4195 | * are highly symmetrical, in that the only thing that varies is the |
| | 4196 | * new lock state we establish |
| | 4197 | */ |
| | 4198 | lockOrUnlockAction(lock) |
| | 4199 | { |
| | 4200 | /* |
| | 4201 | * If it's a keyring, let the keyring's action handler do the |
| | 4202 | * work. Otherwise, if it's my key, lock/unlock; it's not a |
| | 4203 | * key, fail. |
| | 4204 | */ |
| | 4205 | if (gIobj.ofKind(Keyring)) |
| | 4206 | { |
| | 4207 | /* |
| | 4208 | * do nothing - let the indirect object action handler do |
| | 4209 | * the work |
| | 4210 | */ |
| | 4211 | } |
| | 4212 | else if (keyFitsLock(gIobj)) |
| | 4213 | { |
| | 4214 | local ko; |
| | 4215 | |
| | 4216 | /* |
| | 4217 | * get the object (us or our master object) that owns the |
| | 4218 | * known key list |
| | 4219 | */ |
| | 4220 | ko = getKnownKeyOwner(); |
| | 4221 | |
| | 4222 | /* |
| | 4223 | * if the key owner remembers known keys, and it doesn't know |
| | 4224 | * about this working key yet, remember this in the list of |
| | 4225 | * known keys |
| | 4226 | */ |
| | 4227 | if (ko.rememberKnownKeys |
| | 4228 | && ko.knownKeyList.indexOf(gIobj) == nil) |
| | 4229 | ko.knownKeyList += gIobj; |
| | 4230 | |
| | 4231 | /* set my new state and issue a default report */ |
| | 4232 | makeLocked(lock); |
| | 4233 | defaultReport(lock ? &okayLockMsg : &okayUnlockMsg); |
| | 4234 | } |
| | 4235 | else |
| | 4236 | { |
| | 4237 | /* the key doesn't work in this lock */ |
| | 4238 | reportFailure(&keyDoesNotFitLockMsg); |
| | 4239 | } |
| | 4240 | } |
| | 4241 | |
| | 4242 | /* "lock with" */ |
| | 4243 | dobjFor(LockWith) |
| | 4244 | { |
| | 4245 | verify() |
| | 4246 | { |
| | 4247 | /* if we're already locked, there's no point in locking us */ |
| | 4248 | if (isLocked) |
| | 4249 | illogicalAlready(&alreadyLockedMsg); |
| | 4250 | } |
| | 4251 | action() |
| | 4252 | { |
| | 4253 | /* perform the generic lock/unlock action processing */ |
| | 4254 | lockOrUnlockAction(true); |
| | 4255 | } |
| | 4256 | } |
| | 4257 | |
| | 4258 | /* "unlock with" */ |
| | 4259 | dobjFor(UnlockWith) |
| | 4260 | { |
| | 4261 | verify() |
| | 4262 | { |
| | 4263 | /* if we're not locked, there's no point in unlocking us */ |
| | 4264 | if (!isLocked) |
| | 4265 | illogicalAlready(&alreadyUnlockedMsg); |
| | 4266 | } |
| | 4267 | action() |
| | 4268 | { |
| | 4269 | /* perform the generic lock/unlock action processing */ |
| | 4270 | lockOrUnlockAction(nil); |
| | 4271 | } |
| | 4272 | } |
| | 4273 | ; |
| | 4274 | |
| | 4275 | /* ------------------------------------------------------------------------ */ |
| | 4276 | /* |
| | 4277 | * The common base class for containers and surfaces: things that have |
| | 4278 | * limited bulk capacities. This class isn't usually used directly; |
| | 4279 | * subclasses such as Surface and Container are usually used instead. |
| | 4280 | */ |
| | 4281 | class BulkLimiter: Thing |
| | 4282 | /* |
| | 4283 | * A container can limit the cumulative amount of bulk of its |
| | 4284 | * contents, and the maximum bulk of any one object, using |
| | 4285 | * bulkCapacity and maxSingleBulk. We count the cumulative and |
| | 4286 | * single-item limits separately, since we want to allow modelling |
| | 4287 | * some objects as so large that they won't fit in this container at |
| | 4288 | * all, even if the container is carrying nothing else, without |
| | 4289 | * limiting the number of small items we can carry. |
| | 4290 | * |
| | 4291 | * By default, we set bulkCapacity to a very large number, making |
| | 4292 | * the total capacity of the object essentially unlimited. However, |
| | 4293 | * we set maxSingleBulk to a relatively low number - this way, if an |
| | 4294 | * author wants to designate certain objects as especially large and |
| | 4295 | * thus unable to fit in ordinary containers, the author merely |
| | 4296 | * needs to set the bulk of those large items to something greater |
| | 4297 | * than 10. On the other hand, if an author doesn't want to worry |
| | 4298 | * about bulk and limited carrying capacities and simply uses |
| | 4299 | * library defaults for everything, we will be able to contain |
| | 4300 | * anything and everything. |
| | 4301 | * |
| | 4302 | * In a game that models bulk realistically, a container's bulk |
| | 4303 | * should generally be equal to or slightly greater than its |
| | 4304 | * bulkCapacity, because a container shouldn't be smaller on the |
| | 4305 | * outside than on the inside. If bulkCapacity exceeds bulk, the |
| | 4306 | * player can work around a holding bulk limit by piling objects |
| | 4307 | * into the container, thus "hiding" the bulks of the contents |
| | 4308 | * behind the smaller bulk of the container. |
| | 4309 | */ |
| | 4310 | bulkCapacity = 10000 |
| | 4311 | maxSingleBulk = 10 |
| | 4312 | |
| | 4313 | /* |
| | 4314 | * receive notification that we're about to insert an object into |
| | 4315 | * this container |
| | 4316 | */ |
| | 4317 | notifyInsert(obj, newCont) |
| | 4318 | { |
| | 4319 | /* if I'm the new direct container, check our bulk limit */ |
| | 4320 | if (newCont == self) |
| | 4321 | { |
| | 4322 | /* |
| | 4323 | * do a 'what if' test to see what would happen to our |
| | 4324 | * contained bulk if we moved this item into me |
| | 4325 | */ |
| | 4326 | obj.whatIf({: checkBulkInserted(obj)}, &moveInto, self); |
| | 4327 | } |
| | 4328 | |
| | 4329 | /* inherit base class handling */ |
| | 4330 | inherited(obj, newCont); |
| | 4331 | } |
| | 4332 | |
| | 4333 | /* |
| | 4334 | * Check to see if a proposed insertion - already tentatively in |
| | 4335 | * effect when this routine is called - would overflow our bulk |
| | 4336 | * limits. Reports failure and exits if the inserted object would |
| | 4337 | * exceed our capacity. |
| | 4338 | */ |
| | 4339 | checkBulkInserted(insertedObj) |
| | 4340 | { |
| | 4341 | local objBulk; |
| | 4342 | |
| | 4343 | /* get the bulk of the inserted object itself */ |
| | 4344 | objBulk = insertedObj.getBulk(); |
| | 4345 | |
| | 4346 | /* |
| | 4347 | * Check the object itself to see if it fits by itself. If it |
| | 4348 | * doesn't, we can report the simple fact that the object is too |
| | 4349 | * big for the container. |
| | 4350 | */ |
| | 4351 | if (objBulk > maxSingleBulk || objBulk > bulkCapacity) |
| | 4352 | { |
| | 4353 | reportFailure(&tooLargeForContainerMsg, insertedObj, self); |
| | 4354 | exit; |
| | 4355 | } |
| | 4356 | |
| | 4357 | /* |
| | 4358 | * If our contained bulk is over our maximum, don't allow it. |
| | 4359 | * Note that we merely need to check our current bulk within, |
| | 4360 | * since this routine is called with the insertion already |
| | 4361 | * tentatively in effect. |
| | 4362 | */ |
| | 4363 | if (getBulkWithin() > bulkCapacity) |
| | 4364 | { |
| | 4365 | reportFailure(tooFullMsg, insertedObj, self); |
| | 4366 | exit; |
| | 4367 | } |
| | 4368 | } |
| | 4369 | |
| | 4370 | /* |
| | 4371 | * the message property to use when we're too full to hold a new |
| | 4372 | * object (i.e., the object's bulk would push us over our bulk |
| | 4373 | * capacity limit) |
| | 4374 | */ |
| | 4375 | tooFullMsg = &containerTooFullMsg |
| | 4376 | |
| | 4377 | /* |
| | 4378 | * the message property to use when doing something to one of our |
| | 4379 | * contents would make it too large to fit all by itself into this |
| | 4380 | * container (that is, it would cause that object's bulk to exceed |
| | 4381 | * our maxSingleBulk) |
| | 4382 | */ |
| | 4383 | becomingTooLargeMsg = &becomingTooLargeForContainerMsg |
| | 4384 | |
| | 4385 | /* |
| | 4386 | * the message property to use when doing something to one of our |
| | 4387 | * contents would cause our overall contents to exceed our capacity |
| | 4388 | */ |
| | 4389 | becomingTooFullMsg = &containerBecomingTooFullMsg |
| | 4390 | |
| | 4391 | /* |
| | 4392 | * Check a bulk change of one of my direct contents. |
| | 4393 | */ |
| | 4394 | checkBulkChangeWithin(obj) |
| | 4395 | { |
| | 4396 | local objBulk; |
| | 4397 | |
| | 4398 | /* get the object's new bulk */ |
| | 4399 | objBulk = obj.getBulk(); |
| | 4400 | |
| | 4401 | /* |
| | 4402 | * if this change would cause the object to exceed our |
| | 4403 | * single-item bulk limit, don't allow it |
| | 4404 | */ |
| | 4405 | if (objBulk > maxSingleBulk || objBulk > bulkCapacity) |
| | 4406 | { |
| | 4407 | reportFailure(becomingTooLargeMsg, obj, self); |
| | 4408 | exit; |
| | 4409 | } |
| | 4410 | |
| | 4411 | /* |
| | 4412 | * If our total carrying capacity is exceeded with this change, |
| | 4413 | * don't allow it. Note that 'obj' is already among our |
| | 4414 | * contents when this routine is called, so we can simply check |
| | 4415 | * our current total bulk within. |
| | 4416 | */ |
| | 4417 | if (getBulkWithin() > bulkCapacity) |
| | 4418 | { |
| | 4419 | reportFailure(becomingTooFullMsg, obj, self); |
| | 4420 | exit; |
| | 4421 | } |
| | 4422 | } |
| | 4423 | |
| | 4424 | /* |
| | 4425 | * Adjust a THROW destination. Since we only allow a limited amount |
| | 4426 | * of bulk within our contents, we need to make sure the thrown |
| | 4427 | * object would fit if it landed here. If it doesn't, we'll redirect |
| | 4428 | * the landing site to our container. |
| | 4429 | */ |
| | 4430 | adjustThrowDestination(thrownObj, path) |
| | 4431 | { |
| | 4432 | local thrownBulk = thrownObj.getBulk(); |
| | 4433 | local newBulk; |
| | 4434 | local dest; |
| | 4435 | |
| | 4436 | /* |
| | 4437 | * do a 'what if' test to test our total bulk with the projectile |
| | 4438 | * added to my contents |
| | 4439 | */ |
| | 4440 | newBulk = thrownObj.whatIf({: getBulkWithin()}, &moveInto, self); |
| | 4441 | |
| | 4442 | /* |
| | 4443 | * If that exceeds our maximum bulk, or the object's bulk |
| | 4444 | * individually is over our limit, we can't be the landing site. |
| | 4445 | * In this case, defer to our location's drop destination, if it |
| | 4446 | * has one. |
| | 4447 | */ |
| | 4448 | if ((newBulk > bulkCapacity |
| | 4449 | || thrownBulk > bulkCapacity |
| | 4450 | || thrownBulk > maxSingleBulk) |
| | 4451 | && location != nil |
| | 4452 | && (dest = location.getDropDestination(thrownObj, path)) != nil) |
| | 4453 | { |
| | 4454 | /* |
| | 4455 | * It won't fit, so defer to our container's drop |
| | 4456 | * destination. Give the new destination a chance to further |
| | 4457 | * adjust the destination. |
| | 4458 | */ |
| | 4459 | return dest.adjustThrowDestination(thrownObj, path); |
| | 4460 | } |
| | 4461 | |
| | 4462 | /* |
| | 4463 | * the projectile fits, or we just can't find a container to |
| | 4464 | * defer to; use the original destination, i.e., self |
| | 4465 | */ |
| | 4466 | return self; |
| | 4467 | } |
| | 4468 | |
| | 4469 | /* |
| | 4470 | * Examine my interior. This can be used to handle the action() for |
| | 4471 | * LOOK IN, or for other commands appropriate to the subclass. |
| | 4472 | */ |
| | 4473 | examineInterior() |
| | 4474 | { |
| | 4475 | /* examine the interior with our normal look-in lister */ |
| | 4476 | examineInteriorWithLister(lookInLister); |
| | 4477 | |
| | 4478 | /* |
| | 4479 | * Anything that the an overriding caller (a routine that called |
| | 4480 | * us with 'inherited') wants to add is an addendum to our |
| | 4481 | * description, so add a transcript marker to indicate that the |
| | 4482 | * main description is now finished. |
| | 4483 | * |
| | 4484 | * The important thing about this is that any message that an |
| | 4485 | * overriding caller wants to add is not considered part of the |
| | 4486 | * description, in the sense that we don't want it to suppress |
| | 4487 | * any default description we've already generated. One of the |
| | 4488 | * transformations we apply to the transcript is to suppress any |
| | 4489 | * default descriptive text if there's any more specific |
| | 4490 | * descriptive text following (for example, we suppress "It's an |
| | 4491 | * ordinary <thing>" if we also are going to say "it's open" or |
| | 4492 | * "it contains three coins"). If we have an overriding caller |
| | 4493 | * who's going to add anything, then we must assume that what the |
| | 4494 | * caller's adding is something about the act of examining the |
| | 4495 | * object, rather than a description of the object, so we don't |
| | 4496 | * want it to suppress a default description. |
| | 4497 | */ |
| | 4498 | gTranscript.endDescription(); |
| | 4499 | } |
| | 4500 | |
| | 4501 | /* examine my interior, listing the contents with the given lister */ |
| | 4502 | examineInteriorWithLister(lister) |
| | 4503 | { |
| | 4504 | local tab; |
| | 4505 | |
| | 4506 | /* if desired, reveal any "Hidden" items concealed within */ |
| | 4507 | if (revealHiddenItems) |
| | 4508 | { |
| | 4509 | /* scan our contents and reveal each Hidden item */ |
| | 4510 | foreach (local cur in contents) |
| | 4511 | { |
| | 4512 | /* if it's a Hidden item, reveal it */ |
| | 4513 | if (cur.ofKind(Hidden)) |
| | 4514 | cur.discover(); |
| | 4515 | } |
| | 4516 | } |
| | 4517 | |
| | 4518 | /* get my visible sense info */ |
| | 4519 | tab = gActor.visibleInfoTable(); |
| | 4520 | |
| | 4521 | /* show my contents, if I have any */ |
| | 4522 | lister.showList(gActor, self, contents, ListRecurse, 0, tab, nil); |
| | 4523 | |
| | 4524 | /* mark my contents as having been seen */ |
| | 4525 | setContentsSeenBy(tab, gActor); |
| | 4526 | |
| | 4527 | /* examine my special contents */ |
| | 4528 | examineSpecialContents(); |
| | 4529 | } |
| | 4530 | |
| | 4531 | /* |
| | 4532 | * Verify putting something new in my interior. This is suitable |
| | 4533 | * for use as a verify() method for a command like PutIn or PutOn. |
| | 4534 | * Note that this routine assumes and requires that gDobj be the |
| | 4535 | * object to be added, and gIobj be self. |
| | 4536 | */ |
| | 4537 | verifyPutInInterior() |
| | 4538 | { |
| | 4539 | /* |
| | 4540 | * if we haven't resolved the direct object yet, we can at least |
| | 4541 | * check to see if all of the potential direct objects are |
| | 4542 | * already in me, and rule out this indirect object as illogical |
| | 4543 | * if so |
| | 4544 | */ |
| | 4545 | if (gDobj == nil) |
| | 4546 | { |
| | 4547 | /* |
| | 4548 | * check the tentative direct objects to see if (1) all of |
| | 4549 | * them are directly inside me already, or (2) all of them |
| | 4550 | * are at least indirectly inside me already |
| | 4551 | */ |
| | 4552 | if (gTentativeDobj.indexWhich( |
| | 4553 | {x: !x.obj_.isDirectlyIn(self)}) == nil) |
| | 4554 | { |
| | 4555 | /* |
| | 4556 | * All of the potential direct objects are already |
| | 4557 | * directly inside me. This makes this object |
| | 4558 | * illogical, since there's no need to move any of these |
| | 4559 | * objects into me. |
| | 4560 | */ |
| | 4561 | illogicalAlready(&alreadyPutInMsg); |
| | 4562 | } |
| | 4563 | else if (gTentativeDobj.indexWhich( |
| | 4564 | {x: !x.obj_.isIn(self)}) == nil) |
| | 4565 | { |
| | 4566 | /* |
| | 4567 | * All of the potential direct objects are already in |
| | 4568 | * me, at least indirectly. This makes this object |
| | 4569 | * somewhat less likely, since we're more likely to want |
| | 4570 | * to put something in here that wasn't already within. |
| | 4571 | * Note that this isn't actually illogical, though, |
| | 4572 | * since we could be moving something from deeper inside |
| | 4573 | * me to directly inside me. |
| | 4574 | */ |
| | 4575 | logicalRank(50, 'dobjs already inside'); |
| | 4576 | } |
| | 4577 | } |
| | 4578 | else |
| | 4579 | { |
| | 4580 | /* |
| | 4581 | * We can't put myself in myself, obviously. We also can't |
| | 4582 | * put something into any component of itself, so the command |
| | 4583 | * is illogical if we're a component of the direct object. |
| | 4584 | */ |
| | 4585 | if (gDobj == self || isComponentOf(gDobj)) |
| | 4586 | illogicalSelf(&cannotPutInSelfMsg); |
| | 4587 | |
| | 4588 | /* if it's already directly inside me, this is illogical */ |
| | 4589 | if (gDobj.isDirectlyIn(self)) |
| | 4590 | illogicalAlready(&alreadyPutInMsg); |
| | 4591 | } |
| | 4592 | |
| | 4593 | /* |
| | 4594 | * if I'm not held by the actor, give myself a slightly lower |
| | 4595 | * ranking than fully logical, so that objects being held are |
| | 4596 | * preferred |
| | 4597 | */ |
| | 4598 | if (!isIn(gActor)) |
| | 4599 | logicalRank(60, 'not indirectly held'); |
| | 4600 | else if (!isHeldBy(gActor)) |
| | 4601 | logicalRank(70, 'not held'); |
| | 4602 | } |
| | 4603 | |
| | 4604 | /* |
| | 4605 | * Flag: reveal any hidden items contained directly within me when |
| | 4606 | * my interior is explicitly examined, via a command such as LOOK IN |
| | 4607 | * <self>. By default, we reveal our hidden contents on |
| | 4608 | * examination; hidden objects are in most cases meant to be more |
| | 4609 | * inconspicuous than actually camouflaged, so a careful, explicit |
| | 4610 | * examination would normally reveal them. If our hidden objects |
| | 4611 | * are so concealed that even explicit examination of our interior |
| | 4612 | * wouldn't reveal them, set this to nil. |
| | 4613 | */ |
| | 4614 | revealHiddenItems = true |
| | 4615 | ; |
| | 4616 | |
| | 4617 | |
| | 4618 | /* ------------------------------------------------------------------------ */ |
| | 4619 | /* |
| | 4620 | * A basic container is an object that can enclose its contents. This is |
| | 4621 | * the core of the Container type, but this class only has the bare-bones |
| | 4622 | * sense-related enclosing features, without any action implementation. |
| | 4623 | * This can be used for cases where an object isn't meant to have its |
| | 4624 | * contents be manipulable by the player (so we don't want to allow "put |
| | 4625 | * in" and so on), but where we do want the ability to conceal our |
| | 4626 | * contents when we're closed. |
| | 4627 | */ |
| | 4628 | class BasicContainer: BulkLimiter |
| | 4629 | /* |
| | 4630 | * My current open/closed state. By default, this state never |
| | 4631 | * changes, but is fixed in the object's definition; for example, a |
| | 4632 | * box without a lid would always be open, while a hollow glass cube |
| | 4633 | * would always be closed. Our default state is open. |
| | 4634 | */ |
| | 4635 | isOpen = true |
| | 4636 | |
| | 4637 | /* the material that we're made of */ |
| | 4638 | material = adventium |
| | 4639 | |
| | 4640 | /* prepositional phrase for objects being put into me */ |
| | 4641 | putDestMessage = &putDestContainer |
| | 4642 | |
| | 4643 | /* |
| | 4644 | * Determine if I can move an object via a path through this |
| | 4645 | * container. |
| | 4646 | */ |
| | 4647 | checkMoveViaPath(obj, dest, op) |
| | 4648 | { |
| | 4649 | /* |
| | 4650 | * if we're moving the object in or out of me, we must consider |
| | 4651 | * our openness and whether or not the object fits through our |
| | 4652 | * opening |
| | 4653 | */ |
| | 4654 | if (op is in (PathIn, PathOut)) |
| | 4655 | { |
| | 4656 | /* if we're closed, we can't move anything in or out */ |
| | 4657 | if (!isOpen) |
| | 4658 | return new CheckStatusFailure(cannotMoveThroughMsg, |
| | 4659 | obj, self); |
| | 4660 | |
| | 4661 | /* if it doesn't fit through our opening, don't allow it */ |
| | 4662 | if (!canFitObjThruOpening(obj)) |
| | 4663 | return new CheckStatusFailure(op == PathIn |
| | 4664 | ? &cannotFitIntoOpeningMsg |
| | 4665 | : &cannotFitOutOfOpeningMsg, |
| | 4666 | obj, self); |
| | 4667 | } |
| | 4668 | |
| | 4669 | /* in any other cases, allow the operation */ |
| | 4670 | return checkStatusSuccess; |
| | 4671 | } |
| | 4672 | |
| | 4673 | /* |
| | 4674 | * The message property we use when we can't move an object through |
| | 4675 | * the containment boundary. This is a playerActionMessages |
| | 4676 | * property. |
| | 4677 | */ |
| | 4678 | cannotMoveThroughMsg = &cannotMoveThroughContainerMsg |
| | 4679 | |
| | 4680 | /* |
| | 4681 | * Determine if an actor can touch an object via a path through this |
| | 4682 | * container. |
| | 4683 | */ |
| | 4684 | checkTouchViaPath(obj, dest, op) |
| | 4685 | { |
| | 4686 | /* |
| | 4687 | * if we're reaching from inside directly to me, allow it - |
| | 4688 | * treat this as touching our interior, which we allow from |
| | 4689 | * within regardless of our open/closed status |
| | 4690 | */ |
| | 4691 | if (op == PathOut && dest == self) |
| | 4692 | return checkStatusSuccess; |
| | 4693 | |
| | 4694 | /* |
| | 4695 | * if we're reaching in or out of me, consider our openness and |
| | 4696 | * whether or not the actor's hand fits through our opening |
| | 4697 | */ |
| | 4698 | if (op is in (PathIn, PathOut)) |
| | 4699 | { |
| | 4700 | /* if we're closed, we can't reach into/out of the container */ |
| | 4701 | if (!isOpen) |
| | 4702 | return new CheckStatusFailure(cannotTouchThroughMsg, |
| | 4703 | obj, self); |
| | 4704 | |
| | 4705 | /* |
| | 4706 | * if the object's "hand" doesn't fit through our opening, |
| | 4707 | * don't allow it |
| | 4708 | */ |
| | 4709 | if (!canObjReachThruOpening(obj)) |
| | 4710 | return new CheckStatusFailure(op == PathIn |
| | 4711 | ? &cannotReachIntoOpeningMsg |
| | 4712 | : &cannotReachOutOfOpeningMsg, |
| | 4713 | obj, self); |
| | 4714 | } |
| | 4715 | |
| | 4716 | /* in any other cases, allow the operation */ |
| | 4717 | return checkStatusSuccess; |
| | 4718 | } |
| | 4719 | |
| | 4720 | /* |
| | 4721 | * Library message (in playerActionMessages) explaining why we can't |
| | 4722 | * touch an object through this container. This is used when an |
| | 4723 | * actor on the outside tries to reach something on the inside, or |
| | 4724 | * vice versa. |
| | 4725 | */ |
| | 4726 | cannotTouchThroughMsg = &cannotTouchThroughContainerMsg |
| | 4727 | |
| | 4728 | /* |
| | 4729 | * Determine if the given object fits through our opening. This is |
| | 4730 | * only called when we're open; this determines if the object can be |
| | 4731 | * moved in or out of this container. By default, we'll return |
| | 4732 | * true; some objects might want to override this to disallow |
| | 4733 | * objects over a certain size from being moved in or out of this |
| | 4734 | * container. |
| | 4735 | * |
| | 4736 | * Note that this method doesn't care whether or not the object can |
| | 4737 | * actually fit inside the container once through the opening; we |
| | 4738 | * only care about whether or not the object can fit through the |
| | 4739 | * opening itself. This allows for things like narrow-mouthed |
| | 4740 | * bottles which have greater capacity within than in their |
| | 4741 | * openings. |
| | 4742 | */ |
| | 4743 | canFitObjThruOpening(obj) { return true; } |
| | 4744 | |
| | 4745 | /* |
| | 4746 | * Determine if the given object can "reach" through our opening, |
| | 4747 | * for the purposes of touching an object on the other side of the |
| | 4748 | * opening. This is used to determine if the object, which is |
| | 4749 | * usually an actor, can its "hand" (or whatever appendange 'obj' |
| | 4750 | * uses to reach things) through our opening. This is only called |
| | 4751 | * when we're open. By default, we'll simply return true. |
| | 4752 | * |
| | 4753 | * This differs from canFitObjThruOpening() in that we don't care if |
| | 4754 | * all of 'obj' is able to fit through the opening; we only care |
| | 4755 | * whether obj's hand (or whatever it uses for reaching) can fit. |
| | 4756 | */ |
| | 4757 | canObjReachThruOpening(obj) { return true; } |
| | 4758 | |
| | 4759 | /* |
| | 4760 | * Determine how a sense passes to my contents. If I'm open, the |
| | 4761 | * sense passes through directly, since there's nothing in the way. |
| | 4762 | * If I'm closed, the sense must pass through my material. |
| | 4763 | */ |
| | 4764 | transSensingIn(sense) |
| | 4765 | { |
| | 4766 | if (isOpen) |
| | 4767 | { |
| | 4768 | /* I'm open, so the sense passes through without interference */ |
| | 4769 | return transparent; |
| | 4770 | } |
| | 4771 | else |
| | 4772 | { |
| | 4773 | /* I'm closed, so the sense must pass through my material */ |
| | 4774 | return material.senseThru(sense); |
| | 4775 | } |
| | 4776 | } |
| | 4777 | |
| | 4778 | /* |
| | 4779 | * Get my fill medium. If I'm open, inherit my parent's medium, |
| | 4780 | * assuming that the medium behaves like fog or smoke and naturally |
| | 4781 | * disperses to fill any nested open containers. If I'm closed, I |
| | 4782 | * am by default filled with no medium. |
| | 4783 | */ |
| | 4784 | fillMedium() |
| | 4785 | { |
| | 4786 | if (isOpen && location != nil) |
| | 4787 | { |
| | 4788 | /* I'm open, so return my location's medium */ |
| | 4789 | return location.fillMedium(); |
| | 4790 | } |
| | 4791 | else |
| | 4792 | { |
| | 4793 | /* |
| | 4794 | * I'm closed, so we're cut off from the parent - assume |
| | 4795 | * we're filled with nothing |
| | 4796 | */ |
| | 4797 | return nil; |
| | 4798 | } |
| | 4799 | } |
| | 4800 | |
| | 4801 | /* |
| | 4802 | * Display a message explaining why we are obstructing a sense path |
| | 4803 | * to the given object. |
| | 4804 | */ |
| | 4805 | cannotReachObject(obj) |
| | 4806 | { |
| | 4807 | /* |
| | 4808 | * We must be obstructing by containment. Show an appropriate |
| | 4809 | * message depending on whether the object is inside me or not - |
| | 4810 | * if not, then the actor trying to reach the object must be |
| | 4811 | * inside me. |
| | 4812 | */ |
| | 4813 | if (obj.isIn(self)) |
| | 4814 | gLibMessages.cannotReachContents(obj, self); |
| | 4815 | else |
| | 4816 | gLibMessages.cannotReachOutside(obj, self); |
| | 4817 | } |
| | 4818 | |
| | 4819 | /* explain why we can't see the source of a sound */ |
| | 4820 | cannotSeeSoundSource(obj) |
| | 4821 | { |
| | 4822 | /* we must be obstructing by containment */ |
| | 4823 | if (obj.isIn(self)) |
| | 4824 | gLibMessages.soundIsFromWithin(obj, self); |
| | 4825 | else |
| | 4826 | gLibMessages.soundIsFromWithout(obj, self); |
| | 4827 | } |
| | 4828 | |
| | 4829 | /* explain why we can't see the source of an odor */ |
| | 4830 | cannotSeeSmellSource(obj) |
| | 4831 | { |
| | 4832 | /* we must be obstructing by containment */ |
| | 4833 | if (obj.isIn(self)) |
| | 4834 | gLibMessages.smellIsFromWithin(obj, self); |
| | 4835 | else |
| | 4836 | gLibMessages.smellIsFromWithout(obj, self); |
| | 4837 | } |
| | 4838 | |
| | 4839 | ; |
| | 4840 | |
| | 4841 | /* ------------------------------------------------------------------------ */ |
| | 4842 | /* |
| | 4843 | * Container: an object that can have other objects placed within it. |
| | 4844 | */ |
| | 4845 | class Container: BasicContainer |
| | 4846 | /* |
| | 4847 | * Our fixed "look in" description, if any. This is shown on LOOK |
| | 4848 | * IN before our normal listing of our portable contents; it can be |
| | 4849 | * used to describe generally what the interior looks like, for |
| | 4850 | * example. By default, we show nothing here. |
| | 4851 | */ |
| | 4852 | lookInDesc = nil |
| | 4853 | |
| | 4854 | /* |
| | 4855 | * Show our status for "examine". This shows our open/closed status, |
| | 4856 | * and lists our contents. |
| | 4857 | */ |
| | 4858 | examineStatus() |
| | 4859 | { |
| | 4860 | /* show any special container-specific status */ |
| | 4861 | examineContainerStatus(); |
| | 4862 | |
| | 4863 | /* inherit the default handling to show my contents */ |
| | 4864 | inherited(); |
| | 4865 | } |
| | 4866 | |
| | 4867 | /* |
| | 4868 | * mention my open/closed status for Examine processing |
| | 4869 | */ |
| | 4870 | examineContainerStatus() |
| | 4871 | { |
| | 4872 | /* |
| | 4873 | * By default, show nothing extra. This can be overridden by |
| | 4874 | * subclasses as needed to show any extra status before our |
| | 4875 | * contents list. |
| | 4876 | */ |
| | 4877 | } |
| | 4878 | |
| | 4879 | /* |
| | 4880 | * Try putting an object into me when I'm serving as a bag of |
| | 4881 | * holding. For a container, this simply does a "put obj in bag". |
| | 4882 | */ |
| | 4883 | tryPuttingObjInBag(target) |
| | 4884 | { |
| | 4885 | /* if the object won't fit all by itself, don't even try */ |
| | 4886 | if (target.getBulk() > maxSingleBulk) |
| | 4887 | return nil; |
| | 4888 | |
| | 4889 | /* if we can't fit the object with other contents, don't try */ |
| | 4890 | if (target.whatIf({: getBulkWithin() > bulkCapacity}, |
| | 4891 | &moveInto, self)) |
| | 4892 | return nil; |
| | 4893 | |
| | 4894 | /* we're a container, so use "put in" to get the object */ |
| | 4895 | return tryImplicitActionMsg(&announceMoveToBag, PutIn, target, self); |
| | 4896 | } |
| | 4897 | |
| | 4898 | /* |
| | 4899 | * Try moving an object into this container. For a container, this |
| | 4900 | * performs a PUT IN command to move the object into self. |
| | 4901 | */ |
| | 4902 | tryMovingObjInto(obj) { return tryImplicitAction(PutIn, obj, self); } |
| | 4903 | |
| | 4904 | /* -------------------------------------------------------------------- */ |
| | 4905 | /* |
| | 4906 | * "Look in" |
| | 4907 | */ |
| | 4908 | dobjFor(LookIn) |
| | 4909 | { |
| | 4910 | verify() { } |
| | 4911 | check() |
| | 4912 | { |
| | 4913 | /* |
| | 4914 | * If I'm closed, and I can't see my contents when closed, we |
| | 4915 | * can't go on. Unless, of course, the actor is inside us, |
| | 4916 | * in which case our external boundary isn't relevant. |
| | 4917 | */ |
| | 4918 | if (!isOpen |
| | 4919 | && transSensingIn(sight) == opaque |
| | 4920 | && !gActor.isIn(self)) |
| | 4921 | { |
| | 4922 | /* we can't see anything because we're closed */ |
| | 4923 | reportFailure(&cannotLookInClosedMsg); |
| | 4924 | exit; |
| | 4925 | } |
| | 4926 | } |
| | 4927 | action() |
| | 4928 | { |
| | 4929 | /* show our fixed "look in" description, if any */ |
| | 4930 | lookInDesc; |
| | 4931 | |
| | 4932 | /* examine my interior */ |
| | 4933 | examineInterior(); |
| | 4934 | } |
| | 4935 | } |
| | 4936 | |
| | 4937 | /* |
| | 4938 | * "Search". This is mostly like Open, except that the actor has to |
| | 4939 | * be able to reach into the object, not just see into it - searching |
| | 4940 | * implies a more thorough sort of examination, usually including |
| | 4941 | * physically poking through the object's contents. |
| | 4942 | */ |
| | 4943 | dobjFor(Search) |
| | 4944 | { |
| | 4945 | preCond = (nilToList(inherited()) + [touchObj]) |
| | 4946 | check() |
| | 4947 | { |
| | 4948 | /* |
| | 4949 | * if I'm closed, and the actor isn't inside me, make sure my |
| | 4950 | * contents are reachable from the outside |
| | 4951 | */ |
| | 4952 | if (!isOpen |
| | 4953 | && transSensingIn(touch) != transparent |
| | 4954 | && !gActor.isIn(self)) |
| | 4955 | { |
| | 4956 | /* we can't search an object that we can't reach into */ |
| | 4957 | reportFailure(&cannotTouchThroughMsg, gActor, self); |
| | 4958 | exit; |
| | 4959 | } |
| | 4960 | } |
| | 4961 | } |
| | 4962 | |
| | 4963 | |
| | 4964 | /* -------------------------------------------------------------------- */ |
| | 4965 | /* |
| | 4966 | * Put In processing. A container can accept new contents. |
| | 4967 | */ |
| | 4968 | |
| | 4969 | iobjFor(PutIn) |
| | 4970 | { |
| | 4971 | verify() |
| | 4972 | { |
| | 4973 | /* use the standard verification for adding new contents */ |
| | 4974 | verifyPutInInterior(); |
| | 4975 | } |
| | 4976 | |
| | 4977 | action() |
| | 4978 | { |
| | 4979 | /* move the direct object into me */ |
| | 4980 | gDobj.moveInto(self); |
| | 4981 | |
| | 4982 | /* issue our default acknowledgment of the command */ |
| | 4983 | defaultReport(&okayPutInMsg); |
| | 4984 | } |
| | 4985 | } |
| | 4986 | ; |
| | 4987 | |
| | 4988 | |
| | 4989 | /* |
| | 4990 | * A "restricted holder" is a generic mix-in class for various container |
| | 4991 | * types (Containers, Surfaces, Undersides, RearContainers, RearSurfaces) |
| | 4992 | * that adds a restriction to what can be contained. |
| | 4993 | */ |
| | 4994 | class RestrictedHolder: object |
| | 4995 | /* |
| | 4996 | * A list of acceptable items for the container. This list can be |
| | 4997 | * used to identify the objects that can be put in the container (or |
| | 4998 | * on the surface, under the underside, or behind the rear container |
| | 4999 | * or surface). |
| | 5000 | */ |
| | 5001 | validContents = [] |
| | 5002 | |
| | 5003 | /* |
| | 5004 | * Is the given object allowed to go in this container (or |
| | 5005 | * on/under/behind it, as appropriate for the type)? Returns true if |
| | 5006 | * so, nil if not. By default, we'll return true if the object is |
| | 5007 | * found in our validContents list, nil if not. This can be |
| | 5008 | * overridden if a subclass wants to determine which objects are |
| | 5009 | * acceptable with some other kind of per-object test; for example, a |
| | 5010 | * subclass might accept only objects of a given class as contents, |
| | 5011 | * or might accept only contents with some particular attribute. |
| | 5012 | */ |
| | 5013 | canPutIn(obj) { return validContents.indexOf(obj) != nil; } |
| | 5014 | |
| | 5015 | /* |
| | 5016 | * Check a PUT IN/ON/UNDER/BEHIND action to ensure that the direct |
| | 5017 | * object is in our approved-contents list. |
| | 5018 | */ |
| | 5019 | checkPutDobj(msgProp) |
| | 5020 | { |
| | 5021 | /* validate the direct object */ |
| | 5022 | if (!canPutIn(gDobj)) |
| | 5023 | { |
| | 5024 | /* explain the problem */ |
| | 5025 | reportFailure(self.(msgProp)(gDobj)); |
| | 5026 | |
| | 5027 | /* terminate the command */ |
| | 5028 | exit; |
| | 5029 | } |
| | 5030 | } |
| | 5031 | ; |
| | 5032 | |
| | 5033 | |
| | 5034 | /* |
| | 5035 | * A special kind of container that only accepts specific contents. The |
| | 5036 | * acceptable contents can be specified by a list of enumerated items, |
| | 5037 | * or by a method that indicates whether or not an item is allowed. |
| | 5038 | */ |
| | 5039 | class RestrictedContainer: RestrictedHolder, Container |
| | 5040 | /* |
| | 5041 | * A message that explains why the direct object can't be put in this |
| | 5042 | * container. In most cases, the rather generic default message |
| | 5043 | * should be overridden to provide a specific reason that the dobj |
| | 5044 | * can't be put in this object. The rejected object is provided as a |
| | 5045 | * parameter in case the message needs to vary by object, but we |
| | 5046 | * ignore this and just use a single blanket failure message by |
| | 5047 | * default. |
| | 5048 | */ |
| | 5049 | cannotPutInMsg(obj) { return &cannotPutInRestrictedMsg; } |
| | 5050 | |
| | 5051 | /* override PutIn to enforce our contents restriction */ |
| | 5052 | iobjFor(PutIn) { check() { checkPutDobj(&cannotPutInMsg); } } |
| | 5053 | ; |
| | 5054 | |
| | 5055 | /* |
| | 5056 | * A single container is a special kind of container that can only |
| | 5057 | * contain a single item. If another object is put into this container, |
| | 5058 | * we'll remove any current contents. |
| | 5059 | */ |
| | 5060 | class SingleContainer: Container |
| | 5061 | /* override PutIn to enforce our single-contents rule */ |
| | 5062 | iobjFor(PutIn) |
| | 5063 | { |
| | 5064 | preCond { return inherited() + objEmpty; } |
| | 5065 | } |
| | 5066 | ; |
| | 5067 | |
| | 5068 | /* ------------------------------------------------------------------------ */ |
| | 5069 | /* |
| | 5070 | * OpenableContainer: an object that can contain things, and which can |
| | 5071 | * be opened and closed. |
| | 5072 | */ |
| | 5073 | class OpenableContainer: Openable, Container |
| | 5074 | ; |
| | 5075 | |
| | 5076 | /* ------------------------------------------------------------------------ */ |
| | 5077 | /* |
| | 5078 | * LockableContainer: an object that can contain things, and that can be |
| | 5079 | * opened and closed as well as locked and unlocked. |
| | 5080 | */ |
| | 5081 | class LockableContainer: Lockable, OpenableContainer |
| | 5082 | ; |
| | 5083 | |
| | 5084 | /* ------------------------------------------------------------------------ */ |
| | 5085 | /* |
| | 5086 | * KeyedContainer: an openable container that can be locked and |
| | 5087 | * unlocked, but only with a specified key. |
| | 5088 | */ |
| | 5089 | class KeyedContainer: LockableWithKey, OpenableContainer |
| | 5090 | ; |
| | 5091 | |
| | 5092 | |
| | 5093 | /* ------------------------------------------------------------------------ */ |
| | 5094 | /* |
| | 5095 | * Surface: an object that can have other objects placed on top of it. |
| | 5096 | * A surface is essentially the same as a regular container, but the |
| | 5097 | * contents of a surface behave as though they are on the surface's top |
| | 5098 | * rather than contained within the object. |
| | 5099 | */ |
| | 5100 | class Surface: BulkLimiter |
| | 5101 | /* |
| | 5102 | * Our fixed LOOK IN description. This is shown in response to LOOK |
| | 5103 | * IN before we list our portable contents; it can be used to show |
| | 5104 | * generally what the surface looks like. By default, we say |
| | 5105 | * nothing here. |
| | 5106 | */ |
| | 5107 | lookInDesc = nil |
| | 5108 | |
| | 5109 | /* my contents lister */ |
| | 5110 | contentsLister = surfaceContentsLister |
| | 5111 | descContentsLister = surfaceDescContentsLister |
| | 5112 | lookInLister = surfaceLookInLister |
| | 5113 | inlineContentsLister = surfaceInlineContentsLister |
| | 5114 | |
| | 5115 | /* |
| | 5116 | * we're a surface, so taking something from me that's not among my |
| | 5117 | * contents shows the message as "that's not on the iobj" |
| | 5118 | */ |
| | 5119 | takeFromNotInMessage = &takeFromNotOnMsg |
| | 5120 | |
| | 5121 | /* |
| | 5122 | * my message indicating that another object x cannot be put into me |
| | 5123 | * because I'm already in x |
| | 5124 | */ |
| | 5125 | circularlyInMessage = &circularlyOnMsg |
| | 5126 | |
| | 5127 | /* message phrase for objects put into me */ |
| | 5128 | putDestMessage = &putDestSurface |
| | 5129 | |
| | 5130 | /* message when we're too full for another object */ |
| | 5131 | tooFullMsg = &surfaceTooFullMsg |
| | 5132 | |
| | 5133 | /* |
| | 5134 | * Try moving an object into this container. For a surface, this |
| | 5135 | * performs a PUT ON command to move the object onto self. |
| | 5136 | */ |
| | 5137 | tryMovingObjInto(obj) { return tryImplicitAction(PutOn, obj, self); } |
| | 5138 | |
| | 5139 | /* -------------------------------------------------------------------- */ |
| | 5140 | /* |
| | 5141 | * Put On processing |
| | 5142 | */ |
| | 5143 | iobjFor(PutOn) |
| | 5144 | { |
| | 5145 | verify() |
| | 5146 | { |
| | 5147 | /* use the standard put-in verification */ |
| | 5148 | verifyPutInInterior(); |
| | 5149 | } |
| | 5150 | |
| | 5151 | action() |
| | 5152 | { |
| | 5153 | /* move the direct object onto me */ |
| | 5154 | gDobj.moveInto(self); |
| | 5155 | |
| | 5156 | /* issue our default acknowledgment */ |
| | 5157 | defaultReport(&okayPutOnMsg); |
| | 5158 | } |
| | 5159 | } |
| | 5160 | |
| | 5161 | /* |
| | 5162 | * Looking "in" a surface simply shows the surface's contents. |
| | 5163 | */ |
| | 5164 | dobjFor(LookIn) |
| | 5165 | { |
| | 5166 | verify() { } |
| | 5167 | action() |
| | 5168 | { |
| | 5169 | /* show our fixed lookInDesc */ |
| | 5170 | lookInDesc; |
| | 5171 | |
| | 5172 | /* show our contents */ |
| | 5173 | examineInterior(); |
| | 5174 | } |
| | 5175 | } |
| | 5176 | |
| | 5177 | /* use the PUT ON forms of the verifier messages */ |
| | 5178 | cannotPutInSelfMsg = &cannotPutOnSelfMsg |
| | 5179 | alreadyPutInMsg = &alreadyPutOnMsg |
| | 5180 | ; |
| | 5181 | |
| | 5182 | /* |
| | 5183 | * A special kind of surface that only accepts specific contents. |
| | 5184 | */ |
| | 5185 | class RestrictedSurface: RestrictedHolder, Surface |
| | 5186 | /* |
| | 5187 | * A message that explains why the direct object can't be put on this |
| | 5188 | * surface. In most cases, the rather generic default message should |
| | 5189 | * be overridden to provide a specific reason that the dobj can't be |
| | 5190 | * put on this surface. The rejected object is provided as a |
| | 5191 | * parameter in case the message needs to vary by object, but we |
| | 5192 | * ignore this and just use a single blanket failure message by |
| | 5193 | * default. |
| | 5194 | */ |
| | 5195 | cannotPutOnMsg(obj) { return &cannotPutOnRestrictedMsg; } |
| | 5196 | |
| | 5197 | /* override PutOn to enforce our contents restriction */ |
| | 5198 | iobjFor(PutOn) { check() { checkPutDobj(&cannotPutOnMsg); } } |
| | 5199 | ; |
| | 5200 | |
| | 5201 | /* ------------------------------------------------------------------------ */ |
| | 5202 | /* |
| | 5203 | * Food - something you can eat. By default, when an actor eats a food |
| | 5204 | * item, the item disappears. |
| | 5205 | */ |
| | 5206 | class Food: Thing |
| | 5207 | dobjFor(Taste) |
| | 5208 | { |
| | 5209 | /* tasting food is perfectly logical */ |
| | 5210 | verify() { } |
| | 5211 | } |
| | 5212 | |
| | 5213 | dobjFor(Eat) |
| | 5214 | { |
| | 5215 | verify() { } |
| | 5216 | action() |
| | 5217 | { |
| | 5218 | /* describe the consumption */ |
| | 5219 | defaultReport(&okayEatMsg); |
| | 5220 | |
| | 5221 | /* the object disappears */ |
| | 5222 | moveInto(nil); |
| | 5223 | } |
| | 5224 | } |
| | 5225 | ; |
| | 5226 | |
| | 5227 | /* ------------------------------------------------------------------------ */ |
| | 5228 | /* |
| | 5229 | * OnOffControl - a generic control that can be turned on and off. We |
| | 5230 | * keep track of an internal on/off state, and recognize the commands |
| | 5231 | * "turn on" and "turn off". |
| | 5232 | */ |
| | 5233 | class OnOffControl: Thing |
| | 5234 | /* |
| | 5235 | * The current on/off setting. We'll start in the 'off' position by |
| | 5236 | * default. |
| | 5237 | */ |
| | 5238 | isOn = nil |
| | 5239 | |
| | 5240 | /* |
| | 5241 | * On/off status name. This returns the appropriate name ('on' or |
| | 5242 | * 'off' in English) for our current status. |
| | 5243 | */ |
| | 5244 | onDesc = (isOn ? gLibMessages.onMsg(self) : gLibMessages.offMsg(self)) |
| | 5245 | |
| | 5246 | /* |
| | 5247 | * Change our on/off setting. Subclasses can override this to apply |
| | 5248 | * any side effects of changing the value. |
| | 5249 | */ |
| | 5250 | makeOn(val) |
| | 5251 | { |
| | 5252 | /* remember the new value */ |
| | 5253 | isOn = val; |
| | 5254 | } |
| | 5255 | |
| | 5256 | dobjFor(TurnOn) |
| | 5257 | { |
| | 5258 | verify() |
| | 5259 | { |
| | 5260 | /* if it's already on, complain */ |
| | 5261 | if (isOn) |
| | 5262 | illogicalAlready(&alreadySwitchedOnMsg); |
| | 5263 | } |
| | 5264 | action() |
| | 5265 | { |
| | 5266 | /* set to 'on' and generate a default report */ |
| | 5267 | makeOn(true); |
| | 5268 | defaultReport(&okayTurnOnMsg); |
| | 5269 | } |
| | 5270 | } |
| | 5271 | |
| | 5272 | dobjFor(TurnOff) |
| | 5273 | { |
| | 5274 | verify() |
| | 5275 | { |
| | 5276 | /* if it's already off, complain */ |
| | 5277 | if (!isOn) |
| | 5278 | illogicalAlready(&alreadySwitchedOffMsg); |
| | 5279 | } |
| | 5280 | action() |
| | 5281 | { |
| | 5282 | /* set to 'off' and generate a default report */ |
| | 5283 | makeOn(nil); |
| | 5284 | defaultReport(&okayTurnOffMsg); |
| | 5285 | } |
| | 5286 | } |
| | 5287 | ; |
| | 5288 | |
| | 5289 | /* |
| | 5290 | * Switch - a simple extension of the generic on/off control that can be |
| | 5291 | * used with a "switch" command without specifying "on" or "off", and |
| | 5292 | * treats "flip" synonymously. |
| | 5293 | */ |
| | 5294 | class Switch: OnOffControl |
| | 5295 | /* "switch" with no specific new setting - reverse our setting */ |
| | 5296 | dobjFor(Switch) |
| | 5297 | { |
| | 5298 | verify() { } |
| | 5299 | action() |
| | 5300 | { |
| | 5301 | /* reverse our setting and generate a report */ |
| | 5302 | makeOn(!isOn); |
| | 5303 | defaultReport(isOn ? &okayTurnOnMsg : &okayTurnOffMsg); |
| | 5304 | } |
| | 5305 | } |
| | 5306 | |
| | 5307 | /* "flip" is the same as "switch" for our purposes */ |
| | 5308 | dobjFor(Flip) asDobjFor(Switch) |
| | 5309 | ; |
| | 5310 | |
| | 5311 | /* ------------------------------------------------------------------------ */ |
| | 5312 | /* |
| | 5313 | * Settable - an abstract class for things you can set to different |
| | 5314 | * settings; the settings can be essentially anything, such as numbers |
| | 5315 | * (or other markers) on a dial, or stops on a sliding switch. |
| | 5316 | */ |
| | 5317 | class Settable: Thing |
| | 5318 | /* |
| | 5319 | * Our current setting. This is an arbitrary string value. The |
| | 5320 | * value initially assigned here is our initial setting; we'll |
| | 5321 | * update this whenever we're set to another setting. |
| | 5322 | */ |
| | 5323 | curSetting = '1' |
| | 5324 | |
| | 5325 | /* |
| | 5326 | * Canonicalize a proposed setting. This ensures that the setting is |
| | 5327 | * in a specific primary format when there are superficially |
| | 5328 | * different ways of expressing the same value. For example, if the |
| | 5329 | * setting is numeric, this could do things like trim off leading |
| | 5330 | * zeroes; for a text value, it could ensure the value is in the |
| | 5331 | * proper case. |
| | 5332 | */ |
| | 5333 | canonicalizeSetting(val) |
| | 5334 | { |
| | 5335 | /* |
| | 5336 | * by default, we don't have any special canonical format, so |
| | 5337 | * just return the value as it is |
| | 5338 | */ |
| | 5339 | return val; |
| | 5340 | } |
| | 5341 | |
| | 5342 | /* |
| | 5343 | * Change our setting. This is always called with the canonical |
| | 5344 | * version of the new setting, as returned by canonicalizeSetting(). |
| | 5345 | * Subclasses can override this routine to apply any side effects of |
| | 5346 | * changing the value. |
| | 5347 | */ |
| | 5348 | makeSetting(val) |
| | 5349 | { |
| | 5350 | /* remember the new value */ |
| | 5351 | curSetting = val; |
| | 5352 | } |
| | 5353 | |
| | 5354 | /* |
| | 5355 | * Is the given text a valid setting? Returns true if so, nil if |
| | 5356 | * not. This should not display any messages; simply indicate |
| | 5357 | * whether or not the setting is valid. |
| | 5358 | * |
| | 5359 | * This is always called with the *canonical* value of the proposed |
| | 5360 | * new setting, as returned by canonicalizeSetting(). |
| | 5361 | */ |
| | 5362 | isValidSetting(val) |
| | 5363 | { |
| | 5364 | /* |
| | 5365 | * By default, allow anything; subclasses should override to |
| | 5366 | * enforce our valid set of values. |
| | 5367 | */ |
| | 5368 | return true; |
| | 5369 | } |
| | 5370 | |
| | 5371 | /* |
| | 5372 | * "set <self>" action |
| | 5373 | */ |
| | 5374 | dobjFor(Set) |
| | 5375 | { |
| | 5376 | verify() { logicalRank(150, 'settable'); } |
| | 5377 | action() { askForLiteral(SetTo); } |
| | 5378 | } |
| | 5379 | |
| | 5380 | /* |
| | 5381 | * "set <self> to <literal>" action |
| | 5382 | */ |
| | 5383 | dobjFor(SetTo) |
| | 5384 | { |
| | 5385 | preCond = [touchObj] |
| | 5386 | verify() |
| | 5387 | { |
| | 5388 | local txt; |
| | 5389 | |
| | 5390 | /* |
| | 5391 | * If we already know our literal text, and it's not valid, |
| | 5392 | * reduce the logicalness. Don't actually make it |
| | 5393 | * illogical, as it's probably still more logical to set a |
| | 5394 | * settable to an invalid setting than to set something that |
| | 5395 | * isn't settable at all. |
| | 5396 | */ |
| | 5397 | if ((txt = gAction.getLiteral()) != nil |
| | 5398 | && !isValidSetting(canonicalizeSetting(txt))) |
| | 5399 | logicalRank(50, 'invalid setting'); |
| | 5400 | } |
| | 5401 | check() |
| | 5402 | { |
| | 5403 | /* if the setting is not valid, don't allow it */ |
| | 5404 | if (!isValidSetting(canonicalizeSetting(gAction.getLiteral()))) |
| | 5405 | { |
| | 5406 | /* there is no such setting */ |
| | 5407 | reportFailure(setToInvalidMsgProp); |
| | 5408 | exit; |
| | 5409 | } |
| | 5410 | } |
| | 5411 | action() |
| | 5412 | { |
| | 5413 | /* set the new value */ |
| | 5414 | makeSetting(canonicalizeSetting(gAction.getLiteral())); |
| | 5415 | |
| | 5416 | /* remark on the change */ |
| | 5417 | defaultReport(okaySetToMsgProp, curSetting); |
| | 5418 | } |
| | 5419 | } |
| | 5420 | |
| | 5421 | /* our message property for an invalid setting */ |
| | 5422 | setToInvalidMsgProp = &setToInvalidMsg |
| | 5423 | |
| | 5424 | /* our message property for acknowledging a new setting */ |
| | 5425 | okaySetToMsgProp = &okaySetToMsg |
| | 5426 | ; |
| | 5427 | |
| | 5428 | /* |
| | 5429 | * Dial - something you can turn to different settings. Note that dials |
| | 5430 | * are usually used as components of larger objects; since our base |
| | 5431 | * class is the basic Settable, component dials should be created to |
| | 5432 | * inherit multiply from Dial and Component, in that order. |
| | 5433 | * |
| | 5434 | * This is almost hte same as a regular Settable; the only thing we add |
| | 5435 | * is that we make "turn <self> to <literal>" equivalent to "set <self> |
| | 5436 | * to <literal>", as this is the verb most people would use to set a |
| | 5437 | * dial. |
| | 5438 | */ |
| | 5439 | class Dial: Settable |
| | 5440 | /* "turn" with no destination - indicate that we need a setting */ |
| | 5441 | dobjFor(Turn) |
| | 5442 | { |
| | 5443 | verify() { illogical(&mustSpecifyTurnToMsg); } |
| | 5444 | } |
| | 5445 | |
| | 5446 | /* treat "turn <self> to <literal>" the same as "set to" */ |
| | 5447 | dobjFor(TurnTo) asDobjFor(SetTo) |
| | 5448 | |
| | 5449 | /* refer to setting the dial as turning it in our messages */ |
| | 5450 | setToInvalidMsgProp = &turnToInvalidMsg |
| | 5451 | okaySetToMsgProp = &okayTurnToMsg |
| | 5452 | ; |
| | 5453 | |
| | 5454 | /* |
| | 5455 | * Numbered Dial - something you can turn to a range of numeric values. |
| | 5456 | */ |
| | 5457 | class NumberedDial: Dial |
| | 5458 | /* |
| | 5459 | * The range of settings - the dial can be set to values from the |
| | 5460 | * minimum to the maximum, inclusive. |
| | 5461 | */ |
| | 5462 | minSetting = 1 |
| | 5463 | maxSetting = 10 |
| | 5464 | |
| | 5465 | /* |
| | 5466 | * Canonicalize a proposed setting value. For numbers, strip off any |
| | 5467 | * leading zeroes, since these don't change the meaning of the value. |
| | 5468 | */ |
| | 5469 | canonicalizeSetting(val) |
| | 5470 | { |
| | 5471 | local num; |
| | 5472 | |
| | 5473 | /* try parsing it as a digit string or a spelled-out number */ |
| | 5474 | if ((num = parseInt(val)) != nil) |
| | 5475 | { |
| | 5476 | /* |
| | 5477 | * we parsed it successfully - return the string |
| | 5478 | * representation of the numeric value |
| | 5479 | */ |
| | 5480 | return toString(num); |
| | 5481 | } |
| | 5482 | |
| | 5483 | /* it didn't parse as a number, so just return it as-is */ |
| | 5484 | return val; |
| | 5485 | } |
| | 5486 | |
| | 5487 | /* |
| | 5488 | * Check a setting for validity. A setting is valid only if it's a |
| | 5489 | * number within the allowed range for the dial. |
| | 5490 | */ |
| | 5491 | isValidSetting(val) |
| | 5492 | { |
| | 5493 | local num; |
| | 5494 | |
| | 5495 | /* if it doesn't look like a number, it's not valid */ |
| | 5496 | if (rexMatch('<digit>+', val) != val.length()) |
| | 5497 | return nil; |
| | 5498 | |
| | 5499 | /* get the numeric value */ |
| | 5500 | num = toInteger(val); |
| | 5501 | |
| | 5502 | /* it's valid if it's within range */ |
| | 5503 | return num >= minSetting && num <= maxSetting; |
| | 5504 | } |
| | 5505 | ; |
| | 5506 | |
| | 5507 | /* |
| | 5508 | * Labeled Dial - something you can turn to a set of arbitrary text |
| | 5509 | * labels. |
| | 5510 | */ |
| | 5511 | class LabeledDial: Dial |
| | 5512 | /* |
| | 5513 | * The list of valid settings. Each entry in this list should be a |
| | 5514 | * string value. We ignore the case of these labels (we convert |
| | 5515 | * everything to upper-case when comparing labels). |
| | 5516 | */ |
| | 5517 | validSettings = [] |
| | 5518 | |
| | 5519 | /* |
| | 5520 | * Canonicalize the setting. We consider case insignificant in |
| | 5521 | * matching our labels, but the canonical version of a setting is the |
| | 5522 | * one that appears in the validSettings list - so if the player |
| | 5523 | * types in SET DIAL TO EXTRA LOUD, and the validSettings list |
| | 5524 | * contains 'Extra Loud', we'll want to convert the 'EXTRA LOUD' to |
| | 5525 | * the capitalization of the validSettings entry. |
| | 5526 | */ |
| | 5527 | canonicalizeSetting(val) |
| | 5528 | { |
| | 5529 | local txt; |
| | 5530 | |
| | 5531 | /* |
| | 5532 | * convert it to upper-case, so that we can compare it to our |
| | 5533 | * valid labels without regard to case |
| | 5534 | */ |
| | 5535 | txt = val.toUpper(); |
| | 5536 | |
| | 5537 | /* |
| | 5538 | * if we find a match in the validSettings list, return the match |
| | 5539 | * from the list, since that's the canonical format |
| | 5540 | */ |
| | 5541 | if ((txt = validSettings.valWhich({x: x.toUpper() == txt})) != nil) |
| | 5542 | return txt; |
| | 5543 | |
| | 5544 | /* we didn't find a match, so leave the original value unchanged */ |
| | 5545 | return val; |
| | 5546 | } |
| | 5547 | |
| | 5548 | /* |
| | 5549 | * Check a setting for validity. A setting is valid only if it |
| | 5550 | * appears in the validSettings list for this dial. |
| | 5551 | */ |
| | 5552 | isValidSetting(val) |
| | 5553 | { |
| | 5554 | /* |
| | 5555 | * If the given value appears in our validSettings list, it's a |
| | 5556 | * valid setting; otherwise, it's not valid. Ignore case when |
| | 5557 | * comparing values by converting the valid labels to upper case; |
| | 5558 | * we've already converted the value we're testing to upper case, |
| | 5559 | * so the case mix won't matter in our comparison. |
| | 5560 | * |
| | 5561 | * Note that we're handed a canonical setting value, so we don't |
| | 5562 | * have to worry about case differences. |
| | 5563 | */ |
| | 5564 | return validSettings.indexOf(val) != nil; |
| | 5565 | } |
| | 5566 | ; |
| | 5567 | |
| | 5568 | |
| | 5569 | /* ------------------------------------------------------------------------ */ |
| | 5570 | /* |
| | 5571 | * Button - something you can push to activate, as a control for a |
| | 5572 | * mechanical device. |
| | 5573 | */ |
| | 5574 | class Button: Thing |
| | 5575 | dobjFor(Push) |
| | 5576 | { |
| | 5577 | verify() { } |
| | 5578 | action() |
| | 5579 | { |
| | 5580 | /* |
| | 5581 | * individual buttons should override this to carry out any |
| | 5582 | * special action for the button; by default, we'll just |
| | 5583 | * show a simple acknowledgment |
| | 5584 | */ |
| | 5585 | defaultReport(&okayPushButtonMsg); |
| | 5586 | } |
| | 5587 | } |
| | 5588 | ; |
| | 5589 | |
| | 5590 | /* ------------------------------------------------------------------------ */ |
| | 5591 | /* |
| | 5592 | * Lever - something you can push, pull, or move, generally as a control |
| | 5593 | * for a mechanical device. Our basic lever has two states, "pushed" |
| | 5594 | * and "pulled". |
| | 5595 | */ |
| | 5596 | class Lever: Thing |
| | 5597 | /* |
| | 5598 | * The current state. We have two states: "pushed" and "pulled". |
| | 5599 | * We start in the pushed state, so the lever can initially be |
| | 5600 | * pulled, since "pull" is the verb most people would first think to |
| | 5601 | * apply to a lever. |
| | 5602 | */ |
| | 5603 | isPulled = nil |
| | 5604 | |
| | 5605 | /* |
| | 5606 | * Set the state. This can be overridden to apply side effects as |
| | 5607 | * needed. |
| | 5608 | */ |
| | 5609 | makePulled(pulled) |
| | 5610 | { |
| | 5611 | /* note the new state */ |
| | 5612 | isPulled = pulled; |
| | 5613 | } |
| | 5614 | |
| | 5615 | /* |
| | 5616 | * Action handlers. We handle push and pull, and we treat "move" as |
| | 5617 | * equivalent to whichever of push or pull is appropriate to reverse |
| | 5618 | * the current state. |
| | 5619 | */ |
| | 5620 | dobjFor(Push) |
| | 5621 | { |
| | 5622 | verify() |
| | 5623 | { |
| | 5624 | /* if it's already pushed, pushing it again makes no sense */ |
| | 5625 | if (!isPulled) |
| | 5626 | illogicalAlready(&alreadyPushedMsg); |
| | 5627 | } |
| | 5628 | action() |
| | 5629 | { |
| | 5630 | /* set the new state to pushed (i.e., not pulled) */ |
| | 5631 | makePulled(nil); |
| | 5632 | |
| | 5633 | /* make the default report */ |
| | 5634 | defaultReport(&okayPushLeverMsg); |
| | 5635 | } |
| | 5636 | } |
| | 5637 | dobjFor(Pull) |
| | 5638 | { |
| | 5639 | verify() |
| | 5640 | { |
| | 5641 | /* if it's already pulled, pulling it again makes no sense */ |
| | 5642 | if (isPulled) |
| | 5643 | illogicalAlready(&alreadyPulledMsg); |
| | 5644 | } |
| | 5645 | action() |
| | 5646 | { |
| | 5647 | /* set the new state to pulled */ |
| | 5648 | makePulled(true); |
| | 5649 | |
| | 5650 | /* make the default report */ |
| | 5651 | defaultReport(&okayPullLeverMsg); |
| | 5652 | } |
| | 5653 | } |
| | 5654 | dobjFor(Move) |
| | 5655 | { |
| | 5656 | verify() { } |
| | 5657 | check() |
| | 5658 | { |
| | 5659 | /* run the check for pushing or pulling, as appropriate */ |
| | 5660 | if (isPulled) |
| | 5661 | checkDobjPush(); |
| | 5662 | else |
| | 5663 | checkDobjPull(); |
| | 5664 | } |
| | 5665 | action() |
| | 5666 | { |
| | 5667 | /* if we're pulled, push the lever; otherwise pull it */ |
| | 5668 | if (isPulled) |
| | 5669 | actionDobjPush(); |
| | 5670 | else |
| | 5671 | actionDobjPull(); |
| | 5672 | } |
| | 5673 | } |
| | 5674 | ; |
| | 5675 | |
| | 5676 | /* |
| | 5677 | * A spring-loaded lever is a lever that bounces back to its starting |
| | 5678 | * position after being pulled. This is essentially equivalent in terms |
| | 5679 | * of functionality to a button, but can at least provide superficial |
| | 5680 | * variety. |
| | 5681 | */ |
| | 5682 | class SpringLever: Lever |
| | 5683 | dobjFor(Pull) |
| | 5684 | { |
| | 5685 | action() |
| | 5686 | { |
| | 5687 | /* |
| | 5688 | * Individual objects should override this to perform the |
| | 5689 | * appropriate action when the lever is pulled. By default, |
| | 5690 | * we'll do nothing except show a default report. |
| | 5691 | */ |
| | 5692 | defaultReport(&okayPullSpringLeverMsg); |
| | 5693 | } |
| | 5694 | } |
| | 5695 | ; |
| | 5696 | |
| | 5697 | |
| | 5698 | /* ------------------------------------------------------------------------ */ |
| | 5699 | /* |
| | 5700 | * An item that can be worn |
| | 5701 | */ |
| | 5702 | class Wearable: Thing |
| | 5703 | /* is the item currently being worn? */ |
| | 5704 | isWorn() |
| | 5705 | { |
| | 5706 | /* it's being worn if the wearer is non-nil */ |
| | 5707 | return wornBy != nil; |
| | 5708 | } |
| | 5709 | |
| | 5710 | /* |
| | 5711 | * make the item worn by the given actor; if actor is nil, the item |
| | 5712 | * isn't being worn by anyone |
| | 5713 | */ |
| | 5714 | makeWornBy(actor) |
| | 5715 | { |
| | 5716 | /* remember who's wearing the item */ |
| | 5717 | wornBy = actor; |
| | 5718 | } |
| | 5719 | |
| | 5720 | /* |
| | 5721 | * An item being worn is not considered to be held in the wearer's |
| | 5722 | * hands. |
| | 5723 | */ |
| | 5724 | isHeldBy(actor) |
| | 5725 | { |
| | 5726 | if (isWornBy(actor)) |
| | 5727 | { |
| | 5728 | /* it's being worn by the actor, so it's not also being held */ |
| | 5729 | return nil; |
| | 5730 | } |
| | 5731 | else |
| | 5732 | { |
| | 5733 | /* |
| | 5734 | * it's not being worn by this actor, so use the default |
| | 5735 | * interpretation of being held |
| | 5736 | */ |
| | 5737 | return inherited(actor); |
| | 5738 | } |
| | 5739 | } |
| | 5740 | |
| | 5741 | /* |
| | 5742 | * A wearable is not considered held by an actor when it is being |
| | 5743 | * worn, so we must do a what-if test for removing the item if the |
| | 5744 | * actor is currently wearing the item. If the actor isn't wearing |
| | 5745 | * the item, we can use the default test of moving the item into the |
| | 5746 | * actor's inventory. |
| | 5747 | */ |
| | 5748 | whatIfHeldBy(func, newLoc) |
| | 5749 | { |
| | 5750 | /* |
| | 5751 | * If the article is being worn, and it's already in the same |
| | 5752 | * location we're moving it to, simply test with the article no |
| | 5753 | * longer being worn. Otherwise, inherit the default handling. |
| | 5754 | */ |
| | 5755 | if (location == newLoc && wornBy != nil) |
| | 5756 | return whatIf(func, &wornBy, nil); |
| | 5757 | else |
| | 5758 | return inherited(func, newLoc); |
| | 5759 | } |
| | 5760 | |
| | 5761 | /* |
| | 5762 | * Try making the current command's actor hold me. If I'm already |
| | 5763 | * directly in the actor's inventory and I'm being worn, we'll try a |
| | 5764 | * 'doff' command; otherwise, we'll use the default handling. |
| | 5765 | */ |
| | 5766 | tryHolding() |
| | 5767 | { |
| | 5768 | /* |
| | 5769 | * Try an implicit 'take' command. If the actor is carrying the |
| | 5770 | * object indirectly, make the command "take from" instead, |
| | 5771 | * since what we really want to do is take the object out of its |
| | 5772 | * container. |
| | 5773 | */ |
| | 5774 | if (location == gActor && isWornBy(gActor)) |
| | 5775 | return tryImplicitAction(Doff, self); |
| | 5776 | else |
| | 5777 | return inherited(); |
| | 5778 | } |
| | 5779 | |
| | 5780 | /* |
| | 5781 | * The object wearing this object, if any; if I'm not being worn, |
| | 5782 | * this is nil. The wearer should always be a container (direct or |
| | 5783 | * indirect) of this object - in order to wear something, you must |
| | 5784 | * be carrying it. In most cases, the wearer should be the direct |
| | 5785 | * container of the object. |
| | 5786 | * |
| | 5787 | * The reason we keep track of who's wearing the object (rather than |
| | 5788 | * simply keeping track of whether it's being worn) is to allow for |
| | 5789 | * cases where an actor is carrying another actor. Since this |
| | 5790 | * object will be (indirectly) inside both actors in such cases, we |
| | 5791 | * would have to inspect intermediate containers to determine |
| | 5792 | * whether or not the outer actor was wearing the object if we |
| | 5793 | * didn't keep track of the wearer directly. |
| | 5794 | */ |
| | 5795 | wornBy = nil |
| | 5796 | |
| | 5797 | /* am I worn by the given object? */ |
| | 5798 | isWornBy(actor) |
| | 5799 | { |
| | 5800 | return wornBy == actor; |
| | 5801 | } |
| | 5802 | |
| | 5803 | /* |
| | 5804 | * An article of clothing that is being worn by an actor does not |
| | 5805 | * typically encumber the actor at all, so by default we'll return |
| | 5806 | * zero if we're being worn by the actor, and our normal bulk |
| | 5807 | * otherwise. |
| | 5808 | */ |
| | 5809 | getEncumberingBulk(actor) |
| | 5810 | { |
| | 5811 | /* |
| | 5812 | * if we're being worn by the actor, we create no encumbrance at |
| | 5813 | * all; otherwise, return our normal bulk |
| | 5814 | */ |
| | 5815 | return isWornBy(actor) ? 0 : getBulk(); |
| | 5816 | } |
| | 5817 | |
| | 5818 | /* |
| | 5819 | * An article of clothing typically encumbers an actor with the same |
| | 5820 | * weight whether or not the actor is wearing the item. However, |
| | 5821 | * this might not apply to all objects; a suit of armor, for |
| | 5822 | * example, might be slightly less encumbering in terms of weight |
| | 5823 | * when worn than it is when held because the distribution of weight |
| | 5824 | * is more manageable when worn. By default, we simply return our |
| | 5825 | * normal weight, whether worn or not; subclasses can override as |
| | 5826 | * needed to differentiate. |
| | 5827 | */ |
| | 5828 | getEncumberingWeight(actor) |
| | 5829 | { |
| | 5830 | return getWeight(); |
| | 5831 | } |
| | 5832 | |
| | 5833 | /* get my state */ |
| | 5834 | getState = (isWorn() ? wornState : unwornState) |
| | 5835 | |
| | 5836 | /* my list of possible states */ |
| | 5837 | allStates = [wornState, unwornState] |
| | 5838 | |
| | 5839 | |
| | 5840 | /* -------------------------------------------------------------------- */ |
| | 5841 | /* |
| | 5842 | * Action processing |
| | 5843 | */ |
| | 5844 | |
| | 5845 | dobjFor(Wear) |
| | 5846 | { |
| | 5847 | preCond = [objHeld] |
| | 5848 | verify() |
| | 5849 | { |
| | 5850 | /* make sure the actor isn't already wearing the item */ |
| | 5851 | if (isWornBy(gActor)) |
| | 5852 | illogicalAlready(&alreadyWearingMsg); |
| | 5853 | } |
| | 5854 | action() |
| | 5855 | { |
| | 5856 | /* make the item worn and describe what happened */ |
| | 5857 | makeWornBy(gActor); |
| | 5858 | defaultReport(&okayWearMsg); |
| | 5859 | } |
| | 5860 | } |
| | 5861 | |
| | 5862 | dobjFor(Doff) |
| | 5863 | { |
| | 5864 | preCond = [roomToHoldObj] |
| | 5865 | verify() |
| | 5866 | { |
| | 5867 | /* |
| | 5868 | * Make sure the actor is actually wearing the item. If |
| | 5869 | * they're not, it's illogical, but if they are, it's an |
| | 5870 | * especially likely thing to remove. |
| | 5871 | */ |
| | 5872 | if (!isWornBy(gActor)) |
| | 5873 | illogicalAlready(¬WearingMsg); |
| | 5874 | else |
| | 5875 | logicalRank(150, 'worn'); |
| | 5876 | } |
| | 5877 | action() |
| | 5878 | { |
| | 5879 | /* un-wear the item and describe what happened */ |
| | 5880 | makeWornBy(nil); |
| | 5881 | defaultReport(&okayDoffMsg); |
| | 5882 | } |
| | 5883 | } |
| | 5884 | |
| | 5885 | /* "remove <wearable>" is the same as "doff <wearable>" */ |
| | 5886 | dobjFor(Remove) asDobjFor(Doff) |
| | 5887 | |
| | 5888 | /* |
| | 5889 | * if a wearable is being worn, showing it off to someone doesn't |
| | 5890 | * require holding it |
| | 5891 | */ |
| | 5892 | dobjFor(ShowTo) |
| | 5893 | { |
| | 5894 | preCond() |
| | 5895 | { |
| | 5896 | /* get the standard handling */ |
| | 5897 | local lst = inherited(); |
| | 5898 | |
| | 5899 | /* if we're being worn, don't require us to be held */ |
| | 5900 | if (isWornBy(gActor)) |
| | 5901 | lst -= objHeld; |
| | 5902 | |
| | 5903 | /* return the result */ |
| | 5904 | return lst; |
| | 5905 | } |
| | 5906 | } |
| | 5907 | ; |
| | 5908 | |
| | 5909 | /* ------------------------------------------------------------------------ */ |
| | 5910 | /* |
| | 5911 | * An item that can provide light. |
| | 5912 | * |
| | 5913 | * Any Thing can provide light, but this class should be used for |
| | 5914 | * objects that explicitly serve as light sources from the player's |
| | 5915 | * perspective. Objects of this class display a "providing light" |
| | 5916 | * status message in inventory listings, and can be turned on and off |
| | 5917 | * via the isLit property. |
| | 5918 | */ |
| | 5919 | class LightSource: Thing |
| | 5920 | /* is the light source currently turned on? */ |
| | 5921 | isLit = true |
| | 5922 | |
| | 5923 | /* |
| | 5924 | * Turn the light source on or off. Note that we don't have to make |
| | 5925 | * any special check for a change to the light level, because the |
| | 5926 | * main action handler always checks for a change in light/dark |
| | 5927 | * status over the course of the turn. |
| | 5928 | */ |
| | 5929 | makeLit(lit) |
| | 5930 | { |
| | 5931 | /* change the status */ |
| | 5932 | isLit = lit; |
| | 5933 | } |
| | 5934 | |
| | 5935 | /* |
| | 5936 | * We can distinguish light sources according to their isLit status. |
| | 5937 | * Give the lit/unlit distinction higher priority than the normal |
| | 5938 | * ownership/containment distinction. |
| | 5939 | */ |
| | 5940 | distinguishers = [basicDistinguisher, litUnlitDistinguisher, |
| | 5941 | ownershipDistinguisher, locationDistinguisher] |
| | 5942 | |
| | 5943 | /* the brightness that the object has when it is on and off */ |
| | 5944 | brightnessOn = 3 |
| | 5945 | brightnessOff = 0 |
| | 5946 | |
| | 5947 | /* |
| | 5948 | * return the appropriate on/off brightness, depending on whether or |
| | 5949 | * not we're currently lit |
| | 5950 | */ |
| | 5951 | brightness { return isLit ? brightnessOn : brightnessOff; } |
| | 5952 | |
| | 5953 | /* get our current state: lit or unlit */ |
| | 5954 | getState = (brightness > 1 ? lightSourceStateOn : lightSourceStateOff) |
| | 5955 | |
| | 5956 | /* get our set of possible states */ |
| | 5957 | allStates = [lightSourceStateOn, lightSourceStateOff] |
| | 5958 | ; |
| | 5959 | |
| | 5960 | /* |
| | 5961 | * A Flashlight is a special kind of light source that can be switched |
| | 5962 | * on and off. |
| | 5963 | * |
| | 5964 | * To create a limited-use flashlight (with a limited battery life, for |
| | 5965 | * example), you can combine this class with FueledLightSource. The |
| | 5966 | * flashlight's on/off switch status is a separate property from its |
| | 5967 | * lit/unlit light-source status, so combining Flashlight with |
| | 5968 | * FueledLightSource will actually allow the two to become decoupled: a |
| | 5969 | * flashlight can be on without providing light, when the battery is |
| | 5970 | * dead. For this reason, you might want to override the decription, |
| | 5971 | * and possibly the TurnOn action() handler, to customize the messages |
| | 5972 | * for the case when the flashlight is switched on but out of power. |
| | 5973 | */ |
| | 5974 | class Flashlight: LightSource, Switch |
| | 5975 | /* our switch status - start in the 'off' position */ |
| | 5976 | isOn = nil |
| | 5977 | |
| | 5978 | /* |
| | 5979 | * Change the on/off status. Note that switching the flashlight on |
| | 5980 | * or off should always be done via makeOn - the makeLit inherited |
| | 5981 | * from the LightSource should never be called directly on a |
| | 5982 | * Flashlight object, because it doesn't keep the switch on/off and |
| | 5983 | * flashlight lit/unlit status in sync. This routine is the one to |
| | 5984 | * call because it keeps everything properly synchronized. |
| | 5985 | */ |
| | 5986 | makeOn(stat) |
| | 5987 | { |
| | 5988 | /* inherit the default handling */ |
| | 5989 | inherited(stat); |
| | 5990 | |
| | 5991 | /* |
| | 5992 | * Set the 'lit' status to track the on/off status. Note that |
| | 5993 | * we don't simply do this by deriving isLit from isOn because |
| | 5994 | * we want to invoke the side effects of changing the status by |
| | 5995 | * calling makeLit explicitly. We also want to allow the two to |
| | 5996 | * be decoupled when necessary, such as might happen when the |
| | 5997 | * flashlight's bulb is burned out, or its battery has run down. |
| | 5998 | */ |
| | 5999 | makeLit(stat); |
| | 6000 | } |
| | 6001 | |
| | 6002 | /* initialize */ |
| | 6003 | initializeThing() |
| | 6004 | { |
| | 6005 | /* inherit default handling */ |
| | 6006 | inherited(); |
| | 6007 | |
| | 6008 | /* |
| | 6009 | * Make sure our initial isLit setting (for the LightSource) |
| | 6010 | * matches our initial isOn steting (for the Switch). The |
| | 6011 | * switch status drives the light source status, so initialize |
| | 6012 | * the latter from the former. |
| | 6013 | */ |
| | 6014 | isLit = isOn; |
| | 6015 | } |
| | 6016 | |
| | 6017 | /* treat 'light' and 'extinguish' as 'turn on' and 'turn off' */ |
| | 6018 | dobjFor(Light) asDobjFor(TurnOn) |
| | 6019 | dobjFor(Extinguish) asDobjFor(TurnOff) |
| | 6020 | |
| | 6021 | /* if we turn on the flashlight, but it doesn't light, mention this */ |
| | 6022 | dobjFor(TurnOn) |
| | 6023 | { |
| | 6024 | action() |
| | 6025 | { |
| | 6026 | /* do the normal work */ |
| | 6027 | inherited(); |
| | 6028 | |
| | 6029 | /* |
| | 6030 | * If we're now on but not lit, mention this. This can |
| | 6031 | * happen when we run out of power in the battery, or our |
| | 6032 | * bulb is missing or burned out, or we're simply broken. |
| | 6033 | */ |
| | 6034 | if (isOn && !isLit) |
| | 6035 | mainReport(&flashlightOnButDarkMsg); |
| | 6036 | } |
| | 6037 | } |
| | 6038 | ; |
| | 6039 | |