File Coverage

blib/lib/Game/CharacterSheetGenerator.pm
Criterion Covered Total %
statement 521 602 86.5
branch 225 308 73.0
condition 81 115 70.4
subroutine 62 69 89.8
pod 0 58 0.0
total 889 1152 77.1


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             # Copyright (C) 2012-2022 Alex Schroeder
3              
4             # This program is free software: you can redistribute it and/or modify it under
5             # the terms of the GNU General Public License as published by the Free Software
6             # Foundation, either version 3 of the License, or (at your option) any later
7             # version.
8             #
9             # This program is distributed in the hope that it will be useful, but WITHOUT
10             # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11             # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License along with
14             # this program. If not, see .
15              
16             =encoding utf8
17              
18             =head1 NAME
19              
20             Game::CharacterSheetGenerator - a web app to generate character sheets
21              
22             =head1 DESCRIPTION
23              
24             Character Sheet Generator is a web application that generates characters for the
25             Halberts & Helmets game. It does two things: it generates the stats for random
26             characters, and it populates a SVG file with those values.
27              
28             Here's an example of the stats generated:
29              
30             name: Diara
31             str: 11
32             dex: 10
33             con: 13
34             int: 10
35             wis: 9
36             cha: 7
37             level: 1
38             xp: 0
39             thac0: 19
40             class: halfling
41             hp: 4
42             ac: 6
43             property: backpack
44             property: rope
45             property: leather armour
46             property: silver dagger
47             property: sling
48             property: pouch with 30 stones
49             abilities: 1/6 for normal tasks
50             abilities: 2/6 to hide and sneak
51             abilities: 5/6 to hide and sneak outside
52             abilities: +1 for ranged weapons
53             abilities: AC -2 against giants
54             charsheet: Charaktersheet.svg
55             breath: 13
56             poison: 8
57             petrify: 10
58             wands: 9
59             spells: 12
60              
61             Think of it as key value pairs. Some keys have multiple values, resulting in
62             multiline values.
63              
64             The SVG file acts as a template. For every key in the character, a C
65             element with a matching id is searched and if found, C elements matching
66             the value are inserted.
67              
68             The C key is special because it tells the app which file to load.
69              
70             On a technical level, Character Sheet Generator is a web app based on the
71             Mojolicious framework. This class in particular uses L.
72              
73             See L for more information.
74              
75             =cut
76              
77             package Game::CharacterSheetGenerator;
78              
79             our $VERSION = 1.01;
80              
81 9     9   8071 use Modern::Perl;
  9         21  
  9         64  
82 9     9   4794 use Mojolicious::Lite;
  9         882854  
  9         118  
83 9     9   182227 use Mojo::UserAgent;
  9         20  
  9         48  
84 9     9   204 use Mojo::Log;
  9         20  
  9         48  
85 9     9   4018 use File::ShareDir "dist_dir";
  9         188551  
  9         441  
86 9     9   4328 use I18N::AcceptLanguage;
  9         8510  
  9         267  
87 9     9   4924 use XML::LibXML;
  9         231958  
  9         55  
88 9     9   1268 use List::Util qw(shuffle max);
  9         25  
  9         710  
89 9     9   63 use POSIX qw(floor ceil);
  9         22  
  9         81  
90 9     9   632 use Cwd;
  9         22  
  9         483  
91 9     9   50 no warnings qw(uninitialized numeric);
  9         20  
  9         130492  
