diff -ur irpgbot.v3.1.2/.irpg.conf irpgbot.v3.1.2+johm+chschu/.irpg.conf
--- irpgbot.v3.1.2/.irpg.conf	Mon Jun  7 12:28:56 2004
+++ irpgbot.v3.1.2+johm+chschu/.irpg.conf	Thu Jun 17 16:38:19 2004
@@ -17,6 +17,9 @@
 # remove or comment out this line so the bot knows that you edited the config
 # file
 die
+
+# Use ipv6 to connect to irc server?
+ipv6 off
 
 # local hostname or address to bind to. leave blank or comment out if you don't
 # want to use a vhost
@@ -66,6 +69,9 @@
 # base time to level up, 600 = 10 minutes
 rpbase 600
 
+# base time for items to level down, 600 = 10 minutes
+rpitembase 50
+
 # time to next level = rpbase * (rpstep ** CURRENT_LEVEL)
 rpstep 1.16
 
@@ -75,6 +81,9 @@
 # player database file
 dbfile irpg.db
 
+# item database file
+itemdbfile mapitems.db
+
 # where quests/godsends/calamities are stored
 eventsfile events.txt
 
@@ -170,6 +179,10 @@
 # network does not prefix quit messages with "Quit: " (or something other
 # string), then users can cheat this at their whim
 detectsplits on
+
+# auto-login clients (after netsplit, ping timeout ...) if they haven't
+# changed their nick!user@host
+autologin on
 
 # time to wait for netsplit users to return? in seconds. 900 = 15 minutes, good
 # for large nets
diff -ur irpgbot.v3.1.2/ChangeLog.txt irpgbot.v3.1.2+johm+chschu/ChangeLog.txt
--- irpgbot.v3.1.2/ChangeLog.txt	Mon Jun  7 12:28:56 2004
+++ irpgbot.v3.1.2+johm+chschu/ChangeLog.txt	Thu Jun 17 16:21:36 2004
@@ -7,6 +7,33 @@
 comments, or post them in the forum on the website for public view.
 
 --------------------------------------------------------------------------------
+  v3.1.2+johm+chschu: released -/-/04
+--------------------------------------------------------------------------------
+  - setup !idle@IRCnet [johm]
+  - added ipv6 support [johm]
+  - added auto-login (after netsplit, ping timeout ...) if you haven't
+    changed your nick!user@host [johm]
+     * made auto-login optional [johm]
+  - fixed lvl60+ penalty bug [johm]
+  - fixed possible $rpreport timer bug [johm+chschu]
+  - team battles are now fought out around random points on the map [chschu]
+  - added wars of northern half against southern half of the map [chschu]
+     * made wars quadrant-based [chschu]
+  - dropped items stay on the map, lose 1 level every 30 minutes and are
+    found if somebody enters the same spot [chschu]
+     * changed downgrading time to 1 day, limited downgrading of uniques [chschu]
+     * only drop items of level > 0, downgrade uniques to non-uniques
+       below their minimum level [chschu]
+     * downgrading now depends on item level (higher => slower) [chschu]
+  - added reload of quest on startup [chschu]
+  - fixed bug with int() applied on non-numeric strings (item levels) [chschu]
+  - fixed config bug on empty value [chschu]
+  - fixed warning on undefined username in ha() [chschu]
+  - fixed bug breaking userhost on nick change [chschu]
+  - fixed voice on login bug (less nicks than flags) [chschu]
+
+
+--------------------------------------------------------------------------------
   v3.1.2: released 6/6/04
 --------------------------------------------------------------------------------
   - applied a user-submitted patch to fix a sprintf() bug (anonymous @ forum)
diff -ur irpgbot.v3.1.2/bot.v3.1.2.pl irpgbot.v3.1.2+johm+chschu/bot.v3.1.2.pl
--- irpgbot.v3.1.2/bot.v3.1.2.pl	Mon Jun  7 12:28:56 2004
+++ irpgbot.v3.1.2+johm+chschu/bot.v3.1.2.pl	Thu Jun 17 16:40:07 2004
@@ -28,6 +28,7 @@
 use strict;
 use warnings;
 use IO::Socket;
+use IO::Socket::INET6;
 use IO::Select;
 use Data::Dumper;
 use Getopt::Long;
@@ -36,12 +37,13 @@
 
 readconfig();
 
-my $version = "3.1.2";
+my $version = "3.1.2+johm+chschu";
 
 # command line overrides .irpg.conf
 GetOptions(\%opts,
     "help|h",
     "verbose|v",
+    "ipv6",
     "debug",
     "debugfile=s",
     "server|s=s",
@@ -72,6 +74,7 @@
     "modsfile=s",
     "casematters",
     "detectsplits",
+    "autologin",
     "splitwait=i",
     "allowuserinfo",
     "noscale",
@@ -111,8 +114,10 @@
     text => "",
     type => 1,
     stage => 1); # quest info
+my %mapitems = (); # items lying around
 
 my $rpreport = 0; # constant for reporting top players
+my $oldrpreport = 0; # constant for reporting top players (last value)
 my %prev_online; # user@hosts online on restart, die
 my %auto_login; # users to automatically log back on
 my @bans; # bans auto-set by the bot, saved to be removed after 1 hour
