| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library - extras: special-purpose object classes |
| | 7 | * |
| | 8 | * This module defines classes for specialized simulation objects. |
| | 9 | * |
| | 10 | * Portions are based on original work by Eric Eve, incorporated by |
| | 11 | * permission. |
| | 12 | */ |
| | 13 | |
| | 14 | /* include the library header */ |
| | 15 | #include "adv3.h" |
| | 16 | |
| | 17 | |
| | 18 | /* ------------------------------------------------------------------------ */ |
| | 19 | /* |
| | 20 | * A "complex" container is an object that can have multiple kinds of |
| | 21 | * contents simultaneously. For example, a complex container could act |
| | 22 | * as both a surface, so that some objects are sitting on top of it, and |
| | 23 | * simultaneously as a container, with objects inside. |
| | 24 | * |
| | 25 | * The standard containment model only allows one kind of containment per |
| | 26 | * container, because the nature of the containment is a feature of the |
| | 27 | * container itself. The complex container handles multiple simultaneous |
| | 28 | * containment types by using one or more sub-containers: for example, if |
| | 29 | * we want to be able to act as both a surface and a regular container, |
| | 30 | * we use two sub-containers, one of class Surface and one of class |
| | 31 | * Container, to hold the different types of contents. When we need to |
| | 32 | * perform an operation specific to a certain containment type, we |
| | 33 | * delegate the operation to the sub-container of the appropriate type. |
| | 34 | * |
| | 35 | * Note that the complex container itself treats its direct contents as |
| | 36 | * components, so any component parts can be made direct contents of the |
| | 37 | * complex container object. |
| | 38 | * |
| | 39 | * If you want to include objects in your source code that are initially |
| | 40 | * located within the component sub-containers, define them as directly |
| | 41 | * within the ComplexContainer object, but give each one a 'subLocation' |
| | 42 | * property set to the property of the component sub-container that will |
| | 43 | * initially contain it. For example, here's how you'd place a blanket |
| | 44 | * inside a washing machine, and a laundry basket on top of it: |
| | 45 | * |
| | 46 | *. + washingMachine: ComplexContainer 'washing machine' 'washing machine' |
| | 47 | *. subContainer: ComplexComponent, Container { etc } |
| | 48 | *. subSurface: ComplexComponent, Surface { etc } |
| | 49 | *. ; |
| | 50 | *. |
| | 51 | *. ++ Thing 'big cotton blanket' 'blanket' |
| | 52 | *. subLocation = &subContainer |
| | 53 | *. ; |
| | 54 | *. |
| | 55 | *. ++ Container 'laundry basket' 'laundry basket' |
| | 56 | *. subLocation = &subSurface |
| | 57 | *. ; |
| | 58 | * |
| | 59 | * The subLocation setting is only used for initialization, and we |
| | 60 | * automatically set it to nil right after we use it to set up the |
| | 61 | * initial location. If you want to move something into one of the |
| | 62 | * sub-containers on the fly, simply refer to the desired component |
| | 63 | * directly: |
| | 64 | * |
| | 65 | * pants.moveInto(washingMachine.subContainer); |
| | 66 | */ |
| | 67 | class ComplexContainer: Thing |
| | 68 | /* |
| | 69 | * Our inner container, if any. This is a "secret" object (in other |
| | 70 | * words, it doesn't appear to players as a separate named object) |
| | 71 | * that we use to store the contents that are meant to be within the |
| | 72 | * complex container. If this is to be used, it should be set to a |
| | 73 | * Container object - the most convenient way to do this is by using |
| | 74 | * the nested object syntax to define a ComplexComponent Container |
| | 75 | * instance, like so: |
| | 76 | * |
| | 77 | * washingMachine: ComplexContainer |
| | 78 | *. subContainer: ComplexComponent, Container { etc } |
| | 79 | *. ; |
| | 80 | * |
| | 81 | * Note that we use the ComplexComponent class (as well as |
| | 82 | * Container) for the sub-container object. This makes the |
| | 83 | * sub-container automatically use the name of its enclosing object |
| | 84 | * in messages (in this case, the sub-container will use the same |
| | 85 | * name as the washing machine). |
| | 86 | * |
| | 87 | * Note that the sub-containers don't have to be of class |
| | 88 | * ComplexComponent, but using that class makes your job a little |
| | 89 | * easier because the class sets the location and naming |
| | 90 | * automatically. If you prefer to define your sub-containers as |
| | 91 | * separate objects, not nested in the ComplexContainer's |
| | 92 | * definition, there's no need to make them ComplexComponents; just |
| | 93 | * make them ordinary Component objects. |
| | 94 | * |
| | 95 | * If this property is left as nil, then we don't have an inner |
| | 96 | * container. |
| | 97 | */ |
| | 98 | subContainer = nil |
| | 99 | |
| | 100 | /* |
| | 101 | * Our inner surface, if any. This is a secret object like the |
| | 102 | * inner container; this object acts as our surface. |
| | 103 | */ |
| | 104 | subSurface = nil |
| | 105 | |
| | 106 | /* |
| | 107 | * Our underside, if any. This is a secret object like the inner |
| | 108 | * container; this object can act as the space underneath us, or as |
| | 109 | * our bottom surface. |
| | 110 | */ |
| | 111 | subUnderside = nil |
| | 112 | |
| | 113 | /* |
| | 114 | * Our rear surface or container, if any. This is a secret internal |
| | 115 | * object like the inner container; this object can act as our back |
| | 116 | * surface, or as the space just behind us. |
| | 117 | */ |
| | 118 | subRear = nil |
| | 119 | |
| | 120 | /* a list of all of our component objects */ |
| | 121 | allSubLocations = [subContainer, subSurface, subUnderside, subRear] |
| | 122 | |
| | 123 | /* |
| | 124 | * Show our status. We'll show the status for each of our |
| | 125 | * sub-objects, so that we list any contents of our sub-container or |
| | 126 | * sub-surface along with our description. |
| | 127 | */ |
| | 128 | examineStatus() |
| | 129 | { |
| | 130 | /* if we have a sub-container, show its status */ |
| | 131 | if (subContainer != nil) |
| | 132 | subContainer.examineStatus(); |
| | 133 | |
| | 134 | /* if we have a sub-surface, show its status */ |
| | 135 | if (subSurface != nil) |
| | 136 | subSurface.examineStatus(); |
| | 137 | |
| | 138 | /* if we have a sub-rear, show its status */ |
| | 139 | if (subRear != nil) |
| | 140 | subRear.examineStatus(); |
| | 141 | |
| | 142 | /* if we have a sub-underside, show its status */ |
| | 143 | if (subUnderside != nil) |
| | 144 | subUnderside.examineStatus(); |
| | 145 | } |
| | 146 | |
| | 147 | /* |
| | 148 | * In most cases, the open/closed and locked/unlocked status of a |
| | 149 | * complex container refer to the status of the sub-container. |
| | 150 | */ |
| | 151 | isOpen = (subContainer != nil ? subContainer.isOpen : inherited) |
| | 152 | isLocked = (subContainer != nil ? subContainer.isLocked : inherited) |
| | 153 | |
| | 154 | makeOpen(stat) |
| | 155 | { |
| | 156 | if (subContainer != nil) |
| | 157 | subContainer.makeOpen(stat); |
| | 158 | else |
| | 159 | inherited(stat); |
| | 160 | } |
| | 161 | |
| | 162 | makeLocked(stat) |
| | 163 | { |
| | 164 | if (subContainer != nil) |
| | 165 | subContainer.makeLocked(stat); |
| | 166 | else |
| | 167 | inherited(stat); |
| | 168 | } |
| | 169 | |
| | 170 | |
| | 171 | /* |
| | 172 | * route all commands that treat us as a container to our |
| | 173 | * sub-container object |
| | 174 | */ |
| | 175 | dobjFor(Open) maybeRemapTo(subContainer != nil, Open, subContainer) |
| | 176 | dobjFor(Close) maybeRemapTo(subContainer != nil, Close, subContainer) |
| | 177 | dobjFor(LookIn) maybeRemapTo(subContainer != nil, LookIn, subContainer) |
| | 178 | iobjFor(PutIn) maybeRemapTo(subContainer != nil, |
| | 179 | PutIn, DirectObject, subContainer) |
| | 180 | dobjFor(Lock) maybeRemapTo(subContainer != nil, Lock, subContainer) |
| | 181 | dobjFor(LockWith) maybeRemapTo(subContainer != nil, |
| | 182 | LockWith, subContainer, IndirectObject) |
| | 183 | dobjFor(Unlock) maybeRemapTo(subContainer != nil, Unlock, subContainer) |
| | 184 | dobjFor(UnlockWith) maybeRemapTo(subContainer != nil, |
| | 185 | UnlockWith, subContainer, IndirectObject) |
| | 186 | |
| | 187 | /* route commands that treat us as a surface to our sub-surface */ |
| | 188 | iobjFor(PutOn) maybeRemapTo(subSurface != nil, |
| | 189 | PutOn, DirectObject, subSurface) |
| | 190 | |
| | 191 | /* route commands that affect our underside to our sub-underside */ |
| | 192 | iobjFor(PutUnder) maybeRemapTo(subUnderside != nil, |
| | 193 | PutUnder, DirectObject, subUnderside) |
| | 194 | dobjFor(LookUnder) maybeRemapTo(subUnderside != nil, |
| | 195 | LookUnder, subUnderside) |
| | 196 | |
| | 197 | /* route commands that affect our rear to our sub-rear-side */ |
| | 198 | iobjFor(PutBehind) maybeRemapTo(subRear != nil, |
| | 199 | PutBehind, DirectObject, subRear) |
| | 200 | dobjFor(LookBehind) maybeRemapTo(subRear != nil, LookBehind, subRear) |
| | 201 | |
| | 202 | /* route commands relevant to nested rooms to our components */ |
| | 203 | dobjFor(StandOn) maybeRemapTo(getNestedRoomDest(StandOnAction) != nil, |
| | 204 | StandOn, getNestedRoomDest(StandOnAction)) |
| | 205 | dobjFor(SitOn) maybeRemapTo(getNestedRoomDest(SitOnAction) != nil, |
| | 206 | SitOn, getNestedRoomDest(SitOnAction)) |
| | 207 | dobjFor(LieOn) maybeRemapTo(getNestedRoomDest(LieOnAction) != nil, |
| | 208 | LieOn, getNestedRoomDest(LieOnAction)) |
| | 209 | dobjFor(Board) maybeRemapTo(getNestedRoomDest(BoardAction) != nil, |
| | 210 | Board, getNestedRoomDest(BoardAction)) |
| | 211 | dobjFor(Enter) maybeRemapTo(getNestedRoomDest(EnterAction) != nil, |
| | 212 | Enter, getNestedRoomDest(EnterAction)) |
| | 213 | |
| | 214 | /* map GET OUT/OFF to whichever complex component we're currently in */ |
| | 215 | dobjFor(GetOutOf) maybeRemapTo(getNestedRoomSource(gActor) != nil, |
| | 216 | GetOutOf, getNestedRoomSource(gActor)) |
| | 217 | dobjFor(GetOffOf) maybeRemapTo(getNestedRoomSource(gActor) != nil, |
| | 218 | GetOffOf, getNestedRoomSource(gActor)) |
| | 219 | |
| | 220 | /* |
| | 221 | * Get the destination for nested travel into this object. By |
| | 222 | * default, we'll look at the sub-container and sub-surface |
| | 223 | * components to see if either is a nested room, and if so, we'll |
| | 224 | * return that. The surface takes priority if both are nested rooms. |
| | 225 | * |
| | 226 | * You can override this to differentiate by verb, if desired; for |
| | 227 | * example, you could have SIT ON and LIE ON refer to the sub-surface |
| | 228 | * component, while ENTER and BOARD refer to the sub-container |
| | 229 | * component. |
| | 230 | * |
| | 231 | * Note that if you do need to override this method to distinguish |
| | 232 | * between a sub-container ("IN") and a sub-surface ("ON") for nested |
| | 233 | * room purposes, there's a subtlety to watch out for. The English |
| | 234 | * library maps "sit on" and "sit in" to the single Action SitOn; |
| | 235 | * likewise with "lie in/on" for LieOn and "stand in/on" for StandOn. |
| | 236 | * If you're distinguishing the sub-container from the sub-surface, |
| | 237 | * you'll probably want to distinguish SIT IN from SIT ON (and |
| | 238 | * likewise for LIE and STAND). Fortunately, even though the action |
| | 239 | * class is the same for both phrasings, you can still find out |
| | 240 | * exactly which preposition the player typed using |
| | 241 | * action.getEnteredVerbPhrase(). |
| | 242 | */ |
| | 243 | getNestedRoomDest(action) |
| | 244 | { |
| | 245 | /* |
| | 246 | * check the sub-surface first to see if it's a nested room; |
| | 247 | * failing that, check the sub-container; failing that, we don't |
| | 248 | * have a suitable component destination |
| | 249 | */ |
| | 250 | if (subSurface != nil && subSurface.ofKind(NestedRoom)) |
| | 251 | return subSurface; |
| | 252 | else if (subContainer != nil && subContainer.ofKind(NestedRoom)) |
| | 253 | return subContainer; |
| | 254 | else |
| | 255 | return nil; |
| | 256 | } |
| | 257 | |
| | 258 | /* |
| | 259 | * Get the source for nested travel out of this object. This is used |
| | 260 | * for GET OUT OF <self> - we figure out which nested room component |
| | 261 | * the actor is in, so that we can remap the command to GET OUT OF |
| | 262 | * <that component>. |
| | 263 | */ |
| | 264 | getNestedRoomSource(actor) |
| | 265 | { |
| | 266 | /* figure out which child the actor is in */ |
| | 267 | foreach (local chi in allSubLocations) |
| | 268 | { |
| | 269 | if (chi != nil && actor.isIn(chi)) |
| | 270 | return chi; |
| | 271 | } |
| | 272 | |
| | 273 | /* the actor doesn't appear to be in one of our component locations */ |
| | 274 | return nil; |
| | 275 | } |
| | 276 | |
| | 277 | /* |
| | 278 | * Get a list of objects suitable for matching ALL in TAKE ALL FROM |
| | 279 | * <self>. By default, if we have a sub-surface and/or |
| | 280 | * sub-container, we return everything in scope that's inside either |
| | 281 | * one of those. Otherwise, if we have a sub-rear-surface and/or an |
| | 282 | * underside, we'll return everything from those. |
| | 283 | */ |
| | 284 | getAllForTakeFrom(scopeList) |
| | 285 | { |
| | 286 | local containers; |
| | 287 | |
| | 288 | /* |
| | 289 | * Make a list of the containers in which we're going to look. |
| | 290 | * If we have a sub-container or sub-surface, look only in those. |
| | 291 | * Otherwise, if we have a rear surface or underside, look in |
| | 292 | * those. |
| | 293 | */ |
| | 294 | containers = []; |
| | 295 | if (subContainer != nil) |
| | 296 | containers += subContainer; |
| | 297 | if (subSurface != nil) |
| | 298 | containers += subSurface; |
| | 299 | if (containers == []) |
| | 300 | { |
| | 301 | if (subRear != nil) |
| | 302 | containers += subRear; |
| | 303 | if (subUnderside != nil) |
| | 304 | containers += subUnderside; |
| | 305 | } |
| | 306 | |
| | 307 | /* |
| | 308 | * return the list of everything in scope that's directly in one |
| | 309 | * of the selected containers, but isn't a component of its |
| | 310 | * direct container |
| | 311 | */ |
| | 312 | return scopeList.subset( |
| | 313 | {x: (x != self |
| | 314 | && containers.indexOf(x) == nil |
| | 315 | && containers.indexWhich( |
| | 316 | {c: x.isDirectlyIn(c) && !x.isComponentOf(c)}) != nil)}); |
| | 317 | } |
| | 318 | |
| | 319 | /* |
| | 320 | * Add an object to my contents. If the object has a subLocation |
| | 321 | * setting, take it as indicating which of my subcontainers is to |
| | 322 | * contain the object. |
| | 323 | */ |
| | 324 | addToContents(obj) |
| | 325 | { |
| | 326 | local sub; |
| | 327 | |
| | 328 | /* |
| | 329 | * if the object has a subLocation, add it to my appropriate |
| | 330 | * component object; if not, add to my own contents as usual |
| | 331 | */ |
| | 332 | if ((sub = obj.subLocation) != nil) |
| | 333 | { |
| | 334 | /* |
| | 335 | * It specifies a subLocation - add it to the corresponding |
| | 336 | * component's contents. Note that subLocation is a property |
| | 337 | * pointer - &subContainer for my container component, |
| | 338 | * &subSurface for my surface component, etc. |
| | 339 | */ |
| | 340 | self.(sub).addToContents(obj); |
| | 341 | |
| | 342 | /* |
| | 343 | * The object's present location is merely for set-up |
| | 344 | * purposes, so that the '+' object definition notation can |
| | 345 | * be used to give the object its initial location. The |
| | 346 | * object really wants to be in the sub-container, to whose |
| | 347 | * contents list we've just added it. Set its location to |
| | 348 | * the sub-container. |
| | 349 | */ |
| | 350 | obj.location = self.(sub); |
| | 351 | |
| | 352 | /* |
| | 353 | * Now that we've moved the object into its sub-location, |
| | 354 | * forget the subLocation setting, since this property is |
| | 355 | * only for initialization. |
| | 356 | */ |
| | 357 | obj.subLocation = nil; |
| | 358 | } |
| | 359 | else |
| | 360 | { |
| | 361 | /* there's no subLocation, so use the default handling */ |
| | 362 | inherited(obj); |
| | 363 | } |
| | 364 | } |
| | 365 | |
| | 366 | /* |
| | 367 | * If we have any SpaceOverlay children, abandon the contents of the |
| | 368 | * overlaid spaces as needed. |
| | 369 | */ |
| | 370 | mainMoveInto(newCont) |
| | 371 | { |
| | 372 | /* |
| | 373 | * If any of our components are SpaceOverlays, notify them. We |
| | 374 | * only worry about the rear and underside components, since it's |
| | 375 | * never appropriate for our container and surface components to |
| | 376 | * act as space overlays. |
| | 377 | */ |
| | 378 | notifyComponentOfMove(subRear); |
| | 379 | notifyComponentOfMove(subUnderside); |
| | 380 | |
| | 381 | /* do the normal work */ |
| | 382 | inherited(newCont); |
| | 383 | } |
| | 384 | |
| | 385 | /* |
| | 386 | * if we're being pushed into a new location (as a PushTraveler), |
| | 387 | * abandon the contents of any SpaceOverlay components |
| | 388 | */ |
| | 389 | beforeMovePushable(traveler, connector, dest) |
| | 390 | { |
| | 391 | /* |
| | 392 | * notify our SpaceOverlay components that we're being moved, if |
| | 393 | * we're going to end up in a new location |
| | 394 | */ |
| | 395 | if (dest != location) |
| | 396 | { |
| | 397 | /* notify our rear and underside components of the move */ |
| | 398 | notifyComponentOfMove(subRear); |
| | 399 | notifyComponentOfMove(subUnderside); |
| | 400 | } |
| | 401 | |
| | 402 | /* do the normal work */ |
| | 403 | inherited(traveler, connector, dest); |
| | 404 | } |
| | 405 | |
| | 406 | /* |
| | 407 | * if the given component is a SpaceOverlay, notify it that we're |
| | 408 | * moving, so that it can abandon its contents as needed |
| | 409 | */ |
| | 410 | notifyComponentOfMove(sub) |
| | 411 | { |
| | 412 | /* if it's a SpaceOverlay, abandon its contents if necessary */ |
| | 413 | if (sub != nil && sub.ofKind(SpaceOverlay)) |
| | 414 | sub.abandonContents(); |
| | 415 | } |
| | 416 | |
| | 417 | /* pass bag-of-holding operations to our sub-container */ |
| | 418 | tryPuttingObjInBag(target) |
| | 419 | { |
| | 420 | /* if we have a subcontainer, let it handle the operation */ |
| | 421 | return (subContainer != nil |
| | 422 | ? subContainer.tryPuttingObjInBag(target) |
| | 423 | : nil); |
| | 424 | } |
| | 425 | |
| | 426 | /* pass implicit PUT x IN self operations to our subcontainer */ |
| | 427 | tryMovingObjInto(obj) |
| | 428 | { |
| | 429 | /* if we have a subcontainer, let it handle the operation */ |
| | 430 | return (subContainer != nil |
| | 431 | ? subContainer.tryMovingObjInto(obj) |
| | 432 | : nil); |
| | 433 | } |
| | 434 | ; |
| | 435 | |
| | 436 | /* |
| | 437 | * we don't actually define any subLocation property values anywhere, so |
| | 438 | * declare it to make sure the compiler knows it's a property name |
| | 439 | */ |
| | 440 | property subLocation; |
| | 441 | |
| | 442 | /* |
| | 443 | * A component object of a complex container. This class can be used as |
| | 444 | * a mix-in for sub-objects of a complex container (the subContainer or |
| | 445 | * subSurface) defined as nested objects. |
| | 446 | * |
| | 447 | * This class is based on Component, which is suitable for complex |
| | 448 | * container sub-objects because it makes them inseparable from the |
| | 449 | * complex container. It's also based on NameAsParent, which makes the |
| | 450 | * object automatically use the same name (in messages) as the lexical |
| | 451 | * parent object. This is usually what one wants for a sub-object of a |
| | 452 | * complex container, because it makes the sub-object essentially |
| | 453 | * invisible to the user by referring to the sub-object in messages as |
| | 454 | * though it were the complex container itself: "The washing machine |
| | 455 | * contains...". |
| | 456 | * |
| | 457 | * This class also automatically initializes our location to our lexical |
| | 458 | * parent, during the pre-initialization process. Any of these that are |
| | 459 | * dynamically created at run-time (using 'new') must have their |
| | 460 | * locations set manually, because initializeLocation() won't be called |
| | 461 | * automatically in those cases. |
| | 462 | */ |
| | 463 | class ComplexComponent: Component, NameAsParent |
| | 464 | initializeLocation() |
| | 465 | { |
| | 466 | /* set our location to our lexical parent */ |
| | 467 | location = lexicalParent; |
| | 468 | |
| | 469 | /* inherit default so we initialize our container's 'contents' list */ |
| | 470 | inherited(); |
| | 471 | } |
| | 472 | |
| | 473 | /* |
| | 474 | * Get our "identity" object. We take our identity from our parent |
| | 475 | * object, if we have one. Note that our identity isn't simply our |
| | 476 | * parent, but rather is our parent's identity, recursively defined. |
| | 477 | */ |
| | 478 | getIdentityObject() |
| | 479 | { |
| | 480 | return (location != nil ? location.getIdentityObject() : self); |
| | 481 | } |
| | 482 | |
| | 483 | /* don't participate in 'all', since we're a secret internal object */ |
| | 484 | hideFromAll(action) { return true; } |
| | 485 | |
| | 486 | /* |
| | 487 | * In case this component is being used to implement a nested room of |
| | 488 | * some kind (a platform, booth, etc), use the complex container's |
| | 489 | * location as the staging location. Normally our staging location |
| | 490 | * would be our direct container, but as with other aspects of |
| | 491 | * complex containers, the container/component are meant to act as a |
| | 492 | * single combined object, so we'd want to bypass the complex |
| | 493 | * container and move directly between the enclosing location and |
| | 494 | * 'self'. |
| | 495 | */ |
| | 496 | stagingLocations = [lexicalParent.location] |
| | 497 | ; |
| | 498 | |
| | 499 | /* |
| | 500 | * A container door. This is useful for cases where you want to create |
| | 501 | * the door to a container as a separate object in its own right. |
| | 502 | */ |
| | 503 | class ContainerDoor: Component |
| | 504 | /* |
| | 505 | * In most cases, you should create a ContainerDoor as a component of |
| | 506 | * a ComplexContainer. It's usually necessary to use a |
| | 507 | * ComplexContainer in order to use a door, since the door has to go |
| | 508 | * somewhere, and it can't go inside the container it controls |
| | 509 | * (because if it were inside, it wouldn't be accessible when the |
| | 510 | * container is closed). |
| | 511 | * |
| | 512 | * By default, we assume that our immediate location is a complex |
| | 513 | * container, and its subContainer is the actual container for which |
| | 514 | * we're the door. You can override this property to create a |
| | 515 | * different relationship if necessary. |
| | 516 | */ |
| | 517 | subContainer = (location.subContainer) |
| | 518 | |
| | 519 | /* we're open if our associated sub-container is open */ |
| | 520 | isOpen = (subContainer.isOpen) |
| | 521 | |
| | 522 | /* our status description mentions our open status */ |
| | 523 | examineStatus() |
| | 524 | { |
| | 525 | /* add our open status */ |
| | 526 | say(isOpen |
| | 527 | ? gLibMessages.currentlyOpen : gLibMessages.currentlyClosed); |
| | 528 | |
| | 529 | /* add the base class behavior */ |
| | 530 | inherited(); |
| | 531 | } |
| | 532 | |
| | 533 | /* looking in or behind a door is like looking inside the container */ |
| | 534 | dobjFor(LookIn) remapTo(LookIn, subContainer) |
| | 535 | dobjFor(LookBehind) remapTo(LookIn, subContainer) |
| | 536 | |
| | 537 | /* door-like operations on the door map to the container */ |
| | 538 | dobjFor(Open) remapTo(Open, subContainer) |
| | 539 | dobjFor(Close) remapTo(Close, subContainer) |
| | 540 | dobjFor(Lock) remapTo(Lock, subContainer) |
| | 541 | dobjFor(LockWith) remapTo(LockWith, subContainer, IndirectObject) |
| | 542 | dobjFor(Unlock) remapTo(Unlock, subContainer) |
| | 543 | dobjFor(UnlockWith) remapTo(UnlockWith, subContainer, IndirectObject) |
| | 544 | ; |
| | 545 | |
| | 546 | |
| | 547 | /* ------------------------------------------------------------------------ */ |
| | 548 | /* |
| | 549 | * A "space overlay" is a special type of container whose contents are |
| | 550 | * supposed to be adjacent to the container object (i.e., self), but are |
| | 551 | * not truly contained in the usual sense. This is used to model spatial |
| | 552 | * relationships such as UNDER and BEHIND, which aren't directly |
| | 553 | * supported in the normal containment model. |
| | 554 | * |
| | 555 | * The special feature of a space overlay is that the contents aren't |
| | 556 | * truly attached to the container object, so they don't move with it the |
| | 557 | * way that the contents of an ordinary container do. For example, |
| | 558 | * suppose we have a space overlay representing a bookcase and the space |
| | 559 | * behind it, so that we can hide a painting behind the bookcase: in this |
| | 560 | * case, moving the bookcase should leave the painting where it was, |
| | 561 | * because it was just sitting there in that space. In the real world, |
| | 562 | * of course, the painting was sitting on the floor all along, so moving |
| | 563 | * the bookcase would have no effect on it; but our spatial relationship |
| | 564 | * model isn't quite as good as reality's, so we have to resort to an |
| | 565 | * extra fix-up step. Specifically, when we move a space overlay, we |
| | 566 | * always check to see if its contents need to be relocated to the place |
| | 567 | * where they were really supposed to be all along. |
| | 568 | */ |
| | 569 | class SpaceOverlay: BulkLimiter |
| | 570 | /* |
| | 571 | * If we move this object, the objects we contain might stay put |
| | 572 | * rather than moving along with the container. For example, if we |
| | 573 | * represent the space behind a bookcase, moving the bookcase would |
| | 574 | * leave objects that were formerly behind the bookcase just sitting |
| | 575 | * on the floor (or attached to the wall, or whatever). |
| | 576 | */ |
| | 577 | mainMoveInto(newContainer) |
| | 578 | { |
| | 579 | /* check to see if our objects need to be left behind */ |
| | 580 | abandonContents(); |
| | 581 | |
| | 582 | /* now do the normal work */ |
| | 583 | inherited(newContainer); |
| | 584 | } |
| | 585 | |
| | 586 | /* |
| | 587 | * when we're being pushed to a new location via push-travel, abandon |
| | 588 | * our contents before we're moved |
| | 589 | */ |
| | 590 | beforeMovePushable(traveler, connector, dest) |
| | 591 | { |
| | 592 | /* check to see if our objects need to be left behind */ |
| | 593 | if (dest != getIdentityObject().location) |
| | 594 | abandonContents(); |
| | 595 | |
| | 596 | /* do the normal work */ |
| | 597 | inherited(traveler, connector, dest); |
| | 598 | } |
| | 599 | |
| | 600 | /* |
| | 601 | * abandonLocation is where the things under me end up when I'm |
| | 602 | * moved. |
| | 603 | * |
| | 604 | * An Underside or RearContainer represents an object that has a |
| | 605 | * space underneath or behind it, respectively, but the space itself |
| | 606 | * isn't truly part of the container object (i.e., self). This |
| | 607 | * means that when the container moves, the objects under/behind it |
| | 608 | * shouldn't move. For example, if there's a box under a bed, |
| | 609 | * moving the bed out of the room should leave the box sitting on |
| | 610 | * the floor where the bed used to be. |
| | 611 | * |
| | 612 | * By default, our abandonLocation is simply the location of our |
| | 613 | * "identity object" - that is, the location of our nearest |
| | 614 | * enclosing object that isn't a component. |
| | 615 | * |
| | 616 | * This can be overridden if the actual abandonment location should |
| | 617 | * be somewhere other than our assembly location. In addition, you |
| | 618 | * can set this to nil to indicate that objects under/behind me will |
| | 619 | * NOT be abandoned when I move; instead, they'll simply stay with |
| | 620 | * me, as though they're attached to my underside/back surface. |
| | 621 | */ |
| | 622 | abandonLocation = (getIdentityObject().location) |
| | 623 | |
| | 624 | /* |
| | 625 | * By default we list our direct contents the first time we're |
| | 626 | * moved, and ONLY the first time. If alwaysListOnMove is |
| | 627 | * overridden to true, then we'll list our contents EVERY time we're |
| | 628 | * moved. If neverListOnMove is set to true, then we'll NEVER list |
| | 629 | * our contents automatically when moved; this can be used in cases |
| | 630 | * where the game wants to produce its own listing explicitly, |
| | 631 | * rather than using the default listing we generate. (Obviously, |
| | 632 | * setting both 'always' and 'never' is meaningless, but in case |
| | 633 | * you're wondering, 'never' overrides 'always' in this case.) |
| | 634 | * |
| | 635 | * Setting abandonLocation to nil overrules alwaysListOnMove: if |
| | 636 | * there's no abandonment, then we consider nothing to be revealed |
| | 637 | * when we're moved, since my contents move along with me. |
| | 638 | */ |
| | 639 | alwaysListOnMove = nil |
| | 640 | neverListOnMove = nil |
| | 641 | |
| | 642 | /* |
| | 643 | * The lister we use to describe the objects being revealed when we |
| | 644 | * move the SpaceOverlay object and abandon the contents. Each |
| | 645 | * concrete kind of SpaceOverlay must provide a lister that uses |
| | 646 | * appropriate language; the list should be roughly of the form |
| | 647 | * "Moving the armoire reveals a rusty can underneath." Individual |
| | 648 | * objects can override this to customize the message further. |
| | 649 | */ |
| | 650 | abandonContentsLister = nil |
| | 651 | |
| | 652 | /* |
| | 653 | * Abandon my contents when I'm moved. This is called whenever we're |
| | 654 | * moved to a new location, to take care of leaving behind the |
| | 655 | * objects that were formerly under me. |
| | 656 | * |
| | 657 | * We'll move my direct contents into abandonLocation, unless that's |
| | 658 | * set to nil. We don't move any Component objects within me, since |
| | 659 | * we assume those to be attached. |
| | 660 | */ |
| | 661 | abandonContents() |
| | 662 | { |
| | 663 | local dest; |
| | 664 | |
| | 665 | /* |
| | 666 | * if there's no abandonment location, our contents move with us, |
| | 667 | * so there's nothing to do |
| | 668 | */ |
| | 669 | if ((dest = abandonLocation) == nil) |
| | 670 | return; |
| | 671 | |
| | 672 | /* |
| | 673 | * If we've never been moved before, or we always reveal my |
| | 674 | * contents when moved, list our contents now. In any case, if |
| | 675 | * we *never* list on move, don't generate the listing. |
| | 676 | */ |
| | 677 | if ((alwaysListOnMove || !getIdentityObject().moved) |
| | 678 | && !neverListOnMove) |
| | 679 | { |
| | 680 | local marker1, marker2; |
| | 681 | |
| | 682 | /* |
| | 683 | * We want to generate a listing of what is revealed by |
| | 684 | * moving the object, which we can do by generating a |
| | 685 | * listing of what we would normally see by looking in the |
| | 686 | * overlay interior. We want the listing as it stands now, |
| | 687 | * but in most cases, we don't actually want to generate the |
| | 688 | * list quite yet, because we want the action that's moving |
| | 689 | * the object to complete and show all of its messages first. |
| | 690 | * |
| | 691 | * However, if the action leaves the actor in a new |
| | 692 | * location, do generate the listing before the rest of the |
| | 693 | * action output, since the listing won't make any sense |
| | 694 | * after we've moved to (and displayed the description of) |
| | 695 | * the new location. |
| | 696 | * |
| | 697 | * To accomplish all of this, generate the listing now, |
| | 698 | * before the rest of the action output, but insert special |
| | 699 | * report markers into the transcript before and after the |
| | 700 | * listing. Then, register to receive after-action |
| | 701 | * notification; at the end of the action, we'll go back and |
| | 702 | * move the range of transcript output between the markers |
| | 703 | * to the end of the command's transcript entries, if we |
| | 704 | * haven't moved to a new room. |
| | 705 | * |
| | 706 | * One final complication: we don't want our listing here to |
| | 707 | * hide any default report from the main command, so run it |
| | 708 | * as a sub-action. A sub-action doesn't override the |
| | 709 | * visibility of its parent's default report. |
| | 710 | */ |
| | 711 | |
| | 712 | /* first, add a marker to the transcript before the listing */ |
| | 713 | marker1 = gTranscript.addMarker(); |
| | 714 | |
| | 715 | /* generate the listing, using a generic sub-action context */ |
| | 716 | withActionEnv(Action, gActor, {: listContentsForMove() }); |
| | 717 | |
| | 718 | /* add another transcript marker after the listing */ |
| | 719 | marker2 = gTranscript.addMarker(); |
| | 720 | |
| | 721 | /* |
| | 722 | * create our special handler object to receive notification |
| | 723 | * at the end of the command - it'll move the reports to the |
| | 724 | * end of the command output if need be |
| | 725 | */ |
| | 726 | new SpaceOverlayAbandonFinisher(marker1, marker2); |
| | 727 | } |
| | 728 | |
| | 729 | /* now move my non-Component contents to the abandonment location */ |
| | 730 | foreach(obj in contents) |
| | 731 | { |
| | 732 | /* if it's not a component, move it */ |
| | 733 | if(!obj.ofKind(Component)) |
| | 734 | obj.moveInto(dest); |
| | 735 | } |
| | 736 | } |
| | 737 | |
| | 738 | /* |
| | 739 | * My weight does NOT include my "contents" if we abandon our |
| | 740 | * contents on being moved. Our contents are not attached to us as |
| | 741 | * they are in a normal sort of container; instead, they're merely |
| | 742 | * colocated, so when we're moved, that colocation relationship ends. |
| | 743 | */ |
| | 744 | getWeight() |
| | 745 | { |
| | 746 | /* |
| | 747 | * if we abandon our contents on being moved, our weight doesn't |
| | 748 | * include them, because they're not attached; otherwise, they do |
| | 749 | * act like they're attached, and hence must be included in our |
| | 750 | * weight as for any ordinary container |
| | 751 | */ |
| | 752 | if (abandonLocation != nil) |
| | 753 | { |
| | 754 | /* |
| | 755 | * our contents are not included in our weight, so our total |
| | 756 | * weight is simply our own intrinsic weight |
| | 757 | */ |
| | 758 | return weight; |
| | 759 | } |
| | 760 | else |
| | 761 | { |
| | 762 | /* our contents are attached, so include their weight as normal */ |
| | 763 | return inherited(); |
| | 764 | } |
| | 765 | } |
| | 766 | |
| | 767 | /* |
| | 768 | * List our contents for moving the object. By default, we examine |
| | 769 | * our interior using our abandonContentsLister. |
| | 770 | */ |
| | 771 | listContentsForMove() |
| | 772 | { |
| | 773 | /* examine our contents with the abandonContentsLister */ |
| | 774 | examineInteriorWithLister(abandonContentsLister); |
| | 775 | } |
| | 776 | ; |
| | 777 | |
| | 778 | /* |
| | 779 | * Space Overlay Abandon Finisher - this is an internal object that we |
| | 780 | * create in SpaceOverlay.abandonContents(). Its purpose is to receive |
| | 781 | * an afterAction notification and clean up the report order if |
| | 782 | * necessary. |
| | 783 | */ |
| | 784 | class SpaceOverlayAbandonFinisher: object |
| | 785 | construct(m1, m2) |
| | 786 | { |
| | 787 | /* remember the markers */ |
| | 788 | marker1 = m1; |
| | 789 | marker2 = m2; |
| | 790 | |
| | 791 | /* remember the actor's starting location */ |
| | 792 | origLocation = gActor.location; |
| | 793 | |
| | 794 | /* register for afterAction notification */ |
| | 795 | gAction.addBeforeAfterObj(self); |
| | 796 | } |
| | 797 | |
| | 798 | /* the transcript markers identifying the listing reports */ |
| | 799 | marker1 = nil |
| | 800 | marker2 = nil |
| | 801 | |
| | 802 | /* the actor's location at the time we generated the listing */ |
| | 803 | origLocation = nil |
| | 804 | |
| | 805 | /* receive our after-action notification */ |
| | 806 | afterAction() |
| | 807 | { |
| | 808 | /* |
| | 809 | * If the actor hasn't changed locations, move the reports we |
| | 810 | * generated for the listing to the end of the transcript. |
| | 811 | */ |
| | 812 | if (gActor.location == origLocation) |
| | 813 | gTranscript.moveRangeAppend(marker1, marker2); |
| | 814 | } |
| | 815 | ; |
| | 816 | |
| | 817 | /* |
| | 818 | * An "underside" is a special type of container that describes its |
| | 819 | * contents as being under the object. This is appropriate for objects |
| | 820 | * that have a space underneath, such as a bed or a table. |
| | 821 | */ |
| | 822 | class Underside: SpaceOverlay |
| | 823 | /* |
| | 824 | * Can actors put new objects under self, using the PUT UNDER |
| | 825 | * command? By default, we allow it. Override this property to nil |
| | 826 | * if new objects cannot be added by player commands. |
| | 827 | */ |
| | 828 | allowPutUnder = true |
| | 829 | |
| | 830 | /* we need to LOOK UNDER this object to see its contents */ |
| | 831 | nestedLookIn() { nestedAction(LookUnder, self); } |
| | 832 | |
| | 833 | /* use custom contents listers, for our special "under" wording */ |
| | 834 | contentsLister = undersideContentsLister |
| | 835 | descContentsLister = undersideDescContentsLister |
| | 836 | lookInLister = undersideLookUnderLister |
| | 837 | inlineContentsLister = undersideInlineContentsLister |
| | 838 | abandonContentsLister = undersideAbandonContentsLister |
| | 839 | |
| | 840 | /* customize the message for taking something from that's not under me */ |
| | 841 | takeFromNotInMessage = &takeFromNotUnderMsg |
| | 842 | |
| | 843 | /* customize the message indicating another object is already in me */ |
| | 844 | circularlyInMessage = &circularlyUnderMsg |
| | 845 | |
| | 846 | /* message phrase for objects put under me */ |
| | 847 | putDestMessage = &putDestUnder |
| | 848 | |
| | 849 | /* message when we don't have room to put another object under me */ |
| | 850 | tooFullMsg = &undersideTooFull |
| | 851 | |
| | 852 | /* message when an object is too large (all by itself) to fit under me */ |
| | 853 | tooLargeForContainerMsg = &tooLargeForUndersideMsg |
| | 854 | |
| | 855 | /* can't put self under self */ |
| | 856 | cannotPutInSelfMsg = &cannotPutUnderSelfMsg |
| | 857 | |
| | 858 | /* can't put something under me when it's already under me */ |
| | 859 | alreadyPutInMsg = &alreadyPutUnderMsg |
| | 860 | |
| | 861 | /* our implied containment verb is PUT UNDER */ |
| | 862 | tryMovingObjInto(obj) { return tryImplicitAction(PutUnder, obj, self); } |
| | 863 | |
| | 864 | /* -------------------------------------------------------------------- */ |
| | 865 | /* |
| | 866 | * Handle putting things under me |
| | 867 | */ |
| | 868 | iobjFor(PutUnder) |
| | 869 | { |
| | 870 | verify() |
| | 871 | { |
| | 872 | /* use the standard put-in-interior verification */ |
| | 873 | verifyPutInInterior(); |
| | 874 | } |
| | 875 | check() |
| | 876 | { |
| | 877 | /* only allow it if PUT UNDER commands are allowed */ |
| | 878 | if (!allowPutUnder) |
| | 879 | { |
| | 880 | reportFailure(&cannotPutUnderMsg); |
| | 881 | exit; |
| | 882 | } |
| | 883 | } |
| | 884 | action() |
| | 885 | { |
| | 886 | /* move the direct object onto me */ |
| | 887 | gDobj.moveInto(self); |
| | 888 | |
| | 889 | /* issue our default acknowledgment */ |
| | 890 | defaultReport(&okayPutUnderMsg); |
| | 891 | } |
| | 892 | } |
| | 893 | |
| | 894 | /* |
| | 895 | * Looking "under" a surface simply shows the surface's contents. |
| | 896 | */ |
| | 897 | dobjFor(LookUnder) |
| | 898 | { |
| | 899 | verify() { } |
| | 900 | action() { examineInterior(); } |
| | 901 | } |
| | 902 | ; |
| | 903 | |
| | 904 | /* |
| | 905 | * A special kind of Underside that only accepts specific contents. |
| | 906 | */ |
| | 907 | class RestrictedUnderside: RestrictedHolder, Underside |
| | 908 | /* |
| | 909 | * A message that explains why the direct object can't be put under |
| | 910 | * this object. In most cases, the rather generic default message |
| | 911 | * should be overridden to provide a specific reason that the dobj |
| | 912 | * can't be put under me. The rejected object is provided as a |
| | 913 | * parameter in case the message needs to vary by object, but we |
| | 914 | * ignore this and just use a single blanket failure message by |
| | 915 | * default. |
| | 916 | */ |
| | 917 | cannotPutUnderMsg(obj) { return &cannotPutUnderRestrictedMsg; } |
| | 918 | |
| | 919 | /* override PutUnder to enforce our contents restriction */ |
| | 920 | iobjFor(PutUnder) { check() { checkPutDobj(&cannotPutUnderMsg); } } |
| | 921 | ; |
| | 922 | |
| | 923 | /* |
| | 924 | * A "rear container" is similar to an underside: it models the space |
| | 925 | * behind an object. |
| | 926 | */ |
| | 927 | class RearContainer: SpaceOverlay |
| | 928 | /* |
| | 929 | * Can actors put new objects behind self, using the PUT BEHIND |
| | 930 | * command? By default, we allow it. Override this property to nil |
| | 931 | * if new objects cannot be added by player commands. |
| | 932 | */ |
| | 933 | allowPutBehind = true |
| | 934 | |
| | 935 | /* we need to LOOK BEHIND this object to see its contents */ |
| | 936 | nestedLookIn() { nestedAction(LookBehind, self); } |
| | 937 | |
| | 938 | /* use custom contents listers */ |
| | 939 | contentsLister = rearContentsLister |
| | 940 | descContentsLister = rearDescContentsLister |
| | 941 | lookInLister = rearLookBehindLister |
| | 942 | inlineContentsLister = rearInlineContentsLister |
| | 943 | abandonContentsLister = rearAbandonContentsLister |
| | 944 | |
| | 945 | /* the message for taking things from me that aren't behind me */ |
| | 946 | takeFromNotInMessage = &takeFromNotBehindMsg |
| | 947 | |
| | 948 | /* |
| | 949 | * my message indicating that another object x cannot be put into me |
| | 950 | * because I'm already in x |
| | 951 | */ |
| | 952 | circularlyInMessage = &circularlyBehindMsg |
| | 953 | |
| | 954 | /* message phrase for objects put under me */ |
| | 955 | putDestMessage = &putDestBehind |
| | 956 | |
| | 957 | /* message when we're too full for another object */ |
| | 958 | tooFullMsg = &rearTooFullMsg |
| | 959 | |
| | 960 | /* message when object is too large to fit behind me */ |
| | 961 | tooLargeForContainerMsg = &tooLargeForRearMsg |
| | 962 | |
| | 963 | /* customize the verification messages */ |
| | 964 | cannotPutInSelfMsg = &cannotPutBehindSelfMsg |
| | 965 | alreadyPutInMsg = &alreadyPutBehindMsg |
| | 966 | |
| | 967 | /* our implied containment verb is PUT BEHIND */ |
| | 968 | tryMovingObjInto(obj) { return tryImplicitAction(PutBehind, obj, self); } |
| | 969 | |
| | 970 | /* -------------------------------------------------------------------- */ |
| | 971 | /* |
| | 972 | * Handle the PUT UNDER command |
| | 973 | */ |
| | 974 | iobjFor(PutBehind) |
| | 975 | { |
| | 976 | verify() { verifyPutInInterior(); } |
| | 977 | check() |
| | 978 | { |
| | 979 | /* only allow it if PUT BEHIND commands are allowed */ |
| | 980 | if (!allowPutBehind) |
| | 981 | { |
| | 982 | reportFailure(&cannotPutBehindMsg); |
| | 983 | exit; |
| | 984 | } |
| | 985 | } |
| | 986 | action() |
| | 987 | { |
| | 988 | /* move the direct object behind me */ |
| | 989 | gDobj.moveInto(self); |
| | 990 | |
| | 991 | /* issue our default acknowledgment */ |
| | 992 | defaultReport(&okayPutBehindMsg); |
| | 993 | } |
| | 994 | } |
| | 995 | |
| | 996 | /* |
| | 997 | * Looking "behind" a surface simply shows the surface's contents. |
| | 998 | */ |
| | 999 | dobjFor(LookBehind) |
| | 1000 | { |
| | 1001 | verify() { } |
| | 1002 | action() { examineInterior(); } |
| | 1003 | } |
| | 1004 | ; |
| | 1005 | |
| | 1006 | /* |
| | 1007 | * A special kind of RearContainer that only accepts specific contents. |
| | 1008 | */ |
| | 1009 | class RestrictedRearContainer: RestrictedHolder, RearContainer |
| | 1010 | /* |
| | 1011 | * A message that explains why the direct object can't be put behind |
| | 1012 | * this object. In most cases, the rather generic default message |
| | 1013 | * should be overridden to provide a specific reason that the dobj |
| | 1014 | * can't be put behind me. The rejected object is provided as a |
| | 1015 | * parameter in case the message needs to vary by object, but we |
| | 1016 | * ignore this and just use a single blanket failure message by |
| | 1017 | * default. |
| | 1018 | */ |
| | 1019 | cannotPutBehindMsg(obj) { return &cannotPutBehindRestrictedMsg; } |
| | 1020 | |
| | 1021 | /* override PutBehind to enforce our contents restriction */ |
| | 1022 | iobjFor(PutBehind) { check() { checkPutDobj(&cannotPutBehindMsg); } } |
| | 1023 | ; |
| | 1024 | |
| | 1025 | /* |
| | 1026 | * A "rear surface" is essentially the same as a "rear container," but |
| | 1027 | * models the contents as being attached to the back of the object rather |
| | 1028 | * than merely sitting behind it. |
| | 1029 | * |
| | 1030 | * The only practical difference between the "container" and the |
| | 1031 | * "surface" is that moving a surface moves its contents along with it, |
| | 1032 | * whereas moving a container abandons the contents, leaving them behind |
| | 1033 | * where the container used to be. |
| | 1034 | */ |
| | 1035 | class RearSurface: RearContainer |
| | 1036 | /* |
| | 1037 | * We're a surface, not a space, so our contents stay attached when |
| | 1038 | * we move. |
| | 1039 | */ |
| | 1040 | abandonLocation = nil |
| | 1041 | ; |
| | 1042 | |
| | 1043 | /* |
| | 1044 | * A restricted-contents RearSurface |
| | 1045 | */ |
| | 1046 | class RestrictedRearSurface: RestrictedHolder, RearSurface |
| | 1047 | /* explain the problem */ |
| | 1048 | cannotPutBehindMsg(obj) { return &cannotPutBehindRestrictedMsg; } |
| | 1049 | |
| | 1050 | /* override PutBehind to enforce our contents restriction */ |
| | 1051 | iobjFor(PutBehind) { check() { checkPutDobj(&cannotPutBehindMsg); } } |
| | 1052 | ; |
| | 1053 | |
| | 1054 | /* ------------------------------------------------------------------------ */ |
| | 1055 | /* |
| | 1056 | * A "stretchy container." This is a simple container subclass whose |
| | 1057 | * external bulk changes according to the bulks of the contents. |
| | 1058 | */ |
| | 1059 | class StretchyContainer: Container |
| | 1060 | /* |
| | 1061 | * Our minimum bulk. This is the minimum bulk we'll report, even |
| | 1062 | * when the aggregate bulks of our contents are below this limit. |
| | 1063 | */ |
| | 1064 | minBulk = 0 |
| | 1065 | |
| | 1066 | /* get my total external bulk */ |
| | 1067 | getBulk() |
| | 1068 | { |
| | 1069 | local tot; |
| | 1070 | |
| | 1071 | /* start with my own intrinsic bulk */ |
| | 1072 | tot = bulk; |
| | 1073 | |
| | 1074 | /* add the bulk contribution from my contents */ |
| | 1075 | tot += getBulkForContents(); |
| | 1076 | |
| | 1077 | /* return the total, but never less than the minimum */ |
| | 1078 | return tot >= minBulk ? tot : minBulk; |
| | 1079 | } |
| | 1080 | |
| | 1081 | /* |
| | 1082 | * Calculate the contribution to my external bulk of my contents. |
| | 1083 | * The default for a stretchy container is to conform exactly to the |
| | 1084 | * contents, as though the container weren't present at all, hence |
| | 1085 | * we simply sum the bulks of our contents. Subclasses can override |
| | 1086 | * this to define other aggregate bulk effects as needed. |
| | 1087 | */ |
| | 1088 | getBulkForContents() |
| | 1089 | { |
| | 1090 | local tot; |
| | 1091 | |
| | 1092 | /* sum the bulks of the items in our contents */ |
| | 1093 | tot = 0; |
| | 1094 | foreach (local cur in contents) |
| | 1095 | tot += cur.getBulk(); |
| | 1096 | |
| | 1097 | /* return the total */ |
| | 1098 | return tot; |
| | 1099 | } |
| | 1100 | |
| | 1101 | /* |
| | 1102 | * Check what happens when a new object is inserted into my |
| | 1103 | * contents. This is called with the new object already tentatively |
| | 1104 | * added to my contents, so we can examine our current status to see |
| | 1105 | * if everything works. |
| | 1106 | * |
| | 1107 | * Since we can change our own size when a new item is added to our |
| | 1108 | * contents, we'll trigger a full bulk change check. |
| | 1109 | */ |
| | 1110 | checkBulkInserted(insertedObj) |
| | 1111 | { |
| | 1112 | /* |
| | 1113 | * inherit the normal handling to ensure that the new object |
| | 1114 | * fits within this container |
| | 1115 | */ |
| | 1116 | inherited(insertedObj); |
| | 1117 | |
| | 1118 | /* |
| | 1119 | * since we can change our own shape when items are added to our |
| | 1120 | * contents, trigger a full bulk check on myself |
| | 1121 | */ |
| | 1122 | checkBulkChange(); |
| | 1123 | } |
| | 1124 | |
| | 1125 | /* |
| | 1126 | * Check a bulk change of one of my direct contents. Since my own |
| | 1127 | * bulk changes whenever the bulk of one of my contents changes, we |
| | 1128 | * must propagate the bulk change of our contents as a change in our |
| | 1129 | * own bulk. |
| | 1130 | */ |
| | 1131 | checkBulkChangeWithin(changingObj) |
| | 1132 | { |
| | 1133 | /* |
| | 1134 | * This might cause a change in my own bulk, since my bulk |
| | 1135 | * depends on the bulks of my contents. When this is called, |
| | 1136 | * obj is already set to indicate its new bulk; since we |
| | 1137 | * calculate our own bulk by looking at our contents' bulks, |
| | 1138 | * this means that our own getBulk will now report the latest |
| | 1139 | * value including obj's new bulk. |
| | 1140 | */ |
| | 1141 | checkBulkChange(); |
| | 1142 | } |
| | 1143 | ; |
| | 1144 | |
| | 1145 | |
| | 1146 | /* ------------------------------------------------------------------------ */ |
| | 1147 | /* |
| | 1148 | * "Bag of Holding." This is a mix-in that actively moves items from the |
| | 1149 | * holding actor's direct inventory into itself when the actor's hands |
| | 1150 | * are too full. |
| | 1151 | * |
| | 1152 | * The bag of holding offers a solution to the conflict between "realism" |
| | 1153 | * and playability. On the one hand, in real life, you can only hold so |
| | 1154 | * many items at once, so at first glance it seems a simulation ought to |
| | 1155 | * have such a limit in order to be more realistic. On the other hand, |
| | 1156 | * most players justifiably hate having to deal with a carrying limit, |
| | 1157 | * because it forces the player to spend a lot of time doing tedious |
| | 1158 | * inventory management. |
| | 1159 | * |
| | 1160 | * The Bag of Holding is a compromise solution. The concept is borrowed |
| | 1161 | * from live role-playing games, where it's usually a magical item that |
| | 1162 | * can hold objects of unlimited size and weight, thereby allowing |
| | 1163 | * characters to transport impossibly large objects. In text IF, a bag |
| | 1164 | * of holding isn't usually magical - it's usually just something like a |
| | 1165 | * large backpack, or a trenchcoat with lots of pockets. And it usually |
| | 1166 | * isn't meant as a solution to an obvious puzzle; rather, it's meant to |
| | 1167 | * invisibly prevent inventory management from becoming a puzzle in the |
| | 1168 | * first place, by shuffling objects out of the PC's hands automatically |
| | 1169 | * to free up space as needed. |
| | 1170 | * |
| | 1171 | * This Bag of Holding implementation works by automatically moving |
| | 1172 | * objects from an actor's hands into the bag object, whenever the actor |
| | 1173 | * needs space to pick up a new item. Whenever an action has a |
| | 1174 | * "roomToHoldObj" precondition, the precondition will automatically look |
| | 1175 | * for a BagOfHolding object within the actor's inventory, and then move |
| | 1176 | * as many items as necessary from the actor's hands to the bag. |
| | 1177 | */ |
| | 1178 | class BagOfHolding: object |
| | 1179 | /* |
| | 1180 | * Get my bags of holding. Since we are a bag of holding, we'll add |
| | 1181 | * ourselves to the vector, then we'll inherit the normal handling |
| | 1182 | * to pick up our contents. |
| | 1183 | */ |
| | 1184 | getBagsOfHolding(vec) |
| | 1185 | { |
| | 1186 | /* we're a bag of holding */ |
| | 1187 | vec.append(self); |
| | 1188 | |
| | 1189 | /* inherit the normal handling */ |
| | 1190 | inherited(vec); |
| | 1191 | } |
| | 1192 | |
| | 1193 | /* |
| | 1194 | * Get my "affinity" for the given object. This is an indication of |
| | 1195 | * how strongly this bag wants to contain the object. The affinity |
| | 1196 | * is a number in arbitrary units; higher numbers indicate stronger |
| | 1197 | * affinities. An affinity of zero means that the bag does not want |
| | 1198 | * to contain the object at all. |
| | 1199 | * |
| | 1200 | * The purpose of the affinity is to support specialized holders |
| | 1201 | * that are designed to hold only specific types of objects, and |
| | 1202 | * allow these specialized holders to implicitly gather their |
| | 1203 | * specific objects. For example, a key ring might only hold keys, |
| | 1204 | * so it would have a high affinity for keys and a zero affinity for |
| | 1205 | * everything else. A lunchbox might have a higher affinity for |
| | 1206 | * things like sandwiches than for anything else, but might be |
| | 1207 | * willing to serve as a general container for other small items as |
| | 1208 | * well. |
| | 1209 | * |
| | 1210 | * The units of affinity are arbitrary, but the library uses the |
| | 1211 | * following values for its own classes: |
| | 1212 | * |
| | 1213 | * 0 - no affinity at all; the bag cannot hold the object |
| | 1214 | * |
| | 1215 | * 50 - willing to hold the object, but not of the preferred type |
| | 1216 | * |
| | 1217 | * 100 - default affinity; willing and able to hold the object, but |
| | 1218 | * just as willing to hold most other things |
| | 1219 | * |
| | 1220 | * 200 - special affinity; this object is of a type that we |
| | 1221 | * especially want to hold |
| | 1222 | * |
| | 1223 | * We intentionally space these loosely so that games can use |
| | 1224 | * intermediate levels if desired. |
| | 1225 | * |
| | 1226 | * When we are looking for bags of holding to consolidate an actor's |
| | 1227 | * directly-held inventory, note that we always move the object with |
| | 1228 | * the highest bag-to-object affinity out of all of the objects |
| | 1229 | * under consideration. So, if you want to give a particular kind |
| | 1230 | * of bag priority so that the library uses that bag before any |
| | 1231 | * other bag, make this routine return a higher affinity for the |
| | 1232 | * bag's objects than any other bags do. |
| | 1233 | * |
| | 1234 | * By default, we'll return the default affinity of 100. |
| | 1235 | * Specialized bags that don't hold all types of objects must |
| | 1236 | * override this to return zero for objects they can't hold. |
| | 1237 | */ |
| | 1238 | affinityFor(obj) |
| | 1239 | { |
| | 1240 | /* |
| | 1241 | * my affinity for myself is zero, for obvious reasons; for |
| | 1242 | * everything else, use the default affinity |
| | 1243 | */ |
| | 1244 | return (obj == self ? 0 : 100); |
| | 1245 | } |
| | 1246 | ; |
| | 1247 | |
| | 1248 | |
| | 1249 | /* ------------------------------------------------------------------------ */ |
| | 1250 | /* |
| | 1251 | * Keyring - a place to stash keys |
| | 1252 | * |
| | 1253 | * Keyrings have some special properties: |
| | 1254 | * |
| | 1255 | * - A keyring is a bag of holding with special affinity for keys. |
| | 1256 | * |
| | 1257 | * - A keyring can only contain keys. |
| | 1258 | * |
| | 1259 | * - Keys are considered to be on the outside of the ring, so a key can |
| | 1260 | * be used even if attached to the keyring (in other words, if the ring |
| | 1261 | * itself is held, a key attached to the ring is also considered held). |
| | 1262 | * |
| | 1263 | * - If an actor in possession of a keyring executes an "unlock" command |
| | 1264 | * without specifying what key to use, we will automatically test each |
| | 1265 | * key on the ring to find the one that works. |
| | 1266 | * |
| | 1267 | * - When an actor takes one of our keys, and the actor is in possession |
| | 1268 | * of this keyring, we'll automatically attach the key to the keyring |
| | 1269 | * immediately. |
| | 1270 | */ |
| | 1271 | class Keyring: BagOfHolding, Thing |
| | 1272 | /* lister for showing our contents in-line as part of a list entry */ |
| | 1273 | inlineContentsLister = keyringInlineContentsLister |
| | 1274 | |
| | 1275 | /* lister for showing our contents as part of "examine" */ |
| | 1276 | descContentsLister = keyringExamineContentsLister |
| | 1277 | |
| | 1278 | /* |
| | 1279 | * Determine if a key fits our keyring. By default, we will accept |
| | 1280 | * any object of class Key. However, subclasses might want to |
| | 1281 | * override this to associate particular keys with particular |
| | 1282 | * keyrings rather than having a single generic keyring. To allow |
| | 1283 | * only particular keys onto this keyring, override this routine to |
| | 1284 | * return true only for the desired keys. |
| | 1285 | */ |
| | 1286 | isMyKey(key) |
| | 1287 | { |
| | 1288 | /* accept any object of class Key */ |
| | 1289 | return key.ofKind(Key); |
| | 1290 | } |
| | 1291 | |
| | 1292 | /* we have high affinity for our keys */ |
| | 1293 | affinityFor(obj) |
| | 1294 | { |
| | 1295 | /* |
| | 1296 | * if the object is one of my keys, we have high affinity; |
| | 1297 | * otherwise we don't accept it at all |
| | 1298 | */ |
| | 1299 | if (isMyKey(obj)) |
| | 1300 | return 200; |
| | 1301 | else |
| | 1302 | return 0; |
| | 1303 | } |
| | 1304 | |
| | 1305 | /* implicitly put a key on the keyring */ |
| | 1306 | tryPuttingObjInBag(target) |
| | 1307 | { |
| | 1308 | /* we're a container, so use "put in" to get the object */ |
| | 1309 | return tryImplicitActionMsg(&announceMoveToBag, PutOn, target, self); |
| | 1310 | } |
| | 1311 | |
| | 1312 | /* on taking the keyring, attach any loose keys */ |
| | 1313 | dobjFor(Take) |
| | 1314 | { |
| | 1315 | action() |
| | 1316 | { |
| | 1317 | /* do the normal work */ |
| | 1318 | inherited(); |
| | 1319 | |
| | 1320 | /* get the list of loose keys */ |
| | 1321 | local lst = getLooseKeys(gActor); |
| | 1322 | |
| | 1323 | /* consider only the subset that are my valid keys */ |
| | 1324 | lst = lst.subset({x: isMyKey(x)}); |
| | 1325 | |
| | 1326 | /* if there are any, move them onto the keyring */ |
| | 1327 | if (lst.length() != 0) |
| | 1328 | { |
| | 1329 | /* put each loose key on the keyring */ |
| | 1330 | foreach (local cur in lst) |
| | 1331 | cur.moveInto(self); |
| | 1332 | |
| | 1333 | /* announce what happened */ |
| | 1334 | extraReport(&movedKeysToKeyringMsg, self, lst); |
| | 1335 | } |
| | 1336 | } |
| | 1337 | } |
| | 1338 | |
| | 1339 | /* |
| | 1340 | * Get the loose keys in the given actor's possession. On taking the |
| | 1341 | * keyring, we'll attach these loose keys to the keyring |
| | 1342 | * automatically. By default, we return any keys the actor is |
| | 1343 | * directly holding. |
| | 1344 | */ |
| | 1345 | getLooseKeys(actor) { return actor.contents; } |
| | 1346 | |
| | 1347 | /* allow putting a key on the keyring */ |
| | 1348 | iobjFor(PutOn) |
| | 1349 | { |
| | 1350 | /* we can only put keys on keyrings */ |
| | 1351 | verify() |
| | 1352 | { |
| | 1353 | /* we'll only allow our own keys to be attached */ |
| | 1354 | if (gDobj == nil) |
| | 1355 | { |
| | 1356 | /* |
| | 1357 | * we don't know the actual direct object yet, but we |
| | 1358 | * can at least check to see if any of the possible |
| | 1359 | * dobj's is my kind of key |
| | 1360 | */ |
| | 1361 | if (gTentativeDobj.indexWhich({x: isMyKey(x.obj_)}) == nil) |
| | 1362 | illogical(&objNotForKeyringMsg); |
| | 1363 | } |
| | 1364 | else if (!isMyKey(gDobj)) |
| | 1365 | { |
| | 1366 | /* the dobj isn't a valid key for this keyring */ |
| | 1367 | illogical(&objNotForKeyringMsg); |
| | 1368 | } |
| | 1369 | } |
| | 1370 | |
| | 1371 | /* put a key on me */ |
| | 1372 | action() |
| | 1373 | { |
| | 1374 | /* move the key into me */ |
| | 1375 | gDobj.moveInto(self); |
| | 1376 | |
| | 1377 | /* show the default "put on" response */ |
| | 1378 | defaultReport(&okayPutOnMsg); |
| | 1379 | } |
| | 1380 | } |
| | 1381 | |
| | 1382 | /* treat "attach x to keyring" as "put x on keyring" */ |
| | 1383 | iobjFor(AttachTo) remapTo(PutOn, DirectObject, self) |
| | 1384 | |
| | 1385 | /* treat "detach x from keyring" as "take x from keyring" */ |
| | 1386 | iobjFor(DetachFrom) remapTo(TakeFrom, DirectObject, self) |
| | 1387 | |
| | 1388 | /* receive notification before an action */ |
| | 1389 | beforeAction() |
| | 1390 | { |
| | 1391 | /* |
| | 1392 | * Note whether or not we want to consider moving the direct |
| | 1393 | * object to the keyring after a "take" command. We will |
| | 1394 | * consider doing so only if the direct object isn't already on |
| | 1395 | * the keyring - if it is, we don't want to move it back right |
| | 1396 | * after removing it, obviously. |
| | 1397 | * |
| | 1398 | * Skip the implicit keyring attachment if the current command |
| | 1399 | * is implicit, because they must be doing something that |
| | 1400 | * requires holding the object, in which case taking it is |
| | 1401 | * incidental. It could be actively annoying to attach the |
| | 1402 | * object to the keyring in such cases - for example, if the |
| | 1403 | * command is "put key on keyring," attaching it as part of the |
| | 1404 | * implicit action would render the explicit command redundant |
| | 1405 | * and cause it to fail. |
| | 1406 | */ |
| | 1407 | moveAfterTake = (!gAction.isImplicit |
| | 1408 | && gDobj != nil |
| | 1409 | && !gDobj.isDirectlyIn(self)); |
| | 1410 | } |
| | 1411 | |
| | 1412 | /* flag: consider moving to keyring after this "take" action */ |
| | 1413 | moveAfterTake = nil |
| | 1414 | |
| | 1415 | /* receive notification after an action */ |
| | 1416 | afterAction() |
| | 1417 | { |
| | 1418 | /* |
| | 1419 | * If the command was "take", and the direct object was a key, |
| | 1420 | * and the actor involved is holding the keyring and can touch |
| | 1421 | * it, and the command succeeded in moving the key to the |
| | 1422 | * actor's direct inventory, then move the key onto the keyring. |
| | 1423 | * Only consider this if we decided to during the "before" |
| | 1424 | * notification. |
| | 1425 | */ |
| | 1426 | if (moveAfterTake |
| | 1427 | && gActionIs(Take) |
| | 1428 | && isMyKey(gDobj) |
| | 1429 | && isIn(gActor) |
| | 1430 | && gActor.canTouch(self) |
| | 1431 | && gDobj.isDirectlyIn(gActor)) |
| | 1432 | { |
| | 1433 | /* move the key to me */ |
| | 1434 | gDobj.moveInto(self); |
| | 1435 | |
| | 1436 | /* |
| | 1437 | * Mention what we did. If the only report for this action |
| | 1438 | * so far is the default 'take' response, then use the |
| | 1439 | * combined taken-and-attached message. Otherwise, append |
| | 1440 | * our 'attached' message, which is suitable to use after |
| | 1441 | * other messages. |
| | 1442 | */ |
| | 1443 | if (gTranscript.currentActionHasReport( |
| | 1444 | {x: (x.ofKind(CommandReportMessage) |
| | 1445 | && x.messageProp_ != &okayTakeMsg)})) |
| | 1446 | { |
| | 1447 | /* |
| | 1448 | * we have a non-default message already, so add our |
| | 1449 | * message indicating that we added the key to the |
| | 1450 | * keyring |
| | 1451 | */ |
| | 1452 | reportAfter(&movedKeyToKeyringMsg, self); |
| | 1453 | } |
| | 1454 | else |
| | 1455 | { |
| | 1456 | /* use the combination taken-and-attached message */ |
| | 1457 | mainReport(&takenAndMovedToKeyringMsg, self); |
| | 1458 | } |
| | 1459 | } |
| | 1460 | } |
| | 1461 | |
| | 1462 | /* find among our keys a key that works the direct object */ |
| | 1463 | findWorkingKey(lock) |
| | 1464 | { |
| | 1465 | /* try each key on the keyring */ |
| | 1466 | foreach (local key in contents) |
| | 1467 | { |
| | 1468 | /* |
| | 1469 | * if this is the key that unlocks the lock, replace the |
| | 1470 | * command with 'unlock lock with key' |
| | 1471 | */ |
| | 1472 | if (lock.keyFitsLock(key)) |
| | 1473 | { |
| | 1474 | /* note that we tried keys and found the right one */ |
| | 1475 | extraReport(&foundKeyOnKeyringMsg, self, key); |
| | 1476 | |
| | 1477 | /* return the key */ |
| | 1478 | return key; |
| | 1479 | } |
| | 1480 | } |
| | 1481 | |
| | 1482 | /* we didn't find the right key - indicate failure */ |
| | 1483 | reportFailure(&foundNoKeyOnKeyringMsg, self); |
| | 1484 | return nil; |
| | 1485 | } |
| | 1486 | |
| | 1487 | /* |
| | 1488 | * Append my directly-held contents to a vector when I'm directly |
| | 1489 | * held. We consider all of the keys on the keyring to be |
| | 1490 | * effectively at the same containment level as the keyring, so if |
| | 1491 | * the keyring is held, so are its attached keys. |
| | 1492 | */ |
| | 1493 | appendHeldContents(vec) |
| | 1494 | { |
| | 1495 | /* append all of our contents, since they're held when we are */ |
| | 1496 | vec.appendUnique(contents); |
| | 1497 | } |
| | 1498 | |
| | 1499 | /* |
| | 1500 | * Announce myself as a default object for an action. |
| | 1501 | * |
| | 1502 | * Do not announce a keyring as a default for "lock with" or "unlock |
| | 1503 | * with". Although we can use a keyring as the indirect object of a |
| | 1504 | * lock/unlock command, we don't actually do the unlocking with the |
| | 1505 | * keyring; so, when we're chosen as the default, suppress the |
| | 1506 | * announcement, since it would imply that we're being used to lock |
| | 1507 | * or unlock something. |
| | 1508 | */ |
| | 1509 | announceDefaultObject(whichObj, action, resolvedAllObjects) |
| | 1510 | { |
| | 1511 | /* if it's not a lock-with or unlock-with, use the default message */ |
| | 1512 | if (!action.ofKind(LockWithAction) |
| | 1513 | && !action.ofKind(UnlockWithAction)) |
| | 1514 | { |
| | 1515 | /* for anything but our special cases, use the default handling */ |
| | 1516 | return inherited(whichObj, action, resolvedAllObjects); |
| | 1517 | } |
| | 1518 | |
| | 1519 | /* use no announcement */ |
| | 1520 | return ''; |
| | 1521 | } |
| | 1522 | |
| | 1523 | /* |
| | 1524 | * Allow locking or unlocking an object with a keyring. This will |
| | 1525 | * automatically try each key on the keyring to see if it fits the |
| | 1526 | * lock. |
| | 1527 | */ |
| | 1528 | iobjFor(LockWith) |
| | 1529 | { |
| | 1530 | verify() |
| | 1531 | { |
| | 1532 | /* if we don't have any keys, we're not locking anything */ |
| | 1533 | if (contents.length() == 0) |
| | 1534 | illogical(&cannotLockWithMsg); |
| | 1535 | |
| | 1536 | /* |
| | 1537 | * if we know the direct object, and we don't have any keys |
| | 1538 | * that are plausible for the direct object, we're an |
| | 1539 | * unlikely match |
| | 1540 | */ |
| | 1541 | if (gDobj != nil) |
| | 1542 | { |
| | 1543 | local foundPlausibleKey; |
| | 1544 | |
| | 1545 | /* |
| | 1546 | * try each of my keys to see if it's plausible for the |
| | 1547 | * direct object |
| | 1548 | */ |
| | 1549 | foundPlausibleKey = nil; |
| | 1550 | foreach (local cur in contents) |
| | 1551 | { |
| | 1552 | /* |
| | 1553 | * if this is a plausible key, note that we have at |
| | 1554 | * least one plausible key |
| | 1555 | */ |
| | 1556 | if (gDobj.keyIsPlausible(cur)) |
| | 1557 | { |
| | 1558 | /* note that we found a plausible key */ |
| | 1559 | foundPlausibleKey = true; |
| | 1560 | |
| | 1561 | /* no need to look any further - one is good enough */ |
| | 1562 | break; |
| | 1563 | } |
| | 1564 | } |
| | 1565 | |
| | 1566 | /* |
| | 1567 | * If we didn't find a plausible key, we're an unlikely |
| | 1568 | * match. |
| | 1569 | * |
| | 1570 | * If we did find a plausible key, increase the |
| | 1571 | * likelihood that this is the indirect object so that |
| | 1572 | * it's greater than the likelihood for any random key |
| | 1573 | * that's plausible for the lock (which has the default |
| | 1574 | * likelihood of 100), but less than the likelihood of |
| | 1575 | * the known good key (which is 150). This will cause a |
| | 1576 | * keyring to be taken as a default over any ordinary |
| | 1577 | * key, but will cause the correct key to override the |
| | 1578 | * keyring as the default if the correct key is known to |
| | 1579 | * the player already. |
| | 1580 | */ |
| | 1581 | if (foundPlausibleKey) |
| | 1582 | logicalRank(140, 'keyring with plausible key'); |
| | 1583 | else |
| | 1584 | logicalRank(50, 'no plausible key'); |
| | 1585 | } |
| | 1586 | } |
| | 1587 | |
| | 1588 | action() |
| | 1589 | { |
| | 1590 | local key; |
| | 1591 | |
| | 1592 | /* |
| | 1593 | * Try finding a working key. If we find one, replace the |
| | 1594 | * command with 'lock <lock> with <key>, so that we have the |
| | 1595 | * full effect of the 'lock with' command using the key |
| | 1596 | * itself. |
| | 1597 | */ |
| | 1598 | if ((key = findWorkingKey(gDobj)) != nil) |
| | 1599 | replaceAction(LockWith, gDobj, key); |
| | 1600 | } |
| | 1601 | } |
| | 1602 | |
| | 1603 | iobjFor(UnlockWith) |
| | 1604 | { |
| | 1605 | /* verify the same as for LockWith */ |
| | 1606 | verify() |
| | 1607 | { |
| | 1608 | /* if we don't have any keys, we're not unlocking anything */ |
| | 1609 | if (contents.length() == 0) |
| | 1610 | illogical(&cannotUnlockWithMsg); |
| | 1611 | else |
| | 1612 | verifyIobjLockWith(); |
| | 1613 | } |
| | 1614 | |
| | 1615 | action() |
| | 1616 | { |
| | 1617 | local key; |
| | 1618 | |
| | 1619 | /* |
| | 1620 | * if we can find a working key, run an 'unlock with' action |
| | 1621 | * using the key |
| | 1622 | */ |
| | 1623 | if ((key = findWorkingKey(gDobj)) != nil) |
| | 1624 | replaceAction(UnlockWith, gDobj, key); |
| | 1625 | } |
| | 1626 | } |
| | 1627 | ; |
| | 1628 | |
| | 1629 | /* |
| | 1630 | * Key - this is an object that can be used to unlock things, and which |
| | 1631 | * can be stored on a keyring. The key that unlocks a lock is |
| | 1632 | * identified with a property on the lock, not on the key. |
| | 1633 | */ |
| | 1634 | class Key: Thing |
| | 1635 | /* |
| | 1636 | * A key on a keyring that is being held by an actor is considered |
| | 1637 | * to be held by the actor, since the key does not have to be |
| | 1638 | * removed from the keyring in order to be manipulated as though it |
| | 1639 | * were directly held. |
| | 1640 | */ |
| | 1641 | isHeldBy(actor) |
| | 1642 | { |
| | 1643 | /* |
| | 1644 | * if I'm on a keyring, I'm being held if the keyring is being |
| | 1645 | * held; otherwise, use the default definition |
| | 1646 | */ |
| | 1647 | if (location != nil && location.ofKind(Keyring)) |
| | 1648 | return location.isHeldBy(actor); |
| | 1649 | else |
| | 1650 | return inherited(actor); |
| | 1651 | } |
| | 1652 | |
| | 1653 | /* |
| | 1654 | * Try making the current command's actor hold me. If we're on a |
| | 1655 | * keyring, we'll simply try to make the keyring itself held, rather |
| | 1656 | * than taking the key off the keyring; otherwise, we'll inherit the |
| | 1657 | * default behavior to make ourselves held. |
| | 1658 | */ |
| | 1659 | tryHolding() |
| | 1660 | { |
| | 1661 | if (location != nil && location.ofKind(Keyring)) |
| | 1662 | return location.tryHolding(); |
| | 1663 | else |
| | 1664 | return inherited(); |
| | 1665 | } |
| | 1666 | |
| | 1667 | /* -------------------------------------------------------------------- */ |
| | 1668 | /* |
| | 1669 | * Action processing |
| | 1670 | */ |
| | 1671 | |
| | 1672 | /* treat "detach key" as "take key" if it's on a keyring */ |
| | 1673 | dobjFor(Detach) |
| | 1674 | { |
| | 1675 | verify() |
| | 1676 | { |
| | 1677 | /* if I'm not on a keyring, there's nothing to detach from */ |
| | 1678 | if (location == nil || !location.ofKind(Keyring)) |
| | 1679 | illogical(&keyNotDetachableMsg); |
| | 1680 | } |
| | 1681 | remap() |
| | 1682 | { |
| | 1683 | /* if I'm on a keyring, remap to "take self" */ |
| | 1684 | if (location != nil && location.ofKind(Keyring)) |
| | 1685 | return [TakeAction, self]; |
| | 1686 | else |
| | 1687 | return inherited(); |
| | 1688 | } |
| | 1689 | } |
| | 1690 | |
| | 1691 | /* "lock with" */ |
| | 1692 | iobjFor(LockWith) |
| | 1693 | { |
| | 1694 | verify() |
| | 1695 | { |
| | 1696 | /* |
| | 1697 | * if we know the direct object is a LockableWithKey, we can |
| | 1698 | * perform some additional checks on the likelihood of this |
| | 1699 | * key being the intended key for the lock |
| | 1700 | */ |
| | 1701 | if (gDobj != nil |
| | 1702 | && gDobj.ofKind(LockableWithKey)) |
| | 1703 | { |
| | 1704 | /* |
| | 1705 | * If the player should know that we're the key for the |
| | 1706 | * lock, boost our likelihood so that we'll be picked |
| | 1707 | * out automatically from an ambiguous set of keys. |
| | 1708 | */ |
| | 1709 | if (gDobj.isKeyKnown(self)) |
| | 1710 | logicalRank(150, 'known key'); |
| | 1711 | |
| | 1712 | /* |
| | 1713 | * if this isn't a plausible key for the lockable, it's |
| | 1714 | * unlikely that this is a match |
| | 1715 | */ |
| | 1716 | if (!gDobj.keyIsPlausible(self)) |
| | 1717 | illogical(keyNotPlausibleMsg); |
| | 1718 | } |
| | 1719 | } |
| | 1720 | } |
| | 1721 | |
| | 1722 | /* |
| | 1723 | * the message to use when the key is obviously not plausible for a |
| | 1724 | * given lock |
| | 1725 | */ |
| | 1726 | keyNotPlausibleMsg = &keyDoesNotFitLockMsg |
| | 1727 | |
| | 1728 | /* "unlock with" */ |
| | 1729 | iobjFor(UnlockWith) |
| | 1730 | { |
| | 1731 | verify() |
| | 1732 | { |
| | 1733 | /* use the same key selection we use for "lock with" */ |
| | 1734 | verifyIobjLockWith(); |
| | 1735 | } |
| | 1736 | } |
| | 1737 | ; |
| | 1738 | |
| | 1739 | /* ------------------------------------------------------------------------ */ |
| | 1740 | /* |
| | 1741 | * A Dispenser is a container for a special type of item, such as a book |
| | 1742 | * of matches or a box of candy. |
| | 1743 | */ |
| | 1744 | class Dispenser: Container |
| | 1745 | /* |
| | 1746 | * Can we return one of our items to the dispenser once the item is |
| | 1747 | * dispensed? Books of matches wouldn't generally allow this, since |
| | 1748 | * a match must be torn out to be removed, but simple box dispensers |
| | 1749 | * probably would. By default, we won't allow returning an item |
| | 1750 | * once dispensed. |
| | 1751 | */ |
| | 1752 | canReturnItem = nil |
| | 1753 | |
| | 1754 | /* |
| | 1755 | * Is the item one of the types of items we dispense? Normally, we |
| | 1756 | * dispense identical items, so our default implementation simply |
| | 1757 | * determines if the item is an instance of our dispensable class. |
| | 1758 | * If the dispenser can hand out items of multiple, unrelated |
| | 1759 | * classes, this can be overridden to use a different means of |
| | 1760 | * identifying the dispensed items. |
| | 1761 | */ |
| | 1762 | isMyItem(obj) { return obj.ofKind(myItemClass); } |
| | 1763 | |
| | 1764 | /* |
| | 1765 | * The class of items we dispense. This is used by the default |
| | 1766 | * implementation of isMyItem(), so subclasses that inherit that |
| | 1767 | * implementation should provide the appropriate base class here. |
| | 1768 | */ |
| | 1769 | myItemClass = Dispensable |
| | 1770 | |
| | 1771 | /* "put in" indirect object handler */ |
| | 1772 | iobjFor(PutIn) |
| | 1773 | { |
| | 1774 | verify() |
| | 1775 | { |
| | 1776 | /* if we know the direct object, consider it further */ |
| | 1777 | if (gDobj != nil) |
| | 1778 | { |
| | 1779 | /* if we don't allow returning our items, don't allow it */ |
| | 1780 | if (!canReturnItem && isMyItem(gDobj)) |
| | 1781 | illogical(&cannotReturnToDispenserMsg); |
| | 1782 | |
| | 1783 | /* if it's not my dispensed item, it can't go in here */ |
| | 1784 | if (!isMyItem(gDobj)) |
| | 1785 | illogical(&cannotPutInDispenserMsg); |
| | 1786 | } |
| | 1787 | |
| | 1788 | /* inherit default handling */ |
| | 1789 | inherited(); |
| | 1790 | } |
| | 1791 | } |
| | 1792 | ; |
| | 1793 | |
| | 1794 | /* |
| | 1795 | * A Dispensable is an item that comes from a Dispenser. This is in |
| | 1796 | * most respects an ordinary item; the only special thing about it is |
| | 1797 | * that if we're still in our dispenser, we're an unlikely match for any |
| | 1798 | * command except "take" and the like. |
| | 1799 | */ |
| | 1800 | class Dispensable: Thing |
| | 1801 | /* |
| | 1802 | * My dispenser. This is usually my initial location, so by default |
| | 1803 | * we'll pre-initialize this to our location. |
| | 1804 | */ |
| | 1805 | myDispenser = nil |
| | 1806 | |
| | 1807 | /* pre-initialization */ |
| | 1808 | initializeThing() |
| | 1809 | { |
| | 1810 | /* inherit the default initialization */ |
| | 1811 | inherited(); |
| | 1812 | |
| | 1813 | /* |
| | 1814 | * We're usually in our dispenser initially, so assume that our |
| | 1815 | * dispenser is simply our initial location. If myDispenser is |
| | 1816 | * overridden in a subclass, don't overwrite the inherited |
| | 1817 | * value. |
| | 1818 | */ |
| | 1819 | if (propType(&myDispenser) == TypeNil) |
| | 1820 | myDispenser = location; |
| | 1821 | } |
| | 1822 | |
| | 1823 | dobjFor(All) |
| | 1824 | { |
| | 1825 | verify() |
| | 1826 | { |
| | 1827 | /* |
| | 1828 | * If we're in our dispenser, and the command isn't "take" |
| | 1829 | * or "take from", reduce our disambiguation likelihood - |
| | 1830 | * it's more likely that the actor is referring to another |
| | 1831 | * equivalent item that they've already removed from the |
| | 1832 | * dispenser. |
| | 1833 | */ |
| | 1834 | if (isIn(myDispenser) |
| | 1835 | && !gActionIs(Take) && !gActionIs(TakeFrom)) |
| | 1836 | { |
| | 1837 | /* we're in our dispenser - reduce the likelihood */ |
| | 1838 | logicalRank(60, 'in dispenser'); |
| | 1839 | } |
| | 1840 | } |
| | 1841 | } |
| | 1842 | ; |
| | 1843 | |
| | 1844 | |
| | 1845 | /* ------------------------------------------------------------------------ */ |
| | 1846 | /* |
| | 1847 | * A Matchbook is a special dispenser for matches. |
| | 1848 | */ |
| | 1849 | class Matchbook: Collective, Openable, Dispenser |
| | 1850 | /* we cannot return a match to a matchbook */ |
| | 1851 | canReturnItem = nil |
| | 1852 | |
| | 1853 | /* |
| | 1854 | * we dispense matches (subclasses can override this if they want to |
| | 1855 | * dispense a specialized match subclass) |
| | 1856 | */ |
| | 1857 | myItemClass = Matchstick |
| | 1858 | |
| | 1859 | /* |
| | 1860 | * Act as a collective for any items within me. This will have no |
| | 1861 | * effect unless we also have a plural name that matches that of the |
| | 1862 | * contained items. |
| | 1863 | * |
| | 1864 | * It is usually desirable for a matchbook to act as a collective |
| | 1865 | * for the contained items, so that a command like "take matches" |
| | 1866 | * will be taken to apply to the matchbook rather than the |
| | 1867 | * individual matches. |
| | 1868 | */ |
| | 1869 | isCollectiveFor(obj) { return obj.isIn(self); } |
| | 1870 | |
| | 1871 | /* |
| | 1872 | * Append my directly-held contents to a vector when I'm directly |
| | 1873 | * held. When the matchbook is open, append our matches, because we |
| | 1874 | * consider the matches to be effectively attached to the matchbook |
| | 1875 | * (rather than contained within it). |
| | 1876 | */ |
| | 1877 | appendHeldContents(vec) |
| | 1878 | { |
| | 1879 | /* if we're open, append our contents */ |
| | 1880 | if (isOpen) |
| | 1881 | vec.appendUnique(contents); |
| | 1882 | } |
| | 1883 | ; |
| | 1884 | |
| | 1885 | /* |
| | 1886 | * A FireSource is an object that can set another object on fire. This |
| | 1887 | * is a mix-in class that can be used with other classes. |
| | 1888 | */ |
| | 1889 | class FireSource: object |
| | 1890 | /* |
| | 1891 | * We can use a fire source to light another object, provided the |
| | 1892 | * fire source is itself burning. We don't provide any action |
| | 1893 | * handling - we leave that to the direct object. |
| | 1894 | */ |
| | 1895 | iobjFor(BurnWith) |
| | 1896 | { |
| | 1897 | preCond = [objHeld, objBurning] |
| | 1898 | verify() |
| | 1899 | { |
| | 1900 | /* don't allow using me to light myself */ |
| | 1901 | if (gDobj == self) |
| | 1902 | illogicalNow(&cannotBurnDobjWithMsg); |
| | 1903 | |
| | 1904 | /* |
| | 1905 | * If we're already lit, make this an especially good choice |
| | 1906 | * for lighting other objects - this will ensure that we |
| | 1907 | * choose this over a match that isn't already lit, which is |
| | 1908 | * what you'd normally want to do to avoid wasting a match. |
| | 1909 | * |
| | 1910 | * Note that our ranking is specifically coordinated with |
| | 1911 | * that used by Matchstick. We'll use a lit match over any |
| | 1912 | * normal FireSource (rank 160); we'll use a lit FireSource |
| | 1913 | * (rank 150) over an unlit match (rank 140). |
| | 1914 | * |
| | 1915 | * If we're not lit, make the action non-obvious so that |
| | 1916 | * we're not taken as a default to light another object on |
| | 1917 | * fire. We *could* light something once we're lit, but that |
| | 1918 | * presumes there's a way to light me in the first place, |
| | 1919 | * which might require yet another object (a match, for |
| | 1920 | * example) - so ignore me as a default if we're not already |
| | 1921 | * lit, and go directly to some other object. This should be |
| | 1922 | * overridden for self-lighting objects such as matches. |
| | 1923 | */ |
| | 1924 | if (isLit) |
| | 1925 | logicalRank(150, 'fire source'); |
| | 1926 | else |
| | 1927 | nonObvious; |
| | 1928 | } |
| | 1929 | } |
| | 1930 | ; |
| | 1931 | |
| | 1932 | /* |
| | 1933 | * A Matchstick is a self-igniting match from a matchbook. (We use this |
| | 1934 | * lengthy name rather than simply "Match" because the latter is too |
| | 1935 | * generic, and could be taken by a casual reader for an object |
| | 1936 | * representing a successful search result or the like.) |
| | 1937 | */ |
| | 1938 | class Matchstick: FireSource, LightSource |
| | 1939 | /* matches have fairly feeble light */ |
| | 1940 | brightnessOn = 2 |
| | 1941 | |
| | 1942 | /* not lit initially */ |
| | 1943 | isLit = nil |
| | 1944 | |
| | 1945 | /* amount of time we burn, in turns */ |
| | 1946 | burnLength = 2 |
| | 1947 | |
| | 1948 | /* default long description describes burning status */ |
| | 1949 | desc() |
| | 1950 | { |
| | 1951 | if (isLit) |
| | 1952 | gLibMessages.litMatchDesc(self); |
| | 1953 | else |
| | 1954 | gLibMessages.unlitMatchDesc(self); |
| | 1955 | } |
| | 1956 | |
| | 1957 | /* get our state */ |
| | 1958 | getState = (isLit ? matchStateLit : matchStateUnlit) |
| | 1959 | |
| | 1960 | /* get a list of all states */ |
| | 1961 | allStates = [matchStateLit, matchStateUnlit] |
| | 1962 | |
| | 1963 | /* "burn" action */ |
| | 1964 | dobjFor(Burn) |
| | 1965 | { |
| | 1966 | preCond = [objHeld] |
| | 1967 | verify() |
| | 1968 | { |
| | 1969 | /* can't light a match that's already burning */ |
| | 1970 | if (isLit) |
| | 1971 | illogicalAlready(&alreadyBurningMsg); |
| | 1972 | } |
| | 1973 | action() |
| | 1974 | { |
| | 1975 | local t; |
| | 1976 | |
| | 1977 | /* describe it */ |
| | 1978 | defaultReport(&okayBurnMatchMsg); |
| | 1979 | |
| | 1980 | /* make myself lit */ |
| | 1981 | makeLit(true); |
| | 1982 | |
| | 1983 | /* get our default burn length */ |
| | 1984 | t = burnLength; |
| | 1985 | |
| | 1986 | /* |
| | 1987 | * if this is an implicit command, reduce the burn length by |
| | 1988 | * one turn - this ensures that the player can't |
| | 1989 | * artificially extend the match's useful life by doing |
| | 1990 | * something that implicitly lights the match |
| | 1991 | */ |
| | 1992 | if (gAction.isImplicit) |
| | 1993 | --t; |
| | 1994 | |
| | 1995 | /* start our burn-out timer going */ |
| | 1996 | new SenseFuse(self, &matchBurnedOut, t, self, sight); |
| | 1997 | } |
| | 1998 | } |
| | 1999 | |
| | 2000 | iobjFor(BurnWith) |
| | 2001 | { |
| | 2002 | verify() |
| | 2003 | { |
| | 2004 | /* |
| | 2005 | * Whether or not a match is burning, it's an especially |
| | 2006 | * good choice to light something else on fire. Make it |
| | 2007 | * even more likely when it's burning already. |
| | 2008 | * |
| | 2009 | * Note that this is specifically coordinated with the base |
| | 2010 | * FireSource ranking. We'll pick a lit match (160) over an |
| | 2011 | * ordinary lit FireSource (150), but we'll pick a lit |
| | 2012 | * FireSource (150) over an unlit match (140). This will |
| | 2013 | * avoid consuming a match that's not already lit when |
| | 2014 | * another fire source is already available. |
| | 2015 | */ |
| | 2016 | logicalRank(isLit ? 160 : 140, 'fire source'); |
| | 2017 | } |
| | 2018 | } |
| | 2019 | |
| | 2020 | /* "extinguish" */ |
| | 2021 | dobjFor(Extinguish) |
| | 2022 | { |
| | 2023 | verify() |
| | 2024 | { |
| | 2025 | /* can't extinguish a match that isn't burning */ |
| | 2026 | if (!isLit) |
| | 2027 | illogicalAlready(&matchNotLitMsg); |
| | 2028 | } |
| | 2029 | action() |
| | 2030 | { |
| | 2031 | /* describe the match going out */ |
| | 2032 | defaultReport(&okayExtinguishMatchMsg); |
| | 2033 | |
| | 2034 | /* no longer lit */ |
| | 2035 | makeLit(nil); |
| | 2036 | |
| | 2037 | /* remove the match from the game */ |
| | 2038 | moveInto(nil); |
| | 2039 | } |
| | 2040 | } |
| | 2041 | |
| | 2042 | /* fuse handler for burning out */ |
| | 2043 | matchBurnedOut() |
| | 2044 | { |
| | 2045 | /* |
| | 2046 | * if I'm not still burning, I must have been extinguished |
| | 2047 | * explicitly already, so there's nothing to do |
| | 2048 | */ |
| | 2049 | if (!isLit) |
| | 2050 | return; |
| | 2051 | |
| | 2052 | /* make sure we separate any output from other commands */ |
| | 2053 | "<.p>"; |
| | 2054 | |
| | 2055 | /* report that we're done burning */ |
| | 2056 | gLibMessages.matchBurnedOut(self); |
| | 2057 | |
| | 2058 | /* |
| | 2059 | * remove myself from the game (for simplicity, a match simply |
| | 2060 | * disappears when it's done burning) |
| | 2061 | */ |
| | 2062 | moveInto(nil); |
| | 2063 | } |
| | 2064 | |
| | 2065 | /* matches usually come in bunches of equivalents */ |
| | 2066 | isEquivalent = true |
| | 2067 | ; |
| | 2068 | |
| | 2069 | /* |
| | 2070 | * A light source that produces light using a fuel supply. This kind of |
| | 2071 | * light source uses a daemon to consume fuel whenever it's lit. |
| | 2072 | */ |
| | 2073 | class FueledLightSource: LightSource |
| | 2074 | /* provide a bright light by default */ |
| | 2075 | brightnessOn = 3 |
| | 2076 | |
| | 2077 | /* not lit initially */ |
| | 2078 | isLit = nil |
| | 2079 | |
| | 2080 | /* |
| | 2081 | * Our fuel source object. If desired, this can be set to a |
| | 2082 | * separate object to model the fuel supply separately from the |
| | 2083 | * light source itself; for example, you could set this to point to |
| | 2084 | * a battery, or to a vial of oil. By default, for simplicity, the |
| | 2085 | * fuel supply and light source are the same object. |
| | 2086 | * |
| | 2087 | * The fuel supply object must expose two methods: getFuelLevel() |
| | 2088 | * and consumeFuel(). |
| | 2089 | */ |
| | 2090 | fuelSource = (self) |
| | 2091 | |
| | 2092 | /* |
| | 2093 | * Get my fuel level, and consume fuel. We use these methods only |
| | 2094 | * when we're our own fuelSource (which we are by default). When |
| | 2095 | * we're not our own fuel source, the fuel source object must |
| | 2096 | * provide these methods instead of us. |
| | 2097 | * |
| | 2098 | * Our fuel level is the number of turns that we can continue to |
| | 2099 | * burn. Each turn we're lit, we'll reduce the fuel level by one. |
| | 2100 | * We'll automatically extinguish ourself when the fuel level |
| | 2101 | * reaches zero. |
| | 2102 | * |
| | 2103 | * If the light source can burn forever, simply return nil as the |
| | 2104 | * fuel level. |
| | 2105 | */ |
| | 2106 | getFuelLevel() { return fuelLevel; } |
| | 2107 | consumeFuel(amount) { fuelLevel -= amount; } |
| | 2108 | |
| | 2109 | /* our fuel level - we use this when we're our own fuel source */ |
| | 2110 | fuelLevel = 20 |
| | 2111 | |
| | 2112 | /* light or extinguish */ |
| | 2113 | makeLit(lit) |
| | 2114 | { |
| | 2115 | /* if the current fuel level is zero, we can't be lit */ |
| | 2116 | if (lit && fuelSource.getFuelLevel() == 0) |
| | 2117 | return; |
| | 2118 | |
| | 2119 | /* inherit the default handling */ |
| | 2120 | inherited(lit); |
| | 2121 | |
| | 2122 | /* if we're lit, activate our daemon; otherwise, stop our daemon */ |
| | 2123 | if (isLit) |
| | 2124 | { |
| | 2125 | /* start our burn daemon going */ |
| | 2126 | burnDaemonObj = |
| | 2127 | new SenseDaemon(self, &burnDaemon, 1, self, sight); |
| | 2128 | } |
| | 2129 | else |
| | 2130 | { |
| | 2131 | /* stop our daemon */ |
| | 2132 | eventManager.removeEvent(burnDaemonObj); |
| | 2133 | |
| | 2134 | /* forget out daemon */ |
| | 2135 | burnDaemonObj = nil; |
| | 2136 | } |
| | 2137 | } |
| | 2138 | |
| | 2139 | /* burn daemon - this is called on each turn while we're burning */ |
| | 2140 | burnDaemon() |
| | 2141 | { |
| | 2142 | local level = fuelSource.getFuelLevel(); |
| | 2143 | |
| | 2144 | /* if we use fuel, consume one increment of fuel for this turn */ |
| | 2145 | if (level != nil) |
| | 2146 | { |
| | 2147 | /* |
| | 2148 | * If our fuel level has reached zero, stop burning. Note |
| | 2149 | * that the daemon is called on the first turn after we |
| | 2150 | * start burning, so we must go through a turn with the fuel |
| | 2151 | * level at zero before we stop burning. |
| | 2152 | */ |
| | 2153 | if (level == 0) |
| | 2154 | { |
| | 2155 | /* make sure we separate any output from other commands */ |
| | 2156 | "<.p>"; |
| | 2157 | |
| | 2158 | /* mention that the candle goes out */ |
| | 2159 | sayBurnedOut(); |
| | 2160 | |
| | 2161 | /* |
| | 2162 | * Extinguish the candle. Note that we do this *after* |
| | 2163 | * we've already displayed the message about the candle |
| | 2164 | * burning out, because that message is displayed in our |
| | 2165 | * own sight context. If we're the only light source |
| | 2166 | * present, then we're invisible once we're not providing |
| | 2167 | * light, so our message about burning out would be |
| | 2168 | * suppressed if we displayed it after cutting off our |
| | 2169 | * own light. To make sure we can see the message, wait |
| | 2170 | * until after the message to cut off our light. |
| | 2171 | */ |
| | 2172 | makeLit(nil); |
| | 2173 | } |
| | 2174 | else |
| | 2175 | { |
| | 2176 | /* reduce our fuel level by one */ |
| | 2177 | fuelSource.consumeFuel(1); |
| | 2178 | } |
| | 2179 | } |
| | 2180 | } |
| | 2181 | |
| | 2182 | /* mention that we've just burned out */ |
| | 2183 | sayBurnedOut() { gLibMessages.objBurnedOut(self); } |
| | 2184 | |
| | 2185 | /* our daemon object, valid while we're burning */ |
| | 2186 | burnDaemonObj = nil |
| | 2187 | ; |
| | 2188 | |
| | 2189 | /* |
| | 2190 | * A candle is an item that can be set on fire for a controlled burn. |
| | 2191 | * Although we call this a candle, this class can be used for other types |
| | 2192 | * of fuel burners, such as torches and oil lanterns. |
| | 2193 | * |
| | 2194 | * Ordinary candles are usually fire sources as well, in that you can |
| | 2195 | * light one candle with another once the first one is lit. To get this |
| | 2196 | * effect, mix FireSource into the superclass list (but put it before |
| | 2197 | * Candle, since FireSource is specifically designed as a mix-in class). |
| | 2198 | */ |
| | 2199 | class Candle: FueledLightSource |
| | 2200 | /* |
| | 2201 | * The message we display when we try to light the candle and we're |
| | 2202 | * out of fuel. This message can be overridden by subclasses that |
| | 2203 | * don't fit the default message. |
| | 2204 | */ |
| | 2205 | outOfFuelMsg = &candleOutOfFuelMsg |
| | 2206 | |
| | 2207 | /* the message we display when we successfully light the candle */ |
| | 2208 | okayBurnMsg = &okayBurnCandleMsg |
| | 2209 | |
| | 2210 | /* show a message when the candle runs out fuel while burning */ |
| | 2211 | sayBurnedOut() |
| | 2212 | { |
| | 2213 | /* by default, show our standard library message */ |
| | 2214 | gLibMessages.candleBurnedOut(self); |
| | 2215 | } |
| | 2216 | |
| | 2217 | /* |
| | 2218 | * Determine if I can be lit with the specific indirect object. By |
| | 2219 | * default, we'll allow any object to light us if the object passes |
| | 2220 | * the normal checks applied by its own iobjFor(BurnWith) handlers. |
| | 2221 | * This can be overridden if we can only be lit with specific |
| | 2222 | * sources of fire; for example, a furnace with a deeply-recessed |
| | 2223 | * burner could refuse to be lit by anything but particular long |
| | 2224 | * matches, or a particular type of fuel could refuse to be lit |
| | 2225 | * except by certain especially hot flames. |
| | 2226 | */ |
| | 2227 | canLightWith(obj) { return true; } |
| | 2228 | |
| | 2229 | /* |
| | 2230 | * Default long description describes burning status. In most |
| | 2231 | * cases, this should be overridden to provide more details, such as |
| | 2232 | * information on our fuel level. |
| | 2233 | */ |
| | 2234 | desc() |
| | 2235 | { |
| | 2236 | if (isLit) |
| | 2237 | gLibMessages.litCandleDesc(self); |
| | 2238 | else |
| | 2239 | inherited(); |
| | 2240 | } |
| | 2241 | |
| | 2242 | /* "burn with" action */ |
| | 2243 | dobjFor(BurnWith) |
| | 2244 | { |
| | 2245 | preCond = [touchObj] |
| | 2246 | verify() |
| | 2247 | { |
| | 2248 | /* can't light it if it's already lit */ |
| | 2249 | if (isLit) |
| | 2250 | illogicalAlready(&alreadyBurningMsg); |
| | 2251 | } |
| | 2252 | check() |
| | 2253 | { |
| | 2254 | /* |
| | 2255 | * make sure the object being used to light us is a valid |
| | 2256 | * source of fire for us |
| | 2257 | */ |
| | 2258 | if (!canLightWith(obj)) |
| | 2259 | { |
| | 2260 | reportFailure(&cannotBurnDobjWithMsg); |
| | 2261 | exit; |
| | 2262 | } |
| | 2263 | |
| | 2264 | /* if the fuel level is zero, we can't be lit */ |
| | 2265 | if (fuelSource.getFuelLevel() == 0) |
| | 2266 | { |
| | 2267 | reportFailure(outOfFuelMsg); |
| | 2268 | exit; |
| | 2269 | } |
| | 2270 | } |
| | 2271 | action() |
| | 2272 | { |
| | 2273 | /* make myself lit */ |
| | 2274 | makeLit(true); |
| | 2275 | |
| | 2276 | /* describe it */ |
| | 2277 | defaultReport(okayBurnMsg); |
| | 2278 | } |
| | 2279 | } |
| | 2280 | |
| | 2281 | /* "extinguish" */ |
| | 2282 | dobjFor(Extinguish) |
| | 2283 | { |
| | 2284 | verify() |
| | 2285 | { |
| | 2286 | /* can't extinguish a match that isn't burning */ |
| | 2287 | if (!isLit) |
| | 2288 | illogicalAlready(&candleNotLitMsg); |
| | 2289 | } |
| | 2290 | action() |
| | 2291 | { |
| | 2292 | /* describe the match going out */ |
| | 2293 | defaultReport(&okayExtinguishCandleMsg); |
| | 2294 | |
| | 2295 | /* no longer lit */ |
| | 2296 | makeLit(nil); |
| | 2297 | } |
| | 2298 | } |
| | 2299 | ; |
| | 2300 | |
| | 2301 | /* ------------------------------------------------------------------------ */ |
| | 2302 | /* |
| | 2303 | * "Tour Guide" is a mix-in class for Actors. This class can be |
| | 2304 | * multiply inherited by objects along with Actor or a subclass of |
| | 2305 | * Actor. This mix-in makes the Follow action, when applied to the tour |
| | 2306 | * guide, initiate travel according to where the tour guide wants to go |
| | 2307 | * next. So, if the tour guide is here and is waving us through the |
| | 2308 | * door, FOLLOW GUIDE will initiate travel through the door. |
| | 2309 | * |
| | 2310 | * This class should appear in the superclass list ahead of Actor or the |
| | 2311 | * Actor subclass. |
| | 2312 | */ |
| | 2313 | class TourGuide: object |
| | 2314 | dobjFor(Follow) |
| | 2315 | { |
| | 2316 | verify() |
| | 2317 | { |
| | 2318 | /* |
| | 2319 | * If the actor can see us, and we're in a "guided tour" |
| | 2320 | * state, we can definitely perform the travel. Otherwise, |
| | 2321 | * use the standard "follow" behavior. |
| | 2322 | */ |
| | 2323 | if (gActor.canSee(self) && getTourDest() != nil) |
| | 2324 | { |
| | 2325 | /* |
| | 2326 | * we're waiting to show the actor to the next stop on |
| | 2327 | * the tour, so we can definitely proceed with this |
| | 2328 | * action |
| | 2329 | */ |
| | 2330 | } |
| | 2331 | else |
| | 2332 | { |
| | 2333 | /* we're not in a tour state, so use the standard handling */ |
| | 2334 | inherited(); |
| | 2335 | } |
| | 2336 | } |
| | 2337 | |
| | 2338 | action() |
| | 2339 | { |
| | 2340 | local dest; |
| | 2341 | |
| | 2342 | /* |
| | 2343 | * if we're in a guided tour state, initiate travel to our |
| | 2344 | * escort destination; otherwise, use the standard handling |
| | 2345 | */ |
| | 2346 | if (gActor.canSee(self) && (dest = getTourDest()) != nil) |
| | 2347 | { |
| | 2348 | /* initiate travel to our destination */ |
| | 2349 | replaceAction(TravelVia, dest); |
| | 2350 | return; |
| | 2351 | } |
| | 2352 | else |
| | 2353 | { |
| | 2354 | /* no tour state; use the standard handling */ |
| | 2355 | inherited(); |
| | 2356 | } |
| | 2357 | } |
| | 2358 | } |
| | 2359 | |
| | 2360 | /* |
| | 2361 | * Get the travel connector that takes us to our next guided tour |
| | 2362 | * destination. By default, this returns the escortDest from our |
| | 2363 | * current actor state if our state is a guided tour state, or nil |
| | 2364 | * if our state is any other kind of state. Subclasses must |
| | 2365 | * override this if they use other kinds of states to represent |
| | 2366 | * guided tours, since we'll only detect that we're in a guided tour |
| | 2367 | * state if our current actor state object is of class |
| | 2368 | * GuidedTourState (or any subclass). |
| | 2369 | */ |
| | 2370 | getTourDest() |
| | 2371 | { |
| | 2372 | return (curState.ofKind(GuidedTourState) |
| | 2373 | ? curState.escortDest |
| | 2374 | : nil); |
| | 2375 | } |
| | 2376 | ; |
| | 2377 | |
| | 2378 | /* |
| | 2379 | * Guided Tour state. This provides a simple way of defining a "guided |
| | 2380 | * tour," which is a series of locations to which we try to guide the |
| | 2381 | * player character. We don't force the player character to travel as |
| | 2382 | * specified; we merely try to lead the player. The actual travel is up |
| | 2383 | * to the player. |
| | 2384 | * |
| | 2385 | * Here's how this works. For each location on the guided tour, create |
| | 2386 | * one of these state objects. Set escortDest to the travel connector |
| | 2387 | * to which we're attempting to guide the player character from the |
| | 2388 | * current location. Set stateAfterEscort to the state object for the |
| | 2389 | * next location on the tour. Set stateDesc to something indicating |
| | 2390 | * that we're trying to show the player to the next stop - something |
| | 2391 | * along the lines of "Bob waits for you by the door." Set |
| | 2392 | * arrivingWithDesc to a message indicating that we just showed up in |
| | 2393 | * the current location and are ready to show the player to the next - |
| | 2394 | * "Bob goes to the door and waits for you to follow him." |
| | 2395 | */ |
| | 2396 | class GuidedTourState: AccompanyingState |
| | 2397 | /* the travel connector we're trying to show the player into */ |
| | 2398 | escortDest = nil |
| | 2399 | |
| | 2400 | /* |
| | 2401 | * The next state for our actor to assume after the travel. This |
| | 2402 | * should be overridden and set to the state object for the next |
| | 2403 | * stop on the tour. |
| | 2404 | */ |
| | 2405 | stateAfterEscort = nil |
| | 2406 | |
| | 2407 | /* the actor we're escorting - this is usually the player character */ |
| | 2408 | escortActor = (gPlayerChar) |
| | 2409 | |
| | 2410 | /* |
| | 2411 | * The class we use for our actor state during the escort travel. |
| | 2412 | * By default, we use the basic guided-tour accompanying travel |
| | 2413 | * state class, but games will probably want to use a customized |
| | 2414 | * subclass of this basic class in most cases. The main reason to |
| | 2415 | * use a custom subclass is to provide customized messages to |
| | 2416 | * describe the departure of the escorting actor. |
| | 2417 | */ |
| | 2418 | escortStateClass = GuidedInTravelState |
| | 2419 | |
| | 2420 | /* |
| | 2421 | * we should accompany the travel if the actor we're guiding will be |
| | 2422 | * traveling, and they're traveling to the next stop on our tour |
| | 2423 | */ |
| | 2424 | accompanyTravel(traveler, conn) |
| | 2425 | { |
| | 2426 | return (traveler.isActorTraveling(escortActor) && conn == escortDest); |
| | 2427 | } |
| | 2428 | |
| | 2429 | /* |
| | 2430 | * get our accompanying state object - we'll create an instance of |
| | 2431 | * the class specified in our escortStateClass property |
| | 2432 | */ |
| | 2433 | getAccompanyingTravelState(traveler, conn) |
| | 2434 | { |
| | 2435 | return escortStateClass.createInstance( |
| | 2436 | location, gActor, stateAfterEscort); |
| | 2437 | } |
| | 2438 | ; |
| | 2439 | |
| | 2440 | /* |
| | 2441 | * A subclass of the basic accompanying travel state specifically |
| | 2442 | * designed for guided tours. This is almost the same as the basic |
| | 2443 | * accompanying travel state, but provides customized messages to |
| | 2444 | * describe the departure of our associated actor, which is the actor |
| | 2445 | * serving as the tour guide. |
| | 2446 | */ |
| | 2447 | class GuidedInTravelState: AccompanyingInTravelState |
| | 2448 | sayDeparting(conn) |
| | 2449 | { gLibMessages.sayDepartingWithGuide(location, leadActor); } |
| | 2450 | ; |
| | 2451 | |
| | 2452 | /* ------------------------------------------------------------------------ */ |
| | 2453 | /* |
| | 2454 | * An Attachable is an object that can be attached to another, using an |
| | 2455 | * ATTACH X TO Y command. This is a mix-in class that is meant to be |
| | 2456 | * combined with a Thing-derived class to create an attachable object. |
| | 2457 | * |
| | 2458 | * Attachment is symmetrical: we can only attach to other Attachable |
| | 2459 | * objects. As a result, the verb handling for ATTACH can be performed |
| | 2460 | * symmetrically - ATTACH X TO Y is handled the same way as ATTACH Y TO |
| | 2461 | * X. Sometimes reversing the roles makes the command nonsensical, but |
| | 2462 | * when the reversal makes sense, it seems unlikely that it'll ever |
| | 2463 | * change the meaning of the command. This makes it program the verb |
| | 2464 | * handling, because it means that we can designate one of X or Y as the |
| | 2465 | * handler for the verb, and just write the code once there. Refer to |
| | 2466 | * the handleAttach() method to see how this works. |
| | 2467 | * |
| | 2468 | * There's an important detail that we leave to instances, because |
| | 2469 | * there's no good general rule we can implement. Specifically, there's |
| | 2470 | * the matter of imposing appropriate constraints on the relative |
| | 2471 | * locations of objects once they're attached to one another. There are |
| | 2472 | * numerous anomalies that become possible once two objects are attached. |
| | 2473 | * Consider the example of a battery connected to a jumper cable that's |
| | 2474 | * in turn connected to a lamp: |
| | 2475 | * |
| | 2476 | * - if we put the battery in a box but leave the lamp outside the box, |
| | 2477 | * we shouldn't be able to close the lid of the box all the way without |
| | 2478 | * breaking the cables |
| | 2479 | * |
| | 2480 | * - if we're carrying the battery but not the lamp, traveling to a new |
| | 2481 | * room should drag the lamp along |
| | 2482 | * |
| | 2483 | * - if we drop the battery down a well, the lamp should be dragged down |
| | 2484 | * with it |
| | 2485 | * |
| | 2486 | * Our world model isn't sophisticated enough to properly model an |
| | 2487 | * attachment relationship, so it can't deal with these contingencies by |
| | 2488 | * proper physical simulation. Which is why we have to leave these for |
| | 2489 | * the game to handle. |
| | 2490 | * |
| | 2491 | * There are two main strategies you can apply to handle these problems. |
| | 2492 | * |
| | 2493 | * First, you can impose limits that prevent these sorts of situations |
| | 2494 | * from coming up in the first place, either by carefully designing the |
| | 2495 | * scenario so they simply don't come up, or by imposing more or less |
| | 2496 | * artificial constraints. For example, you could solve all of the |
| | 2497 | * problems above by eliminating the jumper cable and attaching the lamp |
| | 2498 | * directly to the battery, or by making the jumper cable very short. |
| | 2499 | * Anything attached to the battery would effectively become located "in" |
| | 2500 | * the battery, so it would move everywhere along with the battery |
| | 2501 | * automatically. Detaching the lamp would move the lamp back outside |
| | 2502 | * the battery, and conversely, moving the lamp out of the battery would |
| | 2503 | * detach the objects. |
| | 2504 | * |
| | 2505 | * Second, you can detect the anomalous cases and handle them explicitly |
| | 2506 | * with special-purpose code. You could use beforeAction and afterAction |
| | 2507 | * methods on one of the attached objects, for example, to detect the |
| | 2508 | * various problematic actions, either blocking them or implementing |
| | 2509 | * appropriate consequences. |
| | 2510 | * |
| | 2511 | * Given the number of difficult anomalies possible with rope-like |
| | 2512 | * objects, the second approach is challenging on its own. However, it |
| | 2513 | * often helps to combine it with the first approach, limiting the |
| | 2514 | * scenario. In other words, you'd limit the scenario to some extent, |
| | 2515 | * but not totally: rather than completely excising the difficult |
| | 2516 | * behavior, you'd narrow it down to a manageable subset of the full |
| | 2517 | * range of real-world possibilities; then, you'd deal with the remaining |
| | 2518 | * anomalies on a case-by-case basis. For example, you could make the |
| | 2519 | * battery too heavy to carry, which would guarantee that it would never |
| | 2520 | * be put in a box, thrown down a well, or carried out of the room. That |
| | 2521 | * would only leave a few issues: walking away while carrying the plugged |
| | 2522 | * in lamp, which could be handled with an afterAction that severs the |
| | 2523 | * attachment; putting the lamp in a box and closing the box, which could |
| | 2524 | * be handled with a beforeAction by blocking Close actions whenever the |
| | 2525 | * lamp is inside the object being closed. |
| | 2526 | */ |
| | 2527 | class Attachable: object |
| | 2528 | /* |
| | 2529 | * The list of objects I'm currently attached to. Note that each of |
| | 2530 | * the objects in this list must usually be an Attachable, and we |
| | 2531 | * must be included in the attachedObjects list in each of these |
| | 2532 | * objects. |
| | 2533 | */ |
| | 2534 | attachedObjects = [] |
| | 2535 | |
| | 2536 | /* |
| | 2537 | * Perform programmatic attachment, without any notifications. This |
| | 2538 | * simply updates my attachedObjects list and the other object's list |
| | 2539 | * to indicate that we're attached to the other object (and vice |
| | 2540 | * versa). |
| | 2541 | */ |
| | 2542 | attachTo(obj) |
| | 2543 | { |
| | 2544 | attachedObjects += obj; |
| | 2545 | obj.attachedObjects += self; |
| | 2546 | } |
| | 2547 | |
| | 2548 | /* perform programmatic detachment, without any notifications */ |
| | 2549 | detachFrom(obj) |
| | 2550 | { |
| | 2551 | attachedObjects -= obj; |
| | 2552 | obj.attachedObjects -= self; |
| | 2553 | } |
| | 2554 | |
| | 2555 | /* get the subset of my attachments that are non-permanent */ |
| | 2556 | getNonPermanentAttachments() |
| | 2557 | { |
| | 2558 | /* return the subset of objects not permanently attached */ |
| | 2559 | return attachedObjects.subset({x: !isPermanentlyAttachedTo(x)}); |
| | 2560 | } |
| | 2561 | |
| | 2562 | /* am I attached to the given object? */ |
| | 2563 | isAttachedTo(obj) |
| | 2564 | { |
| | 2565 | /* we are attached to the other object if it's in our list */ |
| | 2566 | return (attachedObjects.indexOf(obj) != nil); |
| | 2567 | } |
| | 2568 | |
| | 2569 | /* |
| | 2570 | * Am I the "major" item in my attachment relationship to the given |
| | 2571 | * object? This affects how our relationship is described in our |
| | 2572 | * status message: in an asymmetrical relationship, where one object |
| | 2573 | * is the "major" item, we will always describe the minor item as |
| | 2574 | * being attached to the major item rather than vice versa. This |
| | 2575 | * allows you to ensure that the message is always "the sign is |
| | 2576 | * attached to the wall", and never "the wall is attached to the |
| | 2577 | * sign": the wall is the major item in this relationship, so it's |
| | 2578 | * always the sign that's attached to it. |
| | 2579 | * |
| | 2580 | * By default, we always return nil here, which means that |
| | 2581 | * attachment relationships are symmetrical by default. In a |
| | 2582 | * symmetrical relationship, we'll describe the other things as |
| | 2583 | * attached to 'self' when describing self. |
| | 2584 | */ |
| | 2585 | isMajorItemFor(obj) { return nil; } |
| | 2586 | |
| | 2587 | /* |
| | 2588 | * Am I *listed* as attached to the given object? If this is true, |
| | 2589 | * then our examineStatus() will list 'obj' among the things I'm |
| | 2590 | * attached to: "Self is attached to obj." If this is nil, I'm not |
| | 2591 | * listed as attached. |
| | 2592 | * |
| | 2593 | * By default, we're listed if (1) we're not permanently attached to |
| | 2594 | * 'obj', AND (2) we're not the "major" item in the attachment |
| | 2595 | * relationship. The reason we're not listed if we're permanently |
| | 2596 | * attached is that the attachment information is presumably better |
| | 2597 | * handled via the fixed description of the object rather than in |
| | 2598 | * the extra status message; this is analogous to the way immovable |
| | 2599 | * items (such as Fixtures) aren't normally listed in the |
| | 2600 | * description of a room. The reason we're not listed if we're the |
| | 2601 | * "major" item in the relationship is that the "major" status |
| | 2602 | * reverses the relationship: when we're the major item, the other |
| | 2603 | * item is described as attached to *us*, rather than vice versa. |
| | 2604 | */ |
| | 2605 | isListedAsAttachedTo(obj) |
| | 2606 | { |
| | 2607 | /* |
| | 2608 | * only list the item if it's not permanently attached, and |
| | 2609 | * we're not the "major" item for the object |
| | 2610 | */ |
| | 2611 | return (!isPermanentlyAttachedTo(obj) && !isMajorItemFor(obj)); |
| | 2612 | } |
| | 2613 | |
| | 2614 | /* |
| | 2615 | * Is 'obj' listed as attached to me when I'm described? If this is |
| | 2616 | * true, then our examineStatus() will list 'obj' among the things |
| | 2617 | * attached to me: "Attached to self is obj." If this is nil, then |
| | 2618 | * 'obj' is not listed among the things attached to me when I'm |
| | 2619 | * described. |
| | 2620 | * |
| | 2621 | * This routine is simply the "major" list counterpart of |
| | 2622 | * isListedAsAttachedTo(). |
| | 2623 | * |
| | 2624 | * By default, we list 'obj' among my attachments if (1) I'm the |
| | 2625 | * "major" item for 'obj', AND (2) 'obj' is listed as attached to |
| | 2626 | * me, as indicated by obj.isListedAsAttachedTo(self). We only list |
| | 2627 | * our minor attachments here, because we list all of our other |
| | 2628 | * listable attachments separately, as the things I'm attached to. |
| | 2629 | * We also only list items that are themselves listable as |
| | 2630 | * attachments, for obvious reasons. |
| | 2631 | */ |
| | 2632 | isListedAsMajorFor(obj) |
| | 2633 | { |
| | 2634 | /* |
| | 2635 | * only list the item if we're the "major" item for the object, |
| | 2636 | * and the object is itself listable as an attachment |
| | 2637 | */ |
| | 2638 | return (isMajorItemFor(obj) && obj.isListedAsAttachedTo(self)); |
| | 2639 | } |
| | 2640 | |
| | 2641 | /* |
| | 2642 | * Can I attach to the given object? This returns true if the other |
| | 2643 | * object is allowable as an attachment, nil if not. |
| | 2644 | * |
| | 2645 | * By default, we look to see if the other side is an Attachable, and |
| | 2646 | * if so, if it overrides canAttachTo(); if so, we'll call its |
| | 2647 | * canAttachTo to ask whether it thinks it can attach to us. If the |
| | 2648 | * other side doesn't override this, we'll simply return nil. This |
| | 2649 | * arrangement is convenient because it means that only one side of |
| | 2650 | * an attachable pair needs to implement this; the other side will |
| | 2651 | * automatically figure it out by calling the first side and relying |
| | 2652 | * on the symmetry of the relationship. |
| | 2653 | */ |
| | 2654 | canAttachTo(obj) |
| | 2655 | { |
| | 2656 | /* |
| | 2657 | * if the other side's an Attachable, and it overrides this |
| | 2658 | * method, call the override; if not, it's by default not one of |
| | 2659 | * our valid attachments |
| | 2660 | */ |
| | 2661 | if (overrides(obj, Attachable, &canAttachTo)) |
| | 2662 | { |
| | 2663 | /* |
| | 2664 | * the other side is an Attachable that defines a specific |
| | 2665 | * attachment rule, so ask the other side if it thinks we're |
| | 2666 | * one of its attachments; by the symmetry of the |
| | 2667 | * relationship, if we're one of its attachments, then it's |
| | 2668 | * one of ours |
| | 2669 | */ |
| | 2670 | return obj.canAttachTo(self); |
| | 2671 | } |
| | 2672 | else |
| | 2673 | { |
| | 2674 | /* |
| | 2675 | * the other side doesn't want to tell us, so we're on our |
| | 2676 | * own; we don't recognize any attachments on our own, so |
| | 2677 | * it's not a valid attachment |
| | 2678 | */ |
| | 2679 | return nil; |
| | 2680 | } |
| | 2681 | } |
| | 2682 | |
| | 2683 | /* |
| | 2684 | * Explain why we can't attach to the given object. This should |
| | 2685 | * simply display an appropriate mesage. We use reportFailure to |
| | 2686 | * flag it as a failure report, but that's not actually required, |
| | 2687 | * since we call this from our 'check' routine, which will mark the |
| | 2688 | * action as having failed even if we don't here. |
| | 2689 | */ |
| | 2690 | explainCannotAttachTo(obj) { reportFailure(&wrongAttachmentMsg); } |
| | 2691 | |
| | 2692 | /* |
| | 2693 | * Is it possible for me to detach from the given object? This asks |
| | 2694 | * whether a given attachment relationship can be dissolved with |
| | 2695 | * DETACH FROM. |
| | 2696 | * |
| | 2697 | * By default, we'll use similar logic to canAttachTo: if the other |
| | 2698 | * object overrides canDetachFrom(), we'll let it make the |
| | 2699 | * determination. Otherwise, we'll return nil if one or the other |
| | 2700 | * side is a PermanentAttachment, true if not. This lets you prevent |
| | 2701 | * detachment by overriding canDetachFrom() on just one side of the |
| | 2702 | * relationship. |
| | 2703 | */ |
| | 2704 | canDetachFrom(obj) |
| | 2705 | { |
| | 2706 | /* if the other object overrides canDetachFrom, defer to it */ |
| | 2707 | if (overrides(obj, Attachable, &canDetachFrom)) |
| | 2708 | { |
| | 2709 | /* let the other side make the judgment */ |
| | 2710 | return obj.canDetachFrom(self); |
| | 2711 | } |
| | 2712 | else |
| | 2713 | { |
| | 2714 | /* |
| | 2715 | * the other side doesn't override it, so assume we can |
| | 2716 | * detach unless one or the other side is a |
| | 2717 | * PermanentAttachment |
| | 2718 | */ |
| | 2719 | return !isPermanentlyAttachedTo(obj); |
| | 2720 | } |
| | 2721 | } |
| | 2722 | |
| | 2723 | /* |
| | 2724 | * Am I permanently attached to the other object? This returns true |
| | 2725 | * if I'm a PermanentAttachment or the other object is. |
| | 2726 | */ |
| | 2727 | isPermanentlyAttachedTo(obj) |
| | 2728 | { |
| | 2729 | /* |
| | 2730 | * if either one of us is a PermanentAttachment, we're |
| | 2731 | * permanently attached to each other |
| | 2732 | */ |
| | 2733 | return ofKind(PermanentAttachment) || obj.ofKind(PermanentAttachment); |
| | 2734 | } |
| | 2735 | |
| | 2736 | |
| | 2737 | /* |
| | 2738 | * A message explaining why we can't detach from the given object. |
| | 2739 | * Note that 'obj' can be nil, because we could be attempting a |
| | 2740 | * DETACH command with no indirect object. |
| | 2741 | */ |
| | 2742 | cannotDetachMsgFor(obj) |
| | 2743 | { |
| | 2744 | /* |
| | 2745 | * if we have an object, it must be the wrong one; otherwise, we |
| | 2746 | * simply can't detach generically, since the object to detach |
| | 2747 | * from wasn't specified, and there's nothing obvious we can |
| | 2748 | * detach from |
| | 2749 | */ |
| | 2750 | return obj != nil ? &wrongDetachmentMsg : &cannotDetachMsg; |
| | 2751 | } |
| | 2752 | |
| | 2753 | /* |
| | 2754 | * Process attachment to a new object. This routine is called on |
| | 2755 | * BOTH the direct and indirect object during the attachment process |
| | 2756 | * - that is, it's called on the direct object with the indirect |
| | 2757 | * object as the argument, and then it's called on the indirect |
| | 2758 | * object with the direct object as the argument. |
| | 2759 | * |
| | 2760 | * This symmetrical handling makes it easy to handle the frequent |
| | 2761 | * cases where the player might say ATTACH X TO Y or ATTACH Y TO X |
| | 2762 | * and mean the same thing either way. Because this method is called |
| | 2763 | * for both X and Y in either phrasing, you can simply choose to |
| | 2764 | * write the handler code in either X or Y - you only have to write |
| | 2765 | * it once, because the handler will be called on each of the |
| | 2766 | * objects, regardless of the phrasing. So, if you choose to |
| | 2767 | * designate X as the official ATTACH handler, write a handleAttach() |
| | 2768 | * method on X, and leave the one on Y doing nothing: during |
| | 2769 | * execution, the X method will do its work, and the Y method will do |
| | 2770 | * nothing, so regardless of phrasing order, the net result will be |
| | 2771 | * the same. |
| | 2772 | * |
| | 2773 | * By default we do nothing. Each instance should override this to |
| | 2774 | * display any extra message and take any extra action needed to |
| | 2775 | * process the attachment status change. Note that the override |
| | 2776 | * doesn't need to worry about managing the attachedObjects list, as |
| | 2777 | * the main action handler does that automatically. |
| | 2778 | * |
| | 2779 | * Note that handleAttach() is always called after both objects have |
| | 2780 | * updated their attachedObjects lists. This means that you can turn |
| | 2781 | * right around and detach the objects here, if you don't want to |
| | 2782 | * leave them attached. |
| | 2783 | */ |
| | 2784 | handleAttach(other) |
| | 2785 | { |
| | 2786 | /* do nothing by default */ |
| | 2787 | } |
| | 2788 | |
| | 2789 | /* |
| | 2790 | * Receive notification that this object or one of its attachments |
| | 2791 | * is being moved to a new container. When an attached object is |
| | 2792 | * moved, we'll call this on the object being moved AND on every |
| | 2793 | * object attached to it. 'movedObj' is the object being moved, and |
| | 2794 | * 'newCont' is the new container it's being moved into. |
| | 2795 | * |
| | 2796 | * By default we do nothing. Instances can override this as needed. |
| | 2797 | * For example, if you wish to enforce a rule that this object and |
| | 2798 | * all of its attached objects share a common direct container, you |
| | 2799 | * could either block the move (by displaying an error and using |
| | 2800 | * 'exit') or run a nested DetachFrom action to sever the attachment |
| | 2801 | * with the object being moved. |
| | 2802 | */ |
| | 2803 | moveWhileAttached(movedObj, newCont) |
| | 2804 | { |
| | 2805 | /* do nothing by default */ |
| | 2806 | } |
| | 2807 | |
| | 2808 | /* |
| | 2809 | * Receive notification that this object or one of its attachments is |
| | 2810 | * being moved in the course of an actor traveling to a new location. |
| | 2811 | * Whenever anyone travels while carrying an attachable object |
| | 2812 | * (directly or indirectly), we'll call this on the object being |
| | 2813 | * moved AND on every object attached to it. 'movedObj' is the |
| | 2814 | * object being carried by the traveling actor, 'traveler' is the |
| | 2815 | * Traveler performing the travel, and 'connector' is the |
| | 2816 | * TravelConnector that the traveler is traversing. |
| | 2817 | * |
| | 2818 | * By default, we do nothing. Instances can override this as needed. |
| | 2819 | */ |
| | 2820 | travelWhileAttached(movedObj, traveler, connector) |
| | 2821 | { |
| | 2822 | /* do nothing by default */ |
| | 2823 | } |
| | 2824 | |
| | 2825 | /* |
| | 2826 | * Handle detachment. This works like handleAttach(), in that this |
| | 2827 | * routine is invoked symmetrically for both sides of a DETACH X FROM |
| | 2828 | * Y commands. |
| | 2829 | * |
| | 2830 | * As with handleAttach(), we do nothing by default, so instances |
| | 2831 | * should override as needed. Note that the override doesn't need to |
| | 2832 | * worry about managing the attachedObjects list, as the main action |
| | 2833 | * handler does that automatically. As with handleAttach(), this is |
| | 2834 | * called after the attachedObjects lists for both objects are |
| | 2835 | * updated. |
| | 2836 | */ |
| | 2837 | handleDetach(other) |
| | 2838 | { |
| | 2839 | /* do nothing by default */ |
| | 2840 | } |
| | 2841 | |
| | 2842 | /* the Lister we use to show our list of attached objects */ |
| | 2843 | attachmentLister = perInstance(new SimpleAttachmentLister(self)) |
| | 2844 | |
| | 2845 | /* |
| | 2846 | * the Lister we use to list the items attached to us (i.e., the |
| | 2847 | * items for which we're the "major" item in the attachment |
| | 2848 | * relationship) |
| | 2849 | */ |
| | 2850 | majorAttachmentLister = perInstance(new MajorAttachmentLister(self)) |
| | 2851 | |
| | 2852 | /* add a list of our attachments to the desription */ |
| | 2853 | examineStatus() |
| | 2854 | { |
| | 2855 | local tab; |
| | 2856 | |
| | 2857 | /* inherit the normal status description */ |
| | 2858 | inherited(); |
| | 2859 | |
| | 2860 | /* get the actor's visual sense table */ |
| | 2861 | tab = gActor.visibleInfoTable(); |
| | 2862 | |
| | 2863 | /* add our list of attachments */ |
| | 2864 | attachmentLister.showList(gActor, self, attachedObjects, |
| | 2865 | 0, 0, tab, nil); |
| | 2866 | |
| | 2867 | /* add our list of major attachments */ |
| | 2868 | majorAttachmentLister.showList(gActor, self, attachedObjects, |
| | 2869 | 0, 0, tab, nil); |
| | 2870 | } |
| | 2871 | |
| | 2872 | /* |
| | 2873 | * Move into a new container. If I'm attached to anything, we'll |
| | 2874 | * notify ourself and our attachments. |
| | 2875 | */ |
| | 2876 | mainMoveInto(newCont) |
| | 2877 | { |
| | 2878 | /* if I'm attached to anything, notify everyone */ |
| | 2879 | if (attachedObjects.length() != 0) |
| | 2880 | { |
| | 2881 | /* notify myself */ |
| | 2882 | moveWhileAttached(self, newCont); |
| | 2883 | |
| | 2884 | /* notify my attachments */ |
| | 2885 | attachedObjects.forEach({x: x.moveWhileAttached(self, newCont)}); |
| | 2886 | } |
| | 2887 | |
| | 2888 | /* inherit the base handling */ |
| | 2889 | inherited(newCont); |
| | 2890 | } |
| | 2891 | |
| | 2892 | /* |
| | 2893 | * Receive notification of travel. If I'm involved in the travel, |
| | 2894 | * and I'm attached to anything, we'll notify ourself and our |
| | 2895 | * attachments. |
| | 2896 | */ |
| | 2897 | beforeTravel(traveler, connector) |
| | 2898 | { |
| | 2899 | /* |
| | 2900 | * If we're traveling with the traveler, and we're attached to |
| | 2901 | * anything, notify everything that's attached. |
| | 2902 | */ |
| | 2903 | if (attachedObjects.length() != 0 |
| | 2904 | && traveler.isTravelerCarrying(self)) |
| | 2905 | { |
| | 2906 | /* notify myself */ |
| | 2907 | travelWhileAttached(self, traveler, connector); |
| | 2908 | |
| | 2909 | /* notify each of my attachments */ |
| | 2910 | attachedObjects.forEach( |
| | 2911 | {x: x.travelWhileAttached(self, traveler, connector)}); |
| | 2912 | } |
| | 2913 | } |
| | 2914 | |
| | 2915 | /* |
| | 2916 | * during initialization, make sure the attachedObjects list is |
| | 2917 | * symmetrical for both sides of the attachment relationship |
| | 2918 | */ |
| | 2919 | initializeThing() |
| | 2920 | { |
| | 2921 | /* do the normal work */ |
| | 2922 | inherited(); |
| | 2923 | |
| | 2924 | /* |
| | 2925 | * check to make sure that each of our attached objects points |
| | 2926 | * back at us |
| | 2927 | */ |
| | 2928 | foreach (local cur in attachedObjects) |
| | 2929 | { |
| | 2930 | /* |
| | 2931 | * if we're not in this one's attachedObjects list, add |
| | 2932 | * ourselves to the list, so that everyone's consistent |
| | 2933 | */ |
| | 2934 | if (cur.attachedObjects.indexOf(self) == nil) |
| | 2935 | cur.attachedObjects += self; |
| | 2936 | } |
| | 2937 | } |
| | 2938 | |
| | 2939 | /* handle attachment on the direct object side */ |
| | 2940 | dobjFor(AttachTo) |
| | 2941 | { |
| | 2942 | /* require that the actor can touch the direct object */ |
| | 2943 | preCond = [touchObj] |
| | 2944 | |
| | 2945 | verify() |
| | 2946 | { |
| | 2947 | /* |
| | 2948 | * it makes sense to attach to anything but myself, or things |
| | 2949 | * we're already attached to |
| | 2950 | */ |
| | 2951 | if (gIobj != nil) |
| | 2952 | { |
| | 2953 | if (isAttachedTo(gIobj)) |
| | 2954 | illogicalAlready(&alreadyAttachedMsg); |
| | 2955 | else if (gIobj == self) |
| | 2956 | illogicalSelf(&cannotAttachToSelfMsg); |
| | 2957 | } |
| | 2958 | } |
| | 2959 | |
| | 2960 | check() |
| | 2961 | { |
| | 2962 | /* only allow it if we can attach to the other object */ |
| | 2963 | if (!canAttachTo(gIobj)) |
| | 2964 | { |
| | 2965 | explainCannotAttachTo(gIobj); |
| | 2966 | exit; |
| | 2967 | } |
| | 2968 | } |
| | 2969 | |
| | 2970 | action() |
| | 2971 | { |
| | 2972 | /* add the other object to our list of attached objects */ |
| | 2973 | attachedObjects += gIobj; |
| | 2974 | |
| | 2975 | /* add our default acknowledgment */ |
| | 2976 | defaultReport(&okayAttachToMsg); |
| | 2977 | |
| | 2978 | /* fire the handleAttach event if we're ready */ |
| | 2979 | maybeHandleAttach(gIobj); |
| | 2980 | } |
| | 2981 | } |
| | 2982 | |
| | 2983 | /* handle attachment on the indirect object side */ |
| | 2984 | iobjFor(AttachTo) |
| | 2985 | { |
| | 2986 | /* |
| | 2987 | * Require that the direct object can touch the indirect object. |
| | 2988 | * This ensures that the two objects to be attached can touch |
| | 2989 | * one another. Note that we don't also require that the actor |
| | 2990 | * be able to touch the indirect object directly, since it's |
| | 2991 | * good enough that (1) the actor can touch the direct object |
| | 2992 | * (which we enforce with the dobj precondition), and (2) the |
| | 2993 | * direct object can touch the indirect object. This allows for |
| | 2994 | * odd things like plugging something into a recessed outlet, |
| | 2995 | * where the recessed bit can't be reached directly but can be |
| | 2996 | * reached using the plug. |
| | 2997 | */ |
| | 2998 | preCond = [dobjTouchObj] |
| | 2999 | |
| | 3000 | verify() |
| | 3001 | { |
| | 3002 | /* |
| | 3003 | * it makes sense to attach to anything but myself, or things |
| | 3004 | * we're already attached to |
| | 3005 | */ |
| | 3006 | if (gDobj != nil) |
| | 3007 | { |
| | 3008 | if (isAttachedTo(gDobj)) |
| | 3009 | illogicalAlready(&alreadyAttachedMsg); |
| | 3010 | else if (gDobj == self) |
| | 3011 | illogicalSelf(&cannotAttachToSelfMsg); |
| | 3012 | } |
| | 3013 | } |
| | 3014 | |
| | 3015 | check() |
| | 3016 | { |
| | 3017 | /* only allow it if we can attach to the other object */ |
| | 3018 | if (!canAttachTo(gDobj)) |
| | 3019 | { |
| | 3020 | explainCannotAttachTo(gDobj); |
| | 3021 | exit; |
| | 3022 | } |
| | 3023 | } |
| | 3024 | |
| | 3025 | action() |
| | 3026 | { |
| | 3027 | /* add the other object to our list of attached objects */ |
| | 3028 | attachedObjects += gDobj; |
| | 3029 | |
| | 3030 | /* fire the handleAttach event if we're ready */ |
| | 3031 | maybeHandleAttach(gIobj); |
| | 3032 | } |
| | 3033 | } |
| | 3034 | |
| | 3035 | /* |
| | 3036 | * Fire the handleAttach event - we'll notify both sides as soon as |
| | 3037 | * both sides are hooked up with each other. This ensures that both |
| | 3038 | * lists are updated before we notify either side, so the ordering |
| | 3039 | * doesn't depend on whether we handle the dobj or iobj first. |
| | 3040 | */ |
| | 3041 | maybeHandleAttach(other) |
| | 3042 | { |
| | 3043 | /* if both lists are hooked up, send the notifications */ |
| | 3044 | if (attachedObjects.indexOf(other) != nil |
| | 3045 | && other.attachedObjects.indexOf(self) != nil) |
| | 3046 | { |
| | 3047 | /* notify our side */ |
| | 3048 | handleAttach(other); |
| | 3049 | |
| | 3050 | /* notify the other side */ |
| | 3051 | other.handleAttach(self); |
| | 3052 | } |
| | 3053 | } |
| | 3054 | |
| | 3055 | /* handle simple, unspecified detachment (DETACH OBJECT) */ |
| | 3056 | dobjFor(Detach) |
| | 3057 | { |
| | 3058 | verify() |
| | 3059 | { |
| | 3060 | /* if I'm not attached to anything, this is illogical */ |
| | 3061 | if (attachedObjects.length() == 0) |
| | 3062 | illogicalAlready(cannotDetachMsgFor(nil)); |
| | 3063 | } |
| | 3064 | action() |
| | 3065 | { |
| | 3066 | local lst; |
| | 3067 | |
| | 3068 | /* get the non-permanent attachment subset */ |
| | 3069 | lst = getNonPermanentAttachments(); |
| | 3070 | |
| | 3071 | /* check what that leaves us */ |
| | 3072 | if (lst.length() == 0) |
| | 3073 | { |
| | 3074 | /* |
| | 3075 | * we're not attached to anything that we can detach |
| | 3076 | * from, so simply report that we can't detach |
| | 3077 | * generically |
| | 3078 | */ |
| | 3079 | reportFailure(cannotDetachMsgFor(nil)); |
| | 3080 | } |
| | 3081 | else if (lst.length() == 1) |
| | 3082 | { |
| | 3083 | /* |
| | 3084 | * we have exactly one attached object from which we can |
| | 3085 | * detach, so they must want to detach from that - |
| | 3086 | * process this as DETACH FROM my one attached object |
| | 3087 | */ |
| | 3088 | replaceAction(DetachFrom, self, lst[1]); |
| | 3089 | } |
| | 3090 | else |
| | 3091 | { |
| | 3092 | /* |
| | 3093 | * we have more than one detachable attachment, so ask |
| | 3094 | * which one they mean |
| | 3095 | */ |
| | 3096 | askForIobj(DetachFrom); |
| | 3097 | } |
| | 3098 | } |
| | 3099 | } |
| | 3100 | |
| | 3101 | /* handle detaching me from a specific other object */ |
| | 3102 | dobjFor(DetachFrom) |
| | 3103 | { |
| | 3104 | verify() |
| | 3105 | { |
| | 3106 | /* it only makes sense to try detaching us from our attachments */ |
| | 3107 | if (gIobj != nil && !isAttachedTo(gIobj)) |
| | 3108 | illogicalAlready(¬AttachedToMsg); |
| | 3109 | } |
| | 3110 | check() |
| | 3111 | { |
| | 3112 | /* make sure I'm allowed to detach from the given object */ |
| | 3113 | if (!canDetachFrom(gIobj)) |
| | 3114 | { |
| | 3115 | reportFailure(cannotDetachMsgFor(gIobj)); |
| | 3116 | exit; |
| | 3117 | } |
| | 3118 | } |
| | 3119 | action() |
| | 3120 | { |
| | 3121 | /* remove the other object from our list of attached objects */ |
| | 3122 | attachedObjects -= gIobj; |
| | 3123 | |
| | 3124 | /* add our default acknowledgment */ |
| | 3125 | defaultReport(&okayDetachFromMsg); |
| | 3126 | |
| | 3127 | /* fire the handleDetach event if appropriate */ |
| | 3128 | maybeHandleDetach(gIobj); |
| | 3129 | } |
| | 3130 | } |
| | 3131 | |
| | 3132 | /* handle detachment on the indirect object side */ |
| | 3133 | iobjFor(DetachFrom) |
| | 3134 | { |
| | 3135 | verify() |
| | 3136 | { |
| | 3137 | /* it only makes sense to try detaching my attachments */ |
| | 3138 | if (gDobj == nil) |
| | 3139 | { |
| | 3140 | /* |
| | 3141 | * we don't know the dobj yet, but we can check the |
| | 3142 | * tentative list for the possible set |
| | 3143 | */ |
| | 3144 | if (gTentativeDobj |
| | 3145 | .indexWhich({x: isAttachedTo(x.obj_)}) == nil) |
| | 3146 | illogicalAlready(¬AttachedToMsg); |
| | 3147 | } |
| | 3148 | else if (gDobj != nil && !isAttachedTo(gDobj)) |
| | 3149 | illogicalAlready(¬AttachedToMsg); |
| | 3150 | } |
| | 3151 | check() |
| | 3152 | { |
| | 3153 | /* make sure I'm allowed to detach from the given object */ |
| | 3154 | if (!canDetachFrom(gDobj)) |
| | 3155 | { |
| | 3156 | reportFailure(cannotDetachMsgFor(gDobj)); |
| | 3157 | exit; |
| | 3158 | } |
| | 3159 | } |
| | 3160 | action() |
| | 3161 | { |
| | 3162 | /* remove the other object from our list of attached objects */ |
| | 3163 | attachedObjects -= gDobj; |
| | 3164 | |
| | 3165 | /* fire the handleDetach event if appropriate */ |
| | 3166 | maybeHandleDetach(gDobj); |
| | 3167 | } |
| | 3168 | } |
| | 3169 | |
| | 3170 | /* |
| | 3171 | * Fire the handleDetach event - we'll notify both sides as soon as |
| | 3172 | * both sides are un-hooked up. This ensures that both lists are |
| | 3173 | * updated before we notify either side, so the ordering doesn't |
| | 3174 | * depend on whether we handle the dobj or iobj first. |
| | 3175 | */ |
| | 3176 | maybeHandleDetach(other) |
| | 3177 | { |
| | 3178 | /* if both lists are un-hooked up, send the notifications */ |
| | 3179 | if (attachedObjects.indexOf(other) == nil |
| | 3180 | && other.attachedObjects.indexOf(self) == nil) |
| | 3181 | { |
| | 3182 | /* notify our side */ |
| | 3183 | handleDetach(other); |
| | 3184 | |
| | 3185 | /* notify the other side */ |
| | 3186 | other.handleDetach(self); |
| | 3187 | } |
| | 3188 | } |
| | 3189 | |
| | 3190 | /* |
| | 3191 | * TAKE X FROM Y is the same as DETACH X FROM Y for things we're |
| | 3192 | * attached to, but use the inherited handling otherwise |
| | 3193 | */ |
| | 3194 | dobjFor(TakeFrom) |
| | 3195 | { |
| | 3196 | verify() |
| | 3197 | { |
| | 3198 | /* |
| | 3199 | * use the inherited handling only if we're not attached - |
| | 3200 | * if we're attached, consider it logical, overriding any |
| | 3201 | * containment relationship check we might otherwise make |
| | 3202 | */ |
| | 3203 | if (gIobj == nil || !isAttachedTo(gIobj)) |
| | 3204 | inherited(); |
| | 3205 | } |
| | 3206 | check() |
| | 3207 | { |
| | 3208 | /* inherit the default check only if we're not attached */ |
| | 3209 | if (!isAttachedTo(gIobj)) |
| | 3210 | inherited(); |
| | 3211 | } |
| | 3212 | action() |
| | 3213 | { |
| | 3214 | /* |
| | 3215 | * if we're attached, change this into a DETACH FROM action; |
| | 3216 | * otherwise, use the inherited TAKE FROM handling |
| | 3217 | */ |
| | 3218 | if (isAttachedTo(gIobj)) |
| | 3219 | replaceAction(DetachFrom, self, gIobj); |
| | 3220 | else |
| | 3221 | inherited(); |
| | 3222 | } |
| | 3223 | } |
| | 3224 | iobjFor(TakeFrom) |
| | 3225 | { |
| | 3226 | verify() |
| | 3227 | { |
| | 3228 | /* use the inherited handling only if we're not attached */ |
| | 3229 | if (gDobj == nil || !isAttachedTo(gDobj)) |
| | 3230 | inherited(); |
| | 3231 | } |
| | 3232 | check() |
| | 3233 | { |
| | 3234 | /* inherit the default check only if we're not attached */ |
| | 3235 | if (!isAttachedTo(gDobj)) |
| | 3236 | inherited(); |
| | 3237 | } |
| | 3238 | action() |
| | 3239 | { |
| | 3240 | /* inherit the default action only if we're not attached */ |
| | 3241 | if (!isAttachedTo(gDobj)) |
| | 3242 | inherited(); |
| | 3243 | } |
| | 3244 | } |
| | 3245 | ; |
| | 3246 | |
| | 3247 | /* |
| | 3248 | * An Attachable-specific precondition: the Attachable isn't already |
| | 3249 | * attached to something else. This can be added to the preCond list for |
| | 3250 | * an Attachable (for iobjFor(AttachTo) and dobjFor(AttachTo)) to ensure |
| | 3251 | * that any existing attachment is removed before a new attachment is |
| | 3252 | * formed. This is useful when the Attachable can connect to only one |
| | 3253 | * thing at a time. |
| | 3254 | */ |
| | 3255 | objNotAttached: PreCondition |
| | 3256 | checkPreCondition(obj, allowImplicit) |
| | 3257 | { |
| | 3258 | /* |
| | 3259 | * if we don't already have any non-permanent attachments, we're |
| | 3260 | * fine (as we don't require removing permanent attachments) |
| | 3261 | */ |
| | 3262 | if (obj.attachedObjects.indexWhich( |
| | 3263 | {x: !obj.isPermanentlyAttachedTo(x)}) == nil) |
| | 3264 | return nil; |
| | 3265 | |
| | 3266 | /* |
| | 3267 | * Try an implicit Detach command. It should be safe to use the |
| | 3268 | * form that doesn't specify what we're detaching from, since the |
| | 3269 | * whole point of this condition is that the object can have only |
| | 3270 | * one non-permanent attachment, hence the vague Detach handler |
| | 3271 | * should be able to figure out what we mean. |
| | 3272 | */ |
| | 3273 | if (allowImplicit && tryImplicitAction(Detach, obj)) |
| | 3274 | { |
| | 3275 | /* if we're still attached to anything, we failed, so abort */ |
| | 3276 | if (obj.attachedObjects.indexWhich( |
| | 3277 | {x: !obj.isPermanentlyAttachedTo(x)}) != nil) |
| | 3278 | exit; |
| | 3279 | |
| | 3280 | /* tell the caller we executed an implied action */ |
| | 3281 | return true; |
| | 3282 | } |
| | 3283 | |
| | 3284 | /* we must detach first */ |
| | 3285 | reportFailure(&mustDetachMsg, obj); |
| | 3286 | exit; |
| | 3287 | } |
| | 3288 | ; |
| | 3289 | |
| | 3290 | /* |
| | 3291 | * A "nearby" attachable is a subclass of Attachable that adds a |
| | 3292 | * requirement that the attached objects be in a given location. By |
| | 3293 | * default, we simply require that they have a common immediate |
| | 3294 | * container, but this can be overridden so that each object's location |
| | 3295 | * is negotiated separately. This is a simple and effective pattern that |
| | 3296 | * avoids many of the potential anomalies with attachment (see the |
| | 3297 | * Attachable comments for examples). |
| | 3298 | * |
| | 3299 | * In AttachTo actions, we enforce the nearby requirement with a |
| | 3300 | * precondition requiring the direct object to be in the same immediate |
| | 3301 | * container as the indirect object, and vice versa. In |
| | 3302 | * moveWhileAttached(), we enforce the rule by detaching the objects if |
| | 3303 | * one is being moved away from the other's immediate container. |
| | 3304 | */ |
| | 3305 | class NearbyAttachable: Attachable |
| | 3306 | dobjFor(AttachTo) |
| | 3307 | { |
| | 3308 | /* require that the objects be in the negotiated locations */ |
| | 3309 | preCond = (inherited() + nearbyAttachableCond) |
| | 3310 | } |
| | 3311 | iobjFor(AttachTo) |
| | 3312 | { |
| | 3313 | /* require that the objects be in the negotiated locations */ |
| | 3314 | preCond = (inherited() + nearbyAttachableCond) |
| | 3315 | } |
| | 3316 | |
| | 3317 | /* |
| | 3318 | * Get the target locations for attaching to the given other object. |
| | 3319 | * The "target locations" are the locations where the objects are |
| | 3320 | * required to be in order to carry out the ATTACH command to attach |
| | 3321 | * this object to the other object (or vice versa). |
| | 3322 | * |
| | 3323 | * This method returns a list with three elements. The first |
| | 3324 | * element is the target location for 'self', and the second is the |
| | 3325 | * target location for 'other', the object we're attaching to. The |
| | 3326 | * third element is an integer giving the priority; a higher number |
| | 3327 | * means higher priority. |
| | 3328 | * |
| | 3329 | * The priority is an arbitrary value that we use to determine which |
| | 3330 | * of the two objects involved in the attach gets to decide on the |
| | 3331 | * target locations. We call this method on both of the two objects |
| | 3332 | * being attached to one another, then we use the target locations |
| | 3333 | * returned by the object that claims the higher priority. If the |
| | 3334 | * two priorities are equal, we pick one arbitrarily. |
| | 3335 | * |
| | 3336 | * The default implementation chooses my own immediate container as |
| | 3337 | * the target location for both objects. However, if the other |
| | 3338 | * object is non-portable, we'll choose its immediate location |
| | 3339 | * instead, since we obviously can't move it to our container. |
| | 3340 | */ |
| | 3341 | getNearbyAttachmentLocs(other) |
| | 3342 | { |
| | 3343 | /* |
| | 3344 | * If the other object is portable, use our immediate container |
| | 3345 | * as the proposed location for both objects; otherwise, use the |
| | 3346 | * other object's immediate container. In any case, use a low |
| | 3347 | * priority, since we're just the default base class |
| | 3348 | * implementation; any override will generally have higher |
| | 3349 | * priority. |
| | 3350 | */ |
| | 3351 | if (other.ofKind(NonPortable)) |
| | 3352 | { |
| | 3353 | /* the other can't be moved, so use its location */ |
| | 3354 | return [other.location, other.location, 0]; |
| | 3355 | } |
| | 3356 | else |
| | 3357 | { |
| | 3358 | /* |
| | 3359 | * the other can be moved, so use our own location, in a |
| | 3360 | * paraphrase of the realty agent's favorite mantra |
| | 3361 | */ |
| | 3362 | return [location, location, 0]; |
| | 3363 | } |
| | 3364 | } |
| | 3365 | |
| | 3366 | /* when an attached object is being moved, detach the objects */ |
| | 3367 | moveWhileAttached(movedObj, newCont) |
| | 3368 | { |
| | 3369 | /* |
| | 3370 | * If I'm the one being moved, detach me from all of my |
| | 3371 | * non-permanent attachments; otherwise, just detach me from the |
| | 3372 | * other object, since it's the only one of my attachments being |
| | 3373 | * moved. |
| | 3374 | */ |
| | 3375 | if (movedObj == self) |
| | 3376 | { |
| | 3377 | /* I'm being moved - detach from everything */ |
| | 3378 | foreach (local cur in attachedObjects) |
| | 3379 | { |
| | 3380 | /* |
| | 3381 | * If we're not permanently attached to this one, and |
| | 3382 | * it's not inside me, detach from it. We don't need to |
| | 3383 | * detach from objects inside this one, because they'll |
| | 3384 | * be moved along with us automatically. |
| | 3385 | */ |
| | 3386 | if (!cur.isIn(self) && !isPermanentlyAttachedTo(cur)) |
| | 3387 | nestedDetachFrom(cur); |
| | 3388 | } |
| | 3389 | } |
| | 3390 | else |
| | 3391 | { |
| | 3392 | /* just detach from the one object */ |
| | 3393 | nestedDetachFrom(movedObj); |
| | 3394 | } |
| | 3395 | } |
| | 3396 | |
| | 3397 | /* perform a nested DetachFrom action on the given object */ |
| | 3398 | nestedDetachFrom(obj) |
| | 3399 | { |
| | 3400 | /* run the nested DetachFrom as an implied action */ |
| | 3401 | tryImplicitAction(DetachFrom, self, obj); |
| | 3402 | |
| | 3403 | /* |
| | 3404 | * if we're still attached to this object, the implied command |
| | 3405 | * must have failed, so abort the entire action |
| | 3406 | */ |
| | 3407 | if (attachedObjects.indexOf(obj) != nil) |
| | 3408 | exit; |
| | 3409 | } |
| | 3410 | ; |
| | 3411 | |
| | 3412 | /* |
| | 3413 | * Precondition for nearby-attachables. This ensures that the two |
| | 3414 | * objects being attached are in their negotiated locations. |
| | 3415 | */ |
| | 3416 | nearbyAttachableCond: PreCondition |
| | 3417 | /* carry out the precondition */ |
| | 3418 | checkPreCondition(obj, allowImplicit) |
| | 3419 | { |
| | 3420 | local dobjProposal, iobjProposal; |
| | 3421 | local dobjTargetLoc, iobjTargetLoc; |
| | 3422 | local iobjRet, dobjRet; |
| | 3423 | |
| | 3424 | /* |
| | 3425 | * Ask each of the NearbyAttachable objects (the direct and |
| | 3426 | * indirect objects) what it thinks. If an object isn't a |
| | 3427 | * NearbyAttachable, it won't have any opinion, so use a |
| | 3428 | * placeholder result with an extremely negative priority |
| | 3429 | * (ensuring that it won't be chosen). In order for this |
| | 3430 | * precondition to have been triggered, one or the other of the |
| | 3431 | * objects must have been a nearby-attachable. |
| | 3432 | */ |
| | 3433 | dobjProposal = (gDobj.ofKind(NearbyAttachable) |
| | 3434 | ? gDobj.getNearbyAttachmentLocs(gIobj) |
| | 3435 | : [nil, nil, -2147483648]); |
| | 3436 | iobjProposal = (gIobj.ofKind(NearbyAttachable) |
| | 3437 | ? gIobj.getNearbyAttachmentLocs(gDobj) |
| | 3438 | : [nil, nil, -2147483648]); |
| | 3439 | |
| | 3440 | /* |
| | 3441 | * If the direct object claims higher priority, use its |
| | 3442 | * attachment locations; otherwise, use the direct object's |
| | 3443 | * locations. (This means that we take the indirect object's |
| | 3444 | * proposed locations if the priorites are equal.) |
| | 3445 | */ |
| | 3446 | if (dobjProposal[3] > iobjProposal[3]) |
| | 3447 | { |
| | 3448 | /* the direct object claims higher priority, so use its results */ |
| | 3449 | dobjTargetLoc = dobjProposal[1]; |
| | 3450 | iobjTargetLoc = dobjProposal[2]; |
| | 3451 | } |
| | 3452 | else |
| | 3453 | { |
| | 3454 | /* |
| | 3455 | * The direct object doesn't have a higher priority, so use |
| | 3456 | * the indirect object's results. Note that the iobj |
| | 3457 | * results list has the iobj in the first position, since it |
| | 3458 | * was the 'self' when we asked it for its proposal. |
| | 3459 | */ |
| | 3460 | dobjTargetLoc = iobjProposal[2]; |
| | 3461 | iobjTargetLoc = iobjProposal[1]; |
| | 3462 | } |
| | 3463 | |
| | 3464 | /* carry out the pair of moves as needed */ |
| | 3465 | dobjRet = moveObject(gDobj, dobjTargetLoc, allowImplicit); |
| | 3466 | iobjRet = moveObject(gIobj, iobjTargetLoc, allowImplicit); |
| | 3467 | |
| | 3468 | /* |
| | 3469 | * Return the indication of whether or not we carried out an |
| | 3470 | * implied command. (Note that we can't call moveObject in this |
| | 3471 | * 'return' expression directly, because of the short-circuit |
| | 3472 | * behavior of the '||' operator. We must call both, even if |
| | 3473 | * both carry out an action.) |
| | 3474 | */ |
| | 3475 | return (dobjRet || iobjRet); |
| | 3476 | } |
| | 3477 | |
| | 3478 | /* carry out an implied action to move an object to a location */ |
| | 3479 | moveObject(obj, loc, allowImplicit) |
| | 3480 | { |
| | 3481 | /* if the object is already there, we have nothing to do */ |
| | 3482 | if (obj.location == loc) |
| | 3483 | return nil; |
| | 3484 | |
| | 3485 | /* try the implied move */ |
| | 3486 | if (allowImplicit && loc.tryMovingObjInto(obj)) |
| | 3487 | { |
| | 3488 | /* make sure it worked */ |
| | 3489 | if (obj.location != loc) |
| | 3490 | exit; |
| | 3491 | |
| | 3492 | /* we performed an implied action */ |
| | 3493 | return true; |
| | 3494 | } |
| | 3495 | |
| | 3496 | /* we can't move it - report failure and abort */ |
| | 3497 | loc.mustMoveObjInto(obj); |
| | 3498 | exit; |
| | 3499 | } |
| | 3500 | ; |
| | 3501 | |
| | 3502 | /* |
| | 3503 | * A PlugAttachable is a mix-in class that turns PLUG INTO into ATTACH TO |
| | 3504 | * and UNPLUG FROM into DETACH FROM. This can be combined with |
| | 3505 | * Attachable or an Attachable subclass for objects that can be attached |
| | 3506 | * with PLUG INTO commands. |
| | 3507 | */ |
| | 3508 | class PlugAttachable: object |
| | 3509 | /* PLUG IN - to what? */ |
| | 3510 | dobjFor(PlugIn) |
| | 3511 | { |
| | 3512 | verify() { } |
| | 3513 | action() { askForIobj(PlugInto); } |
| | 3514 | } |
| | 3515 | |
| | 3516 | /* PLUG INTO is the same as ATTACH TO for us */ |
| | 3517 | dobjFor(PlugInto) remapTo(AttachTo, self, IndirectObject) |
| | 3518 | iobjFor(PlugInto) remapTo(AttachTo, DirectObject, self) |
| | 3519 | |
| | 3520 | /* UNPLUG FROM is the same as DETACH FROM */ |
| | 3521 | dobjFor(Unplug) remapTo(Detach, self) |
| | 3522 | dobjFor(UnplugFrom) remapTo(DetachFrom, self, IndirectObject) |
| | 3523 | iobjFor(UnplugFrom) remapTo(DetachFrom, DirectObject, self) |
| | 3524 | ; |
| | 3525 | |
| | 3526 | /* ------------------------------------------------------------------------ */ |
| | 3527 | /* |
| | 3528 | * Permanent attachments. This class is for things that are described |
| | 3529 | * in the story text as attached to one another, but which can never be |
| | 3530 | * separated. This is a mix-in class that can be combined with a Thing |
| | 3531 | * subclass. |
| | 3532 | * |
| | 3533 | * Descriptions of attachment tend to invite the player to try detaching |
| | 3534 | * the parts; the purpose of this class is to provide responses that are |
| | 3535 | * better than the defaults. A good custom message for this class |
| | 3536 | * should usually acknowledge the attachment relationship, and explain |
| | 3537 | * why the parts can't be separated. |
| | 3538 | * |
| | 3539 | * There are two ways to express the attachment relationship. |
| | 3540 | * |
| | 3541 | * First, the more flexible way: in each PermanentAttachment object, |
| | 3542 | * define the 'attachedObjects' property to contain a list of the |
| | 3543 | * attached objects. All of those other attached objects should usually |
| | 3544 | * be PermanentAttachment objects themselves, because the real-world |
| | 3545 | * relationship we're modeling is obviously symmetrical. Because of the |
| | 3546 | * symmetrical relationship, it's only necessary to include the list |
| | 3547 | * entry on one side of a pair of attached objects - each side will |
| | 3548 | * automatically link itself to the other at start-up if it appears in |
| | 3549 | * the other's attachedObjects list. |
| | 3550 | * |
| | 3551 | * Second, the really easy way: if one of the attached objects is |
| | 3552 | * directly inside the other (which often happens for permanent |
| | 3553 | * attachments, because one is a component of the other), make the |
| | 3554 | * parent a PermanentAttachment, make the inner one a |
| | 3555 | * PermanentAttachmentChild, and you're done. The two will |
| | 3556 | * automatically link up their attachment lists at start-up. |
| | 3557 | * |
| | 3558 | * Note that this is a subclass of Attachable. Note also that a |
| | 3559 | * PermanentAttachment can be freely combined with a regular Attachable; |
| | 3560 | * for example, you could create a rope with a hook permanently |
| | 3561 | * attached, but stil allow the rope to be attached to other things as |
| | 3562 | * well: you'd make the rope a regular Attachable, and make the hook a |
| | 3563 | * PermanentAttachment. The hook would be unremovable because of its |
| | 3564 | * permanent status, and this would symmetrical prevent the rope from |
| | 3565 | * being removed from the hook. But the rope could still be attached to |
| | 3566 | * and detached from other objects. |
| | 3567 | */ |
| | 3568 | class PermanentAttachment: Attachable |
| | 3569 | /* |
| | 3570 | * Get the message explaining why we can't detach from 'obj'. |
| | 3571 | * |
| | 3572 | * By default, if our container is also a PermanentAttachment, and |
| | 3573 | * we're attached to it, we'll simply return its message. This |
| | 3574 | * makes it really easy to define symmetrical permanent attachment |
| | 3575 | * relationships using containment, since all you have to do is make |
| | 3576 | * the container and the child both be PermanentAttachments, and |
| | 3577 | * then just define the cannot-detach message in the container. If |
| | 3578 | * the container isn't a PermanentAttachment, or we're not attached |
| | 3579 | * to it, we'll return our default library message. |
| | 3580 | */ |
| | 3581 | cannotDetachMsgFor(obj) |
| | 3582 | { |
| | 3583 | if (location != nil |
| | 3584 | && location.ofKind(PermanentAttachment) |
| | 3585 | && isAttachedTo(location)) |
| | 3586 | return location.cannotDetachMsgFor(obj); |
| | 3587 | else |
| | 3588 | return baseCannotDetachMsg; |
| | 3589 | } |
| | 3590 | |
| | 3591 | /* basic message to use when we try to detach something from self */ |
| | 3592 | baseCannotDetachMsg = &cannotDetachPermanentMsg |
| | 3593 | ; |
| | 3594 | |
| | 3595 | /* |
| | 3596 | * A permanent attachment "child" - this is an attachment that's |
| | 3597 | * explicitly attached to its container object. This is a convenient |
| | 3598 | * way of setting up an attachment relationship between container and |
| | 3599 | * contents when the contents object isn't a Component. |
| | 3600 | */ |
| | 3601 | class PermanentAttachmentChild: PermanentAttachment |
| | 3602 | /* we're attached directly to our container */ |
| | 3603 | attachedObjects = perInstance([location]) |
| | 3604 | ; |
| | 3605 | |
| | 3606 | /* ------------------------------------------------------------------------ */ |
| | 3607 | /* |
| | 3608 | * A mix-in class for objects that don't come into play until some |
| | 3609 | * future event. This class lets us initialize these objects with their |
| | 3610 | * *eventual* location, using the standard '+' syntax, but they won't |
| | 3611 | * actually appear in the given location until later in the game. |
| | 3612 | * During pre-initialization, we'll remember the starting location, then |
| | 3613 | * set the actual location to nil; later, the object can be easily moved |
| | 3614 | * to its eventual location by calling makePresent(). |
| | 3615 | */ |
| | 3616 | class PresentLater: object |
| | 3617 | /* |
| | 3618 | * My "key" - this is an optional property you can add to a |
| | 3619 | * PresentLater object to associate it with a group of objects. You |
| | 3620 | * can then use makePresentByKey() to move every object with a given |
| | 3621 | * key into the game world at once. This is useful when an event |
| | 3622 | * triggers a whole set of objects to come into the game world: |
| | 3623 | * rather than having to write a method that calls makePresent() on |
| | 3624 | * each of the related objects individually, you can simply give each |
| | 3625 | * related object the same key value, then call makePresentByKey() on |
| | 3626 | * that key. |
| | 3627 | * |
| | 3628 | * You don't need to define this for an object unless you want to use |
| | 3629 | * makePresentByKey() with the object. |
| | 3630 | */ |
| | 3631 | plKey = nil |
| | 3632 | |
| | 3633 | /* |
| | 3634 | * Flag: are we present initially? By default, we're only present |
| | 3635 | * later, as that's the whole point. In some cases, though, we have |
| | 3636 | * objects that come and go, but start out present. Setting this |
| | 3637 | * property to true makes the object present initially, but still |
| | 3638 | * allows it to come and go using the standard PresentLater |
| | 3639 | * mechanisms. |
| | 3640 | */ |
| | 3641 | initiallyPresent = nil |
| | 3642 | |
| | 3643 | initializeLocation() |
| | 3644 | { |
| | 3645 | /* |
| | 3646 | * Save the initial location for later, and then clear out the |
| | 3647 | * current location. We want to start out being out of the game, |
| | 3648 | * but remember where we'll appear when called upon. To |
| | 3649 | * accommodate MultiLoc objects, check locationList first. |
| | 3650 | */ |
| | 3651 | if (locationList != nil) |
| | 3652 | { |
| | 3653 | /* save the location list */ |
| | 3654 | eventualLocation = locationList; |
| | 3655 | |
| | 3656 | /* |
| | 3657 | * clear my location list if I'm not initially present; if I |
| | 3658 | * am initially present, inherit the normal initialization |
| | 3659 | */ |
| | 3660 | if (!initiallyPresent) |
| | 3661 | locationList = []; |
| | 3662 | else |
| | 3663 | inherited(); |
| | 3664 | } |
| | 3665 | else |
| | 3666 | { |
| | 3667 | /* save my eventual location */ |
| | 3668 | eventualLocation = location; |
| | 3669 | |
| | 3670 | /* |
| | 3671 | * clear my location if I'm not initially present; if I am |
| | 3672 | * present initially, inherit the normal set-up |
| | 3673 | */ |
| | 3674 | if (!initiallyPresent) |
| | 3675 | location = nil; |
| | 3676 | else |
| | 3677 | inherited(); |
| | 3678 | } |
| | 3679 | } |
| | 3680 | |
| | 3681 | /* bring the object into the game world in its eventual location(s) */ |
| | 3682 | makePresent() |
| | 3683 | { |
| | 3684 | local pc; |
| | 3685 | |
| | 3686 | /* |
| | 3687 | * If we have a list, add ourself to each location in the list; |
| | 3688 | * otherwise, simply move ourself to the single location. |
| | 3689 | */ |
| | 3690 | if (eventualLocation != nil && eventualLocation.ofKind(Collection)) |
| | 3691 | eventualLocation.forEach({loc: moveIntoAdd(loc)}); |
| | 3692 | else |
| | 3693 | moveInto(eventualLocation); |
| | 3694 | |
| | 3695 | /* if the player character can now see me, mark me as seen */ |
| | 3696 | pc = gPlayerChar; |
| | 3697 | if (pc.canSee(self)) |
| | 3698 | { |
| | 3699 | /* mark me as seen */ |
| | 3700 | pc.setHasSeen(self); |
| | 3701 | |
| | 3702 | /* mark my visible contents as seen */ |
| | 3703 | setContentsSeenBy(pc.visibleInfoTable(), pc); |
| | 3704 | } |
| | 3705 | } |
| | 3706 | |
| | 3707 | /* |
| | 3708 | * make myself present if the given condition is true; otherwise, |
| | 3709 | * remove me from the game world (i.e. move me into nil) |
| | 3710 | */ |
| | 3711 | makePresentIf(cond) |
| | 3712 | { |
| | 3713 | if (cond) |
| | 3714 | makePresent(); |
| | 3715 | else |
| | 3716 | moveInto(nil); |
| | 3717 | } |
| | 3718 | |
| | 3719 | /* |
| | 3720 | * Bring every PresentLater object with the given key into the game. |
| | 3721 | * Note that this is a "class" method that you call on PresentLater |
| | 3722 | * itself: |
| | 3723 | * |
| | 3724 | * PresentLater.makePresentByKey('foo'); |
| | 3725 | */ |
| | 3726 | makePresentByKey(key) |
| | 3727 | { |
| | 3728 | /* |
| | 3729 | * scan every PresentLater object, and move each one with the |
| | 3730 | * given key into the game |
| | 3731 | */ |
| | 3732 | forEachInstance(PresentLater, new function(obj) { |
| | 3733 | if (obj.plKey == key) |
| | 3734 | obj.makePresent(); |
| | 3735 | }); |
| | 3736 | } |
| | 3737 | |
| | 3738 | /* |
| | 3739 | * Bring every PresentLater object with the given key into the game, |
| | 3740 | * or move every one out of the game, according to the condition |
| | 3741 | * 'cond'. |
| | 3742 | * |
| | 3743 | * If 'cond' is a function pointer, we'll invoke it once per object |
| | 3744 | * with the given key, passing the object as the parameter, and use |
| | 3745 | * the return value as the in game/out of game setting. For example, |
| | 3746 | * if you wanted to show every object with key 'foo' AND with the |
| | 3747 | * property 'showObj' set to true, you could write this: |
| | 3748 | * |
| | 3749 | * PresentLater.makePresentByKeyIf('foo', {x: x.showObj}); |
| | 3750 | * |
| | 3751 | * Note that this is a "class" method that you call on PresentLater |
| | 3752 | * itself. |
| | 3753 | */ |
| | 3754 | makePresentByKeyIf(key, cond) |
| | 3755 | { |
| | 3756 | /* |
| | 3757 | * scan every PresentLater object, check each one's key, and make |
| | 3758 | * each one with the given key present |
| | 3759 | */ |
| | 3760 | forEachInstance(PresentLater, new function(obj) { |
| | 3761 | /* consider this object if its key matches */ |
| | 3762 | if (obj.plKey == key) |
| | 3763 | { |
| | 3764 | local flag = cond; |
| | 3765 | |
| | 3766 | /* |
| | 3767 | * evaluate the condition - if it's a function pointer, |
| | 3768 | * invoke it on the current object, otherwise just take |
| | 3769 | * it as a pre-evaluated condition value |
| | 3770 | */ |
| | 3771 | if (dataTypeXlat(cond) == TypeFuncPtr) |
| | 3772 | flag = (cond)(obj); |
| | 3773 | |
| | 3774 | /* show or hide the object according to the condition */ |
| | 3775 | obj.makePresentIf(flag); |
| | 3776 | } |
| | 3777 | }); |
| | 3778 | } |
| | 3779 | |
| | 3780 | /* our eventual location */ |
| | 3781 | eventualLocation = nil |
| | 3782 | ; |
| | 3783 | |