001package squidpony; 002 003import squidpony.squidmath.CrossHash; 004import squidpony.squidmath.RNG; 005import squidpony.squidmath.StatefulRNG; 006 007import java.util.*; 008 009/** 010 * A class for generating random monster descriptions; can be subclassed to generate stats for a specific game. Use the 011 * nested Chimera class for most of the functionality here; MonsterGen is here so you can change the descriptors that 012 * monsters can be given (they are in public array fields). You can call randomizeAppearance or randomizePowers on a 013 * Chimera to draw from the list of descriptors in MonsterGen, or fuse two Chimera objects with the mix method in the 014 * Chimera class. Chimeras can be printed to a usable format with presentVisible or present; the former does not print 015 * special powers and is suitable for monsters being encountered, and the latter is more useful for texts in the game 016 * world that describe some monster. 017 * Created by Tommy Ettinger on 1/31/2016. 018 */ 019public class MonsterGen { 020 021 public static StatefulRNG srng = new StatefulRNG(); 022 023 public String[] components = new String[]{"head", "tail", "legs", "claws", "fangs", "eyes", "hooves", "beak", 024 "wings", "pseudopods", "snout", "carapace", "sting", "pincers", "fins", "shell"}, 025 adjectives = new String[]{"hairy", "scaly", "feathered", "chitinous", "pulpy", "writhing", "horrid", 026 "fuzzy", "reptilian", "avian", "insectoid", "tentacled", "thorny", "angular", "curvaceous", "lean", 027 "metallic", "stony", "glassy", "gaunt", "obese", "ill-proportioned", "sickly", "asymmetrical", "muscular"}, 028 powerAdjectives = new String[]{"fire-breathing", "electrified", "frigid", "toxic", "noxious", "nimble", 029 "brutish", "bloodthirsty", "furious", "reflective", "regenerating", "earth-shaking", "thunderous", 030 "screeching", "all-seeing", "semi-corporeal", "vampiric", "skulking", "terrifying", "undead", "mechanical", 031 "angelic", "plant-like", "fungal", "contagious", "graceful", "malevolent", "gigantic", "wailing"}, 032 powerPhrases = new String[]{"can evoke foul magic", "can petrify with its gaze", "hurts your eyes to look at", 033 "can spit venom", "can cast arcane spells", "can call on divine power", "embodies the wilderness", 034 "hates all other species", "constantly drools acid", "whispers maddening secrets in forgotten tongues", 035 "shudders between impossible dimensions", "withers any life around it", "revels in pain"}; 036 037 /** 038 * A creature that can be mixed with other Chimeras or given additional descriptors, then printed in a usable format 039 * for game text. 040 */ 041 public static class Chimera 042 { 043 public LinkedHashMap<String, List<String>> parts; 044 public LinkedHashSet<String> unsaidAdjectives, wholeAdjectives, powerAdjectives, powerPhrases; 045 public String name, mainForm, unknown; 046 047 /** 048 * Copies an existing Chimera other into a new Chimera with potentially a different name. 049 * @param name the name to use for the Chimera this constructs 050 * @param other the existing Chimera to copy all fields but name from. 051 */ 052 public Chimera(String name, Chimera other) 053 { 054 this.name = name; 055 unknown = other.unknown; 056 if(unknown != null) 057 mainForm = unknown; 058 else 059 mainForm = other.name; 060 parts = new LinkedHashMap<>(other.parts); 061 List<String> oldParts = new ArrayList<>(parts.remove(mainForm)); 062 parts.put(name, oldParts); 063 unsaidAdjectives = new LinkedHashSet<>(other.unsaidAdjectives); 064 wholeAdjectives = new LinkedHashSet<>(other.wholeAdjectives); 065 powerAdjectives = new LinkedHashSet<>(other.powerAdjectives); 066 powerPhrases = new LinkedHashSet<>(other.powerPhrases); 067 } 068 069 /** 070 * Constructs a Chimera given a name (typically all lower-case), null if the creature is familiar or a String if 071 * the creature's basic shape is likely to be unknown to players, and an array or vararg of String terms 072 * containing, usually, several groups of String elements separated by the literal string ";" . The first group 073 * in terms contains what body parts this creature has and could potentially grant to another creature if mixed; 074 * examples are "head", "legs", "claws", "wings", and "eyes". In the next group are the "unsaid" adjectives, 075 * which are not listed if unknown is false, but may be contributed to other creatures if mixed (mixing a horse 076 * with a snake may make the horse scaly, since "scaly" is an unsaid adjective for snakes). Next are adjectives 077 * that apply to the whole creature's appearance, which don't need to replicate the unsaid adjectives and are 078 * often added as a step to randomize a creature; this part is often empty and simply ends on the separator ";" 079 * . Next are the power adjectives, which are any special abilities a creature might have that aren't 080 * immediately visible, like "furious" or "toxic". Last are the power phrases, which follow a format like "can 081 * cast arcane spells", "embodies the wilderness", or "constantly drools acid"; it should be able to be put in a 082 * sentence after the word "that", like "a snake that can cast arcane spells". 083 * <br> 084 * The unknown argument determines if descriptions need to include basic properties like calling a Snake scaly 085 * (null in this case) or a Pestilence Fiend chitinous (no one knows what that creature is, so a String needs to 086 * be given so a player and player character that don't know its name can call it something, like "demon"). 087 * <br> 088 * An example is {@code Chimera SNAKE = new Chimera("snake", null, "head", "tail", "fangs", "eyes", ";", 089 * "reptilian", "scaly", "lean", "curvaceous", ";", ";", "toxic");} 090 * @param name the name to refer to the creature by and its body parts by when mixed 091 * @param unknown true if the creature's basic shape is unlikely to be known by a player, false for animals and 092 * possibly common mythological creatures like dragons 093 * @param terms an array or vararg of String elements, separated by ";" , see method documentation for details 094 */ 095 public Chimera(String name, String unknown, String... terms) 096 { 097 this.name = name; 098 this.unknown = unknown; 099 if(unknown != null) 100 mainForm = unknown; 101 else 102 mainForm = name; 103 parts = new LinkedHashMap<>(); 104 unsaidAdjectives = new LinkedHashSet<>(); 105 wholeAdjectives = new LinkedHashSet<>(); 106 powerAdjectives = new LinkedHashSet<>(); 107 powerPhrases = new LinkedHashSet<>(); 108 ArrayList<String> selfParts = new ArrayList<>(); 109 int t = 0; 110 for (; t < terms.length; t++) { 111 if(terms[t].equals(";")) 112 { 113 t++; 114 break; 115 } 116 selfParts.add(terms[t]); 117 } 118 parts.put(name, selfParts); 119 for (; t < terms.length; t++) { 120 if (terms[t].equals(";")) { 121 t++; 122 break; 123 } 124 unsaidAdjectives.add(terms[t]); 125 } 126 for (; t < terms.length; t++) { 127 if (terms[t].equals(";")) { 128 t++; 129 break; 130 } 131 wholeAdjectives.add(terms[t]); 132 } 133 wholeAdjectives.removeAll(unsaidAdjectives); 134 for (; t < terms.length; t++) { 135 if (terms[t].equals(";")) { 136 t++; 137 break; 138 } 139 powerAdjectives.add(terms[t]); 140 } 141 for (; t < terms.length; t++) { 142 if (terms[t].equals(";")) { 143 break; 144 } 145 powerPhrases.add(terms[t]); 146 } 147 } 148 /** 149 * Constructs a Chimera given a name (typically all lower-case), null if the creature is familiar or a String if 150 * the creature's basic shape is likely to be unknown to players, and several String Collection args for the 151 * different aspects of the Chimera. The first Collection contains what body parts this creature has and could 152 * potentially grant to another creature if mixed; examples are "head", "legs", "claws", "wings", and "eyes". 153 * The next Collection contains "unsaid" adjectives, which are not listed if unknown is false, but may be 154 * contributed to other creatures if mixed (mixing a horse with a snake may make the horse scaly, since "scaly" 155 * is an unsaid adjective for snakes). Next are adjectives that apply to the "whole" creature's appearance, 156 * which don't need to replicate the unsaid adjectives and are often added as a step to randomize a creature; 157 * this Collection is often empty. Next are the power adjectives, which are any special abilities a creature 158 * might have that aren't immediately visible, like "furious" or "toxic". Last are the power phrases, which 159 * follow a format like "can cast arcane spells", "embodies the wilderness", or "constantly drools acid"; it 160 * should be able to be put in a sentence after the word "that", like "a snake that can cast arcane spells". 161 * <br> 162 * The unknown argument determines if descriptions need to include basic properties like calling a Snake scaly 163 * (null in this case) or a Pestilence Fiend chitinous (no one knows what that creature is, so a String needs to 164 * be given so a player and player character that don't know its name can call it something, like "demon"). 165 * <br> 166 * An example is {@code Chimera SNAKE = new Chimera("snake", null, "head", "tail", "fangs", "eyes", ";", 167 * "reptilian", "scaly", "lean", "curvaceous", ";", ";", "toxic");} 168 * @param name the name to refer to the creature by and its body parts by when mixed 169 * @param unknown true if the creature's basic shape is unlikely to be known by a player, false for animals and 170 * possibly common mythological creatures like dragons 171 * @param parts the different body part nouns this creature can contribute to a creature when mixed 172 * @param unsaid appearance adjectives that don't need to be said if the creature is familiar 173 * @param whole appearance adjectives that apply to the whole creature 174 * @param powerAdj power adjectives like "furious" or "fire-breathing" 175 * @param powerPhr power phrases like "can cast arcane spells" 176 */ 177 public Chimera(String name, String unknown, Collection<String> parts, Collection<String> unsaid, 178 Collection<String> whole, Collection<String> powerAdj, Collection<String> powerPhr) 179 { 180 this.name = name; 181 this.unknown = unknown; 182 if(unknown != null) 183 mainForm = unknown; 184 else 185 mainForm = name; 186 this.parts = new LinkedHashMap<String, List<String>>(); 187 unsaidAdjectives = new LinkedHashSet<String>(unsaid); 188 wholeAdjectives = new LinkedHashSet<String>(whole); 189 powerAdjectives = new LinkedHashSet<String>(powerAdj); 190 powerPhrases = new LinkedHashSet<String>(powerPhr); 191 ArrayList<String> selfParts = new ArrayList<String>(parts); 192 this.parts.put(name, selfParts); 193 } 194 195 /** 196 * Get a string description of this monster's appearance and powers. 197 * @param capitalize true if the description should start with a capital letter. 198 * @return a String description including both appearance and powers 199 */ 200 public String present(boolean capitalize) 201 { 202 StringBuilder sb = new StringBuilder(), tmp = new StringBuilder(); 203 if(capitalize) 204 sb.append('A'); 205 else 206 sb.append('a'); 207 int i = 0; 208 LinkedHashSet<String> allAdjectives = new LinkedHashSet<>(wholeAdjectives); 209 if(unknown != null) 210 allAdjectives.addAll(unsaidAdjectives); 211 allAdjectives.addAll(powerAdjectives); 212 for(String adj : allAdjectives) 213 { 214 tmp.append(adj); 215 if(++i < allAdjectives.size()) 216 tmp.append(','); 217 tmp.append(' '); 218 } 219 tmp.append(mainForm); 220 String ts = tmp.toString(); 221 if(ts.matches("^[aeiouAEIOU].*")) 222 sb.append('n'); 223 sb.append(' '); 224 sb.append(ts); 225 if(!(powerPhrases.isEmpty() && parts.size() == 1)) 226 sb.append(' '); 227 if(parts.size() > 1) 228 { 229 sb.append("with the"); 230 i = 1; 231 for(Map.Entry<String, List<String>> ent : parts.entrySet()) 232 { 233 if(ent.getKey().equals(name)) 234 continue; 235 if(ent.getValue().isEmpty()) 236 sb.append(" feel"); 237 else 238 { 239 int j = 1; 240 for(String p : ent.getValue()) 241 { 242 sb.append(' '); 243 sb.append(p); 244 if(j++ < ent.getValue().size() && ent.getValue().size() > 2) 245 sb.append(','); 246 if(j == ent.getValue().size() && ent.getValue().size() >= 2) 247 sb.append(" and"); 248 } 249 } 250 sb.append(" of a "); 251 sb.append(ent.getKey()); 252 253 254 if(i++ < parts.size() && parts.size() > 3) 255 sb.append(','); 256 if(i == parts.size() && parts.size() >= 3) 257 sb.append(" and"); 258 sb.append(' '); 259 } 260 } 261 262 if(!powerPhrases.isEmpty()) 263 sb.append("that"); 264 i = 1; 265 for(String phr : powerPhrases) 266 { 267 sb.append(' '); 268 sb.append(phr); 269 if(i++ < powerPhrases.size() && powerPhrases.size() > 2) 270 sb.append(','); 271 if(i == powerPhrases.size() && powerPhrases.size() >= 2) 272 sb.append(" and"); 273 } 274 return sb.toString(); 275 } 276 277 /** 278 * Get a string description of this monster's appearance. 279 * @param capitalize true if the description should start with a capital letter. 280 * @return a String description including only the monster's appearance 281 */ 282 public String presentVisible(boolean capitalize) 283 { 284 StringBuilder sb = new StringBuilder(), tmp = new StringBuilder(); 285 if(capitalize) 286 sb.append('A'); 287 else 288 sb.append('a'); 289 int i = 0; 290 291 LinkedHashSet<String> allAdjectives = new LinkedHashSet<>(wholeAdjectives); 292 if(unknown != null) 293 allAdjectives.addAll(unsaidAdjectives); 294 for(String adj : allAdjectives) 295 { 296 tmp.append(adj); 297 if(++i < allAdjectives.size()) 298 tmp.append(','); 299 tmp.append(' '); 300 } 301 tmp.append(mainForm); 302 String ts = tmp.toString(); 303 if(ts.matches("^[aeiouAEIOU].*")) 304 sb.append('n'); 305 sb.append(' '); 306 sb.append(ts); 307 if(parts.size() > 1) 308 { 309 sb.append(" with the"); 310 i = 1; 311 for(Map.Entry<String, List<String>> ent : parts.entrySet()) 312 { 313 if(ent.getKey().equals(name)) 314 continue; 315 if(ent.getValue().isEmpty()) 316 sb.append(" feel"); 317 else 318 { 319 int j = 1; 320 for(String p : ent.getValue()) 321 { 322 sb.append(' '); 323 sb.append(p); 324 if(j++ < ent.getValue().size() && ent.getValue().size() > 2) 325 sb.append(','); 326 if(j == ent.getValue().size() && ent.getValue().size() >= 2) 327 sb.append(" and"); 328 } 329 } 330 sb.append(" of a "); 331 sb.append(ent.getKey()); 332 333 334 if(i++ < parts.size() && parts.size() > 3) 335 sb.append(','); 336 if(i == parts.size() && parts.size() >= 3) 337 sb.append(" and"); 338 sb.append(' '); 339 } 340 } 341 342 return sb.toString(); 343 } 344 345 @Override 346 public String toString() { 347 return name; 348 } 349 350 /** 351 * Fuse two Chimera objects by some fraction of influence, using the given RNG and possibly renaming the 352 * creature. Does not modify the existing Chimera objects. 353 * @param rng the RNG to determine random factors 354 * @param newName the name to call the produced Chimera 355 * @param other the Chimera to mix with this one 356 * @param otherInfluence the fraction between 0.0 and 1.0 of descriptors from other to use 357 * @return a new Chimera mixing features from both inputs 358 */ 359 public Chimera mix(RNG rng, String newName, Chimera other, double otherInfluence) 360 { 361 Chimera next = new Chimera(newName, this); 362 List<String> otherParts = other.parts.get(other.name), 363 p2 = rng.randomPortion(otherParts, (int)Math.round(otherParts.size() * otherInfluence * 0.5)); 364 next.parts.put(other.name, p2); 365 String[] unsaid = other.unsaidAdjectives.toArray(new String[other.unsaidAdjectives.size()]), 366 talentAdj = other.powerAdjectives.toArray(new String[other.powerAdjectives.size()]), 367 talentPhr = other.powerPhrases.toArray(new String[other.powerPhrases.size()]); 368 unsaid = portion(rng, unsaid, (int)Math.round(unsaid.length * otherInfluence)); 369 talentAdj = portion(rng, talentAdj, (int)Math.round(talentAdj.length * otherInfluence)); 370 talentPhr = portion(rng, talentPhr, (int)Math.round(talentPhr.length * otherInfluence)); 371 Collections.addAll(next.wholeAdjectives, unsaid); 372 Collections.addAll(next.powerAdjectives, talentAdj); 373 Collections.addAll(next.powerPhrases, talentPhr); 374 375 return next; 376 } 377 378 /** 379 * Fuse two Chimera objects by some fraction of influence, using the default RNG and possibly renaming the 380 * creature. Does not modify the existing Chimera objects. 381 * @param newName the name to call the produced Chimera 382 * @param other the Chimera to mix with this one 383 * @param otherInfluence the fraction between 0.0 and 1.0 of descriptors from other to use 384 * @return a new Chimera mixing features from both inputs 385 */ 386 public Chimera mix(String newName, Chimera other, double otherInfluence) 387 { 388 return mix(srng, newName, other, otherInfluence); 389 } 390 } 391 public static final Chimera SNAKE = new Chimera("snake", null, "head", "tail", "fangs", "eyes", ";", 392 "reptilian", "scaly", "lean", "curvaceous", ";", 393 ";", 394 "toxic"), 395 LION = new Chimera("lion", null, "head", "tail", "legs", "claws", "fangs", "eyes", ";", 396 "hairy", "muscular", ";", 397 ";", 398 "furious"), 399 HORSE = new Chimera("horse", null, "head", "tail", "legs", "hooves", "eyes", ";", 400 "fuzzy", "muscular", "lean", ";", 401 ";", 402 "nimble"), 403 HAWK = new Chimera("hawk", null, "head", "tail", "legs", "claws", "beak", "eyes", "wings", ";", 404 "feathered", "avian", "lean", ";", 405 ";", 406 "screeching", "nimble"), 407 SHOGGOTH = new Chimera("shoggoth", "non-Euclidean ooze", "eyes", "fangs", "pseudopods", ";", 408 "pulpy", "horrid", "tentacled", ";", 409 ";", 410 "terrifying", "regenerating", "semi-corporeal", ";", 411 "shudders between impossible dimensions"); 412 413 /** 414 * Constructs a MonsterGen with a random seed for the default RNG. 415 */ 416 public MonsterGen() 417 { 418 419 } 420 /** 421 * Constructs a MonsterGen with the given seed for the default RNG. 422 */ 423 public MonsterGen(long seed) 424 { 425 srng.setState(seed); 426 } 427 /** 428 * Constructs a MonsterGen with the given seed (hashing seed with CrossHash) for the default RNG. 429 */ 430 public MonsterGen(String seed) 431 { 432 srng.setState(CrossHash.hash(seed)); 433 } 434 435 /** 436 * Randomly add appearance descriptors to a copy of the Chimera creature. Produces a new Chimera, potentially with a 437 * different name, and adds the specified count of adjectives (if any are added that the creature already has, they 438 * are ignored, and this includes unsaid adjectives if the creature is known). 439 * @param rng the RNG to determine random factors 440 * @param creature the Chimera to add descriptors to 441 * @param newName the name to call the produced Chimera 442 * @param adjectiveCount the number of adjectives to add; may add less if some overlap 443 * @return a new Chimera with additional appearance descriptors 444 */ 445 public Chimera randomizeAppearance(RNG rng, Chimera creature, String newName, int adjectiveCount) 446 { 447 Chimera next = new Chimera(newName, creature); 448 Collections.addAll(next.wholeAdjectives, portion(rng, adjectives, adjectiveCount)); 449 next.wholeAdjectives.removeAll(next.unsaidAdjectives); 450 return next; 451 } 452 453 /** 454 * Randomly add appearance descriptors to a copy of the Chimera creature. Produces a new Chimera, potentially with a 455 * different name, and adds the specified count of adjectives (if any are added that the creature already has, they 456 * are ignored, and this includes unsaid adjectives if the creature is known). 457 * @param creature the Chimera to add descriptors to 458 * @param newName the name to call the produced Chimera 459 * @param adjectiveCount the number of adjectives to add; may add less if some overlap 460 * @return a new Chimera with additional appearance descriptors 461 */ 462 public Chimera randomizeAppearance(Chimera creature, String newName, int adjectiveCount) 463 { 464 return randomizeAppearance(srng, creature, newName, adjectiveCount); 465 } 466 467 /** 468 * Randomly add power descriptors to a copy of the Chimera creature. Produces a new Chimera, potentially with a 469 * different name, and adds the specified total count of power adjectives and phrases (if any are added that the 470 * creature already has, they are ignored). 471 * @param rng the RNG to determine random factors 472 * @param creature the Chimera to add descriptors to 473 * @param newName the name to call the produced Chimera 474 * @param powerCount the number of adjectives to add; may add less if some overlap 475 * @return a new Chimera with additional power descriptors 476 */ 477 public Chimera randomizePowers(RNG rng, Chimera creature, String newName, int powerCount) 478 { 479 Chimera next = new Chimera(newName, creature); 480 int adjs = rng.nextInt(powerCount + 1), phrs = powerCount - adjs; 481 Collections.addAll(next.powerAdjectives, portion(rng, powerAdjectives, adjs)); 482 Collections.addAll(next.powerPhrases, portion(rng, powerPhrases, phrs)); 483 return next; 484 } 485 486 /** 487 * Randomly add power descriptors to a copy of the Chimera creature. Produces a new Chimera, potentially with a 488 * different name, and adds the specified total count of power adjectives and phrases (if any are added that the 489 * creature already has, they are ignored). 490 * @param creature the Chimera to add descriptors to 491 * @param newName the name to call the produced Chimera 492 * @param powerCount the number of adjectives to add; may add less if some overlap 493 * @return a new Chimera with additional power descriptors 494 */ 495 public Chimera randomizePowers(Chimera creature, String newName, int powerCount) 496 { 497 return randomizePowers(srng, creature, newName, powerCount); 498 } 499 500 /** 501 * Randomly add appearance and power descriptors to a new Chimera creature with random body part adjectives. 502 * Produces a new Chimera with the specified name, and adds the specified total count (detail) of appearance 503 * adjectives, power adjectives and phrases, and the same count (detail) of body parts. 504 * @param rng the RNG to determine random factors 505 * @param newName the name to call the produced Chimera 506 * @param detail the number of adjectives and phrases to add, also the number of body parts 507 * @return a new Chimera with random traits 508 */ 509 public Chimera randomize(RNG rng, String newName, int detail) 510 { 511 ArrayList<String> ps = new ArrayList<String>(); 512 Collections.addAll(ps, portion(rng, components, detail)); 513 Chimera next = new Chimera(newName, "thing", ps, new ArrayList<String>(), 514 new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>()); 515 if(detail > 0) { 516 int powerCount = rng.nextInt(detail), bodyCount = detail - powerCount; 517 int adjs = rng.nextInt(powerCount + 1), phrs = powerCount - adjs; 518 519 Collections.addAll(next.unsaidAdjectives, portion(rng, adjectives, bodyCount)); 520 Collections.addAll(next.powerAdjectives, portion(rng, powerAdjectives, adjs)); 521 Collections.addAll(next.powerPhrases, portion(rng, powerPhrases, phrs)); 522 } 523 return next; 524 } 525 526 /** 527 * Randomly add appearance and power descriptors to a new Chimera creature with random body part adjectives. 528 * Produces a new Chimera with the specified name, and adds the specified total count (detail) of appearance 529 * adjectives, power adjectives and phrases, and the same count (detail) of body parts. 530 * @param newName the name to call the produced Chimera 531 * @param detail the number of adjectives and phrases to add, also the number of body parts 532 * @return a new Chimera with random traits 533 */ 534 public Chimera randomize(String newName, int detail) 535 { 536 return randomize(srng, newName, detail); 537 } 538 539 /** 540 * Randomly add appearance and power descriptors to a new Chimera creature with random body part adjectives. 541 * Produces a new Chimera with a random name using FakeLanguageGen, and adds a total of 5 appearance adjectives, 542 * power adjectives and phrases, and 5 body parts. Since this uses FakeLanguageGen, it isn't GWT-compatible. 543 * @return a new Chimera with random traits 544 */ 545 public Chimera randomize() 546 { 547 return randomize(srng, randomName(srng), 5); 548 } 549 550 /** 551 * Gets a random name as a String using FakeLanguageGen. Since this uses FakeLanguageGen, it isn't GWT-compatible. 552 * @param rng the RNG to use for random factors 553 * @return a String meant to be used as a creature name 554 */ 555 public String randomName(RNG rng) 556 { 557 return FakeLanguageGen.FANTASY_NAME.word(rng, false, rng.between(2, 4)); 558 } 559 560 /** 561 * Gets a random name as a String using FakeLanguageGen. Since this uses FakeLanguageGen, it isn't GWT-compatible. 562 * @return a String meant to be used as a creature name 563 */ 564 public String randomName() 565 { 566 return randomName(srng); 567 } 568 569 private static String[] portion(RNG rng, String[] source, int amount) 570 { 571 return rng.randomPortion(source, new String[Math.min(source.length, amount)]); 572 } 573 574}