@@ -225,11 +230,18 @@
 
 while (!$sock && $conn_tries < 2*@{$opts{servers}}) {
     debug("Connecting to $opts{servers}->[0]...");
-    my %sockinfo = (PeerAddr => $opts{servers}->[0],
-                    PeerPort => 6667);
+    my %sockinfo = (PeerAddr => $opts{servers}->[0]);
     if ($opts{localaddr}) { $sockinfo{LocalAddr} = $opts{localaddr}; }
-    $sock = IO::Socket::INET->new(%sockinfo) or
-        debug("Error: failed to connect: $!\n");
+
+    if ($opts{ipv6}) {
+        $sock = IO::Socket::INET6->new(%sockinfo) or
+            debug("Error: failed to connect: $!\n");
+    }
+    else {
+        $sock = IO::Socket::INET->new(%sockinfo) or
+            debug("Error: failed to connect: $!\n");
+    }
+
     ++$conn_tries;
     if (!$sock) {
         # cycle front server to back if connection failed
@@ -327,6 +339,24 @@
             sts($opcmd);
             $lasttime = time(); # start rpcheck()
         }
+        elsif ($opts{autologin}) {
+            for my $k (keys %rps) {
+                if (":".$rps{$k}{userhost} eq $arg[0]) {
+                    if ($opts{voiceonlogin}) {          
+                        sts("MODE $opts{botchan} +v :$usernick");
+                    }
+                    $rps{$k}{online} = 1;
+                    $rps{$k}{nick} = $usernick;
+                    $rps{$k}{lastlogin} = time();
+                    chanmsg("$k, the level $rps{$k}{level} ".
+                            "$rps{$k}{class}, is now online from ".
+                            "nickname $usernick. Next level in ".
+                            duration($rps{$k}{next}).".");       
+                    notice("Logon successful. Next level in ".
+                           duration($rps{$k}{next}).".", $usernick);
+                }
+            }
+        }
     }
     elsif ($arg[1] eq 'quit') {
         # if we see our nick come open, grab it (skipping queue)
@@ -393,17 +423,21 @@
             }
             if ($opts{voiceonlogin}) {
                 my @vnicks = map { $rps{$_}{nick} } keys(%auto_login);
-                while (@vnicks) {
+                while (scalar @vnicks >= $opts{modesperline}) {
                     sts("MODE $opts{botchan} +".
                         ('v' x $opts{modesperline})." ".
                         join(" ",@vnicks[0..$opts{modesperline}-1]));
                     splice(@vnicks,0,$opts{modesperline});
                 }
+                sts("MODE $opts{botchan} +".
+                    ('v' x (scalar @vnicks))." ".
+                    join(" ",@vnicks));
             }
         }
         else { chanmsg("0 users qualified for auto login."); }
         undef(%prev_online);
         undef(%auto_login);
+        loadquestfile();
     }
     elsif ($arg[1] eq '005') {
         if ("@arg" =~ /MODES=(\d+)/) { $opts{modesperline}=$1; }
@@ -1085,6 +1119,20 @@
     }
 }
 
+sub ttl { # return ttl
+    my $lvl = shift;
+    return ($opts{rpbase} * ($opts{rpstep}**$lvl)) if $lvl <= 60;
+    return (($opts{rpbase} * ($opts{rpstep}**60))
+             + (86400*($lvl - 60)));
+}
+
+sub penttl { # return ttl with $opts{rppenstep}
+    my $lvl = shift;
+    return ($opts{rpbase} * ($opts{rppenstep}**$lvl)) if $lvl <= 60;
+    return (($opts{rpbase} * ($opts{rppenstep}**60))
+             + (86400*($lvl - 60)));
+}
+
 sub duration { # return human duration of seconds
     my $s = shift;
     return "NA ($s)" if $s !~ /^\d+$/;
@@ -1148,12 +1196,14 @@
     }
     if (rand((8*86400)/$opts{self_clock}) < $onlineevil) { evilness(); }
     if (rand((12*86400)/$opts{self_clock}) < $onlinegood) { goodness(); }
+    if (rand((10*86400)/$opts{self_clock}) < 1) { war(); }
 
     moveplayers();
+    process_items();
     
     # statements using $rpreport do not bother with scaling by the clock because
     # $rpreport is adjusted by the number of seconds since last rpcheck()
-    if ($rpreport%120==0 && $opts{writequestfile}) { writequestfile(); }
+    if (($rpreport%120 < $oldrpreport%120) && $opts{writequestfile}) { writequestfile(); }
     if (time() > $quest{qtime}) {
         if (!@{$quest{questers}}) { quest(); }
         elsif ($quest{type} == 1) {
@@ -1166,10 +1216,11 @@
             }
             undef(@{$quest{questers}});
             $quest{qtime} = time() + 21600;
+            writequestfile();
         }
         # quest type 2 awards are handled in moveplayers()
     }
-    if ($rpreport && $rpreport%36000==0) { # 10 hours
+    if ($rpreport && ($rpreport%36000 < $oldrpreport%36000)) { # 10 hours
         my @u = sort { $rps{$b}{level} <=> $rps{$a}{level} ||
                        $rps{$a}{next}  <=> $rps{$b}{next} } keys(%rps);
         chanmsg("Idle RPG Top Players:") if @u;
@@ -1181,7 +1232,7 @@
         }
         backup();
     }
-    if ($rpreport%3600==0 && $rpreport) { # 1 hour
+    if (($rpreport%3600 < $oldrpreport%3600) && $rpreport) { # 1 hour
         my @players = grep { $rps{$_}{online} &&
                              $rps{$_}{level} > 44 } keys(%rps);
         # 20% of all players must be level 45+
@@ -1193,13 +1244,13 @@
             splice(@bans,0,4);
         }
     }
