1#charset "us-ascii" 2 3/* Copyright (c) 2000, 2002 Michael J. Roberts. All Rights Reserved. */ 4/* 5 * TADS 3 Library - Lister class 6 * 7 * This module defines the "Lister" class, which generates formatted 8 * lists of objects, and several subclasses of Lister that generate 9 * special kinds of lists. 10 */ 11 12/* include the library header */ 13#include "adv3.h" 14 15 16/* ------------------------------------------------------------------------ */ 17/* 18 * Lister. This is the base class for formatting of lists of objects. 19 * 20 * The external interface consists of the showList() method, which 21 * displays a formatted list of objects according to the rules of the 22 * lister subclass. 23 * 24 * The rest of the methods are an internal interface which lister 25 * subclasses can override to customize the way that a list is shown. 26 * Certain of these methods are meant to be overridden by virtually all 27 * listers, such as the methods that show the prefix and suffix 28 * messages. The remaining methods are designed to allow subclasses to 29 * customize detailed aspects of the formatting, so they only need to be 30 * overridden when something other than the default behavior is needed. 31 */ 32class Lister: object 33 /* 34 * Show a list, showing all items in the list as though they were 35 * fully visible, regardless of their actual sense status. 36 */ 37 showListAll(lst, options, indent) 38 { 39 local infoTab; 40 41 /* create a sense information table with each item in full view */ 42 infoTab = new LookupTable(16, 32); 43 foreach (local cur in lst) 44 { 45 /* add a plain view sensory description to the info list */ 46 infoTab[cur] = new SenseInfo(cur, transparent, nil, 3); 47 } 48 49 /* show the list from the current global point of view */ 50 showList(getPOV(), nil, lst, options, indent, infoTab, nil); 51 } 52 53 /* 54 * Display a list of items, grouping according to the 'listWith' 55 * associations of the items. We will only list items for which 56 * isListed() returns true. 57 * 58 * 'pov' is the point of view of the listing, which is usually an 59 * actor (and usually the player character actor). 60 * 61 * 'parent' is the parent (container) of the list being shown. This 62 * should be nil if the listed objects are not all within a single 63 * object. 64 * 65 * 'lst' is the list of items to display. 66 * 67 * 'options' gives a set of ListXxx option flags. 68 * 69 * 'indent' gives the indentation level. This is used only for 70 * "tall" lists (specified by including ListTall in the options 71 * flags). An indentation level of zero indicates no indentation. 72 * 73 * 'infoTab' is a lookup table of SenseInfo objects for all of the 74 * objects that can be sensed from the perspective of the actor 75 * performing the action that's causing the listing. This is 76 * normally the table returned from Thing.senseInfoTable() for the 77 * actor from whose point of view the list is being generated. (We 78 * take this as a parameter rather than generating ourselves for two 79 * reasons. First, it's often the case that the same information 80 * table will be needed for a series of listings, so we can save the 81 * compute time of recalculating the same table repeatedly by having 82 * the caller obtain the table and pass it to each lister. Second, 83 * in some cases the caller will want to synthesize a special sense 84 * table rather than using the actual sense information; taking this 85 * as a parameter allows the caller to easily customize the table.) 86 * 87 * 'parentGroup' is the ListGroup object that is showing this list. 88 * We will not group the objects we list into the parent group, or 89 * into any group more general than the parent group. 90 * 91 * This routine is not usually overridden in lister subclasses. 92 * Instead, this method calls a number of other methods that 93 * determine the listing style in more detail; usually those other, 94 * simpler methods are customized in subclasses. 95 */ 96 showList(pov, parent, lst, options, indent, infoTab, parentGroup) 97 { 98 local groups; 99 local groupTab; 100 local singles; 101 local origLst; 102 local itemCount; 103 104 /* remember the original list */ 105 origLst = lst; 106 107 /* filter the list to get only the items we actually will list */ 108 lst = getFilteredList(lst, infoTab); 109 110 /* create a lookup table to keep track of the groups we've seen */ 111 groupTab = new LookupTable(); 112 groups = new Vector(10); 113 114 /* set up a vector to keep track of the singles */ 115 singles = new Vector(10); 116 117 /* figure the groupings */ 118 itemCount = getListGrouping(groupTab, groups, singles, 119 lst, parentGroup); 120 121 /* 122 * Now that we've figured out what's in the list and how it's 123 * arranged into groups, show the list. 124 */ 125 showArrangedList(pov, parent, lst, options, indent, infoTab, 126 itemCount, singles, groups, groupTab, origLst); 127 128 /* 129 * If the list is recursive, mention the contents of any items 130 * that weren't listed in the main list, and of any contents 131 * that are specifically to be listed out-of-line. Don't do 132 * this if we're already recursively showing such a listing, 133 * since if we did so we could show items at recursive depths 134 * more than once; if we're already doing a recursive listing, 135 * our caller will itself recurse through all levels of the 136 * tree, so we don't have to recurse any further ourselves. 137 */ 138 if ((options & ListRecurse) != 0 139 && indent == 0 140 && (options & ListContents) == 0) 141 { 142 /* show the contents of each object we didn't list */ 143 showSeparateContents(pov, origLst, 144 options | ListContents, infoTab); 145 } 146 } 147 148 /* 149 * Filter a list to get only the elements we actually want to show. 150 * Returns a new list consisting only of the items that (1) pass the 151 * isListed() test, and (2) are represented in the sense information 152 * table (infoTab). If infoTab is nil, no sense filtering is 153 * applied. 154 */ 155 getFilteredList(lst, infoTab) 156 { 157 /* narrow the list down based on the isListed criteria */ 158 lst = lst.subset({x: isListed(x)}); 159 160 /* 161 * If we have an infoTab, build a new list consisting only of 162 * the items in 'lst' that have infoTab entries - we can't sense 163 * anything that doesn't have an infoTab entry, so we don't want 164 * to show any such objects. 165 */ 166 if (infoTab != nil) 167 { 168 /* create a vector to contain the new filtered list */ 169 local filteredList = new Vector(lst.length()); 170 171 /* 172 * run through our original list and confirm that each one 173 * is in the infoTab 174 */ 175 foreach (local cur in lst) 176 { 177 /* 178 * if this item has an infoTab entry, add this item to 179 * the filtered list 180 */ 181 if (infoTab[cur] != nil) 182 filteredList.append(cur); 183 } 184 185 /* forget the original list, and use the filtered list instead */ 186 lst = filteredList; 187 } 188 189 /* return the filtered list */ 190 return lst; 191 } 192 193 /* 194 * Get the groupings for a given listing. 195 * 196 * 'groupTab' is an empty LookupTable, and 'groups' is an empty 197 * Vector; we'll populate these with the grouping information. 198 * 'singles' is an empty Vector that we'll populate with the single 199 * items not part of any group. 200 */ 201 getListGrouping(groupTab, groups, singles, lst, parentGroup) 202 { 203 local cur; 204 local i, cnt; 205 206 /* 207 * First, scan the list to determine how we're going to group 208 * the objects. 209 */ 210 for (i = 1, cnt = lst.length() ; i <= cnt ; ++i) 211 { 212 local curGroups; 213 local parentIdx; 214 215 /* get this object into a local for easy reference */ 216 cur = lst[i]; 217 218 /* if the item isn't part of this listing, skip it */ 219 if (!isListed(cur)) 220 continue; 221 222 /* get the list of groups with which this object is listed */ 223 curGroups = listWith(cur); 224 225 /* if there are no groups, we can move on to the next item */ 226 if (curGroups == nil) 227 continue; 228 229 /* 230 * If we have a parent group, and it appears in the list of 231 * groups for this item, eliminate everything in the item's 232 * group list up to and including the parent group. If 233 * we're showing this list as part of a group to begin with, 234 * we obviously don't want to show this list grouped into 235 * the same group, and we also don't want to group it into 236 * anything broader than the parent group. Groups are 237 * listed from most general to most specific, so we can 238 * eliminate anything up to and including the parent group. 239 */ 240 if (parentGroup != nil 241 && (parentIdx = curGroups.indexOf(parentGroup)) != nil) 242 { 243 /* eliminate everything up to and including the parent */ 244 curGroups = curGroups.sublist(parentIdx + 1); 245 } 246 247 /* if this item has no groups, skip it */ 248 if (curGroups.length() == 0) 249 continue; 250 251 /* 252 * This item has one or more group associations that we must 253 * consider. 254 */ 255 foreach (local g in curGroups) 256 { 257 local itemsInGroup; 258 259 /* find the group table entry for this group */ 260 itemsInGroup = groupTab[g]; 261 262 /* if there's no entry for this group, create a new one */ 263 if (itemsInGroup == nil) 264 { 265 /* create a new group table entry */ 266 itemsInGroup = groupTab[g] = new Vector(10); 267 268 /* add it to the group vector */ 269 groups.append(g); 270 } 271 272 /* 273 * add this item to the list of items that want to be 274 * grouped with this group 275 */ 276 itemsInGroup.append(cur); 277 } 278 } 279 280 /* 281 * We now have the set of all of the groups that could possibly 282 * be involved in this list display. We must now choose the 283 * single group we'll use to display each grouped object. 284 * 285 * First, eliminate any groups with insufficient membership. 286 * (Most groups require at least two members, but this can vary 287 * by group.) 288 */ 289 for (i = 1, cnt = groups.length() ; i <= cnt ; ++i) 290 { 291 /* if this group has only one member, drop it */ 292 if (groupTab[groups[i]].length() < groups[i].minGroupSize) 293 { 294 /* remove this group from the group list */ 295 groups.removeElementAt(i); 296 297 /* 298 * adjust the list count, and back up to try the element 299 * newly at this index on the next iteration 300 */ 301 --cnt; 302 --i; 303 } 304 } 305 306 /* 307 * Next, scan for groups with identical member lists, and for 308 * groups with subset member lists. For each pair of identical 309 * elements we find, eliminate the more general of the two. 310 */ 311 for (i = 1, cnt = groups.length() ; i <= cnt ; ++i) 312 { 313 local g1; 314 local mem1; 315 316 /* get the current group and its membership list */ 317 g1 = groups[i]; 318 mem1 = groupTab[g1]; 319 320 /* look for matching items in the list after this one */ 321 for (local j = i + 1 ; j <= cnt ; ++j) 322 { 323 local g2; 324 local mem2; 325 326 /* get the current item and its membership list */ 327 g2 = groups[j]; 328 mem2 = groupTab[g2]; 329 330 /* 331 * Compare the membership lists for the two items. Note 332 * that we built these membership lists all in the same 333 * order of objects, so if two membership lists have all 334 * the same members, those members will be in the same 335 * order in the two lists; hence, we can simply compare 336 * the two lists to determine the membership order. 337 */ 338 if (mem1 == mem2) 339 { 340 local ordList; 341 342 /* 343 * The groups have identical membership, so 344 * eliminate the more general group. Groups are 345 * ordered from most general to least general, so 346 * keep the one with the higher index in the group 347 * list for an object in the membership list. Note 348 * that we assume that each member has the same 349 * ordering for the common groups, so we can pick a 350 * member arbitrarily to find the way a member 351 * orders the groups. 352 */ 353 ordList = listWith(mem1[1]); 354 if (ordList.indexOf(g1) > ordList.indexOf(g2)) 355 { 356 /* 357 * group g1 is more specific than group g2, so 358 * keep g1 and discard g2 - remove the 'j' 359 * element from the list, and back up in the 360 * inner loop so we reconsider the element newly 361 * at this index on the next iteration 362 */ 363 groups.removeElementAt(j); 364 --cnt; 365 --j; 366 } 367 else 368 { 369 /* 370 * group g2 is more specific, so discard g1 - 371 * remove the 'i' element from the list, back up 372 * in the outer loop, and break out of the inner 373 * loop, since the outer loop element is no 374 * longer there for us to consider in comparing 375 * more elements in the inner loop 376 */ 377 groups.removeElementAt(i); 378 --cnt; 379 --i; 380 break; 381 } 382 } 383 } 384 } 385 386 /* 387 * Scan for subsets. For each group whose membership list is a 388 * subset of another group in our list, eliminate the subset, 389 * keeping only the larger group. The group lister will be able 390 * to show the subgroup as grouped within its larger list. 391 */ 392 for (local i = 1, cnt = groups.length() ; i <= cnt ; ++i) 393 { 394 local g1; 395 local mem1; 396 397 /* get the current group and its membership list */ 398 g1 = groups[i]; 399 mem1 = groupTab[g1]; 400 401 /* look at the other elements to see if we have any subsets */ 402 for (local j = 1 ; j <= cnt ; ++j) 403 { 404 local g2; 405 local mem2; 406 407 /* don't bother checking the same element */ 408 if (j == i) 409 continue; 410 411 /* get the current item and its membership list */ 412 g2 = groups[j]; 413 mem2 = groupTab[g2]; 414 415 /* 416 * if g2's membership is a subset, eliminate g2 from the 417 * group list 418 */ 419 if (isListSubset(mem2, mem1)) 420 { 421 /* remove g2 from the list */ 422 groups.removeElementAt(j); 423 424 /* adjust the loop counters for the removal */ 425 --cnt; 426 --j; 427 428 /* 429 * adjust the outer loop counter if it's affected - 430 * the outer loop is affected if it's already past 431 * this point in the list, which means that its 432 * index is higher than the inner loop index 433 */ 434 if (i > j) 435 --i; 436 } 437 } 438 } 439 440 /* 441 * We now have a final accounting of the groups that we will 442 * consider using. Reset the membership list for each group in 443 * the surviving list. 444 */ 445 foreach (local g in groups) 446 { 447 local itemsInList; 448 449 /* get this group's membership list vector */ 450 itemsInList = groupTab[g]; 451 452 /* clear the vector */ 453 itemsInList.removeRange(1, itemsInList.length()); 454 } 455 456 /* 457 * Now, run through our item list again, and assign each item to 458 * the surviving group that comes earliest in the item's group 459 * list. 460 */ 461 for (i = 1, cnt = lst.length() ; i <= cnt ; ++i) 462 { 463 local curGroups; 464 local winningGroup; 465 466 /* get this object into a local for easy reference */ 467 cur = lst[i]; 468 469 /* if the item isn't part of this listing, skip it */ 470 if (!isListed(cur)) 471 continue; 472 473 /* get the list of groups with which this object is listed */ 474 curGroups = listWith(cur); 475 if (curGroups == nil) 476 curGroups = []; 477 478 /* 479 * find the first element in the group list that is in the 480 * surviving group list 481 */ 482 winningGroup = nil; 483 foreach (local g in curGroups) 484 { 485 /* if this group is in the surviving list, it's the one */ 486 if (groups.indexOf(g) != nil) 487 { 488 winningGroup = g; 489 break; 490 } 491 } 492 493 /* 494 * if we have a group, add this item to the group's 495 * membership; otherwise, add it to the singles list 496 */ 497 if (winningGroup != nil) 498 groupTab[winningGroup].append(cur); 499 else 500 singles.append(cur); 501 } 502 503 /* eliminate any surviving group with too few members */ 504 for (i = 1, cnt = groups.length() ; i <= cnt ; ++i) 505 { 506 local mem; 507 508 /* get this group's membership list */ 509 mem = groupTab[groups[i]]; 510 511 /* 512 * if this group's membership is too small, eliminate the 513 * group and add the member into the singles pile 514 */ 515 if (mem.length() < groups[i].minGroupSize) 516 { 517 /* put the item into the singles list */ 518 if (mem.length() > 0) 519 singles.append(mem[1]); 520 521 /* eliminate this item from the group list */ 522 groups.removeElementAt(i); 523 524 /* adjust the loop counters */ 525 --cnt; 526 --i; 527 } 528 } 529 530 /* return the cardinality of the arranged list */ 531 return getArrangedListCardinality(singles, groups, groupTab); 532 } 533 534 /* 535 * Show the list. This is called after we've figured out which items 536 * we intend to display, and after we've arranged the items into 537 * groups. In rare cases, listers might want to override this, to 538 * customize the way the way the list is displayed based on the 539 * internal arrangement of the list. 540 */ 541 showArrangedList(pov, parent, lst, options, indent, infoTab, itemCount, 542 singles, groups, groupTab, origLst) 543 { 544 /* 545 * We now know how many items we're listing (grammatically 546 * speaking), so we're ready to display the list prefix. If 547 * we're displaying nothing at all, just display the "empty" 548 * message, and we're done. 549 */ 550 if (itemCount == 0) 551 { 552 /* show the empty list */ 553 showListEmpty(pov, parent); 554 } 555 else 556 { 557 local i; 558 local cnt; 559 local sublists; 560 local origOptions = options; 561 local itemOptions; 562 local groupOptions; 563 local listCount; 564 local dispCount; 565 local cur; 566 567 /* 568 * Check to see if we have one or more group sublists - if 569 * we do, we must use the "long" list format for our overall 570 * list, otherwise we can use the normal "short" list 571 * format. The long list format uses semicolons to separate 572 * items. 573 */ 574 for (i = 1, cnt = groups.length(), sublists = nil ; 575 i <= cnt ; ++i) 576 { 577 /* 578 * if this group's lister item displays a sublist, we 579 * must use the long format 580 */ 581 if (groups[i].groupDisplaysSublist) 582 { 583 /* note that we are using the long format */ 584 sublists = true; 585 586 /* 587 * one is enough to make us use the long format, so 588 * we need not look any further 589 */ 590 break; 591 } 592 } 593 594 /* generate the prefix message if we're in a 'tall' listing */ 595 if ((options & ListTall) != 0) 596 { 597 /* indent the prefix */ 598 showListIndent(options, indent); 599 600 /* 601 * Show the prefix. If this is a contents listing, and 602 * it's not at the top level, show the contents prefix; 603 * otherwise show the full list prefix. Note that we can 604 * have a contents listing at the top level, since some 605 * lists are broken out for separate listing. 606 */ 607 if ((options & ListContents) != 0 && indent != 0) 608 showListContentsPrefixTall(itemCount, pov, parent); 609 else 610 showListPrefixTall(itemCount, pov, parent); 611 612 /* go to a new line for the list contents */ 613 "\n"; 614 615 /* indent the items one level now, since we showed a prefix */ 616 ++indent; 617 } 618 else 619 { 620 /* show the prefix */ 621 showListPrefixWide(itemCount, pov, parent); 622 } 623 624 /* 625 * regardless of whether we're adding long formatting to the 626 * main list, display the group sublists with whatever 627 * formatting we were originally using 628 */ 629 groupOptions = options; 630 631 /* show each item with our current set of options */ 632 itemOptions = options; 633 634 /* 635 * if we're using sublists, show "long list" separators in 636 * the main list 637 */ 638 if (sublists) 639 itemOptions |= ListLong; 640 641 /* 642 * calculate the number of items we'll show in the list - 643 * each group shows up as one list entry, so the total 644 * number of list entries is the number of single items plus 645 * the number of groups 646 */ 647 listCount = singles.length() + groups.length(); 648 649 /* 650 * Show the items. Run through the (filtered) original 651 * list, so that we show everything in the original sorting 652 * order. 653 */ 654 dispCount = 0; 655 foreach (cur in lst) 656 { 657 local group; 658 local displayedCur; 659 660 /* presume we'll display this item */ 661 displayedCur = true; 662 663 /* 664 * Figure out how to show this item: if it's in the 665 * singles list, show it as a single item; if it's in 666 * the group list, show its group; if it's in a group 667 * we've previously shown, show nothing, as we showed 668 * the item when we showed its group. 669 */ 670 if (singles.indexOf(cur) != nil) 671 { 672 /* 673 * It's in the singles list, so show it as a single 674 * item. 675 * 676 * If the item has contents that we'll display in 677 * 'tall' mode, show the item with its contents - we 678 * don't need to show the item separately, since it 679 * will provide a 'tall' list prefix showing itself. 680 * Otherwise, show the item singly. 681 */ 682 if ((options & ListTall) != 0 683 && (options & ListRecurse) != 0 684 && contentsListed(cur) 685 && getListedContents(cur, infoTab) != []) 686 { 687 /* show the item with its contents */ 688 showContentsList(pov, cur, origOptions | ListContents, 689 indent, infoTab); 690 } 691 else 692 { 693 /* show the list indent if necessary */ 694 showListIndent(itemOptions, indent); 695 696 /* show the item */ 697 showListItem(cur, itemOptions, pov, infoTab); 698 699 /* 700 * if we're in wide recursive mode, show the 701 * item's contents as an in-line parenthetical 702 */ 703 if ((options & ListTall) == 0 704 && (options & ListRecurse) != 0 705 && contentsListed(cur) 706 && !contentsListedSeparately(cur)) 707 { 708 /* show the item's in-line contents */ 709 showInlineContentsList(pov, cur, 710 origOptions | ListContents, 711 indent + 1, infoTab); 712 } 713 } 714 } 715 else if ((group = groups.valWhich( 716 {g: groupTab[g].indexOf(cur) != nil})) != nil) 717 { 718 /* show the list indent if necessary */ 719 showListIndent(itemOptions, indent); 720 721 /* we found the item in a group, so show its group */ 722 group.showGroupList(pov, self, groupTab[group], 723 groupOptions, indent, infoTab); 724 725 /* 726 * Forget this group - we only need to show each 727 * group once, since the group shows every item it 728 * contains. Since we'll encounter the groups other 729 * members as we continue to scan the main list, we 730 * want to make sure we don't show the group again 731 * when we reach the other items. 732 */ 733 groups.removeElement(group); 734 } 735 else 736 { 737 /* 738 * We didn't find the item in the singles list or in 739 * a group - it must be part of a group that we 740 * already showed previously, so we don't need to 741 * show it again now. Simply make a note that we 742 * didn't display it. 743 */ 744 displayedCur = nil; 745 } 746 747 /* if we displayed the item, show a suitable separator */ 748 if (displayedCur) 749 { 750 /* count another list entry displayed */ 751 ++dispCount; 752 753 /* show an appropriate separator */ 754 showListSeparator(itemOptions, dispCount, listCount); 755 } 756 } 757 758 /* 759 * if we're in 'wide' mode, finish the listing (note that if 760 * this is a 'tall' listing, we're already done, because a 761 * tall listing format doesn't make provisions for anything 762 * after the item list) 763 */ 764 if ((options & ListTall) == 0) 765 { 766 /* show the wide-mode list suffix */ 767 showListSuffixWide(itemCount, pov, parent); 768 } 769 } 770 } 771 772 /* 773 * Get the cardinality of an arranged list. Returns the number of 774 * items that will appear in the list, for grammatical agreement. 775 */ 776 getArrangedListCardinality(singles, groups, groupTab) 777 { 778 local cnt; 779 780 /* start with a count of zero; we'll add to it as we go */ 781 cnt = 0; 782 783 /* 784 * Add up the cardinality of the single items. Some individual 785 * items in the singles list might count as multiple items 786 * grammatically - in particular, if an item has a plural name, 787 * we need a plural verb to agree with it. 788 */ 789 foreach (local s in singles) 790 { 791 /* add the grammatical cardinality of this single item */ 792 cnt += listCardinality(s); 793 } 794 795 /* add in the cardinality of each group */ 796 foreach (local g in groups) 797 { 798 /* add the grammatical cardinality of this group */ 799 cnt += g.groupCardinality(self, groupTab[g]); 800 } 801 802 /* return the total */ 803 return cnt; 804 } 805 806 /* 807 * Get the number of noun phrase elements in a list. This differs 808 * from the cardinality in that we only count noun phrases, not the 809 * cardinality of each noun phrase. So, for example, "five coins" 810 * has cardinality five, but has only one noun phrase. 811 */ 812 getArrangedListNounPhraseCount(singles, groups, groupTab) 813 { 814 local cnt; 815 816 /* each single item counts as one noun phrase */ 817 cnt = singles.length(); 818 819 /* add in the noun phrases from each group */ 820 foreach (local g in groups) 821 cnt += g.groupNounPhraseCount(self, groupTab[g]); 822 823 /* return the total */ 824 return cnt; 825 } 826 827 /* 828 * Service routine: show the separately-listed contents of the items 829 * in the given list, and their separately-listed contents, and so 830 * on. This routine is not normally overridden in subclasses, and is 831 * not usually called except from the Lister implementation. 832 * 833 * For each item in the given list, we show the item's contents if 834 * the item is either marked as unlisted, or it's marked as showing 835 * its contents separately. In the former case, we know that we 836 * cannot have shown the item's contents in-line in the main list, 837 * since we didn't show the item at all in the main list. In the 838 * latter case, we know that we didn't show the item's contents in 839 * the main list because it's specifically marked as showing its 840 * contents out-of-line. 841 */ 842 showSeparateContents(pov, lst, options, infoTab) 843 { 844 /* 845 * show the separate contents list for each item in the list 846 * which isn't itself listable or which has its contents listed 847 * separately despite its being listed 848 */ 849 foreach (local cur in lst) 850 { 851 /* only show the contents if the contents are listed */ 852 if (contentsListed(cur)) 853 { 854 /* 855 * if we didn't list this item, or if it specifically 856 * wants its contents listed out-of-line, show its 857 * listable contents 858 */ 859 if (!isListed(cur) || contentsListedSeparately(cur)) 860 { 861 /* 862 * Show the item's contents. Note that even though 863 * we're showing this list recursively, it's actually 864 * a new top-level list, so show it at indent level 865 * zero. 866 */ 867 showContentsList(pov, cur, options, 0, infoTab); 868 } 869 870 /* recursively do the same thing with its contents */ 871 showSeparateContents(pov, getContents(cur), options, infoTab); 872 } 873 } 874 } 875 876 /* 877 * Service routine: determine if list a is a subset of list b. a is 878 * a subset of b if every element of a is in b. 879 */ 880 isListSubset(a, b) 881 { 882 /* a can't be a subset if it has more elements than b */ 883 if (a.length() > b.length()) 884 return nil; 885 886 /* check each element of a to see if it's also in b */ 887 foreach (local cur in a) 888 { 889 /* if this element of a is not in b, a is not a subset of b */ 890 if (b.indexOf(cur) == nil) 891 return nil; 892 } 893 894 /* 895 * we didn't find any elements of a not in b, so a is a subset 896 * of b 897 */ 898 return true; 899 } 900 901 /* 902 * Show a list indent if necessary. If ListTall is included in the 903 * options, we'll indent to the given level; otherwise we'll do 904 * nothing. 905 */ 906 showListIndent(options, indent) 907 { 908 /* show the indent only if we're in "tall" mode */ 909 if ((options & ListTall) != 0) 910 { 911 for (local i = 0 ; i < indent ; ++i) 912 "\t"; 913 } 914 } 915 916 /* 917 * Show a newline after a list item if we're in a tall list; does 918 * nothing for a wide list. 919 */ 920 showTallListNewline(options) 921 { 922 if ((options & ListTall) != 0) 923 "\n"; 924 } 925 926 /* 927 * Show a simple list, recursing into contents lists if necessary. 928 * We pay no attention to grouping; we just show the items 929 * individually. 930 * 931 * 'prevCnt' is the number of items already displayed, if anything 932 * has already been displayed for this list. This should be zero if 933 * this will display the entire list. 934 */ 935 showListSimple(pov, lst, options, indent, prevCnt, infoTab) 936 { 937 local i; 938 local cnt; 939 local dispCount; 940 local totalCount; 941 942 /* calculate the total number of items in the lis t*/ 943 totalCount = prevCnt + lst.length(); 944 945 /* display the items */ 946 for (i = 1, cnt = lst.length(), dispCount = prevCnt ; i <= cnt ; ++i) 947 { 948 local cur; 949 950 /* get the item */ 951 cur = lst[i]; 952 953 /* 954 * If the item has contents that we'll display in 'tall' 955 * mode, show the item with its contents - we don't need to 956 * show the item separately, since it will provide a 'tall' 957 * list prefix showing itself. Otherwise, show the item 958 * singly. 959 */ 960 if ((options & ListTall) != 0 961 && (options & ListRecurse) != 0 962 && contentsListed(cur) 963 && getListedContents(cur, infoTab) != []) 964 { 965 /* show the item with its contents */ 966 showContentsList(pov, cur, options | ListContents, 967 indent + 1, infoTab); 968 } 969 else 970 { 971 /* show the list indent if necessary */ 972 showListIndent(options, indent); 973 974 /* show the item */ 975 showListItem(cur, options, pov, infoTab); 976 977 /* 978 * if we're in wide recursive mode, show the item's 979 * contents as an in-line parenthetical 980 */ 981 if ((options & ListTall) == 0 982 && (options & ListRecurse) != 0 983 && contentsListed(cur) 984 && !contentsListedSeparately(cur)) 985 { 986 /* show the item's in-line contents */ 987 showInlineContentsList(pov, cur, 988 options | ListContents, 989 indent + 1, infoTab); 990 } 991 } 992 993 /* count the item displayed */ 994 ++dispCount; 995 996 /* show the list separator */ 997 showListSeparator(options, dispCount, totalCount); 998 } 999 } 1000 1001 /* 1002 * List the contents of an item. 1003 * 1004 * 'pov' is the point of view, which is usually an actor (and 1005 * usually the player character actor). 1006 * 1007 * 'obj' is the item whose contents we are to display. 1008 * 1009 * 'options' is the set of flags that we'll pass to showList(), and 1010 * has the same meaning as for that function. 1011 * 1012 * 'infoTab' is a lookup table of SenseInfo objects giving the sense 1013 * information for all of the objects that the actor to whom we're 1014 * showing the contents listing can sense. 1015 */ 1016 showContentsList(pov, obj, options, indent, infoTab) 1017 { 1018 /* 1019 * List the item's contents. By default, use the contentsLister 1020 * property of the object whose contents we're showing to obtain 1021 * the lister for the contents. 1022 */ 1023 obj.showObjectContents(pov, obj.contentsLister, options, 1024 indent, infoTab); 1025 } 1026 1027 /* 1028 * Determine if an object's contents are listed separately from its 1029 * own list entry for the purposes of our type of listing. If this 1030 * returns true, then we'll list the object's contents in a separate 1031 * listing (a separate sentence following the main listing sentence, 1032 * or a separate tree when in 'tall' mode). 1033 * 1034 * Note that this only matters for objects listed in the top-level 1035 * list. We'll always show the contents separately for an object 1036 * that isn't listed in the top-level list (i.e., an object for which 1037 * isListed(obj) returns nil). 1038 */ 1039 contentsListedSeparately(obj) { return obj.contentsListedSeparately; } 1040 1041 /* 1042 * Show an "in-line" contents list. This shows the item's contents 1043 * list as a parenthetical, as part of a recursive listing. This is 1044 * pretty much the same as showContentsList(), but uses the object's 1045 * in-line contents lister instead of its regular contents lister. 1046 */ 1047 showInlineContentsList(pov, obj, options, indent, infoTab) 1048 { 1049 /* show the item's contents using its in-line contents lister */ 1050 obj.showObjectContents(pov, obj.inlineContentsLister, 1051 options, indent, infoTab); 1052 } 1053 1054 /* 1055 * Show the prefix for a 'wide' listing - this is a message that 1056 * appears just before we start listing the objects. 'itemCount' is 1057 * the number of items to be listed; the items might be grouped in 1058 * the listing, so a list that comes out as "three boxes and two 1059 * books" will have an itemCount of 5. (The purpose of itemCount is 1060 * to allow the message to have grammatical agreement in number.) 1061 * 1062 * This will never be called with an itemCount of zero, because we 1063 * will instead use showListEmpty() to display an empty list. 1064 */ 1065 showListPrefixWide(itemCount, pov, parent) { } 1066 1067 /* 1068 * show the suffix for a 'wide' listing - this is a message that 1069 * appears just after we finish listing the objects 1070 */ 1071 showListSuffixWide(itemCount, pov, parent) { } 1072 1073 /* 1074 * Show the list prefix for a 'tall' listing. Note that there is no 1075 * list suffix for a tall listing, because the format doesn't allow 1076 * it. 1077 */ 1078 showListPrefixTall(itemCount, pov, parent) { } 1079 1080 /* 1081 * Show the list prefix for the contents of an object in a 'tall' 1082 * listing. By default, we just show our usual tall list prefix. 1083 */ 1084 showListContentsPrefixTall(itemCount, pov, parent) 1085 { showListPrefixTall(itemCount, pov, parent); } 1086 1087 /* 1088 * Show an empty list. If the list to be displayed has no items at 1089 * all, this is called instead of the prefix/suffix routines. This 1090 * can be left empty if no message is required for an empty list, or 1091 * can display the complete message appropriate for an empty list 1092 * (such as "You are empty-handed"). 1093 */ 1094 showListEmpty(pov, parent) { } 1095 1096 /* 1097 * Is this item to be listed in room descriptions? Returns true if 1098 * so, nil if not. By default, we'll use the object's isListed 1099 * method to make this determination. We virtualize this into the 1100 * lister interface to allow for different inclusion rules for the 1101 * same object depending on the type of list we're generating. 1102 */ 1103 isListed(obj) { return obj.isListed(); } 1104 1105 /* 1106 * Get the grammatical cardinality of this listing item. This should 1107 * return the number of items that this item appears to be 1108 * grammatically, for noun-verb agreement purposes. 1109 */ 1110 listCardinality(obj) { return obj.listCardinality(self); } 1111 1112 /* 1113 * Are this item's contents listable? 1114 */ 1115 contentsListed(obj) { return obj.contentsListed; } 1116 1117 /* 1118 * Get all contents of this item. 1119 */ 1120 getContents(obj) { return obj.contents; } 1121 1122 /* 1123 * Get the listed contents of an object. 'infoTab' is the sense 1124 * information table for the enclosing listing. By default, we call 1125 * the object's getListedContents() method, but this is virtualized 1126 * in the lister interface to allow for listing other hierarchies 1127 * besides ordinary contents. 1128 */ 1129 getListedContents(obj, infoTab) 1130 { 1131 return obj.getListedContents(self, infoTab); 1132 } 1133 1134 /* 1135 * Get the list of grouping objects for listing the item. By 1136 * default, we return the object's listWith result. Subclasses can 1137 * override this to specify different groupings for the same object 1138 * depending on the type of list we're generating. 1139 * 1140 * The group list returned is in order from most general to most 1141 * specific. For example, if an item is grouped with coins in 1142 * general and silver coins in particular, the general coins group 1143 * would come first, then the silver coin group, because the silver 1144 * coin group is more specific. 1145 */ 1146 listWith(obj) { return obj.listWith; } 1147 1148 /* show an item in a list */ 1149 showListItem(obj, options, pov, infoTab) 1150 { 1151 obj.showListItem(options, pov, infoTab); 1152 } 1153 1154 /* 1155 * Show a set of equivalent items as a counted item ("three coins"). 1156 * The listing mechanism itself never calls this directly; instead, 1157 * this is provided so that ListGroupEquivalent can ask the lister 1158 * how to describe its equivalent sets, so that different listers 1159 * can customize the display of equivalent items. 1160 * 1161 * 'lst' is the full list of equivalent items. By default, we pick 1162 * one of these arbitrarily to show, since they're presumably all 1163 * the same for the purposes of the list. 1164 */ 1165 showListItemCounted(lst, options, pov, infoTab) 1166 { 1167 /* 1168 * by defualt, show the counted name for one of the items 1169 * (chosen arbitrarily, since they're all the same) 1170 */ 1171 lst[1].showListItemCounted(lst, options, pov, infoTab); 1172 } 1173 1174 /* 1175 * Show a list separator after displaying an item. curItemNum is 1176 * the number of the item just displayed (1 is the first item), and 1177 * totalItems is the total number of items that will be displayed in 1178 * the list. 1179 * 1180 * This generic routine is further parameterized by properties for 1181 * the individual types of separators. This default implementation 1182 * distinguishes the following separators: the separator between the 1183 * two items in a list of exactly two items; the separator between 1184 * adjacent items other than the last two in a list of more than two 1185 * items; and the separator between the last two elements of a list 1186 * of more than two items. 1187 */ 1188 showListSeparator(options, curItemNum, totalItems) 1189 { 1190 local useLong = ((options & ListLong) != 0); 1191 1192 /* if this is a tall list, the separator is simply a newline */ 1193 if ((options & ListTall) != 0) 1194 { 1195 "\n"; 1196 return; 1197 } 1198 1199 /* if that was the last item, there are no separators */ 1200 if (curItemNum == totalItems) 1201 return; 1202 1203 /* check to see if the next item is the last */ 1204 if (curItemNum + 1 == totalItems) 1205 { 1206 /* 1207 * We just displayed the penultimate item in the list, so we 1208 * need to use the special last-item separator. If we're 1209 * only displaying two items total, we use an even more 1210 * special separator. 1211 */ 1212 if (totalItems == 2) 1213 { 1214 /* use the two-item separator */ 1215 if (useLong) 1216 longListSepTwo; 1217 else 1218 listSepTwo; 1219 } 1220 else 1221 { 1222 /* use the normal last-item separator */ 1223 if (useLong) 1224 longListSepEnd; 1225 else 1226 listSepEnd; 1227 } 1228 } 1229 else 1230 { 1231 /* in the middle of the list - display the normal separator */ 1232 if (useLong) 1233 longListSepMiddle; 1234 else 1235 listSepMiddle; 1236 } 1237 } 1238 1239 /* 1240 * Show the specific types of list separators for this list. By 1241 * default, these will use the generic separators defined in the 1242 * library messages object (libMessages). For English, these are 1243 * commas and semicolons for short and long lists, respectively; the 1244 * word "and" for a list with only two items; and a comma or 1245 * semicolon and the word "and" between the last two items in a list 1246 * with more than two items. 1247 */ 1248 1249 /* 1250 * normal and "long list" separator between the two items in a list 1251 * with exactly two items 1252 */ 1253 listSepTwo { libMessages.listSepTwo; } 1254 longListSepTwo { libMessages.longListSepTwo; } 1255 1256 /* 1257 * normal and long list separator between items in list with more 1258 * than two items 1259 */ 1260 listSepMiddle { libMessages.listSepMiddle; } 1261 longListSepMiddle { libMessages.longListSepMiddle; } 1262 1263 /* 1264 * normal and long list separator between second-to-last and last 1265 * items in a list with more than two items 1266 */ 1267 listSepEnd { libMessages.listSepEnd; } 1268 longListSepEnd { libMessages.longListSepEnd; } 1269 1270 /* 1271 * Get my "top-level" lister. For a sub-lister, this will return 1272 * the parent lister's top-level lister. The default lister is a 1273 * top-level lister, so we just return ourself. 1274 */ 1275 getTopLister() { return self; } 1276 1277 /* 1278 * The last custom flag defined by this class. Lister and each 1279 * subclass are required to define this so that each subclass can 1280 * allocate its own custom flags in a manner that adapts 1281 * automatically to future additions of flags to base classes. As 1282 * the base class, we allocate our flags statically with #define's, 1283 * so we simply use the fixed #define'd last flag value here. 1284 */ 1285 nextCustomFlag = ListCustomFlag 1286; 1287 1288 1289/* ------------------------------------------------------------------------ */ 1290/* 1291 * Plain lister - this lister doesn't show anything for an empty list, 1292 * and doesn't show a list suffix or prefix. 1293 */ 1294plainLister: Lister 1295 /* show the prefix/suffix in wide mode */ 1296 showListPrefixWide(itemCount, pov, parent) { } 1297 showListSuffixWide(itemCount, pov, parent) { } 1298 1299 /* show the tall prefix */ 1300 showListPrefixTall(itemCount, pov, parent) { } 1301; 1302 1303/* 1304 * Sub-lister for listing the contents of a group. This lister shows a 1305 * simple list with no prefix or suffix, and otherwise uses the 1306 * characteristics of the parent lister. 1307 */ 1308class GroupSublister: object 1309 construct(parentLister, parentGroup) 1310 { 1311 /* remember the parent lister and group objects */ 1312 self.parentLister = parentLister; 1313 self.parentGroup = parentGroup; 1314 } 1315 1316 /* no prefix or suffix */ 1317 showListPrefixWide(itemCount, pov, parent) { } 1318 showListSuffixWide(itemCount, pov, parent) { } 1319 showListPrefixTall(itemCount, pov, parent) { } 1320 1321 /* show nothing when empty */ 1322 showListEmpty(pov, parent) { } 1323 1324 /* 1325 * Show an item in the list. Rather than going through the parent 1326 * lister directly, we go through the parent group, so that it can 1327 * customize the display of items in the group. 1328 */ 1329 showListItem(obj, options, pov, infoTab) 1330 { 1331 /* ask the parent group to handle it */ 1332 parentGroup.showGroupItem(parentLister, obj, options, pov, infoTab); 1333 } 1334 1335 /* 1336 * Show a counted item in the group. As with showListItem, we ask 1337 * the parent group to do the work, so that it can customize the 1338 * display if desired. 1339 */ 1340 showListItemCounted(lst, options, pov, infoTab) 1341 { 1342 /* ask the parent group to handle it */ 1343 parentGroup.showGroupItemCounted( 1344 parentLister, lst, options, pov, infoTab); 1345 } 1346 1347 /* delegate everything we don't explicitly handle to our parent lister */ 1348 propNotDefined(prop, [args]) 1349 { 1350 return delegated (getTopLister()).(prop)(args...); 1351 } 1352 1353 /* get the top-level lister - returns my parent's top-level lister */ 1354 getTopLister() { return parentLister.getTopLister(); } 1355 1356 /* my parent lister */ 1357 parentLister = nil 1358 1359 /* my parent list group */ 1360 parentGroup = nil 1361; 1362 1363/* 1364 * Paragraph lister: this shows its list items separated by paragraph 1365 * breaks, with a paragraph break before the first item. 1366 */ 1367class ParagraphLister: Lister 1368 /* start the list with a paragraph break */ 1369 showListPrefixWide(itemCount, pov, parent) { "<.p>"; } 1370 1371 /* we show no list separators */ 1372 showListSeparator(options, curItemNum, totalItems) 1373 { 1374 /* add a paragraph separator between items */ 1375 if (curItemNum != totalItems) 1376 "<.p>"; 1377 } 1378; 1379 1380/* 1381 * Lister for objects in a room description with special descriptions. 1382 * Each special description gets its own paragraph, so this is based on 1383 * the paragraph lister. 1384 */ 1385specialDescLister: ParagraphLister 1386 /* list everything */ 1387 isListed(obj) { return true; } 1388 1389 /* show a list item */ 1390 showListItem(obj, options, pov, infoTab) 1391 { 1392 /* show the object's special description */ 1393 obj.showSpecialDescWithInfo(infoTab[obj], pov); 1394 } 1395 1396 /* use the object's special description grouper */ 1397 listWith(obj) { return obj.specialDescListWith; } 1398; 1399 1400/* 1401 * Special description lister for the contents of an item being examined. 1402 * This is similar to the regular specialDescLister, but shows the 1403 * special descriptions of the contents of an object being described with 1404 * "examine" or "look in," rather than of the entire location. 1405 */ 1406class SpecialDescContentsLister: ParagraphLister 1407 construct(cont) 1408 { 1409 /* remember the containing object being described */ 1410 cont_ = cont; 1411 } 1412 1413 /* list everything */ 1414 isListed(obj) { return true; } 1415 1416 /* show a list item */ 1417 showListItem(obj, options, pov, infoTab) 1418 { 1419 /* show the object's special description in our container */ 1420 obj.showSpecialDescInContentsWithInfo(infoTab[obj], pov, cont_); 1421 } 1422 1423 /* use the object's special description grouper */ 1424 listWith(obj) { return obj.specialDescListWith; } 1425 1426 /* the containing object we're examining */ 1427 cont_ = nil 1428; 1429 1430 1431/* 1432 * Plain lister for actors. This is the same as an ordinary 1433 * plainLister, but ignores each object's isListed flag and lists it 1434 * anyway. 1435 */ 1436plainActorLister: plainLister 1437 isListed(obj) { return true; } 1438; 1439 1440/* 1441 * Grouper for actors in a common posture and in a common location. We 1442 * create one of these per room per posture when we discover actors in 1443 * the room during "look around" (or "examine" on a nested room). This 1444 * grouper lets us group actors like so: "Dan and Jane are sitting on 1445 * the couch." 1446 */ 1447class RoomActorGrouper: ListGroup 1448 construct(location, posture) 1449 { 1450 self.location = location; 1451 self.posture = posture; 1452 } 1453 1454 showGroupList(pov, lister, lst, options, indent, infoTab) 1455 { 1456 local cont; 1457 local outer; 1458 1459 /* if the location isn't in the sense table, skip the whole list */ 1460 if (infoTab[location] == nil) 1461 return; 1462 1463 /* get the nominal posture container, if it's visible */ 1464 cont = location.getNominalActorContainer(posture); 1465 if (cont != nil && !pov.canSee(cont)) 1466 cont = nil; 1467 1468 /* get the outermost visible enclosing location */ 1469 outer = location.getOutermostVisibleRoom(pov); 1470 1471 /* 1472 * Only mention the outermost location if it's remote and it's 1473 * not the same as the nominal container. (If the remote outer 1474 * room is the same as the nominal container, it would be 1475 * redundant to mention it as both the nominal and remote 1476 * container.) 1477 */ 1478 if (outer == cont || pov.isIn(outer)) 1479 outer = nil; 1480 1481 /* create a sub-lister for the group */ 1482 lister = createGroupSublister(lister); 1483 1484 /* 1485 * show the list prefix message - use the nominal container if 1486 * we can see it, otherwise generate a generic message 1487 */ 1488 if (cont != nil) 1489 cont.actorInGroupPrefix(pov, posture, outer, lst); 1490 else if (outer != nil) 1491 libMessages.actorThereGroupPrefix(pov, posture, outer, lst); 1492 else 1493 libMessages.actorHereGroupPrefix(posture, lst); 1494 1495 /* list the actors' names as a plain list */ 1496 plainActorLister.showList(pov, location, lst, options, 1497 indent, infoTab, self); 1498 1499 /* add the suffix message */ 1500 if (cont != nil) 1501 cont.actorInGroupSuffix(pov, posture, outer, lst); 1502 else if (outer != nil) 1503 libMessages.actorThereGroupSuffix(pov, posture, outer, lst); 1504 else 1505 libMessages.actorHereGroupSuffix(posture, lst); 1506 } 1507; 1508 1509/* 1510 * Base class for inventory listers. This lister uses a special listing 1511 * method to show the items, so that items can be shown with special 1512 * notations in an inventory list that might not appear in other types 1513 * of listings. 1514 */ 1515class InventoryLister: Lister 1516 /* list items in inventory according to their isListedInInventory */ 1517 isListed(obj) { return obj.isListedInInventory; } 1518 1519 /* 1520 * Show list items using the inventory name, which might differ from 1521 * the regular nmae of the object. 1522 */ 1523 showListItem(obj, options, pov, infoTab) 1524 { obj.showInventoryItem(options, pov, infoTab); } 1525 1526 showListItemCounted(lst, options, pov, infoTab) 1527 { lst[1].showInventoryItemCounted(lst, options, pov, infoTab); } 1528 1529 /* 1530 * Show contents of the items in the inventory. We customize this 1531 * so that we can differentiate inventory contents lists from other 1532 * contents lists. 1533 */ 1534 showContentsList(pov, obj, options, indent, infoTab) 1535 { 1536 /* list the item's contents */ 1537 obj.showInventoryContents(pov, obj.contentsLister, options, 1538 indent, infoTab); 1539 } 1540 1541 /* 1542 * Show the contents in-line, for an inventory listing. 1543 */ 1544 showInlineContentsList(pov, obj, options, indent, infoTab) 1545 { 1546 /* list the item's contents using its in-line lister */ 1547 obj.showInventoryContents(pov, obj.inlineContentsLister, 1548 options, indent, infoTab); 1549 } 1550; 1551 1552/* 1553 * Base class for worn-inventory listers. This lister uses a special 1554 * listing method to show the items, so that items being worn are shown 1555 * *without* the special '(being worn)' notation that might otherwise 1556 * appear in inventory listings. 1557 */ 1558class WearingLister: InventoryLister 1559 /* show the list item using the "worn listing" name */ 1560 showListItem(obj, options, pov, infoTab) 1561 { obj.showWornItem(options, pov, infoTab); } 1562 showListItemCounted(lst, options, pov, infoTab) 1563 { lst[1].showWornItemCounted(lst, options, pov, infoTab); } 1564; 1565 1566/* 1567 * "Divided" inventory lister. In 'wide' mode, this shows inventory in 1568 * two parts: the items being carried, and the items being worn. (We use 1569 * the standard single tree-style listing in 'tall' mode.) 1570 */ 1571class DividedInventoryLister: InventoryLister 1572 /* 1573 * Show the list. We completely override the main lister method so 1574 * that we can show our two lists. 1575 */ 1576 showList(pov, parent, lst, options, indent, infoTab, parentGroup) 1577 { 1578 /* 1579 * If this is a 'tall' listing, use the normal listing style; for 1580 * a 'wide' listing, use our special segregated style. If we're 1581 * being invoked recursively to show a contents listing, we 1582 * similarly want to use the base handling. 1583 */ 1584 if ((options & (ListTall | ListContents)) != 0) 1585 { 1586 /* inherit the standard behavior */ 1587 inherited(pov, parent, lst, options, indent, infoTab, 1588 parentGroup); 1589 } 1590 else 1591 { 1592 local carryingLst, wearingLst; 1593 local carryingStr, wearingStr; 1594 1595 /* divide the lists into 'carrying' and 'wearing' sublists */ 1596 carryingLst = new Vector(32); 1597 wearingLst = new Vector(32); 1598 foreach (local cur in lst) 1599 (cur.isWornBy(parent) ? wearingLst : carryingLst).append(cur); 1600 1601 /* generate and capture the 'carried' listing */ 1602 carryingStr = outputManager.curOutputStream.captureOutput({: 1603 carryingLister.showList(pov, parent, carryingLst, options, 1604 indent, infoTab, parentGroup)}); 1605 1606 /* generate and capture the 'worn' listing */ 1607 wearingStr = outputManager.curOutputStream.captureOutput({: 1608 wearingLister.showList(pov, parent, wearingLst, options, 1609 indent, infoTab, parentGroup)}); 1610 1611 /* generate the combined listing */ 1612 showCombinedInventoryList(parent, carryingStr, wearingStr); 1613 1614 /* 1615 * Now show the out-of-line contents for the whole list, if 1616 * appropriate. We save this until after showing both parts 1617 * of the list, to keep the direct inventory parts together 1618 * at the beginning of the output. 1619 */ 1620 if ((options & ListRecurse) != 0 1621 && indent == 0 1622 && (options & ListContents) == 0) 1623 { 1624 /* show the contents of each object we didn't list */ 1625 showSeparateContents(pov, lst, options | ListContents, 1626 infoTab); 1627 } 1628 } 1629 } 1630 1631 /* 1632 * Show the combined listing. This must be provided by each 1633 * language-specific subclass. The inputs are the results (strings) 1634 * of the captured output of the sublistings of the items being 1635 * carried and the items being worn. These will be "raw" listings, 1636 * without any prefix or suffix text. This routine's job is to 1637 * display the final output, adding the framing text. 1638 */ 1639 showCombinedInventoryList(parent, carrying, wearing) { } 1640 1641 /* 1642 * The recommended maximum number of number of noun phrases to show 1643 * in the single-sentence format. This should be used by the 1644 * showCombinedInventoryList() method to decide whether to display 1645 * the combined listing as a single sentence or as two separate 1646 * sentences. 1647 */ 1648 singleSentenceMaxNouns = 7 1649 1650 /* 1651 * Our associated sub-listers for items begin carried and worn, 1652 * respectively. We'll use these to list our sublist of items being 1653 * worn. 1654 */ 1655 carryingLister = actorCarryingSublister 1656 wearingLister = actorWearingSublister 1657; 1658 1659/* 1660 * Base class for the inventory sub-lister for items being carried. This 1661 * is a minor specialization of the basic inventory lister; in this 1662 * version, we omit any prefix, suffix, or empty messages, since we'll 1663 * rely on the caller to combine our raw listing with the raw 'wearing' 1664 * listing to form the full results. 1665 * 1666 * This type of lister should normally only be used from within an 1667 * inventory lister. This type of lister assumes that it's part of a 1668 * larger listing controlled externally; for example, we don't show 1669 * out-of-line contents, since we assume the caller will be doing this. 1670 */ 1671class InventorySublister: InventoryLister 1672 /* don't show any prefix, suffix, or 'empty' messages */ 1673 showListPrefixWide(itemCount, pov, parent) { } 1674 showListSuffixWide(itemCount, pov, parent) { } 1675 showListEmpty(pov, parent) { } 1676 1677 /* don't show out-of-line contents */ 1678 showSeparateContents(pov, lst, options, infoTab) { } 1679; 1680 1681/* 1682 * Base class for the inventory sub-lister for items being worn. We use 1683 * a special listing method to show these items, so that items being 1684 * shown explicitly in a worn list can be shown differently from the way 1685 * they would in a normal inventory list. (For example, a worn item in a 1686 * normal inventory list might show a "(worn)" indication, whereas it 1687 * would not want to show a similar indication in a list of objects 1688 * explicitly being worn.) 1689 * 1690 * This type of lister should normally only be used from within an 1691 * inventory lister. This type of lister assumes that it's part of a 1692 * larger listing controlled externally; for example, we don't show 1693 * out-of-line contents, since we assume the caller will be doing this. 1694 */ 1695class WearingSublister: WearingLister 1696 /* don't show any prefix, suffix, or 'empty' messages */ 1697 showListPrefixWide(itemCount, pov, parent) { } 1698 showListSuffixWide(itemCount, pov, parent) { } 1699 showListEmpty(pov, parent) { } 1700 1701 /* don't show out-of-line contents */ 1702 showSeparateContents(pov, lst, options, infoTab) { } 1703; 1704 1705/* 1706 * The standard inventory sublisters. 1707 */ 1708actorCarryingSublister: InventorySublister; 1709actorWearingSublister: WearingSublister; 1710 1711/* 1712 * Base class for contents listers. This is used to list the contents 1713 * of the objects that appear in top-level lists (a top-level list is 1714 * the list of objects directly in a room that appears in a room 1715 * description, or the list of items being carried in an INVENTORY 1716 * command, or the direct contents of an object being examined). 1717 */ 1718class ContentsLister: Lister 1719; 1720 1721/* 1722 * Base class for description contents listers. This is used to list 1723 * the contents of an object when we examine the object, or when we 1724 * explicitly LOOK IN the object. 1725 */ 1726class DescContentsLister: Lister 1727 /* 1728 * Use the explicit look-in flag for listing contents. We might 1729 * list items within an object on explicit examination of the item 1730 * that we wouldn't list in a room or inventory list containing the 1731 * item. 1732 */ 1733 isListed(obj) { return obj.isListedInContents; } 1734; 1735 1736/* 1737 * Base class for sense listers, which list the items that can be sensed 1738 * for a command like "listen" or "smell". 1739 */ 1740class SenseLister: ParagraphLister 1741 /* show everything we're asked to list */ 1742 isListed(obj) { return true; } 1743 1744 /* show a counted list item */ 1745 showListItemCounted(lst, options, pov, infoTab) 1746 { 1747 /* 1748 * simply show one item, without the count - non-visual senses 1749 * don't distinguish numbers of items that are equivalent 1750 */ 1751 showListItem(lst[1], options, pov, infoTab); 1752 } 1753; 1754 1755/* 1756 * Room contents lister for things that can be heard. 1757 */ 1758roomListenLister: SenseLister 1759 /* list an item in a room if its isSoundListedInRoom is true */ 1760 isListed(obj) { return obj.isSoundListedInRoom; } 1761 1762 /* list an item */ 1763 showListItem(obj, options, pov, infoTab) 1764 { 1765 /* show the "listen" list name for the item */ 1766 obj.soundHereDesc(); 1767 } 1768; 1769 1770/* 1771 * Lister for explicit "listen" action 1772 */ 1773listenActionLister: roomListenLister 1774 /* list everything in response to an explicit general LISTEN command */ 1775 isListed(obj) { return true; } 1776 1777 /* show an empty list */ 1778 showListEmpty(pov, parent) 1779 { 1780 mainReport(¬hingToHearMsg); 1781 } 1782 1783; 1784 1785/* 1786 * Room contents lister for things that can be smelled. 1787 */ 1788roomSmellLister: SenseLister 1789 /* list an item in a room if its isSmellListedInRoom is true */ 1790 isListed(obj) { return obj.isSmellListedInRoom; } 1791 1792 /* list an item */ 1793 showListItem(obj, options, pov, infoTab) 1794 { 1795 /* show the "smell" list name for the item */ 1796 obj.smellHereDesc(); 1797 } 1798; 1799 1800/* 1801 * Lister for explicit "smell" action 1802 */ 1803smellActionLister: roomSmellLister 1804 /* list everything in response to an explicit general SMELL command */ 1805 isListed(obj) { return true; } 1806 1807 /* show an empty list */ 1808 showListEmpty(pov, parent) 1809 { 1810 mainReport(¬hingToSmellMsg); 1811 } 1812 1813; 1814 1815/* 1816 * Inventory lister for things that can be heard. 1817 */ 1818inventoryListenLister: SenseLister 1819 /* list an item */ 1820 showListItem(obj, options, pov, infoTab) 1821 { 1822 /* show the "listen" list name for the item */ 1823 obj.soundHereDesc(); 1824 } 1825; 1826 1827/* 1828 * Inventory lister for things that can be smelled. 1829 */ 1830inventorySmellLister: SenseLister 1831 /* list an item */ 1832 showListItem(obj, options, pov, infoTab) 1833 { 1834 /* show the "smell" list name for the item */ 1835 obj.smellHereDesc(); 1836 } 1837; 1838 1839 1840/* ------------------------------------------------------------------------ */ 1841/* 1842 * List Group Interface. An instance of this object is created for each 1843 * set of objects that are to be grouped together. 1844 */ 1845class ListGroup: object 1846 /* 1847 * Show a list of items from this group. All of the items in the 1848 * list will be members of this list group; we are to display a 1849 * sentence fragment showing the items in the list, suitable for 1850 * embedding in a larger list. 1851 * 1852 * 'options', 'indent', and 'infoTab' have the same meaning as they 1853 * do for showList(). 1854 * 1855 * Note that we are not to display any separator before or after our 1856 * list; the caller is responsible for that. 1857 */ 1858 showGroupList(pov, lister, lst, options, indent, infoTab) { } 1859 1860 /* 1861 * Show an item in the group's sublist. The sublister calls this to 1862 * display each item in the group when the group calls the sublister 1863 * to display the group list. By default, we simply let the 1864 * sublister handle the request, which gives items in our group 1865 * sublist the same appearance they would have had in the sublist to 1866 * begin with. We can customize this behavior to give our list 1867 * items a different appearance special to the group sublist. 1868 * 1869 * Note that the same customization could be accomplished by 1870 * creating a specialized subclass of GroupSublister in 1871 * createGroupSublister(), and overriding showListItem() in the 1872 * specialized GroupSublister subclass. We use this mechanism as a 1873 * convenience, so that a separate group sublister class doesn't 1874 * have to be created simply to customize the display of group 1875 * items. 1876 */ 1877 showGroupItem(sublister, obj, options, pov, infoTab) 1878 { 1879 /* by default, list using the regular sublister */ 1880 sublister.showListItem(obj, options, pov, infoTab); 1881 } 1882 1883 /* 1884 * Show a counted item in our group list. This is the counted item 1885 * equivalent of showGroupItem. 1886 */ 1887 showGroupItemCounted(sublister, lst, options, pov, infoTab) 1888 { 1889 /* by default, list using the regular sublister */ 1890 sublister.showListItemCounted(lst, options, pov, infoTab); 1891 } 1892 1893 /* 1894 * Determine if showing the group list will introduce a sublist into 1895 * an enclosing list. This should return true if we will show a 1896 * sublist without some kind of grouping, so that the caller knows 1897 * to introduce some extra grouping into its enclosing list. This 1898 * should return nil if the sublist we display will be clearly set 1899 * off in some way (for example, by being enclosed in parentheses). 1900 */ 1901 groupDisplaysSublist = true 1902 1903 /* 1904 * The minimum number of elements for which we should retain the 1905 * group in a listing. By default, we need two elements to display a 1906 * group; any group with only one element is discarded, and the 1907 * single element is moved into the 'singles' list. This can be 1908 * overridden to allow single-element groups to be retained. In most 1909 * cases, it's undesirable to retain single-element groups, but when 1910 * grouping is used to partition a list into two or more fixed 1911 * portions, single-element groups become desirable. 1912 */ 1913 minGroupSize = 2 1914 1915 /* 1916 * Determine the cardinality of the group listing, grammatically 1917 * speaking. This is the number of items that the group seems to be 1918 * for the purposes of grammatical agreement. For example, if the 1919 * group is displayed as "$1.38 in change", it would be singular for 1920 * grammatical agreement, hence would return 1 here; if it displays 1921 * "five coins (two copper, three gold)," it would count as five 1922 * items for grammatical agreement. 1923 * 1924 * For languages (like English) that grammatically distinguish 1925 * number only between singular and plural, it is sufficient for 1926 * this to return 1 for singular and anything higher for plural. 1927 * For the sake of languages that make more elaborate number 1928 * distinctions for grammatical agreement, though, this should 1929 * return as accurate a count as is possible. 1930 * 1931 * By default, we return the number of items to be displayed in the 1932 * list group. This should be overridden when necessary, such as 1933 * when the group message is singular in usage even if the list has 1934 * multiple items (as in "$1.38 in change"). 1935 */ 1936 groupCardinality(lister, lst) { return lst.length(); } 1937 1938 /* 1939 * Get the number of noun phrases this group will display. This 1940 * differs from the cardinality in that it doesn't matter how many 1941 * *objects* the phrases represent; it only matters how many phrases 1942 * are displayed. For example, "five coins" has cardinality 5 but 1943 * only displays one noun phrase. 1944 * 1945 * By default, we simply return the number of items in the group, 1946 * since most groups individually list their items. 1947 */ 1948 groupNounPhraseCount(lister, lst) { return lst.length(); } 1949 1950 /* 1951 * Create the group sublister. 1952 * 1953 * In most cases, when a group displays a list of the items in the 1954 * group as a sublist, it will not want to use the same lister that 1955 * was used to show the enclosing group, because the enclosing lister 1956 * will usually have different prefix/suffix styles than the sublist. 1957 * However, the group list and the enclosing list might share many 1958 * other attributes, such as the style of name to use when displaying 1959 * items in the list. The default sublister we create, 1960 * GroupSublister, is a hybrid that uses the enclosing lister's 1961 * attributes except for a few, such as the prefix and suffix, that 1962 * usually need to be changed for the sublist. 1963 * 1964 * This can be overridden to use a completely customized Lister 1965 * object for the group list, if desired. 1966 */ 1967 createGroupSublister(parentLister) 1968 { 1969 /* create the standard group sublister by default */ 1970 return new GroupSublister(parentLister, self); 1971 } 1972; 1973 1974/* 1975 * A "custom" List Group implementation. This type of lister uses a 1976 * completely custom message to show the group, without a need to 1977 * recursively invoke a lister to list the individual elements. The 1978 * main difference between this and the base ListGroup is that the 1979 * interface to the custom message generator is very simple - we can 1980 * dispense with most of the numerous arguments that the base group 1981 * message receives, since most of those arguments are there to allow 1982 * recursive listing of the group list. 1983 */ 1984class ListGroupCustom: ListGroup 1985 showGroupList(pov, lister, lst, options, indent, infoTab) 1986 { 1987 /* simply show the custom message for the list */ 1988 showGroupMsg(lst); 1989 } 1990 1991 /* show the custom group message - subclasses should override */ 1992 showGroupMsg(lst) { } 1993; 1994 1995/* 1996 * Sorted group list. This is a list that simply displays its members 1997 * in a specific sorting order. 1998 */ 1999class ListGroupSorted: ListGroup 2000 /* 2001 * Show the group list 2002 */ 2003 showGroupList(pov, lister, lst, options, indent, infoTab) 2004 { 2005 /* put the list in sorted order */ 2006 lst = sortListGroup(lst); 2007 2008 /* create a sub-lister for the group */ 2009 lister = createGroupSublister(lister); 2010 2011 /* show the list */ 2012 lister.showList(pov, nil, lst, options & ~ListContents, 2013 indent, infoTab, self); 2014 } 2015 2016 /* 2017 * Sort the group list. By default, if we have a 2018 * compareGroupItems() method defined, we'll sort the list using 2019 * that method; otherwise, we'll just return the list unchanged. 2020 */ 2021 sortListGroup(lst) 2022 { 2023 /* 2024 * if we have a compareGroupItems method, use it to sort the 2025 * list; otherwise, just return the list in its current order 2026 */ 2027 if (propDefined(&compareGroupItems, PropDefAny)) 2028 return lst.sort(SortAsc, {a, b: compareGroupItems(a, b)}); 2029 else 2030 return lst; 2031 } 2032 2033 /* 2034 * Compare a pair of items from the group to determine their sorting 2035 * order. Returns 0 if the two items are at the same sorting order, 2036 * 1 if the first item sorts after the second item, -1 if the first 2037 * item sorts before the second item. 2038 * 2039 * We do not provide a default implementation of this method, so 2040 * that sortListGroup will know not to bother sorting the list at 2041 * all if the subclass doesn't provide a definition for this method. 2042 */ 2043 // compareGroupItems(a, b) { return a > b ? 1 : a == b ? 0 : -1; } 2044; 2045 2046/* 2047 * List Group implementation: parenthesized sublist. Displays the 2048 * number of items collectively, then displays the list of items in 2049 * parentheses. 2050 * 2051 * Note that this is a ListGroupSorted subclass. If our subclass 2052 * defines a compareGroupItems() method, we'll show the list in the 2053 * order specified by compareGroupItems(). 2054 */ 2055class ListGroupParen: ListGroupSorted 2056 /* 2057 * show the group list 2058 */ 2059 showGroupList(pov, lister, lst, options, indent, infoTab) 2060 { 2061 /* sort the list group, if we have an ordering method defined */ 2062 lst = sortListGroup(lst); 2063 2064 /* create a sub-lister for the group */ 2065 lister = createGroupSublister(lister); 2066 2067 /* show the collective count of the object */ 2068 showGroupCountName(lst); 2069 2070 /* show the tall or wide sublist */ 2071 if ((options & ListTall) != 0) 2072 { 2073 /* tall list - show the items as a sublist */ 2074 "\n"; 2075 lister.showList(pov, nil, lst, options & ~ListContents, 2076 indent, infoTab, self); 2077 } 2078 else 2079 { 2080 /* wide list - add a space and a paren for the sublist */ 2081 " ("; 2082 2083 /* show the sublist */ 2084 lister.showList(pov, nil, lst, options & ~ListContents, 2085 indent, infoTab, self); 2086 2087 /* end the sublist */ 2088 ")"; 2089 } 2090 } 2091 2092 /* 2093 * Show the collective count for the list of objects. By default, 2094 * we'll simply display the countName of the first item in the list, 2095 * on the assumption that each object has the same plural 2096 * description. However, in most cases this should be overridden to 2097 * provide a more general collective name for the group. 2098 */ 2099 showGroupCountName(lst) 2100 { 2101 /* show the first item's countName */ 2102 say(lst[1].countName(lst.length())); 2103 } 2104 2105 /* we don't add a sublist, since we enclose our list in parentheses */ 2106 groupDisplaysSublist = nil 2107; 2108 2109/* 2110 * List Group implementation: simple prefix/suffix lister. Shows a 2111 * prefix message, then shows the list, then shows a suffix message. 2112 * 2113 * Note that this is a ListGroupSorted subclass. If our subclass 2114 * defines a compareGroupItems() method, we'll show the list in the 2115 * order specified by compareGroupItems(). 2116 */ 2117class ListGroupPrefixSuffix: ListGroupSorted 2118 showGroupList(pov, lister, lst, options, indent, infoTab) 2119 { 2120 /* sort the list group, if we have an ordering method defined */ 2121 lst = sortListGroup(lst); 2122 2123 /* create a sub-lister for the group */ 2124 lister = createGroupSublister(lister); 2125 2126 /* show the prefix */ 2127 showGroupPrefix(pov, lst); 2128 2129 /* if we're in tall mode, start a new line */ 2130 lister.showTallListNewline(options); 2131 2132 /* show the list */ 2133 lister.showList(pov, nil, lst, options & ~ListContents, 2134 indent + 1, infoTab, self); 2135 2136 /* show the suffix */ 2137 showGroupSuffix(pov, lst); 2138 } 2139 2140 /* show the prefix - we just show the groupPrefix message by default */ 2141 showGroupPrefix(pov, lst) { groupPrefix; } 2142 2143 /* show the suffix - we just show the groupSuffix message by default */ 2144 showGroupSuffix(pov, lst) { groupSuffix; } 2145 2146 /* 2147 * The prefix and suffix messages. The showGroupPrefix and 2148 * showGroupSuffix methods simply show these message properties. We 2149 * go through this two-step procedure for convenience: if the 2150 * subclass doesn't need the POV and list parameters, it's less 2151 * typing to just override these parameterless properties. If the 2152 * subclass needs to vary the message according to the POV or what's 2153 * in the list, it can override the showGroupXxx methods instead. 2154 */ 2155 groupPrefix = "" 2156 groupSuffix = "" 2157; 2158 2159/* 2160 * Equivalent object list group. This is the default listing group for 2161 * equivalent items. The Thing class creates an instance of this class 2162 * during initialization for each set of equivalent items. 2163 */ 2164class ListGroupEquivalent: ListGroup 2165 showGroupList(pov, lister, lst, options, indent, infoTab) 2166 { 2167 /* show a count of the items */ 2168 lister.showListItemCounted(lst, options, pov, infoTab); 2169 } 2170 2171 /* 2172 * An equivalence group displays only a single noun phrase to cover 2173 * the entire group. 2174 */ 2175 groupNounPhraseCount(lister, lst) { return 1; } 2176 2177 /* we display as a single item, so there's no sublist */ 2178 groupDisplaysSublist = nil 2179; 2180 2181