#! /usr/pkg/bin/perl
# $Id: htmlgen.cgi,v 1.26 2002/12/11 23:58:49 ksulliva Exp $
#
# My translation of the following license: "Use this wherever and
# however you want, just be sure to give me credit. And if it breaks
# something, don't blame me." It's basically the BSD license.
#
# Copyright (C) 2000 Kevin Sullivan.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the project nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
# Include all of the modules we'll need
use FileHandle;
use CGI qw/:standard/;
use CGI::Carp;
use Text::Wrap;
use MIME::Base64;
use Digest::SHA1 qw(sha1 sha1_hex sha1_base64);
# Global tables.
# Table for physical/easy skills. Skill level is -1 or -2 for medium
# and hard skills
$physical_table = {
0 => -4,
0.5 => -1,
1 => 0,
2 => 1,
4 => 2,
8 => 3,
16 => 4,
24 => 5,
32 => 6,
40 => 7,
};
# Table for mental/easy skills. Skill level is -1 or -2 for medium
# and hard skills. Treat very-hard as hard with 1/2 the points.
$mental_table = {
0 => -4,
0.5 => -1,
1 => 0,
2 => 1,
4 => 2,
6 => 3,
8 => 4,
10 => 5,
12 => 6,
14 => 7,
16 => 8,
18 => 9,
20 => 10,
22 => 11,
24 => 12,
26 => 13,
28 => 14,
30 => 15,
32 => 16,
34 => 17,
36 => 18,
38 => 19,
40 => 20,
};
# Damage per ST. This is an array of pointers to arrays.
# $damage_table[ST][0] is thrust, $damage_table[ST][1] is swing.
@damage_table = (
[ "0" , "0" ], # ST = 0
[ "0" , "0" ], # ST = 1
[ "0" , "0" ], # ST = 2
[ "0" , "0" ], # ST = 3
[ "0" , "0" ], # ST = 4
[ "1d-5" , "1d-5" ], # ST = 5
[ "1d-4" , "1d-4" ], # ST = 6
[ "1d-3" , "1d-3" ], # ST = 7
[ "1d-3" , "1d-2" ], # ST = 8
[ "1d-2" , "1d-1" ], # ST = 9
[ "1d-2" , "1d" ], # ST = 10
[ "1d-1" , "1d+1" ], # ST = 11
[ "1d-1" , "1d+2" ], # ST = 12
[ "1d" , "2d-1" ], # ST = 13
[ "1d" , "2d" ], # ST = 14
[ "1d+1" , "2d+1" ], # ST = 15
[ "1d+1" , "2d+2" ], # ST = 16
[ "1d+2" , "3d-1" ], # ST = 17
[ "1d+2" , "3d" ], # ST = 18
[ "2d-1" , "3d+1" ], # ST = 19
[ "2d-1" , "3d+2" ] # ST = 20
);
# Here is where the program really begins.
$cgi = new CGI;
@names = $cgi->param;
#$hilite = '#E0E0FF';
# Errlist is a global list of problems.
@errlist = ();
if ($filename = $cgi->param('upfile')) {
# The file is being provided by the client
$fh = $cgi->upload('upfile');
} elsif ($filename = $cgi->param('local')) {
# The file is local to this machine
$oldfilename = $filename;
$filename =~ s/[^-\w.]//g;
# $filename =~ tr/[-A-Za-z0-9_.]//dc;
$fh = new FileHandle($filename, "r");
}
# Read in the character info
$char = &read_char($filename, $fh);
# Is there an embedded image?
&handle_image($char);
# Calculate all of the info we can
&calcstuff($char);
# Save a copy of the character if "save: yes"
&save_char($char);
# Print out the page
print $cgi->header;
&print_page($char);
exit;
# read_char(filename, filehandle): Read in a character.
sub read_char {
my ($filename, $fh) = @_;
my ($char) = {};
my ($key, $value, @values);
my (@template);
unless (defined $fh) {
$fh = new FileHandle($filename, "r");
}
while (defined($_ = <$fh>)) {
chomp;
push(@template, $_);
s/\s+$//;
# Skip comment and blank lines
next if /^#/;
next if /^$/;
# Clear out stuff in curly braces
s/\{.*?\}//g;
if (/^\@([\w\s]+):\s*(.*)$/) {
# form: @key: data1 / data2 ...
($key, $value) = ($1, $2);
$key =~ s/\s+//g;
$key = lc($key);
$char->{$key} = [] unless $char->{$key};
push(@{$char->{$key}}, [ split(m!\s*\/\s*!, $value) ]);
} elsif (/^\$([\w\s]+):\s*(.*)$/) {
# form: $key:
# data
# data
# $
$key = $1;
$key =~ s/\s+//g;
$key = lc($key);
$value = '';
while (defined($_ = <$fh>)) {
chomp;
push(@template, $_);
last if $_ =~ /\$\s*$/;
$value .= "\n" . $_;
}
$value =~ s/\r?\n\r?\n/
/g;
$value =~ s/\s+/ /g;
$char->{$key} = $value;
} elsif (/^([\w\s]+):\s*(.*)$/) {
# form: key: data1 / data2 ...
($key, $value) = ($1, $2);
$key =~ s/\s+//g;
$key = lc($key);
$char->{$key} = [ split(m!\s*\/\s*!, $value) ];
}
}
# Store the filename and the template data for later use
$char->{'filename'} = $filename;
$char->{'template'} = \@template;
return $char;
}
# print_page(char): Top-level routine for printing the page
sub print_page {
my ($char) = @_;
my ($date) = scalar(localtime(time));
my ($version) = ('$Revision: 1.26 $' =~ /Revision: (.*)\$/);
# header
print <<"EOT";
$char->{'name'}[0]
|
$char->{'name'}[0]
|
@{ [&nametable($char)] }
@{ [&stattable($char)] }
@{ [&movetable($char)] }
@{ [&damagetable($char)] }
@{ [&defensetable($char, 'pd')] }
@{ [&defensetable($char, 'dr')] }
@{ [&advantagetable($char, 'a')] }
@{ [&advantagetable($char, 'd')] }
|
@{ [&personaltable($char)] }
@{ [&encumbrancetable($char)] }
@{ [&defensetable($char, 'ad')] }
|
|
@{ [&trackingtable($char)] }
|
@{ [&weapontable($char)] }
@{ [&missiletable($char)] }
@{ [&advantagetable($char, 'q')] }
@{ [&summarytable($char)] }
|
@{ [&tableborder(&possesstable($char))] }
Character's background
@{ [wrap('', '', $char->{'background'} . " ")] }
Character's appearance
@{ [wrap('', '', $char->{'appearance'} . " ")] }
|
|
@{ [&tableborder(&skilltable($char))] }
|
|
@{ [&spelltable($char)] }
|
EOT
}
# nametable: Print the player's name, the race, and the profession
sub nametable {
my ($char) = @_;
return table({-border=>0,-width=>"100%",-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left'}, i('Player:')),
td({-align=>'right'}, $char->{'player'}[0]),
), "\n",
Tr(
td({-align=>'left'}, i('Race:')),
td({-align=>'right'}, $char->{'race'}[0]),
), "\n",
Tr(
td({-align=>'left'}, i('Profession:')),
td({-align=>'right'}, $char->{'profession'}[0]),
), "\n",
);
}
# stattable: Print the stats and senses
sub stattable {
my ($char) = @_;
return table( {-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-class=>'bbig'}, 'ST:'),
td({-align=>'right',-class=>'big'}, $char->{'st'}[0]),
td({-align=>'left',-width=>'30%'}, ' '),
td({-align=>'left'}, i('Vision:')),
td({-align=>'right'}, $char->{'vision'}[0])
), "\n",
Tr(
td({-align=>'left',-class=>'bbig'}, 'DX:'),
td({-align=>'right',-class=>'big'}, $char->{'dx'}[0]),
td({-align=>'left'}, ' '),
td({-align=>'left'}, i('Hearing:')),
td({-align=>'right'}, $char->{'hearing'}[0])
), "\n",
Tr(
td({-align=>'left',-class=>'bbig'}, 'IQ:'),
td({-align=>'right',-class=>'big'}, $char->{'iq'}[0]),
td({-align=>'left'}, ' '),
td({-align=>'left'}, i('Smell:')),
td({-align=>'right'}, $char->{'smell'}[0])
), "\n",
Tr(
td({-align=>'left',-class=>'bbig'}, 'HT'),
td({-align=>'right',-class=>'big'}, $char->{'ht'}[0]),
td({-align=>'left'}, ' '),
td({-align=>'left'}, i('Will:')),
td({-align=>'right'}, $char->{'will'}[0])
), "\n"
);
}
# movetable: print speed and move
sub movetable {
my ($char) = @_;
return table({-border=>0,-width=>"100%",-cellspacing=>0,-cellpadding=>0},
Tr(
td(i('Basic Speed')),
td({-align=>"right"}, $char->{'basicspeed'}[0])
), "\n",
Tr(
td(i('Move')),
td({-align=>"right"}, $char->{'move'}[0])
), "\n"
);
}
# damagetable: print basic damage
sub damagetable {
my ($char) = @_;
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td(i('Thrust damage:')),
td({-align=>'right'}, $char->{'thrust'}[0])
), "\n",
Tr(
td(i('Swing damage:')),
td({-align=>'right'}, $char->{'swing'}[0])
), "\n"
);
};
# defensetable: print AD, PD, or DR. This should probably be 3
# different routines. I'm not sure why I did it this way initially.
sub defensetable {
my($char, $type) = @_;
if ($type eq 'pd') {
return table ({-width => '100%',-cellspacing=>0,-cellpadding=>0},
'',
Tr(
td({-align=>'left',-colspan=>2,-class=>'bbig'}, 'Passive defense:'),
td({-align=>"right",-class=>'big'}, $char->{'pd'}[0]),
), '', "\n",
Tr(
td({-align=>'left'}, i('Armor:')),
td({-align=>'left'}, $char->{'armorpd'}[1]),
td({-align=>"right"}, $char->{'armorpd'}[0]),
), "\n",
Tr(
td({-align=>'left'}, i('Shield:')),
td({-align=>'left'}, $char->{'shieldpd'}[1]),
td({-align=>"right"}, $char->{'shieldpd'}[0]),
), "\n",
Tr(
td({-align=>'left'}, i('Other:')),
td({-align=>'left'}, $char->{'otherpd'}[1]),
td({-align=>"right"}, $char->{'otherpd'}[0]),
), "\n"
);
}
if ($type eq 'dr') {
return table ({-border=>0,-width => '100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-colspan=>2,-class=>'bbig'}, 'Damage resistance:'),
td({-align=>"right",-class=>'big'}, $char->{'dr'}[0]),
), "\n",
Tr(
td({-align=>'left'}, i('Armor:')),
td({-align=>'left'}, $char->{'armordr'}[1]),
td({-align=>"right"}, $char->{'armordr'}[0]),
), "\n",
Tr(
td({-align=>'left'}, i('Other:')),
td({-align=>'left'}, $char->{'otherdr'}[1]),
td({-align=>"right"}, $char->{'otherdr'}[0]),
), "\n"
);
}
if ($type eq 'ad') {
return table ({-border=>0,-width => '100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-class=>'bbig'}, 'Active defense'),
td({-align=>'right'}, ' '),
td({-align=>'right'}, b('+PD'))
), "\n",
Tr(
td({-align=>'left'}, i('Dodge:')),
td({-align=>'right'}, $char->{'dodge'}[0]),
td({-align=>"right"}, $char->{'dodge'}[1])
), "\n",
Tr(
td({-align=>'left'}, i('Parry:')),
td({-align=>'right'}, $char->{'parry'}[0]),
td({-align=>"right"}, $char->{'parry'}[1])
), "\n",
Tr(
td({-align=>'left'}, i('Block:')),
td({-align=>'right'}, $char->{'block'}[0]),
td({-align=>"right"}, $char->{'block'}[1])
), "\n"
);
}
};
# advantagetable: Print advantages, disadvantages, or quirks. The
# same routine handles all of them, since they are printed pretty much
# identically.
sub advantagetable {
my ($char, $type) = @_;
my ($header);
$header = ($type eq 'a') ? 'Advantages' :
(($type eq 'd') ? 'Disadvantages' : 'Quirks');
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>"left",-colspan=>2,-class=>'bbig'}, $header),
), "\n",
map { Tr(
td({-align=>"left"}, $_->[0] || " "),
td({-align=>"right"}, $_->[1])
), "\n"
} @{$char->{$type}}
);
};
# personaltable: Print personal information
sub personaltable {
my ($char) = @_;
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left'}, i('Age:')),
td({-align=>'right'}, $char->{'age'}[0]),
td(' '),
td({-align=>'left'}, i('Total points:')),
td({-align=>'right'}, $char->{'totalpoints'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Height:')),
td({-align=>'right'}, $char->{'height'}[0]),
td(' '),
td({-align=>'left'}, i('Unspent points:')),
td({-align=>'right'}, $char->{'unspentpoints'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Weight:')),
td({-align=>'right'}, $char->{'weight'}[0]),
td(' '),
td({-align=>'left'}, i('Money:')),
td({-align=>'right'}, $char->{'money'}[0])
), "\n"
);
};
# encumbrancetable: Calculate and print encumbrance
sub encumbrancetable {
my ($char) = @_;
my ($st) = $char->{'st'}[0];
# XXX This should really be calculated in calcstuff(), so that
# unusual cases can be handled. In the config file, I'd imagine
# "encumbrance: *2 / *4 / *6 / *12 / *20" as the default.
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-colspan=>2,-class=>'bbig'}, 'Encumbrance')
), "\n",
Tr(
td({-align=>'left'}, i("None (0)")),
td({-align=>'right'}, $st * 2)
), "\n",
Tr(
td({-align=>'left'}, i("Light (-1)")),
td({-align=>'right'}, $st * 4)
), "\n",
Tr(
td({-align=>'left'}, i("Medium (-2)")),
td({-align=>'right'}, $st * 6)
), "\n",
Tr(
td({-align=>'left'}, i("Heavy (-3)")),
td({-align=>'right'}, $st * 12)
), "\n",
Tr(
td({-align=>'left'}, i("X-Heavy (-4)")),
td({-align=>'right'}, $st * 20)
), "\n"
);
};
# weapontable: Print damage and skill for all weapons (including missiles)
sub weapontable {
my ($char) = @_;
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-class=>'bbig'}, 'Weapons'),
td({-align=>'center',-class=>'b'}, 'type'),
td({-align=>'center',-class=>'b'}, 'damage'),
td({-align=>'right',-class=>'b'}, 'skill'),
), "\n",
map { Tr(
td({-align=>'left'}, $_->[0]),
td({-align=>'center'}, $_->[1]),
td({-align=>'center'}, $_->[2]),
td({-align=>'right'}, $_->[3])
)
} (@{$char->{'w'}}, @{$char->{'m'}})
);
};
# missiletable: print missile weapon stats
sub missiletable {
my ($char) = @_;
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-class=>'bbig'}, 'Ranged weapons'),
td({-align=>'center',-class=>'b'}, 'SS'),
td({-align=>'center',-class=>'b'}, 'Acc'),
td({-align=>'right',-class=>'b'}, 'half'),
td({-align=>'right',-class=>'b'}, 'max')
), "\n",
map { Tr(
td({-align=>'left'}, $_->[0]),
td({-align=>'center'}, $_->[4]),
td({-align=>'center'}, $_->[5]),
td({-align=>'right'}, $_->[6]),
td({-align=>'right'}, $_->[7])
), "\n"
} @{$char->{'m'}}
);
};
# summarytable: print point summary
sub summarytable {
my ($char) = @_;
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>'left',-colspan=>2}, b('Point summary'))
), "\n",
Tr(
td({-align=>'left'}, i('Attributes')),
td({-align=>'right'}, $char->{'pointsattributes'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Advantages')),
td({-align=>'right'}, $char->{'pointsadvantages'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Disadvantages')),
td({-align=>'right'}, $char->{'pointsdisadvantages'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Quirks')),
td({-align=>'right'}, $char->{'pointsquirks'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Skills')),
td({-align=>'right'}, $char->{'pointsskills'}[0])
), "\n",
Tr(
td({-align=>'left'}, i('Spells')),
td({-align=>'right'}, $char->{'pointsspells'}[0])
), "\n"
);
};
# trackingtable: If an image is supplied, print it. Otherwise, print
# a table to keep track of HP and mana.
sub trackingtable {
my ($char) = @_;
if ($char->{'pictureurl'}[0]) {
return "
{pictureurl}[0]\">";
}
return table({-border=>1,-width=>'100%',-cellspacing=>0,-cellpadding=>0,-valign=>'top'},
Tr(
td({-width=>'50%',-align=>'center'}, b('HP')),
td({-width=>'50%',-align=>'center'}, b('Mana'))
), "\n",
Tr(
td({-height=>200}, ' '),
td({-height=>200}, ' ')
), "\n"
);
}
# skilltable: print skills
sub skilltable {
my ($char, $type) = @_;
my ($header);
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
"\n",
"",
Tr(
td({-align=>"left",-class=>'b'}, 'pts'),
td({-align=>"left",-class=>'bbig'}, 'Skill'),
td({-align=>"center",-class=>'b'}, 'type'),
td({-align=>"right",-class=>'b'}, 'level')
), "\n",
"",
"",
map { Tr(
td({-align=>"left"}, &pointxlate($_->[3])),
td({-align=>"left"}, $_->[0] || " "),
td({-align=>"center"}, $_->[1]),
td({-align=>"right"}, $_->[2])
), "\n"
} @{$char->{'s'}},
""
);
};
# spelltable: print spell list
sub spelltable {
my ($char, $type) = @_;
my ($header);
return '' unless @{$char->{'z'}};
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
"\n",
Tr({-valign=>"bottom"},
td({-align=>"left",-class=>'b'}, 'pts'),
td({-align=>"left",-class=>'bbig'}, 'Spell'),
td({-align=>"center",-class=>'b'}, 'college'),
td({-align=>"center",-class=>'b'}, 'time'),
td({-align=>"center",-class=>'b'}, 'cost'),
td({-align=>"center",-class=>'b'}, 'dur'),
td({-align=>"right",-class=>'b'}, 'level')
), "\n",
map { Tr(
td({-align=>"left"}, &pointxlate($_->[3])),
td({-align=>"left"}, $_->[0] || " "),
td({-align=>"center"}, $_->[1]),
td({-align=>"center"}, $_->[4]),
td({-align=>"center"}, $_->[5]),
td({-align=>"center"}, $_->[6]),
td({-align=>"right"}, $_->[2])
), "\n"
} @{$char->{'z'}}
);
};
# possesstable: print posessions
sub possesstable {
my ($char, $type) = @_;
my ($header);
$header = ($type eq 'a') ? 'Advantages' :
(($type eq 'd') ? 'Disadvantages' : 'Quirks');
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td({-align=>"left",-class=>'bbig'}, 'Possessions'),
td({-align=>"right",-class=>'b'}, 'cost'),
td({-align=>"right",-class=>'b'}, 'weight')
), "\n",
map { Tr(
td({-align=>"left"}, $_->[0] || " "),
td({-align=>"right"}, $_->[2]),
td({-align=>"right"}, $_->[1])
), "\n"
} @{$char->{'p'}}
);
};
# tableborder: test routine to print borders around some tables.
# Currently it's pretty much a no-op.
sub tableborder {
return table({-border=>0,-width=>'100%',-cellspacing=>0,-cellpadding=>0},
Tr(
td(@_)
)
);
}
# pointxlate: translate .5 and 1/2 into an HTML 1/2. Hey, it looks nicer.
sub pointxlate {
my ($pts) = @_;
# Translate "0.5"
$pts =~ s/^0\.5/½/;
# Translate "1.5" and ".5". Do "10.5" correctly!
$pts =~ s/\.5/½/;
# Translate "1/2" and "1 1/2"
$pts =~ s/\s*1\/2/½/;
return $pts;
}
# calcstuff: Calculate lotsa stuff.
sub calcstuff {
my ($char) = @_;
my ($sum, $totalsum, $ptr) = (0, 0, 0);
my ($runningskill, $magery);
# Total the points used for advantages, disadvantages, and quirks.
# Advantages
foreach $ptr (@{$char->{'a'}}) {
$char->{'pointsadvantages'}[0] += $ptr->[1];
# pull out magery
if ($ptr->[0] =~ /magery/i) {
if ($ptr->[0] =~ /(\d+)/) {
$magery = $1 + 0;
} elsif ($ptr->[0] =~ /([ivx]+)/i) {
$magery = &roman2num($1);
}
}
}
# Disadvantages
foreach $ptr (@{$char->{'d'}}) {
$char->{'pointsdisadvantages'}[0] += $ptr->[1];
}
# Quirks
foreach $ptr (@{$char->{'q'}}) {
$ptr->[1] = -1 if $ptr->[1] eq "";
$char->{'pointsquirks'}[0] += $ptr->[1];
}
# Total the points used for skills. Also, calculate skill levels
# where possible.
# Skills
foreach $ptr (@{$char->{'s'}}) {
next unless $ptr->[0];
$char->{'pointsskills'}[0] += $ptr->[3];
$ptr->[2] = &calcskill($char, $ptr->[1], $ptr->[3], 0)
unless $ptr->[2];
# Pull out running skill for later use
$running = $ptr->[2] if $ptr->[0] =~ /^running/i;
}
# Spell point totals and levels
# Spells
foreach $ptr (@{$char->{'z'}}) {
next unless $ptr->[0];
$char->{'pointsspells'}[0] += $ptr->[3];
$ptr->[2] = &calcskill($char,
($ptr->[0] =~ /(vh|path of)/i ? 'MV' : 'MH'),
$ptr->[3], $magery)
unless $ptr->[2];
}
# Keep a subtotal and a total for possessions.
# Possessions
my ($subweight, $totalweight);
foreach $ptr (@{$char->{'p'}}) {
if ($ptr->[0] =~ /^subtotal/i) {
$ptr->[1] = $subweight;
$subweight = 0;
} elsif ($ptr->[0] =~ /^total/i) {
$ptr->[1] = $totalweight;
} else {
$subweight += $ptr->[1];
$totalweight += $ptr->[1];
}
}
# Calculate speed and movement.
# Speed = (DX + HT) / 4
if ($char->{'basicspeed'}[0] eq '') {
$char->{'basicspeed'}[0] = ($char->{'dx'}[0] + $char->{'ht'}[0]) / 4;
}
# Move = Speed + Running/8 - Encumberance, all rounded down to an
# integer
if ($char->{'move'}[0] !~ /^\d/) {
$char->{'move'}[0] = int($char->{'move'}[0] + $char->{'basicspeed'}[0] + $running/8);
}
# Calculate senses
# XXX We should deal with advantages like Alertness and Acute Senses.
if ($char->{'vision'}[0] !~ /^\d+/) {
$char->{'vision'}[0] = int(($char->{'iq'}[0] + $char->{'ht'}[0])/2)
+ $char->{'vision'}[0];
}
if ($char->{'hearing'}[0] !~ /^\d+/) {
$char->{'hearing'}[0] = int(($char->{'iq'}[0] + $char->{'ht'}[0])/2)
+ $char->{'hearing'}[0];
}
if ($char->{'smell'}[0] !~ /^\d+/) {
$char->{'smell'}[0] = int(($char->{'iq'}[0] + $char->{'ht'}[0])/2)
+ $char->{'smell'}[0];
}
# Calculate will
if ($char->{'will'}[0] !~ /^\d+/) {
$char->{'will'}[0] = 11 + $char->{'will'}[0];
}
# damage
if ($char->{'thrust'}[0] eq '') {
$char->{'thrust'}[0] = $damage_table[$char->{'st'}[0]][0];
$char->{'swing'}[0] = $damage_table[$char->{'st'}[0]][1];
}
# DR and PD totals
if ($char->{'pd'}[0] eq '') {
$char->{'pd'}[0] = $char->{'armorpd'}[0] + $char->{'shieldpd'}[0]
+ $char->{'otherpd'}[0];
}
if ($char->{'dr'}[0] eq '') {
$char->{'dr'}[0] = $char->{'armordr'}[0] + $char->{'otherdr'}[0];
}
# Dodge, parry, block
if ($char->{'dodge'}[0] !~ /^\d/) {
$char->{'dodge'}[0] = $char->{'move'}[0] + $char->{'dodge'}[0];
}
if ($char->{'dodge'}[1] eq '') {
$char->{'dodge'}[1] = $char->{'dodge'}[0] + $char->{'pd'}[0];
}
if ($char->{'parry'}[0] !~ /^\d/) {
$char->{'parry'}[0] = int($char->{'w'}[0][3] / 2) + $char->{'parry'}[0];
}
if ($char->{'parry'}[1] eq '') {
$char->{'parry'}[1] = $char->{'parry'}[0] + $char->{'pd'}[0];
}
if ($char->{'block'}[0] !~ /^\d/) {
$char->{'block'}[0] = int(($char->{'dx'}[0] - 4) / 2) + $char->{'block'}[0];
}
if ($char->{'block'}[1] eq '') {
$char->{'block'}[1] = $char->{'block'}[0] + $char->{'pd'}[0];
}
# Stats
$char->{'pointsattributes'}[0] = $char->{'st'}[1] + $char->{'dx'}[1]
+ $char->{'iq'}[1] + $char->{'ht'}[1];
# Total point value of the character
$char->{'totalpoints'}[0] = $char->{'pointsadvantages'}[0] +
$char->{'pointsdisadvantages'}[0] +
$char->{'pointsquirks'}[0] +
$char->{'pointsskills'}[0] +
$char->{'pointsspells'}[0] +
$char->{'pointsattributes'}[0] +
$char->{'unspentpoints'}[0];
}
# calcskill (char, type, points, mod): Calculate the level for a skill.
sub calcskill {
my ($char, $type, $points, $mod) = @_;
my ($table, $base, $delta, $skilltype, $skilldiff);
# XXX This routine does not handle any odd skills. It's actually
# quite limited.
# XXX This will probably fail if you use "1/2" point. Use ".5"
# instead. This should be fixed.
# Figure out the table to use (mental/physical) and the stat (IQ/DX)
($skilltype, $skilldiff) = (lc($type) =~ /^(\w)(\w)/);
if ($skilltype eq 'm') {
$table = $mental_table;
$base = $char->{'iq'}[0];
} elsif ($skilltype eq 'p') {
$table = $physical_table;
$base = $char->{'dx'}[0];
} else {
return 0;
}
# very-hard skills are like hard skills with 1/2 the points.
if ($skilldiff eq 'v') {
$points /= 2;
$skilldiff = 'h';
}
# Find the points in the table, determine the level. We do it
# this way since it is valid to have an odd number of points in a
# skill. For example, you could have 3.5 points in a skill; you
# would be treated as if you had 2 points until you gained another
# half point, which would put you at the 4 point level.
foreach $_ (sort {$a <=> $b} (keys %$table)) {
if ($_ <= $points) {
$delta = $table->{$_};
} else {
last;
}
}
# Modify for easy, medium, and hard. very-hard was handled already.
if ($skilldiff eq 'e') {
$delta -= 0;
} elsif ($skilldiff eq 'a') {
$delta -= 1;
} elsif ($skilldiff eq 'h') {
$delta -= 2;
} else {
return 0;
}
return $base + $delta + $mod;
}
# handle_image: If an image is embedded in the file, save it and
# generate a URL.
sub handle_image {
my ($char) = @_;
my ($data, $hash, $filename, $fh);
push(@errlist, "Testing for a picture\n");
if ($char->{'picturedata'} ne '') {
# Don't handle huge pictures (larger than 100K encoded, ~75K decoded)
if (length($data) > 100*1024) {
push(@errlist, "Picture is too large (".length($data)." bytes)");
return;
}
$data = decode_base64($char->{'picturedata'});
$hash = sha1_hex($data);
if (substr($data, 0, 2) eq "\377\330") {
$filename = "$hash.jpg";
} elsif (substr($data, 0, 4) eq "GIF8") {
$filename = "$hash.gif";
} elsif (substr($data, 0, 4) eq "\211PNG") {
$filename = "$hash.png";
} else {
$filename = $hash;
}
if (! -f "tmpimage/$filename") {
$fh = new FileHandle("tmpimage/$filename", 'w') || return;
print $fh $data;
close($fh);
}
$char->{'pictureurl'}[0] = "tmpimage/$filename";
} else {
push(@errlist, "No picturedata");
}
}
sub save_char {
my ($char) = @_;
my ($hash) = sha1_hex(&print_char($char));
my ($filename);
my ($fh);
return unless ($char->{'save'}[0] =~ /^y/i);
# generate the filename
($filename = $char->{'name'}[0] . "-") =~ s/[^-a-zA-Z0-9]//g;
$filename .= "$hash.txt";
if ( ! -f "saved/$filename") {
$fh = new FileHandle("saved/$filename", 'w') || return;
print $fh &print_char($char);
close($fh);
}
}
sub roman2num {
my ($roman) = @_;
my ($result) = 0;
$result += 10 while $roman =~ s/^x//i;
$result += 9 if $roman =~ s/^ix//i;
$result += 5 while $roman =~ s/^v//i;
$result += 4 if $roman =~ s/^iv//i;
$result += 1 while $roman =~ s/^i//i;
return $result;
}
# Regenerate the template file, with newly filled-in fields
sub print_char {
my ($char) = @_;
my ($line);
my ($array, $key, $goodkey, $rest, @values);
my ($valuep, $out);
# Only do this once and cache the results
return $char->{'out'} if exists $char->{'out'};
foreach $line (@{$char->{'template'}}) {
# Pass most lines through unchanged
unless ($line =~ /^(\@?)([\w ]*)\s*:\s*(.*?)\s*$/) {
$out .= $line . "\n";
next;
}
# $array is '@' or ''; $key is the key, $rest is the rest of
# the line. $nicekey is $key, lowercased and de-spaced.
# @values contains the list of values.
($array, $key, $rest) = ($1, $2, $3);
($nicekey = lc($key)) =~ s/\s+//g;
@values = split(/\s*\/\s*/, $rest);
# Just print empty arrays (generally used as spacers)
if (($array eq '@') && ($rest eq '')) {
$out .= $line . "\n";
next;
}
# Zero out $valuep (pointer to an array of values)
$valuep = undef;
# Set $valuep if possible
if ($array eq '@') {
foreach $_ (@{$char->{$nicekey}}) {
next unless $_->[0] eq $values[0];
$valuep = $_;
}
} else {
$valuep = $char->{$nicekey};
}
# If we couldn't set $valuep; just print the line.
if (!defined $valuep) {
$out .= $line . "\n";
next;
}
# If a value is blank, and we calculated the value, then
# insert the calculated value surrounded by curly braces.
foreach ($i=0; $i<@$valuep; $i++) {
if (($values[$i] == '') && ($valuep->[$i] != '')) {
$values[$i] = '{' . $valuep->[$i] . '}';
}
}
# Print the line
$out .= "$array$key: " . join(' / ', @values) . "\n";
}
# Cache and return the result
$char->{'out'} = $out;
}