-    if ($rpreport%1800==0) { # 30 mins
+    if ($rpreport%1800 < $oldrpreport%1800) { # 30 mins
         if ($opts{botnick} ne $primnick) {
             sts($opts{botghostcmd}) if $opts{botghostcmd};
             sts("NICK $primnick");
         }
     }
-    if ($rpreport%600==0 && $pausemode) { # warn every 10m
+    if (($rpreport%600 < $oldrpreport%600) && $pausemode) { # warn every 10m
         chanmsg("WARNING: Cannot write database in PAUSE mode!");
     }
     # do not write in pause mode, and do not write if not yet connected. (would
@@ -1215,19 +1266,12 @@
                 $rps{$k}{next} -= ($curtime - $lasttime);
                 $rps{$k}{idled} += ($curtime - $lasttime);
                 if ($rps{$k}{next} < 1) {
+                    my $ttl = int(ttl($rps{$k}{level}));
                     $rps{$k}{level}++;
-                    if ($rps{$k}{level} > 60) {
-                        $rps{$k}{next} = int(($opts{rpbase} *
-                                             ($opts{rpstep}**60)) +
-                                             (86400*($rps{$k}{level} - 60)));
-                    }
-                    else {
-                        $rps{$k}{next} = int($opts{rpbase} *
-                                             ($opts{rpstep}**$rps{$k}{level}));
-                    }
+                    $rps{$k}{next} += $ttl;
                     chanmsg("$k, the $rps{$k}{class}, has attained level ".
                             "$rps{$k}{level}! Next level in ".
-                            duration($rps{$k}{next}).".");
+                            duration($ttl).".");
                     find_item($k);
                     challenge_opp($k);
                 }
@@ -1235,12 +1279,88 @@
             # attempt to make sure this is an actual user, and not just an
             # artifact of a bad PEVAL
         }
-        if (!$pausemode && $rpreport%60==0) { writedb(); }
-        $rpreport += $opts{self_clock};
+        if (!$pausemode && ($rpreport%60 < $oldrpreport%60)) { writedb(); }
+        $oldrpreport = $rpreport;
+        $rpreport += $curtime - $lasttime;
         $lasttime = $curtime;
     }
 }
 
+sub war { # let the four quadrants battle
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    my @quadrantname = ("Northeast", "Southeast", "Southwest", "Northwest");
+    my %quadrant = ();
+    my @sum = (0,0,0,0,0);
+    # get quadrant for each player and item sum per quadrant
+    for my $k (@players) {
+        # "quadrant" 4 is for players in the middle
+        $quadrant{$k} = 4;
+        if (2 * $rps{$k}{y} + 1 < $opts{mapy}) {
+            $quadrant{$k} = 3 if (2 * $rps{$k}{x} + 1 < $opts{mapx});
+            $quadrant{$k} = 0 if (2 * $rps{$k}{x} + 1 > $opts{mapx});
+        }
+        elsif (2 * $rps{$k}{y} + 1 > $opts{mapy})
+        {
+            $quadrant{$k} = 2 if (2 * $rps{$k}{x} + 1 < $opts{mapx});
+            $quadrant{$k} = 1 if (2 * $rps{$k}{x} + 1 > $opts{mapx});
+        }
+        $sum[$quadrant{$k}] += itemsum($k);
+    }
+    # roll for each quadrant
+    my @roll = (0,0,0,0);
+    $roll[$_] = int(rand($sum[$_])) foreach (0..3);
+    # winner if value >= maximum value of both direct neighbors, "quadrant" 4 never wins
+    my @iswinner = map($_ < 4 && $roll[$_] >= $roll[($_ + 1) % 4] &&
+                                 $roll[$_] >= $roll[($_ + 3) % 4],(0..4));
+    my @winners = map("the $quadrantname[$_] [$roll[$_]/$sum[$_]]",grep($iswinner[$_],(0..3)));
+    # construct text from winners array
+    my $winnertext = "";
+    $winnertext = pop(@winners) if (scalar(@winners) > 0);
+    $winnertext = pop(@winners)." and $winnertext" if (scalar(@winners) > 0);
+    $winnertext = pop(@winners).", $winnertext" while (scalar(@winners) > 0);
+    $winnertext = "has shown the power of $winnertext" if ($winnertext ne "");
+    # loser if value < minimum value of both direct neighbors, "quadrant" 4 never loses
+    my @isloser = map($_ < 4 && $roll[$_] < $roll[($_ + 1) % 4] &&
+                                $roll[$_] < $roll[($_ + 3) % 4],(0..4));
+    my @losers = map("the $quadrantname[$_] [$roll[$_]/$sum[$_]]",grep($isloser[$_],(0..3)));
+    # construct text from losers array
+    my $losertext = "";
+    $losertext = pop(@losers) if (scalar(@losers) > 0);
+    $losertext = pop(@losers)." and $losertext" if (scalar(@losers) > 0);
+    $losertext = pop(@losers).", $losertext" while (scalar(@losers) > 0);
+    $losertext = "led $losertext to perdition" if ($losertext ne "");
+    # build array of text for neutrals
+    my @neutrals = map("the $quadrantname[$_] [$roll[$_]/$sum[$_]]",grep(!$iswinner[$_] && !$isloser[$_],(0..3)));
+    # construct text from neutrals array
+    my $neutraltext = "";
+    $neutraltext = pop(@neutrals) if (scalar(@neutrals) > 0);
+    $neutraltext = pop(@neutrals)." and $neutraltext" if (scalar(@neutrals) > 0);
+    $neutraltext = pop(@neutrals).", $neutraltext" while (scalar(@neutrals) > 0);
+    $neutraltext = " The diplomacy of $neutraltext was admirable." if ($neutraltext ne "");
+    if ($winnertext ne "" && $losertext ne "") {
+        # there are winners and losers
+        chanmsg(clog("The war between the four parts of the realm ".
+                     "$winnertext, whereas it $losertext.$neutraltext"));
+    }
+    elsif ($winnertext eq "" && $losertext eq "") {
+        # there are only neutrals
+        chanmsg(clog("The war between the four parts of the realm ".
+                     "was well-balanced.$neutraltext"));
+    }
+    else {
+        # there are either winners or losers
+        chanmsg(clog("The war between the four parts of the realm ".
+                     "$winnertext$losertext.$neutraltext"));
+    }
+    for my $k (@players) {
+        # halve ttl of users in winning quadrant
+        # users in "quadrant" 4 are not awarded or penalized
+        $rps{$k}{next} = int($rps{$k}{next} / 2) if ($iswinner[$quadrant{$k}]);
+        # double ttl of users in losing quadrant
+        $rps{$k}{next} *= 2 if ($isloser[$quadrant{$k}]);
+    }
+}
+
 sub challenge_opp { # pit argument player against random player
     my $u = shift;
     if ($rps{$u}{level} < 25) { return unless rand(4) < 1; }
@@ -1277,11 +1397,11 @@
                          "pair of gloves","set of leggings","shield",
                          "pair of boots");
             my $type = $items[rand(@items)];
