Difference between revisions of "List of spells/parse-spl-data"
CommanderC (talk | contribs) (Import Neil's script) |
m (Propose deletion.) |
||
(7 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
+ | {{delete|No longer used}} | ||
<pre> | <pre> | ||
<nowiki> | <nowiki> | ||
Line 10: | Line 11: | ||
# | # | ||
# The latest version of this program may be found at: | # The latest version of this program may be found at: | ||
− | # http://crawl.chaosforge.org/ | + | # http://crawl.chaosforge.org/List_of_spells/parse-spl-data |
use strict; | use strict; | ||
Line 18: | Line 19: | ||
our $VERSION = "0.1.0"; | our $VERSION = "0.1.0"; | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
sub crawl_version { | sub crawl_version { | ||
+ | my ($indir) = @_; | ||
# Look for util/release_ver first | # Look for util/release_ver first | ||
if (open VERSION, "<", "${indir}util/release_ver") { | if (open VERSION, "<", "${indir}util/release_ver") { | ||
Line 139: | Line 40: | ||
my $range = shift; | my $range = shift; | ||
return "LOS" if $range eq "LOS_RADIUS"; | return "LOS" if $range eq "LOS_RADIUS"; | ||
− | return | + | return 6 if $range eq "TORNADO_RADIUS"; |
return "" if $range eq "-1"; | return "" if $range eq "-1"; | ||
return $range; | return $range; | ||
Line 166: | Line 67: | ||
} | } | ||
+ | sub ucfirst_array { | ||
+ | my $items = join ",", map { | ||
+ | my $name = ucfirst(lc $_); | ||
+ | "\"$name\""; | ||
+ | } @_; | ||
+ | "{ $items }"; | ||
+ | } | ||
# Add a spell if it exists | # Add a spell if it exists | ||
− | sub maybe_add_spell | + | sub maybe_add_spell { |
− | my $spell = | + | my ($spell, $by_id, $by_key, $sortkey) = @_; |
return undef unless $spell->{name}; | return undef unless $spell->{name}; | ||
Line 179: | Line 87: | ||
# Sanity checks: abort if we encounter one of these, as it means | # Sanity checks: abort if we encounter one of these, as it means | ||
# something funny is up with our book-parsing. | # something funny is up with our book-parsing. | ||
− | + | ||
# Spells that belong to certain monster-only schools. | # Spells that belong to certain monster-only schools. | ||
for my $school (@{$spell->{schools}}) { | for my $school (@{$spell->{schools}}) { | ||
Line 194: | Line 102: | ||
# It's worth keeping. | # It's worth keeping. | ||
− | $by_id{$spell->{id}} = $spell; | + | $by_id->{$spell->{id}} = $spell; |
− | + | if ($sortkey eq "alpha") { | |
− | + | $by_key->{$spell->{name}} = $spell; | |
− | + | } elsif ($sortkey eq "letter") { | |
− | + | my $let = uc(substr $spell->{name}, 0, 1); | |
− | + | push @{$by_key->{$let}}, $spell; | |
− | + | } elsif ($sortkey eq "level") { | |
− | + | push @{$by_key->{$spell->{level}}}, $spell; | |
− | + | } elsif ($sortkey eq "school") { | |
− | + | for my $school (@{$spell->{schools}}) { | |
− | + | push @{$by_key->{$school}}, $spell; | |
− | + | } | |
− | + | } elsif ($sortkey eq "flag") { | |
− | + | for my $flag (@{$spell->{flags}}) { | |
+ | push @{$by_key->{$flag}}, $spell; | ||
+ | } | ||
+ | } | ||
return 1; | return 1; | ||
} | } | ||
Line 229: | Line 140: | ||
### MAIN | ### MAIN | ||
− | my $sortkey = "alpha"; | + | |
− | my $sortfn = \&sort_by_name; | + | sub main { |
− | GetOptions( | + | # Input directory. |
− | + | my $indir = "./"; | |
− | + | ||
− | + | # Map from letter, school, flag, book name, or level to list of spells. | |
− | + | my %by_key = (); | |
− | + | my %by_letter = (); | |
− | + | my %by_school = (); | |
− | + | my %by_flag = (); | |
+ | my %by_book = (); | ||
+ | my %by_level = (); | ||
+ | |||
+ | # Map from ID or name to spell | ||
+ | my %by_id = (); | ||
+ | my %by_name = (); | ||
+ | |||
+ | # Map from spell name to list of book names. | ||
+ | my %book_spells = (); | ||
+ | |||
+ | |||
+ | |||
+ | my $sortkey = "alpha"; | ||
+ | my $sortfn = \&sort_by_name; | ||
+ | my $module = "default"; | ||
+ | |||
+ | GetOptions( | ||
+ | "a|alphabetic" => sub { $sortkey = "letter"; $sortfn = \&sort_by_name; }, | ||
+ | "b|book" => sub { $sortkey = "book"; $sortfn = undef; }, | ||
+ | "f|flag" => sub { $sortkey = "flag"; $sortfn = \&sort_by_level; }, | ||
+ | "l|level" => sub { $sortkey = "level"; $sortfn = \&sort_by_school; }, | ||
+ | "s|school" => sub { $sortkey = "school"; $sortfn = \&sort_by_level; }, | ||
+ | "m|module-book" => sub { $module = "book"; $sortkey = "book" ; $sortfn = undef; }, | ||
+ | "k|module-spell" => sub { $module = "spell"; $sortkey = "letter"; $sortfn = \&sort_by_name; }, | ||
+ | "h|help" => sub { | ||
+ | print <<"EOF"; | ||
Usage: $0 [options] [directory] | Usage: $0 [options] [directory] | ||
Line 245: | Line 182: | ||
Options include: | Options include: | ||
− | -a, --alphabetic | + | -a, --alphabetic Arrange alphabetically (default). |
− | -b, --book | + | -b, --book Arrange by book, then level. |
− | -f, --flag | + | -f, --flag Arrange by flag, then level. |
− | -l, --level | + | -k, --module-spell Generate a Lua table of spells. |
− | -s, --school | + | -l, --level Arrange by level, then schools. |
− | -h, --help | + | -m, --module-book Generate a Lua table of spellbooks. |
+ | -s, --school Arrange by school, then level. | ||
+ | -h, --help Display this help. | ||
Spells of the same schools and level are sorted by name. With the --book, | Spells of the same schools and level are sorted by name. With the --book, | ||
--school, and --flag option, spells may appear multiple times. | --school, and --flag option, spells may appear multiple times. | ||
EOF | EOF | ||
− | + | exit 0; | |
− | + | }, | |
− | ); | + | ); |
+ | |||
+ | if (@ARGV) { | ||
+ | $indir = shift @ARGV; | ||
+ | $indir .= "/" unless $indir eq "" or $indir =~ m!/$!; | ||
+ | } | ||
+ | |||
+ | $DATE = gmtime; | ||
+ | $CRAWL_VERSION = crawl_version($indir); | ||
+ | ($PROGNAME = $0) =~ s!.*/!!; | ||
+ | |||
+ | parse_book_data($indir, \%book_spells, \%by_book); | ||
+ | parse_spl_data($indir, \%book_spells, \%by_id, \%by_key, $sortkey); | ||
+ | for my $k (keys %by_book) { | ||
+ | # Convert the list of spell ids to a list of spells, but | ||
+ | # remove those that aren't in %by_id because maybe_add_spell | ||
+ | # skipped them. | ||
+ | my @spells = map { $by_id{$_} || () } @{$by_book{$k}}; | ||
+ | if (@spells) { | ||
+ | $by_book{$k} = \@spells; | ||
+ | } else { | ||
+ | # Remove the book if it has no spells. | ||
+ | delete $by_book{$k}; | ||
+ | } | ||
+ | } | ||
− | if ( | + | # Print data |
− | + | if ($module eq "default") { | |
− | + | if ($sortkey eq "book") { | |
+ | module_default($sortkey, $sortfn, \%by_book); | ||
+ | } else { | ||
+ | module_default($sortkey, $sortfn, \%by_key); | ||
+ | } | ||
+ | } elsif ($module eq "book") { | ||
+ | module_book(\%by_book); | ||
+ | } elsif ($module eq "spell") { | ||
+ | module_spell($sortfn, \%by_key); | ||
+ | } | ||
} | } | ||
− | $ | + | sub parse_book_data { |
− | $ | + | my ($indir, $book_spells, $by_book) = @_; |
− | + | # Name of current book. | |
− | + | my $book = "bug" ; | |
+ | open BOOKS, "${indir}book-data.h" | ||
+ | or die "could not open ${indir}book-data.h: $!"; | ||
+ | while (<BOOKS>) { | ||
+ | # Skip conditional sections. Really we need to look at | ||
+ | # the condition, but that requires the C preprocessor and | ||
+ | # I'd rather avoid that. | ||
− | # | + | #next if /^#if/../^#endif/; |
− | + | next if /^#if TAG_MAJOR_VERSION == 34/../^#endif/; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | # Kind of hackish --- quit at the first rod | |
− | + | last if m!// Rod!; | |
− | + | if (m!^{\s*// (.*)!) { | |
− | + | # Get the spell name from the comment. | |
− | + | $book = $1; | |
− | + | # Disambiguation for Conjuration | |
− | + | my $extra = ""; | |
− | + | # Remove parenthesized bits | |
− | + | $book =~ s/\s+\([^)]*\)//g; | |
− | + | # Remove extra description | |
− | + | if ($book =~ s/ - (.*)//) { | |
− | + | # But remember it for Conjuration | |
− | + | if ($1 =~ /Fire and Earth/) { | |
− | + | $extra = " (fire+earth)"; | |
− | + | } elsif ($1 =~ /Air and Ice/) { | |
− | + | $extra = " (ice+air)"; | |
− | + | } | |
− | + | } | |
− | + | # Remove roman numeral counter | |
− | + | $book =~ s/\s+[IVX]+$//; | |
− | + | # Replace "Tome of" with "Book of" (special case for Dragon) | |
− | + | $book =~ s/^Tome of/Book of/; | |
− | + | # And special-case Minor Magic. | |
− | + | $book =~ s/^Minor/Book of Minor/; | |
− | + | # Append disambiguation | |
− | + | $book .= $extra; | |
− | + | } else { | |
− | + | while (/SPELL_(\w+)/g) { | |
− | + | push @{$book_spells->{$1}}, $book; | |
− | + | push @{$by_book->{$book}}, $1; | |
− | + | } | |
− | + | } | |
+ | } | ||
+ | close BOOKS; | ||
} | } | ||
− | |||
− | # Current spell. Members are | + | sub parse_spl_data { |
− | # {name} : Spell name | + | # Current spell. Members are |
− | # {id} : SPELL_* identifier (without the SPELL_) | + | # {name} : Spell name |
− | # {schools} : List of SPTYP_* constants (without the SPTYP_) | + | # {id} : SPELL_* identifier (without the SPELL_) |
− | # {flags} : List of SPFLAG_* constants (without the SPFLAG_) | + | # {schools} : List of SPTYP_* constants (without the SPTYP_) |
− | # {books} : List of book names. | + | # {flags} : List of SPFLAG_* constants (without the SPFLAG_) |
− | # {level} : Level | + | # {books} : List of book names. |
− | # {cap} : Power cap | + | # {level} : Level |
− | # {minrange}: Minimum range, or "LOS" or "" | + | # {cap} : Power cap |
− | # {maxrange}: Maximum range, or "LOS" or "" | + | # {minrange}: Minimum range, or "LOS" or "" |
− | # {noisy} : Noise modifier | + | # {maxrange}: Maximum range, or "LOS" or "" |
− | # | + | # {noisy} : Noise modifier |
− | # {data} : List of extra data; converted to {level}-{noise} at the end | + | # |
− | # of the spell block. | + | # {data} : List of extra data; converted to {level}-{noise} at the end |
− | my $spell = {}; | + | # of the spell block. |
− | open SPELLS, "${indir}spl-data.h" | + | my ($indir, $book_spells, $by_id, $by_key, $sortkey) = @_; |
− | + | my $spell = {}; | |
− | while (<SPELLS>) { | + | open SPELLS, "${indir}spl-data.h" |
− | + | or die "could not open ${indir}spl-data.h: $!"; | |
− | + | while (<SPELLS>) { | |
− | + | chomp; | |
− | + | if (/^{/) { | |
− | + | $spell = {}; | |
− | + | } elsif (/^}/) { | |
− | + | # Unpack data | |
− | + | my ( | |
+ | $sch, $flag, $lev, $cap, $minr, $maxr, $nm, @rest | ||
+ | ) = @{$spell->{data}}; | ||
− | + | # Parse out schools and flags | |
− | + | $spell->{schools} = [ | |
− | + | map { s/SPTYP_//; $_ } split /\s*\|\s*/, $sch | |
− | + | ]; | |
− | + | $spell->{flags} = [ | |
− | + | map { s/SPFLAG_//; $_ } split /\s*\|\s*/, $flag | |
− | + | ]; | |
− | + | # Include the rest of the data | |
− | + | $spell->{level} = $lev; | |
− | + | $spell->{cap} = $cap; | |
− | + | $spell->{minrange} = xlate_range $minr; | |
− | + | $spell->{maxrange} = xlate_range $maxr; | |
− | + | $spell->{noisy} = $nm; | |
− | + | maybe_add_spell($spell, $by_id, $by_key, $sortkey); | |
− | + | } elsif (/^\s*SPELL_(\w+),\s+"([^"]*)",/) { | |
− | + | $spell->{id} = $1; | |
− | + | $spell->{name} = $2; | |
− | + | if (exists $book_spells->{$1}) { | |
− | + | $spell->{books} = [ @{$book_spells->{$1}} ]; | |
− | + | } else { | |
− | + | $spell->{books} = []; | |
− | + | } | |
− | + | } else { | |
− | + | # Strip comments first. | |
− | + | s!\s*//.*!!; | |
− | + | # Get comma-delimited sections | |
− | + | while (/\s*([^,]+)(,|$)/g) { | |
− | + | if (substr($1, 0, 1) eq "|") { | |
# Continuation line; really we should check | # Continuation line; really we should check | ||
# whether the previous line ended with a | # whether the previous line ended with a | ||
# comma, but this is probably good enough. | # comma, but this is probably good enough. | ||
− | + | $spell->{data}[-1] .= $1 | |
− | + | } else { | |
− | + | push @{$spell->{data}}, $1; | |
− | + | } | |
− | + | } | |
− | + | } | |
+ | } | ||
+ | close SPELLS; | ||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | print <<"EOF"; | + | sub module_default { |
+ | my ($sortkey, $sortfn, $by_key) = @_; | ||
+ | # Descriptions of spell flags | ||
+ | my %flag_descs = ( | ||
+ | ALLOW_SELF => <<"EOF", | ||
+ | The spell is not helpful, but you will not receive a "Really target yourself?" | ||
+ | prompt. You may still receive "That beam is likely to hit you." for | ||
+ | "[[#Dir or target|dir or target]]" spells such as Mephitic Cloud. | ||
+ | EOF | ||
+ | AREA => <<"EOF", | ||
+ | The spell harms an area. Pacified fleeing monsters will not use emergency | ||
+ | spells with this flag. | ||
+ | EOF | ||
+ | BATTLE => <<"EOF", | ||
+ | The spell is a non-[[Conjuration]] spell disliked by [[Elyvilon]]. There is no | ||
+ | piety penalty for using such spells, but a randart spellbook containing one of | ||
+ | these spells will never have Elyvilon's name on it. | ||
+ | EOF | ||
+ | CHAOTIC => <<"EOF", | ||
+ | The spell is hated by [[Zin]], who will impose penance on any player casting | ||
+ | it, and reward killing monsters who can cast it. | ||
+ | EOF | ||
+ | CORPSE_VIOLATING => <<"EOF", | ||
+ | The spell is hated by [[Fedhas Madash]], who will impose penance on any player | ||
+ | casting it. | ||
+ | EOF | ||
+ | DIR => <<"EOF", | ||
+ | The spell requires choosing a direction (and not a target). | ||
+ | EOF | ||
+ | DIR_OR_TARGET => <<"EOF", | ||
+ | The spell requires choosing a direction or target, and is stopped by | ||
+ | interposing creatures. | ||
+ | EOF | ||
+ | ESCAPE => <<"EOF", | ||
+ | The spell helps you get out of trouble. Xom considers such spells boring, and | ||
+ | will not gift spellbooks containing them. Furthermore, the spell is an option | ||
+ | when control-clicking yourself in tiles mode. | ||
+ | EOF | ||
+ | GRID => <<"EOF", | ||
+ | The spell targets a grid square, disregarding any creatures in the way. This | ||
+ | is a form of smite targeting that does not require a target. | ||
+ | EOF | ||
+ | HASTY => <<"EOF", | ||
+ | The spell is hated by [[Cheibriados]], who will impose penance on any player | ||
+ | casting it. | ||
+ | EOF | ||
+ | HELPFUL => <<"EOF", | ||
+ | The spell helps you or the target; if targeted, the targeting commands | ||
+ | cycle through friendlies rather than hostiles. Xom considers such spells | ||
+ | boring, and will not gift spellbooks containing them. | ||
+ | EOF | ||
+ | NEUTRAL => <<"EOF", | ||
+ | The spell is neither harmful nor helpful; if targeted, the targeting | ||
+ | commands cycle through all creatures, not just hostiles. Xom considers | ||
+ | such spells boring, and will not gift spellbooks containing them. | ||
+ | EOF | ||
+ | NONE => <<"EOF", | ||
+ | The spell has no special flags. Such spells are always untargeted. | ||
+ | EOF | ||
+ | NOT_SELF => <<"EOF", | ||
+ | The spell may not target you or your square. | ||
+ | EOF | ||
+ | RECOVERY => <<"EOF", | ||
+ | The spell helps you recover from ill effects. Xom considers such spells | ||
+ | boring, and will not gift spellbooks containing them. Furthermore, the spell | ||
+ | is an option when control-clicking yourself in tiles mode. | ||
+ | EOF | ||
+ | TARGET => <<"EOF", | ||
+ | The spell targets a creature, disregarding any other creatures in the way. | ||
+ | This is a form of smite targeting that requires a target creature. | ||
+ | EOF | ||
+ | TARG_OBJ => <<"EOF", | ||
+ | The spell targets an object, disregarding any other creatures in the way. | ||
+ | This is a form of smite targeting that requires a target object. | ||
+ | EOF | ||
+ | UNCLEAN => <<"EOF", | ||
+ | The spell is hated by [[Zin]], who will impose penance on any player casting | ||
+ | it, and reward killing monsters who can cast it. | ||
+ | EOF | ||
+ | UNHOLY => <<"EOF", | ||
+ | The spell is a non-necromantic unholy spell. It is hated by [[good]] gods | ||
+ | ([[Elyvilon]], [[The Shining One]], and [[Zin]]), who will impose penance on | ||
+ | any player casting it. | ||
+ | EOF | ||
+ | ); | ||
+ | print <<"EOF"; | ||
==Spells== <!-- We *must* have a heading before the table, or the TOC will end up inside the table! --> | ==Spells== <!-- We *must* have a heading before the table, or the TOC will end up inside the table! --> | ||
<!-- Automatically generated by $PROGNAME $VERSION | <!-- Automatically generated by $PROGNAME $VERSION | ||
from Dungeon Crawl Stone Soup version $CRAWL_VERSION | from Dungeon Crawl Stone Soup version $CRAWL_VERSION | ||
− | |||
--> | --> | ||
{| class="prettytable" | {| class="prettytable" | ||
+ | !rowspan=2|Image | ||
!rowspan=2|Name | !rowspan=2|Name | ||
!rowspan=2|Schools | !rowspan=2|Schools | ||
Line 424: | Line 472: | ||
EOF | EOF | ||
− | + | # TODO: allow sorting by other criteria | |
− | + | for my $key (sort keys %{$by_key}) { | |
− | + | my @spells = @{$by_key->{$key}}; | |
− | + | @spells = sort $sortfn @spells if $sortfn; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | # TODO: allow sorting by other criteria | ||
− | for my $key (sort keys %by_key) { | ||
− | |||
− | |||
− | + | print "|----\n! colspan=9 style=\"text-align:left\"|\n===="; | |
− | + | # Format and link the key appropriately | |
− | + | if ($sortkey eq "book") { | |
− | + | print "[[$key]]"; | |
− | + | } elsif ($sortkey eq "school") { | |
− | + | print format_schools $key; | |
− | + | } elsif ($sortkey eq "level") { | |
− | + | print "level $key"; | |
− | + | } elsif ($sortkey eq "flag") { | |
− | + | my $fl = ucfirst lc $key; | |
− | + | $fl =~ s/_/ /g; | |
− | + | print $fl; | |
− | + | } else { | |
− | + | print $key; | |
− | + | } | |
− | + | print "====\n"; | |
− | + | if ($sortkey eq "flag") { | |
− | + | my $desc = $flag_descs{$key}; | |
− | + | if ($desc) { | |
− | + | $desc =~ s/\n/ /g; | |
− | + | print "|----\n| colspan=9|$desc\n"; | |
− | + | } | |
− | + | } | |
− | + | for my $spell (@spells) { | |
− | + | # Format schools and flags | |
− | + | my $schools = format_schools @{$spell->{schools}}; | |
− | + | my $flags = join ", ", map { | |
− | + | s/_/ /g; lc $_ | |
− | + | } @{$spell->{flags}}, ($spell->{noisy} ? "noise $spell->{noisy}" : ()); | |
− | + | my $books = join "<br>", map { "[[$_]]" } @{$spell->{books}}; | |
− | + | print <<"EOF"; | |
|---- | |---- | ||
+ | |[[File:{{lc:$spell->{name}.png}}]] | ||
|style="padding-left:1em"|[[$spell->{name}]] | |style="padding-left:1em"|[[$spell->{name}]] | ||
|$schools | |$schools | ||
Line 487: | Line 523: | ||
|$books | |$books | ||
EOF | EOF | ||
− | + | } | |
− | } | + | } |
− | print <<"EOF"; | + | print <<"EOF"; |
|---- | |---- | ||
|} | |} | ||
EOF | EOF | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | sub module_book { | ||
+ | my ($by_book) = @_; | ||
+ | print <<"EOF"; | ||
+ | --[=[ | ||
+ | Table of spellbooks | ||
+ | ]=]-- | ||
+ | |||
+ | local m = {} | ||
+ | EOF | ||
+ | |||
+ | my @letters = qw(a b c d e f g); | ||
+ | my $i = 0; | ||
+ | for my $key (sort keys %$by_book) { | ||
+ | print "m[\"$key\"] = {\n"; | ||
+ | my @spells = @{$by_book->{$key}}; | ||
+ | $i = 0; | ||
+ | for my $spell (@spells) { | ||
+ | my $schools = format_schools @{$spell->{schools}}; | ||
+ | print " {\n"; | ||
+ | print " [\"letter\"] = \"$letters[$i++]\", \n"; | ||
+ | print " [\"name\"] = \"$spell->{name}\", \n"; | ||
+ | my $lc_name = lc($spell->{name}); | ||
+ | print " [\"image\"] = \"[[File:${lc_name}.png]]\", \n"; | ||
+ | print " [\"level\"] = \"$spell->{level}\", \n"; | ||
+ | print " [\"schools\"] = \"$schools\", \n"; | ||
+ | print " },\n"; | ||
+ | } | ||
+ | print "}\n" | ||
+ | } | ||
+ | print "return m\n"; | ||
+ | } | ||
+ | |||
+ | sub module_spell { | ||
+ | my ($sortfn, $by_letter) = @_; | ||
+ | # Table of spells | ||
+ | print <<"EOF"; | ||
+ | --[=[ | ||
+ | Table of spells | ||
+ | ]=]-- | ||
+ | |||
+ | local m = {} | ||
+ | EOF | ||
+ | |||
+ | for my $key (sort keys %$by_letter) { | ||
+ | my @spells = @{$by_letter->{$key}}; | ||
+ | @spells = sort $sortfn @spells if $sortfn; | ||
+ | for my $spell (@spells) { | ||
+ | my $schools = ucfirst_array @{$spell->{schools}}; | ||
+ | my $flags = ucfirst_array @{$spell->{flags}}; | ||
+ | $flags =~ s/_/ /g; | ||
+ | $flags =~ s/Mr check/MR check/; # special case | ||
+ | $flags =~ s/^{ \"None\" }$/nil/; # special case | ||
+ | my $books = join ",", map { "\"$_\"" } @{$spell->{books}}; | ||
+ | my $range; | ||
+ | if ($spell->{minrange} eq "") { | ||
+ | $range = "nil"; | ||
+ | } elsif ($spell->{minrange} eq "LOS") { | ||
+ | $range = "\"LOS\""; | ||
+ | } elsif ($spell->{minrange} eq $spell->{maxrange}) { | ||
+ | $range = $spell->{minrange}; | ||
+ | } else { | ||
+ | $range = "{$spell->{minrange}, $spell->{maxrange}}"; | ||
+ | } | ||
+ | print "m[\"$spell->{name}\"] = {\n"; | ||
+ | print " [\"schools\"] = $schools, \n"; | ||
+ | print " [\"flags\"] = $flags, \n"; | ||
+ | print " [\"books\"] = { $books }, \n"; | ||
+ | print " [\"level\"] = $spell->{level}, \n"; | ||
+ | print " [\"cap\"] = $spell->{cap}, \n"; | ||
+ | print " [\"range\"] = $range, \n"; | ||
+ | print " [\"noise\"] = $spell->{noisy}, \n"; | ||
+ | print "}\n"; | ||
+ | } | ||
+ | } | ||
+ | print "return m\n"; | ||
+ | } | ||
+ | |||
+ | main; | ||
+ | |||
</nowiki> | </nowiki> | ||
</pre> | </pre> |
Latest revision as of 11:31, 3 November 2016
A user has suggested the deletion of this page. Reason: No longer used
#! /usr/bin/perl -w # parse-spl-data, by http://crawl.chaosforge.org/index.php?title=User:Neil # Copyright (C) 2011. No rights reserved. # # You may use, distribute, modify, study, fold, spindle, or mutilate this # software as you see fit, but know that there is NO WARRANTY, EXPRESS # OR IMPLIED (to the extent permitted by law). # # The latest version of this program may be found at: # http://crawl.chaosforge.org/List_of_spells/parse-spl-data use strict; use Getopt::Long qw(:config gnu_getopt); our ($PROGNAME, $DATE, $CRAWL_VERSION); our $VERSION = "0.1.0"; sub crawl_version { my ($indir) = @_; # Look for util/release_ver first if (open VERSION, "<", "${indir}util/release_ver") { my $ver = <VERSION>; chomp $ver; close VERSION; return $ver; } # TODO: maybe try running git return "<unknown>"; } # Translate a range into something friendlier for display. -1 (no range) # becomes the empty string, while "LOS_RADIUS" becomes just LOS. # TORNADO_RADIUS becomes 5 (its value), but maybe we should use "Tornado" # instead. sub xlate_range { my $range = shift; return "LOS" if $range eq "LOS_RADIUS"; return 6 if $range eq "TORNADO_RADIUS"; return "" if $range eq "-1"; return $range; } # Wiki-format a list of spell schools, linking to the corresponding # magic skill. sub format_schools { join "/", map { my $school = ucfirst(lc $_); # Convert school names to skill names for linking my $skill = $school; if ($school =~ /^(Poison|Air|Fire|Ice|Earth)$/) { $skill = "$school Magic"; } elsif ($school !~ /[ys]$/) { # "Necromancy" isn't pluralised as a skill, # and "Hexes" and "Charms" are already # pluralized as a magic school. The others # are singular as a school, plural as a skill. $skill = "${school}s"; } $skill eq $school ? "[[$school]]" : "[[$skill|$school]]"; } @_; } sub ucfirst_array { my $items = join ",", map { my $name = ucfirst(lc $_); "\"$name\""; } @_; "{ $items }"; } # Add a spell if it exists sub maybe_add_spell { my ($spell, $by_id, $by_key, $sortkey) = @_; return undef unless $spell->{name}; # Ignore spells that do not occur in any book return "" unless scalar @{$spell->{books}}; # ..and NO_SPELL, which "occurs" in books but not really. return "" if $spell->{id} eq "NO_SPELL"; # Sanity checks: abort if we encounter one of these, as it means # something funny is up with our book-parsing. # Spells that belong to certain monster-only schools. for my $school (@{$spell->{schools}}) { die "SPTYP_NONE for $spell->{name}" if $school eq "NONE"; } # Spells that don't belong to a school at all, even SPTYP_NONE. die "No school for $spell->{name}" unless scalar @{$spell->{schools}}; # Monster and testing spells. for my $flag (@{$spell->{flags}}) { die "Monster spell $spell->{name}" if $flag eq "MONSTER"; die "Testing spell $spell->{name}" if $flag eq "TESTING"; } # It's worth keeping. $by_id->{$spell->{id}} = $spell; if ($sortkey eq "alpha") { $by_key->{$spell->{name}} = $spell; } elsif ($sortkey eq "letter") { my $let = uc(substr $spell->{name}, 0, 1); push @{$by_key->{$let}}, $spell; } elsif ($sortkey eq "level") { push @{$by_key->{$spell->{level}}}, $spell; } elsif ($sortkey eq "school") { for my $school (@{$spell->{schools}}) { push @{$by_key->{$school}}, $spell; } } elsif ($sortkey eq "flag") { for my $flag (@{$spell->{flags}}) { push @{$by_key->{$flag}}, $spell; } } return 1; } # Sorting functions sub sort_by_name { $a->{name} cmp $b->{name} } sub sort_by_level { $a->{level} <=> $b->{level} or sort_by_name } sub sort_by_school { # A Schwartzian transform would be more efficient, but we have # few enough spells that it's not necessary. join("/", @{$a->{schools}}) cmp join("/", @{$b->{schools}}) or sort_by_name } ### MAIN sub main { # Input directory. my $indir = "./"; # Map from letter, school, flag, book name, or level to list of spells. my %by_key = (); my %by_letter = (); my %by_school = (); my %by_flag = (); my %by_book = (); my %by_level = (); # Map from ID or name to spell my %by_id = (); my %by_name = (); # Map from spell name to list of book names. my %book_spells = (); my $sortkey = "alpha"; my $sortfn = \&sort_by_name; my $module = "default"; GetOptions( "a|alphabetic" => sub { $sortkey = "letter"; $sortfn = \&sort_by_name; }, "b|book" => sub { $sortkey = "book"; $sortfn = undef; }, "f|flag" => sub { $sortkey = "flag"; $sortfn = \&sort_by_level; }, "l|level" => sub { $sortkey = "level"; $sortfn = \&sort_by_school; }, "s|school" => sub { $sortkey = "school"; $sortfn = \&sort_by_level; }, "m|module-book" => sub { $module = "book"; $sortkey = "book" ; $sortfn = undef; }, "k|module-spell" => sub { $module = "spell"; $sortkey = "letter"; $sortfn = \&sort_by_name; }, "h|help" => sub { print <<"EOF"; Usage: $0 [options] [directory] Produce a wiki table of DCSS spells. The specified directory should contain spl-data.h an book-data.h. If omitted, the current directory is used. Options include: -a, --alphabetic Arrange alphabetically (default). -b, --book Arrange by book, then level. -f, --flag Arrange by flag, then level. -k, --module-spell Generate a Lua table of spells. -l, --level Arrange by level, then schools. -m, --module-book Generate a Lua table of spellbooks. -s, --school Arrange by school, then level. -h, --help Display this help. Spells of the same schools and level are sorted by name. With the --book, --school, and --flag option, spells may appear multiple times. EOF exit 0; }, ); if (@ARGV) { $indir = shift @ARGV; $indir .= "/" unless $indir eq "" or $indir =~ m!/$!; } $DATE = gmtime; $CRAWL_VERSION = crawl_version($indir); ($PROGNAME = $0) =~ s!.*/!!; parse_book_data($indir, \%book_spells, \%by_book); parse_spl_data($indir, \%book_spells, \%by_id, \%by_key, $sortkey); for my $k (keys %by_book) { # Convert the list of spell ids to a list of spells, but # remove those that aren't in %by_id because maybe_add_spell # skipped them. my @spells = map { $by_id{$_} || () } @{$by_book{$k}}; if (@spells) { $by_book{$k} = \@spells; } else { # Remove the book if it has no spells. delete $by_book{$k}; } } # Print data if ($module eq "default") { if ($sortkey eq "book") { module_default($sortkey, $sortfn, \%by_book); } else { module_default($sortkey, $sortfn, \%by_key); } } elsif ($module eq "book") { module_book(\%by_book); } elsif ($module eq "spell") { module_spell($sortfn, \%by_key); } } sub parse_book_data { my ($indir, $book_spells, $by_book) = @_; # Name of current book. my $book = "bug" ; open BOOKS, "${indir}book-data.h" or die "could not open ${indir}book-data.h: $!"; while (<BOOKS>) { # Skip conditional sections. Really we need to look at # the condition, but that requires the C preprocessor and # I'd rather avoid that. #next if /^#if/../^#endif/; next if /^#if TAG_MAJOR_VERSION == 34/../^#endif/; # Kind of hackish --- quit at the first rod last if m!// Rod!; if (m!^{\s*// (.*)!) { # Get the spell name from the comment. $book = $1; # Disambiguation for Conjuration my $extra = ""; # Remove parenthesized bits $book =~ s/\s+\([^)]*\)//g; # Remove extra description if ($book =~ s/ - (.*)//) { # But remember it for Conjuration if ($1 =~ /Fire and Earth/) { $extra = " (fire+earth)"; } elsif ($1 =~ /Air and Ice/) { $extra = " (ice+air)"; } } # Remove roman numeral counter $book =~ s/\s+[IVX]+$//; # Replace "Tome of" with "Book of" (special case for Dragon) $book =~ s/^Tome of/Book of/; # And special-case Minor Magic. $book =~ s/^Minor/Book of Minor/; # Append disambiguation $book .= $extra; } else { while (/SPELL_(\w+)/g) { push @{$book_spells->{$1}}, $book; push @{$by_book->{$book}}, $1; } } } close BOOKS; } sub parse_spl_data { # Current spell. Members are # {name} : Spell name # {id} : SPELL_* identifier (without the SPELL_) # {schools} : List of SPTYP_* constants (without the SPTYP_) # {flags} : List of SPFLAG_* constants (without the SPFLAG_) # {books} : List of book names. # {level} : Level # {cap} : Power cap # {minrange}: Minimum range, or "LOS" or "" # {maxrange}: Maximum range, or "LOS" or "" # {noisy} : Noise modifier # # {data} : List of extra data; converted to {level}-{noise} at the end # of the spell block. my ($indir, $book_spells, $by_id, $by_key, $sortkey) = @_; my $spell = {}; open SPELLS, "${indir}spl-data.h" or die "could not open ${indir}spl-data.h: $!"; while (<SPELLS>) { chomp; if (/^{/) { $spell = {}; } elsif (/^}/) { # Unpack data my ( $sch, $flag, $lev, $cap, $minr, $maxr, $nm, @rest ) = @{$spell->{data}}; # Parse out schools and flags $spell->{schools} = [ map { s/SPTYP_//; $_ } split /\s*\|\s*/, $sch ]; $spell->{flags} = [ map { s/SPFLAG_//; $_ } split /\s*\|\s*/, $flag ]; # Include the rest of the data $spell->{level} = $lev; $spell->{cap} = $cap; $spell->{minrange} = xlate_range $minr; $spell->{maxrange} = xlate_range $maxr; $spell->{noisy} = $nm; maybe_add_spell($spell, $by_id, $by_key, $sortkey); } elsif (/^\s*SPELL_(\w+),\s+"([^"]*)",/) { $spell->{id} = $1; $spell->{name} = $2; if (exists $book_spells->{$1}) { $spell->{books} = [ @{$book_spells->{$1}} ]; } else { $spell->{books} = []; } } else { # Strip comments first. s!\s*//.*!!; # Get comma-delimited sections while (/\s*([^,]+)(,|$)/g) { if (substr($1, 0, 1) eq "|") { # Continuation line; really we should check # whether the previous line ended with a # comma, but this is probably good enough. $spell->{data}[-1] .= $1 } else { push @{$spell->{data}}, $1; } } } } close SPELLS; } sub module_default { my ($sortkey, $sortfn, $by_key) = @_; # Descriptions of spell flags my %flag_descs = ( ALLOW_SELF => <<"EOF", The spell is not helpful, but you will not receive a "Really target yourself?" prompt. You may still receive "That beam is likely to hit you." for "[[#Dir or target|dir or target]]" spells such as Mephitic Cloud. EOF AREA => <<"EOF", The spell harms an area. Pacified fleeing monsters will not use emergency spells with this flag. EOF BATTLE => <<"EOF", The spell is a non-[[Conjuration]] spell disliked by [[Elyvilon]]. There is no piety penalty for using such spells, but a randart spellbook containing one of these spells will never have Elyvilon's name on it. EOF CHAOTIC => <<"EOF", The spell is hated by [[Zin]], who will impose penance on any player casting it, and reward killing monsters who can cast it. EOF CORPSE_VIOLATING => <<"EOF", The spell is hated by [[Fedhas Madash]], who will impose penance on any player casting it. EOF DIR => <<"EOF", The spell requires choosing a direction (and not a target). EOF DIR_OR_TARGET => <<"EOF", The spell requires choosing a direction or target, and is stopped by interposing creatures. EOF ESCAPE => <<"EOF", The spell helps you get out of trouble. Xom considers such spells boring, and will not gift spellbooks containing them. Furthermore, the spell is an option when control-clicking yourself in tiles mode. EOF GRID => <<"EOF", The spell targets a grid square, disregarding any creatures in the way. This is a form of smite targeting that does not require a target. EOF HASTY => <<"EOF", The spell is hated by [[Cheibriados]], who will impose penance on any player casting it. EOF HELPFUL => <<"EOF", The spell helps you or the target; if targeted, the targeting commands cycle through friendlies rather than hostiles. Xom considers such spells boring, and will not gift spellbooks containing them. EOF NEUTRAL => <<"EOF", The spell is neither harmful nor helpful; if targeted, the targeting commands cycle through all creatures, not just hostiles. Xom considers such spells boring, and will not gift spellbooks containing them. EOF NONE => <<"EOF", The spell has no special flags. Such spells are always untargeted. EOF NOT_SELF => <<"EOF", The spell may not target you or your square. EOF RECOVERY => <<"EOF", The spell helps you recover from ill effects. Xom considers such spells boring, and will not gift spellbooks containing them. Furthermore, the spell is an option when control-clicking yourself in tiles mode. EOF TARGET => <<"EOF", The spell targets a creature, disregarding any other creatures in the way. This is a form of smite targeting that requires a target creature. EOF TARG_OBJ => <<"EOF", The spell targets an object, disregarding any other creatures in the way. This is a form of smite targeting that requires a target object. EOF UNCLEAN => <<"EOF", The spell is hated by [[Zin]], who will impose penance on any player casting it, and reward killing monsters who can cast it. EOF UNHOLY => <<"EOF", The spell is a non-necromantic unholy spell. It is hated by [[good]] gods ([[Elyvilon]], [[The Shining One]], and [[Zin]]), who will impose penance on any player casting it. EOF ); print <<"EOF"; ==Spells== <!-- We *must* have a heading before the table, or the TOC will end up inside the table! --> <!-- Automatically generated by $PROGNAME $VERSION from Dungeon Crawl Stone Soup version $CRAWL_VERSION --> {| class="prettytable" !rowspan=2|Image !rowspan=2|Name !rowspan=2|Schools !rowspan=2|Level !rowspan=2|Power<br>cap ! colspan=2 |Range !rowspan=2|Flags !rowspan=2|Books |---- !min !max EOF # TODO: allow sorting by other criteria for my $key (sort keys %{$by_key}) { my @spells = @{$by_key->{$key}}; @spells = sort $sortfn @spells if $sortfn; print "|----\n! colspan=9 style=\"text-align:left\"|\n===="; # Format and link the key appropriately if ($sortkey eq "book") { print "[[$key]]"; } elsif ($sortkey eq "school") { print format_schools $key; } elsif ($sortkey eq "level") { print "level $key"; } elsif ($sortkey eq "flag") { my $fl = ucfirst lc $key; $fl =~ s/_/ /g; print $fl; } else { print $key; } print "====\n"; if ($sortkey eq "flag") { my $desc = $flag_descs{$key}; if ($desc) { $desc =~ s/\n/ /g; print "|----\n| colspan=9|$desc\n"; } } for my $spell (@spells) { # Format schools and flags my $schools = format_schools @{$spell->{schools}}; my $flags = join ", ", map { s/_/ /g; lc $_ } @{$spell->{flags}}, ($spell->{noisy} ? "noise $spell->{noisy}" : ()); my $books = join "<br>", map { "[[$_]]" } @{$spell->{books}}; print <<"EOF"; |---- |[[File:{{lc:$spell->{name}.png}}]] |style="padding-left:1em"|[[$spell->{name}]] |$schools |$spell->{level} |$spell->{cap} |$spell->{minrange} |$spell->{maxrange} |$flags |$books EOF } } print <<"EOF"; |---- |} EOF } sub module_book { my ($by_book) = @_; print <<"EOF"; --[=[ Table of spellbooks ]=]-- local m = {} EOF my @letters = qw(a b c d e f g); my $i = 0; for my $key (sort keys %$by_book) { print "m[\"$key\"] = {\n"; my @spells = @{$by_book->{$key}}; $i = 0; for my $spell (@spells) { my $schools = format_schools @{$spell->{schools}}; print " {\n"; print " [\"letter\"] = \"$letters[$i++]\", \n"; print " [\"name\"] = \"$spell->{name}\", \n"; my $lc_name = lc($spell->{name}); print " [\"image\"] = \"[[File:${lc_name}.png]]\", \n"; print " [\"level\"] = \"$spell->{level}\", \n"; print " [\"schools\"] = \"$schools\", \n"; print " },\n"; } print "}\n" } print "return m\n"; } sub module_spell { my ($sortfn, $by_letter) = @_; # Table of spells print <<"EOF"; --[=[ Table of spells ]=]-- local m = {} EOF for my $key (sort keys %$by_letter) { my @spells = @{$by_letter->{$key}}; @spells = sort $sortfn @spells if $sortfn; for my $spell (@spells) { my $schools = ucfirst_array @{$spell->{schools}}; my $flags = ucfirst_array @{$spell->{flags}}; $flags =~ s/_/ /g; $flags =~ s/Mr check/MR check/; # special case $flags =~ s/^{ \"None\" }$/nil/; # special case my $books = join ",", map { "\"$_\"" } @{$spell->{books}}; my $range; if ($spell->{minrange} eq "") { $range = "nil"; } elsif ($spell->{minrange} eq "LOS") { $range = "\"LOS\""; } elsif ($spell->{minrange} eq $spell->{maxrange}) { $range = $spell->{minrange}; } else { $range = "{$spell->{minrange}, $spell->{maxrange}}"; } print "m[\"$spell->{name}\"] = {\n"; print " [\"schools\"] = $schools, \n"; print " [\"flags\"] = $flags, \n"; print " [\"books\"] = { $books }, \n"; print " [\"level\"] = $spell->{level}, \n"; print " [\"cap\"] = $spell->{cap}, \n"; print " [\"range\"] = $range, \n"; print " [\"noise\"] = $spell->{noisy}, \n"; print "}\n"; } } print "return m\n"; } main;