| | 1 | /* Copyright (c) 2000, 2002 Michael J. Roberts. All Rights Reserved. */ |
| | 2 | /* |
| | 3 | * adv3.t - TADS 3 library main source file |
| | 4 | */ |
| | 5 | |
| | 6 | /* include the library header */ |
| | 7 | #include "adv3.h" |
| | 8 | |
| | 9 | |
| | 10 | /* ------------------------------------------------------------------------ */ |
| | 11 | /* |
| | 12 | * Library Pre-Initializer. This object performs the following |
| | 13 | * initialization operations immediately after compilation is completed: |
| | 14 | * |
| | 15 | * - adds each defined Thing to its container's contents list |
| | 16 | * |
| | 17 | * - adds each defined Sense to the global sense list |
| | 18 | * |
| | 19 | * This object is named so that other libraries and/or user code can |
| | 20 | * create initialization order dependencies upon it. |
| | 21 | */ |
| | 22 | adv3LibPreinit: PreinitObject |
| | 23 | execute() |
| | 24 | { |
| | 25 | /* |
| | 26 | * visit every Thing, and move each item into its location's |
| | 27 | * contents list |
| | 28 | */ |
| | 29 | forEachInstance(Thing, { obj: obj.initializeLocation() }); |
| | 30 | |
| | 31 | /* |
| | 32 | * visit every Sense, and add each one into the global sense |
| | 33 | * list |
| | 34 | */ |
| | 35 | forEachInstance(Sense, { obj: libGlobal.allSenses += obj }); |
| | 36 | |
| | 37 | /* initialize each Actor */ |
| | 38 | forEachInstance(Actor, { obj: obj.initializeActor() }); |
| | 39 | |
| | 40 | /* initialize the globals */ |
| | 41 | libGlobal.initialize(); |
| | 42 | } |
| | 43 | ; |
| | 44 | |
| | 45 | /* |
| | 46 | * Library Initializer. This object performs the following |
| | 47 | * initialization operations each time the game is started: |
| | 48 | * |
| | 49 | * - sets up the library's default output function |
| | 50 | */ |
| | 51 | adv3LibInit: InitObject |
| | 52 | execute() |
| | 53 | { |
| | 54 | /* set oup our default output function */ |
| | 55 | t3SetSay(say); |
| | 56 | } |
| | 57 | ; |
| | 58 | |
| | 59 | /* ------------------------------------------------------------------------ */ |
| | 60 | /* |
| | 61 | * The library output processor. |
| | 62 | */ |
| | 63 | say(val) |
| | 64 | { |
| | 65 | local idx; |
| | 66 | |
| | 67 | /* if it's a string, check for substitutions */ |
| | 68 | if (dataType(val) == TypeSString) |
| | 69 | { |
| | 70 | local tagPattern = '<nocase>[<]%.(/?[a-z]+)[>]'; |
| | 71 | |
| | 72 | /* search for our special '<.xxx>' tags, and expand any we find */ |
| | 73 | idx = rexSearch(tagPattern, val); |
| | 74 | while (idx != nil) |
| | 75 | { |
| | 76 | local xlat; |
| | 77 | local afterOfs; |
| | 78 | local afterStr; |
| | 79 | |
| | 80 | /* ask the formatter to translate it */ |
| | 81 | xlat = libFormatter.translateTag(rexGroup(1)[3]); |
| | 82 | |
| | 83 | /* get the part of the string that follows the tag */ |
| | 84 | afterOfs = idx[1] + idx[2]; |
| | 85 | afterStr = val.substr(idx[1] + idx[2]); |
| | 86 | |
| | 87 | /* |
| | 88 | * if we got a translation, replace it; otherwise, leave the |
| | 89 | * original text intact |
| | 90 | */ |
| | 91 | if (xlat != nil) |
| | 92 | { |
| | 93 | /* replace the tag with its translation */ |
| | 94 | val = val.substr(1, idx[1] - 1) + xlat + afterStr; |
| | 95 | |
| | 96 | /* |
| | 97 | * figure the offset of the remainder of the string in |
| | 98 | * the replaced version of the string - this is the |
| | 99 | * length of the original part up to the replacement |
| | 100 | * text plus the length of the replacement text |
| | 101 | */ |
| | 102 | afterOfs = idx[1] + xlat.length(); |
| | 103 | } |
| | 104 | |
| | 105 | /* |
| | 106 | * search for the next tag, considering only the part of |
| | 107 | * the string following the replacement text - we do not |
| | 108 | * want to re-scan the replacement text for tags |
| | 109 | */ |
| | 110 | idx = rexSearch(tagPattern, afterStr); |
| | 111 | |
| | 112 | /* |
| | 113 | * If we found it, adjust the starting index of the match to |
| | 114 | * its position in the actual string. Note that we do this |
| | 115 | * by adding the OFFSET of the remainder of the string, |
| | 116 | * which is 1 less than its INDEX, because idx[1] is already |
| | 117 | * a string index. (An offset is one less than an index |
| | 118 | * because the index of the first character is 1.) |
| | 119 | */ |
| | 120 | if (idx != nil) |
| | 121 | idx[1] += afterOfs - 1; |
| | 122 | } |
| | 123 | } |
| | 124 | |
| | 125 | /* write the value to the console */ |
| | 126 | tadsSay(val); |
| | 127 | } |
| | 128 | |
| | 129 | /* |
| | 130 | * Library text formatter |
| | 131 | */ |
| | 132 | libFormatter: object |
| | 133 | /* |
| | 134 | * Translate a tag |
| | 135 | */ |
| | 136 | translateTag(tag) |
| | 137 | { |
| | 138 | /* make sure the tag is lower-case only */ |
| | 139 | tag = tag.toLower(); |
| | 140 | |
| | 141 | /* scan each tag/translation pair for the translation */ |
| | 142 | for (local i = 1, local cnt = tagList.length() ; i < cnt ; i += 2) |
| | 143 | { |
| | 144 | /* |
| | 145 | * if this tag name matches, return its expansion; the tag |
| | 146 | * is the first element of the pair, which is the current |
| | 147 | * element, and the expansion is the second of the pair, |
| | 148 | * thus the next element |
| | 149 | */ |
| | 150 | if (tag == tagList[i]) |
| | 151 | return tagList[i + 1]; |
| | 152 | } |
| | 153 | |
| | 154 | /* didn't find it */ |
| | 155 | return nil; |
| | 156 | } |
| | 157 | |
| | 158 | /* |
| | 159 | * our tag translation list - entries are in pairs: first the tag |
| | 160 | * name, then the expansion |
| | 161 | */ |
| | 162 | tagList = |
| | 163 | [ |
| | 164 | /* room name as part of room description */ |
| | 165 | 'roomname', '<p><b>', |
| | 166 | '/roomname', '</b>', |
| | 167 | |
| | 168 | /* body of room description */ |
| | 169 | 'roomdesc', '<br>\n', |
| | 170 | '/roomdesc', '', |
| | 171 | |
| | 172 | /* paragraph break within a room description */ |
| | 173 | 'roompara', '<p>\n' |
| | 174 | ] |
| | 175 | ; |
| | 176 | |
| | 177 | |
| | 178 | |
| | 179 | /* ------------------------------------------------------------------------ */ |
| | 180 | /* |
| | 181 | * Interface for showList() - this class is used to provide information |
| | 182 | * to showList() on how the list should be displayed. |
| | 183 | */ |
| | 184 | class ShowListInterface: object |
| | 185 | /* |
| | 186 | * Show the prefix for a 'wide' listing - this is a message that |
| | 187 | * appears just before we start listing the objects. 'itemCount' is |
| | 188 | * the number of items to be listed; the items might be grouped in |
| | 189 | * the listing, so a list that comes out as "three boxes and two |
| | 190 | * books" will have an itemCount of 5. (The purpose of itemCount is |
| | 191 | * to allow the message to have grammatical agreement in number.) |
| | 192 | * |
| | 193 | * This will never be called with an itemCount of zero, because we |
| | 194 | * will instead use showListEmpty() to display an empty list. |
| | 195 | */ |
| | 196 | showListPrefixWide(itemCount, pov, parent) { } |
| | 197 | |
| | 198 | /* |
| | 199 | * show the suffix for a 'wide' listing - this is a message that |
| | 200 | * appears just after we finish listing the objects |
| | 201 | */ |
| | 202 | showListSuffixWide(itemCount, pov, parent) { } |
| | 203 | |
| | 204 | /* |
| | 205 | * Show the list prefix for a 'tall' listing. Note that there is no |
| | 206 | * list suffix for a tall listing, because the format doesn't allow |
| | 207 | * it. |
| | 208 | */ |
| | 209 | showListPrefixTall(itemCount, pov, parent) { } |
| | 210 | |
| | 211 | /* |
| | 212 | * Show an empty list. If the list to be displayed has no items at |
| | 213 | * all, this is called instead of the prefix/suffix routines. This |
| | 214 | * can be left empty if no message is required for an empty list, or |
| | 215 | * can display the complete message appropriate for an empty list |
| | 216 | * (such as "You are carrying nothing"). |
| | 217 | */ |
| | 218 | showListEmpty(pov, parent) { } |
| | 219 | ; |
| | 220 | |
| | 221 | /* |
| | 222 | * Show a list, showing all items in the list as though they were fully |
| | 223 | * visible, regardless of their actual sense status. |
| | 224 | */ |
| | 225 | showListAll(lister, lst, options, indent) |
| | 226 | { |
| | 227 | local infoList; |
| | 228 | |
| | 229 | /* create an infoList with each item in full view */ |
| | 230 | infoList = new Vector(lst.length()); |
| | 231 | foreach (local cur in lst) |
| | 232 | { |
| | 233 | /* add a plain view sensory description to the info list */ |
| | 234 | infoList += new SenseInfoListEntry(cur, transparent, nil, 3); |
| | 235 | } |
| | 236 | |
| | 237 | /* convert the info vector to a regular list */ |
| | 238 | infoList = infoList.toList(); |
| | 239 | |
| | 240 | /* show the list from the current global point of view */ |
| | 241 | showList(getPOV(), nil, lister, lst, options, indent, infoList); |
| | 242 | } |
| | 243 | |
| | 244 | /* |
| | 245 | * Display a list of items, grouping together members of a list group |
| | 246 | * and equivalent items. We will only list items for which isListed() |
| | 247 | * returns true. |
| | 248 | * |
| | 249 | * 'pov' is the point of view of the listing, which is usually an actor |
| | 250 | * (and usually the player character actor). |
| | 251 | * |
| | 252 | * 'parent' is the parent (container) of the list being shown. This |
| | 253 | * should be nil if the listed objects are not all within a single |
| | 254 | * object. |
| | 255 | * |
| | 256 | * 'lister' is an object implementing the methods in ShowListInterface - |
| | 257 | * this object displays the before and after messages for the list. |
| | 258 | * |
| | 259 | * 'lst' is the list of items to display. |
| | 260 | * |
| | 261 | * 'options' gives a set of LIST_xxx option flags. |
| | 262 | * |
| | 263 | * 'indent' gives the indentation level. This is used only for "tall" |
| | 264 | * lists (specified by including LIST_TALL in the options flags). An |
| | 265 | * indentation level of zero indicates no indentation. |
| | 266 | * |
| | 267 | * 'infoList' the list of SenseInfoListEntry objects for all of the |
| | 268 | * objects that can be sensed from the perspective of the actor |
| | 269 | * performing the action that's causing the listing. |
| | 270 | */ |
| | 271 | showList(pov, parent, lister, lst, options, indent, infoList) |
| | 272 | { |
| | 273 | local cur; |
| | 274 | local i, cnt; |
| | 275 | local itemCount; |
| | 276 | local groups; |
| | 277 | local groupCount; |
| | 278 | local singles; |
| | 279 | local uniqueSingles; |
| | 280 | local sublists; |
| | 281 | local groupOptions; |
| | 282 | |
| | 283 | /* no groups yet */ |
| | 284 | groupCount = 0; |
| | 285 | groups = new Vector(10); |
| | 286 | |
| | 287 | /* no singles yet */ |
| | 288 | singles = new Vector(10); |
| | 289 | |
| | 290 | /* |
| | 291 | * First, scan the list to determine how many objects we're going to |
| | 292 | * display. |
| | 293 | */ |
| | 294 | for (i = 1, cnt = lst.length(), itemCount = 0 ; i <= cnt ; ++i) |
| | 295 | { |
| | 296 | /* get this object into a local for easy reference */ |
| | 297 | cur = lst[i]; |
| | 298 | |
| | 299 | /* if the item isn't listed, skip it */ |
| | 300 | if (!cur.isListed()) |
| | 301 | continue; |
| | 302 | |
| | 303 | /* count the object */ |
| | 304 | ++itemCount; |
| | 305 | |
| | 306 | /* check for groupings */ |
| | 307 | if (cur.listWith != nil) |
| | 308 | { |
| | 309 | findPriorGroup: |
| | 310 | { |
| | 311 | /* |
| | 312 | * look through our list of grouped objects for other |
| | 313 | * objects in the same list group |
| | 314 | */ |
| | 315 | for (local j = 1 ; j <= groups.length() ; ++j) |
| | 316 | { |
| | 317 | /* |
| | 318 | * check the first object in this sublist (we only need |
| | 319 | * to check the first item in a sublist, since |
| | 320 | * everything in a sublist will have the same list group |
| | 321 | * - that's the way we construct the list) |
| | 322 | */ |
| | 323 | if (groups[j][1].listWith == cur.listWith) |
| | 324 | { |
| | 325 | /* found it - add this item to this group list */ |
| | 326 | groups[j] += cur; |
| | 327 | |
| | 328 | /* no need to look any further */ |
| | 329 | break findPriorGroup; |
| | 330 | } |
| | 331 | } |
| | 332 | |
| | 333 | /* |
| | 334 | * if we get here, it means that we didn't find this |
| | 335 | * item in an existing group - create a new group |
| | 336 | * sublist, and add this item as the first item in the |
| | 337 | * sublist |
| | 338 | */ |
| | 339 | groups = groups.append([cur]); |
| | 340 | |
| | 341 | /* we have a new group */ |
| | 342 | ++groupCount; |
| | 343 | } |
| | 344 | } |
| | 345 | else |
| | 346 | { |
| | 347 | /* |
| | 348 | * the object is not part of a group - add it to the list of |
| | 349 | * items to be listed singly |
| | 350 | */ |
| | 351 | singles += cur; |
| | 352 | } |
| | 353 | } |
| | 354 | |
| | 355 | /* |
| | 356 | * We now know how many items we're listing, so we can call the list |
| | 357 | * interface object to set up the list. If we're displaying nothing |
| | 358 | * at all, just ask the interface object to display the message for |
| | 359 | * an empty list, and we're done. |
| | 360 | */ |
| | 361 | if (itemCount == 0) |
| | 362 | { |
| | 363 | lister.showListEmpty(pov, parent); |
| | 364 | return; |
| | 365 | } |
| | 366 | |
| | 367 | /* |
| | 368 | * If any of the groups have only one item, move them to the singles |
| | 369 | * list. Rebuild the groups list with the new list. |
| | 370 | */ |
| | 371 | if (groups.length() != 0) |
| | 372 | { |
| | 373 | /* scan the groups for single item entries */ |
| | 374 | for (local i = 1 ; i <= groups.length() ; ) |
| | 375 | { |
| | 376 | /* get this item */ |
| | 377 | local cur = groups[i]; |
| | 378 | |
| | 379 | /* if this group has only one member, it's effectively a single */ |
| | 380 | if (cur.length() == 1) |
| | 381 | { |
| | 382 | /* move the single item in this group to the singles list */ |
| | 383 | singles += cur[1]; |
| | 384 | |
| | 385 | /* delete the entry from the groups list */ |
| | 386 | groups.removeElementAt(i); |
| | 387 | } |
| | 388 | else |
| | 389 | { |
| | 390 | /* |
| | 391 | * it's a legitimate group - keep it, and move on to the |
| | 392 | * next entry in the group list |
| | 393 | */ |
| | 394 | ++i; |
| | 395 | } |
| | 396 | } |
| | 397 | } |
| | 398 | |
| | 399 | |
| | 400 | /* |
| | 401 | * Check to see if we have one or more group sublists - if we do, we |
| | 402 | * must use the "long" list format for our overall list, otherwise |
| | 403 | * we can use the normal "short" list format. The long list format |
| | 404 | * uses semicolons to separate items. |
| | 405 | */ |
| | 406 | for (i = 1, cnt = groups.length(), sublists = nil ; i <= cnt ; ++i) |
| | 407 | { |
| | 408 | /* |
| | 409 | * if this group's lister item displays a sublist, we must use |
| | 410 | * the long format |
| | 411 | */ |
| | 412 | if (groups[i][1].listWith.groupDisplaysSublist) |
| | 413 | { |
| | 414 | /* note that we are using the long format */ |
| | 415 | sublists = true; |
| | 416 | |
| | 417 | /* |
| | 418 | * one is enough to make us use the long format, so we need |
| | 419 | * not look any further |
| | 420 | */ |
| | 421 | break; |
| | 422 | } |
| | 423 | } |
| | 424 | |
| | 425 | /* generate the prefix message */ |
| | 426 | if ((options & LIST_TALL) != 0) |
| | 427 | { |
| | 428 | /* indent the prefix */ |
| | 429 | showListIndent(options, indent); |
| | 430 | |
| | 431 | /* show the prefix */ |
| | 432 | lister.showListPrefixTall(itemCount, pov, parent); |
| | 433 | |
| | 434 | /* go to a new line for the list contents */ |
| | 435 | "\n"; |
| | 436 | |
| | 437 | /* indent the items one level now, since we showed a prefix */ |
| | 438 | ++indent; |
| | 439 | } |
| | 440 | else |
| | 441 | { |
| | 442 | /* show the prefix */ |
| | 443 | lister.showListPrefixWide(itemCount, pov, parent); |
| | 444 | } |
| | 445 | |
| | 446 | /* |
| | 447 | * calculate the number of unique items in the singles list - we |
| | 448 | * need to know this because we need to know how many items will |
| | 449 | * follow the group list in order to figure out what type of |
| | 450 | * separator to use after the last couple of group items |
| | 451 | */ |
| | 452 | uniqueSingles = getUniqueCount(singles, options); |
| | 453 | |
| | 454 | /* |
| | 455 | * regardless of whether we're adding long formatting to the main |
| | 456 | * list, display the group sublists with whatever formatting we were |
| | 457 | * originally using |
| | 458 | */ |
| | 459 | groupOptions = options; |
| | 460 | |
| | 461 | /* if we're using sublists, show the main list using long separators */ |
| | 462 | if (sublists) |
| | 463 | options |= LIST_LONG; |
| | 464 | |
| | 465 | /* show each group */ |
| | 466 | for (i = 1, cnt = groups.length() ; i <= cnt ; ++i) |
| | 467 | { |
| | 468 | /* in tall mode, indent before the group description */ |
| | 469 | showListIndent(options, indent); |
| | 470 | |
| | 471 | /* ask the listGroup object to display the list */ |
| | 472 | groups[i][1].listWith. |
| | 473 | showGroupList(pov, lister, groups[i], groupOptions, |
| | 474 | indent, infoList); |
| | 475 | |
| | 476 | /* |
| | 477 | * Show the post-group separator. For the purposes of figuring |
| | 478 | * out what separator to use here, each group and each single |
| | 479 | * item counts as one item. |
| | 480 | */ |
| | 481 | showListSeparator(options, i, cnt + uniqueSingles); |
| | 482 | } |
| | 483 | |
| | 484 | /* show the individual items in the list */ |
| | 485 | showListSimple(pov, lister, singles, options, indent, cnt, infoList); |
| | 486 | |
| | 487 | /* |
| | 488 | * generate the suffix message if we're in 'wide' mode ('tall' lists |
| | 489 | * have no suffix; the format only allows for a prefix) |
| | 490 | */ |
| | 491 | if ((options & LIST_TALL) == 0) |
| | 492 | lister.showListSuffixWide(itemCount, pov, parent); |
| | 493 | } |
| | 494 | |
| | 495 | /* |
| | 496 | * Show a list indent if necessary. If LIST_TALL is included in the |
| | 497 | * options, we'll indent to the given level; otherwise we'll do nothing. |
| | 498 | */ |
| | 499 | showListIndent(options, indent) |
| | 500 | { |
| | 501 | /* show the indent only if we're in "tall" mode */ |
| | 502 | if ((options & LIST_TALL) != 0) |
| | 503 | { |
| | 504 | for (local i = 0 ; i < indent ; ++i) |
| | 505 | "\t"; |
| | 506 | } |
| | 507 | } |
| | 508 | |
| | 509 | /* |
| | 510 | * Show a newline after a list item if we're in a tall list; does |
| | 511 | * nothing for a wide list. |
| | 512 | */ |
| | 513 | showTallListNewline(options) |
| | 514 | { |
| | 515 | if ((options & LIST_TALL) != 0) |
| | 516 | "\n"; |
| | 517 | } |
| | 518 | |
| | 519 | /* |
| | 520 | * Show a simple list, grouping together equivalent items. This |
| | 521 | * "simple" list formatter doesn't pay any attention to list groups. |
| | 522 | * |
| | 523 | * 'prevCnt' is the number of items already displayed, if anything has |
| | 524 | * already been displayed for this list. This should be zero if this |
| | 525 | * will display the entire list. |
| | 526 | */ |
| | 527 | showListSimple(pov, lister, lst, options, indent, prevCnt, infoList) |
| | 528 | { |
| | 529 | local i; |
| | 530 | local cnt; |
| | 531 | local dispCount; |
| | 532 | local uniqueCount; |
| | 533 | |
| | 534 | /* count the unique entries in the list */ |
| | 535 | uniqueCount = getUniqueCount(lst, options) + prevCnt; |
| | 536 | |
| | 537 | /* display the items */ |
| | 538 | dispLoop: |
| | 539 | for (i = 1, cnt = lst.length(), dispCount = prevCnt ; i <= cnt ; ++i) |
| | 540 | { |
| | 541 | local cur; |
| | 542 | local curSenseInfo; |
| | 543 | local dispSingle; |
| | 544 | |
| | 545 | /* get the item */ |
| | 546 | cur = lst[i]; |
| | 547 | |
| | 548 | /* presume we'll display it as a single item */ |
| | 549 | dispSingle = true; |
| | 550 | |
| | 551 | /* |
| | 552 | * If this is an item with equivalents, search the list for |
| | 553 | * other items that match. If this is the first one, list it, |
| | 554 | * with the count of matching items. If this isn't the first |
| | 555 | * one, don't bother listing it, since we've already included it |
| | 556 | * in the earlier item's listing. |
| | 557 | */ |
| | 558 | if (cur.isEquivalent) |
| | 559 | { |
| | 560 | local equivCount; |
| | 561 | |
| | 562 | /* search for an earlier instance of the item in the list */ |
| | 563 | for (local j = 1 ; j < i ; ++j) |
| | 564 | { |
| | 565 | /* |
| | 566 | * check to see if this item lists the same as the first |
| | 567 | * item we found - if so, we can skip to the next item |
| | 568 | * in the main loop, since we've already listed this |
| | 569 | * item |
| | 570 | */ |
| | 571 | if (lst[j].isEquivalent |
| | 572 | && lst[j].isListEquivalent(cur, options)) |
| | 573 | continue dispLoop; |
| | 574 | } |
| | 575 | |
| | 576 | /* |
| | 577 | * we didn't find it, so it hasn't been listed yet - count |
| | 578 | * the number of instances of this item in the rest of the |
| | 579 | * list, so we can include all of them in the count of this |
| | 580 | * item |
| | 581 | */ |
| | 582 | for (local j = i + 1, equivCount = 1 ; j <= cnt ; ++j) |
| | 583 | { |
| | 584 | /* if it matches, count it */ |
| | 585 | if (lst[j].isEquivalent |
| | 586 | && lst[j].isListEquivalent(cur, options)) |
| | 587 | ++equivCount; |
| | 588 | } |
| | 589 | |
| | 590 | /* |
| | 591 | * if we found more than one such item, display the entire |
| | 592 | * set of them with a count |
| | 593 | */ |
| | 594 | if (equivCount > 1) |
| | 595 | { |
| | 596 | /* show the list indent if necessary */ |
| | 597 | showListIndent(options, indent); |
| | 598 | |
| | 599 | /* get the sense information for this item */ |
| | 600 | curSenseInfo = infoList.valWhich({x: x.obj == cur}); |
| | 601 | |
| | 602 | /* display the whole group */ |
| | 603 | cur.showListItemCounted(equivCount, options, |
| | 604 | pov, curSenseInfo); |
| | 605 | |
| | 606 | /* note that we don't need to display it as a single */ |
| | 607 | dispSingle = nil; |
| | 608 | } |
| | 609 | } |
| | 610 | |
| | 611 | /* if necessary, display the item as a single item */ |
| | 612 | if (dispSingle) |
| | 613 | { |
| | 614 | /* show the list indent if necessary */ |
| | 615 | showListIndent(options, indent); |
| | 616 | |
| | 617 | /* get the sense information for this item */ |
| | 618 | curSenseInfo = infoList.valWhich({x: x.obj == cur}); |
| | 619 | |
| | 620 | /* show the item */ |
| | 621 | cur.showListItem(options, pov, curSenseInfo); |
| | 622 | } |
| | 623 | |
| | 624 | /* count the item displayed */ |
| | 625 | ++dispCount; |
| | 626 | |
| | 627 | /* show the list separator */ |
| | 628 | showListSeparator(options, dispCount, uniqueCount); |
| | 629 | |
| | 630 | /* if this is a tall recursive list, show the item's contents */ |
| | 631 | if ((options & LIST_TALL) != 0 && (options & LIST_RECURSE) != 0) |
| | 632 | cur.showObjectContents(pov, lister, options, |
| | 633 | indent + 1, infoList); |
| | 634 | } |
| | 635 | } |
| | 636 | |
| | 637 | /* |
| | 638 | * Show a list separator after displaying an item. curItemNum is the |
| | 639 | * number of the item just displayed (1 is the first item), and |
| | 640 | * totalItems is the total number of items that will be displayed in the |
| | 641 | * list. |
| | 642 | */ |
| | 643 | showListSeparator(options, curItemNum, totalItems) |
| | 644 | { |
| | 645 | local useLong = ((options & LIST_LONG) != 0); |
| | 646 | |
| | 647 | /* if this is a tall list, the separator is simply a newline */ |
| | 648 | if ((options & LIST_TALL) != 0) |
| | 649 | { |
| | 650 | "\n"; |
| | 651 | return; |
| | 652 | } |
| | 653 | |
| | 654 | /* if that was the last item, there are no separators */ |
| | 655 | if (curItemNum == totalItems) |
| | 656 | return; |
| | 657 | |
| | 658 | /* check to see if the next item is the last */ |
| | 659 | if (curItemNum + 1 == totalItems) |
| | 660 | { |
| | 661 | /* |
| | 662 | * We just displayed the penultimate item in the list, so we |
| | 663 | * need to use the special last-item separator. If we're only |
| | 664 | * displaying two items total, we use an even more special |
| | 665 | * separator. |
| | 666 | */ |
| | 667 | if (totalItems == 2) |
| | 668 | { |
| | 669 | /* use the two-item separator */ |
| | 670 | if (useLong) |
| | 671 | libMessages.longListSepTwo; |
| | 672 | else |
| | 673 | libMessages.listSepTwo; |
| | 674 | } |
| | 675 | else |
| | 676 | { |
| | 677 | /* use the normal last-item separator */ |
| | 678 | if (useLong) |
| | 679 | libMessages.longListSepEnd; |
| | 680 | else |
| | 681 | libMessages.listSepEnd; |
| | 682 | } |
| | 683 | } |
| | 684 | else |
| | 685 | { |
| | 686 | /* we're in the middle of the list - display the normal separator */ |
| | 687 | if (useLong) |
| | 688 | libMessages.longListSepMiddle; |
| | 689 | else |
| | 690 | libMessages.listSepMiddle; |
| | 691 | } |
| | 692 | } |
| | 693 | |
| | 694 | /* |
| | 695 | * Calculate the number of unique items in the list. |
| | 696 | */ |
| | 697 | getUniqueCount(lst, options) |
| | 698 | { |
| | 699 | local uniqueCount; |
| | 700 | |
| | 701 | /* run through the list and count the unique entries */ |
| | 702 | for (local i = 1, local cnt = lst.length(), uniqueCount = 0 ; |
| | 703 | i <= cnt ; ++i) |
| | 704 | { |
| | 705 | local cur; |
| | 706 | |
| | 707 | /* get this item into a local for easier access */ |
| | 708 | cur = lst[i]; |
| | 709 | |
| | 710 | /* tentatively assume this item is unique */ |
| | 711 | ++uniqueCount; |
| | 712 | |
| | 713 | /* |
| | 714 | * if it's marked as interchangeable with other items of its |
| | 715 | * class, note its class list, then try to find other objects |
| | 716 | * marked the same way |
| | 717 | */ |
| | 718 | if (cur.isEquivalent) |
| | 719 | { |
| | 720 | /* look for equivalents among the previous items */ |
| | 721 | for (local j = 1 ; j < i ; ++j) |
| | 722 | { |
| | 723 | /* check for a match */ |
| | 724 | if (lst[j].isEquivalent |
| | 725 | && lst[j].isListEquivalent(cur, options)) |
| | 726 | { |
| | 727 | /* it doesn't count as unique after all */ |
| | 728 | --uniqueCount; |
| | 729 | |
| | 730 | /* no need to look any further */ |
| | 731 | break; |
| | 732 | } |
| | 733 | } |
| | 734 | } |
| | 735 | } |
| | 736 | |
| | 737 | /* return the number of unique items we counted */ |
| | 738 | return uniqueCount; |
| | 739 | } |
| | 740 | |
| | 741 | /* ------------------------------------------------------------------------ */ |
| | 742 | /* |
| | 743 | * Show the contents of a set of items. For each item in the list, if |
| | 744 | * the object's contents can be sensed, we'll display them. We don't |
| | 745 | * display anything for the objects in the lists themselves - only for |
| | 746 | * the contents of the objects in the list. |
| | 747 | * |
| | 748 | * 'pov' is the point of view, which is usually an actor (and usually |
| | 749 | * the player character actor). |
| | 750 | * |
| | 751 | * 'lst' is the list of objects to display. |
| | 752 | * |
| | 753 | * 'options' is the set of flags that we'll pass to showList(), and has |
| | 754 | * the same meaning as for that function. |
| | 755 | * |
| | 756 | * 'infoList' is a list of SenseInfoListEntry objects giving the sense |
| | 757 | * information for all of the objects that the actor to whom we're |
| | 758 | * showing the contents listing can sense. |
| | 759 | */ |
| | 760 | showContentsList(pov, lst, options, indent, infoList) |
| | 761 | { |
| | 762 | /* go through each item and show its contents */ |
| | 763 | foreach (local cur in lst) |
| | 764 | cur.showObjectContents(pov, cur.contentsLister, options, |
| | 765 | indent, infoList); |
| | 766 | } |
| | 767 | |
| | 768 | /* ------------------------------------------------------------------------ */ |
| | 769 | /* |
| | 770 | * Sense Info List entry. Thing.senseInfoList() returns a list of these |
| | 771 | * objects to provide full sensory detail on the objects within range of |
| | 772 | * a sense. |
| | 773 | */ |
| | 774 | class SenseInfoListEntry: object |
| | 775 | construct(obj, trans, obstructor, ambient) |
| | 776 | { |
| | 777 | self.obj = obj; |
| | 778 | self.trans = trans; |
| | 779 | self.obstructor = obstructor; |
| | 780 | self.ambient = ambient; |
| | 781 | } |
| | 782 | |
| | 783 | /* the object being sensed */ |
| | 784 | obj = nil |
| | 785 | |
| | 786 | /* the transparency from the point of view to this object */ |
| | 787 | trans = nil |
| | 788 | |
| | 789 | /* the obstructor that introduces a non-transparent value of trans */ |
| | 790 | obstructor = nil |
| | 791 | |
| | 792 | /* the ambient sense energy level at this object */ |
| | 793 | ambient = nil |
| | 794 | ; |
| | 795 | |
| | 796 | |
| | 797 | /* ------------------------------------------------------------------------ */ |
| | 798 | /* |
| | 799 | * List Group Interface. This object is instantiated for each set of |
| | 800 | * non-equivalent items that should be grouped together in listings. |
| | 801 | */ |
| | 802 | class ListGroup: object |
| | 803 | /* |
| | 804 | * Show a list of items from this group. All of the items in the |
| | 805 | * list will be members of this list group; we are to display a |
| | 806 | * sentence fragment showing the items in the list, suitable for |
| | 807 | * embedding in a larger list. |
| | 808 | * |
| | 809 | * 'options', 'indent', and 'infoList' have the same meaning as they |
| | 810 | * do for showList(). |
| | 811 | * |
| | 812 | * Note that we are not to display any separator before or after our |
| | 813 | * list; the caller is responsible for that. |
| | 814 | */ |
| | 815 | showGroupList(pov, lister, lst, options, indent, infoList) { } |
| | 816 | |
| | 817 | /* |
| | 818 | * Determine if showing the group list will introduce a sublist into |
| | 819 | * an enclosing list. This should return true if we will show a |
| | 820 | * sublist without some kind of grouping, so that the caller knows |
| | 821 | * to introduce some extra grouping into its enclosing list. This |
| | 822 | * should return nil if the sublist we display will be clearly set |
| | 823 | * off in some way (for example, by being enclosed in parentheses). |
| | 824 | */ |
| | 825 | groupDisplaysSublist = true |
| | 826 | ; |
| | 827 | |
| | 828 | /* |
| | 829 | * List Group implementation: parenthesized sublist. Displays the |
| | 830 | * number of items collectively, then displays the list of items in |
| | 831 | * parentheses. |
| | 832 | */ |
| | 833 | class ListGroupParen: ListGroup |
| | 834 | /* |
| | 835 | * show the group list |
| | 836 | */ |
| | 837 | showGroupList(pov, lister, lst, options, indent, infoList) |
| | 838 | { |
| | 839 | /* show the collective count of the object */ |
| | 840 | showGroupCountDesc(lst); |
| | 841 | |
| | 842 | /* show the tall or wide sublist */ |
| | 843 | if ((options & LIST_TALL) != 0) |
| | 844 | { |
| | 845 | /* tall list - show the items as a sublist */ |
| | 846 | "\n"; |
| | 847 | showListSimple(pov, lister, lst, options | LIST_GROUP, indent, |
| | 848 | 0, infoList); |
| | 849 | } |
| | 850 | else |
| | 851 | { |
| | 852 | /* wide list - add a space and a paren for the sublist */ |
| | 853 | " ("; |
| | 854 | |
| | 855 | /* show the sublist */ |
| | 856 | showListSimple(pov, lister, lst, options | LIST_GROUP, indent, |
| | 857 | 0, infoList); |
| | 858 | |
| | 859 | /* end the sublist */ |
| | 860 | ")"; |
| | 861 | } |
| | 862 | } |
| | 863 | |
| | 864 | /* |
| | 865 | * Show the collective count for the list of objects. By default, |
| | 866 | * we'll simply display the countDesc of the first item in the list, |
| | 867 | * on the assumption that each object has the same plural |
| | 868 | * description. However, in most cases this should be overridden to |
| | 869 | * provide a more general collective name for the group. |
| | 870 | */ |
| | 871 | showGroupCountDesc(lst) |
| | 872 | { |
| | 873 | /* show the first item's countDesc */ |
| | 874 | lst[1].countDesc(lst.length()); |
| | 875 | } |
| | 876 | |
| | 877 | /* we don't add a sublist, since we enclose our list in parentheses */ |
| | 878 | groupDisplaysSublist = nil |
| | 879 | ; |
| | 880 | |
| | 881 | /* |
| | 882 | * List Group implementation: simple prefix/suffix lister. Shows a |
| | 883 | * prefix message, then shows the list, then shows a suffix message. |
| | 884 | */ |
| | 885 | class ListGroupPrefixSuffix: ListGroup |
| | 886 | showGroupList(pov, lister, lst, options, indent, infoList) |
| | 887 | { |
| | 888 | /* show the prefix */ |
| | 889 | showGroupPrefix; |
| | 890 | |
| | 891 | /* if we're in tall mode, start a new line */ |
| | 892 | showTallListNewline(options); |
| | 893 | |
| | 894 | /* show the list */ |
| | 895 | showListSimple(pov, lister, lst, options | LIST_GROUP, indent + 1, |
| | 896 | 0, infoList); |
| | 897 | |
| | 898 | /* show the suffix */ |
| | 899 | showGroupSuffix; |
| | 900 | } |
| | 901 | |
| | 902 | /* the prefix - subclasses should override this if desired */ |
| | 903 | showGroupPrefix = "" |
| | 904 | |
| | 905 | /* the suffix */ |
| | 906 | showGroupSuffix = "" |
| | 907 | ; |
| | 908 | |
| | 909 | /* ------------------------------------------------------------------------ */ |
| | 910 | /* |
| | 911 | * Library global variables |
| | 912 | */ |
| | 913 | libGlobal: object |
| | 914 | /* initialize the library globals */ |
| | 915 | initialize() |
| | 916 | { |
| | 917 | /* set up a vector for our point of view stack */ |
| | 918 | povStack = new Vector(32); |
| | 919 | |
| | 920 | /* set up the full-score vectors */ |
| | 921 | fullScorePoints = new Vector(32); |
| | 922 | fullScoreDesc = new Vector(32); |
| | 923 | } |
| | 924 | |
| | 925 | /* |
| | 926 | * List of all of the senses. The library pre-initializer will load |
| | 927 | * this list with a reference to each instance of class Sense. |
| | 928 | */ |
| | 929 | allSenses = [] |
| | 930 | |
| | 931 | /* |
| | 932 | * The current player character |
| | 933 | */ |
| | 934 | playerChar = nil |
| | 935 | |
| | 936 | /* |
| | 937 | * The current perspective object. This is usually the actor |
| | 938 | * performing the current command. |
| | 939 | */ |
| | 940 | pointOfView = nil |
| | 941 | |
| | 942 | /* |
| | 943 | * The stack of point of view objects. We initialize this during |
| | 944 | * start-up to a vector. The last element of the vector is the most |
| | 945 | * recent point of view after the current point of view. |
| | 946 | */ |
| | 947 | povStack = nil |
| | 948 | |
| | 949 | /* |
| | 950 | * Vectors for the score. The first stores the number of points for |
| | 951 | * each item scored, and the second stores the description |
| | 952 | * corresponding to each point count. We'll allocate these at |
| | 953 | * startup. |
| | 954 | */ |
| | 955 | fullScorePoints = nil |
| | 956 | fullScoreDesc = nil |
| | 957 | |
| | 958 | /* the total number of points scored so far */ |
| | 959 | totalScore = 0 |
| | 960 | |
| | 961 | /* |
| | 962 | * the maximum number of points possible - the game should set this |
| | 963 | * to the appropriate value at startup |
| | 964 | */ |
| | 965 | maxScore = 100 |
| | 966 | |
| | 967 | /* the total number of turns so far */ |
| | 968 | totalTurns = 0 |
| | 969 | ; |
| | 970 | |
| | 971 | /* ------------------------------------------------------------------------ */ |
| | 972 | /* |
| | 973 | * An Achievement is an object used to award points in the score. For |
| | 974 | * most purposes, an achievement can be described simply by a string, |
| | 975 | * but the Achievement object provides more flexibility in describing |
| | 976 | * combined scores when a similar set of achievements are to be grouped. |
| | 977 | */ |
| | 978 | class Achievement: object |
| | 979 | /* |
| | 980 | * The number of times the achievement has been awarded. Each time |
| | 981 | * the achievement is passed to addToScore(), this is incremented. |
| | 982 | * Note that this is distinct from the number of points. |
| | 983 | */ |
| | 984 | scoreCount = 0 |
| | 985 | |
| | 986 | /* |
| | 987 | * Describe the achievement - this must display a string explaining |
| | 988 | * the reason the points associated with this achievement were |
| | 989 | * awarded. |
| | 990 | * |
| | 991 | * Note that this description can make use of the scoreCount |
| | 992 | * information to show different descriptions depending on how many |
| | 993 | * times the item has scored. For example, an achievement for |
| | 994 | * finding various treasure items might want to display "finding a |
| | 995 | * treasure" if only one treasure was found and "finding five |
| | 996 | * treasures" if five were found. |
| | 997 | * |
| | 998 | * In some cases, it might be desirable to keep track of additional |
| | 999 | * custom information, and use that information in generating the |
| | 1000 | * description. For example, the game might keep a list of |
| | 1001 | * treasures found with the achievement, adding to the list each |
| | 1002 | * time the achievement is scored, and displaying the contents of |
| | 1003 | * the list when the description is shown. |
| | 1004 | */ |
| | 1005 | lDesc = "" |
| | 1006 | ; |
| | 1007 | |
| | 1008 | /* |
| | 1009 | * Add to the score. 'points' is the number of points to add to the |
| | 1010 | * score, and 'desc' is a string describing the reason the points are |
| | 1011 | * being awarded, or an Achievement object describing the points. |
| | 1012 | * |
| | 1013 | * We keep a list of each unique description. If 'desc' is already in |
| | 1014 | * this list, we'll simply add the given number of points to the |
| | 1015 | * existing entry for the same description. |
| | 1016 | * |
| | 1017 | * Note that, if 'desc' is an Achievement object, it will match a |
| | 1018 | * previous item only if it's exactly the same Achievement instance. |
| | 1019 | */ |
| | 1020 | addToScore(points, desc) |
| | 1021 | { |
| | 1022 | local idx; |
| | 1023 | |
| | 1024 | /* if 'desc' is an Achievement item, increase its use count */ |
| | 1025 | if (desc.ofKind(Achievement)) |
| | 1026 | desc.scoreCount++; |
| | 1027 | |
| | 1028 | /* add the points to the total */ |
| | 1029 | libGlobal.totalScore += points; |
| | 1030 | |
| | 1031 | /* try to find a matching achievement in our list of past score items */ |
| | 1032 | idx = libGlobal.fullScoreDesc.indexOf(desc); |
| | 1033 | |
| | 1034 | /* if we found it, merely increase the number of points for the item */ |
| | 1035 | if (idx != nil) |
| | 1036 | { |
| | 1037 | /* |
| | 1038 | * it's already in there - simply combine the points into the |
| | 1039 | * existing entry |
| | 1040 | */ |
| | 1041 | libGlobal.fullScorePoints[idx] += points; |
| | 1042 | } |
| | 1043 | else |
| | 1044 | { |
| | 1045 | /* it's not in the list yet - add it */ |
| | 1046 | libGlobal.fullScoreDesc.append(desc); |
| | 1047 | libGlobal.fullScorePoints.append(points); |
| | 1048 | } |
| | 1049 | } |
| | 1050 | |
| | 1051 | /* |
| | 1052 | * Show the simple score |
| | 1053 | */ |
| | 1054 | showScore() |
| | 1055 | { |
| | 1056 | /* show the basic score statistics */ |
| | 1057 | libMessages.showScoreMessage(libGlobal.totalScore, libGlobal.maxScore, |
| | 1058 | libGlobal.totalTurns); |
| | 1059 | |
| | 1060 | /* show the score ranking */ |
| | 1061 | showScoreRank(libGlobal.totalScore); |
| | 1062 | } |
| | 1063 | |
| | 1064 | /* |
| | 1065 | * show the score rank message |
| | 1066 | */ |
| | 1067 | showScoreRank(points) |
| | 1068 | { |
| | 1069 | local idx; |
| | 1070 | |
| | 1071 | /* if there's no rank table, skip the ranking */ |
| | 1072 | if (libMessages.scoreRankTable == nil) |
| | 1073 | return; |
| | 1074 | |
| | 1075 | /* |
| | 1076 | * find the last item for which our score is at least the minimum - |
| | 1077 | * the table is in ascending order of minimum score, so we want the |
| | 1078 | * last item for which our score is sufficient |
| | 1079 | */ |
| | 1080 | idx = libMessages.scoreRankTable.lastIndexWhich({x: points >= x[1]}); |
| | 1081 | |
| | 1082 | /* if we didn't find an item, use the first by default */ |
| | 1083 | if (idx == nil) |
| | 1084 | idx = 1; |
| | 1085 | |
| | 1086 | /* show the description from the item we found */ |
| | 1087 | libMessages.showScoreRankMessage(libMessages.scoreRankTable[idx][2]); |
| | 1088 | } |
| | 1089 | |
| | 1090 | /* |
| | 1091 | * Display the full score |
| | 1092 | */ |
| | 1093 | showFullScore() |
| | 1094 | { |
| | 1095 | /* show the basic score statistics */ |
| | 1096 | showScore(); |
| | 1097 | |
| | 1098 | /* if any points have been awarded, show the full score */ |
| | 1099 | if (libGlobal.totalScore != 0) |
| | 1100 | { |
| | 1101 | /* show a blank line before the full list */ |
| | 1102 | "\b"; |
| | 1103 | |
| | 1104 | /* show the list prefix */ |
| | 1105 | libMessages.showFullScorePrefix; |
| | 1106 | "\n"; |
| | 1107 | |
| | 1108 | /* show each item in the full score list */ |
| | 1109 | for (local i = 1, local cnt = libGlobal.fullScoreDesc.length() ; |
| | 1110 | i <= cnt ; ++i) |
| | 1111 | { |
| | 1112 | local desc; |
| | 1113 | |
| | 1114 | /* show the number of points */ |
| | 1115 | libMessages.fullScoreItemPoints(libGlobal.fullScorePoints[i]); |
| | 1116 | |
| | 1117 | /* |
| | 1118 | * if this description item is an Achivement, call its lDesc |
| | 1119 | * to display it; otherwise, it must simply be a string, so |
| | 1120 | * we can display it directly |
| | 1121 | */ |
| | 1122 | desc = libGlobal.fullScoreDesc[i]; |
| | 1123 | if (desc.ofKind(Achievement)) |
| | 1124 | { |
| | 1125 | /* it's an Achievement - use its lDesc to display it */ |
| | 1126 | desc.lDesc; |
| | 1127 | } |
| | 1128 | else |
| | 1129 | { |
| | 1130 | /* it's simply a string - display it directly */ |
| | 1131 | say(desc); |
| | 1132 | } |
| | 1133 | |
| | 1134 | /* one item per line - end the line here */ |
| | 1135 | "\n"; |
| | 1136 | } |
| | 1137 | } |
| | 1138 | } |
| | 1139 | |
| | 1140 | |
| | 1141 | /* ------------------------------------------------------------------------ */ |
| | 1142 | /* |
| | 1143 | * Get the current point of view. This is the actor object that should |
| | 1144 | * be used when generating names of objects. |
| | 1145 | */ |
| | 1146 | getPOV() |
| | 1147 | { |
| | 1148 | return libGlobal.pointOfView; |
| | 1149 | } |
| | 1150 | |
| | 1151 | /* |
| | 1152 | * Change the point of view without altering the point-of-view stack |
| | 1153 | */ |
| | 1154 | setPOV(pov) |
| | 1155 | { |
| | 1156 | /* set the new point of view */ |
| | 1157 | libGlobal.pointOfView = pov; |
| | 1158 | } |
| | 1159 | |
| | 1160 | /* |
| | 1161 | * Set the root point of view. This doesn't affect the current point of |
| | 1162 | * view unless there is no current point of view; this merely sets the |
| | 1163 | * outermost default point of view. |
| | 1164 | */ |
| | 1165 | setRootPOV(pov) |
| | 1166 | { |
| | 1167 | /* |
| | 1168 | * if there's nothing in the stacked list, set the current point of |
| | 1169 | * view; otherwise, just set the innermost stacked element |
| | 1170 | */ |
| | 1171 | if (libGlobal.povStack.length() == 0) |
| | 1172 | { |
| | 1173 | /* there is no point of view, so set the current point of view */ |
| | 1174 | libGlobal.pointOfView = pov; |
| | 1175 | } |
| | 1176 | else |
| | 1177 | { |
| | 1178 | /* set the innermost stacked point of view */ |
| | 1179 | libGlobal.povStack[1] = pov; |
| | 1180 | } |
| | 1181 | } |
| | 1182 | |
| | 1183 | /* |
| | 1184 | * Push the current point of view |
| | 1185 | */ |
| | 1186 | pushPOV(pov) |
| | 1187 | { |
| | 1188 | /* stack the current one */ |
| | 1189 | libGlobal.povStack += libGlobal.pointOfView; |
| | 1190 | |
| | 1191 | /* set the new point of view */ |
| | 1192 | setPOV(pov); |
| | 1193 | } |
| | 1194 | |
| | 1195 | /* |
| | 1196 | * Pop the most recent point of view pushed |
| | 1197 | */ |
| | 1198 | popPOV() |
| | 1199 | { |
| | 1200 | local len; |
| | 1201 | |
| | 1202 | /* check if there's anything left on the stack */ |
| | 1203 | len = libGlobal.povStack.length(); |
| | 1204 | if (len != 0) |
| | 1205 | { |
| | 1206 | /* take the most recent element off the stack */ |
| | 1207 | libGlobal.pointOfView = libGlobal.povStack[len]; |
| | 1208 | |
| | 1209 | /* take it off the stack */ |
| | 1210 | libGlobal.povStack.removeElementAt(len); |
| | 1211 | } |
| | 1212 | else |
| | 1213 | { |
| | 1214 | /* nothing on the stack - clear the point of view */ |
| | 1215 | libGlobal.pointOfView = nil; |
| | 1216 | } |
| | 1217 | } |
| | 1218 | |
| | 1219 | /* |
| | 1220 | * Clear the point of view and all stacked elements |
| | 1221 | */ |
| | 1222 | clearPOV() |
| | 1223 | { |
| | 1224 | local len; |
| | 1225 | |
| | 1226 | /* forget the current point of view */ |
| | 1227 | setPOV(nil); |
| | 1228 | |
| | 1229 | /* drop everything on the stack */ |
| | 1230 | len = libGlobal.povStack.length(); |
| | 1231 | libGlobal.povStack.removeRange(1, len); |
| | 1232 | } |
| | 1233 | |
| | 1234 | /* |
| | 1235 | * Call a function from a point of view. We'll set the new point of |
| | 1236 | * view, call the function with the given arguments, then restore the |
| | 1237 | * original point of view. |
| | 1238 | */ |
| | 1239 | callFromPOV(pov, funcToCall, [args]) |
| | 1240 | { |
| | 1241 | /* push the new point of view */ |
| | 1242 | pushPOV(pov); |
| | 1243 | |
| | 1244 | /* make sure we pop the point of view no matter how we leave */ |
| | 1245 | try |
| | 1246 | { |
| | 1247 | /* call the function */ |
| | 1248 | (funcToCall)(args...); |
| | 1249 | } |
| | 1250 | finally |
| | 1251 | { |
| | 1252 | /* restore the enclosing point of view on the way out */ |
| | 1253 | popPOV(); |
| | 1254 | } |
| | 1255 | } |
| | 1256 | |
| | 1257 | |
| | 1258 | /* ------------------------------------------------------------------------ */ |
| | 1259 | /* |
| | 1260 | * "Add" two transparency levels, yielding a new transparency level. |
| | 1261 | * This function can be used to determine the result of passing a sense |
| | 1262 | * through multiple layers of material. |
| | 1263 | */ |
| | 1264 | transparencyAdd(a, b) |
| | 1265 | { |
| | 1266 | /* transparent + x -> x for all x */ |
| | 1267 | if (a == transparent) |
| | 1268 | return b; |
| | 1269 | if (b == transparent) |
| | 1270 | return a; |
| | 1271 | |
| | 1272 | /* opaque + x -> opaque for all x */ |
| | 1273 | if (a == opaque || b == opaque) |
| | 1274 | return opaque; |
| | 1275 | |
| | 1276 | /* |
| | 1277 | * distant + distant, obscured + obscured, and distant + obscured |
| | 1278 | * all yield opaque - since neither is transparent or opaque, both |
| | 1279 | * must be obscured or distant, so the result must be opaque |
| | 1280 | */ |
| | 1281 | return opaque; |
| | 1282 | } |
| | 1283 | |
| | 1284 | /* |
| | 1285 | * Compare two transparency levels to determine which one is more |
| | 1286 | * transparent. Returns 0 if the two levels are equally transparent, 1 |
| | 1287 | * if the first one is more transparent, and -1 if the second one is |
| | 1288 | * more transparent. The comparison follows this rule: |
| | 1289 | * |
| | 1290 | * transparent > distant == obscured > opaque |
| | 1291 | */ |
| | 1292 | transparencyCompare(a, b) |
| | 1293 | { |
| | 1294 | /* |
| | 1295 | * for the purposes of the comparison, consider obscured to be |
| | 1296 | * identical to distant |
| | 1297 | */ |
| | 1298 | if (a == obscured) |
| | 1299 | a = distant; |
| | 1300 | if (b == obscured) |
| | 1301 | b = distant; |
| | 1302 | |
| | 1303 | /* if they're the same, return zero to so indicate */ |
| | 1304 | if (a == b) |
| | 1305 | return 0; |
| | 1306 | |
| | 1307 | /* |
| | 1308 | * We know they're not equal, so if one is transparent, then the |
| | 1309 | * other one isn't. Thus, if either one is transparent, it's the |
| | 1310 | * winner. |
| | 1311 | */ |
| | 1312 | if (a == transparent) |
| | 1313 | return 1; |
| | 1314 | if (b == transparent) |
| | 1315 | return -1; |
| | 1316 | |
| | 1317 | /* |
| | 1318 | * We now know neither one is transparent, and we've already |
| | 1319 | * transformed obscured into distant, so the only possible values |
| | 1320 | * remaining are distant and opaque. We know also they're not |
| | 1321 | * equal, because we would have already returned if that were the |
| | 1322 | * case. So, we can conclude that one must be distant and the other |
| | 1323 | * must be opaque. Hence, the one that's opaque is the less |
| | 1324 | * transparent one. |
| | 1325 | */ |
| | 1326 | if (a == opaque) |
| | 1327 | return -1; |
| | 1328 | else |
| | 1329 | return 1; |
| | 1330 | } |
| | 1331 | |
| | 1332 | /* |
| | 1333 | * Given a brightness level and a transparency level, compute the |
| | 1334 | * brightness as modified by the transparency level. |
| | 1335 | */ |
| | 1336 | adjustBrightness(br, trans) |
| | 1337 | { |
| | 1338 | switch(trans) |
| | 1339 | { |
| | 1340 | case transparent: |
| | 1341 | /* transparent medium - this doesn't modify brightness at all */ |
| | 1342 | return br; |
| | 1343 | |
| | 1344 | case obscured: |
| | 1345 | case distant: |
| | 1346 | /* |
| | 1347 | * Distant or obscured. We reduce self-illuminating light |
| | 1348 | * (level 1) and dim light (level 2) to nothing (level 0), we |
| | 1349 | * leave nothing as nothing (obviously), and we reduce all other |
| | 1350 | * levels one step. So, everything below level 3 goes to 0, and |
| | 1351 | * everything at or above level 3 gets decremented by 1. |
| | 1352 | */ |
| | 1353 | return (br >= 3 ? br - 1 : 0); |
| | 1354 | |
| | 1355 | case opaque: |
| | 1356 | /* opaque medium - nothing makes it through */ |
| | 1357 | return 0; |
| | 1358 | |
| | 1359 | default: |
| | 1360 | /* shouldn't get to other cases */ |
| | 1361 | return nil; |
| | 1362 | } |
| | 1363 | } |
| | 1364 | |
| | 1365 | |
| | 1366 | /* ------------------------------------------------------------------------ */ |
| | 1367 | /* |
| | 1368 | * Material: the base class for library objects that specify the way |
| | 1369 | * senses pass through objects. |
| | 1370 | */ |
| | 1371 | class Material: object |
| | 1372 | /* |
| | 1373 | * Determine how a sense passes through the material. We'll return |
| | 1374 | * a transparency level. (Individual materials should not need to |
| | 1375 | * override this method, since it simply dispatches to the various |
| | 1376 | * xxxThru methods.) |
| | 1377 | */ |
| | 1378 | senseThru(sense) |
| | 1379 | { |
| | 1380 | /* dispatch to the xxxThru method for the sense */ |
| | 1381 | return self.(sense.thruProp); |
| | 1382 | } |
| | 1383 | |
| | 1384 | /* |
| | 1385 | * For each sense, each material must define an appropriate xxxThru |
| | 1386 | * property that returns the transparency level for that sense |
| | 1387 | * through the material. Any xxxThru property not defined in an |
| | 1388 | * individual material defaults to opaque. |
| | 1389 | */ |
| | 1390 | seeThru = opaque |
| | 1391 | hearThru = opaque |
| | 1392 | smellThru = opaque |
| | 1393 | touchThru = opaque |
| | 1394 | ; |
| | 1395 | |
| | 1396 | /* |
| | 1397 | * Adventium is the basic stuff of the game universe. This is the |
| | 1398 | * default material for any object that doesn't specify a different |
| | 1399 | * material. This type of material is opaque to all senses. |
| | 1400 | */ |
| | 1401 | adventium: Material |
| | 1402 | seeThru = opaque |
| | 1403 | hearThru = opaque |
| | 1404 | smellThru = opaque |
| | 1405 | touchThru = opaque |
| | 1406 | ; |
| | 1407 | |
| | 1408 | /* |
| | 1409 | * Paper is opaque to sight and touch, but allows sound and smell to |
| | 1410 | * pass. |
| | 1411 | */ |
| | 1412 | paper: Material |
| | 1413 | seeThru = opaque |
| | 1414 | hearThru = transparent |
| | 1415 | smellThru = transparent |
| | 1416 | touchThru = opaque |
| | 1417 | ; |
| | 1418 | |
| | 1419 | /* |
| | 1420 | * Glass is transparent to light, but opaque to touch, sound, and smell. |
| | 1421 | * |
| | 1422 | */ |
| | 1423 | glass: Material |
| | 1424 | seeThru = transparent |
| | 1425 | hearThru = opaque |
| | 1426 | smellThru = opaque |
| | 1427 | touchThru = opaque |
| | 1428 | ; |
| | 1429 | |
| | 1430 | /* |
| | 1431 | * Fine Mesh is transparent to all senses except touch. |
| | 1432 | */ |
| | 1433 | fineMesh: Material |
| | 1434 | seeThru = transparent |
| | 1435 | hearThru = transparent |
| | 1436 | smellThru = transparent |
| | 1437 | touchThru = opaque |
| | 1438 | ; |
| | 1439 | |
| | 1440 | /* |
| | 1441 | * Coarse Mesh is transparent to all senses, including touch, but |
| | 1442 | * doesn't allow large objects to pass through. |
| | 1443 | */ |
| | 1444 | coarseMesh: Material |
| | 1445 | seeThru = transparent |
| | 1446 | hearThru = transparent |
| | 1447 | smellThru = transparent |
| | 1448 | touchThru = transparent |
| | 1449 | ; |
| | 1450 | |
| | 1451 | /* ------------------------------------------------------------------------ */ |
| | 1452 | /* |
| | 1453 | * Sense: the basic class for senses. |
| | 1454 | */ |
| | 1455 | class Sense: object |
| | 1456 | /* |
| | 1457 | * Each sense must define the property thruProp as a property |
| | 1458 | * pointer giving the xxxThru property for the sense. The xxxThru |
| | 1459 | * property is the property of a material which determines how the |
| | 1460 | * sense passes through that material. |
| | 1461 | */ |
| | 1462 | thruProp = nil |
| | 1463 | |
| | 1464 | /* |
| | 1465 | * Each sense must define the property sizeProp as a property |
| | 1466 | * pointer giving the xxxSize property for the sense. The xxxSize |
| | 1467 | * property is the property of a Thing which determines how "large" |
| | 1468 | * the object is with respect to the sense. For example, sightSize |
| | 1469 | * indicates how large the object is visually, while soundSize |
| | 1470 | * indicates how loud the object is. |
| | 1471 | * |
| | 1472 | * The purpose of an object's size in a given sense is to determine |
| | 1473 | * how well the object can be sensed through an obscuring medium or |
| | 1474 | * at a distance. |
| | 1475 | */ |
| | 1476 | sizeProp = nil |
| | 1477 | |
| | 1478 | /* |
| | 1479 | * Each sense must define the property presenceProp as a property |
| | 1480 | * pointer giving the xxxPresence property for the sense. The |
| | 1481 | * xxxPresence property is the property of a Thing which determines |
| | 1482 | * whether or not the object has a "presence" in this sense, which |
| | 1483 | * is to say whether or not the object is emitting any detectable |
| | 1484 | * sensory data for the sense. For example, soundPresence indicates |
| | 1485 | * whether or not a Thing is making any noise. |
| | 1486 | */ |
| | 1487 | presenceProp = nil |
| | 1488 | |
| | 1489 | /* |
| | 1490 | * Each sense can define this property to specify a property pointer |
| | 1491 | * used to define a Thing's "ambient" energy emissions. Senses |
| | 1492 | * which do not use ambient energy should define this to nil. |
| | 1493 | * |
| | 1494 | * Some senses work only on directly emitted sensory data; human |
| | 1495 | * hearing, for example, has no (at least effectively no) use for |
| | 1496 | * reflected sound, and can sense objects only by the sounds they're |
| | 1497 | * actually emitting. Sight, on the other hand, can make use not |
| | 1498 | * only of light emitted by an object but of light reflected by the |
| | 1499 | * object. So, sight defines an ambience property, whereas hearing, |
| | 1500 | * touch, and smell do not. |
| | 1501 | */ |
| | 1502 | ambienceProp = nil |
| | 1503 | |
| | 1504 | /* |
| | 1505 | * Determine if, in general, the given object can be sensed under |
| | 1506 | * the given conditions. Returns true if so, nil if not. By |
| | 1507 | * default, if the ambient level is zero, we'll return nil; |
| | 1508 | * otherwise, if the transparency level is 'transparent', we'll |
| | 1509 | * return true; otherwise, we'll consult the object's size: |
| | 1510 | * |
| | 1511 | * - Small objects cannot be sensed under less than transparent |
| | 1512 | * conditions. |
| | 1513 | * |
| | 1514 | * - Medium or large objects can be sensed in any conditions other |
| | 1515 | * than opaque. |
| | 1516 | */ |
| | 1517 | canSenseObj(obj, trans, ambient) |
| | 1518 | { |
| | 1519 | /* if the ambient energy level is zero, we can't sense it */ |
| | 1520 | if (ambient == 0) |
| | 1521 | return nil; |
| | 1522 | |
| | 1523 | /* check the transparency level */ |
| | 1524 | switch(trans) |
| | 1525 | { |
| | 1526 | case transparent: |
| | 1527 | /* we can always sense under transparent conditions */ |
| | 1528 | return true; |
| | 1529 | |
| | 1530 | case distant: |
| | 1531 | case obscured: |
| | 1532 | /* |
| | 1533 | * we can only sense medium and large objects under less |
| | 1534 | * than transparent conditions |
| | 1535 | */ |
| | 1536 | return obj.(self.sizeProp) != small; |
| | 1537 | |
| | 1538 | default: |
| | 1539 | /* we can never sense under other conditions */ |
| | 1540 | return nil; |
| | 1541 | } |
| | 1542 | } |
| | 1543 | ; |
| | 1544 | |
| | 1545 | /* |
| | 1546 | * The senses. We define sight, sound, smell, and touch. We do not |
| | 1547 | * define a separate sense for taste, since it would add nothing to our |
| | 1548 | * model: you can taste something if and only if you can touch it. |
| | 1549 | * |
| | 1550 | * To add a new sense, you must do the following: |
| | 1551 | * |
| | 1552 | * - Define the sense object itself, in parallel to the senses defined |
| | 1553 | * below. |
| | 1554 | * |
| | 1555 | * - Modify class Material to set the default transparency level for |
| | 1556 | * this sense by defining the property xxxThru - for most senses, the |
| | 1557 | * default transparency level is 'opaque', but you must decide on the |
| | 1558 | * appropriate default for your new sense. |
| | 1559 | * |
| | 1560 | * - Modify class Thing to set the default xxxSize setting, if desired. |
| | 1561 | * |
| | 1562 | * - Modify class Thing to set the default xxxPresence setting, if |
| | 1563 | * desired. |
| | 1564 | * |
| | 1565 | * - Modify each instance of class 'Material' that should have a |
| | 1566 | * non-default transparency for the sense by defining the property |
| | 1567 | * xxxThru for the material. |
| | 1568 | * |
| | 1569 | * - Modify class Actor to add the sense to the default mySenses list; |
| | 1570 | * this is only necessary if the sense is one that all actors should |
| | 1571 | * have by default. |
| | 1572 | */ |
| | 1573 | |
| | 1574 | sight: Sense |
| | 1575 | thruProp = &seeThru |
| | 1576 | sizeProp = &sightSize |
| | 1577 | presenceProp = &sightPresence |
| | 1578 | ambienceProp = &brightness |
| | 1579 | ; |
| | 1580 | |
| | 1581 | sound: Sense |
| | 1582 | thruProp = &hearThru |
| | 1583 | sizeProp = &soundSize |
| | 1584 | presenceProp = &soundPresence |
| | 1585 | ; |
| | 1586 | |
| | 1587 | smell: Sense |
| | 1588 | thruProp = &smellThru |
| | 1589 | sizeProp = &smellSize |
| | 1590 | presenceProp = &smellPresence |
| | 1591 | ; |
| | 1592 | |
| | 1593 | touch: Sense |
| | 1594 | thruProp = &touchThru |
| | 1595 | sizeProp = &touchSize |
| | 1596 | presenceProp = &touchPresence |
| | 1597 | |
| | 1598 | /* |
| | 1599 | * Override canSenseObj for touch. Unlike other senses, touch |
| | 1600 | * requires physical contact with an object, so it cannot operate at |
| | 1601 | * a distance, regardless of the size of an object. |
| | 1602 | */ |
| | 1603 | canSenseObj(obj, trans, ambient) |
| | 1604 | { |
| | 1605 | /* if it's distant, we can't sense the object no matter how large */ |
| | 1606 | if (trans == distant) |
| | 1607 | return nil; |
| | 1608 | |
| | 1609 | /* for other cases, inherit the default handling */ |
| | 1610 | return inherited.canSenseObj(obj, trans, ambient); |
| | 1611 | } |
| | 1612 | ; |
| | 1613 | |
| | 1614 | |
| | 1615 | /* ------------------------------------------------------------------------ */ |
| | 1616 | /* |
| | 1617 | * Thing: the basic class for game objects. An object of this class |
| | 1618 | * represents a physical object in the simulation. |
| | 1619 | * |
| | 1620 | * The LangThing base class defines some language-specific methods and |
| | 1621 | * properties, which are separated out from Thing to make it easy for a |
| | 1622 | * translator to substitute a translated implementation without changing |
| | 1623 | * the rest of the library code. |
| | 1624 | */ |
| | 1625 | class Thing: LangThing |
| | 1626 | /* |
| | 1627 | * If we define a non-nil initDesc, this property will be called to |
| | 1628 | * describe the object in room listings until the object is first |
| | 1629 | * moved to a new location. By default, objects don't have initial |
| | 1630 | * descriptions. |
| | 1631 | */ |
| | 1632 | initDesc = nil |
| | 1633 | |
| | 1634 | /* |
| | 1635 | * Flag: I've been moved out of my initial location. Whenever we |
| | 1636 | * move the object to a new location, we'll set this to true. |
| | 1637 | */ |
| | 1638 | isMoved = nil |
| | 1639 | |
| | 1640 | /* |
| | 1641 | * Determine if I should be described using my initial description. |
| | 1642 | * This returns true if I have an initial description that isn't |
| | 1643 | * nil, and I have never been moved out of my initial location. If |
| | 1644 | * this returns nil, the object should be described in room |
| | 1645 | * descriptions using the ordinary generated message. |
| | 1646 | */ |
| | 1647 | useInitDesc() |
| | 1648 | { |
| | 1649 | return !isMoved && propType(&initDesc) != TypeNil; |
| | 1650 | } |
| | 1651 | |
| | 1652 | /* |
| | 1653 | * Determine if I'm to be listed at all in my room description. |
| | 1654 | * Most objects should be listed normally, but some types of objects |
| | 1655 | * should be suppressed from the normal listing. For example, |
| | 1656 | * fixed-in-place scenery objects are generally described in the |
| | 1657 | * custom message for the containing room, so these are normally |
| | 1658 | * omitted from the listing of the room's contents. |
| | 1659 | * |
| | 1660 | * By default, we'll return true, unless we are using our initial |
| | 1661 | * description; we return nil if our initial description is to be |
| | 1662 | * used because the initial description overrides the default |
| | 1663 | * listing. |
| | 1664 | * |
| | 1665 | * Individual objects are free to override this as needed to control |
| | 1666 | * their listing status. |
| | 1667 | */ |
| | 1668 | isListed() { return !useInitDesc(); } |
| | 1669 | |
| | 1670 | /* |
| | 1671 | * The default long description, which is displayed in response to |
| | 1672 | * an explicit player request to examine the object. We'll use a |
| | 1673 | * generic library message; most objects should override this to |
| | 1674 | * customize the object's desription. |
| | 1675 | */ |
| | 1676 | lDesc { libMessages.thingLDesc(self); } |
| | 1677 | |
| | 1678 | /* |
| | 1679 | * "Equivalence" flag. If this flag is set, then all objects with |
| | 1680 | * the same immediate superclass will be considered interchangeable; |
| | 1681 | * such objects will be listed collectively in messages (so we would |
| | 1682 | * display "five coins" rather than "a coin, a coin, a coin, a coin, |
| | 1683 | * and a coin"), and will be treated as equivalent in resolving noun |
| | 1684 | * phrases to objects in user input. |
| | 1685 | * |
| | 1686 | * By default, this property is nil, since we want most objects to |
| | 1687 | * be treated as unique. |
| | 1688 | */ |
| | 1689 | isEquivalent = nil |
| | 1690 | |
| | 1691 | /* |
| | 1692 | * Listing equivalence test. This returns true if we should be |
| | 1693 | * treated as equivalent to the given item in a list, which is to |
| | 1694 | * say that we should be combined with the given item and reported |
| | 1695 | * as one of several such items. |
| | 1696 | * |
| | 1697 | * 'options' is the set of LIST_xxx listing options for the object. |
| | 1698 | * In some cases, equivalency might depend upon the listing mode; |
| | 1699 | * for example, in 'tall' and 'recursive' listing mode, an object |
| | 1700 | * with children to display should probably not be marked as |
| | 1701 | * equivalent, since it would have to show its contents list in the |
| | 1702 | * recursive listing. |
| | 1703 | * |
| | 1704 | * By default, this returns true if this object is of the same class |
| | 1705 | * or classes as the given object. This should be overridden when |
| | 1706 | * an object's listing format will show some kind of additional |
| | 1707 | * state about the item; for example, if the item is shown as |
| | 1708 | * "(providing light)" in listings, but its light can be turned on |
| | 1709 | * and off, items should be grouped only when their on/off states |
| | 1710 | * are the same. |
| | 1711 | */ |
| | 1712 | isListEquivalent(obj, options) |
| | 1713 | { |
| | 1714 | /* |
| | 1715 | * by default, list myself with any other object of the |
| | 1716 | * identical set of superclasses |
| | 1717 | */ |
| | 1718 | return getSuperclassList() == obj.getSuperclassList(); |
| | 1719 | } |
| | 1720 | |
| | 1721 | /* |
| | 1722 | * "List Group" object. If this property is set, then this object |
| | 1723 | * is grouped with other objects in the same list group for listing |
| | 1724 | * purposes. This should be an object of class ListGroup. |
| | 1725 | * |
| | 1726 | * By default, we set this to nil, which makes an object ungrouped |
| | 1727 | * in listings. |
| | 1728 | */ |
| | 1729 | listWith = nil |
| | 1730 | |
| | 1731 | /* |
| | 1732 | * The strength of the light the object is giving off, if indeed it |
| | 1733 | * is giving off light. This value should be one of the following: |
| | 1734 | * |
| | 1735 | * 0: The object is giving off no light at all. |
| | 1736 | * |
| | 1737 | * 1: The object is self-illuminating, but doesn't give off enough |
| | 1738 | * light to illuminate any other objects. This is suitable for |
| | 1739 | * something like an LED digital clock. |
| | 1740 | * |
| | 1741 | * 2: The object gives off dim light. This level is bright enough |
| | 1742 | * to illuminate nearby objects, but not enough to reach distant |
| | 1743 | * objects or go through obscuring media, and not enough for certain |
| | 1744 | * activities requiring strong lighting, such as reading. |
| | 1745 | * |
| | 1746 | * 3: The object gives off medium light. This level is bright |
| | 1747 | * enough to illuminate nearby objects, and is enough for most |
| | 1748 | * activities, including reading and the like. Traveling a distance |
| | 1749 | * or through an obscuring medium reduces this level to dim (2). |
| | 1750 | * |
| | 1751 | * 4: The object gives off strong light. This level is bright |
| | 1752 | * enough to illuminate nearby objects, and travel through an |
| | 1753 | * obscuring medium or over a distance reduces it to medium light |
| | 1754 | * (3). |
| | 1755 | * |
| | 1756 | * Note that the special value -1 is reserved as an invalid level, |
| | 1757 | * used to flag certain events (such as the need to recalculate the |
| | 1758 | * ambient light level from a new point of view). |
| | 1759 | * |
| | 1760 | * Most objects do not give off light at all. |
| | 1761 | */ |
| | 1762 | brightness = 0 |
| | 1763 | |
| | 1764 | /* |
| | 1765 | * Sense sizes of the object. Each object has an individual size |
| | 1766 | * for each sense. By default, objects are medium for all senses; |
| | 1767 | * this allows them to be sensed from a distance or through an obscuring |
| | 1768 | * medium, but doesn't allow their details to be sensed. |
| | 1769 | */ |
| | 1770 | sightSize = medium |
| | 1771 | soundSize = medium |
| | 1772 | smellSize = medium |
| | 1773 | touchSize = medium |
| | 1774 | |
| | 1775 | /* |
| | 1776 | * Determine whether or not the object has a "presence" in each |
| | 1777 | * sense. An object has a presence in a sense if an actor |
| | 1778 | * immediately adjacent to the object could detect the object by the |
| | 1779 | * sense alone. For example, an object has a "hearing presence" if |
| | 1780 | * it is making some kind of noise, and does not if it is silent. |
| | 1781 | * |
| | 1782 | * Presence in a given sense is an intrinsic (which does not imply |
| | 1783 | * unchanging) property of the object, in that presence is |
| | 1784 | * independent of the relationship to any given actor. If an alarm |
| | 1785 | * clock is ringing, it has a hearing presence, unconditionally; it |
| | 1786 | * doesn't matter if the alarm clock is sealed inside a sound-proof |
| | 1787 | * box, because whether or not a given actor has a sense path to the |
| | 1788 | * object is a matter for a different computation. |
| | 1789 | * |
| | 1790 | * Note that presence doesn't control access: an actor might have |
| | 1791 | * access to an object for a sense even if the object has no |
| | 1792 | * presence in the sense. Presence indicates whether or not the |
| | 1793 | * object is actively emitting sensory data that would make an actor |
| | 1794 | * aware of the object without specifically trying to apply the |
| | 1795 | * sense to the object. |
| | 1796 | * |
| | 1797 | * By default, an object is visible and touchable, but does not emit |
| | 1798 | * any sound or odor. |
| | 1799 | */ |
| | 1800 | sightPresence = true |
| | 1801 | soundPresence = nil |
| | 1802 | smellPresence = nil |
| | 1803 | touchPresence = true |
| | 1804 | |
| | 1805 | /* |
| | 1806 | * My "contents lister." This is a ShowListInterface object that we |
| | 1807 | * use to display the contents of this object for room descriptions, |
| | 1808 | * inventories, and the like. |
| | 1809 | */ |
| | 1810 | contentsLister = thingContentsLister |
| | 1811 | |
| | 1812 | /* |
| | 1813 | * Determine if I can be sensed under the given conditions. Returns |
| | 1814 | * true if the object can be sensed, nil if not. If this method |
| | 1815 | * returns nil, this object will not be considered in scope for the |
| | 1816 | * current conditions. |
| | 1817 | * |
| | 1818 | * By default, we return nil if the ambient energy level for the |
| | 1819 | * object is zero. If the ambient level is non-zero, we'll return |
| | 1820 | * true in 'transparent' conditions, nil for 'opaque', and we'll let |
| | 1821 | * the sense decide via its canSenseObj() method for any other |
| | 1822 | * transparency conditions. |
| | 1823 | */ |
| | 1824 | canBeSensed(sense, trans, ambient) |
| | 1825 | { |
| | 1826 | /* if the ambient level is zero, I can't be sensed this way */ |
| | 1827 | if (ambient == 0) |
| | 1828 | return nil; |
| | 1829 | |
| | 1830 | /* check the viewing conditions */ |
| | 1831 | switch(trans) |
| | 1832 | { |
| | 1833 | case transparent: |
| | 1834 | /* under transparent conditions, I appear as myself */ |
| | 1835 | return true; |
| | 1836 | |
| | 1837 | case obscured: |
| | 1838 | case distant: |
| | 1839 | /* |
| | 1840 | * ask the sense to determine if I can be sensed under these |
| | 1841 | * conditions |
| | 1842 | */ |
| | 1843 | return sense.canSenseObj(self, trans, ambient); |
| | 1844 | |
| | 1845 | default: |
| | 1846 | /* for any other conditions, I can't be sensed at all */ |
| | 1847 | return nil; |
| | 1848 | } |
| | 1849 | } |
| | 1850 | |
| | 1851 | /* |
| | 1852 | * Call a method on this object from the given point of view. We'll |
| | 1853 | * push the current point of view, call the method, then restore the |
| | 1854 | * enclosing point of view. |
| | 1855 | */ |
| | 1856 | fromPOV(pov, propToCall, [args]) |
| | 1857 | { |
| | 1858 | /* push the new point of view */ |
| | 1859 | pushPOV(pov); |
| | 1860 | |
| | 1861 | /* make sure we pop the point of view no matter how we leave */ |
| | 1862 | try |
| | 1863 | { |
| | 1864 | /* call the method */ |
| | 1865 | self.(propToCall)(args...); |
| | 1866 | } |
| | 1867 | finally |
| | 1868 | { |
| | 1869 | /* restore the enclosing point of view on the way out */ |
| | 1870 | popPOV(); |
| | 1871 | } |
| | 1872 | } |
| | 1873 | |
| | 1874 | /* |
| | 1875 | * Every Thing has a location, which is the Thing that contains this |
| | 1876 | * object. A Thing's location can only be a simple object |
| | 1877 | * reference, or nil; it cannot be a list, and it cannot be a method. |
| | 1878 | * |
| | 1879 | * If the location is nil, the object does not exist anywhere in the |
| | 1880 | * simulation's physical model. A nil location can be used to |
| | 1881 | * remove an object from the game world, temporarily or permanently. |
| | 1882 | * |
| | 1883 | * In general, the 'location' property should be declared for each |
| | 1884 | * statically defined object (explicitly or implicitly via the '+' |
| | 1885 | * syntax). 'location' is a private property - it should never be |
| | 1886 | * evaluated or changed by any subclass or by any other object. |
| | 1887 | * Only Thing methods may evaluate or change the 'location' |
| | 1888 | * property. So, you can declare a 'location' property when |
| | 1889 | * defining an object, but you should essentially never refer to |
| | 1890 | * 'location' directly in any other context; instead, use the |
| | 1891 | * location and containment methods (isIn, etc) when you want to |
| | 1892 | * know an object's location. |
| | 1893 | */ |
| | 1894 | location = nil |
| | 1895 | |
| | 1896 | /* |
| | 1897 | * Initialize my location's contents list - add myself to my |
| | 1898 | * container during initialization |
| | 1899 | */ |
| | 1900 | initializeLocation() |
| | 1901 | { |
| | 1902 | if (location != nil) |
| | 1903 | location.addToContents(self); |
| | 1904 | } |
| | 1905 | |
| | 1906 | /* |
| | 1907 | * My contents. This is a list of the objects that this object |
| | 1908 | * directly contains. |
| | 1909 | */ |
| | 1910 | contents = [] |
| | 1911 | |
| | 1912 | /* |
| | 1913 | * Show the contents of this object. If the object has any |
| | 1914 | * contents, we'll display a listing of the contents. |
| | 1915 | * |
| | 1916 | * 'options' is the set of flags that we'll pass to showList(), and |
| | 1917 | * has the same meaning as for that function. |
| | 1918 | * |
| | 1919 | * 'infoList' is a list of SenseInfoListEntry objects for the |
| | 1920 | * objects that the actor to whom we're showing the contents listing |
| | 1921 | * can see via the sight-like senses. |
| | 1922 | * |
| | 1923 | * This method should be overridden by any object that doesn't store |
| | 1924 | * its contents using a simple 'contents' list property. |
| | 1925 | */ |
| | 1926 | showObjectContents(pov, lister, options, indent, infoList) |
| | 1927 | { |
| | 1928 | local cont; |
| | 1929 | |
| | 1930 | /* add in the CONTENTS flag to the options */ |
| | 1931 | options |= LIST_CONTENTS; |
| | 1932 | |
| | 1933 | /* start with my direct contents */ |
| | 1934 | cont = contents; |
| | 1935 | |
| | 1936 | /* |
| | 1937 | * keep only the visible objects - only objects in the infoList |
| | 1938 | * are visible from the listing actor's point of view |
| | 1939 | */ |
| | 1940 | cont = cont.subset({x: infoList.indexWhich({y: y.obj == x}) != nil}); |
| | 1941 | |
| | 1942 | /* if the surviving list isn't empty, show it */ |
| | 1943 | if (cont != []) |
| | 1944 | showList(pov, self, lister, cont, options, indent, infoList); |
| | 1945 | } |
| | 1946 | |
| | 1947 | /* |
| | 1948 | * Determine if I'm is inside another Thing. Returns true if this |
| | 1949 | * object is contained within obj. |
| | 1950 | */ |
| | 1951 | isIn(obj) |
| | 1952 | { |
| | 1953 | /* if obj is my immediate container, I'm obviously in it */ |
| | 1954 | if (location == obj) |
| | 1955 | return true; |
| | 1956 | |
| | 1957 | /* if I have no location, I'm obviously not in obj */ |
| | 1958 | if (location == nil) |
| | 1959 | return nil; |
| | 1960 | |
| | 1961 | /* I'm in obj if my container is in obj */ |
| | 1962 | return location.isIn(obj); |
| | 1963 | } |
| | 1964 | |
| | 1965 | /* |
| | 1966 | * Determine if I'm directly inside another Thing. Returns true if |
| | 1967 | * this object is contained directly within obj. Returns nil if |
| | 1968 | * this object isn't directly within obj, even if it is indirectly |
| | 1969 | * in obj (i.e., its container is directly or indirectly in obj). |
| | 1970 | */ |
| | 1971 | isDirectlyIn(obj) |
| | 1972 | { |
| | 1973 | /* I'm directly in obj only if it's my immediate container */ |
| | 1974 | return location == obj; |
| | 1975 | } |
| | 1976 | |
| | 1977 | /* |
| | 1978 | * Add an object to my contents. |
| | 1979 | */ |
| | 1980 | addToContents(obj) |
| | 1981 | { |
| | 1982 | /* add the object to my contents list */ |
| | 1983 | contents += obj; |
| | 1984 | } |
| | 1985 | |
| | 1986 | /* |
| | 1987 | * Remove an object from my contents. |
| | 1988 | */ |
| | 1989 | removeFromContents(obj) |
| | 1990 | { |
| | 1991 | /* remove the object from my contents list */ |
| | 1992 | contents -= obj; |
| | 1993 | } |
| | 1994 | |
| | 1995 | /* |
| | 1996 | * Move this object to a new container. |
| | 1997 | */ |
| | 1998 | moveInto(newContainer) |
| | 1999 | { |
| | 2000 | /* if I have a container, remove myself from its contents list */ |
| | 2001 | if (location != nil) |
| | 2002 | location.removeFromContents(self); |
| | 2003 | |
| | 2004 | /* remember my new location */ |
| | 2005 | location = newContainer; |
| | 2006 | |
| | 2007 | /* note that I've been moved */ |
| | 2008 | isMoved = true; |
| | 2009 | |
| | 2010 | /* |
| | 2011 | * if I'm not being moved into nil, add myself to the |
| | 2012 | * container's contents |
| | 2013 | */ |
| | 2014 | if (location != nil) |
| | 2015 | location.addToContents(self); |
| | 2016 | } |
| | 2017 | |
| | 2018 | /* |
| | 2019 | * Get the visual sense information for this object from the current |
| | 2020 | * global point of view. If we have explicit sense information set |
| | 2021 | * with setSenseInfo, we'll return that; otherwise, we'll calculate |
| | 2022 | * the current sense information for the given point of view. |
| | 2023 | * Returns a SenseInfoListEntry object giving the information. |
| | 2024 | */ |
| | 2025 | getVisualSenseInfo() |
| | 2026 | { |
| | 2027 | local lst; |
| | 2028 | |
| | 2029 | /* if we have explicit sense information already set, use it */ |
| | 2030 | if (explicitVisualSenseInfo != nil) |
| | 2031 | return explicitVisualSenseInfo; |
| | 2032 | |
| | 2033 | /* calculate the sense information for the point of view */ |
| | 2034 | lst = getPOV().visibleInfoList(); |
| | 2035 | |
| | 2036 | /* find and return the information for myself */ |
| | 2037 | return lst.valWhich({x: x.obj == self}); |
| | 2038 | } |
| | 2039 | |
| | 2040 | /* |
| | 2041 | * Call a description method with explicit point-of-view and the |
| | 2042 | * related point-of-view sense information. 'pov' is the point of |
| | 2043 | * view object, which is usually an actor; 'senseInfo' is a |
| | 2044 | * SenseInfoListEntry object giving the sense information for this |
| | 2045 | * object, which getSenseInfo() will use instead of dynamically |
| | 2046 | * calculating the sense information for the duration of the routine |
| | 2047 | * called. |
| | 2048 | */ |
| | 2049 | withVisualSenseInfo(pov, senseInfo, methodToCall, [args]) |
| | 2050 | { |
| | 2051 | local oldSenseInfo; |
| | 2052 | |
| | 2053 | /* push the sense information */ |
| | 2054 | oldSenseInfo = setVisualSenseInfo(senseInfo); |
| | 2055 | |
| | 2056 | /* push the point of view */ |
| | 2057 | pushPOV(pov); |
| | 2058 | |
| | 2059 | /* make sure we restore the old value no matter how we leave */ |
| | 2060 | try |
| | 2061 | { |
| | 2062 | /* call the method with the given arguments */ |
| | 2063 | self.(methodToCall)(args...); |
| | 2064 | } |
| | 2065 | finally |
| | 2066 | { |
| | 2067 | /* restore the old point of view */ |
| | 2068 | popPOV(); |
| | 2069 | |
| | 2070 | /* restore the old sense information */ |
| | 2071 | setVisualSenseInfo(oldSenseInfo); |
| | 2072 | } |
| | 2073 | } |
| | 2074 | |
| | 2075 | /* |
| | 2076 | * Set the explicit visual sense information; if this is not nil, |
| | 2077 | * getVisualSenseInfo() will return this rather than calculating the |
| | 2078 | * live value. Returns the old value, which is a SenseInfoListEntry |
| | 2079 | * or nil. |
| | 2080 | */ |
| | 2081 | setVisualSenseInfo(info) |
| | 2082 | { |
| | 2083 | local oldInfo; |
| | 2084 | |
| | 2085 | /* remember the old value */ |
| | 2086 | oldInfo = explicitVisualSenseInfo; |
| | 2087 | |
| | 2088 | /* remember the new value */ |
| | 2089 | explicitVisualSenseInfo = info; |
| | 2090 | |
| | 2091 | /* return the original value */ |
| | 2092 | return oldInfo; |
| | 2093 | } |
| | 2094 | |
| | 2095 | /* current explicit visual sense information overriding live value */ |
| | 2096 | explicitVisualSenseInfo = nil |
| | 2097 | |
| | 2098 | /* |
| | 2099 | * Determine how accessible my contents are to a sense. Any items |
| | 2100 | * contained within a Thing are considered external features of the |
| | 2101 | * Thing, hence they are transparently accessible to all senses. |
| | 2102 | */ |
| | 2103 | transSensingIn(sense) { return transparent; } |
| | 2104 | |
| | 2105 | /* |
| | 2106 | * Determine how accessible peers of this object are to the contents |
| | 2107 | * of this object, via a given sense. This has the same meaning as |
| | 2108 | * transSensingIn(), but in the opposite direction: whereas |
| | 2109 | * transSensingIn() determines how accessible my contents are from |
| | 2110 | * the outside, this determines how accessible the outside is from |
| | 2111 | * the contents. |
| | 2112 | * |
| | 2113 | * By default, we simply return the same thing as transSensingIn(), |
| | 2114 | * since most containers are symmetrical for sense passing from |
| | 2115 | * inside to outside or outside to inside. However, we distinguish |
| | 2116 | * this as a separate method so that asymmetrical containers can |
| | 2117 | * have different effects in the different directions; for example, |
| | 2118 | * a box made of one-way mirrors might be transparent when looking |
| | 2119 | * from the inside to the outside, but opaque in the other |
| | 2120 | * direction. |
| | 2121 | */ |
| | 2122 | transSensingOut(sense) { return transSensingIn(sense); } |
| | 2123 | |
| | 2124 | /* |
| | 2125 | * Determine how well I can sense the given object. Returns a |
| | 2126 | * transparency level indicating how the sense passes from me to the |
| | 2127 | * given object. |
| | 2128 | * |
| | 2129 | * Returns a list consisting of the transparency level, the |
| | 2130 | * obstructor object, and the ambient sense energy level. If the |
| | 2131 | * path's transparency level is 'transparent' or 'opaque', the |
| | 2132 | * obstructor will always be nil; if the level is 'distant' or |
| | 2133 | * 'obscured', the obstructor will be the first object in the path |
| | 2134 | * that reduces the level. |
| | 2135 | * |
| | 2136 | * Note that, because 'distant' and 'obscured' transparency levels |
| | 2137 | * always compound (with one another and with themselves) to opaque, |
| | 2138 | * there will never be more than a single obstructor in a path, |
| | 2139 | * because any path with two or more obstructors would be an opaque |
| | 2140 | * path, and hence not a path at all. |
| | 2141 | */ |
| | 2142 | senseObj(sense, obj) |
| | 2143 | { |
| | 2144 | local bestTrans; |
| | 2145 | local bestObstructor; |
| | 2146 | local bestAmbient; |
| | 2147 | |
| | 2148 | /* we haven't found any path yet */ |
| | 2149 | bestTrans = opaque; |
| | 2150 | bestObstructor = nil; |
| | 2151 | bestAmbient = nil; |
| | 2152 | |
| | 2153 | /* |
| | 2154 | * Iterate over everything reachable through the sense. Keep |
| | 2155 | * track of the best path we find, and stop entirely if we find |
| | 2156 | * a transparent path. |
| | 2157 | */ |
| | 2158 | senseIter(sense, new function(cur, trans, obstructor, ambient) { |
| | 2159 | /* |
| | 2160 | * If this is the object we're looking for, and this is the |
| | 2161 | * best transparency so far, note the transparency to this |
| | 2162 | * point. |
| | 2163 | */ |
| | 2164 | if (cur == obj && transparencyCompare(trans, bestTrans) > 0) |
| | 2165 | { |
| | 2166 | /* this is the best one yet - note it */ |
| | 2167 | bestTrans = trans; |
| | 2168 | bestObstructor = obstructor; |
| | 2169 | bestAmbient = ambient; |
| | 2170 | |
| | 2171 | /* |
| | 2172 | * if this path is completely transparent, we will never |
| | 2173 | * find anything better, so return nil to tell the |
| | 2174 | * caller to stop iterating |
| | 2175 | */ |
| | 2176 | if (trans == transparent) |
| | 2177 | return nil; |
| | 2178 | } |
| | 2179 | |
| | 2180 | /* tell the caller to keep searching */ |
| | 2181 | return true; |
| | 2182 | }); |
| | 2183 | |
| | 2184 | /* return the best transparency we found */ |
| | 2185 | return [bestTrans, bestObstructor, bestAmbient]; |
| | 2186 | } |
| | 2187 | |
| | 2188 | /* |
| | 2189 | * Determine this object's level of illumination for the given |
| | 2190 | * senses. This returns the highest level of brightness of any |
| | 2191 | * energy source that this object can sense with the given senses, |
| | 2192 | * adjusted for transparency along the path to the energy source. |
| | 2193 | * |
| | 2194 | * This returns a brightness level with the same meaning as the |
| | 2195 | * 'brightness' property. |
| | 2196 | */ |
| | 2197 | illuminationLevel(senses) |
| | 2198 | { |
| | 2199 | local illum; |
| | 2200 | |
| | 2201 | /* we have no illumination so far */ |
| | 2202 | illum = 0; |
| | 2203 | |
| | 2204 | /* check each of the requested senses */ |
| | 2205 | foreach (local sense in senses) |
| | 2206 | { |
| | 2207 | local curIllum; |
| | 2208 | |
| | 2209 | /* get the ambient energy level for this sense */ |
| | 2210 | curIllum = senseAmbient(sense); |
| | 2211 | |
| | 2212 | /* if this is the highest so far, note it */ |
| | 2213 | if (curIllum != nil && curIllum > illum) |
| | 2214 | illum = curIllum; |
| | 2215 | } |
| | 2216 | |
| | 2217 | /* return the highest illumination level we found */ |
| | 2218 | return illum; |
| | 2219 | } |
| | 2220 | |
| | 2221 | /* |
| | 2222 | * Build a list of all of the objects reachable from me through the |
| | 2223 | * given sense. |
| | 2224 | */ |
| | 2225 | senseList(sense) |
| | 2226 | { |
| | 2227 | local lst; |
| | 2228 | |
| | 2229 | /* |
| | 2230 | * start out with an empty vector (use a vector for efficiency - |
| | 2231 | * since we'll be continually adding objects, a vector is a lot |
| | 2232 | * more efficient than a list since a vector can expand without |
| | 2233 | * being reallocated) |
| | 2234 | */ |
| | 2235 | lst = new Vector(32); |
| | 2236 | |
| | 2237 | /* |
| | 2238 | * Iterate over everything reachable through the sense, and add |
| | 2239 | * each reachable object to the list |
| | 2240 | */ |
| | 2241 | senseIter(sense, new function(cur, trans, obstructor, ambient) { |
| | 2242 | |
| | 2243 | /* add the object to the list so far */ |
| | 2244 | lst += cur; |
| | 2245 | |
| | 2246 | /* tell the caller to proceed with the iteration */ |
| | 2247 | return true; |
| | 2248 | }); |
| | 2249 | |
| | 2250 | /* return the list we built (as an ordinary list) */ |
| | 2251 | return lst.toList(); |
| | 2252 | } |
| | 2253 | |
| | 2254 | /* |
| | 2255 | * Build a list of full information on all of the objects reachable |
| | 2256 | * from me through the given sense, along with full information for |
| | 2257 | * each object's sense characteristics. For each object, the |
| | 2258 | * returned list will contain a SenseInfoList entry describing the |
| | 2259 | * sense conditions from the point of view of 'self' to the object. |
| | 2260 | */ |
| | 2261 | senseInfoList(sense) |
| | 2262 | { |
| | 2263 | local lst; |
| | 2264 | |
| | 2265 | /* start out with an empty vector */ |
| | 2266 | lst = new Vector(64); |
| | 2267 | |
| | 2268 | /* |
| | 2269 | * Iterate over everything reachable through the sense, and add |
| | 2270 | * each reachable object to the list |
| | 2271 | */ |
| | 2272 | senseIter(sense, new function(cur, trans, obstructor, ambient) { |
| | 2273 | |
| | 2274 | /* add the information to the list */ |
| | 2275 | lst += new SenseInfoListEntry(cur, trans, obstructor, ambient); |
| | 2276 | |
| | 2277 | /* tell the caller to proceed with the iteration */ |
| | 2278 | return true; |
| | 2279 | }); |
| | 2280 | |
| | 2281 | /* return the list we built (as an ordinary list) */ |
| | 2282 | return lst.toList(); |
| | 2283 | } |
| | 2284 | |
| | 2285 | /* |
| | 2286 | * Find an item in a senseInfoList list. Returns the |
| | 2287 | * SenseInfoListEntry which describes the given item, or nil if |
| | 2288 | * there is no such entry. |
| | 2289 | */ |
| | 2290 | findSenseInfo(lst, item) |
| | 2291 | { |
| | 2292 | /* scan the items in the list */ |
| | 2293 | return lst.valWhich({x: x.obj == item}); |
| | 2294 | } |
| | 2295 | |
| | 2296 | /* |
| | 2297 | * Merge two senseInfoList lists. Returns a new list containing |
| | 2298 | * only one instance of each item. If the same object appears in |
| | 2299 | * more than one of the lists, the result list will have the |
| | 2300 | * occurrence with better detail or brightness. |
| | 2301 | */ |
| | 2302 | mergeSenseInfoList(a, b) |
| | 2303 | { |
| | 2304 | local lst; |
| | 2305 | |
| | 2306 | /* if either list is empty, just return the other list */ |
| | 2307 | if (a == []) |
| | 2308 | return b; |
| | 2309 | else if (b == []) |
| | 2310 | return a; |
| | 2311 | |
| | 2312 | /* create a vector for the result list */ |
| | 2313 | lst = new Vector(max(a.length(), b.length())); |
| | 2314 | |
| | 2315 | /* |
| | 2316 | * loop over the items in the first list - keep each item in the |
| | 2317 | * first list, but if it also appears in the second list then |
| | 2318 | * merge it to keep the better of the two occurrences |
| | 2319 | */ |
| | 2320 | foreach (local aEle in a) |
| | 2321 | { |
| | 2322 | local keepA; |
| | 2323 | local bEle; |
| | 2324 | |
| | 2325 | /* assume we'll keep the occurrence from the first list */ |
| | 2326 | keepA = true; |
| | 2327 | |
| | 2328 | /* try finding this same item in the second list */ |
| | 2329 | if ((bEle = findSenseInfo(b, aEle)) != nil) |
| | 2330 | { |
| | 2331 | /* |
| | 2332 | * found it - keep the one with better transparency, or |
| | 2333 | * better ambient sense energy if the transparencies are |
| | 2334 | * the same |
| | 2335 | */ |
| | 2336 | if (aEle.trans == bEle.trans) |
| | 2337 | { |
| | 2338 | /* same transparency - compare energy levels */ |
| | 2339 | if (aEle.ambient < bEle.ambient) |
| | 2340 | keepA = nil; |
| | 2341 | } |
| | 2342 | else if (transparencyCompare(aEle.trans, bEle.trans) < 0) |
| | 2343 | { |
| | 2344 | /* a is less transparent than b */ |
| | 2345 | keepA = nil; |
| | 2346 | } |
| | 2347 | } |
| | 2348 | |
| | 2349 | /* keep the item from the appropriate list */ |
| | 2350 | if (keepA) |
| | 2351 | { |
| | 2352 | /* keep the item from the first list */ |
| | 2353 | lst += aEle; |
| | 2354 | } |
| | 2355 | else |
| | 2356 | { |
| | 2357 | /* keep the item from the second list */ |
| | 2358 | lst += bEle; |
| | 2359 | } |
| | 2360 | } |
| | 2361 | |
| | 2362 | /* |
| | 2363 | * loop over the second list, keeping only the items that aren't |
| | 2364 | * in the first list (any item that's also in the first list |
| | 2365 | * will already have been merged from the first pass) |
| | 2366 | */ |
| | 2367 | foreach (local bEle in b) |
| | 2368 | { |
| | 2369 | /* |
| | 2370 | * find the item in the first list - if it's not in the |
| | 2371 | * first list, we need to add this item to the result list |
| | 2372 | */ |
| | 2373 | if (findSenseInfo(a, bEle) == nil) |
| | 2374 | { |
| | 2375 | /* keep this item */ |
| | 2376 | lst += bEle; |
| | 2377 | } |
| | 2378 | } |
| | 2379 | |
| | 2380 | /* return the merged result in list format */ |
| | 2381 | return lst.toList(); |
| | 2382 | } |
| | 2383 | |
| | 2384 | /* |
| | 2385 | * Iterate over all objects reachable via the given sense. For each |
| | 2386 | * object, invokes the callback function. |
| | 2387 | * |
| | 2388 | * The callback returns true to continue the iteration, false to |
| | 2389 | * terminate it. |
| | 2390 | */ |
| | 2391 | senseIter(sense, func) |
| | 2392 | { |
| | 2393 | local ambient; |
| | 2394 | |
| | 2395 | /* |
| | 2396 | * compute the ambient sense energy level (such as the light |
| | 2397 | * level) at the starting point |
| | 2398 | */ |
| | 2399 | ambient = senseAmbient(sense); |
| | 2400 | |
| | 2401 | /* |
| | 2402 | * perform the iteration, starting with an empty path, an |
| | 2403 | * initial path transparency level of 'transparent', and no |
| | 2404 | * obstructor so far |
| | 2405 | */ |
| | 2406 | senseIterPath(sense, func, [], transparent, nil, ambient); |
| | 2407 | } |
| | 2408 | |
| | 2409 | /* |
| | 2410 | * Determine the ambient sense energy level for the given sense from |
| | 2411 | * my point of view. This is useful for senses such as sight, which |
| | 2412 | * can sense objects not only by the light they emit but also by the |
| | 2413 | * light they reflect. For sight, this returns the ambient energy |
| | 2414 | * level at me, taking into account all light sources visible from |
| | 2415 | * my point of view. |
| | 2416 | */ |
| | 2417 | senseAmbient(sense) |
| | 2418 | { |
| | 2419 | local best; |
| | 2420 | |
| | 2421 | /* if the sense doesn't use ambient energy, we can ignore this */ |
| | 2422 | if (sense.ambienceProp == nil) |
| | 2423 | return nil; |
| | 2424 | |
| | 2425 | /* we haven't seen any energy yet */ |
| | 2426 | best = 0; |
| | 2427 | |
| | 2428 | /* |
| | 2429 | * Iterate over all objects in line of sight from me, looking |
| | 2430 | * for energy sources. This is the correct direction to look, |
| | 2431 | * because we want to know if we can see an energy source - if |
| | 2432 | * we can, its energy reaches us and we are illuminated by it. |
| | 2433 | */ |
| | 2434 | senseIterPath(sense, new function(cur, trans, obstructor, ambient) { |
| | 2435 | |
| | 2436 | local br; |
| | 2437 | |
| | 2438 | /* |
| | 2439 | * if this object is a energy source, and the amount of |
| | 2440 | * energy that reaches us is greater than the brightest |
| | 2441 | * energy we've seen so far, note it as the strongest energy |
| | 2442 | * level reaching us |
| | 2443 | */ |
| | 2444 | br = adjustBrightness(cur.(sense.ambienceProp), trans); |
| | 2445 | if (br > best) |
| | 2446 | best = br; |
| | 2447 | |
| | 2448 | /* tell the caller to continue */ |
| | 2449 | return true; |
| | 2450 | |
| | 2451 | }, [], transparent, nil, nil); |
| | 2452 | |
| | 2453 | /* |
| | 2454 | * if the ambient level is level 1 (self-illuminating only), |
| | 2455 | * reduce it to nothing, since this doesn't cast energy on any |
| | 2456 | * of our surroundings and hence doesn't count as ambient energy |
| | 2457 | */ |
| | 2458 | if (best == 1) |
| | 2459 | best = 0; |
| | 2460 | |
| | 2461 | /* return the best brightness level we found */ |
| | 2462 | return best; |
| | 2463 | } |
| | 2464 | |
| | 2465 | /* |
| | 2466 | * Invoke the callback function for a sense iteration, passing self |
| | 2467 | * as the object. We'll call this for each object we visit in the |
| | 2468 | * course of the iteration. |
| | 2469 | */ |
| | 2470 | senseIterCallFunc(func, sense, trans, obstructor, ambient) |
| | 2471 | { |
| | 2472 | /* |
| | 2473 | * make some ambient energy adjustments if we have an ambient |
| | 2474 | * level at all (some senses do, some senses don't) |
| | 2475 | */ |
| | 2476 | if (ambient != nil) |
| | 2477 | { |
| | 2478 | local selfIllum; |
| | 2479 | |
| | 2480 | /* |
| | 2481 | * if this object's own self-illumination in this sense is |
| | 2482 | * greater than the ambient level, use the object's |
| | 2483 | * self-illumination level instead |
| | 2484 | */ |
| | 2485 | selfIllum = self.(sense.ambienceProp); |
| | 2486 | if (selfIllum > ambient) |
| | 2487 | ambient = selfIllum; |
| | 2488 | |
| | 2489 | /* adjust the energy level for the transparency */ |
| | 2490 | ambient = adjustBrightness(ambient, trans); |
| | 2491 | |
| | 2492 | /* |
| | 2493 | * if this leaves us with no energy at all, we can't sense |
| | 2494 | * the object after all |
| | 2495 | */ |
| | 2496 | if (ambient == 0) |
| | 2497 | return true; |
| | 2498 | } |
| | 2499 | |
| | 2500 | /* |
| | 2501 | * Check to see if we can be sensed at all under the current |
| | 2502 | * conditions. If we can, invoke the callback on this object. |
| | 2503 | * If we can't be sensed at all under these conditions, there's |
| | 2504 | * nothing more to do. |
| | 2505 | */ |
| | 2506 | if (canBeSensed(sense, trans, ambient)) |
| | 2507 | { |
| | 2508 | /* I can be sensed under these conditions - call the callback */ |
| | 2509 | return func(self, trans, obstructor, ambient); |
| | 2510 | } |
| | 2511 | else |
| | 2512 | { |
| | 2513 | /* |
| | 2514 | * I can't be sensed under these conditions - skip the |
| | 2515 | * callback and simply tell the caller to keep going |
| | 2516 | */ |
| | 2517 | return true; |
| | 2518 | } |
| | 2519 | } |
| | 2520 | |
| | 2521 | /* |
| | 2522 | * Iterate over objects that can be reached via the sense from this |
| | 2523 | * vantage. |
| | 2524 | * |
| | 2525 | * 'path' is a list of objects that we have already visited and thus |
| | 2526 | * must exclude from further searching. |
| | 2527 | * |
| | 2528 | * 'transToHere' is the transparency level up to this point. If |
| | 2529 | * we're being called recursively, this is the cumulative |
| | 2530 | * transparency level along the path so far. On the initial call, |
| | 2531 | * this should simply be 'transparent'. |
| | 2532 | * |
| | 2533 | * 'obstructor' is the object along the path so far that introduced |
| | 2534 | * a non-transparent level. On the initial call, this should be nil. |
| | 2535 | * |
| | 2536 | * 'ambient' is the ambient sense level along the path so far. Each |
| | 2537 | * time we traverse a transparent connection, we use the fact that |
| | 2538 | * the ambient level is the same on both sides of a transparent |
| | 2539 | * connection to avoid re-computing the ambient light level on the |
| | 2540 | * other side. On the initial call, this should be the ambient |
| | 2541 | * light level from self's point of view, as computed with |
| | 2542 | * senseAmbient(). |
| | 2543 | */ |
| | 2544 | senseIterPath(sense, func, path, transToHere, obstructor, ambient) |
| | 2545 | { |
| | 2546 | /* invoke the callback on myself */ |
| | 2547 | if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient)) |
| | 2548 | return nil; |
| | 2549 | |
| | 2550 | /* iterate up the containment hierarchy into my location */ |
| | 2551 | if (!senseIterUp(sense, func, path, transToHere, obstructor, ambient)) |
| | 2552 | return nil; |
| | 2553 | |
| | 2554 | /* sense down the hierarchy into my contents */ |
| | 2555 | return senseIterDown(sense, func, path, |
| | 2556 | transToHere, obstructor, ambient); |
| | 2557 | } |
| | 2558 | |
| | 2559 | /* |
| | 2560 | * Iterate over objects reachable up the hierarchy from here. |
| | 2561 | * |
| | 2562 | * Note that we do not invoke the function on self - the caller is |
| | 2563 | * responsible for doing this. |
| | 2564 | * |
| | 2565 | * This is a single-parent implementation. Multi-parent objects |
| | 2566 | * must override this method. |
| | 2567 | */ |
| | 2568 | senseIterUp(sense, func, path, transToHere, obstructor, ambient) |
| | 2569 | { |
| | 2570 | /* |
| | 2571 | * if I have no location, there's no way up from here - simply |
| | 2572 | * return true in this case, so the caller knows to proceed with |
| | 2573 | * further iteration |
| | 2574 | */ |
| | 2575 | if (location == nil) |
| | 2576 | return true; |
| | 2577 | |
| | 2578 | /* |
| | 2579 | * if my container is already in the path, don't consider it, |
| | 2580 | * since we'd be getting into an infinite loop by going back to |
| | 2581 | * a location we've already considered |
| | 2582 | */ |
| | 2583 | if (path.indexOf(location) != nil) |
| | 2584 | return true; |
| | 2585 | |
| | 2586 | /* add myself to the path, so that we don't loop back into me */ |
| | 2587 | path += self; |
| | 2588 | |
| | 2589 | /* look up into the container */ |
| | 2590 | return location. |
| | 2591 | senseIterFromBelow(sense, func, path, |
| | 2592 | transToHere, obstructor, ambient); |
| | 2593 | } |
| | 2594 | |
| | 2595 | /* |
| | 2596 | * Iterate down the containment hierarchy into my contents, visiting |
| | 2597 | * all of the objects reachable through the sense. |
| | 2598 | * |
| | 2599 | * Note that we do not invoke the callback on self - the caller is |
| | 2600 | * responsible for doing this. |
| | 2601 | */ |
| | 2602 | senseIterDown(sense, func, path, transToHere, obstructor, ambient) |
| | 2603 | { |
| | 2604 | local myTrans; |
| | 2605 | |
| | 2606 | /* |
| | 2607 | * figure my transparency looking into my contents, and note if |
| | 2608 | * I'm the first obstructor of the sense so far |
| | 2609 | */ |
| | 2610 | myTrans = transSensingIn(sense); |
| | 2611 | if (myTrans != transparent && obstructor == nil) |
| | 2612 | obstructor = self; |
| | 2613 | |
| | 2614 | /* |
| | 2615 | * add the transparency so far to the transparency to my own |
| | 2616 | * contents to get the cumulative transparency from the starting |
| | 2617 | * point to my contents |
| | 2618 | */ |
| | 2619 | transToHere = transparencyAdd(transToHere, myTrans); |
| | 2620 | |
| | 2621 | /* |
| | 2622 | * if I am opaque from the outside, we cannot iterate into my |
| | 2623 | * contents - in this case, just return true to tell the caller |
| | 2624 | * to proceed with its own further iteration |
| | 2625 | */ |
| | 2626 | if (transToHere == opaque) |
| | 2627 | return true; |
| | 2628 | |
| | 2629 | /* |
| | 2630 | * if we have an ambient level, check the transparency looking |
| | 2631 | * out from my contents (we look out rather than in, because in |
| | 2632 | * this case the sense is traveling from the outside to the |
| | 2633 | * inside, hence we want to know what it looks like from the |
| | 2634 | * point of view of the contents) |
| | 2635 | */ |
| | 2636 | if (ambient != nil) |
| | 2637 | { |
| | 2638 | /* |
| | 2639 | * get the transparency looking from inside to outside - if |
| | 2640 | * it's not transparent, we must re-calculate the ambient |
| | 2641 | * sense level within our contents - do so for our first |
| | 2642 | * child only, since all of our children will be at the same |
| | 2643 | * ambient level |
| | 2644 | */ |
| | 2645 | if (transSensingOut(sense) != transparent |
| | 2646 | && contents.length() != 0) |
| | 2647 | { |
| | 2648 | /* re-calculate the ambient level for our first child */ |
| | 2649 | ambient = contents[1].senseAmbient(sense); |
| | 2650 | } |
| | 2651 | } |
| | 2652 | |
| | 2653 | /* iterate through my contents */ |
| | 2654 | return senseIterList(sense, func, path, transToHere, obstructor, |
| | 2655 | ambient, contents, &senseIterFromAbove); |
| | 2656 | } |
| | 2657 | |
| | 2658 | /* |
| | 2659 | * Iterate through a given list of objects related to us in some way |
| | 2660 | * (contents, for example, or multiple containers), invoking the |
| | 2661 | * callback on the objects in the list and the objects further |
| | 2662 | * related to them. |
| | 2663 | */ |
| | 2664 | senseIterList(sense, func, path, transToHere, obstructor, ambient, |
| | 2665 | objList, objMethod) |
| | 2666 | { |
| | 2667 | /* add myself to the path, so that we don't loop back into me */ |
| | 2668 | path += self; |
| | 2669 | |
| | 2670 | /* iterate over the list */ |
| | 2671 | foreach (local cur in objList) |
| | 2672 | { |
| | 2673 | /* |
| | 2674 | * if this object is in the path already, don't consider it |
| | 2675 | * - this would get us into a loop, since we've been this |
| | 2676 | * way once before |
| | 2677 | */ |
| | 2678 | if (path.indexOf(cur) != nil) |
| | 2679 | continue; |
| | 2680 | |
| | 2681 | /* proceed into this object */ |
| | 2682 | if (!cur.(objMethod)(sense, func, path, |
| | 2683 | transToHere, obstructor, ambient)) |
| | 2684 | return nil; |
| | 2685 | } |
| | 2686 | |
| | 2687 | /* |
| | 2688 | * we didn't have anyone tell us to stop, so tell the caller to |
| | 2689 | * proceed |
| | 2690 | */ |
| | 2691 | return true; |
| | 2692 | } |
| | 2693 | |
| | 2694 | /* |
| | 2695 | * Iterate over objects reachable from here, traversing into this |
| | 2696 | * object from below us in the containment hierarchy - in other |
| | 2697 | * words, from a child of this object (something contained within |
| | 2698 | * this object). |
| | 2699 | * |
| | 2700 | * When traversing the tree from below, we'll scan both up and down |
| | 2701 | * the hierarchy from here. |
| | 2702 | */ |
| | 2703 | senseIterFromBelow(sense, func, path, transToHere, obstructor, ambient) |
| | 2704 | { |
| | 2705 | local myTrans; |
| | 2706 | |
| | 2707 | /* |
| | 2708 | * If the ambient level is -1, this is a special flag indicating |
| | 2709 | * that we need to recalculate the ambient level from our point |
| | 2710 | * of view. Callers will set this value when traversing a |
| | 2711 | * connection that invalidates the ambient level and they are |
| | 2712 | * unable to calculate the new level themselves. |
| | 2713 | */ |
| | 2714 | if (ambient == -1) |
| | 2715 | ambient = senseAmbient(sense); |
| | 2716 | |
| | 2717 | /* |
| | 2718 | * Iterate over other children of the same container. Items in |
| | 2719 | * the same container are not affected by the transparency |
| | 2720 | * properties of the container itself, since the sense doesn't |
| | 2721 | * have to pass through the container to go from one child to |
| | 2722 | * another. |
| | 2723 | */ |
| | 2724 | if (!senseIterList(sense, func, path, transToHere, obstructor, |
| | 2725 | ambient, contents, &senseIterFromAbove)) |
| | 2726 | return nil; |
| | 2727 | |
| | 2728 | /* |
| | 2729 | * Regardless of whether we can sense through the container or |
| | 2730 | * not, we can certainly sense the container itself (its |
| | 2731 | * insides, anyway), so invoke the callback on the container |
| | 2732 | * without further loss of transparency |
| | 2733 | */ |
| | 2734 | if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient)) |
| | 2735 | return nil; |
| | 2736 | |
| | 2737 | /* |
| | 2738 | * Note the transparency going through my containment boundary, |
| | 2739 | * passing from inside to outside; if I'm the first |
| | 2740 | * non-transparent boundary on this path, note that I'm the |
| | 2741 | * obstructor |
| | 2742 | */ |
| | 2743 | myTrans = transSensingOut(sense); |
| | 2744 | if (myTrans != transparent && obstructor == nil) |
| | 2745 | obstructor = self; |
| | 2746 | |
| | 2747 | /* |
| | 2748 | * Accumulate the transparency of my boundary into the total |
| | 2749 | * transparency level to this point. If this makes the total |
| | 2750 | * path opaque, we cannot sense anything above us. |
| | 2751 | */ |
| | 2752 | transToHere = transparencyAdd(transToHere, myTrans); |
| | 2753 | if (transToHere != opaque) |
| | 2754 | { |
| | 2755 | /* |
| | 2756 | * if we have an ambient level, check the transparency |
| | 2757 | * looking in to my contents (we look in rather than out, |
| | 2758 | * because in this case the sense is traveling from the |
| | 2759 | * inside to the outside, hence we want to know what it |
| | 2760 | * looks like from the point of view of the container) |
| | 2761 | */ |
| | 2762 | if (ambient != nil |
| | 2763 | && transSensingIn(sense) != transparent) |
| | 2764 | { |
| | 2765 | /* re-calculate the ambient level here */ |
| | 2766 | ambient = senseAmbient(sense); |
| | 2767 | } |
| | 2768 | |
| | 2769 | /* iterate up through the container */ |
| | 2770 | if (!senseIterUp(sense, func, path, |
| | 2771 | transToHere, obstructor, ambient)) |
| | 2772 | return nil; |
| | 2773 | } |
| | 2774 | |
| | 2775 | /* we didn't find any reason to stop the caller from continuing */ |
| | 2776 | return true; |
| | 2777 | } |
| | 2778 | |
| | 2779 | /* |
| | 2780 | * Iterate into this object, coming from above the object in the |
| | 2781 | * containment hierarchy - in other words, from a parent (a |
| | 2782 | * container) of this object. |
| | 2783 | * |
| | 2784 | * When traversing the tree from above, we'll scan only down from |
| | 2785 | * here. An object that exists in multiple containers does not by |
| | 2786 | * default serve as a sense conduit across those containers, so the |
| | 2787 | * fact that it has other locations is irrelevant to the search. |
| | 2788 | * Connectors override this, because they do connect all of their |
| | 2789 | * locations as sense conduits and hence must look back up the tree |
| | 2790 | * as well as down when entered from above. |
| | 2791 | */ |
| | 2792 | senseIterFromAbove(sense, func, path, transToHere, obstructor, ambient) |
| | 2793 | { |
| | 2794 | /* invoke the callback on myself */ |
| | 2795 | if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient)) |
| | 2796 | return nil; |
| | 2797 | |
| | 2798 | /* iterate into my contents */ |
| | 2799 | return senseIterDown(sense, func, path, |
| | 2800 | transToHere, obstructor, ambient); |
| | 2801 | } |
| | 2802 | ; |
| | 2803 | |
| | 2804 | |
| | 2805 | /* ------------------------------------------------------------------------ */ |
| | 2806 | /* |
| | 2807 | * MultiLoc: this class can be multiply inherited by any object that |
| | 2808 | * must exist in more than one place at a time. To use this class, put |
| | 2809 | * it BEFORE Thing (or any subclass of Thing) in the object's superclass |
| | 2810 | * list, to ensure that we override the default containment |
| | 2811 | * implementation for the object. |
| | 2812 | */ |
| | 2813 | class MultiLoc: object |
| | 2814 | /* |
| | 2815 | * We can be in any number of locations. Our location must be given |
| | 2816 | * as a list. |
| | 2817 | */ |
| | 2818 | locationList = [] |
| | 2819 | |
| | 2820 | /* |
| | 2821 | * Initialize my location's contents list - add myself to my |
| | 2822 | * container during initialization |
| | 2823 | */ |
| | 2824 | initializeLocation() |
| | 2825 | { |
| | 2826 | /* |
| | 2827 | * Add myself to each of my container's contents lists |
| | 2828 | */ |
| | 2829 | locationList.forEach({loc: loc.addToContents(self)}); |
| | 2830 | } |
| | 2831 | |
| | 2832 | /* |
| | 2833 | * Determine if I'm in a given object, directly or indirectly |
| | 2834 | */ |
| | 2835 | isIn(obj) |
| | 2836 | { |
| | 2837 | /* first, check to see if I'm directly in the given object */ |
| | 2838 | if (isDirectlyIn(obj)) |
| | 2839 | return true; |
| | 2840 | |
| | 2841 | /* |
| | 2842 | * Look at each object in my location list. For each location |
| | 2843 | * object, if the location is within the object, I'm within the |
| | 2844 | * object. |
| | 2845 | */ |
| | 2846 | return locationList.indexWhich({loc: loc.isIn(obj)}) != nil; |
| | 2847 | } |
| | 2848 | |
| | 2849 | /* |
| | 2850 | * Determine if I'm directly in the given object |
| | 2851 | */ |
| | 2852 | isDirectlyIn(obj) |
| | 2853 | { |
| | 2854 | /* |
| | 2855 | * we're directly in the given object only if the object is in |
| | 2856 | * my list of immediate locations |
| | 2857 | */ |
| | 2858 | return (locationList.indexOf(obj) != nil); |
| | 2859 | } |
| | 2860 | |
| | 2861 | /* |
| | 2862 | * Note that we don't need to override any of the contents |
| | 2863 | * management methods, since we provide special handling for our |
| | 2864 | * location relationships, not for our contents relationships. |
| | 2865 | */ |
| | 2866 | |
| | 2867 | /* |
| | 2868 | * Move this object into a given single container. Removes the |
| | 2869 | * object from all of its other containers. |
| | 2870 | */ |
| | 2871 | moveInto(newContainer) |
| | 2872 | { |
| | 2873 | /* remove myself from all of my current contents */ |
| | 2874 | locationList.forEach({loc: loc.removeFromContents(self)}); |
| | 2875 | |
| | 2876 | /* set my location list to include only the new location */ |
| | 2877 | locationList = [newContainer]; |
| | 2878 | |
| | 2879 | /* note that I've been moved */ |
| | 2880 | isMoved = true; |
| | 2881 | |
| | 2882 | /* add myself to my new container's contents */ |
| | 2883 | newContainer.addToContents(self); |
| | 2884 | } |
| | 2885 | |
| | 2886 | /* |
| | 2887 | * Add this object to a new location. |
| | 2888 | */ |
| | 2889 | moveIntoAdd(newContainer) |
| | 2890 | { |
| | 2891 | /* note that I've been moved */ |
| | 2892 | isMoved = true; |
| | 2893 | |
| | 2894 | /* add the new container to my list of locations */ |
| | 2895 | locationList += newContainer; |
| | 2896 | |
| | 2897 | /* add myself to my new container's contents */ |
| | 2898 | newContainer.addToContents(self); |
| | 2899 | } |
| | 2900 | |
| | 2901 | /* |
| | 2902 | * Remove myself from a given container, leaving myself in any other |
| | 2903 | * containers. |
| | 2904 | */ |
| | 2905 | moveOutOf(cont) |
| | 2906 | { |
| | 2907 | /* if I'm not actually directly in this container, do nothing */ |
| | 2908 | if (!isDirectlyIn(cont)) |
| | 2909 | return; |
| | 2910 | |
| | 2911 | /* remove myself from this container's contents list */ |
| | 2912 | cont.removeFromContents(self); |
| | 2913 | |
| | 2914 | /* note that I've been moved */ |
| | 2915 | isMoved = true; |
| | 2916 | |
| | 2917 | /* remove this container from my location list */ |
| | 2918 | locationList -= cont; |
| | 2919 | } |
| | 2920 | |
| | 2921 | /* |
| | 2922 | * Look up the containment hierarchy at my parents, trying to find a |
| | 2923 | * path to the given object. |
| | 2924 | */ |
| | 2925 | senseIterUp(sense, func, path, transToHere, obstructor, ambient) |
| | 2926 | { |
| | 2927 | /* iterate over all of my locations */ |
| | 2928 | return senseIterList(sense, func, path, transToHere, obstructor, |
| | 2929 | ambient, locationList, &senseIterFromBelow); |
| | 2930 | } |
| | 2931 | ; |
| | 2932 | |
| | 2933 | /* ------------------------------------------------------------------------ */ |
| | 2934 | /* |
| | 2935 | * Multi-Location item with automatic initialization. This is a |
| | 2936 | * subclass of MultiLoc for objects with computed initial locations. |
| | 2937 | * Each instance must provide one of the following: |
| | 2938 | * |
| | 2939 | * - Override initializeLocation() with a method that initializes the |
| | 2940 | * location list by calling moveIntoAdd() for each location. If this |
| | 2941 | * method isn't overridden, the default implementation will initialize |
| | 2942 | * the location list using initialLocationClass and/or isInitiallyIn(), |
| | 2943 | * as described below. |
| | 2944 | * |
| | 2945 | * - Define the method isInitiallyIn(loc) to return true if the 'loc' is |
| | 2946 | * one of the object's initial containers, nil if not. The default |
| | 2947 | * implementation of this method simply returns true, so if this isn't |
| | 2948 | * overridden, every object matching the initialLocationClass() will be |
| | 2949 | * part of the contents list. |
| | 2950 | * |
| | 2951 | * - Define the property initialLocationClass as a class object. We |
| | 2952 | * will add each instance of the class that passes the isInitiallyIn() |
| | 2953 | * test to the location list. If this is nil, we'll test every object |
| | 2954 | * instance with the isInitiallyIn() method. |
| | 2955 | */ |
| | 2956 | class AutoMultiLoc: MultiLoc |
| | 2957 | /* initialize the location */ |
| | 2958 | initializeLocation() |
| | 2959 | { |
| | 2960 | /* get the list of locations */ |
| | 2961 | locationList = buildLocationList(); |
| | 2962 | |
| | 2963 | /* add ourselves into each of our containers */ |
| | 2964 | foreach (local loc in locationList) |
| | 2965 | loc.addToContents(self); |
| | 2966 | } |
| | 2967 | |
| | 2968 | /* |
| | 2969 | * build my list of locations, and return the list |
| | 2970 | */ |
| | 2971 | buildLocationList() |
| | 2972 | { |
| | 2973 | local lst; |
| | 2974 | |
| | 2975 | /* we have nothing in our list yet */ |
| | 2976 | lst = new Vector(16); |
| | 2977 | |
| | 2978 | /* |
| | 2979 | * if initialLocationClass is defined, loop over all objects of |
| | 2980 | * that class; otherwise, loop over all objects |
| | 2981 | */ |
| | 2982 | if (initialLocationClass != nil) |
| | 2983 | { |
| | 2984 | /* loop over all instances of the given class */ |
| | 2985 | for (local obj = firstObj(initialLocationClass) ; obj != nil ; |
| | 2986 | obj = nextObj(obj, initialLocationClass)) |
| | 2987 | { |
| | 2988 | /* if the object passes the test, include it */ |
| | 2989 | if (isInitiallyIn(obj)) |
| | 2990 | lst += obj; |
| | 2991 | } |
| | 2992 | } |
| | 2993 | else |
| | 2994 | { |
| | 2995 | /* loop over all objects */ |
| | 2996 | for (local obj = firstObj() ; obj != nil ; obj = nextObj(obj)) |
| | 2997 | { |
| | 2998 | /* if the object passes the test, include it */ |
| | 2999 | if (isInitiallyIn(obj)) |
| | 3000 | lst += obj; |
| | 3001 | } |
| | 3002 | } |
| | 3003 | |
| | 3004 | /* return the list of locations */ |
| | 3005 | return lst.toList(); |
| | 3006 | } |
| | 3007 | |
| | 3008 | /* |
| | 3009 | * Class of our locations. If this is nil, we'll test every object |
| | 3010 | * in the entire game with our isInitiallyIn() method; otherwise, |
| | 3011 | * we'll test only objects of the given class. |
| | 3012 | */ |
| | 3013 | initialLocationClass = nil |
| | 3014 | |
| | 3015 | /* |
| | 3016 | * Test an object for inclusion in our initial location list. By |
| | 3017 | * default, we'll simply return true to include every object. We |
| | 3018 | * return true by default so that an instance can merely specify a |
| | 3019 | * value for initialLocationClass in order to place this object in |
| | 3020 | * every instance of the given class. |
| | 3021 | */ |
| | 3022 | isInitiallyIn(obj) { return true; } |
| | 3023 | ; |
| | 3024 | |
| | 3025 | /* |
| | 3026 | * Dynamic Multi Location Item. This is almost exactly the same as a |
| | 3027 | * regular multi-location item with automatic initialization, but the |
| | 3028 | * library will re-initialize the location of these items, by calling |
| | 3029 | * the object's reInitializeLocation(), at the start of every turn. |
| | 3030 | */ |
| | 3031 | class DynamicMultiLoc: AutoMultiLoc |
| | 3032 | reInitializeLocation() |
| | 3033 | { |
| | 3034 | local newList; |
| | 3035 | |
| | 3036 | /* build the new location list */ |
| | 3037 | newList = buildLocationList(); |
| | 3038 | |
| | 3039 | /* |
| | 3040 | * Update any containers that are not in the intersection of the |
| | 3041 | * two lists. Note that we don't simply move ourselves out of |
| | 3042 | * the old list and into the new list, because the two lists |
| | 3043 | * could have common members; to avoid unnecessary work that |
| | 3044 | * might result from removing ourselves from a container and |
| | 3045 | * then adding ourselves right back in to the same container, we |
| | 3046 | * only notify containers when we're actually moving out or |
| | 3047 | * moving in. |
| | 3048 | */ |
| | 3049 | |
| | 3050 | /* |
| | 3051 | * For each item in the old list, if it's not in the new list, |
| | 3052 | * notify the old container that we're being removed. |
| | 3053 | */ |
| | 3054 | foreach (local loc in locationList) |
| | 3055 | { |
| | 3056 | /* if it's not in the new list, remove me from the container */ |
| | 3057 | if (newList.indexOf(loc) == nil) |
| | 3058 | loc.removeFromContents(self); |
| | 3059 | } |
| | 3060 | |
| | 3061 | /* |
| | 3062 | * for each item in the new list, if we weren't already in this |
| | 3063 | * location, add ourselves to the location |
| | 3064 | */ |
| | 3065 | foreach (local loc in newList) |
| | 3066 | { |
| | 3067 | /* if it's not in the old list, add me to the new container */ |
| | 3068 | if (!isDirectlyIn(loc) == nil) |
| | 3069 | loc.addToContents(self); |
| | 3070 | } |
| | 3071 | |
| | 3072 | /* make the new location list current */ |
| | 3073 | locationList = newList; |
| | 3074 | } |
| | 3075 | ; |
| | 3076 | |
| | 3077 | |
| | 3078 | /* ------------------------------------------------------------------------ */ |
| | 3079 | /* |
| | 3080 | * Openable: a mix-in class that can be combined with an object's other |
| | 3081 | * superclasses to make the object respond to the verbs "open" and |
| | 3082 | * "close." |
| | 3083 | */ |
| | 3084 | class Openable: object |
| | 3085 | ; |
| | 3086 | |
| | 3087 | /* ------------------------------------------------------------------------ */ |
| | 3088 | /* |
| | 3089 | * Lockable: a mix-in class that can be combined with an object's other |
| | 3090 | * superclasses to make the object respond to the verbs "lock" and |
| | 3091 | * "unlock." A Lockable requires no key. |
| | 3092 | * |
| | 3093 | * Note that Lockable and LockableWithKey are mutually exclusive - an |
| | 3094 | * object can inherit from only one of these classes. |
| | 3095 | */ |
| | 3096 | class Lockable: object |
| | 3097 | ; |
| | 3098 | |
| | 3099 | /* ------------------------------------------------------------------------ */ |
| | 3100 | /* |
| | 3101 | * LockableWithKey: a mix-in class that can be combined with an object's |
| | 3102 | * other superclasses to make the object respond to the verbs "lock" and |
| | 3103 | * "unlock," with a key as an indirect object. A LockableWithKey cannot |
| | 3104 | * be locked or unlocked except with the keys listed in the keyList |
| | 3105 | * property. |
| | 3106 | * |
| | 3107 | * Note that Lockable and LockableWithKey are mutually exclusive - an |
| | 3108 | * object can inherit from only one of these classes. |
| | 3109 | */ |
| | 3110 | class LockableWithKey: object |
| | 3111 | /* the list of objects that can serve as keys for this object */ |
| | 3112 | keyList = [] |
| | 3113 | ; |
| | 3114 | |
| | 3115 | /* ------------------------------------------------------------------------ */ |
| | 3116 | /* |
| | 3117 | * Container: an object that can have other objects placed within it. |
| | 3118 | */ |
| | 3119 | class Container: Thing |
| | 3120 | /* |
| | 3121 | * My current open/closed state. By default, this state never |
| | 3122 | * changes, but is fixed in the object's definition; for example, a |
| | 3123 | * box without a lid would always be open, while a hollow glass cube |
| | 3124 | * would always be closed. Our default state is open. |
| | 3125 | */ |
| | 3126 | isOpen = true |
| | 3127 | |
| | 3128 | /* the material that the container is made of */ |
| | 3129 | material = adventium |
| | 3130 | |
| | 3131 | /* |
| | 3132 | * Determine how a sense passes to my contents. If I'm open, the |
| | 3133 | * sense passes through directly, since there's nothing in the way. |
| | 3134 | * If I'm closed, the sense must pass through my material. |
| | 3135 | */ |
| | 3136 | transSensingIn(sense) |
| | 3137 | { |
| | 3138 | if (isOpen) |
| | 3139 | { |
| | 3140 | /* I'm open, so the sense passes through without interference */ |
| | 3141 | return transparent; |
| | 3142 | } |
| | 3143 | else |
| | 3144 | { |
| | 3145 | /* I'm closed, so the sense must pass through my material */ |
| | 3146 | return material.senseThru(sense); |
| | 3147 | } |
| | 3148 | } |
| | 3149 | ; |
| | 3150 | |
| | 3151 | /* ------------------------------------------------------------------------ */ |
| | 3152 | /* |
| | 3153 | * OpenableContainer: an object that can contain things, and which can |
| | 3154 | * be opened and closed. |
| | 3155 | */ |
| | 3156 | class OpenableContainer: Openable, Container |
| | 3157 | ; |
| | 3158 | |
| | 3159 | /* ------------------------------------------------------------------------ */ |
| | 3160 | /* |
| | 3161 | * LockableContainer: an object that can contain things, and that can be |
| | 3162 | * opened and closed as well as locked and unlocked. |
| | 3163 | */ |
| | 3164 | class LockableContainer: Lockable, OpenableContainer |
| | 3165 | ; |
| | 3166 | |
| | 3167 | /* ------------------------------------------------------------------------ */ |
| | 3168 | /* |
| | 3169 | * KeyedContainer: an openable container that can be locked and |
| | 3170 | * unlocked, but only with a specified key. |
| | 3171 | */ |
| | 3172 | class KeyedContainer: LockableWithKey, OpenableContainer |
| | 3173 | ; |
| | 3174 | |
| | 3175 | |
| | 3176 | /* ------------------------------------------------------------------------ */ |
| | 3177 | /* |
| | 3178 | * Surface: an object that can have other objects placed on top of it. |
| | 3179 | * A surface is essentially the same as a regular container, but the |
| | 3180 | * contents of a surface behave as though they are on the surface's top |
| | 3181 | * rather than contained within the object. |
| | 3182 | */ |
| | 3183 | class Surface: Thing |
| | 3184 | /* my contents lister */ |
| | 3185 | contentsLister = surfaceContentsLister |
| | 3186 | ; |
| | 3187 | |
| | 3188 | |
| | 3189 | /* ------------------------------------------------------------------------ */ |
| | 3190 | /* |
| | 3191 | * Room: the basic class for game locations. This is the smallest unit |
| | 3192 | * of movement; we do not distinguish among locations within a room, |
| | 3193 | * even if a Room represents a physically large location. If it is |
| | 3194 | * necessary to distinguish among different locations in a large |
| | 3195 | * physical room, simply divide the physical room into sections and |
| | 3196 | * represent each section with a separate Room object. |
| | 3197 | * |
| | 3198 | * A Room is not necessarily indoors; it is simply a location where an |
| | 3199 | * actor can be located. This peculiar usage of "room" to denote any |
| | 3200 | * atomic location, even outdoors, was adopted by the authors of the |
| | 3201 | * earliest adventure games, and has been the convention ever since. |
| | 3202 | * |
| | 3203 | * A room's contents are the objects contained directly within the room. |
| | 3204 | * These include fixed features of the room as well as loose items in |
| | 3205 | * the room, which are effectively "on the floor" in the room. |
| | 3206 | */ |
| | 3207 | class Room: Thing |
| | 3208 | /* |
| | 3209 | * Most rooms provide their own implicit lighting. We'll use |
| | 3210 | * 'medium' lighting (level 3) by default, which provides enough |
| | 3211 | * light for all activities, but is reduced to dim light (level 2) |
| | 3212 | * when it goes through obscuring media or over distance. |
| | 3213 | */ |
| | 3214 | brightness = 3 |
| | 3215 | |
| | 3216 | /* |
| | 3217 | * Flag: we've seen this location before. We'll set this to true |
| | 3218 | * the first time we see the location (as long as the location isn't |
| | 3219 | * dark). |
| | 3220 | */ |
| | 3221 | isSeen = nil |
| | 3222 | |
| | 3223 | /* |
| | 3224 | * The room's long description. This is displayed in a "verbose" |
| | 3225 | * display of the room. |
| | 3226 | */ |
| | 3227 | lDesc = "" |
| | 3228 | |
| | 3229 | /* |
| | 3230 | * The room's first description. This is the description we'll |
| | 3231 | * display the first time we see the room when it isn't dark (it's |
| | 3232 | * the first time if isSeen is nil). By default, we'll just display |
| | 3233 | * the normal long description, but a room can override this if it |
| | 3234 | * wants a special description on the first non-dark arrival. |
| | 3235 | */ |
| | 3236 | firstDesc { lDesc; } |
| | 3237 | |
| | 3238 | /* |
| | 3239 | * Display my short description when the room is dark. By default, |
| | 3240 | * we display a standard library message. |
| | 3241 | */ |
| | 3242 | darkSDesc { libMessages.roomDarkSDesc; } |
| | 3243 | |
| | 3244 | /* |
| | 3245 | * Display my long description when the room is dark. Note that |
| | 3246 | * authors must take special care to list items that are both |
| | 3247 | * self-illuminating and marked as non-listed (isListed = nil) here, |
| | 3248 | * because such items will not be otherwise displayed (non-listed |
| | 3249 | * items are non-listed in a dark room, just like in a lit room). |
| | 3250 | */ |
| | 3251 | darkLDesc { libMessages.roomDarkLDesc; } |
| | 3252 | |
| | 3253 | /* |
| | 3254 | * Describe the room. Produces the full description of the room if |
| | 3255 | * 'verbose' is true, or a briefer description if not. |
| | 3256 | * |
| | 3257 | * Note that this method must be overridden if the room uses a |
| | 3258 | * non-conventional contents mechanism (i.e., it doesn't store its |
| | 3259 | * complete set of direct contents in a 'contents' list property). |
| | 3260 | */ |
| | 3261 | lookAround(actor, verbose) |
| | 3262 | { |
| | 3263 | local illum; |
| | 3264 | local infoList; |
| | 3265 | |
| | 3266 | /* |
| | 3267 | * get a list of all of the objects that the actor can sense in |
| | 3268 | * the location using sight-like senses (such as sight) |
| | 3269 | */ |
| | 3270 | infoList = actor.visibleInfoList(); |
| | 3271 | |
| | 3272 | /* |
| | 3273 | * drop the actor, to ensure that we don't list the actor doing |
| | 3274 | * the looking among the room's contents |
| | 3275 | */ |
| | 3276 | infoList = infoList.subset({x: x.obj != actor}); |
| | 3277 | |
| | 3278 | /* |
| | 3279 | * check for ambient illumination in the room for the actor's |
| | 3280 | * sight-like senses |
| | 3281 | */ |
| | 3282 | illum = illuminationLevel(actor.sightlikeSenses); |
| | 3283 | |
| | 3284 | /* display the short description */ |
| | 3285 | "<.roomname>"; |
| | 3286 | statusDescIllum(actor, illum); |
| | 3287 | "<./roomname>"; |
| | 3288 | |
| | 3289 | /* if we're in verbose mode, display the full description */ |
| | 3290 | if (verbose) |
| | 3291 | { |
| | 3292 | local initDescItems; |
| | 3293 | |
| | 3294 | "<.roomdesc>"; |
| | 3295 | |
| | 3296 | /* |
| | 3297 | * check for illumination - we must have at least dim |
| | 3298 | * ambient lighting (level 2) to see the room's long |
| | 3299 | * description |
| | 3300 | */ |
| | 3301 | if (illum > 1) |
| | 3302 | { |
| | 3303 | /* |
| | 3304 | * display the normal description of the room - use the |
| | 3305 | * firstDesc if this is the first time in the room, |
| | 3306 | * otherwise the lDesc |
| | 3307 | */ |
| | 3308 | if (isSeen) |
| | 3309 | { |
| | 3310 | /* we've seen it already - show the regular description */ |
| | 3311 | lDesc; |
| | 3312 | } |
| | 3313 | else |
| | 3314 | { |
| | 3315 | /* |
| | 3316 | * we've never seen this location before - show the |
| | 3317 | * first-time description |
| | 3318 | */ |
| | 3319 | firstDesc; |
| | 3320 | |
| | 3321 | /* note that we've seen it now */ |
| | 3322 | isSeen = true; |
| | 3323 | } |
| | 3324 | } |
| | 3325 | else |
| | 3326 | { |
| | 3327 | /* display the in-the-dark description of the room */ |
| | 3328 | darkLDesc; |
| | 3329 | } |
| | 3330 | |
| | 3331 | "<./roomdesc>"; |
| | 3332 | |
| | 3333 | /* |
| | 3334 | * Display any initial-location messages for objects |
| | 3335 | * directly within the room. These messages are part of the |
| | 3336 | * verbose description rather than the portable item |
| | 3337 | * listing, because these messages are meant to look like |
| | 3338 | * part of the room's full description and thus should not |
| | 3339 | * be included in a non-verbose listing. |
| | 3340 | * |
| | 3341 | * Start out with everything in the room's contents. |
| | 3342 | */ |
| | 3343 | initDescItems = contents; |
| | 3344 | |
| | 3345 | /* |
| | 3346 | * If the room isn't at least dimly lit (level 2), only keep |
| | 3347 | * items that are self-illuminating. |
| | 3348 | */ |
| | 3349 | if (illum < 2) |
| | 3350 | { |
| | 3351 | /* |
| | 3352 | * keep only the items sensible to the actor - note that |
| | 3353 | * it's faster to compute the entire list of sensible |
| | 3354 | * objects and then intersect it with our contents list |
| | 3355 | * than it is to test each item in the contents list |
| | 3356 | * separately, because the list computation is optimized |
| | 3357 | * to carry light levels throughout the calculation, |
| | 3358 | * whereas testing each item individually requires |
| | 3359 | * starting from scratch on each item |
| | 3360 | */ |
| | 3361 | initDescItems = initDescItems.subset( |
| | 3362 | {x: infoList.indexWhich({y: y.obj == x}) != nil}); |
| | 3363 | } |
| | 3364 | |
| | 3365 | /* show each initial description item */ |
| | 3366 | foreach (local cur in initDescItems) |
| | 3367 | { |
| | 3368 | /* if this item uses an initial description, display it */ |
| | 3369 | if (cur.useInitDesc()) |
| | 3370 | { |
| | 3371 | /* start a new paragraph */ |
| | 3372 | "<.roompara>"; |
| | 3373 | |
| | 3374 | /* display the item's initial description */ |
| | 3375 | cur.initDesc; |
| | 3376 | } |
| | 3377 | } |
| | 3378 | } |
| | 3379 | |
| | 3380 | /* |
| | 3381 | * Describe each visible object directly contained in the room |
| | 3382 | * that wants to be listed - generally, we list any mobile |
| | 3383 | * objects that don't have special initial descriptions. We |
| | 3384 | * list items in all room descriptions, verbose or not, because |
| | 3385 | * the portable item list is more of a status display than it is |
| | 3386 | * a part of the full description. |
| | 3387 | */ |
| | 3388 | showRoomContents(actor, illum, infoList); |
| | 3389 | } |
| | 3390 | |
| | 3391 | /* |
| | 3392 | * Display the contents of the room. The illumination level is |
| | 3393 | * provided so we know how to construct the list. |
| | 3394 | * |
| | 3395 | * Note that this method must be overridden if the room uses a |
| | 3396 | * non-conventional contents mechanism (i.e., it doesn't store its |
| | 3397 | * complete set of direct contents in a 'contents' list property). |
| | 3398 | */ |
| | 3399 | showRoomContents(actor, illum, infoList) |
| | 3400 | { |
| | 3401 | local lst; |
| | 3402 | local lister; |
| | 3403 | |
| | 3404 | /* |
| | 3405 | * if the illumination is less than 'dim' (level 2), display |
| | 3406 | * only self-illuminating items |
| | 3407 | */ |
| | 3408 | if (illum != nil && illum < 2) |
| | 3409 | { |
| | 3410 | /* |
| | 3411 | * We're in the dark - list only those objects that the |
| | 3412 | * actor can sense via the sight-like senses, which will be |
| | 3413 | * the list of self-illuminating objects. (To produce this |
| | 3414 | * list, simply make a list consisting of only the 'obj' |
| | 3415 | * from each sense info entry in the sense info list.) |
| | 3416 | */ |
| | 3417 | lst = infoList.mapAll({x: x.obj}); |
| | 3418 | |
| | 3419 | /* use my dark lister */ |
| | 3420 | lister = darkRoomLister; |
| | 3421 | } |
| | 3422 | else |
| | 3423 | { |
| | 3424 | /* start with my contents list */ |
| | 3425 | lst = contents; |
| | 3426 | |
| | 3427 | /* always remove the actor from the list */ |
| | 3428 | lst -= actor; |
| | 3429 | |
| | 3430 | /* use the normal (lighted) lister */ |
| | 3431 | lister = roomLister; |
| | 3432 | } |
| | 3433 | |
| | 3434 | /* start a new paragraph if the list isn't empty */ |
| | 3435 | if (lst != []) |
| | 3436 | "<.roompara>"; |
| | 3437 | |
| | 3438 | /* show the contents */ |
| | 3439 | showList(actor, self, lister, lst, 0, 0, infoList); |
| | 3440 | |
| | 3441 | /* show the (visible) contents of the contents */ |
| | 3442 | showContentsList(actor, lst, 0, 0, infoList); |
| | 3443 | } |
| | 3444 | |
| | 3445 | /* |
| | 3446 | * Get my lighted room lister - this is the ShowListInterface object |
| | 3447 | * that we use to display the room's contents when the room is lit. |
| | 3448 | * We'll return the default library room lister. |
| | 3449 | */ |
| | 3450 | roomLister { return libMessages.roomLister; } |
| | 3451 | |
| | 3452 | /* |
| | 3453 | * Get my dark room lister - this is the ShowListInterface object |
| | 3454 | * we'll use to display the room's self-illuminating contents when |
| | 3455 | * the room is dark. |
| | 3456 | */ |
| | 3457 | darkRoomLister { return libMessages.darkRoomLister; } |
| | 3458 | |
| | 3459 | /* |
| | 3460 | * Display the "status line" description of the room. This is |
| | 3461 | * normally a brief, single-line description giving, effectively, |
| | 3462 | * the name of the room. |
| | 3463 | * |
| | 3464 | * By long-standing convention, each location in a game usually has |
| | 3465 | * a distinctive name that's displayed here. Players usually find |
| | 3466 | * these names helpful in forming a mental map of the game. |
| | 3467 | * |
| | 3468 | * By default, we'll simply display the room's short description. |
| | 3469 | */ |
| | 3470 | statusDesc(actor) |
| | 3471 | { |
| | 3472 | /* the description depends on whether we're illuminated or not */ |
| | 3473 | statusDescIllum(actor, illuminationLevel(actor.sightlikeSenses)); |
| | 3474 | } |
| | 3475 | |
| | 3476 | /* |
| | 3477 | * Display the status line description given our illumination |
| | 3478 | * status. (This separate version of statusDesc is provided so that |
| | 3479 | * we can avoid re-calculating our illumination status if the caller |
| | 3480 | * has already done so for its own purposes.) |
| | 3481 | */ |
| | 3482 | statusDescIllum(actor, illum) |
| | 3483 | { |
| | 3484 | /* |
| | 3485 | * Check for illumination. We must have at least dim light |
| | 3486 | * (level 2) to show the room's description. |
| | 3487 | */ |
| | 3488 | if (illum > 1) |
| | 3489 | { |
| | 3490 | /* there's light - display my short description */ |
| | 3491 | sDesc; |
| | 3492 | } |
| | 3493 | else |
| | 3494 | { |
| | 3495 | /* no light - display the in-the-dark message */ |
| | 3496 | darkSDesc; |
| | 3497 | } |
| | 3498 | } |
| | 3499 | ; |
| | 3500 | |
| | 3501 | /* |
| | 3502 | * A dark room, which provides no light of its own |
| | 3503 | */ |
| | 3504 | class DarkRoom: Room |
| | 3505 | /* |
| | 3506 | * turn off the lights |
| | 3507 | */ |
| | 3508 | brightness = 0 |
| | 3509 | ; |
| | 3510 | |
| | 3511 | |
| | 3512 | /* ------------------------------------------------------------------------ */ |
| | 3513 | /* |
| | 3514 | * Connector: an object that can pass senses across room boundaries. |
| | 3515 | * This is a mix-in class. |
| | 3516 | * |
| | 3517 | * A Connector acts as a sense conduit across all of its locations, so |
| | 3518 | * to establish a connection between locations, simply place a Connector |
| | 3519 | * in each location. Since a Connector is useful only when placed |
| | 3520 | * placed in multiple locations, Connector is based on MultiLoc. |
| | 3521 | */ |
| | 3522 | class Connector: MultiLoc |
| | 3523 | /* |
| | 3524 | * A Connector's material generally determines how senses pass |
| | 3525 | * through the connection. |
| | 3526 | */ |
| | 3527 | connectorMaterial = adventium |
| | 3528 | |
| | 3529 | /* |
| | 3530 | * Determine how senses pass through this connection. By default, |
| | 3531 | * we simply use the material's transparency. |
| | 3532 | */ |
| | 3533 | transSensingThru(sense) { return connectorMaterial.senseThru(sense); } |
| | 3534 | |
| | 3535 | /* |
| | 3536 | * A Connector is a sense conduit connecting all of the locations in |
| | 3537 | * which it exists. Therefore, when traversing the containment tree |
| | 3538 | * looking for a sense path, we must always traverse all of a |
| | 3539 | * Connector's parents, even when we are coming into this object |
| | 3540 | * from one of our parents. (The 'path' exclusion prevents us from |
| | 3541 | * traversing back into the same parent we just came from, of |
| | 3542 | * course.) |
| | 3543 | */ |
| | 3544 | senseIterFromAbove(sense, func, path, transToHere, obstructor, ambient) |
| | 3545 | { |
| | 3546 | local myTrans; |
| | 3547 | local transUp; |
| | 3548 | local obstructorUp = obstructor; |
| | 3549 | |
| | 3550 | /* |
| | 3551 | * First, try looking up the containment hierarchy at my other |
| | 3552 | * locations. This has the effect of looking from one of our |
| | 3553 | * containers, through our connection material, to each of our |
| | 3554 | * other containers. |
| | 3555 | * |
| | 3556 | * Since we're looking through our connection material, we must |
| | 3557 | * find the cumulative transparency of the full path through the |
| | 3558 | * material. |
| | 3559 | * |
| | 3560 | * Note that if I'm the first non-transparent item in the path |
| | 3561 | * so far, I'm the obstructor on this path. |
| | 3562 | */ |
| | 3563 | myTrans = transSensingThru(sense); |
| | 3564 | if (myTrans != transparent && obstructor == nil) |
| | 3565 | obstructorUp = self; |
| | 3566 | |
| | 3567 | /* note the accumulated transparency along the through path */ |
| | 3568 | transUp = transparencyAdd(myTrans, transToHere); |
| | 3569 | |
| | 3570 | /* |
| | 3571 | * iterate up the hierarchy into my other containers - note that |
| | 3572 | * we need only do this if the total path so far is not opaque |
| | 3573 | */ |
| | 3574 | if (transUp != opaque) |
| | 3575 | { |
| | 3576 | local myAmbient = ambient; |
| | 3577 | |
| | 3578 | /* |
| | 3579 | * if the connection is not transparent, we must |
| | 3580 | * re-calculate the ambient light level when get to each |
| | 3581 | * other connected location |
| | 3582 | */ |
| | 3583 | if (ambient != nil && myTrans != transparent) |
| | 3584 | { |
| | 3585 | /* |
| | 3586 | * Set the ambient level to -1 to indicate that we must |
| | 3587 | * re-calculate the ambient level for each location. We |
| | 3588 | * can't calculate it ourselves, because each location |
| | 3589 | * will need to calculate it separately. |
| | 3590 | */ |
| | 3591 | myAmbient = -1; |
| | 3592 | } |
| | 3593 | |
| | 3594 | /* iterate up through my other containers */ |
| | 3595 | if (!senseIterUp(sense, func, path, |
| | 3596 | transUp, obstructorUp, myAmbient)) |
| | 3597 | return nil; |
| | 3598 | } |
| | 3599 | |
| | 3600 | /* |
| | 3601 | * We can sense this object itself. Note that our connection |
| | 3602 | * material's transparency is not relevant here, since we do not |
| | 3603 | * have to look through ourself to see ourself! |
| | 3604 | */ |
| | 3605 | if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient)) |
| | 3606 | return nil; |
| | 3607 | |
| | 3608 | /* |
| | 3609 | * Finally, try looking down the hierarchy at my children. Note |
| | 3610 | * that, when looking at my children, we do not consider our |
| | 3611 | * connection material's transparency, because we do not see |
| | 3612 | * children through the connecting material. |
| | 3613 | */ |
| | 3614 | return senseIterDown(sense, func, path, |
| | 3615 | transToHere, obstructor, ambient); |
| | 3616 | } |
| | 3617 | ; |
| | 3618 | |
| | 3619 | /* ------------------------------------------------------------------------ */ |
| | 3620 | /* |
| | 3621 | * An item that can be worn |
| | 3622 | */ |
| | 3623 | class Wearable: Thing |
| | 3624 | /* is the item currently being worn? */ |
| | 3625 | isWorn() |
| | 3626 | { |
| | 3627 | /* it's being worn if the wearer is non-nil */ |
| | 3628 | return wornBy != nil; |
| | 3629 | } |
| | 3630 | |
| | 3631 | /* |
| | 3632 | * The object wearing this object, if any; if I'm not being worn, |
| | 3633 | * this is nil. The wearer should always be a container (direct or |
| | 3634 | * indirect) of this object - in order to wear something, you must |
| | 3635 | * be carrying it. In most cases, the wearer should be the direct |
| | 3636 | * container of the object. |
| | 3637 | * |
| | 3638 | * The reason we keep track of who's wearing the object (rather than |
| | 3639 | * simply keeping track of whether it's being worn) is to allow for |
| | 3640 | * cases where an actor is carrying another actor. Since this |
| | 3641 | * object will be (indirectly) inside both actors in such cases, we |
| | 3642 | * would have to inspect intermediate containers to determine |
| | 3643 | * whether or not the outer actor was wearing the object if we |
| | 3644 | * didn't keep track of the wearer directly. |
| | 3645 | */ |
| | 3646 | wornBy = nil |
| | 3647 | |
| | 3648 | /* am I worn by the given object? */ |
| | 3649 | isWornBy(actor) |
| | 3650 | { |
| | 3651 | return wornBy == actor; |
| | 3652 | } |
| | 3653 | |
| | 3654 | /* |
| | 3655 | * Determine if I'm equivalent to another object of the same class |
| | 3656 | * for listing purposes. If we're showing the "(being worn)" |
| | 3657 | * status, then to be equivalent, two wearables must have the same |
| | 3658 | * being-worn state to be listed together. |
| | 3659 | */ |
| | 3660 | isListEquivalent(obj, options) |
| | 3661 | { |
| | 3662 | /* |
| | 3663 | * if we're not equivalent by the standard test, we're not |
| | 3664 | * equivalent |
| | 3665 | */ |
| | 3666 | if (!inherited.isListEquivalent(obj, options)) |
| | 3667 | return nil; |
| | 3668 | |
| | 3669 | /* |
| | 3670 | * if we're displaying an explicit list of worn items, then we |
| | 3671 | * won't need a "being worn" status message, so this doesn't |
| | 3672 | * differentiate objects - in this case, the objects are |
| | 3673 | * equivalent regardless of their 'worn' status |
| | 3674 | */ |
| | 3675 | if (!(options & LIST_WORN)) |
| | 3676 | return true; |
| | 3677 | |
| | 3678 | /* |
| | 3679 | * we pass the standard test, but we're showing the 'worn' |
| | 3680 | * status in the display - the objects are thus equivalent for |
| | 3681 | * listing purposes only if their states are the same |
| | 3682 | */ |
| | 3683 | return isWorn() == obj.isWorn(); |
| | 3684 | } |
| | 3685 | |
| | 3686 | /* show myself in a listing */ |
| | 3687 | showListItem(options, pov, senseInfo) |
| | 3688 | { |
| | 3689 | /* inherit the default handling */ |
| | 3690 | inherited.showListItem(options, pov, senseInfo); |
| | 3691 | |
| | 3692 | /* show our 'worn' status */ |
| | 3693 | wornDesc(options); |
| | 3694 | } |
| | 3695 | |
| | 3696 | /* show myself in a counted listing */ |
| | 3697 | showListItemCounted(equivCount, options, pov, senseInfo) |
| | 3698 | { |
| | 3699 | /* inherit the default handling */ |
| | 3700 | inherited.showListItemCounted(equivCount, options, pov, senseInfo); |
| | 3701 | |
| | 3702 | /* show our 'worn' status */ |
| | 3703 | wornDesc(options); |
| | 3704 | } |
| | 3705 | |
| | 3706 | /* show my 'worn' status */ |
| | 3707 | wornDesc(options) |
| | 3708 | { |
| | 3709 | /* |
| | 3710 | * show our status only if we're being worn and this isn't an |
| | 3711 | * explicit list of items being worn (if it is, the list will |
| | 3712 | * announce that all of the items are being worn, hence the |
| | 3713 | * individual items don't need to be marked as such) |
| | 3714 | */ |
| | 3715 | if (isWorn() && !(options & LIST_WORN)) |
| | 3716 | libMessages.wornDesc; |
| | 3717 | } |
| | 3718 | ; |
| | 3719 | |
| | 3720 | /* ------------------------------------------------------------------------ */ |
| | 3721 | /* |
| | 3722 | * An item that can provide light. |
| | 3723 | * |
| | 3724 | * Any Thing can provide light, but this class should be used for |
| | 3725 | * objects that explicitly serve as light sources from the player's |
| | 3726 | * perspective. Objects of this class display a "providing light" |
| | 3727 | * status message in inventory listings, and can be turned on and off |
| | 3728 | * via the isLit property. |
| | 3729 | */ |
| | 3730 | class LightSource: Thing |
| | 3731 | /* is the light source currently turned on? */ |
| | 3732 | isLit = true |
| | 3733 | |
| | 3734 | /* the brightness that the object has when it is on and off */ |
| | 3735 | brightnessOn = 3 |
| | 3736 | brightnessOff = 0 |
| | 3737 | |
| | 3738 | /* |
| | 3739 | * return the appropriate on/off brightness, depending on whether or |
| | 3740 | * not we're currently lit |
| | 3741 | */ |
| | 3742 | brightness { return isLit ? brightnessOn : brightnessOff; } |
| | 3743 | |
| | 3744 | /* |
| | 3745 | * Determine if I'm equivalent to another object of the same class |
| | 3746 | * for listing purposes. To be equivalent, two light sources must |
| | 3747 | * have the same isLit status, because we show the status (via a |
| | 3748 | * "providing light" message) in listings. |
| | 3749 | */ |
| | 3750 | isListEquivalent(obj, options) |
| | 3751 | { |
| | 3752 | /* |
| | 3753 | * if we're not equivalent by the standard test, we're not |
| | 3754 | * equivalent |
| | 3755 | */ |
| | 3756 | if (!inherited.isListEquivalent(obj, options)) |
| | 3757 | return nil; |
| | 3758 | |
| | 3759 | /* |
| | 3760 | * we pass the standard test, so check to see if the 'lit' |
| | 3761 | * states of the two objects are the same - if they are, we're |
| | 3762 | * equivalent, otherwise we're not |
| | 3763 | */ |
| | 3764 | return isLit == obj.isLit; |
| | 3765 | } |
| | 3766 | |
| | 3767 | /* show myself in a listing */ |
| | 3768 | showListItem(options, pov, senseInfo) |
| | 3769 | { |
| | 3770 | /* inherit the default handling */ |
| | 3771 | inherited.showListItem(options, pov, senseInfo); |
| | 3772 | |
| | 3773 | /* show our light status */ |
| | 3774 | providingLightDesc; |
| | 3775 | } |
| | 3776 | |
| | 3777 | /* show myself in a counted listing */ |
| | 3778 | showListItemCounted(equivCount, options, pov, senseInfo) |
| | 3779 | { |
| | 3780 | /* inherit the default handling */ |
| | 3781 | inherited.showListItemCounted(equivCount, options, pov, senseInfo); |
| | 3782 | |
| | 3783 | /* show our light status */ |
| | 3784 | providingLightDesc; |
| | 3785 | } |
| | 3786 | |
| | 3787 | /* |
| | 3788 | * show our "providing light" status, but only if we actually are |
| | 3789 | * providing light |
| | 3790 | */ |
| | 3791 | providingLightDesc |
| | 3792 | { |
| | 3793 | /* |
| | 3794 | * if we're providing light to surrounding objects, say so - |
| | 3795 | * note that we do not provide light to any nearby objects if |
| | 3796 | * we're merely self-illuminating (which is the case if our |
| | 3797 | * brightness is 1) |
| | 3798 | */ |
| | 3799 | if (brightness > 1) |
| | 3800 | libMessages.providingLightDesc; |
| | 3801 | } |
| | 3802 | ; |
| | 3803 | |
| | 3804 | /* ------------------------------------------------------------------------ */ |
| | 3805 | /* |
| | 3806 | * An Actor is a living person, animal, or other entity with a will of |
| | 3807 | * its own. |
| | 3808 | * |
| | 3809 | * An actor's contents are the things the actor is carrying or wearing. |
| | 3810 | */ |
| | 3811 | class Actor: Thing |
| | 3812 | /* |
| | 3813 | * Actors usually have proper names, so they don't need articles |
| | 3814 | * when their names are used (just "Bob", not "a Bob" or "the Bob") |
| | 3815 | */ |
| | 3816 | aDesc { sDesc; } |
| | 3817 | theDesc { sDesc; } |
| | 3818 | |
| | 3819 | /* |
| | 3820 | * The senses that determine scope for this actor. An actor might |
| | 3821 | * possess only a subset of the defined sense. |
| | 3822 | * |
| | 3823 | * By default, we give each actor all of the human senses that we |
| | 3824 | * define, except touch. In general, merely being able to touch an |
| | 3825 | * object doesn't put the object in scope, because an actor can |
| | 3826 | * usually touch a number of objects that aren't immediately within |
| | 3827 | * reach. |
| | 3828 | */ |
| | 3829 | scopeSenses = [sight, sound, smell] |
| | 3830 | |
| | 3831 | /* |
| | 3832 | * "Sight-like" senses: these are the senses that operate like sight |
| | 3833 | * for the actor, and which the actor can use to determine the names |
| | 3834 | * of objects and the spatial relationships between objects. These |
| | 3835 | * senses should operate passively, in the sense that they should |
| | 3836 | * tend to collect sensory input continuously and without explicit |
| | 3837 | * action by the actor, the way sight does and the way touch, for |
| | 3838 | * example, does not. These senses should also operate instantly, |
| | 3839 | * in the sense that the sense can reasonably take in most or all of |
| | 3840 | * a location at one time. |
| | 3841 | * |
| | 3842 | * These senses are used to determine what objects should be listed |
| | 3843 | * in room descriptions, for example. |
| | 3844 | * |
| | 3845 | * By default, the only sight-like sense is sight, since other human |
| | 3846 | * senses don't normally provide a clear picture of the spatial |
| | 3847 | * relationships among objects. (Touch could with some degree of |
| | 3848 | * effort, but it can't operate passively or instantly, since |
| | 3849 | * deliberate and time-consuming action would be necessary.) |
| | 3850 | * |
| | 3851 | * An actor can have more than one sight-like sense, in which case |
| | 3852 | * the senses will act effectively as one sense that can reach the |
| | 3853 | * union of objects reachable through the individual senses. |
| | 3854 | */ |
| | 3855 | sightlikeSenses = [sight] |
| | 3856 | |
| | 3857 | /* |
| | 3858 | * Build a list of all of the objects of which an actor is aware. |
| | 3859 | * |
| | 3860 | * An actor is aware of an object if the object is within reach of |
| | 3861 | * the actor's senses, and has some sort of presence in that sense. |
| | 3862 | * Note that both of these conditions must be true for at least one |
| | 3863 | * sense possessed by the actor; an object that is within earshot, |
| | 3864 | * but not within reach of any other sense, is in scope only if the |
| | 3865 | * object is making some kind of noise. |
| | 3866 | */ |
| | 3867 | scopeList() |
| | 3868 | { |
| | 3869 | local lst; |
| | 3870 | |
| | 3871 | /* we have nothing in our master list yet */ |
| | 3872 | lst = []; |
| | 3873 | |
| | 3874 | /* iterate over each sense */ |
| | 3875 | foreach (local sense in scopeSenses) |
| | 3876 | { |
| | 3877 | /* |
| | 3878 | * get everything reachable to this sense, and add the |
| | 3879 | * unique elements into the result list |
| | 3880 | */ |
| | 3881 | lst = lst.appendUnique(senseScopeList(sense)); |
| | 3882 | } |
| | 3883 | |
| | 3884 | /* return the result */ |
| | 3885 | return lst; |
| | 3886 | } |
| | 3887 | |
| | 3888 | /* |
| | 3889 | * Build a list of all of the objects of which the actor is aware |
| | 3890 | * through the sight-like senses. |
| | 3891 | */ |
| | 3892 | visibleList() |
| | 3893 | { |
| | 3894 | local lst; |
| | 3895 | |
| | 3896 | /* we have nothing in our master list yet */ |
| | 3897 | lst = []; |
| | 3898 | |
| | 3899 | /* iterate over each sense */ |
| | 3900 | foreach (local sense in sightlikeSenses) |
| | 3901 | { |
| | 3902 | local cur; |
| | 3903 | |
| | 3904 | /* get information for all objects for the current sense */ |
| | 3905 | cur = senseList(sense); |
| | 3906 | |
| | 3907 | /* add the unique elements */ |
| | 3908 | lst = lst.appendUnique(cur); |
| | 3909 | } |
| | 3910 | |
| | 3911 | /* return the result */ |
| | 3912 | return lst; |
| | 3913 | } |
| | 3914 | |
| | 3915 | /* |
| | 3916 | * Build a list of full sight-like sensory information for all of |
| | 3917 | * the objects visible to the actor through the actor's sight-like |
| | 3918 | * senses. Returns a list with the same set of information as |
| | 3919 | * senseInfoList(). |
| | 3920 | */ |
| | 3921 | visibleInfoList() |
| | 3922 | { |
| | 3923 | local lst; |
| | 3924 | |
| | 3925 | /* we have nothing in our master list yet */ |
| | 3926 | lst = []; |
| | 3927 | |
| | 3928 | /* iterate over each sense */ |
| | 3929 | foreach (local sense in sightlikeSenses) |
| | 3930 | { |
| | 3931 | local cur; |
| | 3932 | |
| | 3933 | /* get information for all objects for the current sense */ |
| | 3934 | cur = senseInfoList(sense); |
| | 3935 | |
| | 3936 | /* merge the two lists */ |
| | 3937 | lst = mergeSenseInfoList(cur, lst); |
| | 3938 | } |
| | 3939 | |
| | 3940 | /* return the result */ |
| | 3941 | return lst; |
| | 3942 | } |
| | 3943 | |
| | 3944 | |
| | 3945 | /* |
| | 3946 | * Build a list of all of the objects that are in scope for this |
| | 3947 | * actor through the given sense. |
| | 3948 | */ |
| | 3949 | senseScopeList(sense) |
| | 3950 | { |
| | 3951 | local lst; |
| | 3952 | |
| | 3953 | /* we don't have any objects in our list yet */ |
| | 3954 | lst = new Vector(32); |
| | 3955 | |
| | 3956 | /* |
| | 3957 | * Iterate over everything reachable through the sense, and add |
| | 3958 | * each reachable object to the list |
| | 3959 | */ |
| | 3960 | senseIter(sense, new function(cur, trans, obstructor, ambient) { |
| | 3961 | /* |
| | 3962 | * include the object only if it has a presence in this |
| | 3963 | * sense - if it doesn't, the actor is not made aware of the |
| | 3964 | * object by this sense, because even though it is within |
| | 3965 | * reach of the sense, the object would not be detectable by |
| | 3966 | * this sense alone no matter what its relation to the actor |
| | 3967 | */ |
| | 3968 | if (cur.(sense.presenceProp)) |
| | 3969 | { |
| | 3970 | /* add the object to the list so far */ |
| | 3971 | lst += cur; |
| | 3972 | } |
| | 3973 | |
| | 3974 | /* tell the caller to proceed with the iteration */ |
| | 3975 | return true; |
| | 3976 | }); |
| | 3977 | |
| | 3978 | /* return the list we built */ |
| | 3979 | return lst.toList(); |
| | 3980 | } |
| | 3981 | |
| | 3982 | /* |
| | 3983 | * Show what the actor is carrying. |
| | 3984 | * |
| | 3985 | * Note that this method must be overridden if the actor does not |
| | 3986 | * use a conventional 'contents' list property to store its full set |
| | 3987 | * of contents. |
| | 3988 | */ |
| | 3989 | showInventory(tall) |
| | 3990 | { |
| | 3991 | local infoList; |
| | 3992 | |
| | 3993 | /* |
| | 3994 | * Get the list of objects visible to the actor through the |
| | 3995 | * actor's sight-like senses, along with the sense information |
| | 3996 | * for each object. We'll only worry about this for the |
| | 3997 | * contents of the items being carried; we'll assume that the |
| | 3998 | * actor has at some point taken note of each item being |
| | 3999 | * directly carried (by virtue of having picked it up at some |
| | 4000 | * point in the past) and can still identify each by touch, even |
| | 4001 | * if it's too dark to see it. |
| | 4002 | */ |
| | 4003 | infoList = visibleInfoList(); |
| | 4004 | |
| | 4005 | /* list in the appropriate mode ("wide" or "tall") */ |
| | 4006 | if (tall) |
| | 4007 | { |
| | 4008 | /* |
| | 4009 | * show the list of items being carried, recursively |
| | 4010 | * displaying the contents; show items being worn in-line |
| | 4011 | * with the regular listing |
| | 4012 | */ |
| | 4013 | showList(self, self, inventoryLister, contents, |
| | 4014 | LIST_TALL | LIST_RECURSE | LIST_INVENT, |
| | 4015 | 0, infoList); |
| | 4016 | } |
| | 4017 | else |
| | 4018 | { |
| | 4019 | local carrying; |
| | 4020 | local wearing; |
| | 4021 | |
| | 4022 | /* |
| | 4023 | * paragraph style ("wide") inventory - go through the |
| | 4024 | * direct contents, and separate the list into what's being |
| | 4025 | * carried and what's being worn |
| | 4026 | */ |
| | 4027 | carrying = new Vector(32); |
| | 4028 | wearing = new Vector(32); |
| | 4029 | foreach (local cur in contents) |
| | 4030 | { |
| | 4031 | if (cur.isWornBy(self)) |
| | 4032 | wearing += cur; |
| | 4033 | else |
| | 4034 | carrying += cur; |
| | 4035 | } |
| | 4036 | |
| | 4037 | /* convert the carrying and wearing vectors to lists */ |
| | 4038 | carrying = carrying.toList(); |
| | 4039 | wearing = wearing.toList(); |
| | 4040 | |
| | 4041 | /* show the list of items being carried */ |
| | 4042 | showList(self, self, inventoryLister, carrying, LIST_INVENT, |
| | 4043 | 0, infoList); |
| | 4044 | |
| | 4045 | /* show the visible contents of the items being carried */ |
| | 4046 | showContentsList(self, carrying, LIST_INVENT, 0, infoList); |
| | 4047 | |
| | 4048 | /* show the list of items being worn */ |
| | 4049 | if (wearing.length() != 0) |
| | 4050 | { |
| | 4051 | /* show the items being worn */ |
| | 4052 | showList(self, self, wearingLister, wearing, LIST_WORN, 0, |
| | 4053 | infoList); |
| | 4054 | |
| | 4055 | /* show the contents of the items being worn */ |
| | 4056 | showContentsList(self, wearing, LIST_WORN, 0, infoList); |
| | 4057 | } |
| | 4058 | } |
| | 4059 | } |
| | 4060 | |
| | 4061 | /* |
| | 4062 | * The ShowListInterface object that we use for inventory listings |
| | 4063 | */ |
| | 4064 | inventoryLister |
| | 4065 | { |
| | 4066 | /* |
| | 4067 | * if I'm the player character, use the player character lister; |
| | 4068 | * otherwise use the non-player lister |
| | 4069 | */ |
| | 4070 | if (libGlobal.playerChar == self) |
| | 4071 | return libMessages.playerInventoryLister; |
| | 4072 | else |
| | 4073 | return self.myInventoryLister; |
| | 4074 | } |
| | 4075 | |
| | 4076 | /* |
| | 4077 | * The ShowListInterface object that we use for inventory listings |
| | 4078 | * of items being worn |
| | 4079 | */ |
| | 4080 | wearingLister |
| | 4081 | { |
| | 4082 | if (libGlobal.playerChar == self) |
| | 4083 | return libMessages.playerWearingLister; |
| | 4084 | else |
| | 4085 | return self.myWearingLister; |
| | 4086 | } |
| | 4087 | |
| | 4088 | /* |
| | 4089 | * My inventory lister item. During pre-initialization, the library |
| | 4090 | * will automatically create an instance of NonPlayerInventoryLister |
| | 4091 | * for each actor that doesn't define a non-nil value for this |
| | 4092 | * property. Note that we never use this for the current player |
| | 4093 | * character, but rather use libMessages.playerInventoryLister. |
| | 4094 | */ |
| | 4095 | myInventoryLister = nil |
| | 4096 | |
| | 4097 | /* my inventory lister for items being worn */ |
| | 4098 | myWearingLister = nil |
| | 4099 | |
| | 4100 | /* |
| | 4101 | * Perform library pre-initialization on the actor |
| | 4102 | */ |
| | 4103 | initializeActor() { } |
| | 4104 | ; |
| | 4105 | |
| | 4106 | /* ------------------------------------------------------------------------ */ |
| | 4107 | /* |
| | 4108 | * Set the current player character |
| | 4109 | */ |
| | 4110 | setPlayer(actor) |
| | 4111 | { |
| | 4112 | /* remember the new player character */ |
| | 4113 | libGlobal.playerChar = actor; |
| | 4114 | |
| | 4115 | /* set the root global point of view to this actor */ |
| | 4116 | setRootPOV(actor); |
| | 4117 | } |
| | 4118 | |