-            if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) {
+            if (itemlevel($rps{$opp}{item}{$type}) > itemlevel($rps{$u}{item}{$type})) {
                 chanmsg(clog("In the fierce battle, $opp dropped his level ".
-                             int($rps{$opp}{item}{$type})." $type! $u picks ".
+                             itemlevel($rps{$opp}{item}{$type})." $type! $u picks ".
                              "it up, tossing his old level ".
-                             int($rps{$u}{item}{$type})." $type to $opp."));
+                             itemlevel($rps{$u}{item}{$type})." $type to $opp."));
                 my $tempitem = $rps{$u}{item}{$type};
                 $rps{$u}{item}{$type}=$rps{$opp}{item}{$type};
                 $rps{$opp}{item}{$type} = $tempitem;
@@ -1303,8 +1423,31 @@
 sub team_battle { # pit three players against three other players
     my @opp = grep { $rps{$_}{online} } keys(%rps);
     return if @opp < 6;
-    splice(@opp,int(rand(@opp)),1) while @opp > 6;
-    fisher_yates_shuffle(\@opp);
+    # choose random point       
+    my $x = int(rand($opts{mapx}));
+    my $y = int(rand($opts{mapy}));
+    my %polar = ();
+    for my $player (@opp) {   
+        my $dx = $rps{$player}{x}-$x;
+        my $dy = $rps{$player}{y}-$y;
+        # polar coordinates
+        $polar{$player}{r} = sqrt($dx*$dx+$dy*$dy);
+        $polar{$player}{phi} = atan2($dy,$dx)      
+    }
+    # sort by radius 
+    my @sorted = sort { $polar{$a}{r} <=> $polar{$b}{r} } keys %polar;
+    # get players at least as close as #6
+    @sorted = grep { $polar{$_}{r} <= $polar{$sorted[5]}{r} } @sorted;
+    # pick 6 random players from these  
+    @opp = ();
+    for (my $i = 0; $i < 6; $i++) {
+        $opp[$i] = splice(@sorted,int(rand(@sorted)),1);  
+    }
+    # sort by angle
+    @opp = sort { $polar{$a}{phi} <=> $polar{$b}{phi} } @opp;
+    # shift splitting position
+    my $rot = int(rand(6));
+    @opp = @opp[$rot..5,0..$rot-1];  
     my $mysum = itemsum($opp[0],1) + itemsum($opp[1],1) + itemsum($opp[2],1);
     my $oppsum = itemsum($opp[3],1) + itemsum($opp[4],1) + itemsum($opp[5],1);
     my $gain = $rps{$opp[0]}{next};
@@ -1315,25 +1458,140 @@
     my $myroll = int(rand($mysum));
     my $opproll = int(rand($oppsum));
     if ($myroll >= $opproll) {
-        chanmsg(clog("$opp[0], $opp[1], and $opp[2] [$myroll/$mysum] have ".
-                     "team battled $opp[3], $opp[4], and $opp[5] [$opproll/".
-                     "$oppsum] and won! ".duration($gain)." is removed from ".
-                     "their clocks."));
+        chanmsg(clog("$opp[0], $opp[1] and $opp[2] [$myroll/$mysum] have team ".
+                     "battled $opp[3], $opp[4] and $opp[5] [$opproll/$oppsum] ".
+                     "at [$x,$y] and won! ".duration($gain)." is removed ".
+                     "from their clocks."));
         $rps{$opp[0]}{next} -= $gain;
         $rps{$opp[1]}{next} -= $gain;
         $rps{$opp[2]}{next} -= $gain;
     }
     else {
-        chanmsg(clog("$opp[0], $opp[1], and $opp[2] [$myroll/$mysum] have ".
-                     "team battled $opp[3], $opp[4], and $opp[5] [$opproll/".
-                     "$oppsum] and lost! ".duration($gain)." is added to ".
-                     "their clocks."));
+        chanmsg(clog("$opp[0], $opp[1] and $opp[2] [$myroll/$mysum] have team ".
+                     "battled $opp[3], $opp[4] and $opp[5] [$opproll/$oppsum] ".
+                     "at [$x,$y] and lost! ".duration($gain)." is added ".
+                     "to their clocks."));
         $rps{$opp[0]}{next} += $gain;
         $rps{$opp[1]}{next} += $gain;
         $rps{$opp[2]}{next} += $gain;
     }
 }
 
