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}