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