+sub itemlevel {
+    my $level = shift;
+    $level =~ s/\D$//;
+    return $level;
+}
+
+sub itemtag {
+    my $level = shift;
+    $level =~ s/^\d+//;
+    return $level;
+}
+
+sub process_items { # decrease items lying around
+    my $curtime = time();
+
+    for my $xy (keys(%mapitems)) {
+        for my $i (0..$#{$mapitems{$xy}}) {
+            my $level = $mapitems{$xy}[$i]{level};
+            my $ttl = int($opts{rpitembase} * ttl(itemlevel($level)) / 600);
+            if ($mapitems{$xy}[$i]{lasttime} + $ttl <= $curtime ) {
+               $mapitems{$xy}[$i]{lasttime} += $ttl;
+               $mapitems{$xy}[$i]{level} = downgrade_item($level);
+               splice(@{$mapitems{$xy}},$i,1) if ($mapitems{$xy}[$i]{level} == 0);
+            }
+        }
+    }
+}
+
+sub drop_item { # drop item on the map
+    my $u = shift;
+    my $type = shift;
+    my $level = shift;
+    my $ulevel = itemlevel($level);
+    my $x = $rps{$u}{x};
+    my $y = $rps{$u}{y};
+
+    push(@{$mapitems{"$x:$y"}},{type=>$type,level=>$level,lasttime=>time()}) if ($ulevel > 0);
+}
+
+sub downgrade_item { # returns the decreased item level
+    my $level = shift;
+    my $ulevel = itemlevel($level);
+    my $tag = itemtag($level);
+    my %minlevel = (''=>0,a=>50,h=>50,b=>75,d=>150,e=>175,f=>250,g=>300);
+    $tag = '' if ($ulevel == $minlevel{$tag});
+    $ulevel-- if ($ulevel > 0);
+    return "$ulevel$tag";
+}
+
+sub exchange_item { # take item and drop the current
+    my $u = shift;
+    my $type = shift;
+    my $level = shift;
+    my $ulevel = itemlevel($level);
+    my $tag = itemtag($level);
+
+    if ($tag eq 'a') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Mattt's Omniscience Grand Crown! ".
+               "Your enemies fall before you as you anticipate their ".
+               "every move.",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'b') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Res0's Protectorate Plate Mail! ".
+               "Your enemies cower in fear as their attacks have no ".
+               "effect on you.",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'c') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Dwyn's Storm Magic Amulet! Your ".
+               "enemies are swept away by an elemental fury before the ".
+               "war has even begun",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'd') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Jotun's Fury Colossal Sword! Your ".
+               "enemies' hatred is brought to a quick end as you arc your ".
+               "wrist, dealing the crushing blow.",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'e') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Drdink's Cane of Blind Rage! Your ".
+               "enemies are tossed aside as you blindly swing your arm ".
+               "around hitting stuff.",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'f') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Mrquick's Magical Boots of ".
+               "Swiftness! Your enemies are left choking on your dust as ".
+               "you run from them very, very quickly.",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'g') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Jeff's Cluehammer of Doom! Your ".
+               "enemies are left with a sudden and intense clarity of ".
+               "mind... even as you relieve them of it.",$rps{$u}{nick});
+    }
+    elsif ($tag eq 'h') {
+        notice("The light of the gods shines down upon you! You have ".
+               "found the level $ulevel Juliet's Glorious Ring of ".
+               "Sparkliness! You enemies are blinded by both its glory ".
+               "and their greed as you bring desolation upon them.",
+               $rps{$u}{nick});
+    }
+    else {
+        notice("You found a level $level $type! Your current $type is only ".
+               "level ".itemlevel($rps{$u}{item}{$type}).", so it seems Luck is ".
+               "with you!",$rps{$u}{nick});
+    }
+
+    drop_item($u,$type,$rps{$u}{item}{$type});
+    $rps{$u}{item}{$type} = $level;
+}
+
 sub find_item { # find item for argument player
     my $u = shift;
     my @items = ("ring","amulet","charm","weapon","helm","tunic",
@@ -1348,108 +1606,73 @@
     }
     if ($rps{$u}{level} >= 25 && rand(40) < 1) {
         $ulevel = 50+int(rand(25));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{helm})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Mattt's Omniscience Grand Crown! ".
-                   "Your enemies fall before you as you anticipate their ".
-                   "every move.",$rps{$u}{nick});
-            $rps{$u}{item}{helm} = $ulevel."a";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{helm})) {
+            exchange_item($u,"helm",$ulevel."a");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 25 && rand(40) < 1) {
         $ulevel = 50+int(rand(25));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{ring})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Juliet's Glorious Ring of ".
-                   "Sparkliness! You enemies are blinded by both its glory ".
-                   "and their greed as you bring desolation upon them.",
-                   $rps{$u}{nick});
-            $rps{$u}{item}{ring} = $ulevel."h";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{ring})) {
+            exchange_item($u,"ring",$ulevel."h");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 30 && rand(40) < 1) {
         $ulevel = 75+int(rand(25));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{tunic})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Res0's Protectorate Plate Mail! ".
-                   "Your enemies cower in fear as their attacks have no ".
-                   "effect on you.",$rps{$u}{nick});
-            $rps{$u}{item}{tunic} = $ulevel."b";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{tunic})) {
+            exchange_item($u,"tunic",$ulevel."b");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 35 && rand(40) < 1) {
         $ulevel = 100+int(rand(25));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{amulet})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Dwyn's Storm Magic Amulet! Your ".