92              
93             # Commands for the command line!
94             push @{app->commands->namespaces}, "Game::CharacterSheetGenerator::Command";
95              
96             # Change scheme if "X-Forwarded-Proto" header is set (presumably to HTTPS)
97             app->hook(before_dispatch => sub {
98             my $c = shift;
99             $c->req->url->base->scheme("https")
100             if $c->req->headers->header("X-Forwarded-Proto") } );
101              
102             =head2 Configuration
103              
104             As a Mojolicious application, it will read a config file called
105             F in the same directory, if it exists. As the
106             default log level is "debug", one use of the config file is to change the log
107             level using the C key, and if you're not running the server in a
108             terminal, using the C key to set a file.
109              
110             The default map and table are stored in the F directory. You can change
111             this directory using the C key. By default, the directory included with
112             the distribution is used. Thus, if you're a developer, you probably want to use
113             something like the following to use the files from the source directory.
114              
115             The code also needs to know where the Face Generator can be found, if at all.
116             You can set the URL using the C key. If you're a developer
117             and have it running locally on port 3020, this is what you'd use:
118              
119             {
120             loglevel => "debug",
121             logfile => undef,
122             contrib => "share",
123             face_generator_url => "http://localhost:3020",
124             }
125              
126             =cut
127              
128             plugin Config => {
129             default => {
130             loglevel => "warn",
131             logfile => undef,
132             contrib => undef,
133             },
134             file => getcwd() . "/character-sheet-generator.conf",
135             };
136              
137             my $log = Mojo::Log->new;
138             $log->level(app->config("loglevel"));
139             $log->path(app->config("logfile"));
140             $log->debug($log->path ? "Logfile is " . $log->path : "Logging to stderr");
141              
142             my $dist_dir = app->config("contrib") // dist_dir("Game-CharacterSheetGenerator");
143             $log->debug("Reading contrib files from $dist_dir");
144              
145             sub translations {
146             # strings in sinqle quotes are translated into German if necessary
147             # use %0, %1, etc. for parameters
148 10     10 0 6036 my %translations = split(/\n/, q{%0 gold
149             %0 Gold
150             %0 silver
151             %0 Silber
152             %0: How much does this cost?
153             %0: Wieviel kostet das?
154             +1 bonus to ranged weapons
155             +1 für Fernwaffen
156             +4 to hit and double damage backstabbing
157             +4 und Schaden ×2 für hinterhältigen Angriff
158             1/6 for normal tasks
159             1/6 für normale Aufgaben
160             2/6 to find secret constructions and traps
161             2/6 um Geheimbauten und Fallen zu finden
162             2/6 to find secret or concealed doors
163             2/6 um geheime und versteckte Türen zu finden
164             2/6 to hear noise
165             2/6 um Geräusche zu hören
166             2/6 to hide and sneak
167             2/6 für Verstecken und Schleichen
168             5/6 to hide and sneak outdoors
169             5/6 für Verstecken und Schleichen im Freien
170             %d/6 for all activities
171             %d/6 für alle Aktivitäten
172             AC -2 vs. opponents larger than humans
173             Rüstung -2 bei Gegnern über Menschengrösse
174             Charactersheet.svg
175             Charakterblatt.svg
176             Charactersheet-landscape.svg
177             Charakterblatt-quer.svg
178             Hireling.svg
179             Mietling.svg
180             Classes
181             Klassen
182             Property
183             Eigentum
184             Spells:
185             Zaubersprüche:
186             Unknown Price
187             Unbekannter Preis
188             and
189             und
190             backpack
191             Rucksack
192             battle axe
193             Streitaxt
194             case with 30 bolts
195             Kiste mit 30 Bolzen
196             chain mail
197             Kettenhemd
198             charm person
199             Person bezaubern
200             club
201             Keule
202             crossbow
203             Armbrust
204             d6
205             W6
206             dagger
207             Dolch
208             detect magic
209             Magie entdecken
210             dwarf
211             Zwerg
212             elderly man
213             älterer Mann
214             elderly woman
215             ältere Frau
216             elf
217             Elf
218             fighter
219             Krieger
220             flask of oil
221             Ölflasche
222             floating disc
223             Schwebende Scheibe
224             halfling
225             Halbling
226             hand axe
227             Handaxt
228             helmet
229             Helm
230             hireling
231             Mietling
232             porter
233             Träger
234             hold portal
235             Portal verschliessen
236             12 iron spikes and hammer
237             12 Eisenkeile und Hammer
238             3 stakes and hammer
239             12 Holzpfähle und Hammer
240             lantern
241             Laterne
242             leather armor
243             Lederrüstung
244             light
245             Licht
246             long bow
247             Langbogen
248             long sword
249             Langschwert
250             mace
251             Streitkeule
252             magic missile
253             Magisches Geschoss
254             magic-user
255             Magier
256             man
257             Mann
258             mirror
259             Spiegel
260             plate mail
261             Plattenpanzer
262             pole arm
263             Stangenwaffe
264             pouch with 30 stones
265             Beutel mit 30 Steinen
266             protection from evil
267             Schutz vor Bösem
268             quiver with 20 arrows
269             Köcher mit 20 Pfeilen
270             read languages
271             Sprachen lesen
272             read magic
273             Magie lesen
274             rope
275             Seil
276             shield
277             Schild
278             short bow
279             Kurzbogen
280             short sword
281             Kurzschwert
282             silver dagger
283             Silberner Dolch
284             sleep
285             Schlaf
286             sling
287             Schleuder
288             spear
289             Speer
290             spell book
291             Zauberbuch
292             staff
293             Stab
294             thief
295             Dieb
296             thieves’ tools
297             Diebeswerkzeug
298             6 torches
299             6 Fackeln
300             two handed sword
301             Zweihänder
302             ventriloquism
303             Bauchreden
304             war hammer
305             Kriegshammer
306             wolfsbane
307             Eisenhut (sog. Wolfsbann)
308             garlic
309             Knoblauch
310             woman
311             Frau
312             pole
313             Stab
314             young man
315             junger Mann
316             young woman
317             junge Frau
318             rations (1 week)
319             Wegzehrung (1 Woche)
320             });
321              
322 10         84 return \%translations;
323             }
324              
325             my $translation = translations();
326             our $lang; # we'll set it in random_parameters
327              
328             sub T {
329 36382     36382 0 55252 my ($en, @arg) = @_;
330 36382         43355 my $suffix = '';
331             # handle (2) suffixes
332 36382 100       64498 if ($en =~ /(.*)( \(\d+\))$/) {
333 3         16 $en = $1;
334 3         8 $suffix = $2;
335             }
336 36382 100 100     95029 if ($translation->{$en} and $lang eq "de") {
337 17828         25866 $en = $translation->{$en};
338             }
339             # utf8::encode($en);
340 36382         64438 for (my $i = 0; $i < scalar @arg; $i++) {
341 271         425 my $s = $arg[$i];
342 271         2752 $en =~ s/%$i/$s/g;
343             }
344 36382         105356 return $en . $suffix;
345             }
346              
347             sub svg_read {
348 13     13 0 26 my ($char) = @_;
349 13   100     59 my $filename = $char->{charsheet} || 'Charactersheet.svg';
350 13         26 my $doc;
351 13 50       421 if (-f "$dist_dir/$filename") {
352 13         202 $doc = XML::LibXML->load_xml(location => "$dist_dir/$filename");
353             } else {
354 0         0 my $ua = Mojo::UserAgent->new;
355 0         0 my $tx = $ua->get($filename);
356 0 0       0 die "«$filename»: " . $tx->res->error->{message} . "\n" unless $tx->success;
357 0         0 $doc = XML::LibXML->load_xml(string => $tx->res->body);
358             }
359 13         25312 return ($char, $doc); # used as parameters for svg_transform
360             }
361              
362             sub replace_text {
363 438     438 0 795 my ($parser, $node, $str) = @_;
364 438         1274 my @line = split(/\\\\/, $str);
365              
366             # is this multiline in the template
367             # (ignore text nodes, go for tspans only)
368 438         603 my $dy;
369 438         1070 my $tspans = $node->find(qq{svg:tspan});
370 438 100       12991 if ($tspans->size() > 1) {
371 13         94 $dy = $tspans->get_node(2)->getAttribute("y")
372             - $tspans->get_node(1)->getAttribute("y");
373             } else {
374             # mismatch, attempt readable compromise
375 425         2538 @line = (join(", ", @line));
376             }
377              
378             # delete the tspan nodes of the text node
379 438         1985 $node->removeChildNodes();
380              
381 438         1346 my $tspan = XML::LibXML::Element->new("tspan");
382 438         1056 $tspan->setAttribute("x", $node->getAttribute("x"));
383 438         6510 $tspan->setAttribute("y", $node->getAttribute("y"));
384              
385 438         5328 while (@line) {
386 519         2337 my $line = shift(@line); # cannot have this in while cond because of "0"
387 519 100       1069 next if $line eq ''; # cannot parse empty strings
388 495         907 my $fragment = $parser->parse_balanced_chunk(T($line));
389 495         69105 foreach my $child ($fragment->childNodes) {
390 495         4649 my $tag = $child->nodeName;
391 495 50 33     2537 if ($tag eq "strong" or $tag eq "b") {
    50 33        
    50          
392 0         0 my $node = XML::LibXML::Element->new("tspan");
393 0         0 $node->setAttribute("style", "font-weight:bold");
394 0         0 $node->appendText($child->textContent);
395 0         0 $tspan->appendChild($node);
396             } elsif ($tag eq "em" or $tag eq "i") {
397 0         0 my $node = XML::LibXML::Element->new("tspan");
398 0         0 $node->setAttribute("style", "font-style:italic");
399 0         0 $node->appendText($child->textContent);
400 0         0 $tspan->appendChild($node);
401             } elsif ($tag eq "a") {
402 0         0 $child->setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href",
403             $child->getAttribute("href"));
404 0         0 $child->removeAttribute("href");
405 0         0 $tspan->appendChild($child);
406             } else {
407 495         2250 $tspan->appendText($child->textContent);
408             }
409             }
410 495         2731 $node->appendChild($tspan);
411 495 100       1128 if (@line) {
412 81         1168 $tspan = $tspan->cloneNode();
413 81         722 $tspan->setAttribute("y", $tspan->getAttribute("y") + $dy);
414             }
415             }
416             }
417              
418             sub svg_transform {
419 11     11 0 32 my ($self, $char, $doc) = @_;
420 11         41 my $parser = XML::LibXML->new;
421 11         350 my $svg = XML::LibXML::XPathContext->new;
422 11         79 $svg->registerNs("svg", "http://www.w3.org/2000/svg");
423              
424 11         135 for my $id (keys %$char) {
425 633 50       5284 next unless $id =~ /^[-a-z0-9]+$/;
426 633         2032 my $nodes = $svg->find(qq{//svg:text[\@id="$id"]}, $doc);
427 633         99391 for my $node ($nodes->get_nodelist) {
428 438         2511 replace_text($parser, $node, $char->{$id}, $doc);
429 438         12601 next;
430             }
431 633         2651 $nodes = $svg->find(qq{//svg:image[\@id="$id"]}, $doc);
432 633         50865 for my $node ($nodes->get_nodelist) {
433             $node->setAttributeNS("http://www.w3.org/1999/xlink",
434 5         50 "xlink:href", $char->{$id});
435 5         139 next;
436             }
437             }
438              
439             # $self is not set when using the random command
440             # (Game::CharacterSheetGenerator::Command::random).
441 11 50       142 if ($self) {
442 11         39 my $nodes = $svg->find(qq{//svg:a[\@id="link"]/attribute::xlink:href}, $doc);
443 11         842 for my $node ($nodes->get_nodelist) {
444 11         112 my $params = Mojo::Parameters->new;
445 11         174 for my $key (@{$char->{provided}}) {
  11         37  
446 155   100     2453 $params->append($key => $char->{$key}||'');
447             }
448 11         265 $node->setValue($self->url_for("edit")->query($params));
449             }
450             }
451              
452 11         18740 return $doc;
453             }
454              
455             sub svg_show_id {
456 2     2 0 5 my ($char, $doc) = @_;
457              
458 2         45 my $svg = XML::LibXML::XPathContext->new;
459 2         11 $svg->registerNs("svg", "http://www.w3.org/2000/svg");
460              
461 2         10 for my $node ($svg->find(qq{//svg:text/svg:tspan/..}, $doc)->get_nodelist) {
462 225         1636 my $id = $node->getAttribute("id");
463 225 100       1741 next if $id =~ /^text[0-9]+(-[0-9]+)*$/; # skip Inkscape default texts
464 100 50       280 next unless $id =~ /^[-a-z0-9]+$/;
465 100         317 $node->removeChildNodes();
466 100         251 $node->appendText($id);
467 100         158 my $style = $node->getAttribute("style");
468 100         1172 $style =~ s/font-size:\d+px/font-size:8px/;
469 100 50       513 $style =~ s/fill:#\d+/fill:magenta/ or $style .= ";fill:magenta";
470 100         208 $node->setAttribute("style", $style);
471             }
472              
473 2         18 for my $node ($svg->find(qq{//svg:image}, $doc)->get_nodelist) {
474 2         1401 my $id = $node->getAttribute("id");
475 2 50       23 next if $id =~ /^text[0-9]+(-[0-9]+)*$/; # skip Inkscape default texts
476 2 50       10 next unless $id =~ /^[-a-z0-9]+$/;
477 2         8 my $text = XML::LibXML::Element->new("text");
478 2         6 $text->setAttribute("x", $node->getAttribute("x") + 5);
479 2         64 $text->setAttribute("y", $node->getAttribute("y") + 10);
480 2         43 $text->appendText($id);
481 2         4 $text->setAttribute("style", "font-size:8px;fill:magenta");
482 2         27 $node->addSibling($text);
483             }
484              
485 2         36 return $doc;
486             }
487              
488             sub bonus {
489 680     680 0 951 my $n = shift;
490 680 100       1300 return "-3" if $n <= 3;
491 675 100       1278 return "-2" if $n <= 5;
492 653 100       1767 return "-1" if $n <= 8;
493 500 100       1916 return "" if $n <= 12;
494 180 100       742 return "+1" if $n <= 15;
495 23 100       101 return "+2" if $n <= 17;
496 3         23 return "+3";
497             }
498              
499             sub cha_bonus {
500 7     7 0 16 my $n = shift;
501 7 50       20 return "-2" if $n <= 3;
502 7 100       19 return "-1" if $n <= 8;
503 5 50       36 return "" if $n <= 12;
504 0 0       0 return "+1" if $n <= 17;
505 0         0 return "+2";
506             }
507              
508             sub character {
509 11     11 0 25 my $char = shift;
510 11         33 for my $id (qw(str dex con int wis cha)) {
511 66 100 100     225 if ($char->{$id} and not defined $char->{"$id-bonus"}) {
512 43         86 $char->{"$id-bonus"} = bonus($char->{$id});
513             }
514             }
515 11 100 66     68 if ($char->{cha} and not defined $char->{reaction}) {
516 7         25 $char->{reaction} = cha_bonus($char->{cha});
517             }
518 11 50       35 if (not $char->{loyalty}) {
519 11         46 $char->{loyalty} = 7 + $char->{"cha-bonus"};
520             }
521 11 50       43 if (not defined $char->{hirelings}) {
522 11         30 $char->{hirelings} = 4 + $char->{"cha-bonus"};
523             }
524 11 100 66     53 if ($char->{thac0} and not defined $char->{"melee-thac0"}) {
525 7         26 $char->{"melee-thac0"} = $char->{thac0} - $char->{"str-bonus"};
526             }
527 11 100 66     46 if ($char->{thac0} and not defined $char->{"range-thac0"}) {
528 7         28 $char->{"range-thac0"} = $char->{thac0} - $char->{"dex-bonus"};
529             }
530 11 100 66     54 if ($char->{thac0} and not defined $char->{"other-thac0"}) {
531 7         15 $char->{"other-thac0"} = $char->{thac0};
532             }
533 11         30 for my $type ("melee", "range", "other") {
534 33         80 for (my $n = 0; $n <= 9; $n++) {
535 330         495 my $val = $char->{"$type-thac0"} - $n;
536 330 100       501 $val = 20 if $val > 20;
537 330 100       466 $val = 1 if $val < 1;
538 330 50       956 $char->{"$type$n"} = $val unless $char->{"$type$n"};
539             }
540             }
541 11 50       49 if (not defined $char->{damage}) {
542 11         70 $char->{damage} = 1 . T('d6');
543             }
544 11 50       77 if (not defined $char->{"melee-damage"}) {
545 11         36 $char->{"melee-damage"} = $char->{damage} . $char->{"str-bonus"};
546             }
547 11 50       32 if (not defined $char->{"range-damage"}) {
548 11         27 $char->{"range-damage"} = $char->{damage};
549             }
550 11 50       29 if (not defined $char->{"other-damage"}) {
551 11         70 $char->{"other-damage"} = $char->{damage};
552             }
553 11         67 saves($char);
554             }
555              
556             # This function is called when preparing data for display in SVG.
557             sub compute_data {
558 11     11 0 61 my ($char, $language) = @_;
559 11         27 local $lang = $language; # make sure T works as intended
560 11         40 character($char);
561             }
562              
563             sub starting_gold {
564 304     304 0 488 my $class = shift;
565 304 50 33     581 return 0 if $class eq T('hireling') or $class eq T('porter');
566 304         640 return roll_3d6() * 10;
567             }
568              
569             my %price_cache;
570              
571             sub equipment {
572 305     305 0 479 my $char = shift;
573 305         551 my $xp = $char->{xp};
574 305         466 my $level = $char->{level};
575 305         552 my $class = $char->{class};
576 305 100 66     1471 return if $xp or $level > 1 or not $class;
      66        
577              
578 304         755 get_price_cache($char);
579 304         1229 my $money = starting_gold($class);
580 304         467 my @property;
581              
582             # free spellbook for arcane casters
583 304 100       623 if (member($class, T('magic-user'), T('elf'))) {
584 118         228 push(@property, T('spell book'));
585             }
586              
587 304         925 ($money, @property) = buy_basics($char, $money, $class, @property);
588 304         741 ($money, @property) = buy_armor($char, $money, $class, @property);
589 304         912 ($money, @property) = buy_weapon($char, $money, $class, @property);
590 304         760 ($money, @property) = buy_tools($char, $money, $class, @property);
591 304         724 ($money, @property) = buy_light($char, $money, $class, @property);
592 304         996 ($money, @property) = buy_gear($char, $money, $class, @property);
593 304         922 ($money, @property) = buy_protection($char, $money, $class, @property);
594 304         642 my $gold = int($money);
595 304         716 my $silver = int(10 * ($money - $gold) + 0.5);;
596 304 100       781 push(@property, T('%0 gold', $gold)) if $gold;
597 304 50       687 push(@property, T('%0 silver', $silver)) if $silver;
598 304         1453 provide($char, "property", join("\\\\", @property));
599             }
600              
601             # This is computed at runtime because of the translations.
602             sub get_price_cache {
603 304     304 0 570 my $char = shift;
604 304         654 %price_cache = (
605             T('backpack') => 5,
606             T('rations (1 week)') => 15,
607             T('thieves’ tools') => 25,
608             T('lantern') => 10,
609             T('flask of oil') => 2,
610             T('6 torches') => 1,
611             T('rope') => 1,
612             T('3 stakes and hammer') => 3,
613             T('12 iron spikes and hammer') => 3,
614             T('pole') => 1,
615             T('wolfsbane') => 10,
616             T('garlic') => 1,
617             T('mirror') => 5,
618             T('leather armor') => 20,
619             T('chain mail') => 40,
620             T('plate mail') => 60,
621             T('shield') => 10,
622             T('helmet') => 10,
623             T('club') => 3,
624             T('mace') => 5,
625             T('war hammer') => 5,
626             T('staff') => 2,
627             T('dagger') => 3,
628             T('silver dagger') => 30,
629             T('two handed sword') => 15,
630             T('battle axe') => 7,
631             T('pole arm') => 7,
632             T('long sword') => 10,
633             T('short sword') => 7,
634             T('long bow') => 40,
635             T('quiver with 20 arrows') => 5,
636             T('short bow') => 25,
637             T('crossbow') => 30,
638             T('case with 30 bolts') => 10,
639             T('sling') => 2,
640             T('pouch with 30 stones') => 0,
641             T('hand axe') => 4,
642             T('spear') => 3,
643             );
644             }
645              
646             sub price {
647 6283     6283 0 8966 my ($char, $item) = @_;
648 6283         9117 my $price = $price_cache{$item};
649 6283 50       9891 if (not defined $price) {
650 0         0 $log->error(T('Unknown Price'), T('%0: How much does this cost?', $item));
651 0         0 return 0;
652             }
653 6283         9284 return $price;
654             }
655              
656             # add($item, \@property) modifies @property directly
657             sub add {
658 3545     3545 0 5229 my ($item, $property) = @_;
659 3545         5176 foreach (@$property) {
660 19419 100       29835 if ($_ eq $item) {
661 271 50       669 if (/\(\d+\)$/) {
662 0         0 my $n = $1++;
663 0         0 s/\(\d+\)$/($n)/;
664             } else {
665 271         582 $_ .= " (2)";
666             }
667 271         398 $item = undef;
668 271         377 last;
669             }
670             }
671 3545 100       5661 if ($item) {
672 3274         5386 push(@$property, $item);
673             }
674             }
675              
676             # Use array references to buy one of several alternatives.
677             # Buy a and b, or buy c instead:
678             # ($money, @property) = buy($char, [[a, b], c], $money, @property)
679             sub buy {
680 3992     3992 0 8569 my ($char, $item, $money, @property) = @_;
681 3992 100       6976 if (ref $item eq "ARRAY") {
682 2008         3074 for my $elem (@$item) {
683 2773 100       4658 if (ref $elem eq "ARRAY") {
684 1276         1513 my $price = 0;
685 1276         1749 for my $thing (@$elem) {
686 2802         3702 $price += price($char, $thing);
687             }
688 1276 100       2313 if ($money >= $price) {
689 548         717 $money -= $price;
690 548 50       1038 $elem->[-1] .= " (${price}gp)" if $char->{debug};
691 548         983 foreach (@$elem) {
692 1108         1681 add($_, \@property);
693             }
694 548         784 last;
695             }
696             } else {
697 1497         2141 my $price = price($char, $elem);
698 1497 100       2674 if ($money >= $price) {
699 1141         1435 $money -= $price;
700 1141 50       2016 $elem .= " (${price}gp)" if $char->{debug};
701 1141         2425 add($elem, \@property);
702 1141         1753 last;
703             }
704             }
705             }
706             } else {
707 1984         2968 my $price = price($char, $item);
708 1984 100       3761 if ($money >= $price) {
709 1296         1636 $money -= $price;
710 1296 50       2089 $item .= " (${price}gp)" if $char->{debug};
711 1296         2291 add($item, \@property);
712             }
713             }
714 3992         13262 return ($money, @property);
715             }
716              
717             sub buy_basics {
718 304     304 0 774 my ($char, $money, $class, @property) = @_;
719 304 50       708 push(@property, "- $money gp -") if $char->{debug};
720 304         595 ($money, @property) = buy($char, T('backpack'), $money, @property);
721 304         745 ($money, @property) = buy($char, T('rations (1 week)'), $money, @property);
722              
723 304         768 return ($money, @property);
724             }
725              
726             sub buy_tools {
727 304     304 0 797 my ($char, $money, $class, @property) = @_;
728 304 50       672 push(@property, "- $money gp -") if $char->{debug};
729 304 100       573 if ($class eq T('thief')) {
730 48         129 ($money, @property) = buy($char, T('thieves’ tools'), $money, @property);
731             }
732 304         1010 return ($money, @property);
733             }
734              
735             sub buy_light {
736 304     304 0 812 my ($char, $money, $class, @property) = @_;
737 304 50       677 push(@property, "- $money gp -") if $char->{debug};
738 304         636 return buy($char, [[T('lantern'), T('flask of oil')],
739             T('6 torches')],
740             $money, @property);
741             }
742              
743             sub buy_gear {
744 304     304 0 926 my ($char, $money, $class, @property) = @_;
745 304 50       747 push(@property, "- $money gp -") if $char->{debug};
746 304         602 my @preferences = shuffle(
747             T('rope'),
748             T('12 iron spikes and hammer'),
749             T('3 stakes and hammer'),
750             T('pole'));
751 304         760 return buy($char, \@preferences, $money, @property);
752             }
753              
754             sub buy_protection {
755 304     304 0 864 my ($char, $money, $class, @property) = @_;
756 304 50       664 push(@property, "- $money gp -") if $char->{debug};
757 304         567 my @preferences = shuffle(
758             T('garlic'),
759             T('wolfsbane'),
760             T('mirror'));
761 304         701 return buy($char, \@preferences, $money, @property);
762             }
763              
764             sub buy_armor {
765 304     304 0 743 my ($char, $money, $class, @property) = @_;
766 304 50       639 push(@property, "- $money gp -") if $char->{debug};
767 304         553 my $budget = $money / 2;
768 304         445 $money -= $budget;
769              
770 304 100       523 if ($class eq T('magic-user')) {
    100          
771             # no armor
772             } elsif ($class eq T('thief')) {
773             # leather, no shield, no helmet
774 48         122 ($budget, @property) = buy($char, T('leather armor'), $budget, @property);
775             } else {
776             # any armor
777 184         402 ($budget, @property) = buy($char, [T('plate mail'),
778             T('chain mail'),
779             T('leather armor')], $budget, @property);
780 184         532 ($budget, @property) = buy($char, T('shield'), $budget, @property);
781 184         436 ($budget, @property) = buy($char, T('helmet'), $budget, @property);
782             }
783              
784             # compute AC
785 304         610 my $dex = $char->{dex};
786 304         697 my $ac = 9 - bonus($dex);
787              
788 304 100       639 if (member(T('plate mail'), @property)) { $ac -= 6; }
  32 100       65  
    100          
789 88         150 elsif (member(T('chain mail'), @property)) { $ac -= 4; }
790 103         177 elsif (member(T('leather armor'), @property)) { $ac -= 2; }
791              
792 304 100       693 if (member(T('shield'), @property)) { $ac -= 1; }
  90         151  
793              
794 304 100       676 if ($class eq T('halfling')) {
795 26         73 $ac .= "/" . ($ac - 2);
796             }
797 304         777 provide($char, "ac", $ac);
798              
799 304         951 return ($money + $budget, @property);
800             }
801              
802             sub buy_melee_weapon {
803 304     304 0 455 my $char = shift;
804 304         672 my ($money, $class, @property) = @_;
805 304         563 my $str = $char->{str};
806 304         538 my $hp = $char->{hp};
807 304         608 my $shield = member(T('shield'), @property);
808 304         493 my @preferences;
809              
810 304 100       564 if ($class eq T('magic-user')) {
    100          
    100          
    100          
    100          
    50          
    0          
    0          
811 72         181 @preferences = shuffle(
812             T('dagger'),
813             T('staff'));
814             } elsif ($class eq T('fighter')) {
815 58 50 100     135 if (good($str)
      66        
816             and $hp > 6
817             and not $shield) {
818             # prefer a shield!
819 0         0 push(@preferences,
820             shuffle(T('two handed sword'),
821             T('battle axe'),
822             T('pole arm')));
823             }
824 58         141 push(@preferences,
825             T('long sword'),
826             T('short sword'),
827             T('mace'));
828             } elsif ($class eq T('dwarf')) {
829 54 100       176 push(@preferences, T('battle axe')) unless $shield;
830 54         111 push(@preferences,
831             T('war hammer'),
832             T('mace'),
833             T('short sword'));
834             } elsif ($class eq T('halfling')) {
835 26         55 @preferences = (T('short sword'),
836             T('mace'),
837             T('club'));
838             } elsif ($class eq T('elf')) {
839 46         102 @preferences = (T('long sword'),
840             T('short sword'));
841             } elsif ($class eq T('thief')) {
842 48         109 @preferences = (T('long sword'),
843             T('short sword'),
844             T('mace'),
845             T('club'));
846             } elsif ($class eq T('hireling')) {
847 0         0 @preferences = (T('spear'),
848             T('club'));
849             } elsif ($class eq T('porter')) {
850 0         0 @preferences = ();
851             } else {
852 0         0 $log->warn("Unknown class $class has no preferred weapons");
853             }
854 304         860 return buy($char, \@preferences, $money, @property);
855             }
856              
857             sub buy_throwing_weapon {
858 304     304 0 469 my $char = shift;
859 304         747 my ($money, $class, @property) = @_;
860 304         433 my @preferences;
861 304 100 66     629 if ($class eq T('dwarf') or member(T('battle axe'), @property)) {
862 54         149 push(@preferences, [T('hand axe'), T('hand axe')]);
863 54         118 push(@preferences, T('hand axe'));
864             }
865 304 100       740 if ($class eq T('fighter')) {
866 58         120 push(@preferences, T('spear'));
867             }
868 304         708 return buy($char, \@preferences, $money, @property);
869             }
870              
871             sub buy_ranged_weapon {
872 304     304 0 506 my $char = shift;
873 304         722 my ($money, $class, @property) = @_;
874 304         391 my @preferences;
875 304         490 my $dex = $char->{dex};
876 304 100 100     659 if (($class eq T('fighter') or $class eq T('elf'))
      100        
877             and average($dex)) {
878 75         164 push(@preferences,
879             [T('long bow'),
880             T('quiver with 20 arrows'),
881             T('quiver with 20 arrows')],
882             [T('long bow'),
883             T('quiver with 20 arrows')]);
884             }
885 304 100       626 if ($class ne T('magic-user')) {
886 232 100       425 if (average($dex)) {
887 175         387 push(@preferences,
888             [T('short bow'),
889             T('quiver with 20 arrows'),
890             T('quiver with 20 arrows')],
891             [T('short bow'),
892             T('quiver with 20 arrows')]);
893             }
894 232         531 push(@preferences,
895             [T('crossbow'),
896             T('case with 30 bolts')]);
897 232         514 push(@preferences,
898             [T('sling'),
899             T('pouch with 30 stones')]);
900             }
901 304         706 return buy($char, \@preferences, $money, @property);
902             }
903              
904             sub buy_weapon {
905 304     304 0 477 my $char = shift;
906 304         774 my ($money, $class, @property) = @_;
907 304 50       656 push(@property, "- $money gp -") if $char->{debug};
908 304         607 my $budget = $money / 2;
909 304         479 $money -= $budget;
910              
911 304         772 ($budget, @property) = buy_melee_weapon($char, $budget, $class, @property);
912 304         902 ($budget, @property) = buy_throwing_weapon($char, $budget, $class, @property);
913 304         747 ($budget, @property) = buy_ranged_weapon($char, $budget, $class, @property);
914              
915 304         896 ($budget, @property) = buy($char, T('silver dagger'), $budget, @property);
916              
917 304         695 ($budget, @property) = buy($char, T('dagger'), $budget, @property);
918 304         637 ($budget, @property) = buy($char, T('dagger'), $budget, @property);
919              
920 304         1031 return ($money + $budget, @property);
921             }
922              
923             sub spellbook {
924 118     118 0 214 my $char = shift;
925 118         203 return T('Spells:') . " "
926             . one(T('charm person'),
927             T('detect magic'),
928             T('floating disc'),
929             T('hold portal'),
930             T('light'),
931             T('magic missile'),
932             T('protection from evil'),
933             T('read languages'),
934             T('read magic'),
935             T('shield'),
936             T('sleep'),
937             T('ventriloquism'));
938             }
939              
940             sub saves {
941 11     11 0 21 my $char = shift;
942 11         29 my $class = $char->{class};
943 11         27 my $level = $char->{level};
944 11 100       60 return unless $class;
945 7         15 my ($breath, $poison, $petrify, $wands, $spells);
946 7 50 33     15 if ($class eq T('dwarf') or $class eq T('halfling')) {
    100          
    100          
    50          
    50          
947 0         0 ($breath, $poison, $petrify, $wands, $spells) =
948             improve([13, 8, 10, 9, 12], [3, 2, 2, 2, 2], int(($level-1)/3));
949             } elsif ($class eq T('elf')) {
950 2         81 ($breath, $poison, $petrify, $wands, $spells) =
951             improve([15, 12, 13, 13, 15], 2, int(($level-1)/3));
952             } elsif ($class eq T('fighter')) {
953 3         21 ($breath, $poison, $petrify, $wands, $spells) =
954             improve([15, 12, 14, 13, 16], 2, int(($level-1)/3));
955             } elsif ($class eq T('magic-user')) {
956 0         0 ($breath, $poison, $petrify, $wands, $spells) =
957             improve([16, 13, 13, 13, 14], 2, int(($level-1)/5));
958             } elsif ($class eq T('thief')) {
959 2         12 ($breath, $poison, $petrify, $wands, $spells) =
960             improve([16, 14, 13, 15, 14], 2, int(($level-1)/4));
961             } else {
962 0         0 ($breath, $poison, $petrify, $wands, $spells) =
963             (17, 14, 16, 15, 18);
964             }
965              
966 7 100       41 provide($char, "breath", $breath) unless $char->{breath};
967 7 100       32 provide($char, "poison", $poison) unless $char->{poison};
968 7 100       32 provide($char, "petrify", $petrify) unless $char->{petrify};
969 7 100       30 provide($char, "wands", $wands) unless $char->{wands};
970 7 100       45 provide($char, "spells", $spells) unless $char->{spells};
971             }
972              
973             sub improve {
974 7     7 0 37 my $saves = shift;
975 7         12 my $improvement = shift;
976 7         13 my $steps = shift;
977 7         34 for (my $i = 0; $i < @$saves; $i++) {
978 35 0       80 $saves->[$i] -= ref($improvement) ? $improvement->[$i] : $improvement for 1 .. $steps;
979             }
980 7         26 return @$saves;
981             }
982              
983             sub d3 {
984 100     100 0 214 return 1 + int(rand(3));
985             }
986              
987             sub d4 {
988 123     123 0 402 return 1 + int(rand(4));
989             }
990              
991             sub d6 {
992 6596     6596 0 11269 return 1 + int(rand(6));
993             }
994              
995             sub d8 {
996 112     112 0 365 return 1 + int(rand(8));
997             }
998              
999             sub d10 {
1000 0     0 0 0 return 1 + int(rand(10));
1001             }
1002              
1003             sub d12 {
1004 0     0 0 0 return 1 + int(rand(12));
1005             }
1006              
1007             sub roll_3d6 {
1008 2134     2134 0 3232 return d6() + d6() + d6();
1009             }
1010              
1011             sub roll_3d8 {
1012 0     0 0 0 return d8() + d8() + d8();
1013             }
1014              
1015             sub best {
1016 305     305 0 442 my $best = 0;
1017 305         476 my $max = $_[0];
1018 305         753 for (my $i = 1; $i < 6; $i++) {
1019 1525 100       3026 if ($_[$i] > $max) {
1020 340         518 $best = $i;
1021 340         619 $max = $_[$best];
1022             }
1023             }
1024 305         859 my @stat = qw(str dex con int wis cha);
1025 305         687 return $stat[$best];
1026             }
1027              
1028             sub above {
1029 1726     1726 0 2307 my $limit = shift;
1030 1726         2029 my $n = 0;
1031 1726         3033 for (my $i = 0; $i <= $#_; $i++) {
1032 3107 100       6883 $n++ if $_[$i] > $limit;
1033             }
1034 1726         6223 return $n;
1035             }
1036              
1037             sub good {
1038 462     462 0 849 return above(12, @_);
1039             }
1040              
1041             sub average {
1042 1264     1264 0 2084 return above(8, @_);
1043             }
1044              
1045             # use prototype so that Perl knows that there are only three arguments, which
1046             # allows wrap to use wantarray when used to wrap $value
1047             sub provide ($$$) {
1048 4932     4932 0 8010 my ($char, $key, $value) = @_;
1049 4932 100 66     9790 return unless not defined $char->{$key} or $char->{$key} eq '';
1050             # empty strings get overwritten, but zero does not get overwritten
1051 4928         5986 push(@{$char->{provided}}, $key);
  4928         9946  
1052 4928         10661 $char->{$key} = $value;
1053             }
1054              
1055             sub one {
1056 793     793 0 2255 my $i = int(rand(scalar @_));
1057 793         5055 return $_[$i];
1058             }
1059              
1060             sub two {
1061 0     0 0 0 my $i = int(rand(scalar @_));
1062 0         0 my $j = int(rand(scalar @_));
1063 0         0 $j = int(rand(scalar @_)) until $i != $j;
1064 0         0 return ($_[$i], $_[$j]);
1065             }
1066              
1067             sub member {
1068 2227     2227 0 3248 my $element = shift;
1069 2227         3545 foreach (@_) {
1070 6809 100       14079 return 1 if $element eq $_;
1071             }
1072             }
1073              
1074             sub wrap {
1075 0     0 0 0 my ($text, $width) = @_;
1076 0         0 my @result;
1077 0         0 while (length($text) > $width) {
1078 0         0 my $n = $width;
1079 0         0 while ($n > 0) {
1080 0 0       0 if (substr($text, $n, 1) eq " ") {
1081 0         0 push(@result, substr($text, 0, $n));
1082 0         0 $text = substr($text, $n + 1);
1083 0         0 last;
1084             } else {
1085 0         0 $n--;
1086             }
1087             }
1088             }
1089 0         0 push(@result, $text);
1090 0 0       0 return @result if wantarray;
1091 0         0 return join("\\\\", @result);
1092             }
1093              
1094             # http://www.stadt-zuerich.ch/content/prd/de/index/statistik/publikationsdatenbank/Vornamen-Verzeichnis/VVZ_2012.html
1095              
1096             my @names = qw{Aadhya F Aaliyah F Aanya F Aarna F Aarusha F Abiha F Abira F
1097             Abisana F Abishana F Abisheya F Ada F Adalia F Adelheid F Adelia F Adina F Adira
1098             F Adisa F Adisha F Adriana F Adriane F Adrijana F Aela F Afriela F Agata F
1099             Agatha F Aicha F Aikiko F Aiko F Aila F Ainara F Aischa F Aisha F Aissatou F
1100             Aiyana F Aiza F Aji F Ajshe F Akksaraa F Aksha F Akshaya F Alaa F Alaya F Alea F
1101             Aleeya F Alegria F Aleksandra F Alena F Alessandra F Alessia F Alexa F Alexandra
1102             F Aleyda F Aleyna F Alia F Alice F Alicia F Aliena F Alienor F Aliénor F Alija F
1103             Alina F Aline F Alisa F Alisha F Alissa F Alissia F Alix F Aliya F Aliyana F
1104             Aliza F Alizée F Allegra F Allizza F Alma F Almira F Alva F Alva-Maria F Alya F
1105             Alysha F Alyssa F Amalia F Amalya F Amanda F Amara F Amaris F Amber F Ambra F
1106             Amea F Amelia F Amelie F Amélie F Amina F Amira F Amor F Amora F Amra F Amy F
1107             Amy-Lou ? Ana F Anaahithaa F Anabell F Anabella F Anaëlle F Anaïs F Ananya F
1108             Anastasia F Anastasija F Anastazia F Anaya F Andeline F Andjela F Andrea F
1109             Anduena F Anela F Anesa F Angel ? Angela F Angelina F Angeline F Anik ? Anika F
1110             Anila F Anisa F Anise F Anisha F Anja F Ann F Anna F Anna-Malee F Annabel F
1111             Annabelle F Annalena F Anne F Anne-Sophie F Annica F Annicka F Annigna F Annik F
1112             Annika F Anouk F Antonet F Antonia F Antonina F Anusha F Aralyn F Ariane F
1113             Arianna F Ariel ? Ariela F Arina F Arisa F Arishmi F Arlinda F Arsema F Arwana F
1114             Arwen F Arya F Ashley F Ashmi F Asmin F Astrid F Asya F Athena F Aubrey F Audrey
1115             F Aurelia F Aurélie F Aurora F Ava F Avery F Avy F Aya F Ayana F Ayla F Ayleen F
1116             Aylin F Ayse F Azahel F Azra F Barfin F Batoul F Batya F Beatrice F Belén F
1117             Bella F Bente F Beril F Betel F Betelehim F Bleona F Bracha F Briana F Bronwyn F
1118             Bruchi F Bruna F Büsra F Caelynn F Caitlin F Caja F Callista F Camille F Cao F
1119             Carice F Carina F Carla F Carlotta F Carolina F Caroline F Cassandra F Castille
1120             F Cataleya F Caterina F Catherine F Céleste F Celia F Celina F Celine F Ceylin F
1121             Chana F Chanel F Chantal F Charielle F Charleen F Charlie ? Charlize F Charlott
1122             F Charlotte F Charly F Chavi F Chaya F Chiara F Chiara-Maé F Chinyere F Chléa F
1123             Chloe F Chloé F Chrisbely F Christiana F Christina F Ciara F Cilgia F Claire F
1124             Clara F Claudia F Clea F Cleo F Cleofe F Clodagh F Cloé F Coco F Colette F Coral
1125             F Coralie F Cyrielle F Daliah F Dalila F Dalilah F Dalina F Damiana F Damla F
1126             Dana F Daniela F Daria F Darija F Dean ? Deborah F Déborah-Isabel F Defne F
1127             Delaila F Delia F Delina F Derya F Deshira F Deva F Diana F Diara F Diarra F
1128             Diesa F Dilara F Dina F Dinora F Djurdjina F Dominique F Donatella F Dora F
1129             Dorina F Dunja F Eda F Edessa F Edith F Edna F Eduina F Eidi F Eileen F Ela F
1130             Elanur F Elda F Eldana F Elea F Eleanor F Elena F Eleni F Elenor F Eleonor F
1131             Eleonora F Elhana F Eliana F Elidiana F Eliel F Elif F Elin F Elina F Eline F
1132             Elinor F Elisa F Elisabeth F Elise F Eliska F Eliza F Ella F Ellen F Elliana F
1133             Elly F Elma F Elodie F Elona F Elora F Elsa F Elva F Elyssa F Emelie F Emi F
1134             Emilia F Emiliana F Emilie F Émilie F Emilija F Emily F Emma F Enis F Enna F
1135             Enrica F Enya F Erdina F Erika F Erin F Erina F Erisa F Erna F Erona F Erva F
1136             Esma F Esmeralda F Estée F Esteline F Estelle F Ester F Esther F Eteri F
1137             Euphrasie F Eva F Eve F Evelin F Eviana F Evita F Ewa F Eya F Fabia F Fabienne F
1138             Fatima F Fatma F Fay F Faye F Fe F Fedora F Felia F Felizitas F Fiamma F Filipa
1139             F Filippa F Filomena F Fina F Finja F Fiona F Fjolla F Flaminia F Flavia F Flor
1140             F Flora F Florence F Florina F Florita F Flurina F Franca F Francesca F
1141             Francisca F Franziska F Freija F Freya F Freyja F Frida F Gabriela F Gabrielle F
1142             Gaia F Ganiesha F Gaon F Gavisgaa F Gemma F Georgina F Ghazia F Gia F Giada F
1143             Gianna F Gila F Gioanna F Gioia F Giorgia F Gitty F Giulia F Greta F Grete F
1144             Gwenaelle F Gwendolin F Gyane F Hadidscha F Hadzera F Hana F Hanar F Hania F
1145             Hanna F Hannah F Hanni F Hédi ? Heidi F Helen F Helena F Helene F Helia F
1146             Heloise F Héloïse F Helya F Henna F Henrietta F Heran F Hermela F Hiba F Hinata
1147             F Hiteshree F Hodman F Honey F Iara F Ibtihal F Ida F Idil F Ilaria F Ileenia F
1148             Ilenia F Iman F Ina F Indira F Ines F Inés F Inez F Ira F Irene F Iria F Irina F
1149             Iris F Isabel F Isabela F Isabell F Isabella F Isabelle F Isra F Iva F Jada F
1150             Jael F Jaël F Jaelle F Jaelynn F Jalina F Jamaya F Jana F Jane F Jannatul F Jara
1151             F Jasmijn F Jasmin F Jasmina F Jayda F Jeanne F Jelisaveta F Jemina F Jenna F
1152             Jennifer F Jerishka F Jessica F Jesuela F Jil F Joan ? Joana F Joanna F Johanna
1153             F Jola F Joleen F Jolie F Jonna F Joseline F Josepha F Josephine F Joséphine F
1154             Joudia F Jovana F Joy F Judith F Jule F Juli F Julia F Julie F Juliette F Julija
1155             F Jully F Juna F Juno F Justine F Kahina F Kaja F Kalina F Kalista F Kapua F
1156             Karina F Karla F Karnika F Karolina F Kashfia F Kassiopeia F Kate F Katerina F
1157             Katharina F Kaya F Kayla F Kayley F Kayra F Kehla F Keira F Keren-Happuch F
1158             Keziah F Khadra F Khardiata F Kiana F Kiara F Kim F Kinda F Kira F Klara F Klea
1159             F Kostana F Kristina F Kristrún F Ksenija F Kugagini F Kyra F Ladina F Laetitia
1160             F Laila F Laís F Lakshmi F Lana F Lani F Lara F Laraina F Larina F Larissa F
1161             Laura F Laurelle F Lauri ? Laurianne F Lauryn F Lavin F Lavinia F Laya F Layana
1162             F Layla F Lea F Léa F Leah F Leana F Leandra F Leanne F Leia F Leilani-Yara F
1163             Lejla F Lelia F Lena F Leni F Lenia F Lenie F Lenja F Lenka F Lennie ? Leona F
1164             Leoni F Leonie F Léonie F Leonor F Leticia F Leya F Leyla F Leyre F Lia F Liana
1165             F Liane F Liann F Lianne F Liara F Liayana F Liba F Lidia F Lidija F Lijana F
1166             Lila-Marie F Lili F Lilia F Lilian F Liliane F Lilijana F Lilith F Lilja F Lilla
1167             F Lilli F Lilly F Lilly-Rose F Lilo F Lily F Lin F Lina F Linda F Line F Linn F
1168             Lioba F Liora F Lisa F Lisandra F Liselotte F Liv F Liva F Livia F Liz F Loa F
1169             Loe F Lokwa F Lola F Lorea F Loreen F Lorena F Loriana F Lorina F Lorisa F Lotta
1170             F Louanne F Louisa F Louise F Lovina F Lua F Luana F Luanda F Lucia F Luciana F
1171             Lucie F Lucy F Luisa F Luise F Lux ? Luzia F Lya F Lyna F Lynn F Lynna F Maëlle
1172             F Maelyn F Maëlys F Maeva F Magali F Magalie F Magdalena F Mahsa F Maira F
1173             Maisun F Maja F Maka F Malaeka F Malaika F Malea F Maléa F Malia F Malin F
1174             Malkif F Malky F Maltina F Malu F Manar F Manha F Manisha F Mara F Maram F Mare
1175             F Mareen F Maren F Margarida F Margherita F Margo F Margot F Maria F Mariangely
1176             F Maribel F Marie F Marie-Alice F Marietta F Marija F Marika F Mariko F Marina F
1177             Marisa F Marisol F Marissa F Marla F Marlen F Marlene F Marlène F Marlin F Marta
1178             F Martina F Martje F Mary F Maryam F Mascha F Mathilda F Matilda F Matilde F
1179             Mauadda F Maxine F Maya F Mayas F Mayla F Maylin F Mayra F Mayumi F Medea F
1180             Medina F Meena F Mehjabeen F Mehnaz F Meila F Melanie F Mélanie F Melek F Melian
1181             F Melike F Melina F Melisa F Melissa F Mélissa F Melka F Melyssa F Mena F Meret
1182             F Meri F Merry F Meryem F Meta F Mia F Mía F Michal F Michelle F Mihaela F Mila
1183             F Milania F Milena F Milica F Milja F Milla F Milou F Mina F Mingke F Minna F
1184             Minu F Mira F Miray F Mirdie F Miriam F Mirjam F Mirta F Miya F Miyu F Moa F
1185             Moena F Momo F Momoco F Mona F Morea F Mubera F Muriel F Mylène F Myriam F N'Dea
1186             F Nabihaumama F Nadija F Nadin F Nadja F Nael ? Naemi F Naila F Naïma F Naina F
1187             Naliya F Nandi F Naomi F Nara F Naraya F Nardos F Nastasija F Natalia F Natalina
1188             F Natania F Natascha F Nathalie F Nava F Navida F Navina F Nayara F Nea F Neda F
1189             Neea F Nejla F Nela F Nepheli F Nera F Nerea F Nerine F Nesma F Nesrine F Neva F
1190             Nevia F Nevya F Nico ? Nicole F Nika F Nikita F Nikolija F Nikolina F Nina F
1191             Nine F Nirya F Nisa F Nisha F Nives F Noa ? Noé ? Noë F Noée F Noelia F Noemi F
1192             Noémie F Nola F Nora F Nordon F Norea F Norin F Norina F Norlha F Nour F Nova F
1193             Nóva F Nubia F Nuo F Nura F Nurah F Nuray F Nuria F Nuriyah F Nusayba F Oceane F
1194             Oda F Olive F Olivia F Olsa F Oluwashayo F Ornela F Ovia F Pamela-Anna F Paola F
1195             Pattraporn F Paula F Paulina F Pauline F Penelope F Pepa F Perla F Pia F Pina F
1196             Rabia F Rachel F Rahel F Rahela F Raïssa F Raizel F Rajana F Rana F Ranim F
1197             Raphaela F Raquel F Rayan ? Rejhana F Rejin F Réka F Renata F Rhea F
1198             Rhynisha-Anna F Ria F Riga F Rijona F Rina F Rita F Rivka F Riya F Roberta F
1199             Robin ? Robyn F Rohzerin F Róisín F Romina F Romy F Ronja F Ronya F Rosa F Rose
1200             F Rosina F Roxane F Royelle F Rozen F Rubaba F Rubina F Ruby F Rufina F Rukaye F
1201             Rumi ? Rym F Saanvika F Sabrina F Sadia F Safiya F Sahira F Sahra F Sajal F
1202             Salma F Salome F Salomé F Samantha F Samina F Samira F Samira-Aliyah F
1203             Samira-Mukaddes F Samruddhi F Sania F Sanna F Sara F Sarah F Sarahi F Saraia F
1204             Saranda F Saray F Sari F Sarina F Sasha F Saskia F Savka F Saya F Sayema F
1205             Scilla F Sejla F Selene F Selihom F Selina F Selma F Semanur F Sena F Sephora F
1206             Serafima F Serafina F Serafine F Seraina F Seraphina F Seraphine F Serena F
1207             Serra F Setareh F Shan F Shanar F Shathviha F Shayenna F Shayna F Sheindel F
1208             Shireen F Shirin F Shiyara F Shreshtha F Sia F Sidona F Siena F Sienna F Siiri F
1209             Sila F Silja F Silvanie-Alison F Silvia F Simea F Simi F Simona F Sina F Sira F
1210             Sirajum F Siri F Sirija F Sivana F Smilla F Sofia F Sofia-Margarita F Sofie F
1211             Sofija F Solea F Soleil F Solène F Solenn F Sonia F Sophia F Sophie F Sora F
1212             Soraya F Sosin F Sriya F Stella F Stina F Su F Subah F Suela F Suhaila F Suleqa
1213             F Sumire F Summer F Syria F Syrina F Tabea F Talina F Tamara F Tamasha F Tamina
1214             F Tamiya F Tara F Tatjana F Tayla F Tayscha F Tea F Tenzin ? Teodora F Tessa F
1215             Tharusha F Thea F Theniya F Tiana F Tijana F Tilda F Timea F Timeja F Tina F
1216             Tomma F Tonia F Tsiajara F Tuana F Tyra F Tzi ? Uendi F Uma F Urassaya F Vailea
1217             F Valentina F Valentine F Valeria F Valerie F Vanessa F Vanja F Varshana F Vella
1218             F Vera F Victoria F Viktoria F Vinda F Viola F Vivianne F Vivien F Vivienne F
1219             Wanda F Wayane F Wilma F Xin F Xingchen F Yael F Yaël F Yamina F Yang F Yara F
1220             Yasmine F Yeilin F Yen F Yersalm F Yesenia F Yeva F Yi F Yildiz-Kiymet F Ying ?
1221             Yixin F Ylvi F Yocheved F Yoko F Yosan F Yosmely F Yuen F Yuhan F Yuna F Yvaine
1222             F Zahraa F Zaina F Zazie F Zeinab F Zelda F Zeliha F Zenan F Zerya F Zeta F
1223             Zeyna F Zeynep F Ziporah F Zivia F Zoe F Zoé F Zoë F Zoë-Sanaa F Zoey F Zohar F
1224             Zoi F Zuri F Aadil M Aaron M Abdimaalik M Abdirahman M Abdul M Abdullah M Abi M
1225             Abraham M Abrar M Abubakar M Achmed M Adam M Adan M Adesh M Adhrit M Adil M
1226             Adiyan M Adrian M Adriano M Adrien M Adrijan M Adthish M Advay M Advik M Aeneas
1227             M Afonso M Agustín M Ahammed M Ahnaf M Ahron M Aiden M Ailo M Aimo M Ajan M
1228             Ajdin M Ajish M Akil M Akilar M Akira M Akito M Aksayan M Alan M Aldin M Aldion
1229             M Alec M Alejandro M Aleksa M Aleksandar M Aleksander M Aleksandr M Alem M
1230             Alessandro M Alessio M Alex M Alexander M Alexandre M Alexandru M Alexey M
1231             Alexis M Alfred M Ali M Allison M Almir M Alois M Altin M Aly M Amael M Aman M
1232             Amar M Amaury M Amedeo M Ami M Amil M Amin M Amir M Amirhan M Amirthesh M Ammar
1233             M Amogh M Anaël M Anakin M Anas M Anatol M Anatole M Anay M Anayo M Andi M
1234             Andreas M Andrej M Andrés M Andrey M Andri M Andrin M Andriy M Andy M Aneesh M
1235             Anes M Angelo M Anoush M Anqi M Antoine M Anton M Antonio M António M Anua M
1236             Anush M Arab M Arafat M Aramis M Aras M Arbion M Arda M Ardit M Arham M Arian M
1237             Arianit M Arijon M Arin M Aris M Aritra M Ariya M Arlind M Arman M Armin M
1238             Arnaud M Arne M Arno M Aron M Arsène M Art M Artemij M Arthur M Arturo M Arvid M
1239             Arvin M Aryan M Arye M Aswad M Atharv M Attila M Attis M Aulon M Aurel M Aurelio
1240             M Austin M Avinash M Avrohom M Axel M Ayan M Ayano M Ayham M Ayman M Aymar M
1241             Aymon M Azaan M Azad M Azad-Can M Bailey M Balthazar M Barnaba M Barnabas M
1242             Basil M Basilio M Bátor M Beda M Bela M Ben M Benart M Benjamin M Bennet M Benno
1243             M Berend M Berktan M Bertal M Besir M Bilal M Bilgehan M Birk M Bjarne M Bleart
1244             M Blend M Blendi M Bo M Bogdan M Bolaji M Bora M Boris M Brady M Brandon M
1245             Breyling M Brice M Bruce M Bruno M Bryan M Butrint M Caleb M Camil M Can M Cário
1246             M Carl M Carlo M Carlos M Carmelo M Cas M Caspar M Cedric M Cédric M Célestin M
1247             Celestino M Cemil-Lee M César M Chaim M Chandor M Charles M Chilo M Chris M
1248             Christian M Christopher M Christos M Ciaran M Cillian M Cla M Claudio M Colin M
1249             Collin M Connor M Conrad M Constantin M Corey M Cosmo M Cristian M Curdin M
1250             Custavo M Cynphael M Cyprian M Cyrill M Daan M Dagemawi M Daha M Dalmar M Damian
1251             M Damián M Damien M Damjan M Daniel M Daniele M Danilo M Danny M Dareios M Darel
1252             M Darian M Dario M Daris M Darius M Darwin M Davi M David M Dávid M Davide M
1253             Davin M Davud M Denis M Deniz M Deon M Devan M Devin M Diago M Dian M Diar M
1254             Diego M Dilom M Dimitri M Dino M Dion M Dionix M Dior M Dishan M Diyari M Djamal
1255             M Djamilo M Domenico M Dominic M Dominik M Donart M Dorian M Dries M Drisar M
1256             Driton M Duart M Duarte M Durgut M Durim M Dylan M Ebu M Ebubeker M Edgar M Edi
1257             M Edon M Édouard M Edrian M Edward M Edwin M Efehan M Efraim M Ehimay M Einar M
1258             Ekrem M Eldi M Eldian M Elia M Eliah M Elias M Elija M Elijah M Elio M Eliot M
1259             Elliot M Elouan M Élouan M Eloy M Elvir M Emanuel M Emil M Emilio M Emin M Emir
1260             M Emmanuel M Endrit M Enea M Enes M Engin M Engjëll M Ennio M Enrico M Enrique M
1261             Ensar M Enzo M Erblin M Erd M Eren M Ergin M Eric M Erik M Erind M Erion M Eris
1262             M Ernest-Auguste M Erol M Eron M Ersin M Ervin M Erwin M Essey M Ethan M Etienne
1263             M Evan M Ewan M Eymen M Ezio M Fabian M Fabiàn M Fabio M Fabrice M Fadri M Faris
1264             M Faruk M Federico M Félicien M Felipe M Felix M Ferdinand M Fernando M Filip M
1265             Filipe M Finlay M Finn M Fionn M Firat M Fitz-Patrick M Flavio M Flori M Florian
1266             M Florin M Flurin M Flynn M Francesco M Frederic M Frederick M Frederik M Frédo
1267             M Fridtjof M Fritz M Furkan M Fynn M Gabriel M Gabriele M Gael M Galin M Gaspar
1268             M Gaspard M Gavin M Geeth M Genc M Georg M Gerald M Geronimo M Getoar M Gian M
1269             Gian-Andri M Gianluca M Gianno M Gibran M Gibril M Gil M Gil-Leo M Gilles M Gion
1270             M Giona M Giovanni M Giuliano M Giuseppe M Glen M Glenn M Gonçalo M Gondini M
1271             Gregor M Gregory M Güney M Guilien M Guillaume M Gustav M Gustavo M Gusti M
1272             Haakon M Haci M Hadeed M Halil M Hamad M Hamid M Hamza M Hannes M Hans M Hari M
1273             Haris M Harry M Hassan M Heath M Hektor M Hendri M Henri M Henrik M Henry M
1274             Henus M Hugo M Hussein M Huw M Iago M Ian M Iasu M Ibrahim M Idan M Ieremia M
1275             Ifran M Iheb M Ikechi M Ilai M Ilarion M Ilian M Ilias M Ilja M Ilyes M Ioav M
1276             Iorek M Isaac M Isak M Ishaan M Ishak M Isi M Isidor M Ismael M Ismaël M Itay M
1277             Ivan M Iven M Ivo M Jack M Jacob M Jacques M Jaden M Jae-Eun M Jago M Jahongir M
1278             Jake M Jakob M Jakov M Jakub M Jamal M Jamen M James M Jamie M Jamiro M Jan M
1279             Janick M Janis M Jann M Jannes M Jannik M Jannis M Janos M János M Janosch M
1280             Jari M Jaron M Jasha M Jashon M Jason M Jasper M Javier M Jawhar M Jay M Jayden
1281             M Jayme M Jean M Jechiel M Jemuël M Jens M Jeremias M Jeremy M Jerlen M Jeroen M
1282             Jérôme M Jerun M Jhun M Jim M Jimmy M Jitzchak M Joah M Joaquin M Joel M Joël M
1283             Johan M Johann M Johannes M Johansel M John M Johnny M Jon M Jona M Jonah M
1284             Jonas M Jonathan M Joona M Jordan M Jorin M Joris M Jose M Josef M Joseph-Lion M
1285             Josh M Joshua M Jovan M Jovin M Jules M Julian M Julien M Julius M Jun-Tao M
1286             Junior M Junis M Juri M Jurij M Justin M Jythin M Kaan M Kailash M Kaitos M
1287             Kajeesh M Kajetan M Kardo M Karim M Karl M Karl-Nikolaus M Kasimir M Kaspar M
1288             Kassim M Kathiravan M Kaynaan M Kaynan M Keanan M Keano M Kejwan M Kenai M
1289             Kennedy M Kento M Kerim M Kevin M Khodor M Kian M Kieran M Kilian M Kimon M
1290             Kiran M Kiyan M Koji M Konrad M Konstantin M Kosmo M Krishang M Krzysztof M
1291             Kuzey M Kyan M Kyle M Labib M Lakishan M Lamoral M Lanyu M Laris M Lars M Larton
1292             M Lasse M Laurent M Laurenz M Laurin M Lawand M Lawrence M Lazar M Lean M
1293             Leander M Leandro M Leano M Leart M Leas M Leen M Leif M Len M Lenart M Lend M
1294             Lendrit M Lenert M Lenn M Lennard M Lennart M Lenno M Lennox M Lenny M Leno M
1295             Leo M Leon M León M Léon M Leonard M Leonardo M Leonel M Leonidas M Leopold M
1296             Leopoldo M Leron M Levi M Leviar M Levin M Levis M Lewis M Liam M Lian M Lias M
1297             Liél M Lieven M Linard M Lino M Linor M Linus M Linus-Lou M Lio M Lion M Lionel
1298             M Lior M Liun M Livio M Lizhang M Lloyd M Logan M Loïc M Lois M Long M Lono M
1299             Lorenz M Lorenzo M Lorian M Lorik M Loris M Lou M Louay M Louis M Lovis M Lowell
1300             M Luan M Luc M Luca M Lucas M Lucian M Lucien M Lucio M Ludwig M Luis M Luís M
1301             Luk M Luka M Lukas M Lumen M Lyan M Maaran M Maddox M Mads M Mael M Maél M Máel
1302             M Mahad M Mahir M Mahmoud M Mailo M Maksim M Maksut M Malik M Manfred M Máni M
1303             Manuel M Manuele M Maor M Marc M Marcel M Marco M Marek M Marino M Marius M Mark
1304             M Marko M Markus M Marley M Marlon M Marouane M Marti M Martim M Martin M Marvin
1305             M Marwin M Mason M Massimo M Matay M Matej M Mateja M Mateo M Matheo M Mathéo M
1306             Matheus M Mathias M Mathieu M Mathis M Matia M Matija M Matisjohu M Mats M
1307             Matteo M Matthew M Matthias M Matthieu M Matti M Mattia M Mattis M Maurice M
1308             Mauricio M Maurin M Maurizio M Mauro M Maurus M Max M Maxence M Maxim M Maxime M
1309             Maximilian M Maximiliano M Maximilien M Maxmilan M Maylon M Median M Mehmet M
1310             Melis M Melvin M Memet M Memet-Deniz M Menachem M Meo M Meris M Merlin M Mert M
1311             Mete M Methma M Mias M Micah M Michael M Michele M Miguel M Mihailo M Mihajlo M
1312             Mika M Mike M Mikias M Mikka M Mikko M Milad M Milan M Milo M Milos M Minyou M
1313             Mio M Miran M Miraxh M Miro M Miron M Mishkin M Mithil M Mohamed M Mohammed M
1314             Moische M Momodou M Mordechai M Moreno M Moritz M Morris M Moses M Mubaarak M
1315             Muhamet M Muhammad M Muhammed M Muhannad M Muneer M Munzur M Mustafa M Nadir M
1316             Nahuel M Naïm M Nando M Nasran M Nathan M Nathanael M Natnael M Nelio M Nelson M
1317             Nenad M Neo M Néo M Nepomuk M Nevan M Nevin M Nevio M Nic M Nick M Nick-Nolan M
1318             Niclas M Nicolas M Nicolás M Niilo M Nik M Nikhil M Niklas M Nikola M Nikolai M
1319             Nikos M Nilas M Nils M Nima M Nimo M Nino M Niven M Nnamdi M Noah M Noam M Noan
1320             M Noè M Noel M Noël M Nurhat M Nuri M Nurullmubin M Odarian M Odin M Ognjen M
1321             Oliver M Olufemi M Omar M Omer M Ömer M Orell M Orlando M Oscar M Oskar M Osman
1322             M Otávio M Otto M Ousmane M Pablo M Pablo-Battista M Paolo M Paris M Pascal M
1323             Patrice M Patrick M Patrik M Paul M Pavle M Pawat M Pax M Paxton M Pedro M
1324             Peppino M Pessach M Peven M Phil M Philemon M Philip M Philipp M Phineas M
1325             Phoenix-Rock M Piero M Pietro M Pio M Pjotr M Prashanth M Quentin M Quinnlan M
1326             Quirin M Rafael M Raffael M Raffaele M Rainer M Rami M Ramí M Ran M Raoul M
1327             Raphael M Raphaël M Rasmus M Raúl M Ray M Rayen M Reban M Reda M Refoel M Rejan
1328             M Relja M Remo M Remy M Rémy M Rénas M Rens M Resul M Rexhep M Rey M Riaan M
1329             Rian M Riccardo M Richard M Rico M Ridley M Riley M Rimon M Rinaldo M Rio M Rion
1330             M Riyan M Riza M Roa M Roald M Robert M Rodney-Jack M Rodrigo M Roman M Romeo M
1331             Ronan M Rory M Rouven M Roy M Ruben M Rúben M Rubino M Rufus M Ryan M Saakith M
1332             Saatvik M Sabir M Sabit M Sacha M Sahl M Salaj M Salman M Salomon M Sami M
1333             Sami-Abdeljebar M Sammy M Samuel M Samuele M Samy M Sandro M Santiago M Saqlain
1334             M Saranyu M Sascha M Sava M Schloime M Schmuel M Sebastian M Sebastián M Selim M
1335             Semih M Semir M Semyon M Senthamil M Serafin M Seraphin M Seth M Sevan M Severin
1336             M Seya M Seymen M Seymour M Shafin M Shahin M Shaor M Sharon M Shayaan M Shayan
1337             M Sheerbaz M Shervin M Shian M Shiraz M Shlomo M Shon M Siddhesh M Silas M
1338             Sileye M Silvan M Silvio M Simeon M Simon M Sirak M Siro M Sivan M Soel M Sol M
1339             Solal M Souleiman M Sriswaralayan M Sruli M Stefan M Stefano M Stephan M Steven
1340             M Stian M Strahinja M Sumedh M Suryansh M Sven M Taavi M Taha M Taner M Tanerau
1341             M Tao M Tarik M Tashi M Tassilo M Tayshawn M Temuulen M Teo M Teris M Thelonious
1342             M Thenujan M Theo M Theodor M Thiago M Thierry M Thies M Thijs M Thilo M Thom M
1343             Thomas M Thor M Tiago M Tiemo M Til M Till M Tilo M Tim M Timo M Timon M
1344             Timothée M Timotheos M Timothy M Tino M Titus M Tjade M Tjorben M Tobias M Tom M
1345             Tomás M Tomeo M Tosco M Tristan M Truett M Tudor M Tugra M Turan M Tyson M Uari
1346             M Uros M Ursin M Usuy M Uwais M Valentin M Valerian M Valerio M Vangelis M
1347             Vasilios M Vico M Victor M Viggo M Vihaan M Viktor M Villads M Vincent M Vinzent
1348             M Vinzenz M Vito M Vladimir M Vleron M Vo M Vojin M Wander M Wanja M William M
1349             Wim M Xavier M Yaakov M Yadiel M Yair M Yamin M Yanhao M Yanic M Yanik M Yanis M
1350             Yann M Yannick M Yannik M Yannis M Yardil M Yared M Yari M Yasin M Yasir M Yavuz
1351             M Yecheskel M Yehudo M Yeirol M Yekda M Yellyngton M Yiannis M Yifan M Yilin M
1352             Yitzchok M Ylli M Yoan M Yohannes M Yonatan M Yonathan M Yosias M Younes M
1353             Yousef M Yousif M Yousuf M Youwei M Ysaac M Yuma M Yussef M Yusuf M Yves M Zaim
1354             M Zeno M Zohaq M Zuheyb M Zvi M};
1355              
1356             # This slow setting allows us to find errors.
1357             my %names;
1358             my $last = "";
1359             while (@names) {
1360             my $key = shift(@names);
1361             my $val = shift(@names);
1362             die "$last $key" unless $val =~ /^[FM?]$/;
1363             $names{$key} = $val;
1364             $last = $val;
1365             }
1366              
1367             sub name {
1368 403     403 0 149824 return one(keys %names);
1369             }
1370              
1371             my $traits = {
1372             # http://charaktereigenschaften.miroso.de/
1373             de => [qw{
1374             aalglatt abenteuerlustig abfällig abgebrüht abgehoben abgeklärt abgestumpft
1375             absprachefähig abwartend abweisend abwägend achtsam affektiert affig aggressiv
1376             agil akkurat akribisch aktiv albern altklug altruistisch ambitioniert
1377             anarchisch angeberisch angepasst angriffslustig angsteinflößend angstvoll
1378             anhänglich anmutig anpassungsfähig ansprechend anspruchslos anspruchsvoll
1379             anstrengend anzüglich arbeitswütig arglistig arglos argwöhnisch arrogant artig
1380             asketisch athletisch attraktiv aufbegehrend aufbrausend aufdringlich aufgedreht
1381             aufgeregt aufgeschlossen aufgeweckt aufhetzerisch aufmerksam
1382             aufmerksamkeitsbedürftig aufmüpfig aufopfernd aufrichtig aufschneiderisch
1383             aufsässig ausdauernd ausdruckslos ausdrucksstark ausfallend ausgeflippt
1384             ausgefuchst ausgeglichen ausländerfeindlich ausnutzbar autark authentisch
1385             autonom autoritär außergewöhnlich barbarisch barmherzig barsch bedacht
1386             bedrohlich bedrückt bedächtig bedürfnislos beeinflussbar befangen befehlerisch
1387             begeistert begeisterungsfähig begierig begnügsam begriffsstutzig behaglich
1388             beharrlich behende beherrscht beherzt behutsam behäbig beirrbar belastbar
1389             belebend beliebt bemüht bequem berechnend beredsam berüchtigt bescheiden
1390             besessen besitzergreifend besonders besonnen besorgt besserwissend
1391             besserwisserisch bestechend bestechlich bestialisch bestimmend bestimmerisch
1392             beständig betriebsam betrügerisch betörend bewandert bewusst bezaubernd bieder
1393             bigott bissig bitter bizarr blasiert blass blauäugig blumig blutrünstig bockig
1394             bodenständig borniert boshaft brav breitspurig brisant brummig brutal bärbeißig
1395             bösartig böse böswillig chaotisch charismatisch charmant chauvinistisch
1396             cholerisch clever cool couragiert damenhaft dankbar defensiv dekadent
1397             demagogisch demütig depressiv derb desorganisiert despotisch destruktiv
1398             determinativ devot dezent dezidiert diabolisch dickhäutig dickköpfig diffus
1399             diktatorisch diplomatisch direkt diskret distanziert distinguiert diszipliniert
1400             disziplinlos divenhaft dogmatisch doktrinär dominant doof dramatisch
1401             dramatisierend draufgängerisch dreist drängend dubios duckmäuserisch duldsam
1402             dumm durchblickend durcheinander durchschaubar durchschauend durchsetzungsstark
1403             durchtrieben dusselig dynamisch dämlich dünkelhaft dünnhäutig echt edel
1404             effizient egoistisch egoman egozentrisch ehrenhaft ehrenwert ehrfürchtig
1405             ehrgeizig ehrlich eifersüchtig eifrig eigen eigenartig eigenbestimmt
1406             eigenbrödlerisch eigenmächtig eigennützig eigensinnig eigenständig eigenwillig
1407             eilig einfach einfallslos einfallsreich einfältig einfühlsam eingebildet
1408             eingeschüchtert einladend einnehmend einsam einsatzbereit einschüchternd
1409             einseitig einsichtig einträchtig eintönig einzelgängerisch einzigartig eisern
1410             eiskalt eitel ekelig elastisch elefantös elegant elitär emotional empathisch
1411             empfindlich empfindsam empfindungsvoll emsig energetisch energiegeladen
1412             energievoll energisch engagiert engstirnig entgegenkommend enthaltsam enthemmt
1413             enthusiastisch entscheidungsfreudig entschieden entschlossen entspannt
1414             enttäuscht erbarmungslos erbärmlich erfinderisch erfolgsorientiert erfrischend
1415             ergeben erhaben erlebnisse ermutigend ernst ernsthaft erotisch erwartungsvoll
1416             exaltiert exorbitant experimentierfreudig extravagant extravertiert
1417             extrovertiert exzentrisch facettenreich fair falsch familiär fantasielos
1418             fantasiereich fantasievoll fantastisch fatalistisch faul feige fein feindselig
1419             feinfühlig feinsinnig feminin fesselnd feurig fies fixiert flatterhaft fleissig
1420             fleißig flexibel folgsam fordernd forsch fragil frech freiheitskämfend
1421             freiheitsliebend freimütig freizügig fremdbestimmend fremdbestimmt freudvoll
1422             freundlich friedfertig friedlich friedliebend friedlos friedselig friedvoll
1423             frigide frisch frohgemut frohnatur frohsinnig fromm frostig fröhlich furchtlos
1424             furchtsam furios fügsam fürsorglich galant gallig gamsig garstig gastfreundlich
1425             gebieterisch gebildet gebührend gedankenlos gediegen geduldig gefallsüchtig
1426             gefährlich gefällig gefügig gefühllos gefühlsbetont gefühlskalt gefühlvoll
1427             geheimnisvoll gehemmt gehorsam gehässig geistreich geizig geladen gelassen
1428             geldgierig geltungssüchtig gemein gemütvoll genauigkeitsliebend generös genial
1429             genügsam gepflegt geradlinig gerecht gerechtigkeitsliebend gerissen gescheit
1430             geschickt geschmeidig geschwätzig gesellig gesprächig gesundheitsbewusst
1431             gewaltsam gewalttätig gewieft gewissenhaft gewissenlos gewitzt gewöhnlich
1432             gierig giftig glamurös glaubensstark gleichgültig gleichmütig gläubig gnadenlos
1433             gottergeben gottesfürchtig grantig grausam grazil griesgrämig grimmig grob
1434             grotesk großherzig großkotzig großmäulig großmütig großspurig großzügig
1435             gräßlich größenwahnsinnig grübelnd gründlich gutgläubig gutherzig gutmütig
1436             gönnerhaft gütig haarspalterisch habgierig habsüchtig halsstarrig harmlos
1437             harmoniebedürftig harmoniesüchtig hart hartherzig hartnäckig hasenherzig
1438             hasserfüllt hedonistisch heimatverbunden heimtückisch heiter hektisch
1439             heldenhaft heldenmütig hellhörig hemmungslos herablassend herausfordernd
1440             heroisch herrisch herrlich herrschsüchtig herzerfrischend herzlich herzlos
1441             hetzerisch heuchlerisch hibbelig hilflos hilfsbereit hingebungsvoll
1442             hinterfotzig hintergründig hinterhältig hinterlistig hinterwäldlerisch
1443             hirnrissig hitzig hitzköpfig hochbegabt hochfahrend hochmütig hochnäsig
1444             hochtrabend humorlos humorvoll hyperkorrekt hysterisch hämisch hässlich
1445             häuslich höflich höflichkeitsliebend höhnisch hübsch ichbezogen idealistisch
1446             ideenreich idiotisch ignorant impertinent impulsiv inbrünstig individualistisch
1447             infam infantil initiativ inkompetent inkonsequent innovativ instinktiv integer
1448             intelektuell intelligent intensiv interessiert intolerant intrigant
1449             introvertiert intuitiv ironisch irre jovial jugendlich jung jähzornig
1450             kalkulierend kalt kaltblütig kaltherzig kaltschnäuzig kapriziös kasuistisch
1451             katzig kauzig keck kess ketzerisch keusch kinderlieb kindisch kindlich klar
1452             kleingeistig kleinkariert kleinlaut kleinlich kleinmütig klug knackig knallhart
1453             knickrig kokett komisch kommunikationsfähig kommunikativ kompetent kompliziert
1454             kompromissbereit konfliktfreudig konfliktscheu konkret konsequent konservativ
1455             konsistent konstant kontaktarm kontaktfreudig kontraproduktiv kontrareligiös
1456             kontrolliert konziliant kooperativ kopffrorm kopflastig kordial korrekt korrupt
1457             kosmopolitisch kraftvoll krank kratzbürstig kreativ kriecherisch
1458             kriegstreiberisch kriminell kritisch kritkfähig kräftig kulant kultiviert
1459             kumpelhaft kurios kämpferisch kühl kühn künstlerisch künstlich labil lachhaft
1460             lahm lammfromm langmütig langweilig larmoyant launisch laut lebendig
1461             lebensbejahend lebensfroh lebenslustig lebhaft leicht leichtfertig leichtfüssig
1462             leichtgläubig leichtlebig leichtsinnig leidenschaftlich leidlich leise
1463             leistungsbereit leistungsstark lernbereit lethargisch leutselig liberal lieb
1464             liebenswert liebevoll lieblich lieblos locker loyal lustlos lustvoll
1465             lösungsorientiert lügnerisch lüstern machtbesessen machtgierig machthaberisch
1466             machthungrig mager magisch manipulativ markant martialisch maskulin
1467             masochistisch materialistisch matriachalisch maßlos melancholisch memmenhaft
1468             menschenscheu menschenverachtend merkwürdig mies mild militant mimosenhaft
1469             minimalistisch misanthropisch missgünstig missmutig misstrauisch mitfühlend
1470             mitleiderregend mitleidlos mitleidslos mitteilsam modisch mollig mondän
1471             moralisch motivierend motiviert musikalisch mutig männerfeindlich mürrisch
1472             mütterlich nachdenklich nachgiebig nachlässig nachsichtig nachtragend naiv
1473             naturfreudig naturverbunden natürlich nebulös neckisch negativ neiderfüllt
1474             neidisch nervig nervös nett neugierig neurotisch neutral nichtssagend
1475             niedergeschlagen niederträchtig niedlich nihilistisch nonchalant normal notgeil
1476             nutzlos nüchtern oberflächlich objektiv obszön offen offenherzig
1477             opportunistisch oppositionell optimistisch ordentlich ordinär ordnungsfähig
1478             ordnungsliebend organisiert orientierungslos originell paranoid passiv patent
1479             patriarchisch patriotisch pedantisch pejorativ penibel perfektionistisch
1480             pervers pessimistisch pfiffig pflegeleicht pflichtbewusst pflichtversessen
1481             phantasievoll philanthropisch phlegmatisch phobisch pingelig planlos plump
1482             polarisierend politisch positiv pragmatisch prinzipientreu problembewusst
1483             profilierungssüchtig progressiv prollig promiskuitiv prophetisch protektiv
1484             provokant prüde psychotisch putzig pünktlich qualifiziert quengelig querdenkend
1485             querulant quicklebendig quirlig quälend rabiat rachsüchtig radikal raffiniert
1486             rastlos ratgebend rational ratlos ratsuchend rau reaktionsschnell reaktionär
1487             realistisch realitätsfremd rebellisch rechthaberisch rechtlos rechtschaffend
1488             redegewandt redelustig redselig reflektiert rege reif reiselustig reizbar
1489             reizend reizvoll religiös renitent reserviert resigniert resolut respektlos
1490             respektvoll reumütig rigoros risikofreudig robust romantisch routineorientiert
1491             ruhelos ruhig ruppig rückgratlos rücksichtslos rücksichtsvoll rüde sachlich
1492             sadistisch sanft sanftmütig sanguinisch sardonisch sarkastisch sauertöpfisch
1493             schadenfroh schamlos scheinheilig scheu schlagfertig schlampig schlau
1494             schmeichelhaft schneidig schnell schnippisch schnoddrig schreckhaft schrullig
1495             schullehrerhaft schusselig schwach schweigsam schwermütig schäbig schöngeistig
1496             schüchtern seicht selbstbewusst selbstdarstellerisch selbstgefällig
1497             selbstgerecht selbstherrlich selbstkritisch selbstlos selbstreflektierend
1498             selbstsicher selbstständig selbstsüchtig selbstverliebt selbstzweifelnd seltsam
1499             senil sensationslüstern sensibel sensitiv sentimental seriös sexistisch sexy
1500             sicherheitsbedürftig sinnlich skeptisch skrupellos skurril smart solidarisch
1501             solide sonnig sorgfältig sorglos sorgsam souverän sparsam spaßig spießig
1502             spirituell spitzfindig spontan sportlich sprachbegabt spritzig sprunghaft
1503             spröde spöttisch staatsmännisch stabil stachelig standhaft stark starr
1504             starrköpfig starrsinnig stereotypisch stilbewusst still stilsicher stilvoll
1505             stinkig stoisch stolz strahlend strategisch streberhaft strebsam streitsüchtig
1506             streng strikt stumpf stur sturköpfig störend störrisch stürmisch subjektiv
1507             subtil suchend suchtgefährdet suspekt sympathisch süchtig tadellos taff
1508             tagträumerisch taktisch taktlos taktvoll talentiert tatkräftig tatlos teamfähig
1509             temperamentlos temperamentvoll tiefgründig tierlieb tolerant toll tollkühn
1510             tollpatschig tough transparent traurig treu trotzig träge träumerisch
1511             trübsinnig tyrannisch töricht tüchtig ulkig umgänglich umsichtig umständlich
1512             umtriebig unabhängig unanständig unantastbar unartig unaufrichtig
1513             unausgeglichen unbedeutend unbeherrscht unbeirrbar unbelehrbar unberechenbar
1514             unbeschreiblich unbeschwert unbesonnen unbeständig unbeugsam undankbar
1515             undiszipliniert undurchschaubar undurchsichtig unehrlich uneigennützig uneinig
1516             unentschlossen unerbittlich unerreichbar unerschrocken unerschütterlich
1517             unerträglich unfair unfein unflätig unfolgsam unfreundlich ungeduldig
1518             ungehorsam ungehörig ungerecht ungeschickt ungesellig ungestüm ungewöhnlich
1519             ungezogen ungezügelt unglaubwürdig ungläubig unhöflich unkompliziert
1520             unkonventionell unkonzentriert unmenschlich unnachgiebig unnahbar unordentlich
1521             unparteiisch unproblematisch unpünktlich unrealistisch unreflektiert unruhig
1522             unsachlich unscheinbar unschlüssig unschuldig unselbständig unsensibel unsicher
1523             unstet unternehmungsfreudig unternehmungslustig untertänig unterwürfig untreu
1524             unverschämt unverwechselbar unverzagt unzufrieden unzuverlässig verachtend
1525             verantwortungsbewusst verantwortungslos verantwortungsvoll verbindlich
1526             verbissen verbittert verbrecherisch verfressen verführerisch vergebend
1527             vergesslich verhandlungsstark verharrend verkopft verlangend verletzbar
1528             verletzend verliebt verlogen verlustängstlich verlässlich vermittelnd
1529             vernetzend vernünftig verrucht verräterisch verrückt verschlagen verschlossen
1530             verschmitzt verschroben verschüchtert versiert verspielt versponnen
1531             verständnislos verständnisvoll verstört vertrauensvoll vertrauenswürdig
1532             verträumt verwahrlost verwegen verwirrt verwundert verwöhnt verzweifelt
1533             vielfältig vielschichtig vielseitig vital vorausschauend voreingenommen vorlaut
1534             vornehm vorsichtig vorwitzig väterlich wagemutig waghalsig wahnhaft wahnsinnig
1535             wahnwitzig wahrhaftig wahrheitsliebend wankelmütig warm warmherzig wechselhaft
1536             wehmütig weiblich weich weinselig weise weitsichtig weltfremd weltoffen wendig
1537             wichtigtuerisch widerlich widerspenstig widersprüchlich widerstandsfähig wild
1538             willenlos willensschwach willensstark willig willkürlich wirsch wissbegierig
1539             wissensdurstig witzig wohlerzogen wohlgesinnt wortkarg wählerisch würdelos
1540             würdevoll xanthippisch zaghaft zappelig zartbesaitet zartfühlend zauberhaft
1541             zaudernd zerbrechlich zerdenkend zerknautscht zerstreut zerstörerisch zickig
1542             zielbewusst zielführend zielorientiert zielstrebig zimperlich zufrieden
1543             zugeknöpft zuhörend zukunftsgläubig zupackend zurechnungsfähig zurückhaltend
1544             zuverlässig zuversichtlich zuvorkommend zwanghaft zweifelnd zwiegespalten
1545             zwingend zäh zärtlich zögerlich züchtig ängstlich ätzend öde überdreht
1546             überemotional überfürsorglich übergenau überheblich überkandidelt überkritisch
1547             überlebensfähig überlegen überlegt übermütig überragend überraschend
1548             überreagierend überschwenglich übersensibel überspannt überwältigent}],
1549             # http://www.roleplayingtips.com/tools/1000-npc-traits/
1550             en => [qw{able abrasive abrupt absent minded abusive accepting
1551             accident prone accommodating accomplished action oriented active
1552             adaptable substance abusing adorable adventurous affable affected
1553             affectionate afraid uncommited aggressive agnostic agreeable alert
1554             alluring aloof altruistic always hungry always late ambiguous
1555             ambitious amiable amused amusing angry animated annoyed annoying
1556             anti-social anxious apathetic apologetic appreciative apprehensive
1557             approachable argumentative aristocratic arrogant artistic ashamed
1558             aspiring assertive astonished attentive audacious austere
1559             authoritarian authoritative available average awful awkward babbling
1560             babyish bad bashful beautiful belligerent bewildered biter
1561             blames others blasé blowhard boastful boisterous bold boorish bored
1562             boring bossy boundless brainy brash bratty brave brazen bright
1563             brilliant brotherly brutish bubbly busy calculating callous calm
1564             candid capable capricious carefree careful careless caring caustic
1565             cautious changeable charismatic charming chaste cheerful cheerless
1566             childish chivalrous civilised classy clean clever close closed clumsy
1567             coarse cocky coherent cold cold hearted combative comfortable
1568             committed communicative compassionate competent complacent compliant
1569             composed compulsive conceited concerned condescending confident
1570             confused congenial conscientious considerate consistent constricting
1571             content contented contrarian contrite controlling conversational
1572             cooperative coquettish courageous courteous covetous cowardly cowering
1573             coy crabby crafty cranky crazy creative credible creepy critical cross
1574             crude cruel cuddly cultured curious cutthroat cynical dainty dangerous
1575             daring dark dashing dauntless dazzling debonair deceitful deceiving
1576             decent decisive decorous deep defeated defective deferential defiant
1577             deliberate delicate delightful demanding demonic dependable dependent
1578             depressed deranged despicable despondent detached detailed determined
1579             devilish devious devoted dignified diligent direct disaffected
1580             disagreeable discerning disciplined discontented discouraged discreet
1581             disgusting dishonest disillusioned disinterested disloyal dismayed
1582             disorderly disorganized disparaging disrespectful dissatisfied
1583             dissolute distant distraught distressed disturbed dogmatic domineering
1584             dorky doubtful downtrodden draconian dramatic dreamer dreamy dreary
1585             dubious dull dumb dutiful dynamic eager easygoing eccentric educated
1586             effervescent efficient egocentric egotistic elated eloquent
1587             embarrassed embittered embraces change eminent emotional empathetic
1588             enchanting encouraging enduring energetic engaging enigmatic
1589             entertaining enthusiastic envious equable erratic ethical evasive evil
1590             exacting excellent excessive excitable excited exclusive expansive
1591             expert extravagant extreme exuberant fabulous facetious faded fair
1592             faith in self faithful faithless fake fanatical fanciful fantastic
1593             fatalistic fearful fearless feisty ferocious fidgety fierce fiery
1594             fighter filthy fine finicky flagging flakey flamboyant flashy fleeting
1595             flexible flighty flippant flirty flustered focused foolish forceful
1596             forgetful forgiving formal fortunate foul frank frantic fresh fretful
1597             friendly frightened frigid frugal frustrated fuddy duddy fun
1598             fun loving funny furious furtive fussy gabby garrulous gaudy generous
1599             genial gentle giddy giggly gives up easily giving glamorous gloomy
1600             glorious glum goal orientated good goofy graceful gracious grandiose
1601             grateful greedy gregarious grieving grouchy growly gruesome gruff
1602             grumpy guarded guilt ridden guilty gullible haggling handsome happy
1603             hard hard working hardy harmonious harried harsh hateful haughty
1604             healthy heart broken heartless heavy hearted hedonistic helpful
1605             helpless hesitant high high self esteem hilarious homeless honest
1606             honor bound honorable hopeful hopeless hormonal horrible hospitable
1607             hostile hot headed huffy humble humorous hurt hysterical ignorant ill
1608             ill-bred imaginative immaculate immature immobile immodest impartial
1609             impatient imperial impolite impotent impractical impudent impulsive
1610             inactive incoherent incompetent inconsiderate inconsistent indecisive
1611             independent indifferent indiscrete indiscriminate indolent indulgent
1612             industrious inefficient inept inflexible inimitable innocent
1613             inquisitive insecure insensitive insightful insincere insipid
1614             insistent insolent instinctive insulting intellectual intelligent
1615             intense interested interrupting intimidating intolerant intrepid
1616             introspective introverted intuitive inventive involved irresolute
1617             irresponsible irreverent irritable irritating jackass jaded jealous
1618             jittery joking jolly jovial joyful joyous judgmental keen kenderish
1619             kind hearted kittenish knowledgeable lackadaisical lacking languid
1620             lascivious late lazy leader lean lethargic level lewd liar licentious
1621             light-hearted likeable limited lineat lingering lively logical lonely
1622             loquacious lordly loud loudmouth lovable lovely loves challenge loving
1623             low confidence lowly loyal lucky lunatic lying macho mad malicious
1624             manipulative mannerly materialistic matronly matter-of-fact mature
1625             mean meek melancholy melodramatic mentally slow merciful mercurial
1626             messy meticulous mild mischievous miserable miserly mistrusting modern
1627             modest moody moping moralistic motherly motivated mysterious nagging
1628             naive narcissistic narrow-minded nasty naughty neat
1629             needs social approval needy negative negligent nervous neurotic
1630             never hungry nibbler nice night owl nihilistic nimble nit picker
1631             no purpose no self confidence noble noisy nonchalant nosy
1632             not trustworthy nuanced nuisance nurturing nut obedient obese obliging
1633             obnoxious obscene obsequious observant obstinate odd odious open
1634             open-minded opinionated opportunistic optimistic orcish orderly
1635             organized ornery ossified ostentatious outgoing outrageous outspoken
1636             overbearing overweight overwhelmed overwhelming paces pacifistic
1637             painstaking panicky paranoid particular passionate passive
1638             passive-aggressive pathetic patient patriotic peaceful penitent
1639             pensive perfect perfectionist performer perserverant perseveres
1640             persevering persistent persuasive pert perverse pessimistic petty
1641             petulant philanthropic picky pious pitiful placid plain playful
1642             pleasant pleasing plotting plucky polite pompous poor popular positive
1643             possessive practical precise predictable preoccupied pretentious
1644             pretty prim primitive productive profane professional promiscuous
1645             proper protective proud prudent psychotic puckish punctilious punctual
1646             purposeful pushy puzzled quarrelsome queer quick quick tempered quiet
1647             quirky quixotic rambunctious random rash rational rawboned realistic
1648             reasonable rebellious recalcitrant receptive reckless reclusive
1649             refined reflective regretful rejects change relaxed relents reliable
1650             relieved religious reluctant remorseful repugnant repulsive resentful
1651             reserved resilient resolute resourceful respectful responsible
1652             responsive restless retiring rhetorical rich right righteous rigid
1653             risk-taking romantic rough rowdy rude rugged ruthless sacrificing sad
1654             sadistic safe sagely saintly salient sanctimonious sanguine sarcastic
1655             sassy satisfied saucy savage scared scarred scary scattered scheming
1656             scornful scrawny scruffy secretive secure sedate seductive selective
1657             self-centered self-confident self-conscious self-controlling
1658             self-directed self-disciplined self-giving self-reliant self-serving
1659             selfish selfless senile sensitive sensual sentimental serene serious
1660             sexual sexy shallow shameless sharp sharp-tongued sharp-witted
1661             sheepish shiftless shifty short shrewd shy silent silky silly simian
1662             simple sincere sisterly skillful sleazy sloppy slovenly slow paced
1663             slutty sly small-minded smart smiling smooth sneaky snob sociable
1664             soft-hearted soft-spoken solitary sore sorry sour spendthrift spiteful
1665             splendid spoiled spontaneous spunky squeamish stately static steadfast
1666             sterile stern stimulating stingy stoical stolid straight laced strange
1667             strict strident strong strong willed stubborn studious stupid suave
1668             submissive successful succinct sulky sullen sultry supercilious
1669             superstitious supportive surly suspicious sweet sympathetic systematic
1670             taciturn tacky tactful tactless talented talkative tall tardy tasteful
1671             temperamental temperate tenacious tense tentative terrible terrified
1672             testy thankful thankless thick skinned thorough thoughtful thoughtless
1673             threatening thrifty thrilled tight timid tired tireless tiresome
1674             tolerant touchy tough trivial troubled truculent trusting trustworthy
1675             truthful typical ugly unappreciative unassuming unbending unbiased
1676             uncaring uncommitted unconcerned uncontrolled unconventional
1677             uncooperative uncoordinated uncouth undependable understanding
1678             undesirable undisciplined unenthusiastic unfeeling unfocused
1679             unforgiving unfriendly ungrateful unhappy unhelpful uninhibited unkind
1680             unmotivated unpredictable unreasonable unreceptive unreliable
1681             unresponsive unrestrained unruly unscrupulous unselfish unsure
1682             unsympathetic unsystematic unusual unwilling upbeat upset uptight
1683             useful vacant vague vain valiant vengeful venomous verbose versatile
1684             vigorous vindictive violent virtuous visual vivacious volatile
1685             voracious vulgar vulnerable warlike warm hearted wary wasteful weak
1686             weary weird well grounded whimsical wholesome wicked wild willing wise
1687             wishy washy withdrawn witty worldly worried worthless wretched
1688             xenophobic youthful zany zealous}], };
1689              
1690             # one way to test this on the command-line:
1691             # perl halberdsnhelmets.pl get --redirect /characters | w3m -T text/html
1692              
1693             sub traits {
1694 100     100 0 136 my $language = shift;
1695 100         144 local $lang = $language; # make sure T works as intended
1696 100         157 my $name = name();
1697 100         8262 my $gender = $names{$name};
1698 100         248 my $description = "$name, ";
1699 100         145 my $d;
1700 100 100       223 if ($gender eq "F") {
    50          
1701 46         81 $d = d3();
1702             } elsif ($gender eq "M") {
1703 54         102 $d = 3 + d3();
1704             } else {
1705 0         0 $d = d6();
1706             }
1707 100 100       319 if ($d == 1) {
    100          
    100          
    100          
    100          
    50          
1708 12         26 $description .= T('young woman');
1709             } elsif ($d == 2) {
1710 17         32 $description .= T('woman');
1711             } elsif ($d == 3) {
1712 17         33 $description .= T('elderly woman');
1713             } elsif ($d == 4) {
1714 14         30 $description .= T('young man');
1715             } elsif ($d == 5) {
1716 23         43 $description .= T('man');
1717             } elsif ($d == 6) {
1718 17         33 $description .= T('elderly man');
1719             };
1720 100         192 $description .= ", ";
1721 100         132 my $trait = one(@{$traits->{$lang}});
  100         792  
1722 100         159 $description .= $trait;
1723 100         129 my $other = one(@{$traits->{$lang}});
  100         592  
1724 100 50       195 if ($other ne $trait) {
1725 100         150 $description .= " " . T('and') . " " . $other;
1726             }
1727 100         344 return $description;
1728             }
1729              
1730             sub random {
1731 305     305 0 504 my $char = shift;
1732             # keys that can be provided: name, class, charsheet
1733              
1734 305 100       1039 provide($char, "name", name()) unless $char->{name};
1735 305         25598 my $class = $char->{class};
1736              
1737 305 50 33     820 my ($str, $dex, $con, $int, $wis, $cha) =
1738             $class eq T('hireling') || $class eq T('porter')
1739             ? (10, 10, 10, 10, 10, 10)
1740             : (roll_3d6(), roll_3d6(), roll_3d6(),
1741             roll_3d6(), roll_3d6(), roll_3d6());
1742              
1743             # if a class is provided, make sure minimum requirements are met
1744 305 50       825 if ($class eq T('dwarf')) {
1745 0         0 $con = roll_3d6() until average($con);
1746             }
1747 305 50       637 if ($class eq T('elf')) {
1748 0         0 $int = roll_3d6() until average($int);
1749             }
1750 305 50       632 if ($class eq T('halfling')) {
1751 0         0 $con = roll_3d6() until average($con);
1752 0         0 $dex = roll_3d6() until average($dex);
1753             }
1754              
1755 305         921 provide($char, "str", $str);
1756 305         668 provide($char, "dex", $dex);
1757 305         724 provide($char, "con", $con);
1758 305         662 provide($char, "int", $int);
1759 305         695 provide($char, "wis", $wis);
1760 305         639 provide($char, "cha", $cha);
1761              
1762 305         673 provide($char, "xp", "0");
1763              
1764 305 50 33     641 if ($class eq T('hireling') or $class eq T('porter')) {
1765 0         0 provide($char, "level", "0");
1766 0         0 provide($char, "thac0", 20);
1767             } else {
1768 305         771 provide($char, "level", "1");
1769 305         537 provide($char, "thac0", 19);
1770             }
1771              
1772 305         806 my $best = best($str, $dex, $con, $int, $wis, $cha);
1773              
1774 305 100       700 if (not $class) {
1775 302 100 100     615 if (average($con) and $best eq "str") {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
      100        
      100        
1776 54         103 $class = T('dwarf');
1777             } elsif (average($int)
1778             and good($str, $dex)
1779             and d6() > 2) {
1780 46         87 $class = T('elf');
1781             } elsif (average($str, $dex, $con) == 3
1782             and good($str, $dex, $con)
1783             and d6() > 2) {
1784 26         102 $class = T('halfling');
1785             } elsif (average($str, $dex, $con) >= 2
1786             and ($best eq "str" or $best eq "con")
1787             or good($str, $dex, $con) >= 2) {
1788 32         75 $class = T('fighter');
1789             } elsif ($best eq "int") {
1790 48         82 $class = T('magic-user');
1791             } elsif ($best eq "dex") {
1792 24         50 $class = T('thief');
1793             } else {
1794 72         152 my @candidates = (T('thief'), T('magic-user'), T('fighter'));
1795 72         185 $class = one(@candidates);
1796             }
1797             }
1798              
1799 305         827 provide($char, "class", $class);
1800              
1801 305 100       575 if ($class eq T('halfling')) {
1802 26         75 provide($char, "range-thac0", 18 - bonus($dex));
1803             }
1804              
1805 305         665 my $level = $char->{level};
1806 305         541 my $hp = $char->{hp};
1807 305 50       608 if (not $hp) {
1808 305 100 100     597 if ($class eq T('fighter') or $class eq T('dwarf')) {
    100 100        
1809 112         658 $hp += max(1, d8() + bonus($con)) for 1.. $level;
1810             } elsif ($class eq T('elf') or $class eq T('halfling')) {
1811 72         321 $hp += max(1, d6() + bonus($con)) for 1.. $level;
1812             } else {
1813 121         622 $hp += max(1, d4() + bonus($con)) for 1.. $level;
1814             }
1815             }
1816 305         917 provide($char, "hp", $hp);
1817              
1818 305         780 equipment($char);
1819              
1820 305         776 my $abilities = abilities($char);
1821             # spellbook
1822 305 100 100     611 if ($class eq T('magic-user') or $class eq T('elf')) {
1823 118         266 $abilities .= "\\\\" . spellbook();
1824             }
1825              
1826 305         898 provide($char, "abilities", $abilities);
1827              
1828 305 50       905 if (not $char->{charsheet}) {
1829 305 50 33     559 if ($class eq T('hireling') or $class eq T('porter')) {
    50          
1830 0         0 provide($char, "charsheet", T('Hireling.svg'));
1831             } elsif ($char->{landscape}) {
1832 0         0 provide($char, "charsheet", T('Charactersheet-landscape.svg'));
1833             } else {
1834 305         598 provide($char, "charsheet", T('Charactersheet.svg'));
1835             }
1836             }
1837             }
1838              
1839             sub abilities {
1840 305     305 0 503 my $char = shift;
1841 305         578 my $class = $char->{class};
1842 305         494 my $abilities = T('1/6 for normal tasks');
1843 305 100       635 if ($class eq T('elf')) {
    100          
    100          
    100          
1844 46         90 $abilities .= "\\\\" . T('2/6 to hear noise');
1845 46         114 $abilities .= "\\\\" . T('2/6 to find secret or concealed doors');
1846             } elsif ($class eq T('dwarf')) {
1847 54         116 $abilities .= "\\\\" . T('2/6 to hear noise');
1848 54         117 $abilities .= "\\\\" . T('2/6 to find secret constructions and traps');
1849             } elsif ($class eq T('halfling')) {
1850 26         68 $abilities .= "\\\\" . T('2/6 to hear noise');
1851 26         63 $abilities .= "\\\\" . T('2/6 to hide and sneak');
1852 26         130 $abilities .= "\\\\" . T('5/6 to hide and sneak outdoors');
1853 26         93 $abilities .= "\\\\" . T('+1 bonus to ranged weapons');
1854 26         56 $abilities .= "\\\\" . T('AC -2 vs. opponents larger than humans');
1855             } elsif ($class eq T('thief')) {
1856 49         104 my $level = $char->{level};
1857 49         137 my $n = 2 + int($char->{level} / 3);
1858 49 50       118 $n = 5 if $n > 5;
1859             # override the 1/6 for normal tasks
1860 49         95 $abilities = sprintf(T('%d/6 for all activities'), $n);
1861 49         111 $abilities .= "\\\\" . T('+4 to hit and double damage backstabbing');
1862             }
1863 305         786 return $abilities;
1864             }
1865              
1866             sub classes {
1867             return {
1868 0     0 0 0 T('dwarf') => "D",
1869             T('elf') => "E",
1870             T('halfling') => "H",
1871             T('fighter') => "F",
1872             T('magic-user') => "M",
1873             T('thief') => "T",
1874             };
1875             }
1876              
1877             sub random_parameters {
1878 305     305 0 648 my ($char, $language) = @_;
1879 305         624 local $lang = $language; # make sure T works as intended
1880 305         750 random($char);
1881             # choose a random portrait based on the character name or class
1882 305 100       791 if (member("portrait", @_)) {
1883 5 50       28 provide($char, "portrait", portrait($char)) unless $char->{portrait};
1884             }
1885             }
1886              
1887             sub portrait {
1888 5     5 0 11 my $char = shift;
1889 5 50       34 my $face_generator_url = app->config("face_generator_url") or return '';
1890 0   0     0 my $gender = $char->{gender} || $names{$char->{name}};
1891 0 0 0     0 if ($char->{class} eq T('elf')
    0 0        
    0          
    0          
1892             or $char->{race} eq T('elf')) {
1893 0         0 $gender = "elf";
1894             } elsif ($char->{class} eq T('dwarf')
1895             or $char->{race} eq T('dwarf')) {
1896 0         0 $gender = "dwarf";
1897             } elsif ($gender eq "F") {
1898 0         0 $gender = "woman";
1899             } elsif ($gender eq "M") {
1900 0         0 $gender = "man";
1901             } else {
1902 0         0 $gender = one("woman", "man");
1903             }
1904 0         0 my $url = Mojo::URL->new("$face_generator_url/redirect/alex/$gender");
1905 0         0 my $ua = Mojo::UserAgent->new;
1906 0         0 my $tx = $ua->get($url);
1907 0 0       0 if ($tx->res->code == 302) {
1908 0         0 $url->path($tx->res->headers->location);
1909             } else {
1910             $log->warn("Did you configure the face_generator_url setting in the config file correctly? "
1911             . "It is currently set to $face_generator_url. "
1912             . ($tx->res->code
1913             ? "It returns: " . $tx->res->code . " " . $tx->res->message
1914 0 0       0 : "The error: " . $tx->res->error->{message}));
1915             }
1916 0         0 return $url;
1917             }
1918              
1919             sub characters {
1920 2     2 0 4 my ($char, $lang) = @_;
1921 2         4 my @characters;
1922 2         7 for (my $i = 0; $i < 50; $i++) {
1923 100         295 my %one = %$char; # defaults
1924 100         263 random_parameters(\%one, $lang);
1925 100         186 $one{traits} = traits($lang);
1926 100         361 push(@characters, \%one);
1927             }
1928 2         27 return \@characters;
1929             }
1930              
1931             sub stats {
1932 2     2 0 8 my ($char, $language, $n) = @_;
1933 2         6 local $lang = $language; # make sure T works as intended
1934 2         4 my (%class, %property);
1935 2         15 for (my $i = 0; $i < $n; $i++) {
1936 200         850 my %one = %$char; # defaults
1937 200         712 random_parameters(\%one, $lang);
1938 200         612 $class{$one{class}}++;
1939 200         1388 foreach (split(/\\\\/, $one{property})) {
1940 2398         5495 $property{$_}++;
1941             }
1942             }
1943              
1944 2         5 $n = 0;
1945 2         7 my $txt = T('Classes') . "\n";
1946 2         21 foreach (sort { $class{$b} <=> $class{$a} } keys %class) {
  20         38  
1947 12         42 $txt .= sprintf "%25s %4d\n", $_, $class{$_};
1948 12         22 $n += $class{$_};
1949             }
1950 2         8 $txt .= sprintf "%25s %4d\n", "total", $n;
1951              
1952 2         6 $txt .= T('Property') . "\n";
1953 2         26 foreach (sort { $property{$b} <=> $property{$a} }
  657         880  
1954             keys %property) {
1955 140 100 66     364 next if /starting gold:/ or /gold$/;
1956 104 100 66     250 next if /Startgold:/ or /Gold$/;
1957 68         197 $txt .= sprintf "%25s %4d\n", $_, $property{$_};
1958             }
1959 2         54 return $txt;
1960             }
1961              
1962             sub url_encode {
1963 0     0 0 0 my $str = shift;
1964 0 0       0 return '' unless defined $str;
1965 0         0 utf8::encode($str);
1966 0         0 my @letters = split(//, $str);
1967 0         0 my %safe = map {$_ => 1} ("a" .. "z", "A" .. "Z", "0" .. "9", "-", "_", ".", "!", "~", "*", "\"", "(", ")", "#");
  0         0  
1968 0         0 foreach my $letter (@letters) {
1969 0 0       0 $letter = sprintf("%%%02x", ord($letter)) unless $safe{$letter};
1970             }
1971 0         0 return join('', @letters);
1972             }
1973              
1974             sub init {
1975 20     20 0 47 my $self = shift;
1976 20         38 my %char = %{$self->req->params->to_hash};
  20         67  
1977 20         8606 my @provided; # We want to remember the order!
1978 20         49 my @pairs = @{$self->req->params->pairs};
  20         71  
1979 20         440 while (@pairs) {
1980 100         145 my $key = shift @pairs;
1981 100         134 my $value = shift @pairs;
1982 100         214 push(@provided, $key);
1983             }
1984 20         72 $char{provided} = \@provided;
1985 20         57 return \%char;
1986             }
1987              
1988             sub lang {
1989 7     7 0 16 my $self = shift;
1990 7         57 my $acceptor = I18N::AcceptLanguage->new(defaultLanguage => "en");
1991 7         271 return $acceptor->accepts($self->req->headers->accept_language, [qw(en de)]);
1992             }
1993              
1994             plugin "Config" => {default => {}};
1995              
1996             get "/" => sub {
1997             my $self = shift;
1998             $self->redirect_to($self->url_with("main" => {lang => lang($self)}));
1999             };
2000              
2001             get "/:lang" => [lang => qr/(?:en|de)/] => sub {
2002             my $self = shift;
2003             my $lang = $self->param("lang");
2004             my $query = $self->req->query_params->to_string;
2005             if ($query) {
2006             # deprecated
2007             $query =~ tr/;/&/;
2008             my $params = Mojo::Parameters->new($query);
2009             return $self->redirect_to($self->url_for("char" => {lang => $lang})->query(@$params));
2010             }
2011             $self->render(template => "index.$lang");
2012             } => "main";
2013              
2014             get "/help" => "help";
2015              
2016             get "/hilfe" => "hilfe";
2017              
2018             get "/random" => sub {
2019             my $self = shift;
2020             $self->redirect_to($self->url_with("random" => {lang => lang($self)}));
2021             };
2022              
2023             get "/random/text/:lang" => sub {
2024             my $self = shift;
2025             my $char = init($self);
2026             my $lang = $self->param("lang");
2027             random_parameters($char, $lang, "portrait");
2028             compute_data($char, $lang);
2029             $self->render(template => "text.$lang",
2030             format => "txt",
2031             char => $char);
2032             } => "text";
2033              
2034             get "/random/:lang" => [lang => qr/(?:en|de)/] => sub {
2035             my $self = shift;
2036             my $char = init($self);
2037             my $lang = $self->param("lang");
2038             random_parameters($char, $lang, "portrait");
2039             compute_data($char, $lang);
2040             my $svg = svg_transform($self, svg_read($char));
2041             $self->render(format => "svg",
2042             data => $svg->toString());
2043             } => "random";
2044              
2045             get "/char" => sub {
2046             my $self = shift;
2047             $self->redirect_to($self->url_with("char" => {lang => lang($self)}));
2048             };
2049              
2050             get "/char/:lang" => [lang => qr/(?:en|de)/] => sub {
2051             my $self = shift;
2052             my $char = init($self);
2053             my $lang = $self->param("lang");
2054             # no random parameters
2055             compute_data($char, $lang);
2056             my $svg = svg_transform($self, svg_read($char));
2057             $self->render(format => "svg",
2058             data => $svg->toString());
2059             } => "char";
2060              
2061             # deprecated
2062             get "/link/:lang" => [lang => qr/(?:en|de)/] => sub {
2063             my $self = shift;
2064             my $lang = $self->param("lang");
2065             my $query = $self->req->query_params;
2066             $query =~ tr/;/&/;
2067             my $params = Mojo::Parameters->new($query);
2068             $self->redirect_to($self->url_for("edit" => {lang => lang($self)})->query(@$params));
2069             };
2070              
2071             get "/edit" => sub {
2072             my $self = shift;
2073             $self->redirect_to(edit => {lang => lang($self)});
2074             };
2075              
2076             get "/edit/:lang" => [lang => qr/(?:en|de)/] => sub {
2077             my $self = shift;
2078             my $char = init($self);
2079             my $lang = $self->param("lang");
2080             $self->render(template => "edit.$lang",
2081             char => $char);
2082             } => "edit";
2083              
2084             get "/redirect" => sub {
2085             my $self = shift;
2086             $self->redirect_to($self->url_with("redirect" => {lang => lang($self)}));
2087             };
2088              
2089             get "/redirect/:lang" => [lang => qr/(?:en|de)/] => sub {
2090             my $self = shift;
2091             my $lang = $self->param("lang");
2092             my $input = $self->param("input");
2093             my $params = Mojo::Parameters->new;
2094             my $last;
2095             while ($input =~ /^([-a-z0-9]*): *(.*?)\r?$/gm) {
2096             if ($1 eq $last or $1 eq "") {
2097             $params->param($1 => $params->param($1) . "\\\\$2");
2098             } else {
2099             $params->append($1 => $2);
2100             $last = $1;
2101             }
2102             }
2103             $self->redirect_to($self->url_for("char" => {lang => $lang})->query($params));
2104             } => "redirect";
2105              
2106              
2107             get "/show" => sub {
2108             my $self = shift;
2109             my $char = init($self);
2110             my $svg = svg_show_id(svg_read($char));
2111             $self->render(format => "svg",
2112             data => $svg->toString());
2113             } => "show";
2114              
2115             get "/characters" => sub {
2116             my $self = shift;
2117             $self->redirect_to($self->url_with("characters" => {lang => lang($self)}));
2118             };
2119              
2120             get "/characters/:lang" => [lang => qr/(?:en|de)/] => sub {
2121             my $self = shift;
2122             my $lang = $self->param("lang");
2123             my $char = init($self);
2124             $self->render(template => "characters.$lang",
2125             width => "100%",
2126             characters => characters($char, $lang));
2127             } => "characters";
2128              
2129             get "/stats" => sub {
2130             my $self = shift;
2131             $self->redirect_to($self->url_with("stats" => {lang => lang($self),
2132             n => 100}));
2133             };
2134              
2135             get "/stats/:n" => [n => qr/\d+/] => sub {
2136             my $self = shift;
2137             my $n = $self->param("n");
2138             $self->redirect_to($self->url_with("stats" => {lang => lang($self),
2139             n => $n}));
2140             };
2141              
2142             get "/stats/:lang" => [lang => qr/(?:en|de)/] => sub {
2143             my $self = shift;
2144             my $lang = $self->param("lang");
2145             $self->redirect_to($self->url_with("stats" => {lang => $lang,
2146             n => 100}));
2147             };
2148              
2149             get "/stats/:lang/:n" => [lang => qr/(?:en|de)/, n => qr/\d+/] => sub {
2150             my $self = shift;
2151             my $lang = $self->param("lang");
2152             my $n = $self->param("n");
2153             my $char = init($self);
2154             $self->render(format => "txt",
2155             text => stats($char, $lang, $n));
2156             } => "stats";
2157              
2158             app->secrets([app->config("secret")]) if app->config("secret");
2159              
2160             app->start;
2161              
2162             __DATA__