-                   "enemies are swept away by an elemental fury before the ".
-                   "war has even begun",$rps{$u}{nick});
-            $rps{$u}{item}{amulet} = $ulevel."c";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{amulet})) {
+            exchange_item($u,"amulet",$ulevel."c");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 40 && rand(40) < 1) {
         $ulevel = 150+int(rand(25));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Jotun's Fury Colossal Sword! Your ".
-                   "enemies' hatred is brought to a quick end as you arc your ".
-                   "wrist, dealing the crushing blow.",$rps{$u}{nick});
-            $rps{$u}{item}{weapon} = $ulevel."d";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{weapon})) {
+            exchange_item($u,"weapon",$ulevel."d");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 45 && rand(40) < 1) {
         $ulevel = 175+int(rand(26));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Drdink's Cane of Blind Rage! Your ".
-                   "enemies are tossed aside as you blindly swing your arm ".
-                   "around hitting stuff.",$rps{$u}{nick});
-            $rps{$u}{item}{weapon} = $ulevel."e";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{weapon})) {
+            exchange_item($u,"weapon",$ulevel."e");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 48 && rand(40) < 1) {
         $ulevel = 250+int(rand(51));
         if ($ulevel >= $level && $ulevel >
-            int($rps{$u}{item}{"pair of boots"})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Mrquick's Magical Boots of ".
-                   "Swiftness! Your enemies are left choking on your dust as ".
-                   "you run from them very, very quickly.",$rps{$u}{nick});
-            $rps{$u}{item}{"pair of boots"} = $ulevel."f";
+            itemlevel($rps{$u}{item}{"pair of boots"})) {
+            exchange_item($u,"pair of boots",$ulevel."f");
             return;
         }
     }
     elsif ($rps{$u}{level} >= 52 && rand(40) < 1) {
         $ulevel = 300+int(rand(51));
-        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) {
-            notice("The light of the gods shines down upon you! You have ".
-                   "found the level $ulevel Jeff's Cluehammer of Doom! Your ".
-                   "enemies are left with a sudden and intense clarity of ".
-                   "mind... even as you relieve them of it.",$rps{$u}{nick});
-            $rps{$u}{item}{weapon} = $ulevel."g";
+        if ($ulevel >= $level && $ulevel > itemlevel($rps{$u}{item}{weapon})) {
+            exchange_item($u,"weapon",$ulevel."g");
             return;
         }
     }
-    if ($level > int($rps{$u}{item}{$type})) {
-        notice("You found a level $level $type! Your current $type is only ".
-               "level ".int($rps{$u}{item}{$type}).", so it seems Luck is ".
-               "with you!",$rps{$u}{nick});
-        $rps{$u}{item}{$type} = $level;
+    if ($level > itemlevel($rps{$u}{item}{$type})) {
+        exchange_item($u,$type,$level);
     }
     else {
         notice("You found a level $level $type. Your current $type is level ".
-               int($rps{$u}{item}{$type}).", so it seems Luck is against you. ".
+               itemlevel($rps{$u}{item}{$type}).", so it seems Luck is against you. ".
                "You toss the $type.",$rps{$u}{nick});
+        drop_item($u,$type,$level);
     }
 }
 
-sub loaddb { # load the players database
+sub loaddb { # load the players and items database
     backup();
     my $l;
     %rps = ();
@@ -1505,6 +1728,28 @@
     close(RPS);
     debug("loaddb(): loaded ".scalar(keys(%rps))." accounts, ".
           scalar(keys(%prev_online))." previously online.");
+    if (!open(ITEMS,$opts{itemdbfile}) && -e $opts{itemdbfile}) {
+        sts("QUIT :loaddb() failed: $!");
+    }
+    my $cnt = 0;
+    %mapitems = ();
+    while ($l=<ITEMS>) {
+        chomp($l);
+        next if $l =~ /^#/; # skip comments
+        my @i = split("\t",$l);
+        print Dumper(@i) if @i != 5;
+        if (@i != 5) {
+            sts("QUIT: Anomaly in loaddb(); line $. of $opts{itemdbfile} has ".
+                "wrong fields (".scalar(@i).")");
+            debug("Anomaly in loaddb(); line $. of $opts{itemdbfile} has wrong ".
+                "fields (".scalar(@i).")",1);
+        }
+        my $curtime = time();
+        push(@{$mapitems{"$i[0]:$i[1]"}},{type=>$i[2],level=>$i[3],lasttime=>$curtime-$i[4]});
+        $cnt++;
+    }
+    close(ITEMS);
+    debug("loaddb(): loaded $cnt items.");
 }
 
 sub moveplayers {
@@ -1641,6 +1886,19 @@
                 }
             }
         }
+        # pick up items lying around
+        for my $u (keys(%rps)) {
+            next unless $rps{$u}{online};
+            my $x = $rps{$u}{x};
+            my $y = $rps{$u}{y};
+            for $i (0..$#{$mapitems{"$x:$y"}}) {
+                my $item = $mapitems{"$x:$y"}[$i];
+                if (itemlevel($item->{level}) > itemlevel($rps{$u}{item}{$item->{type}})) {
+                    exchange_item($u,$item->{type},$item->{level});
+                    splice(@{$mapitems{"$x:$y"}},$i,1);
+                }
+            }
+        }
     }
 }
 
@@ -1728,7 +1986,7 @@
         return $sum+1;
     }
     if (!exists($rps{$user})) { return -1; }
-    $sum += int($rps{$user}{item}{$_}) for keys(%{$rps{$user}{item}});
+    $sum += itemlevel($rps{$user}{item}{$_}) for keys(%{$rps{$user}{item}});
     if ($battle) {
         return $rps{$user}{alignment} eq 'e' ? int($sum*.9) :
                $rps{$user}{alignment} eq 'g' ? int($sum*1.1) :
@@ -1800,7 +2058,7 @@
         }
         my $suffix="";
         if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
-        $rps{$player}{item}{$type} = int(int($rps{$player}{item}{$type}) * .9);
+        $rps{$player}{item}{$type} = int(itemlevel($rps{$player}{item}{$type}) * .9);
         $rps{$player}{item}{$type}.=$suffix;
     }
     else {
@@ -1857,7 +2115,7 @@
         }
         my $suffix="";
         if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
-        $rps{$player}{item}{$type} = int(int($rps{$player}{item}{$type}) * 1.1);
+        $rps{$player}{item}{$type} = int(itemlevel($rps{$player}{item}{$type}) * 1.1);
         $rps{$player}{item}{$type}.=$suffix;
     }
     else {
@@ -1940,12 +2198,14 @@
                          "pressure towards hell. Therefore have you drawn ".
                          "yourselves 15 steps closer to that gaping maw."));
             for $player (grep { $rps{$_}{online} } keys %rps) {
-                my $gain = int(15 * ($opts{rppenstep}**$rps{$player}{level}));
+                my $gain = int(15 * penttl($rps{$player}{level}) / $opts{rpbase});
                 $rps{$player}{pen_quest} += $gain;
                 $rps{$player}{next} += $gain;
             }
             undef(@{$quest{questers}});
             $quest{qtime} = time() + 43200; # 12 hours
+            writequestfile();
+            last;
         }
     }
 }
@@ -1966,9 +2226,11 @@
     if (! -d ".dbbackup/") { mkdir(".dbbackup",0700); }
     if ($^O ne "MSWin32") {
         system("cp $opts{dbfile} .dbbackup/$opts{dbfile}".time());
+        system("cp $opts{itemdbfile} .dbbackup/$opts{itemdbfile}".time());
     }
     else {
         system("copy $opts{dbfile} .dbbackup\\$opts{dbfile}".time());
+        system("copy $opts{itemdbfile} .dbbackup\\$opts{itemdbfile}".time());
     }
 }
 
@@ -1980,7 +2242,7 @@
     my $pen = 0;
     questpencheck($username);
     if ($type eq "quit") {
-        $pen = int(20 * ($opts{rppenstep}**$rps{$username}{level}));
+        $pen = int(20 * penttl($rps{$username}{level}) / $opts{rpbase});
         if ($opts{limitpen} && $pen > $opts{limitpen}) {
             $pen = $opts{limitpen};
         }
@@ -1989,19 +2251,18 @@
     }
     elsif ($type eq "nick") {
         my $newnick = shift;
-        $pen = int(30 * ($opts{rppenstep}**$rps{$username}{level}));
+        $pen = int(30 * penttl($rps{$username}{level}) / $opts{rpbase});
         if ($opts{limitpen} && $pen > $opts{limitpen}) {
             $pen = $opts{limitpen};
         }
         $rps{$username}{pen_nick}+=$pen;
         $rps{$username}{nick} = substr($newnick,1);
-        substr($rps{$username}{userhost},0,length($rps{$username}{nick})) =
-            substr($newnick,1);
+        $rps{$username}{userhost} =~ s/^[^!]+/$rps{$username}{nick}/e;
         notice("Penalty of ".duration($pen)." added to your timer for ".
                "nick change.",$rps{$username}{nick});
     }
     elsif ($type eq "privmsg" || $type eq "notice") {
-        $pen = int(shift(@_) * ($opts{rppenstep}**$rps{$username}{level}));
+        $pen = int(shift(@_) * penttl($rps{$username}{level}) / $opts{rpbase});
         if ($opts{limitpen} && $pen > $opts{limitpen}) {
             $pen = $opts{limitpen};
         }
@@ -2010,7 +2271,7 @@
                $type.".",$rps{$username}{nick});
     }
     elsif ($type eq "part") {
-        $pen = int(200 * ($opts{rppenstep}**$rps{$username}{level}));
+        $pen = int(200 * penttl($rps{$username}{level}) / $opts{rpbase});
         if ($opts{limitpen} && $pen > $opts{limitpen}) {
             $pen = $opts{limitpen};
         }
@@ -2020,7 +2281,7 @@
         $rps{$username}{online}=0;
     }
     elsif ($type eq "kick") {
-        $pen = int(250 * ($opts{rppenstep}**$rps{$username}{level}));
+        $pen = int(250 * penttl($rps{$username}{level}) / $opts{rpbase});
         if ($opts{limitpen} && $pen > $opts{limitpen}) {
             $pen = $opts{limitpen};
         }
@@ -2030,7 +2291,7 @@
         $rps{$username}{online}=0;
     }
     elsif ($type eq "logout") {
-        $pen = int(20 * ($opts{rppenstep}**$rps{$username}{level}));
+        $pen = int(20 * penttl($rps{$username}{level}) / $opts{rpbase});
         if ($opts{limitpen} && $pen > $opts{limitpen}) {
             $pen = $opts{limitpen};
         }
@@ -2070,7 +2331,11 @@
 
 sub ha { # return 0/1 if username has access
     my $user = shift;
-    if (!defined($user) || !exists($rps{$user})) {
+    if (!defined($user)) {
+        debug("Error: Attempted ha() for undefined username");
+        return 0;
+    }
+    if (!exists($rps{$user})) {
         debug("Error: Attempted ha() for invalid username \"$user\"");
         return 0;
     }
@@ -2115,10 +2380,10 @@
                          "pair of gloves","set of leggings","shield",
                          "pair of boots");
             my $type = $items[rand(@items)];
-            if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) {
+            if (itemlevel($rps{$opp}{item}{$type}) > itemlevel($rps{$u}{item}{$type})) {
                 chanmsg("In the fierce battle, $opp dropped his level ".
-                        int($rps{$opp}{item}{$type})." $type! $u picks it up, ".
-                        "tossing his old level ".int($rps{$u}{item}{$type}).
+                        itemlevel($rps{$opp}{item}{$type})." $type! $u picks it up, ".
+                        "tossing his old level ".itemlevel($rps{$u}{item}{$type}).
                         " $type to $opp.");
                 my $tempitem = $rps{$u}{item}{$type};
                 $rps{$u}{item}{$type}=$rps{$opp}{item}{$type};
@@ -2174,6 +2439,45 @@
     close(QF);
 }
 
+sub loadquestfile {
+    return unless ($opts{writequestfile} && -e $opts{questfilename});
+    open(QF,$opts{questfilename}) or do {
+        chanmsg("Error: Cannot open $opts{questfilename}: $!");
+        return;
+    };
+
+    my %questdata = ();
+    while (my $line = <QF>) {
+        chomp $line;
+        my ($tag,$data) = split(/ /,$line,2);
+        $questdata{$tag} = $data;
+    }
+    return unless defined($questdata{Y});
+
+    $quest{text} = $questdata{T};
+    $quest{type} = $questdata{Y};
+    if ($quest{type} == 1) {
+        $quest{qtime} = $questdata{S};
+    }
+    else {
+        $quest{stage} = $questdata{S};
+        my ($p1x,$p1y,$p2x,$p2y) = split(/ /,$questdata{P});
+        $quest{p1}->[0] = $p1x;
+        $quest{p1}->[1] = $p1y;
+        $quest{p2}->[0] = $p2x;
+        $quest{p2}->[1] = $p2y;
+    }
+    for my $i (0..3) {
+        ($quest{questers}->[$i],) = split(/ /,$questdata{'P'.($i+1)},2);
+        if (!$rps{$quest{questers}->[$i]}{online}) {
+            undef(@{$quest{questers}});
+            last;
+        }
+    }
+    close(QF);
+    writequestfile();
+}
+
 sub goodness {
     my @players = grep { $rps{$_}{alignment} eq "g" &&
                          $rps{$_}{online} } keys(%rps);
@@ -2206,14 +2510,14 @@
                      "pair of gloves","set of leggings","shield",
                      "pair of boots");
         my $type = $items[rand(@items)];
-        if (int($rps{$target}{item}{$type}) > int($rps{$me}{item}{$type})) {
+        if (itemlevel($rps{$target}{item}{$type}) > itemlevel($rps{$me}{item}{$type})) {
             my $tempitem = $rps{$me}{item}{$type};
             $rps{$me}{item}{$type} = $rps{$target}{item}{$type};
             $rps{$target}{item}{$type} = $tempitem;
             chanmsg(clog("$me stole $target\'s level ".
-                         int($rps{$me}{item}{$type})." $type while they were ".
+                         itemlevel($rps{$me}{item}{$type})." $type while they were ".
                          "sleeping! $me leaves his old level ".
-                         int($rps{$target}{item}{$type})." $type behind, ".
+                         itemlevel($rps{$target}{item}{$type})." $type behind, ".
                          "which $target then takes."));
         }
         else {
@@ -2232,16 +2536,6 @@
     }
 }
 
-sub fisher_yates_shuffle {
-    my $array = shift;
-    my $i;
-    for ($i = @$array; --$i; ) {
-        my $j = int rand ($i+1);
-        next if $i == $j;
-        @$array[$i,$j] = @$array[$j,$i];
-    }
-}
-
 sub writedb {
     open(RPS,">$opts{dbfile}") or do {
         chanmsg("ERROR: Cannot write $opts{dbfile}: $!");
@@ -2318,6 +2612,27 @@
         }
     }
     close(RPS);
+    open(ITEMS,">$opts{itemdbfile}") or do {
+        chanmsg("ERROR: Cannot write $opts{itemdbfile}: $!");
+        return 0;
+    };
+    print ITEMS join("\t","# x pos",
+                        "y pos",
+                        "type",
+                        "level",
+                        "age")."\n";
+    my $curtime = time();
+    for my $xy (keys(%mapitems)) {
+        for my $i (0..$#{$mapitems{$xy}}) {
+            my @coords = split(/:/,$xy);
+            print ITEMS join("\t",$coords[0],
+                                  $coords[1],
+                                  $mapitems{$xy}[$i]{type},
+                                  $mapitems{$xy}[$i]{level},
+                                  $curtime-$mapitems{$xy}[$i]{lasttime})."\n";
+        }
+    }
+    close(ITEMS);
 }
 
 sub readconfig {
@@ -2336,6 +2651,7 @@
             $line =~ s/^\s+//g;
             next() if !length($line); # skip blank lines
             ($key,$val) = split(/\s+/,$line,2);
+            $val = "" if !defined($val);
             $key = lc($key);
             if (lc($val) eq "on" || lc($val) eq "yes") { $val = 1; }
             elsif (lc($val) eq "off" || lc($val) eq "no") { $val = 0; }
