Index: /irpg/trunk/bot/.dbbackup/irpg.db1136544191
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136544191	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136544191	(revision 1444)
@@ -0,0 +1,2 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+DT	6nz5Tt5hmvaBk	1	0	bot v 1.45	600			0	0	324	91	0	0	0	0	0	0	0	1136544191	1136544191	0	0	0	0	0	0	0	0	0	0	n
Index: /irpg/trunk/bot/.dbbackup/irpg.db1136547764
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136547764	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136547764	(revision 1444)
@@ -0,0 +1,5 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+skalp	8f8IgfKPEuBJs	0	4	grand master	729	skalpetteMiam	skalpetteMiamalp@skalpette.registered.netrusk.net	1	3433	440	253	0	34	0	0	0	0	0	1136544326	1136544326	0	0	0	1	0	6	4	3	0	0	n
+faty	bzF6g8nEhQ9Gk	0	4	lol	708	fatman	fatman!~507684c3@irc.nainwak.org	1	3318	20	377	19	0	0	0	0	0	0	1136544440	1136544440	0	0	0	6	1	0	0	0	0	4	n
+Ksempac	fbCyjP0dLOweY	0	3	gars perdu	133	Ksempac	Ksempac!~K@APuteaux-151-1-48-50.w82-124.abo.wanadoo.fr	1	2727	12	403	0	0	0	0	0	0	0	1136545032	1136545032	4	0	0	0	0	3	0	0	1	0	g
+DT	6nz5Tt5hmvaBk	1	4	bot v 1.45	497	DT	DT!~tereutes@DT.registered.netrusk.net	1	3567	337	118	0	0	0	0	0	0	0	1136544191	1136544319	0	0	0	0	1	5	4	1	0	0	g
Index: /irpg/trunk/bot/.dbbackup/irpg.db1136556095
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136556095	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136556095	(revision 1444)
@@ -0,0 +1,6 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+skalp	8f8IgfKPEuBJs	0	6	grand master	1452	skalpette	skalpette!~skalp@skalpette.registered.netrusk.net	1	5423	441	240	0	34	0	0	0	0	0	1136544326	1136554100	0	5	0	1	0	6	4	9	0	0	n
+Colar	q4bNW52YkfCBQ	0	7	nelefan	334	Colar	Colar!~Colar@Colar.registered.netrusk.net	1	8293	77	384	0	0	0	0	0	0	0	1136547796	1136547796	0	6	3	0	0	1	0	6	8	7	n
+faty	bzF6g8nEhQ9Gk	0	9	lol	1055	fatman	fatman!~507684c3@irc.nainwak.org	1	11639	1	367	19	0	0	0	0	0	0	1136544440	1136544440	10	0	0	11	1	12	0	0	0	4	n
+Ksempac	fbCyjP0dLOweY	0	9	gars perdu	1548	Ksempac	Ksempac!~K@APuteaux-151-1-48-50.w82-124.abo.wanadoo.fr	1	11048	42	473	0	0	0	0	0	0	0	1136545032	1136545032	12	0	9	0	0	3	0	0	7	10	g
+DT	6nz5Tt5hmvaBk	1	9	bot v 1.45	842	DT	DT!~tereutes@DT.registered.netrusk.net	1	11891	337	189	0	0	0	0	0	0	0	1136544191	1136544319	13	11	0	8	1	5	4	1	0	8	g
Index: /irpg/trunk/bot/.dbbackup/irpg.db1136571383
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136571383	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136571383	(revision 1444)
@@ -0,0 +1,10 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+xOu	nAOKyq2D4OFkY	0	2	nainn	465	ikss	ikss!~ikss@iKsS.registered.netrusk.net	1	1640	399	59	0	0	0	0	0	0	0	1136568220	1136568220	0	0	0	3	0	0	0	1	0	0	g
+faty	bzF6g8nEhQ9Gk	0	10	lol	1467	fatman	fatman!~507684c3@irc.nainwak.org	0	14132	44	410	19	0	0	0	74	0	0	1136544440	1136544440	10	0	0	11	1	12	0	0	0	11	n
+Colar	q4bNW52YkfCBQ	0	12	nelefan	263	Colar	Colar!~Colar@Colar.registered.netrusk.net	1	22052	119	6	0	0	0	0	0	0	0	1136547796	1136547796	15	15	8	0	0	1	0	6	8	16	n
+Ksempac	fbCyjP0dLOweY	0	13	gars perdu	1297	Ksempac	Ksempac!~K@APuteaux-151-1-48-50.w82-124.abo.wanadoo.fr	1	24807	20	463	0	0	0	0	0	0	0	1136545032	1136545032	14	0	9	0	0	3	0	0	15	13	g
+Ethernum	fDji/TgPZFU8U	0	10	Assassin	1897	arwall	arwall!~nccngcg@arwall.registered.netrusk.net	1	13428	293	439	0	0	0	0	0	0	0	1136556430	1136556430	11	10	0	1	4	12	8	0	15	0	n
+skalp	8f8IgfKPEuBJs	0	12	grand master	3134	skalpette	skalpette!~skalp@skalpette.registered.netrusk.net	1	19182	400	238	0	34	0	0	0	0	0	1136544326	1136554100	11	5	0	7	15	6	4	9	17	0	n
+Tiseb	cXXq9N8T7BisI	0	0	Evil-Dwarf	445	Tiseb	Tiseb!~Tiseb@lns-bzn-39-82-255-6-205.adsl.proxad.net	1	155	446	19	0	0	0	0	0	0	0	1136569705	1136569705	0	0	0	0	0	0	0	0	0	0	n
+yom	CoGQQeCjQsGso	0	9	cliqueur	715	yom	yom!~yom@gudule.orbus.fr	1	12184	7	167	0	0	0	0	0	0	0	1136557674	1136557674	0	10	13	6	0	0	0	0	3	0	e
+DT	6nz5Tt5hmvaBk	1	13	bot v 1.45	200	DT	DT!~tereutes@DT.registered.netrusk.net	1	25654	320	243	0	0	0	0	0	0	0	1136544191	1136544319	13	11	0	16	1	12	15	17	0	8	g
Index: /irpg/trunk/bot/.dbbackup/irpg.db1136607387
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136607387	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136607387	(revision 1444)
@@ -0,0 +1,14 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+rophle	PyqKeqFH0iwj6	0	13	guerrier en carton	777	rophle	rophle!~rophle@Rophle.registered.netrusk.net	0	21813	170	447	0	0	0	0	109	0	0	1136571832	1136571832	13	8	14	4	0	0	18	12	0	3	n
+xOu	nAOKyq2D4OFkY	0	17	nainn	6613	ikss	ikss!~ikss@iKsS.registered.netrusk.net	1	37389	160	232	0	0	0	0	125	0	0	1136568220	1136596297	16	14	15	3	9	17	20	17	0	4	g
+Colar	q4bNW52YkfCBQ	0	19	nelefan	4677	Colar	Colar!~Colar@Colar.registered.netrusk.net	1	57997	146	98	0	0	0	0	0	0	0	1136547796	1136547796	15	15	21	0	0	21	16	21	27	16	g
+faty	bzF6g8nEhQ9Gk	0	10	lol	1467	fatman	fatman!~507684c3@irc.nainwak.org	0	14132	44	410	19	0	0	0	74	0	0	1136544440	1136544440	10	0	0	11	1	12	0	0	0	11	n
+Madj	kb5PIL/pBRegc	0	5	dormeur	131	Madj`	Madj`!~mulet@ARennes-252-1-21-132.w83-195.abo.wanadoo.fr	0	5002	353	47	370	0	0	0	38	0	0	1136579567	1136579567	3	7	0	0	4	0	0	1	0	4	n
+Ksempac	fbCyjP0dLOweY	0	18	gars perdu	4649	Ksempac	Ksempac!~K@quasempaque.registered.netrusk.net	0	50129	474	27	0	0	0	0	336	0	0	1136545032	1136576781	14	0	23	0	0	16	0	0	22	21	g
+katcha	78spSlK4qhqNU	0	16	serial-idler	1208	katcha	katcha!~katcha@katcha.registered.netrusk.net	1	35530	422	490	0	0	0	0	0	0	0	1136571798	1136571798	11	8	19	6	18	17	20	15	0	10	g
+Ethernum	fDji/TgPZFU8U	0	10	Assassin	1897	arwall	arwall!~nccngcg@arwall.registered.netrusk.net	0	13428	293	439	0	0	0	0	0	0	0	1136556430	1136556430	11	10	0	1	4	12	8	0	15	0	n
+skalp	8f8IgfKPEuBJs	0	19	grand master	7723	skalpette	skalpette!~skalp@skalpette.registered.netrusk.net	1	55127	431	200	221	34	0	0	0	0	0	1136544326	1136554100	11	19	18	25	15	6	4	9	17	22	e
+Tiseb	cXXq9N8T7BisI	0	10	Evil-Dwarf	2459	Tiseb	Tiseb!~Tiseb@lns-bzn-39-82-255-6-205.adsl.proxad.net	0	11617	87	454	54	0	741	0	0	0	0	1136569705	1136575205	10	0	3	8	0	7	7	6	11	4	e
+yom	CoGQQeCjQsGso	0	17	cliqueur	2537	yom	yom!~yom@gudule.orbus.fr	1	42461	308	127	0	0	0	0	0	0	0	1136557674	1136577052	0	10	22	20	0	15	15	21	16	0	e
+Mickey	U1lW9O/UJPYQM	0	14	Souris	327	Mickey	Mickey!~amphi@l02v-213-44-144-152.d4.club-internet.fr	1	26995	93	308	0	0	0	0	0	0	0	1136580335	1136580335	21	9	7	2	4	15	14	6	0	15	n
+DT	6nz5Tt5hmvaBk	1	19	bot v 1.45	1567	DT	DT!~tereutes@DT.registered.netrusk.net	1	61595	274	451	609	0	0	0	0	0	0	1136544191	1136544319	13	24	0	27	1	19	15	17	0	8	g
Index: /irpg/trunk/bot/.dbbackup/irpg.db1136636702
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136636702	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136636702	(revision 1444)
@@ -0,0 +1,2 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+DT	mYUNQtOnudT.s	1	0	bot	600			0	0	246	446	0	0	0	0	0	0	0	1136636702	1136636702	0	0	0	0	0	0	0	0	0	0	n
Index: /irpg/trunk/bot/.dbbackup/irpg.db1136744409
===================================================================
--- /irpg/trunk/bot/.dbbackup/irpg.db1136744409	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/irpg.db1136744409	(revision 1444)
@@ -0,0 +1,2 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+DT	mYUNQtOnudT.s	1	0	bot	705	DT	DT!~tereutes@DT.registered.netrusk.net	1	95	248	437	0	0	200	0	0	0	0	1136636702	1136636800	0	0	0	0	0	0	0	0	0	0	n
Index: /irpg/trunk/bot/.dbbackup/mapitems.db1136607387
===================================================================
--- /irpg/trunk/bot/.dbbackup/mapitems.db1136607387	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/mapitems.db1136607387	(revision 1444)
@@ -0,0 +1,5 @@
+# x pos	y pos	type	level	age
+394	193	pair of boots	10	58
+359	450	pair of boots	8	120
+381	206	weapon	8	133
+144	243	helm	13	283
Index: /irpg/trunk/bot/.dbbackup/mapitems.db1136636702
===================================================================
--- /irpg/trunk/bot/.dbbackup/mapitems.db1136636702	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/mapitems.db1136636702	(revision 1444)
@@ -0,0 +1,1 @@
+# x pos	y pos	type	level	age
Index: /irpg/trunk/bot/.dbbackup/mapitems.db1136744409
===================================================================
--- /irpg/trunk/bot/.dbbackup/mapitems.db1136744409	(revision 1444)
+++ /irpg/trunk/bot/.dbbackup/mapitems.db1136744409	(revision 1444)
@@ -0,0 +1,1 @@
+# x pos	y pos	type	level	age
Index: /irpg/trunk/bot/.irpg.conf
===================================================================
--- /irpg/trunk/bot/.irpg.conf	(revision 1444)
+++ /irpg/trunk/bot/.irpg.conf	(revision 1444)
@@ -0,0 +1,256 @@
+# Configuration file for IRPG bot. Prefix comments with a #. Line must start
+# with a # to be a comment (no leading spaces and no comments starting in the
+# middle of a line).
+#
+# If you don't personally know your admins, or you're just not the trusting
+# type, you may want to look at the ownerpevalonly, owneraddonly, and
+# ownerdelonly options. ownerpevalonly prevents non-owner accounts from using
+# the PEVAL command, which can allow admins to execute arbitrary code under the
+# username that the bot runs as. owneraddonly prevents non-owner accounts from
+# assigning admin status to users. ownerdelonly prevents non-owner accounts from
+# removing admin status from users
+#
+# 'disablepeval' option was renamed to 'ownerpevalonly'
+#
+# Command line options override options in this file.
+
+# 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
+
+# set language
+lang fr_FR
+
+# local hostname or address to bind to. leave blank or comment out if you don't
+# want to use a vhost
+#localaddr myvhost.domain.com
+
+# server name:port, enter as many as you like
+server irc.nainwak.org:6667
+
+# bot's nickname
+botnick Schlavbeuk2
+
+# bot's username
+botuser grand_schtroumpf
+
+# real name field
+botrlnm http://zZzZ.nainwak.com/
+
+# channel name (followed by key, if your channel uses a key
+botchan #puolb
+
+# (identify) command to send upon successful connect. if using a privmsg
+# command, you must begin the text of the message with a ":" -- see below
+botident PRIVMSG NickServ :identify whatthefuck
+
+# modes to set bot upon successful connect
+botmodes +ix
+
+# command to send upon joining channel. %botnick% will evaluate to the bot's
+# current nickname, so you don't have to worry about opping the wrong person. if
+# using a privmsg command, you must begin the text of the message with a ":" --
+# see below
+botopcmd PRIVMSG ChanServ :op %botchan% %botnick%
+
+# command sent to recover nick if bot's primary nickname is in use if using a
+# privmsg command, you must begin the text of the message with a ":" -- see
+# below
+botghostcmd PRIVMSG NickServ :ghost %botnick% whatthefuck
+
+# URL to send users to for help
+helpurl http://zzz.nainwak.com/
+
+# admin commands list (for admin help)
+admincommurl http://zzz.nainwak.com/admincomms.txt
+
+# 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
+
+# penalty time = penalty * (rppenstep ** CURRENT_LEVEL)
+rppenstep 1.14
+
+# player database file
+dbfile irpg.db
+
+# item database file
+itemdbfile mapitems.db
+
+# where quests/godsends/calamities are stored
+eventsfile eventsnainwak.txt
+
+# debug mode on/off flag, merely prints what text was received, what queue
+# number outgoing text is given, and what text is sent to server (to the
+# debug file, no longer to STDOUT)
+debug off
+
+# choose filename to send debug output to. text is appended to this file
+# while the bot is in debug mode, in lieu of STDOUT
+debugfile debug.txt
+
+# Use URL-type banning for non-logged-in users that have been on the channel
+# less than 90 seconds?
+doban on
+
+# URLs containing these terms will not be banned by the 'http:'
+# advertisement ban (if you have it turned on). enter as many as you like
+okurl nainwak.org
+okurl netrusk.net
+okurl nainwak.com
+
+# modes of silence. in mode 0, bot sends all privmsgs. in mode 1, only
+# chanmsg() is disabled. in mode 2, only privmsg() to non-channels is
+# disabled. in mode 3, privmsgs to users and channels are disabled
+silentmode 0
+
+# write quest info file? all this file does is give outside programs info
+# about the active quests, its participants, their positions, and time to
+# completion
+writequestfile on
+
+# filename for the above-mentioned file
+questfilename questinfo.txt
+
+# voice users on login (and register)? if you like, you can set your channel
+# +m, then +v clients as they login, cutting down on spam. however, if your
+# users generally bring in a second client to chat with, that client won't
+# be able to speak in the channel
+voiceonlogin on
+
+# disallow usernames and character classes with control codes (bold, color,
+# underline, bell, etc)?
+noccodes on
+
+# disallow usernames and character classes that contain "non-printable"
+# characters? it's a good idea to leave this option on, as I have had
+# problems in the past with using binary hash keys
+nononp on
+
+# URL where users can reach the online quest map, if available. if not
+# offering a map to users, leave this blank
+mapurl http://zzz.nainwak.com/quest.php
+
+# allow a STATUS command for users? this is a p0 command to view information
+# on an irpg user. useful if you don't have a website where users can view
+# their stats
+statuscmd off
+
+# filename to write our PID to. leave blank or comment out if pidfile is
+# unnecessary to you
+pidfile .irpg.pid
+
+# attempt to reconnect if disconnected?
+reconnect on
+
+# seconds to wait before attempting to reconnect? don't hammer your irc
+# network, please; 90+ seconds is suggested
+reconnect_wait 120
+
+# this is what the bot considers to be an "internal clock" of sorts. some
+# examples of where this is used: $freemessages lines of text from the
+# message queue are sent every self_clock seconds; every self_clock seconds,
+# the players move on the map (self_clock times to simulate movement every
+# second); HOGs, calamities, godsends, etc. are given a chance to occur
+# every self_clock seconds; and the list goes on. if you have problems with
+# the bot flooding off, try increasing this number to 4 or 5. if your bot
+# appears to 'lag' because it is queueing too much text, you can set this
+# as low as 1. this must be an integral value (no fractions), and it must be a
+# factor of 60 (or certain events will not occur, like database rewrites)
+self_clock 3
+
+# file into which character modifier texts are appended
+modsfile modifiers.txt
+
+# disallow the registration of usernames already existing in a different
+# case? ie, jon == Jon == JON
+casematters on
+
+# allow rudimentary netsplit detection, and a) give no penalty and b) log
+# them back in upon return? I always suggest to users that they switch to
+# the server that the bot is on, but this has been a frequent request, so.
+# will pick up quit messages that match /^\S+\.\S+ \S+\.\S+$/. if your
+# 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
+splitwait 900
+
+# allow non-admin users some information on the bot, such as the server it
+# is connected to and the nicknames of online admins via a p0 INFO command?
+allowuserinfo on
+
+# ignore the new scaling features and use the old method for calculating the
+# odds of events occurring? if you have a very large game and were
+# comfortable with the speed that HoGs, Godsends, Calamities, etc. were
+# occurring, you may want to set this
+noscale off
+
+# allow bot to access http://jotun.ultrazone.org/g7/count.php?new=1 each
+# time someone registers a new username? it only takes a second, and I'd
+# really like to be able to keep up with the total player count :^)
+phonehome on
+
+# username of the bot's owner. this account cannot be DELADMINed and has access
+# to PEVAL even if it is disabled
+owner DT
+
+# disable the PEVAL command for non-owner accounts? this command allows the
+# execution of arbitrary Perl code by bot admins, effectively giving them
+# complete control of the account under which the bot runs. I prefer to leave
+# this command available and choose my admins with care, but, whatever :^)
+ownerpevalonly off
+
+# only owner account can use the MKADMIN command to assign admin status to
+# users?
+owneraddonly on
+
+# only owner account can use the DELADMIN command to remove admin status from
+# users?
+ownerdelonly on
+
+# check for newer versions each time the bot starts up? this will access the
+# URL http://jotun.ultrazone.org/g7/version.php?version=$version and report on
+# any updated versions and what features there are/bugs have been fixed
+checkupdates off
+
+# send list of usernames that are automatically logged back in when we restart
+# (iff that list is < 1 k)? this should, hopefully, no longer cause the bot to
+# flood off. the function that sends text to the server will no longer send more
+# than 768 bytes to the server every self_clock seconds. the old function would
+# send as much as 5 * 512b or 2.5k each self_clock seconds, and as this was
+# usually the feature that caused such a large amount of text to be sent at
+# once, if your channel had a large amount of users, this would cause it to
+# flood off. so, though I think the bug is fixed, I offer the option to turn
+# this off
+senduserlist on
+
+# limit maximum amount of penalty for one event? this will prevent a user from
+# being penalized more than <X> seconds for one event: part, privmsg, notice,
+# kick, etc. set to 0 if you want to disable limiting.
+# 604800 == 7 * 86400 == 1 week
+limitpen 604800
+
+# if you would like a custom-sized map, define the width of your map here:
+mapx 500
+
+# if you would like a custom-sized map, define the length of your map here:
+mapy 500
+
+# specify modes / line. the bot will override this from what it grabs from the
+# server's 005 numeric, though, if anything. used only for auto-login voicing
+modesperline 3
Index: /irpg/trunk/bot/.irpg.pid
===================================================================
--- /irpg/trunk/bot/.irpg.pid	(revision 1444)
+++ /irpg/trunk/bot/.irpg.pid	(revision 1444)
@@ -0,0 +1,1 @@
+11879
Index: /irpg/trunk/bot/ChangeLog.txt
===================================================================
--- /irpg/trunk/bot/ChangeLog.txt	(revision 1444)
+++ /irpg/trunk/bot/ChangeLog.txt	(revision 1444)
@@ -0,0 +1,678 @@
+This is the changelog for the Idle RPG bot by jotun, jotun@idlerpg.net,
+http://idlerpg.net. Entries are written backwards. That is, items at the bottom
+of the file were added first, and each subsequent addition is placed on a line
+before it. Don't ask me why I do it that way: I do not know.
+
+Thanks for your interest in the Idle RPG! Feel free to contact me with ideas and
+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)
+  - quest() now calls writequestfile() when it completes
+  - loaddb() now calls backup() before it starts
+
+
+--------------------------------------------------------------------------------
+  v3.1.1: released 6/3/04
+--------------------------------------------------------------------------------
+  - fixed 2 typos having to deal with die() messages when reading the config
+    file (meij, et al)
+  - quest() now calls writequestfile() when it completes so that it is
+    immediately available
+  - loaddb() now calls backup() before it starts so you always have an
+    up-to-date backup if your bot mangles your db
+  - readconfig() now strips out \r and \n (meij, kylemson, et al)
+
+
+--------------------------------------------------------------------------------
+  v3.1.0: released 6/2/04
+--------------------------------------------------------------------------------
+  - added a config file so you don't have to re-setup configuration options
+    everytime you upgrade. added a function readconfig() to read this file or
+    die() if it cannot be found
+  - SIGHUP will now call readconfig()
+  - added a DELADMIN command to remove admin status from a username. format:
+        /msg bot DELADMIN <account>
+    owner account cannot be DELADMINed. DELADMIN may be limited to owner account
+    depending on configuration (TGS)
+  - added an 'owner' account option. owner cannot be DELADMINed, among other
+    things
+  - added an 'owneraddonly' option. if set, only owner account can MKADMIN
+  - added an 'ownerdelonly' option. if set, only owner account can DELADMIN
+  - renamed disablepeval to ownerpevalonly. if set, only owner account can PEVAL
+  - added missing command line options for %opts options
+  - fq() will limit itself to either 1 message or <= 768 bytes output per call,
+    regardless of $freemessages
+  - added an option to turn off the sending of the list of users automatically
+    logged back in on a bot restart, even if the list < 1 k
+  - added an option to limit the penalty a single event can incur, 'limitpen'.
+    set to 0 to disable the feature, otherwise is taken to be an integral number
+    of seconds
+  - Win32 no longer tries to turn terminal echo on/off via "stty"
+  - tarball now unpacks files to their own directory instead of .
+  - removed unused $v, $debug variables
+  - auto-login would not voice users that it logged in even if voiceonlogin was
+    set, fixed (wogi, et al)
+  - fixed a comment having to do with auto login
+  - $rpreport should not have been adjusted by $curtime, but by
+    $opts{self_clock} to keep it reliable. this may have caused some loss of
+    data as the bot neglected to properly backup its database. so sorry! should
+    now be fixed (many)
+  - item godsend was removing 10% of item's value instead of adding it. another
+    copy/paste error. sorry! (Jim Dew)
+  - fixed a bug that resulted in users that caused quests to fail not being
+    penalized, as they were set as 'offline' before the bot had a chance to
+    penalize them (Jim Dew)
+  - added options to define the size of your IRPG grid, 'mapx' and 'mapy' (Rick)
+  - team battle now shows roll/sum like other battles (anonymous @ forum)
+  - added REHASH command to call readconfig()
+  - added an option to define the number of modes per line to use when voicing
+    users after an auto login. this variable may be overriden by the server's
+    directive (Rick)
+
+
+--------------------------------------------------------------------------------
+  v3.0.2: released 5/30/04
+--------------------------------------------------------------------------------
+  - calls evilness() and goodness() should have been checking against the number
+    of online evil users and good users respectively, not the total number of
+    online users (SickMind)
+  - changed max length of auto-login text to 1k before bot will refuse to send
+  - fixed a problem with the bot penalizing the kicker instead of the kickee
+    when someone was kicked (Preston, anonymous, Soc @ idlerpg.net forum)
+
+
+--------------------------------------------------------------------------------
+  v3.0.1: released 5/29/04
+--------------------------------------------------------------------------------
+  - forgot to add some sort of mechanism for setting up admins for new bots.
+    whoops. bot will now prompt for an owner's account details if it cannot find
+    $opts{dbfile} (Secret, anonymous @ idlerpg.net forum)
+  - fixed a typo in the ChangeLog ;)
+  - Vayanla noted that there was STILL a time discrepancy for very large games
+    (or very slow computers). many thanks to him for his help! this is now
+    fixed
+
+--------------------------------------------------------------------------------
+  v3.0: released 5/29/04
+--------------------------------------------------------------------------------
+  - fixed a bug causing RESTART not to work unless the bot's filename happened
+    to be the same as its nickname
+  - item modifiers as well as time modifiers are now stored in the modifiers
+    file. changed name of tlog sub to clog (time -> character)
+  - changed database write to every minute instead of every $opts{self_clock}.
+    to lower the chance of lost stats, the bot calls writedb() if you request a
+    DIE, JUMP, or RESTART. this should cut down on much of the cpu usage
+  - added a function writedb() which writes out the bot's db from memory so it
+    can be done outside of rpcheck()
+  - the team battle code would choose 6 random users to participate in a team
+    battle, but would not then randomize these users as far as teams go. that
+    is, if a username generally appeared at the end of a keys(%rps) list, and
+    made it into the list of 6 random users, that user would always defend
+    instead of attacking, as he would be at the end of the list. the list of 6
+    users is now shuffled using a Fisher-Yates shuffle, code from The Perl
+    Cookbook (by O'Reilly. a really great read!) (Peter Beentje)
+  - added a "user alignment" feature. users may align with good, neutral, or
+    evil. 'good' users have a 10% boost to their item sum for battles, and a
+    1/12 chance each day that they, along with a 'good' friend, will have the
+    light of their god shine upon them, accelerating them 5-12% toward their
+    next level. 'evil' users have a 10% detriment to their item sum for battles
+    (ever forsaken in their time of most need...), but have a 1/8 chance each
+    day that they will either a) attempt to steal an item from a 'good' user
+    (whom they cannot help but hate) or b) be forsaken (for 1-5% of their TTL)
+    by their evil god. after all, we all know that crime doesn't pay. also,
+    'good' users have only a 1/50 chance of landing a critical strike when
+    battling, while 'evil' users (who always fight dirty) have a 1/20 chance.
+    neutral users haven't had anything changed, and all users start off as
+    neutral. to change your alignment:
+        /msg bot ALIGN <good|neutral|evil>
+    I haven't run the numbers to see which alignment it is better to follow, so
+    the stats for this feature may change in the future (FishyTowel @
+    idlerpg.net forum)
+  - added new item, Juliet's Glorious Ring of Sparkliness, item level 50-74,
+    required user level 25+, chance 1/40, tag 'h'
+  - rather than error when PEVAL produces > 15 lines of output, PEVAL will now
+    queue its text if lines of output created >= 4 or size of text > 1k
+  - LOGIN command now responds via notice rather than privmsg
+  - added "named items," meaning that unique items have a letter appended to
+    them, saying which unique item they are. Mattt's Omniscience Grand Crown is
+    "a," Res0's Protectorate Plate Mail is "b," Dwyn's Storm Magic Amulet is
+    "c," Jotun's Fury Colossal Sword is "d," Drdink's Cane of Blind Rage is "e,"
+    Mrquick's Magical Boots of Swiftness is "f," and Jeff's Cluehammer of Doom
+    is "g"
+  - changed split() on incoming data to split on /\s/ instead of / /; users
+    could otherwise register usernames or classes containing tabs, which would
+    cause the bot to die when reading the (tab-delimited) database (chris young)
+  - changed the SIGHUP handler from '0' to 'sub { };'. should eliminate the
+    "Signal handler '0' not defined" warning (too many to list)
+  - added an item calamity and an item godsend. if you are calamitized, you have
+    a 10% chance of one of your amulet, charm, weapon, tunic, set of leggings,
+    or shield losing 10% of its item value. if you are godsent, you have a 10%
+    chance that one of the above items will gain 10% of its item value (carl
+    wyles @ idlerpg.net forum)
+  - %botnick% in $opts{botopcmd} will be evaluated to the bot's current nickname
+    to avoid opping another, more evil user when the bot's nickname is in use
+  - added an option to give non-admin users limited access to the INFO command.
+    when enabled, non-admin users can use the INFO command to learn to which
+    server the bot is connected and the nicknames of online admins (mike @
+    idlerpg.net forum)
+  - added an option to disallow the registration of usernames and classes
+    containing "non-printable" characters. it's a good idea to leave this option
+    on, as I have had problems in the past with using binary hash keys (TGS)
+  - whenever a non-admin players walks over an admin player on the map, he/she
+    has a 1% chance to bow ;) (mike stewart)
+  - changed sending of WHO and $opts{botopcmd} from numeric 001 to receipt of
+    bot's JOIN
+  - added an option to disable the PEVAL command for users that want to have
+    less than trustworthy admins ;^) (TGS)
+  - Run noted that (undernet?) servers allow you a certain number of "free"
+    messages before output should be limited to 1 message / 2 seconds. fq() now
+    sends as many of these "free" messages as it can, rather than sending only
+    one message per call (Run)
+  - removed some odd sts("MODE $opts{botchan}"); -- not sure why i put that in
+  - added rudimentary netsplit detection, which a) gives no penalty and b) logs
+    users in upon return. will pick up quit messages that match
+    /^\S+\.\S+ \S+\.\S+$/. if your network (or server) does not prefix quit
+    messages with "Quit: " (or some other string), or otherwise disallows faked
+    netsplit quit messages, then users can cheat this at their whim. added
+    option to turn netsplit detection on or off. added option of how long to
+    wait before automatically logging split users out and forgetting they ever
+    existed. added sub checksplits() which will iterate over the list of split
+    nick!user@hosts, remove those which have expired ($opts{splitwait}), and log
+    the user out. would love input on this feature, as i expect bugs
+  - HELP command for non-admins is now less helpful. generates one line of text
+    containing URL for help
+  - attempting to PUSH a user more than their TTL now sets their TTL to 0 as
+    well as generating a notice to the admin. successful PUSH now only
+    chanmsg()s instead of chanmsg()ing and privmsg()ing the admin
+  - $arg[3] changed to lowercased, leading-:-stripped $arg[3] in privmsg block
+  - cleaned up more code. changed (most) elses elsifs where appropriate. cleaned
+    up some logic. dropped all uses of next(). attempted to add () to function
+    calls wherever it was missing
+  - private messages and notices to the bot no longer penalize you (mrChewie)
+  - changed ha() to find access by username instead of nickname
+  - added a finduser() sub to return a logged-in username matching a given
+    nickname.
+  - changed case of $arg[1] after PONG rather than lc()ing it for every
+    comparison (mrChewie)
+  - at least 15% of all online players must be level 45+ for the hourly battle
+    of a level 45+ player to occur (anonymous @ idlerpg.net forum)
+  - fixed a serious bug with the bot not tracking changes to its nick (ie, by
+    NickServ or PEVAL) -- this caused all messages sent to the bot to be
+    penalized (TGS)
+  - added $opts{casematters}, which, when set, will not allow the registration
+    of usernames that already exist in a different case (MeBeHere)
+  - changed db backup, top players report to every 10 hours
+  - added $opts{modsfile}, which is where Time Modifier texts are appended.
+    also, tlog() now debug()s and chanmsg()s an error message if it cannot open
+    the file (MeBeHere)
+  - HOG was only 5-74%, corrected to 5-75% of TTL
+  - added an option $opts{self_clock} which is rather like the old $alrmint var,
+    except this probably works without exploding if you change it. calamities,
+    godsends, etc. should take this number into account when calculating odds
+  - added a server list rather than static server. bot should try each server in
+    the server list twice before giving up (meij)
+  - added a PID filename option, to which the bot will write its PID (meij)
+  - added a few more cattle-themed calamities (anonymous @ idlerpg.net forum)
+  - added a trailing '!' to godsend() text instead of the drab old '.'
+  - added some new quests/calamities/godsends contributed by users. edited some
+    old calamities/quests/godsends. please feel free to post your ideas for more
+    to http://idlerpg.net/forum.php! (anonymous, Afbc0m, anjira, jv, mrChewie,
+    W8TVI, et al)
+  - attempted to scale occurences of HOGs, godsends, calamities, and team
+    battles. HOGs should occur about 1/user/20 days, calamities 1/user/8 days,
+    godsends 1/user/4 days, team battles 1/user/4 days. does this by calculating
+    the odds of a HOG as rand(20*86400/clock) < NUMBER_ONLINE_PLAYERS, odds of
+    calamities as rand(8*86400/clock) < NUMBER_ONLINE_PLAYERS, etc. this appears
+    to work great with at least 10 clients (tested up to 300), but doesn't seem
+    to work as well below that. would appreciate input
+  - debug information is now written to a file instead of STDOUT. bot now
+    daemonizes even though it is in debug mode. added sub debug() which takes an
+    argument of text to write to the debug file (yeoj)
+  - added new item, Jeff's Cluehammer of Doom, item level 300-350, required user
+    level 52+, chance 1/40
+  - bot will try to regain primary nickname if he sees it come open through a
+    /nick or /quit
+  - DELOLD command will removed non-logged-in accounts that have not been logged
+    into in more than N days. format is:
+        /msg bot DELOLD <N days>
+    DELOLD is a p0, admin command (DinTn)
+  - added option to enable a STATUS command. this p0 command gives information
+    on a user, such as level, class, time to level, item sum, etc. useful for
+    those IRPGs that lack a website. format is:
+        /msg bot STATUS [username]
+    if username argument is not passed, returns information on the user issuing
+    command. must be logged in to use STATUS (TGS)
+  - possibly added option to choose which local address/hostname and local port
+    to bind to. let me know if this does/doesn't work (DARKutz, Brad)
+  - added security note to head of file
+  - will not send auto-login user list if text > 2048 bytes
+  - added $opts{botmodes} which will set the bot's usermode to given string on
+    connect
+  - removed reset of last login time on auto-login; last login should be when
+    user last logged in, not when the bot logged them back in
+  - levels after level 60 have a next time to level of (time to level @ 60) +
+    (1 day) * (level - 60). levels below 60 have not changed. the exponential
+    code was getting a little too heavy by itself (TGS)
+  - RELOADDB would log all players out; fixed
+  - sts() & fq() check state of socket before attempting write. if cannot write,
+    outgoing queue is cleared
+  - added new item, Mrquick's Magical Boots of Swiftness, item level 250-300,
+    required user level 48+, chance 1/40
+  - debug messages are now timestamped. added a few extra debug messages, mostly
+    for fun
+  - top player report no longer occurs immediately after startup, but every 6
+    hours from then on
+  - added option to disallow registration of usernames/character classes
+    containing ctrl codes (Skill0)
+  - changed auto-login code from list to hash. would take several minutes to
+    synch to a channel with hundreds of users. now takes < 1s (Vayanla et al)
+  - removed the hard-coded "#G7" from the top players list.. whoops :^) that's
+    been there for some time now, can't believe no one ever noticed (HaRRo)
+  - added option to voice users on login/register. if you +m your channel, this
+    will cut down on spam, but won't allow non-logged-in or devoiced/deopped
+    clients to talk (pingh, wishes, aphade, et al)
+  - added a penalize() sub to make penalties a little cleaner
+  - added %quest hash to keep up with active quest info
+  - added option to write active quest info to file; this makes it readable by
+    outside programs
+  - can now specify multiple words for the advertisement ban, or turn feature
+    off entirely
+  - removed all alarm() code, should now run on any system supporting select(2)
+    (including, but not limited to, Win32). should also fix a nasty, terrible
+    time drift bug; with the amount of processing done in rpcheck(), the next
+    alarm() would come later than expected, awarding the user with idling less
+    than he had actually idled. it's a small amount, but adds up to about 205
+    seconds difference in the clocks after ~9.25 hours (in my tests; will differ
+    from machine to machine). MANY thanks to this bug's reporter, Ville
+    Luolajan-Mikkola
+  - added CLEARQ command, will clear all items in outgoing message queue
+  - INFO shows outgoing queue status, registrations this period, and total users
+  - cleaned up options section a little bit, made a few other code clean-ups
+  - created this file, ChangeLog.txt. the bot's code was getting too long
+  - registrations are now limited to 1 / sec. this should keep floodbots from
+    registering hundreds of accounts
+  - removed ALERT command
+  - removed $opts{admin} array, there is now a field in the db to mark this.
+    there is also a MKADMIN command to set an account as having admin access.
+    syntax is:
+        /msg bot MKADMIN <username>
+    there is no way to remove an account's administrator privileges, save from
+    editing the database by hand (well, there is PEVAL). so, don't assign this
+    lightly. admins have full access to the account under which you run the bot
+  - die() call if could not write irpg.db is now a chanmsg() instead
+  - added an outgoing message queue. all messages are now output at 1 message /
+    second, unless a non-zero $skipq flag is passed to sts(). privmsg(),
+    notice(), and chanmsg() calls pass their $force flag as a $skipq flag.
+    PONGs pass a $skipq of 1 (LexCyber)
+  - fixed a rather huge bug that someone on slashnet noticed. registering
+    \001PING\001 (or any other ctcp) would send that CTCP to the channel, then
+    penalize any who responded. usernames may no longer include a \001
+  - godsends, calamities, and quests were all moved to one file,
+    $opts{eventsfile}. quests are prefixed with a Q1 or Q2, depending on their
+    type; calamities with a C; and godsends with a G. quests are also prefixed
+    by their required coordinates if the quest type is 2
+  - fixed a typo in the Dwyn's Storm text
+  - changed code indentation to four spaces. reworked a lot of code to fit <= 80
+    columns
+  - changed QUEST command and quest() output to be a little more grammar-
+    friendly
+  - item stealing! if you are level >= 20, and you win a battle against a
+    player, you have a slightly less than 2% chance of stealing an item from
+    them. the 2% comes from a) you have a 1/25 chance to attempt to steal an
+    item and b) you have a 50% chance that your item (type is random) is lower
+    than theirs. the reason it's 'slightly less than' 2% is because you cannot
+    both Critical Strike a user and steal an item, so it's (2 - (1/35))%. you
+    cannot steal an item of lower level than your current item (Afbc0m)
+  - fixed duration() to show 1 day without trailing 's'
+  - re-added report of TTL after battle and after critical strike
+  - quest() function now chanmsg()s an error if it cannot open the
+    $opts{eventsfile} file
+  - INFO command now uses privmsg() force flag
+  - added grid! thanks to Joakim @ orkut for this great idea. within the irpg
+    world are all of the players on a 500x500 "grid" or map. every second, your
+    character has an equal chance to step left, right, or neither, and an equal
+    chance to step up, down, or neither. if your character encounters another
+    player, you have a 1/(# of online players) chance to battle. also, some
+    quests require all characters to reach some point on the map. quest
+    penalties and awards have not changed
+  - added REMOVEME command for users. if you are logged in,
+       /msg bot REMOVEME
+    will delete your account. this is a p0 command :^)
+  - added NEWPASS command for users. if you are logged in,
+       /msg bot NEWPASS <new password>
+    will set a new password. this is a p0 command
+
+
+--------------------------------------------------------------------------------
+  v2.4.1: unreleased
+--------------------------------------------------------------------------------
+
+  - PEVAL will now error and refuse to send output > 15 lines. this is to avoid
+    my own errors
+
+
+--------------------------------------------------------------------------------
+  v2.4+fixes: released 2/20/04
+--------------------------------------------------------------------------------
+
+  - items are set to 0 on account creation; they were previous undefined
+  - bug with QUEST command fixed; would say no active quest even when a quest
+    was active
+
+
+--------------------------------------------------------------------------------
+  v2.4: released 10/13/03
+--------------------------------------------------------------------------------
+
+  - updated privmsg() function to avoid annoying substr()/uninitialized value
+    warnings
+  - few small bugs in battling bot fixed. a win against bot awards you with 20%
+    of your TTL removed. a loss to bot adds 10% of your TTL to your clock
+  - bot's item sum is now the highest item sum of all users + 1 (mumkin)
+  - fixed RESTART command to clear alarm() before trying to exec()
+  - WHOAMI displays class, TTL (Minhiriath)
+  - CALC command removed
+  - added notice() function which mirrors the operation of privmsg()
+  - SILENT command allows admin to switch bot between 4 modes of silence. in
+    mode 0, bot sends all privmsgs. in mode 1, only chanmsg() is disabled. in
+    mode 2, only privmsg()/notice() to non-channels is disabled. in mode 3,
+    privmsgs/notices to users and channels are disabled. silent mode is also
+    configurable as $opts{'silentmode'}, so you can setup a bot in any channel
+    without it interrupting the channel with its privmsgs (???)
+  - third parameter added to privmsg()/notice(); force flag ignores $silentmode
+  - hard-coded check for OKish URLs to bot's 'http:'-style banning now
+    configurable (sean)
+  - JUMP command no longer penalizes if required argument is left blank
+  - BACKUP admin command tells bot to copy $opts{'dbfile'} to
+    .dbbackup/$opts{'dbfile'}TIMESTAMP; added backup() function to handle this
+  - RELOADDB command allows admin to force bot to reload player database file,
+    rewriting all memory. RELOADDB can only be used while in pause mode
+  - PAUSE command allows admin to place bot into pause mode. in pause mode, bot
+    will update player stats, but will not write database. combined with
+    RELOADDB, very effective for updating all players stats through external
+    script without taking bot offline. new accounts cannot be registered
+    while in pause mode
+  - QUEST command (p0) tells the active quest, its participants, and its time
+    left to completion
+  - ban message for 'http:'-type bans now makes unban-time more clear
+  - things have been sped up a bit. random battles for users level 45+ now occur
+    every hour. random chance for HOG, Godsends, Calamities, and Team Battles
+    were increased by a factor of 5
+  - time between quests upped to 12 hours. level requirement for quests upped to
+    40+. in addition, must have been online for at least 10 hours to be selected
+    for quests. number of persons on quest lowered to 4. quest penalty is now a
+    p15 instead of 2% of your TTL. this makes more sense, as users who were very
+    close to leveling were penalized almost nothing (inkblot et al)
+  - fixed spelling of 'caffeinated' (sean)
+  - botchan variable now shows how to join channel with key (Dan)
+
+
+--------------------------------------------------------------------------------
+  v2.3.1: released 9/20/03
+--------------------------------------------------------------------------------
+
+  - fixed bug with item finding; bad logic sometimes resulted in user not
+    finding any item (thanks mumkin!)
+
+
+--------------------------------------------------------------------------------
+  v2.3: released 8/29/03
+--------------------------------------------------------------------------------
+
+  - Jotun's Fury max level dropped back to 174
+  - added the Drdink's Cane of Blind Rage with item level 175-200
+  - all time modifiers (battles, HoG, etc) are now written to modifiers.txt
+  - function tlog() logs a string to modifiers.txt and returns the string
+  - changed WHOAMI to not use $_
+  - fixed another bug where changing your nick would prevent you from being a
+    candidate for auto-login
+  - LOGOUT command added as a p20
+  - you may now only be logged in under one character at a time. this will help
+    protect the bot from being flooded when a single user signs on under 10
+    accounts, then is penalized and warned 10 times. attempts to login under two
+    names are not penalized
+  - fixed a bug where all of your accounts were automatically logged on so long
+    as they shared the same host as you, regardless of whether they were online
+    before (on bot restart)
+  - there is a 1/20,000 chance of a calamity occuring every 5 seconds. the
+    calamity() function chooses a random user, then smites them with bad luck.
+    the penalty for a calamity is a random 5-12% of next TTL. users are only
+    chosen from the pool of online players
+  - there is a 1/10,000 chance of a godsend occuring every 5 seconds. the
+    godsend() function chooses a random user, then betters their luck. the
+    award for a godsend is a random 5-12% of next TTL. users are only chosen
+    from the pool of online players
+  - there are now 'quests' -- six level 30+ users are chosen to go on a quest
+    at a time. if all six users make it to the quest's end, all questers are
+    awarded by removing 25% of their TTL (ie, their TTL at quest's end). to
+    complete a quest, no user can be penalized until the quest's end. quests
+    last a random time between 12 and 24 hours. if the quest is not completed,
+    ALL online users are penalized 2% of their time as punishment. users are
+    only chosen from the pool of online players (original idea from Nerje; quest
+    ideas from Tristan, brt)
+  - quests are read from file 'quests.txt' every time quest() is called. this
+    allows you to add or remove quests while the bot is still running. quests
+    are not picked in order, but chosen at random from the file
+  - fixed bug in PUSH, allowing to push into negative TTL
+  - db times changed to ctime format in lieu of scalar localtime() (now
+    sortable)
+  - added db fields for total time idled; total times penalized for privmsg,
+    nick change, part, kick, LOGOUT, quest, and quit; and time account created
+  - REGISTER no longer penalizes if you are already logged in and the command
+    fails
+  - fixed 'http:' checking to only look at message text, not entire string
+  - messages passed through privmsg() are split into 450-byte chunks and then
+    passed to their target
+  - bans put into place by the 'http:' method are now removed after 1 hour to
+    prevent filling the banlist. bans are stored in @bans, which will hold at
+    most 12 bans to prevent the bot from flooding on unban. after 12, bans are
+    still set, but not stored
+  - 'license' in header slightly changed
+  - battle results now include item sums and the random number rolled for each
+    player. format is [roll/sum]
+  - bot will try to regain his nickname every 30 mins if it is in use at
+    sign-on. added vars $primnick and $opts{'botghostcmd'}. $primnick is set
+    to $opts{'botnick'} (which may change) on load, and $opts{'botghostcmd'}
+    is a nickserv ghost command string
+  - the bot's nick ($opts{'botnick'} and $primnick) cannot be registered as
+    character names
+  - bot is now a fightable player. his item sum is random 250-650. (someone;
+    mail me if this was your idea). chances of fighting him are equal to
+    fighting any other player
+  - bot now daemonizes when starting (jwbozzy)
+  - fixed duration code to use the correct secs/day (drdink/inkblot)
+  - added a penalty to Team Battle. players will now receive or lose 20% of the
+    lowest team member's TTL (drdink)
+  - changed battling to award tie to challenger, not challengee. random number
+    is also, now, an integer, not a float
+  - every 3.5 hours, a level 45+, online player will battle; this will make it
+    easier for high-level users to level
+  - added function itemsum() to return item sum for supplied username
+  - battle results written to battles.txt are now timestamped (Juliet)
+
+
+--------------------------------------------------------------------------------
+  v2.2.2 (schmolli): released 7/18/03
+--------------------------------------------------------------------------------
+
+  * The changes in this version are based almost completely on a patch sent to
+    me by Ed Schmollinger, schmolli@IRC. Many thanks to him for his help! Here
+    are his changes:
+  - SECURITY: added subroutine mksalt to generate random salt for passwds
+  - CLEANUP: added subroutines chanmsg and privmsg to send messages to
+    bot's channel and to a specified user, respectively
+  - FEATURE: added command line argument processing and removed TEST_MODE
+    (TEST_MODE is no longer necessary.)  Part of this includes moving most
+    of the variables into %opts.
+  - FIX: added check for number of existing players when printing top 3
+  - CLEANUP: changed "in:" and "out:" debug message to "<-" and "->"
+  - CLEANUP: indented concatenated lines
+
+
+--------------------------------------------------------------------------------
+  v2.2.1: released 7/16/03
+--------------------------------------------------------------------------------
+
+  - fixed a bug in item finding; if unique item was better than helm, not
+    better than its class, you would get the item (emad)
+
+
+--------------------------------------------------------------------------------
+  v2.2
+--------------------------------------------------------------------------------
+
+  - added 1/20000 chance of 'team battle' every 5 seconds. team battle is 3
+    players versus 3 other players. if the first three players win, their time
+    is lowered by 20% of the lowest of the three's TTL. if they lose, no time is
+    removed from any players. there is no chance for critical strike in a team
+    battle (Asterax)
+  - max level of Jotun's Fury Colossal Sword changed to 175
+  - fixed 'kick' bug; users that were kicked were not logged out
+  - kick added as a p250
+  - bot now only bans those non-logged in users that say 'http:' that've been in
+    the channel < 90 seconds
+  - bot won't ban for #G7-type URLs
+  - bot now shows nick of user when new account is registered
+  - forgot to close filehandle in loaddb(); fixed
+  - added a db backup every 6 hours
+
+
+--------------------------------------------------------------------------------
+  v2.1.3
+--------------------------------------------------------------------------------
+
+  - fixed bug where users changing their nick would not be candidates for
+    auto-login on a bot restart
+  - changed some messages to make them more friendly to female players (LapCat)
+
+
+--------------------------------------------------------------------------------
+  v2.1.2
+--------------------------------------------------------------------------------
+
+  - HoG can now carry or displace a player 5 - 75% toward the next level
+  - fixed CTCP version bug
+  - battling was changed from all users within 7 levels of you to all online
+    users
+  - added "unique" items, or a chance starting at level 25 to roll
+    higher-than-normal items
+
+
+--------------------------------------------------------------------------------
+  v2.1.1
+--------------------------------------------------------------------------------
+
+  - DIE, JUMP, RESTART, INFO, and PEVAL now send warnings to users that don't
+    have access to tell them so. they are still penalized
+  - bot will now penalize users without the proper access that try to use an
+    admin command
+  - add commands CHCLASS, CHUSER, and PUSH to adjust class names, usernames,
+    and next time to level, respectively
+  - HoG could occur for offline users; this is no longer the case
+  - bot now responds to CTCP version requests (drdink)
+
+
+--------------------------------------------------------------------------------
+  v2.1
+--------------------------------------------------------------------------------
+
+  - bot bans non-logged-in users that say 'http:'
+  - INFO did not check ha(); fixed
+  - bot will automagically log you back in if you were logged in before a bot
+    restart, and if you haven't changed your nick!user@host since then
+  - removed logging
+  - dropped functions relating to old database in favor of the new one
+  - changed level up report from seconds to duration()
+  - changed item/userinfo db's to one file; battles still in battles.txt
+  - changed challenge report from seconds to duration()
+  - changed penalty text to display duration() instead of seconds
+  - added critical strike, 1/35 chance upon winning battle to cause opponent to
+    lose time (dwyn)
+  - changed summon text for HoG (res0)
+  - changed access to base off of irpg username in lieu of host
+  - changed top player report to every 6 hours
+  - changed positive HoG text (res0)
+  - changed random HoG chance to 1/20000 every 5 seconds
+
+
+--------------------------------------------------------------------------------
+  v2.0.3
+--------------------------------------------------------------------------------
+
+  - dropped top players back to 3
+  - removed STATUS; TTL available through website.
+  - battle history added to website; added logging of battles to battles.txt
+  - peval did not next(); fixed.
+  - added HOG command, randomly chooses someone, then randomly raises/lowers
+    their TTL (20% raise, 80% lower). HOG is, of course, an abbreviation for
+    Hand of God
+  - added a 1/7500 random HoG into rpcheck()
+
+
+--------------------------------------------------------------------------------
+  v2.0.2
+--------------------------------------------------------------------------------
+
+  - STATUS would log you out; fixed.
+  - could STATUS if not online; fixed.
+  - added DEL command to remove accounts
+  - added ALERT command to make channel alerts
+  - changed admin HELP command text to display website
+
+
+--------------------------------------------------------------------------------
+  v2.0.1
+--------------------------------------------------------------------------------
+
+  - fixed self-battle bug
+  - changed chance to battle from 20% to 25% if level < 25, 100% if >= 25
+  - setup companion website
+  - updated HELP command to reflect website
+  - changed battle gain to (max(7,opplevel/4)/100)*your_next_ttl
+  - added battle loss of (max(7,opplevel/7)/100)*your_next_ttl
+
+
+--------------------------------------------------------------------------------
+  v2.0
+--------------------------------------------------------------------------------
+
+  - added item finding and battling
+  - added penalties for QUIT, PART, instead of resetting time to the beginning
+    of that level
+
+
+--------------------------------------------------------------------------------
+  v1.0
+--------------------------------------------------------------------------------
+
+  - initial version
Index: /irpg/trunk/bot/README
===================================================================
--- /irpg/trunk/bot/README	(revision 1444)
+++ /irpg/trunk/bot/README	(revision 1444)
@@ -0,0 +1,62 @@
+--------------------------------------------------------------------------------
+First-time users:
+--------------------------------------------------------------------------------
+
+1. Using your favorite text editor, open the bot's source. Read the file header.
+   If you don't agree with the license, please delete the source and remove
+   each of your brain cells associated with it. Kthx.
+2. Open the file .irpg.conf and edit the bot's options to suit you. You must
+   also move this file into the same directory where the bot resides.
+3. Run it with: perl bot.filename.pl
+4. If you have problems, try running it in debug mode:
+   perl bot.filename.pl --debug
+   If you cannot diagnose the problem, post to http://idlerpg.net/forum.php
+5. Thanks for your interest in Idle RPG!
+
+--------------------------------------------------------------------------------
+IRPG 3.0 users looking to upgrade:
+--------------------------------------------------------------------------------
+
+1. Using your favorite text editor, open the bot's source. Read the file header.
+   If you don't agree with the license, please delete the source and remove
+   each of your brain cells associated with it. Kthx.
+2. Open the file .irpg.conf and edit the bot's options to suit you. You must
+   also move this file into the same directory where the bot resides.
+3. Replace your old bot source with the new one, ie, rm -f that old, buggy crap.
+4. Run it with: perl bot.filename.pl
+5. If you have problems, try running it in debug mode:
+   perl bot.filename.pl --debug
+   If you cannot diagnose the problem, post to http://idlerpg.net/forum.php
+6. Thanks for your interest in Idle RPG!
+
+
+--------------------------------------------------------------------------------
+IRPG 2.4 users looking to upgrade:
+--------------------------------------------------------------------------------
+
+1. Using your favorite text editor, open the bot's source. Read the file header.
+   If you don't agree with the license, please delete the source and remove
+   each of your brain cells associated with it. Kthx.
+2. Run the db conversion tool: perl irpgdbtool
+3. Answer the questions to suit you.
+4. Open the file .irpg.conf and edit the bot's options to suit you. You must
+   also move this file into the same directory where the bot resides.
+5. Run it with: perl bot.filename.pl
+6. If you have problems, try running it in debug mode:
+   perl bot.filename.pl --debug
+   If you cannot diagnose the problem, post to http://idlerpg.net/forum.php
+7. Thanks for your interest in Idle RPG!
+
+
+--------------------------------------------------------------------------------
+Pre-2.4 users looking to upgrade:
+--------------------------------------------------------------------------------
+
+1. Using your favorite text editor, open the bot's source. Read the file header.
+   If you don't agree with the license, please delete the source and remove
+   each of your brain cells associated with it. Kthx.
+2. I don't think the irpgdbtool will help you unless you're comfortable with
+   Perl, sorry. :/ If you are, though, you can pull the loaddb() sub from the
+   bot that you're currently using instead of using the loaddb() supplied in
+   irpgdbtool. You'll also need to add code to add in the other missing fields
+   that exist in v2.4.
Index: /irpg/trunk/bot/bot.v3.1.2.K.pl
===================================================================
--- /irpg/trunk/bot/bot.v3.1.2.K.pl	(revision 1444)
+++ /irpg/trunk/bot/bot.v3.1.2.K.pl	(revision 1444)
@@ -0,0 +1,2354 @@
+#!/usr/local/bin/perl
+# irpg bot v3.1.2 by jotun, jotun@idlerpg.net, et al. See http://idlerpg.net/
+#
+# Some code within this file was written by authors other than myself. As such,
+# distributing this code or distributing modified versions of this code is
+# strictly prohibited without written authorization from the authors. Contact
+# jotun@idlerpg.net. Please note that this may change (at any time, no less) if
+# authorization for distribution is given by patch submitters.
+#
+# As a side note, patches submitted for this project are automatically taken to
+# be freely distributable and modifiable for any use, public or private, though
+# I make no claim to ownership; original copyrights will be retained.. except as
+# I've just stated.
+#
+# Please mail bugs, etc. to me. Patches are welcome to fix bugs or clean up
+# the code, but please do not use a radically different coding style. Thanks
+# to everyone that's contributed!
+#
+# NOTE: This code should NOT be run as root. You deserve anything that happens
+#       to you if you run this code as a superuser. Also, note that giving a
+#       user admin access to the bot effectively gives them full access to the
+#       user under which your bot runs, as they can use the PEVAL command to
+#       execute any command, or possibly even change your password. I sincerely
+#       suggest that you exercise extreme caution when giving someone admin
+#       access to your bot, or that you disable the PEVAL command for non-owner
+#       accounts in your config file, .irpg.conf
+
+use strict;
+use warnings;
+use IO::Socket;
+use IO::Select;
+use Data::Dumper;
+use Getopt::Long;
+
+my %opts;
+
+readconfig();
+
+my $version = "3.1.2";
+
+# command line overrides .irpg.conf
+GetOptions(\%opts,
+    "help|h",
+    "verbose|v",
+    "debug",
+    "debugfile=s",
+    "server|s=s",
+    "botnick|n=s",
+    "botuser|u=s",
+    "botrlnm|r=s",
+    "botchan|c=s",
+    "botident|p=s",
+    "botmodes|m=s",
+    "botopcmd|o=s",
+    "localaddr=s",
+    "botghostcmd|g=s",
+    "helpurl=s",
+    "admincommurl=s",
+    "doban",
+    "silentmode=i",
+    "writequestfile",
+    "questfilename=s",
+    "voiceonlogin",
+    "noccodes",
+    "nononp",
+    "mapurl=s",
+    "statuscmd",
+    "pidfile=s",
+    "reconnect",
+    "reconnect_wait=i",
+    "self_clock=i",
+    "modsfile=s",
+    "casematters",
+    "detectsplits",
+    "splitwait=i",
+    "allowuserinfo",
+    "noscale",
+    "phonehome",
+    "owner=s",
+    "owneraddonly",
+    "ownerdelonly",
+    "ownerpevalonly",
+    "checkupdates",
+    "senduserlist",
+    "limitpen=i",
+    "mapx=i",
+    "mapy=i",
+    "modesperline=i",
+    "okurl|k=s@",
+    "eventsfile=s",
+    "rpstep=f",
+    "rpbase=i",
+    "rppenstep=f",
+    "dbfile|irpgdb|db|d=s",
+) or debug("Error: Could not parse command line. Try $0 --help\n",1);
+
+$opts{help} and do { help(); exit 0; };
+
+debug("Config: read $_: ".Dumper($opts{$_})) for keys(%opts);
+
+my $outbytes = 0; # sent bytes
+my $primnick = $opts{botnick}; # for regain or register checks
+my $inbytes = 0; # received bytes
+my %onchan; # users on game channel
+my %rps; # role-players
+my %quest = (
+    questers => [],
+    p1 => [], # point 1 for q2
+    p2 => [], # point 2 for q2
+    qtime => time() + int(rand(21600)), # first quest starts in <=6 hours
+    text => "",
+    type => 1,
+    stage => 1); # quest info
+
+my $rpreport = 0; # constant for reporting top players
+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
+my $pausemode = 0; # pausemode on/off flag
+my $silentmode = 0; # silent mode 0/1/2/3, see head of file
+my @queue; # outgoing message queue
+my $lastreg = 0; # holds the time of the last reg. cleared every second.
+                 # prevents more than one account being registered / second
+my $registrations = 0; # count of registrations this period
+my $sel; # IO::Select object
+my $lasttime = 1; # last time that rpcheck() was run
+my $buffer; # buffer for socket stuff
+my $conn_tries = 0; # number of connection tries. gives up after trying each
+                    # server twice
+my $sock; # IO::Socket::INET object
+my %split; # holds nick!user@hosts for clients that have been netsplit
+my $freemessages = 4; # number of "free" privmsgs we can send. 0..$freemessages
+
+sub daemonize(); # prototype to avoid warnings
+
+if (! -e $opts{dbfile}) {
+    $|=1;
+    %rps = ();
+    print "$opts{dbfile} does not appear to exist. I'm guessing this is your ".
+          "first time using IRPG. Please give an account name that you would ".
+          "like to have admin access [$opts{owner}]: ";
+    chomp(my $uname = <STDIN>);
+    $uname =~ s/\s.*//g;
+    $uname = length($uname)?$uname:$opts{owner};
+    print "Enter a character class for this account: ";
+    chomp(my $uclass = <STDIN>);
+    $rps{$uname}{class} = substr($uclass,0,30);
+    print "Enter a password for this account: ";
+    if ($^O ne "MSWin32") {
+        system("stty -echo");
+    }
+    chomp(my $upass = <STDIN>);
+    if ($^O ne "MSWin32") {
+        system("stty echo");
+    }
+    $rps{$uname}{pass} = crypt($upass,mksalt());
+    $rps{$uname}{next} = $opts{rpbase};
+    $rps{$uname}{nick} = "";
+    $rps{$uname}{userhost} = "";
+    $rps{$uname}{level} = 0;
+    $rps{$uname}{online} = 0;
+    $rps{$uname}{idled} = 0;
+    $rps{$uname}{created} = time();
+    $rps{$uname}{lastlogin} = time();
+    $rps{$uname}{x} = int(rand($opts{mapx}));
+    $rps{$uname}{y} = int(rand($opts{mapy}));
+    $rps{$uname}{alignment}="n";
+    $rps{$uname}{isadmin} = 1;
+    for my $item ("Jouet","Arme de Precision","Bouffe","Arme de CaC","Casque (Rune 1)",
+                  "Tenue (Rune 3)","Rune de Force","Accessoire (Rune 2)",
+                  "Rune de Precision","Vehicule") {
+        $rps{$uname}{item}{$item} = 0;
+    }
+    for my $pen ("pen_mesg","pen_nick","pen_part",
+                 "pen_kick","pen_quit","pen_quest",
+                 "pen_logout","pen_logout") {
+        $rps{$uname}{$pen} = 0;
+    }
+    writedb();
+    print "OK, wrote you into $opts{dbfile}.\n";
+}
+
+# this is almost silly...
+if ($opts{checkupdates}) {
+    print "Checking for updates...\n\n";
+    my $tempsock = IO::Socket::INET->new(PeerAddr=>"jotun.ultrazone.org:80",
+                                         Timeout => 15);
+    if ($tempsock) {
+        print $tempsock "GET /g7/version.php?version=$version HTTP/1.1\r\n".
+                        "Host: jotun.ultrazone.org:80\r\n\r\n";
+        my($line,$newversion);
+        while ($line=<$tempsock>) {
+            chomp($line);
+            next() unless $line;
+            if ($line =~ /^Current version : (\S+)/) {
+                if ($version ne $1) {
+                    print "There is an update available! Changes include:\n";
+                    $newversion=1;
+                }
+                else {
+                    print "You are running the latest version (v$1).\n";
+                    close($tempsock);
+                    last();
+                }
+            }
+            elsif ($newversion && $line =~ /^(  -? .+)/) { print "$1\n"; }
+            elsif ($newversion && $line =~ /^URL: (.+)/) {
+                print "\nGet the newest version from $1!\n";
+                close($tempsock);
+                last();
+            }
+        }
+    }
+    else { print debug("Could not connect to update server.")."\n"; }
+}
+
+print "\n".debug("Becoming a daemon...")."\n";
+daemonize();
+
+$SIG{HUP} = "readconfig"; # sighup = reread config file
+
+CONNECT: # cheese.
+
+loaddb();
+
+while (!$sock && $conn_tries < 2*@{$opts{servers}}) {
+    debug("Connecting to $opts{servers}->[0]...");
+    my %sockinfo = (PeerAddr => $opts{servers}->[0],
+                    PeerPort => 6667);
+    if ($opts{localaddr}) { $sockinfo{LocalAddr} = $opts{localaddr}; }
+    $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
+        push(@{$opts{servers}},shift(@{$opts{servers}}));
+    }
+    else { debug("Connected."); }
+}
+
+if (!$sock) {
+    debug("Error: Too many connection failures, exhausted server list.\n",1);
+}
+
+$conn_tries=0;
+
+$sel = IO::Select->new($sock);
+
+sts("NICK $opts{botnick}");
+sts("USER $opts{botuser} 0 0 :$opts{botrlnm}");
+
+while (1) {
+    my($readable) = IO::Select->select($sel,undef,undef,0.5);
+    if (defined($readable)) {
+        my $fh = $readable->[0];
+        my $buffer2;
+        $fh->recv($buffer2,512,0);
+        if (length($buffer2)) {
+            $buffer .= $buffer2;
+            while (index($buffer,"\n") != -1) {
+                my $line = substr($buffer,0,index($buffer,"\n")+1);
+                $buffer = substr($buffer,length($line));
+                parse($line);
+            }
+        }
+        else {
+            # uh oh, we've been disconnected from the server, possibly before
+            # we've logged in the users in %auto_login. so, we'll set those
+            # users' online flags to 1, rewrite db, and attempt to reconnect
+            # (if that's wanted of us)
+            $rps{$_}{online}=1 for keys(%auto_login);
+            writedb();
+
+            close($fh);
+            $sel->remove($fh);
+
+            if ($opts{reconnect}) {
+                undef(@queue);
+                undef($sock);
+                debug("Socket closed; disconnected. Cleared outgoing message ".
+                      "queue. Waiting $opts{reconnect_wait}s before next ".
+                      "connection attempt...");
+                sleep($opts{reconnect_wait});
+                goto CONNECT;
+            }
+            else { debug("Socket closed; disconnected.",1); }
+        }
+    }
+    else { select(undef,undef,undef,1); }
+    if ((time()-$lasttime) >= $opts{self_clock}) { rpcheck(); }
+}
+
+
+sub parse {
+    my($in) = shift;
+    $inbytes += length($in); # increase parsed byte count
+    $in =~ s/[\r\n]//g; # strip all \r and \n
+    debug("<- $in");
+    my @arg = split(/\s/,$in); # split into "words"
+    my $usernick = substr((split(/!/,$arg[0]))[0],1);
+    # logged in char name of nickname, or undef if nickname is not online
+    my $username = finduser($usernick);
+    if (lc($arg[0]) eq 'ping') { sts("PONG $arg[1]",1); }
+    elsif (lc($arg[0]) eq 'error') {
+        # uh oh, we've been disconnected from the server, possibly before we've
+        # logged in the users in %auto_login. so, we'll set those users' online
+        # flags to 1, rewrite db, and attempt to reconnect (if that's wanted of
+        # us)
+        $rps{$_}{online}=1 for keys(%auto_login);
+        writedb();
+        return;
+    }
+    $arg[1] = lc($arg[1]); # original case no longer matters
+    if ($arg[1] eq '433' && $opts{botnick} eq $arg[3]) {
+        $opts{botnick} .= 0;
+        sts("NICK $opts{botnick}");
+    }
+    elsif ($arg[1] eq 'join') {
+        # %onchan holds time user joined channel. used for the advertisement ban
+        $onchan{$usernick}=time();
+        if ($opts{'detectsplits'} && exists($split{substr($arg[0],1)})) {
+            delete($split{substr($arg[0],1)});
+        }
+        elsif ($opts{botnick} eq $usernick) {
+            sts("WHO $opts{botchan}");
+            (my $opcmd = $opts{botopcmd}) =~ s/%botnick%/$opts{botnick}/eg;
+            sts($opcmd);
+            $lasttime = time(); # start rpcheck()
+        }
+    }
+    elsif ($arg[1] eq 'quit') {
+        # if we see our nick come open, grab it (skipping queue)
+        if ($usernick eq $primnick) { sts("NICK $primnick",1); }
+        elsif ($opts{'detectsplits'} &&
+               "@arg[2..$#arg]" =~ /^:\S+\.\S+ \S+\.\S+$/) {
+            if (defined($username)) { # user was online
+                $split{substr($arg[0],1)}{time}=time();
+                $split{substr($arg[0],1)}{account}=$username;
+            }
+        }
+        else {
+            penalize($username,"quit");
+        }
+        delete($onchan{$usernick});
+    }
+    elsif ($arg[1] eq 'nick') {
+        # if someone (nickserv) changes our nick for us, update $opts{botnick}
+        if ($usernick eq $opts{botnick}) {
+            $opts{botnick} = substr($arg[2],1);
+        }
+        # if we see our nick come open, grab it (skipping queue), unless it was
+        # us who just lost it
+        elsif ($usernick eq $primnick) { sts("NICK $primnick",1); }
+        else {
+            penalize($username,"nick",$arg[2]);
+            $onchan{substr($arg[2],1)} = delete($onchan{$usernick});
+        }
+    }
+    elsif ($arg[1] eq 'part') {
+        penalize($username,"part");
+        delete($onchan{$usernick});
+    }
+    elsif ($arg[1] eq 'kick') {
+        $usernick = $arg[3];
+        penalize(finduser($usernick),"kick");
+        delete($onchan{$usernick});
+    }
+    # don't penalize /notices to the bot
+    elsif ($arg[1] eq 'notice' && $arg[2] ne $opts{botnick}) {
+        penalize($username,"notice",length("@arg[3..$#arg]")-1);
+    }
+    elsif ($arg[1] eq '001') {
+        # send our identify command, set our usermode, join channel
+        sts($opts{botident});
+        sts("MODE $opts{botnick} :$opts{botmodes}");
+        sts("JOIN $opts{botchan}");
+        $opts{botchan} =~ s/ .*//; # strip channel key if present
+    }
+    elsif ($arg[1] eq '315') {
+        # 315 is /WHO end. report who we automagically signed online iff it will
+        # print < 1k of text
+        if (keys(%auto_login)) {
+            # not a true measure of size, but easy
+            if (length("%auto_login") < 1024 && $opts{senduserlist}) {
+                chanmsg(scalar(keys(%auto_login))." users matching ".
+                        scalar(keys(%prev_online))." hosts automatically ".
+                        "logged in; accounts: ".join(", ",keys(%auto_login)));
+            }
+            else {
+                chanmsg(scalar(keys(%auto_login))." users matching ".
+                        scalar(keys(%prev_online))." hosts automatically ".
+                        "logged in.");
+            }
+            if ($opts{voiceonlogin}) {
+                my @vnicks = map { $rps{$_}{nick} } keys(%auto_login);
+                while (@vnicks) {
+                    sts("MODE $opts{botchan} +".
+                        ('v' x $opts{modesperline})." ".
+                        join(" ",@vnicks[0..$opts{modesperline}-1]));
+                    splice(@vnicks,0,$opts{modesperline});
+                }
+            }
+        }
+        else { chanmsg("0 users qualified for auto login."); }
+        undef(%prev_online);
+        undef(%auto_login);
+    }
+    elsif ($arg[1] eq '005') {
+        if ("@arg" =~ /MODES=(\d+)/) { $opts{modesperline}=$1; }
+    }
+    elsif ($arg[1] eq '352') {
+        my $user;
+        # 352 is one line of /WHO. check that the nick!user@host exists as a key
+        # in %prev_online, the list generated in loaddb(). the value is the user
+        # to login
+        $onchan{$arg[7]}=time();
+        if (exists($prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]})) {
+            $rps{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}{online} = 1;
+            $auto_login{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}=1;
+        }
+    }
+    elsif ($arg[1] eq 'privmsg') {
+        $arg[0] = substr($arg[0],1); # strip leading : from privmsgs
+        if (lc($arg[2]) eq lc($opts{botnick})) { # to us, not channel
+            $arg[3] = lc(substr($arg[3],1)); # lowercase, strip leading :
+            if ($arg[3] eq "\1version\1") {
+                notice("\1VERSION IRPG bot v$version by jotun; ".
+                       "http://idlerpg.net/\1",$usernick);
+            }
+            elsif ($arg[3] eq "peval") {
+                if (!ha($username) || ($opts{ownerpevalonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg("You don't have access to PEVAL.", $usernick);
+                }
+                else {
+                    my @peval = eval "@arg[4..$#arg]";
+                    if (@peval >= 4 || length("@peval") > 1024) {
+                        privmsg("Command produced too much output to send ".
+                                "outright; queueing ".length("@peval").
+                                " bytes in ".scalar(@peval)." items. Use ".
+                                "CLEARQ to clear queue if needed.",$usernick,1);
+                        privmsg($_,$usernick) for @peval;
+                    }
+                    else { privmsg($_,$usernick, 1) for @peval; }
+                    privmsg("EVAL ERROR: $@", $usernick, 1) if $@;
+                }
+            }
+            elsif ($arg[3] eq "register") {
+                if (defined $username) {
+                    privmsg("Sorry, you are already online as $username.",
+                            $usernick);
+                }
+                else {
+                    if ($#arg < 6 || $arg[6] eq "") {
+                        privmsg("Try: REGISTER <char name> <password> <class>",
+                                $usernick);
+                        privmsg("IE : REGISTER Poseidon MyPassword God of the ".
+                                "Sea",$usernick);
+                    }
+                    elsif ($pausemode) {
+                        privmsg("Sorry, new accounts may not be registered ".
+                                "while the bot is in pause mode; please wait ".
+                                "a few minutes and try again.",$usernick);
+                    }
+                    elsif (exists $rps{$arg[4]} || ($opts{casematters} &&
+                           scalar(grep { lc($arg[4]) eq lc($_) } keys(%rps)))) {
+                        privmsg("Sorry, that character name is already in use.",
+                                $usernick);
+                    }
+                    elsif (lc($arg[4]) eq lc($opts{botnick}) ||
+                           lc($arg[4]) eq lc($primnick)) {
+                        privmsg("Sorry, that character name cannot be ".
+                                "registered.",$usernick);
+                    }
+                    elsif (!exists($onchan{$usernick})) {
+                        privmsg("Sorry, you're not in $opts{botchan}.",
+                                $usernick);
+                    }
+                    elsif (length($arg[4]) > 16 || length($arg[4]) < 1) {
+                        privmsg("Sorry, character names must be < 17 and > 0 ".
+                                "chars long.", $usernick);
+                    }
+                    elsif ($arg[4] =~ /^#/) {
+                        privmsg("Sorry, character names may not begin with #.",
+                                $usernick);
+                    }
+                    elsif ($arg[4] =~ /\001/) {
+                        privmsg("Sorry, character names may not include ".
+                                "character \\001.",$usernick);
+                    }
+                    elsif ($opts{noccodes} && ($arg[4] =~ /[[:cntrl:]]/ ||
+                           "@arg[6..$#arg]" =~ /[[:cntrl:]]/)) {
+                        privmsg("Sorry, neither character names nor classes ".
+                                "may include control codes.",$usernick);
+                    }
+                    elsif ($opts{nononp} && ($arg[4] =~ /[[:^print:]]/ ||
+                           "@arg[6..$#arg]" =~ /[[:^print:]]/)) {
+                        privmsg("Sorry, neither character names nor classes ".
+                                "may include non-printable chars.",$usernick);
+                    }
+                    elsif (length("@arg[6..$#arg]") > 30) {
+                        privmsg("Sorry, character classes must be < 31 chars ".
+                                "long.",$usernick);
+                    }
+                    elsif (time() == $lastreg) {
+                        privmsg("Wait 1 second and try again.",$usernick);                
+                    }
+                    else {
+                        if ($opts{voiceonlogin}) {
+                            sts("MODE $opts{botchan} +v :$usernick");
+                        }
+                        ++$registrations;
+                        $lastreg = time();
+                        $rps{$arg[4]}{next} = $opts{rpbase};
+                        $rps{$arg[4]}{class} = "@arg[6..$#arg]";
+                        $rps{$arg[4]}{level} = 0;
+                        $rps{$arg[4]}{online} = 1;
+                        $rps{$arg[4]}{nick} = $usernick;
+                        $rps{$arg[4]}{userhost} = $arg[0];
+                        $rps{$arg[4]}{created} = time();
+                        $rps{$arg[4]}{lastlogin} = time();
+                        $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
+                        $rps{$arg[4]}{x} = int(rand($opts{mapx}));
+                        $rps{$arg[4]}{y} = int(rand($opts{mapy}));
+                        $rps{$arg[4]}{alignment}="n";
+                        $rps{$arg[4]}{isadmin} = 0;
+                        for my $item ("Jouet","Arme de Precision","Bouffe","Arme de CaC","Casque (Rune 1)",
+                                      "Tenue (Rune 3)","Rune de Force","Accessoire (Rune 2)",
+                                      "Rune de Precision","Vehicule") {
+                            $rps{$arg[4]}{item}{$item} = 0;
+                        }
+                        for my $pen ("pen_mesg","pen_nick","pen_part",
+                                     "pen_kick","pen_quit","pen_quest",
+                                     "pen_logout","pen_logout") {
+                            $rps{$arg[4]}{$pen} = 0;
+                        }
+                        chanmsg(sprintf(gettext("Welcome %s\'s new player %s, ".
+						"the %s! Next level in %s."),
+					$usernick,$arg[4],@arg[6..$#arg],
+					duration($opts{rpbase})));
+                        privmsg("Success! Account $arg[4] created. You have ".
+                                "$opts{rpbase} seconds idleness until you ".
+                                "reach level 1. ", $usernick);
+                        privmsg("NOTE: The point of the game is to see who ".
+                                "can idle the longest. As such, talking in ".
+                                "the channel, parting, quitting, and changing ".
+                                "nicks all penalize you.",$usernick);
+                        if ($opts{phonehome}) {
+                            my $tempsock = IO::Socket::INET->new(PeerAddr=>
+                                "jotun.ultrazone.org:80");
+                            if ($tempsock) {
+                                print $tempsock
+                                    "GET /g7/count.php?new=1 HTTP/1.1\r\n".
+                                    "Host: jotun.ultrazone.org:80\r\n\r\n";
+                                sleep(1);
+                                close($tempsock);
+                            }
+                        }
+                    }
+                }
+            }
+            elsif ($arg[3] eq "delold") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to DELOLD.", $usernick);
+                }
+                # insure it is a number
+                elsif ($arg[4] !~ /^[\d\.]+$/) {
+                    privmsg("Try: DELOLD <# of days>", $usernick, 1);
+                }
+                else {
+                    my @oldaccounts = grep { (time()-$rps{$_}{lastlogin}) >
+                                             ($arg[4] * 86400) &&
+                                             !$rps{$_}{online} } keys(%rps);
+                    delete(@rps{@oldaccounts});
+                    chanmsg(scalar(@oldaccounts)." accounts not accessed in ".
+                            "the last $arg[4] days removed by $arg[0].");
+                }
+            }
+            elsif ($arg[3] eq "del") {
+                if (!ha($username)) {
+                    privmsg("Vous n'avez pas acces a DEL.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                   privmsg("Essayez: DEL <nomduperso>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de compte $arg[4].", $usernick, 1);
+                }
+                else {
+                    delete($rps{$arg[4]});
+                    chanmsg("Compte $arg[4] supprime par $arg[0].");
+                }
+            }
+            elsif ($arg[3] eq "mkadmin") {
+                if (!ha($username) || ($opts{owneraddonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg("Vous n avez pas acces a MKADMIN.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Essayez: MKADMIN <nomperso>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de compte $arg[4].", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{isadmin}=1;
+                    privmsg("Compte $arg[4] est maintenant un bot admin.",$usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "deladmin") {
+                if (!ha($username) || ($opts{ownerdelonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg("Vous n avez pas acces a DELADMIN.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Essayez: DELADMIN <nomperso>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de compte $arg[4].", $usernick, 1);
+                }
+                elsif ($arg[4] eq $opts{owner}) {
+                    privmsg("Impossible de DELADMIN sur le compte du proprietaire.", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{isadmin}=0;
+                    privmsg("Compte $arg[4] n est plus un compte admin.",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "hog") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a HOG.", $usernick);
+                }
+                else {
+                    chanmsg("$usernick a invoque la main de Schlavbeuk.");
+                    hog();
+                }
+            }
+            elsif ($arg[3] eq "rehash") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a REHASH.", $usernick);
+                }
+                else {
+                    readconfig();
+                    privmsg("Reread config file.",$usernick,1);
+                    $opts{botchan} =~ s/ .*//; # strip channel key if present
+                }
+            }
+            elsif ($arg[3] eq "chpass") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a CHPASS.", $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg("Essayez: CHPASS <nomperso> <nouveaupass>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de perso nomme $arg[4].", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
+                    privmsg("Le mot de passe pour $arg[4] est change.", $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "chuser") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a CHUSER.", $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg("Essayez: CHUSER <nomperso> <nouveaunomperso>",
+                            $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de perso nomme $arg[4].", $usernick, 1);
+                }
+                elsif (exists($rps{$arg[5]})) {
+                    privmsg("Le nom $arg[5] est deja pris.", $usernick,1);
+                }
+                else {
+                    $rps{$arg[5]} = delete($rps{$arg[4]});
+                    privmsg("Le nom de perso $arg[4] est change en $arg[5].",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "chclass") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a CHCLASS.", $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg("Essayez: CHCLASS <nomperso> <nouvelleclasse>",
+                            $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de perso $arg[4].", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{class} = "@arg[5..$#arg]";
+                    privmsg("La classe de $arg[4] est change en @arg[5..$#arg].",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "push") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a PUSH.", $usernick);
+                }
+                # insure it's a positive or negative, integral number of seconds
+                elsif ($arg[5] !~ /^\-?\d+$/) {
+                    privmsg("Essayez: PUSH <nomperso> <secondes>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("Pas de compte $arg[4].", $usernick, 1);
+                }
+                elsif ($arg[5] > $rps{$arg[4]}{next}) {
+                    privmsg("Le temps pour changer de niveau de $arg[4] ($rps{$arg[4]}{next}s) ".
+                            "est plus petit que $arg[5]; mise a 0 du compteur.",
+                            $usernick, 1);
+                    chanmsg("$usernick a pousse $arg[4] $rps{$arg[4]}{next} ".
+                            "secondes vers le niveau ".($rps{$arg[4]}{level}+1));
+                    $rps{$arg[4]}{next}=0;
+                }
+                else {
+                    $rps{$arg[4]}{next} -= $arg[5];
+                     chanmsg("$usernick a pousse $arg[4] $arg[5] secondes ".
+                             "vers le niveau ".($rps{$arg[4]}{level}+1).". ".
+                             "$arg[4] atteindra le niveau suivant dans ".
+                             duration($rps{$arg[4]}{next}).".");
+                }
+            }   
+            elsif ($arg[3] eq "logout") {
+                if (defined($username)) {
+                    penalize($username,"logout");
+                }
+                else {
+                    privmsg("Vous n etes pas connecte.", $usernick);
+                }
+            }
+            elsif ($arg[3] eq "quest") {
+                if (!@{$quest{questers}}) {
+                    privmsg("Il n y a pas de quete active.",$usernick);
+                }
+                elsif ($quest{type} == 1) {
+                    privmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                            "$quest{questers}->[3] are on a quest to ".
+                            "$quest{text}. Quest to complete in ".
+                            duration($quest{qtime}-time()).".",$usernick);
+                }
+                elsif ($quest{type} == 2) {
+                    privmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                            "$quest{questers}->[3] are on a quest to ".
+                            "$quest{text}. Participants must first reach ".
+                            "[$quest{p1}->[0],$quest{p1}->[1]], then ".
+                            "[$quest{p2}->[0],$quest{p2}->[1]].".
+                            ($opts{mapurl}?" See $opts{mapurl} to monitor ".
+                            "their journey's progress.":""),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "status" && $opts{statuscmd}) {
+                if (!defined($username)) {
+                    privmsg("Vous n etes pas connecte.", $usernick);
+                }
+                # argument is optional
+                elsif ($arg[4] && !exists($rps{$arg[4]})) {
+                    privmsg("No such user.",$usernick);
+                }
+                elsif ($arg[4]) { # optional 'user' argument
+                    privmsg("$arg[4]: Level $rps{$arg[4]}{level} ".
+                            "$rps{$arg[4]}{class}; Status: O".
+                            ($rps{$arg[4]}{online}?"n":"ff")."line; ".
+                            "TTL: ".duration($rps{$arg[4]}{next})."; ".
+                            "Idled: ".duration($rps{$arg[4]}{idled}).
+                            "; Item sum: ".itemsum($arg[4]),$usernick);
+                }
+                else { # no argument, look up this user
+                    privmsg("$username: Level $rps{$username}{level} ".
+                            "$rps{$username}{class}; Status: O".
+                            ($rps{$username}{online}?"n":"ff")."line; ".
+                            "TTL: ".duration($rps{$username}{next})."; ".
+                            "Idled: ".duration($rps{$username}{idled})."; ".
+                            "Item sum: ".itemsum($username),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "whoami") {
+                if (!defined($username)) {
+                    privmsg("Vous n etes pas connecte.", $usernick);
+                }
+                else {
+                    privmsg("Vous etes $username, le niveau ".
+                            $rps{$username}{level}." $rps{$username}{class}. ".
+                            "Prochain Level dans ".duration($rps{$username}{next}),
+                            $usernick);
+                }
+            }
+            elsif ($arg[3] eq "newpass") {
+                if (!defined($username)) {
+                    privmsg("Vous n etes pas connecte.", $usernick)
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Essayez : NEWPASS <nouveaumotdepasse>", $usernick);
+                }
+                else {
+                    $rps{$username}{pass} = crypt($arg[4],mksalt());
+                    privmsg("Your password was changed.",$usernick);
+                }
+            }
+            elsif ($arg[3] eq "align") {
+                if (!defined($username)) {
+                    privmsg("Vous n etes pas connecte.", $usernick)
+                }
+                elsif (!defined($arg[4]) || (lc($arg[4]) ne "good" && 
+                       lc($arg[4]) ne "neutral" && lc($arg[4]) ne "evil")) {
+                    privmsg("Try: ALIGN <good|neutral|evil>", $usernick);
+                }
+                else {
+                    $rps{$username}{alignment} = substr(lc($arg[4]),0,1);
+                    chanmsg("$username has changed alignment to: ".lc($arg[4]).
+                            ".");
+                    privmsg("Votre alignement a ete change, Vous etes maintenant ".lc($arg[4]).".",
+                            $usernick);
+                }
+            }
+            elsif ($arg[3] eq "removeme") {
+                if (!defined($username)) {
+                    privmsg("Vous n etes pas connecte.", $usernick)
+                }
+                else {
+                    privmsg("Compte $username supprime.",$usernick);
+                    chanmsg("$arg[0] removed his account, $username, the ".
+                            $rps{$username}{class}.".");
+                    delete($rps{$username});
+                }
+            }
+            elsif ($arg[3] eq "help") {
+                if (!ha($username)) {
+                    privmsg("Pour avoir des infos sur les commandes IRPG, allez voir ".
+                            $opts{helpurl}, $usernick);
+                }
+                else {
+                    privmsg("Le site d'aide est $opts{helpurl}", $usernick, 1);
+                    privmsg("Admin commands URL is $opts{admincommurl}",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "die") {
+                if (!ha($username)) {
+                    privmsg("Vous n'avez pas acces a DIE.", $usernick);
+                }
+                else {
+                    $opts{reconnect} = 0;
+                    writedb();
+                    sts("QUIT :DIE from $arg[0]",1);
+                }
+            }
+            elsif ($arg[3] eq "reloaddb") {
+                if (!ha($username)) {
+                    privmsg("Vous n'avez pas acces a RELOADDB.", $usernick);
+                }
+                elsif (!$pausemode) {
+                    privmsg("ERROR: Can only use LOADDB while in PAUSE mode.",
+                            $usernick, 1);
+                }
+                else {
+                    loaddb();
+                    privmsg("Reread player database file; ".scalar(keys(%rps)).
+                            " accounts loaded.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "backup") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a BACKUP.", $usernick);
+                }
+                else {
+                    backup();
+                    privmsg("$opts{dbfile} copied to ".
+                            ".dbbackup/$opts{dbfile}".time(),$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "pause") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a PAUSE.", $usernick);
+                }
+                else {
+                    $pausemode = $pausemode ? 0 : 1;
+                    privmsg("PAUSE_MODE mis a $pausemode.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "silent") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a SILENT.", $usernick);
+                }
+                elsif (!defined($arg[4]) || $arg[4] < 0 || $arg[4] > 3) {
+                    privmsg("Essayez: SILENT <mode>", $usernick,1);
+                }
+                else {
+                    $silentmode = $arg[4];
+                    privmsg("SILENT_MODE mis a $silentmode.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "jump") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a JUMP.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Try: JUMP <server[:port]>", $usernick, 1);
+                }
+                else {
+                    writedb();
+                    sts("QUIT :JUMP to $arg[4] from $arg[0]");
+                    unshift(@{$opts{servers}},$arg[4]);
+                    close($sock);
+                    sleep(3);
+                    goto CONNECT;
+                }
+            }
+            elsif ($arg[3] eq "restart") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas acces a RESTART.", $usernick);
+                }
+                else {
+                    writedb();
+                    sts("QUIT :RESTART de $arg[0]",1);
+                    close($sock);
+                    exec("perl $0");
+                }
+            }
+            elsif ($arg[3] eq "clearq") {
+                if (!ha($username)) {
+                    privmsg("Vous n avez pas a acces a CLEARQ.", $usernick);
+                }
+                else {
+                    undef(@queue);
+                    chanmsg("Outgoing message queue cleared by $arg[0].");
+                    privmsg("Outgoing message queue cleared.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "info") {
+                my $info;
+                if (!ha($username) && $opts{allowuserinfo}) {
+                    $info = "IRPG bot v$version par jotun ".
+                            "http://idlerpg.net/. On via server: ".
+                            $opts{servers}->[0].". Admins online: ".
+                            join(", ", map { $rps{$_}{nick} }
+                                      grep { $rps{$_}{isadmin} &&
+                                             $rps{$_}{online} } keys(%rps)).".";
+                    privmsg($info, $usernick);
+                }
+                elsif (!ha($username) && !$opts{allowuserinfo}) {
+                    privmsg("Vous n avez pas acces a INFO.", $usernick);
+                }
+                else {
+                    my $queuedbytes = 0;
+                    $queuedbytes += (length($_)+2) for @queue; # +2 = \r\n
+                    $info = sprintf(
+                        "%.2fkb sent, %.2fkb received in %s. %d IRPG users ".
+                        "online of %d total users. %d accounts created since ".
+                        "startup. PAUSE_MODE is %d, SILENT_MODE is %d. ".
+                        "Outgoing queue is %d bytes in %d items. On via: %s. ".
+                        "Admins online: %s.",
+                        $outbytes/1024,
+                        $inbytes/1024,
+                        duration(time()-$^T),
+                        scalar(grep { $rps{$_}{online} } keys(%rps)),
+                        scalar(keys(%rps)),
+                        $registrations,
+                        $pausemode,
+                        $silentmode,
+                        $queuedbytes,
+                        scalar(@queue),
+                        $opts{servers}->[0],
+                        join(", ",map { $rps{$_}{nick} }
+                                  grep { $rps{$_}{isadmin} && $rps{$_}{online} }
+                                  keys(%rps)));
+                    privmsg($info, $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "login") {
+                if (defined($username)) {
+                    notice("Desole, vous etes deja online en tant que $username.",
+                            $usernick);
+                }
+                else {
+                    if ($#arg < 5 || $arg[5] eq "") {
+                        notice("Essayez : LOGIN <nomdutilisateur> <motdepasse>", $usernick);
+                    }
+                    elsif (!exists $rps{$arg[4]}) {
+                        notice("Desole, aucun compte de ce nom. Attention les noms de compte ".
+                                "tiennent compte des majuscules.",$usernick);
+                    }
+                    elsif (!exists $onchan{$usernick}) {
+                        notice("Desole, vous n etes pas dans $opts{botchan}.",
+                                $usernick);
+                    }
+                    elsif ($rps{$arg[4]}{pass} ne
+                           crypt($arg[5],$rps{$arg[4]}{pass})) {
+                        notice("Wrong password.", $usernick);
+                    }
+                    else {
+                        if ($opts{voiceonlogin}) {
+                            sts("MODE $opts{botchan} +v :$usernick");
+                        }
+                        $rps{$arg[4]}{online} = 1;
+                        $rps{$arg[4]}{nick} = $usernick;
+                        $rps{$arg[4]}{userhost} = $arg[0];
+                        $rps{$arg[4]}{lastlogin} = time();
+                        chanmsg("$arg[4], the level $rps{$arg[4]}{level} ".
+                                "$rps{$arg[4]}{class}, is now online from ".
+                                "nickname $usernick. Next level in ".
+                                duration($rps{$arg[4]}{next}).".");
+                        notice("Logon successful. Next level in ".
+                               duration($rps{$arg[4]}{next}).".", $usernick);
+                    }
+                }
+            }
+        }
+        # penalize returns true if user was online and successfully penalized.
+        # if the user is not logged in, then penalize() fails. so, if user is
+        # offline, and they say something including "http:", and they've been on
+        # the channel less than 90 seconds, and the http:-style ban is on, then
+        # check to see if their url is in @{$opts{okurl}}. if not, kickban them
+        elsif (!penalize($username,"privmsg",length("@arg[3..$#arg]")) &&
+               index(lc("@arg[3..$#arg]"),"http:") != -1 &&
+               (time()-$onchan{$usernick}) < 90 && $opts{doban}) {
+            my $isokurl = 0;
+            for (@{$opts{okurl}}) {
+                if (index(lc("@arg[3..$#arg]"),lc($_)) != -1) { $isokurl = 1; }
+            }
+            if (!$isokurl) {
+                sts("MODE $opts{botchan} +b $arg[0]");
+                sts("KICK $opts{botchan} $usernick :No advertising; ban will ".
+                    "be lifted within the hour.");
+                push(@bans,$arg[0]) if @bans < 12;
+            }
+        }
+    }
+}
+
+sub sts { # send to server
+    my($text,$skipq) = @_;
+    if ($skipq) {
+        if ($sock) {
+            print $sock "$text\r\n";
+            $outbytes += length($text) + 2;
+            debug("-> $text");
+        }
+        else {
+            # something is wrong. the socket is closed. clear the queue
+            undef(@queue);
+            debug("\$sock isn't writeable in sts(), cleared outgoing queue.\n");
+            return;
+        }
+    }
+    else {
+        push(@queue,$text);
+        debug(sprintf("(q%03d) = %s\n",$#queue,$text));
+    }
+}
+
+sub fq { # deliver message(s) from queue
+    if (!@queue) {
+        ++$freemessages if $freemessages < 4;
+        return undef;
+    }
+    my $sentbytes = 0;
+    for (0..$freemessages) {
+        last() if !@queue; # no messages left to send
+        # lower number of "free" messages we have left
+        my $line=shift(@queue);
+        # if we have already sent one message, and the next message to be sent
+        # plus the previous messages we have sent this call to fq() > 768 bytes,
+        # then requeue this message and return. we don't want to flood off,
+        # after all
+        if ($_ != 0 && (length($line)+$sentbytes) > 768) {
+            unshift(@queue,$line);
+            last();
+        }
+        if ($sock) {
+            debug("(fm$freemessages) -> $line");
+            --$freemessages if $freemessages > 0;
+            print $sock "$line\r\n";
+            $sentbytes += length($line) + 2;
+        }
+        else {
+            undef(@queue);
+            debug("Disconnected: cleared outgoing message queue.");
+            last();
+        }
+        $outbytes += length($line) + 2;
+    }
+}
+
+sub duration { # return human duration of seconds
+    my $s = shift;
+    return "NA ($s)" if $s !~ /^\d+$/;
+    return sprintf("%d day%s, %02d:%02d:%02d",$s/86400,int($s/86400)==1?"":"s",
+                   ($s%86400)/3600,($s%3600)/60,($s%60));
+}
+
+sub ts { # timestamp
+    my @ts = localtime(time());
+    return sprintf("[%02d/%02d/%02d %02d:%02d:%02d] ",
+                   $ts[4]+1,$ts[3],$ts[5]%100,$ts[2],$ts[1],$ts[0]);
+}
+
+sub hog { # summon the hand of god
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    my $player = $players[rand(@players)];
+    my $win = int(rand(5));
+    my $time = int(((5 + int(rand(71)))/100) * $rps{$player}{next});
+    if ($win) {
+        chanmsg(clog("Schlavbeuk est descendu des cieux et, dans sa grandes bonte, ".
+                     "a accorde sa benediction a $player ".
+                     duration($time)." pour le niveau ".($rps{$player}{level}+1).
+                     "."));
+        $rps{$player}{next} -= $time;
+    }
+    else {
+        chanmsg(clog("Schlavbeuk, ayant un peu trop force sur la biere ".
+                     "et a lance quelques eclairs au hasard... $player etait au mauvais endroit au mauvais moment ".
+                     duration($time)." from level ".($rps{$player}{level}+1).
+                     "."));
+        $rps{$player}{next} += $time;
+    }
+    chanmsg("$player aura atteint le prochain niveau dans ".duration($rps{$player}{next}).".");
+}
+
+sub rpcheck { # check levels, update database
+    # check splits hash to see if any split users have expired
+    checksplits() if $opts{detectsplits};
+    # send out $freemessages lines of text from the outgoing message queue
+    fq();
+    # clear registration limiting
+    $lastreg = 0;
+    my $online = scalar(grep { $rps{$_}{online} } keys(%rps));
+    # there's really nothing to do here if there are no online users
+    return unless $online;
+    my $onlineevil = scalar(grep { $rps{$_}{online} &&
+                                   $rps{$_}{alignment} eq "e" } keys(%rps));
+    my $onlinegood = scalar(grep { $rps{$_}{online} &&
+                                   $rps{$_}{alignment} eq "g" } keys(%rps));
+    if (!$opts{noscale}) {
+        if (rand((20*86400)/$opts{self_clock}) < $online) { hog(); }
+        if (rand((24*86400)/$opts{self_clock}) < $online) { team_battle(); }
+        if (rand((8*86400)/$opts{self_clock}) < $online) { calamity(); }
+        if (rand((4*86400)/$opts{self_clock}) < $online) { godsend(); }
+    }
+    else {
+        hog() if rand(4000) < 1;
+        team_battle() if rand(4000) < 1;
+        calamity() if rand(4000) < 1;
+        godsend() if rand(2000) < 1;
+    }
+    if (rand((8*86400)/$opts{self_clock}) < $onlineevil) { evilness(); }
+    if (rand((12*86400)/$opts{self_clock}) < $onlinegood) { goodness(); }
+
+    moveplayers();
+    
+    # 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 (time() > $quest{qtime}) {
+        if (!@{$quest{questers}}) { quest(); }
+        elsif ($quest{type} == 1) {
+            chanmsg(clog(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                         "$quest{questers}->[3] have blessed the realm by ".
+                         "completing their quest! 25% of their burden is ".
+                         "eliminated."));
+            for (@{$quest{questers}}) {
+                $rps{$_}{next} = int($rps{$_}{next} * .75);
+            }
+            undef(@{$quest{questers}});
+            $quest{qtime} = time() + 21600;
+        }
+        # quest type 2 awards are handled in moveplayers()
+    }
+    if ($rpreport && $rpreport%36000==0) { # 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;
+        for my $i (0..2) {
+            $#u >= $i and
+            chanmsg("$u[$i], the level $rps{$u[$i]}{level} ".
+                    "$rps{$u[$i]}{class}, is #" . ($i + 1) . "! Next level in ".
+                    (duration($rps{$u[$i]}{next})).".");
+        }
+        backup();
+    }
+    if ($rpreport%3600==0 && $rpreport) { # 1 hour
+        my @players = grep { $rps{$_}{online} &&
+                             $rps{$_}{level} > 44 } keys(%rps);
+        # 20% of all players must be level 45+
+        if ((scalar(@players)/scalar(grep { $rps{$_}{online} } keys(%rps))) > .15) {
+            challenge_opp($players[int(rand(@players))]);
+        }
+        while (@bans) {
+            sts("MODE $opts{botchan} -bbbb :@bans[0..3]");
+            splice(@bans,0,4);
+        }
+    }
+    if ($rpreport%1800==0) { # 30 mins
+        if ($opts{botnick} ne $primnick) {
+            sts($opts{botghostcmd}) if $opts{botghostcmd};
+            sts("NICK $primnick");
+        }
+    }
+    if ($rpreport%600==0 && $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
+    # log everyone out if the bot failed to connect. $lasttime = time() on
+    # successful join to $opts{botchan}, initial value is 1). if fails to open
+    # $opts{dbfile}, will not update $lasttime and so should have correct values
+    # on next rpcheck(). 
+    if ($lasttime != 1) {
+        my $curtime=time();
+        for my $k (keys(%rps)) {
+            if ($rps{$k}{online} && exists $rps{$k}{nick} &&
+                $rps{$k}{nick} && exists $onchan{$rps{$k}{nick}}) {
+                $rps{$k}{next} -= ($curtime - $lasttime);
+                $rps{$k}{idled} += ($curtime - $lasttime);
+                if ($rps{$k}{next} < 1) {
+                    $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}));
+                    }
+                    chanmsg("$k, the $rps{$k}{class}, has attained level ".
+                            "$rps{$k}{level}! Next level in ".
+                            duration($rps{$k}{next}).".");
+                    find_item($k);
+                    challenge_opp($k);
+                }
+            }
+            # 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};
+        $lasttime = $curtime;
+    }
+}
+
+sub challenge_opp { # pit argument player against random player
+    my $u = shift;
+    if ($rps{$u}{level} < 25) { return unless rand(4) < 1; }
+    my @opps = grep { $rps{$_}{online} && $u ne $_ } keys(%rps);
+    return unless @opps;
+    my $opp = $opps[int(rand(@opps))];
+    $opp = $primnick if rand(@opps+1) < 1;
+    my $mysum = itemsum($u,1);
+    my $oppsum = itemsum($opp,1);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        my $gain = ($opp eq $primnick)?20:int($rps{$opp}{level}/4);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] a defie $opp [$opproll/".
+                     "$oppsum] en duel et a gagne! ".duration($gain)." est ".
+                     "enleve du compteur de $u\ ."));
+        $rps{$u}{next} -= $gain;
+        chanmsg("$u atteint le prochain niveau dans ".duration($rps{$u}{next}).".");
+        my $csfactor = $rps{$u}{alignment} eq "g" ? 50 :
+                       $rps{$u}{alignment} eq "e" ? 20 :
+                       35;
+        if (rand($csfactor) < 1 && $opp ne $primnick) {
+            $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
+            chanmsg(clog("$u has dealt $opp a Critical Strike! ".
+                         duration($gain)." is added to $opp\'s clock."));
+            $rps{$opp}{next} += $gain;
+            chanmsg("$opp atteint le prochain niveau dans ".duration($rps{$opp}{next}).
+                    ".");
+        }
+        elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
+            my @items = ("Jouet","Arme de Precision","Bouffe","Arme","Casque (Rune 1)","Tenue (Rune 3)",
+                         "Rune de Force","Rune de Precision","Accessoire (Rune 2)",
+                         "Vehicule");
+            my $type = $items[rand(@items)];
+            if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) {
+                chanmsg(clog("Durant cette bataille acharnee, $opp a perdu son objet niveau ".
+                             int($rps{$opp}{item}{$type})." $type! $u le prends ".
+                             "jettant son ancien objet ".
+                             int($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;
+            }
+        }
+    }
+    else {
+        my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] a defie $opp [$opproll/".
+                     "$oppsum] en duel et a perdu! ".duration($gain)." est ".
+                     "ajoute au compteur de $u\'s ."));
+        $rps{$u}{next} += $gain;
+        chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
+    }
+}
+
+sub team_battle { # choisit 3 joueurs contre 3 autres joueurs
+    my @opp = grep { $rps{$_}{online} } keys(%rps);
+    return if @opp < 6;
+    splice(@opp,int(rand(@opp)),1) while @opp > 6;
+    fisher_yates_shuffle(\@opp);
+    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};
+    for my $p (1,2) {
+        $gain = $rps{$opp[$p]}{next} if $gain > $rps{$opp[$p]}{next};
+    }
+    $gain = int($gain*.20);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        chanmsg(clog("$opp[0], $opp[1], and $opp[2] [$myroll/$mysum] ont defié ".
+                     "l equipe de $opp[3], $opp[4], et $opp[5] [$opproll/".
+                     "$oppsum] et ont gagne! ".duration($gain)." est enleve de ".
+                     "leur compteur."));
+        $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] ont defie ".
+                     "l equipe de $opp[3], $opp[4], et $opp[5] [$opproll/".
+                     "$oppsum] et ont perdu! ".duration($gain)." est ajoute a ".
+                     "leur compteur."));
+        $rps{$opp[0]}{next} += $gain;
+        $rps{$opp[1]}{next} += $gain;
+        $rps{$opp[2]}{next} += $gain;
+    }
+}
+
+sub find_item { # find item for argument player
+    my $u = shift;
+    my @items = ("Jouet","Arme de Precision","Bouffe","Arme","Casque (Rune 1)","Tenue (Rune 3)",
+                 "Rune de Force","Rune de Precision","Accessoire (Rune 2)","Vehicule");
+    my $type = $items[rand(@items)];
+    my $level = 1;
+    my $ulevel;
+    for my $num (1 .. int($rps{$u}{level}*1.5)) {
+        if (rand(1.4**($num/4)) < 1) {
+            $level = $num;
+        }
+    }
+    if ($rps{$u}{level} >= 25 && rand(40) < 1) {
+        $ulevel = 50+int(rand(25));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{"Casque (Rune 1)"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez le Casque de niveau $ulevel de Hunnin Avherty Envodeux le Viking Nain ! ".
+                   "Vous anticipez les mouvements de tout vos ennemis et vous en ".
+                   "debarrassez aisement.",$rps{$u}{nick});
+            $rps{$u}{item}{"Casque (Rune 1)"} = $ulevel."a";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 25 && rand(40) < 1) {
+        $ulevel = 50+int(rand(25));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{Jouet})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez le pistolet a retro propulsion plasmatique de SPOOK de niveau $ulevel ".
+                   "Vous envoyez tous vos ennemis valdaguer ailleurs".
+                   "voir si vous y etes.",
+                   $rps{$u}{nick});
+            $rps{$u}{item}{Jouet} = $ulevel."h";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 30 && rand(40) < 1) {
+        $ulevel = 75+int(rand(25));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{"Tenue (Rune 3)"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez la Combinaison de Bionain Cameleon deniveau $ulevel".
+                   "Plus rien ne vous atteint avec cette protection integrale ".
+                   "(jusqu'a la coquille en titane pour les parties sensibles).",$rps{$u}{nick});
+            $rps{$u}{item}{"Tenue (Rune 3)"} = $ulevel."b";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 35 && rand(40) < 1) {
+        $ulevel = 100+int(rand(25));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{"Arme de Precision"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez la Patate Nucleaire de niveau $ulevel ".
+                   "Grace a sa portee hors du commun, ".
+                   "plus personne n echappe a votre puissance",$rps{$u}{nick});
+            $rps{$u}{item}{"Arme de Precision"} = $ulevel."c";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 40 && rand(40) < 1) {
+        $ulevel = 150+int(rand(25));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{"Arme de CaC"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez la calculette scientifique en or retro-eclairee TI-9239073195 de niveau $ulevel".
+                   "Avec elle vous vous sentez capable de decouvrir la valeur exacte de Pi ".
+                   "et donnez mal a la tete a vos ennemis faibles en math.",$rps{$u}{nick});
+            $rps{$u}{item}{"Arme de CaC"} = $ulevel."d";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 45 && rand(40) < 1) {
+        $ulevel = 175+int(rand(26));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{"Arme de CaC"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez le legendaire Mega giga super gros coup de pied au cul de niveau $ulevel ".
+                   "Vous commencez a infligez la douleur supreme a tous les derrieres".
+                   "de vos ennemis.",$rps{$u}{nick});
+            $rps{$u}{item}{"Arme de CaC"} = $ulevel."e";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 48 && rand(40) < 1) {
+        $ulevel = 250+int(rand(51));
+        if ($ulevel >= $level && $ulevel >
+            int($rps{$u}{item}{"Vehicule"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez la brouette spatio-temporelle edition limitee Einst-Nain lui meme de niveau $ulevel ".
+                   "Avec ca vous pouvez attaquer et fuir avant meme ".
+                   "que vos ennemis reagissent.",$rps{$u}{nick});
+            $rps{$u}{item}{"Vehicule"} = $ulevel."f";
+            return;
+        }
+    }
+    elsif ($rps{$u}{level} >= 52 && rand(40) < 1) {
+        $ulevel = 300+int(rand(51));
+        if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{"Arme de CaC"})) {
+            notice("La divine lumiere de Schlavbeuk est sur vous ! Vous ".
+                   "trouvez Le glaive de Schlavbeuk de niveau $ulevel ".
+                   "C est l ultime glaive : il a ete beni par un pretre dans le vomi sacre".
+                   "de Schlavbeuk apres une autre soiree trop arrosee.",$rps{$u}{nick});
+            $rps{$u}{item}{"Arme de CaC"} = $ulevel."g";
+            return;
+        }
+    }
+    if ($level > int($rps{$u}{item}{$type})) {
+        notice("Vous trouvez un objet de type $type de niveau $level ! Votre precedent $type est seulement ".
+               "niveau " Votre precedent $type est seulement ".
+               "niveau ".int($rps{$u}{item}{$type}).", donc il semble que la Chance soit ".
+               "avec vous!",$rps{$u}{nick});
+        $rps{$u}{item}{$type} = $level;
+    }
+    else {
+        notice("Vous trouvez un objet de type $type de niveau $level ! Votre $type actuel est niveau ".
+               int($rps{$u}{item}{$type}).", donc il semble que la Chance ne soit pas avec vous. ".
+               "Vous jetez le $type.",$rps{$u}{nick});
+    }
+}
+
+sub loaddb { # load the players database
+    backup();
+    my $l;
+    %rps = ();
+    if (!open(RPS,$opts{dbfile}) && -e $opts{dbfile}) {
+        sts("QUIT :loaddb() failed: $!");
+    }
+    while ($l=<RPS>) {
+        chomp($l);
+        next if $l =~ /^#/; # skip comments
+        my @i = split("\t",$l);
+        print Dumper(@i) if @i != 32;
+        if (@i != 32) {
+            sts("QUIT: Anomaly in loaddb(); line $. of $opts{dbfile} has ".
+                "wrong fields (".scalar(@i).")");
+            debug("Anomaly in loaddb(); line $. of $opts{dbfile} has wrong ".
+                "fields (".scalar(@i).")",1);
+        }
+        if (!$sock) { # if not RELOADDB
+            if ($i[8]) { $prev_online{$i[7]}=$i[0]; } # log back in
+        }
+        ($rps{$i[0]}{pass},
+        $rps{$i[0]}{isadmin},
+        $rps{$i[0]}{level},
+        $rps{$i[0]}{class},
+        $rps{$i[0]}{next},
+        $rps{$i[0]}{nick},
+        $rps{$i[0]}{userhost},
+        $rps{$i[0]}{online},
+        $rps{$i[0]}{idled},
+        $rps{$i[0]}{x},
+        $rps{$i[0]}{y},
+        $rps{$i[0]}{pen_mesg},
+        $rps{$i[0]}{pen_nick},
+        $rps{$i[0]}{pen_part},
+        $rps{$i[0]}{pen_kick},
+        $rps{$i[0]}{pen_quit},
+        $rps{$i[0]}{pen_quest},
+        $rps{$i[0]}{pen_logout},
+        $rps{$i[0]}{created},
+        $rps{$i[0]}{lastlogin},
+        $rps{$i[0]}{item}{"Arme de Precision"},
+        $rps{$i[0]}{item}{Bouffe},
+        $rps{$i[0]}{item}{"Casque (Rune 1)"},
+        $rps{$i[0]}{item}{"Vehicule"},
+        $rps{$i[0]}{item}{"Rune de Force"},
+        $rps{$i[0]}{item}{Jouet},
+        $rps{$i[0]}{item}{"Rune de Precision"},
+        $rps{$i[0]}{item}{"Accessoire (Rune 2)"},
+        $rps{$i[0]}{item}{"Tenue (Rune 3)"},
+        $rps{$i[0]}{item}{"Arme de CaC"},
+        $rps{$i[0]}{alignment}) = (@i[1..7],($sock?$i[8]:0),@i[9..$#i]);
+    }
+    close(RPS);
+    debug("loaddb(): loaded ".scalar(keys(%rps))." accounts, ".
+          scalar(keys(%prev_online))." previously online.");
+}
+
+sub moveplayers {
+    return unless $lasttime > 1;
+    my $onlinecount = grep { $rps{$_}{online} } keys %rps;
+    return unless $onlinecount;
+    for (my $i=0;$i<$opts{self_clock};++$i) {
+        # temporary hash to hold player positions, detect collisions
+        my %positions = ();
+        if ($quest{type} == 2 && @{$quest{questers}}) {
+            my $allgo = 1; # have all users reached <p1|p2>?
+            for (@{$quest{questers}}) {
+                if ($quest{stage}==1) {
+                    if ($rps{$_}{x} != $quest{p1}->[0] ||
+                        $rps{$_}{y} != $quest{p1}->[1]) {
+                        $allgo=0;
+                        last();
+                    }
+                }
+                else {
+                    if ($rps{$_}{x} != $quest{p2}->[0] ||
+                        $rps{$_}{y} != $quest{p2}->[1]) {
+                        $allgo=0;
+                        last();
+                    }
+                }
+            }
+            # all participants have reached point 1, now point 2
+            if ($quest{stage}==1 && $allgo) {
+                $quest{stage}=2;
+                $allgo=0; # have not all reached p2 yet
+            }
+            elsif ($quest{stage} == 2 && $allgo) {
+                chanmsg(clog(join(", ",(@{$quest{questers}})[0..2]).", ".
+                             "and $quest{questers}->[3] have completed their ".
+                             "journey! 25% of their burden is eliminated."));
+                for (@{$quest{questers}}) {
+                    $rps{$_}{next} = int($rps{$_}{next} * .75);
+                }
+                undef(@{$quest{questers}});
+                $quest{qtime} = time() + 21600; # next quest starts in 6 hours
+                $quest{type} = 1; # probably not needed
+                writequestfile();
+            }
+            else {
+                my(%temp,$player);
+                # load keys of %temp with online users
+                ++@temp{grep { $rps{$_}{online} } keys(%rps)};
+                # delete questers from list
+                delete(@temp{@{$quest{questers}}});
+                while ($player = each(%temp)) {
+                    $rps{$player}{x} += int(rand(3))-1;
+                    $rps{$player}{y} += int(rand(3))-1;
+                    # if player goes over edge, wrap them back around
+                    if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x}=0; }
+                    if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y}=0; }
+                    if ($rps{$player}{x} < 0) { $rps{$player}{x}=$opts{mapx}; }
+                    if ($rps{$player}{y} < 0) { $rps{$player}{y}=$opts{mapy}; }
+                    
+                    if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
+                        !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
+                        if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
+                            !$rps{$player}{isadmin} && rand(100) < 1) {
+                            chanmsg("$player encounters ".
+                               $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}.
+                                    " and bows humbly.");
+                        }
+                        if (rand($onlinecount) < 1) {
+                            $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
+                            collision_fight($player,
+                                $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
+                        }
+                    }
+                    else {
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
+                    }
+                }
+                for (@{$quest{questers}}) {
+                    if ($quest{stage} == 1) {
+                        if (rand(100) < 1) {
+                            if ($rps{$_}{x} != $quest{p1}->[0]) {
+                                $rps{$_}{x} += ($rps{$_}{x} < $quest{p1}->[0] ?
+                                                1 : -1);
+                            }
+                            if ($rps{$_}{y} != $quest{p1}->[1]) {
+                                $rps{$_}{y} += ($rps{$_}{y} < $quest{p1}->[1] ?
+                                                1 : -1);
+                            }
+                        }
+                    }
+                    elsif ($quest{stage}==2) {
+                        if (rand(100) < 1) {
+                            if ($rps{$_}{x} != $quest{p2}->[0]) {
+                                $rps{$_}{x} += ($rps{$_}{x} < $quest{p2}->[0] ?
+                                                1 : -1);
+                            }
+                            if ($rps{$_}{y} != $quest{p2}->[1]) {
+                                $rps{$_}{y} += ($rps{$_}{y} < $quest{p2}->[1] ?
+                                                1 : -1);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        else {
+            for my $player (keys(%rps)) {
+                next unless $rps{$player}{online};
+                $rps{$player}{x} += int(rand(3))-1;
+                $rps{$player}{y} += int(rand(3))-1;
+                # if player goes over edge, wrap them back around
+                if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x} = 0; }
+                if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y} = 0; }
+                if ($rps{$player}{x} < 0) { $rps{$player}{x} = $opts{mapx}; }
+                if ($rps{$player}{y} < 0) { $rps{$player}{y} = $opts{mapy}; }
+                if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
+                    !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
+                    if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
+                        !$rps{$player}{isadmin} && rand(100) < 1) {
+                        chanmsg("$player encounters ".
+                           $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}.
+                                " and bows humbly.");
+                    }
+                    if (rand($onlinecount) < 1) {
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
+                        collision_fight($player,
+                            $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
+                    }
+                }
+                else {
+                    $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
+                    $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
+                }
+            }
+        }
+    }
+}
+
+sub mksalt { # generate a random salt for passwds
+    join '',('a'..'z','A'..'Z','0'..'9','/','.')[rand(64), rand(64)];
+}
+
+sub chanmsg { # send a message to the channel
+    my $msg = shift or return undef;
+    if ($silentmode & 1) { return undef; }
+    privmsg($msg, $opts{botchan}, shift);
+}
+
+sub privmsg { # send a message to an arbitrary entity
+    my $msg = shift or return undef;
+    my $target = shift or return undef;
+    my $force = shift;
+    if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
+        && !$force) {
+        return undef;
+    }
+    while (length($msg)) {
+        sts("PRIVMSG $target :".substr($msg,0,450),$force);
+        substr($msg,0,450)="";
+    }
+}
+
+sub notice { # send a notice to an arbitrary entity
+    my $msg = shift or return undef;
+    my $target = shift or return undef;
+    my $force = shift;
+    if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
+        && !$force) {
+        return undef;
+    }
+    while (length($msg)) {
+        sts("NOTICE $target :".substr($msg,0,450),$force);
+        substr($msg,0,450)="";
+    }
+}
+
+sub help { # print help message
+    (my $prog = $0) =~ s/^.*\///;
+
+    print "
+usage: $prog [OPTIONS]
+  --help, -h           Print this message
+  --verbose, -v        Print verbose messages
+  --server, -s         Specify IRC server:port to connect to
+  --botnick, -n        Bot's IRC nick
+  --botuser, -u        Bot's username
+  --botrlnm, -r        Bot's real name
+  --botchan, -c        IRC channel to join
+  --botident, -p       Specify identify-to-services command
+  --botmodes, -m       Specify usermodes for the bot to set upon connect
+  --botopcmd, -o       Specify command to send to server on successful connect
+  --botghostcmd, -g    Specify command to send to server to regain primary
+                       nickname when in use
+  --doban              Advertisement ban on/off flag
+  --okurl, -k          Bot will not ban for web addresses that contain these
+                       strings
+  --debug              Debug on/off flag
+  --helpurl            URL to refer new users to
+  --admincommurl       URL to refer admins to
+
+  Timing parameters:
+  --rpbase             Base time to level up
+  --rpstep             Time to next level = rpbase * (rpstep ** CURRENT_LEVEL)
+  --rppenstep          PENALTY_SECS=(PENALTY*(RPPENSTEP**CURRENT_LEVEL))
+
+";
+}
+
+sub itemsum {
+    my $user = shift;
+    # is this for a battle? if so, good users get a 10% boost and evil users get
+    # a 10% detriment
+    my $battle = shift;
+    return -1 unless defined $user;
+    my $sum = 0;
+    if ($user eq $primnick) {
+        for my $u (keys(%rps)) {
+            $sum = itemsum($u) if $sum < itemsum($u);
+        }
+        return $sum+1;
+    }
+    if (!exists($rps{$user})) { return -1; }
+    $sum += int($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) :
+               $sum;
+    }
+    return $sum;
+}
+
+sub daemonize() {
+    # win32 doesn't daemonize (this way?)
+    if ($^O eq "MSWin32") {
+        print debug("Nevermind, this is Win32, no I'm not.")."\n";
+        return;
+    }
+    use POSIX 'setsid';
+    $SIG{CHLD} = sub { };
+    fork() && exit(0); # kill parent
+    POSIX::setsid() || debug("POSIX::setsid() failed: $!",1);
+    $SIG{CHLD} = sub { };
+    fork() && exit(0); # kill the parent as the process group leader
+    $SIG{CHLD} = sub { };
+    open(STDIN,'/dev/null') || debug("Cannot read /dev/null: $!",1);
+    open(STDOUT,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
+    open(STDERR,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
+    # write our PID to $opts{pidfile}, or return semi-silently on failure
+    open(PIDFILE,">$opts{pidfile}") || do {
+        debug("Error: failed opening pid file: $!");
+        return;
+    };
+    print PIDFILE $$;
+    close(PIDFILE);
+}
+
+sub calamity { # suffer a little one
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    return unless @players;
+    my $player = $players[rand(@players)];
+    if (rand(10) < 1) {
+        my @items = ("Arme de Precision","Bouffe","Arme","Tenue (Rune 3)","Rune de Precision",
+                     "Accessoire (Rune 2)");
+        my $type = $items[rand(@items)];
+        if ($type eq "Arme de Precision") {
+            chanmsg(clog("$player tombe, abimant son Arme de precision ".
+                         "L $type de $player perd 10% de son efficacite."));
+        }
+        elsif ($type eq "Bouffe") {
+            chanmsg(clog("$player glisse et salit sa bouffe ".
+                         "bog! La $type de $player perd 10% de son ".
+                         "efficacite."));
+        }
+        elsif ($type eq "Arme") {
+            chanmsg(clog("$player a laisse son Arme de CaC dehors sous la pluie et celle ci rouille! ".
+                         "L $type de $player perd 10% de son efficacite."));
+        }
+        elsif ($type eq "Tenue (Rune 3)") {
+            chanmsg(clog("$player fout une touche de ketchup sur sa tenue en voulant".
+                         "manger un hamburger! La $type de $player perd 10% de son".
+                         "efficacite."));
+        }
+        elsif ($type eq "Accessoire (Rune 2)") {
+            chanmsg(clog("Un lutin essaye de voler l accessoire de $player\'s  ".
+                         "mais ne fait que l abimer! L $type de $player\'s perd 10% de son".
+                         "efficacite."));
+        }
+        else {
+            chanmsg(clog("$player a une fuite d'huile dans son vehicule ".
+                         "Le $type de $player perd 10% de son ".
+                         "efficacite."));
+        }
+        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}.=$suffix;
+    }
+    else {
+        my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
+        if (!open(Q,$opts{eventsfile})) {
+            return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+        }
+        my($i,$actioned);
+        while (my $line = <Q>) {
+            chomp($line);
+            if ($line =~ /^C (.*)/ && rand(++$i) < 1) { $actioned = $1; }
+        }
+        chanmsg(clog("$player $actioned. Cette terrible calamite l a ralenti ".
+                     "pour ".duration($time)." pour le niveau ".
+                     ($rps{$player}{level}+1)."."));
+        $rps{$player}{next} += $time;
+        chanmsg("$player atteint le prochain niveau dans ".duration($rps{$player}{next}).
+                ".");
+    }
+}
+
+sub godsend { # bless the unworthy
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    return unless @players;
+    my $player = $players[rand(@players)];
+    if (rand(10) < 1) {
+        my @items = ("Arme de Precision","Bouffe","Arme","Tenue (Rune 3)","Rune de Precision",
+                     "Accessoire (Rune 2)");
+        my $type = $items[rand(@items)];
+        if ($type eq "Arme de Precision") {
+            chanmsg(clog("$player\'s amulet was blessed by a passing cleric! ".
+                         "$player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "Bouffe") {
+            chanmsg(clog("$player\'s Bouffe ate a bolt of lightning! ".
+                         "$player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "Arme") {
+            chanmsg(clog("$player sharpened the edge of his "Arme de CaC"! ".
+                         "$player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "Tenue (Rune 3)") {
+            chanmsg(clog("A magician cast a spell of Rigidity on $player\'s ".
+                         ""Tenue (Rune 3)"! $player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "Accessoire (Rune 2)") {
+            chanmsg(clog("$player reinforced his shield with a dragon's ".
+                         "scales! $player\'s $type gains 10% effectiveness."));
+        }
+        else {
+            chanmsg(clog("The local wizard imbued $player\'s pants with a ".
+                         "Spirit of Fortitude! $player\'s $type gains 10% ".
+                         "effectiveness."));
+        }
+        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}.=$suffix;
+    }
+    else {
+        my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
+        my $actioned;
+        if (!open(Q,$opts{eventsfile})) {
+            return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+        }
+        my $i;
+        while (my $line = <Q>) {
+            chomp($line);
+            if ($line =~ /^G (.*)/ && rand(++$i) < 1) {
+                $actioned = $1;
+            }
+        }
+        chanmsg(clog("$player $actioned! Ce merveilleux evenement ".
+                     "l a booster de ".duration($time)." vers le niveau ".
+                     ($rps{$player}{level}+1)."."));
+        $rps{$player}{next} -= $time;
+        chanmsg("$player atteint le prochain niveau dans ".duration($rps{$player}{next}).
+                ".");
+    }
+}
+
+sub quest {
+    @{$quest{questers}} = grep { $rps{$_}{online} && $rps{$_}{level} > 39 &&
+                                 time()-$rps{$_}{lastlogin}>36000 } keys(%rps);
+    if (@{$quest{questers}} < 4) { return undef(@{$quest{questers}}); }
+    while (@{$quest{questers}} > 4) {
+        splice(@{$quest{questers}},int(rand(@{$quest{questers}})),1);
+    }
+    if (!open(Q,$opts{eventsfile})) {
+        return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+    }
+    my $i;
+    while (my $line = <Q>) {
+        chomp($line);
+        if ($line =~ /^Q/ && rand(++$i) < 1) {
+            if ($line =~ /^Q1 (.*)/) {
+                $quest{text} = $1;
+                $quest{type} = 1;
+                $quest{qtime} = time() + 43200 + int(rand(43201)); # 12-24 hours
+            }
+            elsif ($line =~ /^Q2 (\d+) (\d+) (\d+) (\d+) (.*)/) {
+                $quest{p1} = [$1,$2];
+                $quest{p2} = [$3,$4];
+                $quest{text} = $5;
+                $quest{type} = 2;
+                $quest{stage} = 1;
+            }
+        }
+    }
+    close(Q);
+    if ($quest{type} == 1) {
+        chanmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                "$quest{questers}->[3] a ete choisi par les dieux pour ".
+                "$quest{text}. La quete se termine dans ".duration($quest{qtime}-time()).
+                ".");    
+    }
+    elsif ($quest{type} == 2) {
+        chanmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                "$quest{questers}->[3] a ete choisi par les dieux pour ".
+                "$quest{text}. Il doit d abord atteindre [$quest{p1}->[0],".
+                "$quest{p1}->[1]], puis [$quest{p2}->[0],$quest{p2}->[1]].".
+                ($opts{mapurl}?" Regardez $opts{mapurl} pour voir le journal de leurs ".
+                "aventures.":""));
+    }
+    writequestfile();
+}
+
+sub questpencheck {
+    my $k = shift;
+    my ($quester,$player);
+    for $quester (@{$quest{questers}}) {
+        if ($quester eq $k) {
+            chanmsg(clog("$k\'s prudence and self-regard has brought the ".
+                         "wrath of the gods upon the realm. All your great ".
+                         "wickedness makes you as it were heavy with lead, ".
+                         "and to tend downwards with great weight and ".
+                         "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}));
+                $rps{$player}{pen_quest} += $gain;
+                $rps{$player}{next} += $gain;
+            }
+            undef(@{$quest{questers}});
+            $quest{qtime} = time() + 43200; # 12 hours
+        }
+    }
+}
+
+sub clog {
+    my $mesg = shift;
+    open(B,">>$opts{modsfile}") or do {
+        debug("Error: Cannot open $opts{modsfile}: $!");
+        chanmsg("Error: Cannot open $opts{modsfile}: $!");
+        return $mesg;
+    };
+    print B ts()."$mesg\n";
+    close(B);
+    return $mesg;
+}
+
+sub backup() {
+    if (! -d ".dbbackup/") { mkdir(".dbbackup",0700); }
+    if ($^O ne "MSWin32") {
+        system("cp $opts{dbfile} .dbbackup/$opts{dbfile}".time());
+    }
+    else {
+        system("copy $opts{dbfile} .dbbackup\\$opts{dbfile}".time());
+    }
+}
+
+sub penalize {
+    my $username = shift;
+    return 0 if !defined($username);
+    return 0 if !exists($rps{$username});
+    my $type = shift;
+    my $pen = 0;
+    questpencheck($username);
+    if ($type eq "quit") {
+        $pen = int(20 * ($opts{rppenstep}**$rps{$username}{level}));
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_quit}+=$pen;
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "nick") {
+        my $newnick = shift;
+        $pen = int(30 * ($opts{rppenstep}**$rps{$username}{level}));
+        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);
+        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}));
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_mesg}+=$pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               $type.".",$rps{$username}{nick});
+    }
+    elsif ($type eq "part") {
+        $pen = int(200 * ($opts{rppenstep}**$rps{$username}{level}));
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_part}+=$pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               "parting.",$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "kick") {
+        $pen = int(250 * ($opts{rppenstep}**$rps{$username}{level}));
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_kick}+=$pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               "being kicked.",$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "logout") {
+        $pen = int(20 * ($opts{rppenstep}**$rps{$username}{level}));
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_logout} += $pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               "LOGOUT command.",$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    $rps{$username}{next} += $pen;
+    return 1; # successfully penalized a user! woohoo!
+}
+
+sub debug {
+    (my $text = shift) =~ s/[\r\n]//g;
+    my $die = shift;
+    if ($opts{debug} || $opts{verbose}) {
+        open(DBG,">>$opts{debugfile}") or do {
+            chanmsg("Error: Cannot open debug file: $!");
+            return;
+        };
+        print DBG ts()."$text\n";
+        close(DBG);
+    }
+    if ($die) { die("$text\n"); }
+    return $text;
+}
+
+sub finduser {
+    my $nick = shift;
+    return undef if !defined($nick);
+    for my $user (keys(%rps)) {
+        next unless $rps{$user}{online};
+        if ($rps{$user}{nick} eq $nick) { return $user; }
+    }
+    return undef;
+}
+
+sub ha { # return 0/1 if username has access
+    my $user = shift;
+    if (!defined($user) || !exists($rps{$user})) {
+        debug("Error: Attempted ha() for invalid username \"$user\"");
+        return 0;
+    }
+    return $rps{$user}{isadmin};
+}
+
+sub checksplits { # removed expired split hosts from the hash
+    my $host;
+    while ($host = each(%split)) {
+        if (time()-$split{$host}{time} > $opts{splitwait}) {
+            $rps{$split{$host}{account}}{online} = 0;
+            delete($split{$host});
+        }
+    }
+}
+
+sub collision_fight {
+    my($u,$opp) = @_;
+    my $mysum = itemsum($u,1);
+    my $oppsum = itemsum($opp,1);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        my $gain = int($rps{$opp}{level}/4);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] has come upon $opp [$opproll/$oppsum".
+                     "] and taken them in combat! ".duration($gain)." is ".
+                     "removed from $u\'s clock."));
+        $rps{$u}{next} -= $gain;
+        chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
+        if (rand(35) < 1 && $opp ne $primnick) {
+            $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
+            chanmsg(clog("$u has dealt $opp a Critical Strike! ".
+                         duration($gain)." is added to $opp\'s clock."));
+            $rps{$opp}{next} += $gain;
+            chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).
+                    ".");
+        }
+        elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
+            my @items = ("Jouet","Arme de Precision","Bouffe","Arme","Casque (Rune 1)","Tenue (Rune 3)",
+                         "Rune de Force","Rune de Precision","Accessoire (Rune 2)",
+                         "Vehicule");
+            my $type = $items[rand(@items)];
+            if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) {
+                chanmsg("Durant la bataille acharnee, $opp a perdu son ".
+                        int($rps{$opp}{item}{$type})." $type! $u le prend, ".
+                        "et jette son ancien objet ".int($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;
+            }
+        }
+    }
+    else {
+        my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] has come upon $opp [$opproll/$oppsum".
+                     "] and been defeated in combat! ".duration($gain)." is ".
+                     "added to $u\'s clock."));
+        $rps{$u}{next} += $gain;
+        chanmsg("$u atteint le prochain niveau dans ".duration($rps{$u}{next}).".");
+    }
+}
+
+sub writequestfile {
+    return unless $opts{writequestfile};
+    open(QF,">$opts{questfilename}") or do {
+        chanmsg("Error: Cannot open $opts{questfilename}: $!");
+        return;
+    };
+    # if no active quest, just empty questfile. otherwise, write it
+    if (@{$quest{questers}}) {
+        if ($quest{type}==1) {
+            print QF "T $quest{text}\n".
+                     "Y 1\n".
+                     "S $quest{qtime}\n".
+                     "P1 $quest{questers}->[0]\n".
+                     "P2 $quest{questers}->[1]\n".
+                     "P3 $quest{questers}->[2]\n".
+                     "P4 $quest{questers}->[3]\n";
+        }
+        elsif ($quest{type}==2) {
+            print QF "T $quest{text}\n".
+                     "Y 2\n".
+                     "S $quest{stage}\n".
+                     "P $quest{p1}->[0] $quest{p1}->[1] $quest{p2}->[0] ".
+                        "$quest{p2}->[1]\n".
+                     "P1 $quest{questers}->[0] $rps{$quest{questers}->[0]}{x} ".
+                         "$rps{$quest{questers}->[0]}{y}\n".
+                     "P2 $quest{questers}->[1] $rps{$quest{questers}->[1]}{x} ".
+                         "$rps{$quest{questers}->[1]}{y}\n".
+                     "P3 $quest{questers}->[2] $rps{$quest{questers}->[2]}{x} ".
+                         "$rps{$quest{questers}->[2]}{y}\n".
+                     "P4 $quest{questers}->[3] $rps{$quest{questers}->[3]}{x} ".
+                         "$rps{$quest{questers}->[3]}{y}\n";
+        }
+    }
+    close(QF);
+}
+
+sub goodness {
+    my @players = grep { $rps{$_}{alignment} eq "g" &&
+                         $rps{$_}{online} } keys(%rps);
+    return unless @players > 1;
+    splice(@players,int(rand(@players)),1) while @players > 2;
+    my $gain = 5 + int(rand(8));
+    chanmsg(clog("$players[0] et $players[1] ne se sont pas laisses avoir par les pieges".
+                 "des sadiques . Ensemble ils ont prié Dieu".
+                 "et maintenant sa lumiere les guide. $gain\% ".
+                 "est enleve de leur compteur."));
+    $rps{$players[0]}{next} = int($rps{$players[0]}{next}*(1 - ($gain/100)));
+    $rps{$players[1]}{next} = int($rps{$players[1]}{next}*(1 - ($gain/100)));
+    chanmsg("$players[0] reaches next level in ".
+            duration($rps{$players[0]}{next}).".");
+    chanmsg("$players[1] reaches next level in ".
+            duration($rps{$players[1]}{next}).".");
+}
+
+sub evilness {
+    my @evil = grep { $rps{$_}{alignment} eq "e" &&
+                      $rps{$_}{online} } keys(%rps);
+    return unless @evil;
+    my $me = $evil[rand(@evil)];
+    if (int(rand(2)) < 1) {
+        # evil only steals from good :^(
+        my @good = grep { $rps{$_}{alignment} eq "g" &&
+                          $rps{$_}{online} } keys(%rps);
+        my $target = $good[rand(@good)];
+        my @items = ("Jouet","Arme de Precision","Bouffe","Arme","Casque (Rune 1)","Tenue (Rune 3)",
+                     "Rune de Force","Rune de Precision","Accessoire (Rune 2)",
+                     "Vehicule");
+        my $type = $items[rand(@items)];
+        if (int($rps{$target}{item}{$type}) > int($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 ".
+                         "sleeping! $me leaves his old level ".
+                         int($rps{$target}{item}{$type})." $type behind, ".
+                         "which $target then takes."));
+        }
+        else {
+            notice("Vous essayez de voler $target\'s $type, mais realisez ".
+                   "que son niveau est plus bas que le votre. Vous retournez dans les".
+                   "ombres.",$rps{$me}{nick});
+        }
+    }
+    else { # being evil only pays about half of the time...
+        my $gain = 1 + int(rand(5));
+        chanmsg(clog("$me est oublie par Satan. ".
+                     duration(int($rps{$me}{next} * ($gain/100)))." est ajoute ".
+                     "a son compteur."));
+        $rps{$me}{next} = int($rps{$me}{next} * (1 + ($gain/100)));
+        chanmsg("$me atteint le prochain niveau dans ".duration($rps{$me}{next}).".");
+    }
+}
+
+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}: $!");
+        return 0;
+    };
+    print RPS join("\t","# username",
+                        "pass",
+                        "is admin",
+                        "level",
+                        "class",
+                        "next ttl",
+                        "nick",
+                        "userhost",
+                        "online",
+                        "idled",
+                        "x pos",
+                        "y pos",
+                        "pen_mesg",
+                        "pen_nick",
+                        "pen_part",
+                        "pen_kick",
+                        "pen_quit",
+                        "pen_quest",
+                        "pen_logout",
+                        "created",
+                        "last login",
+                        "Arme de Precision",
+                        "Bouffe",
+                        "Casque (Rune 1)",
+                        "boots",
+                        "gloves",
+                        "Jouet",
+                        "leggings",
+                        "Accessoire (Rune 2)",
+                        "Tenue (Rune 3)",
+                        "Arme",
+                        "alignment")."\n";
+    my $k;
+    keys(%rps); # reset internal pointer
+    while ($k=each(%rps)) {
+        if (exists($rps{$k}{next}) && defined($rps{$k}{next})) {
+            print RPS join("\t",$k,
+                                $rps{$k}{pass},
+                                $rps{$k}{isadmin},
+                                $rps{$k}{level},
+                                $rps{$k}{class},
+                                $rps{$k}{next},
+                                $rps{$k}{nick},
+                                $rps{$k}{userhost},
+                                $rps{$k}{online},
+                                $rps{$k}{idled},
+                                $rps{$k}{x},
+                                $rps{$k}{y},
+                                $rps{$k}{pen_mesg},
+                                $rps{$k}{pen_nick},
+                                $rps{$k}{pen_part},
+                                $rps{$k}{pen_kick},
+                                $rps{$k}{pen_quit},
+                                $rps{$k}{pen_quest},
+                                $rps{$k}{pen_logout},
+                                $rps{$k}{created},
+                                $rps{$k}{lastlogin},
+                                $rps{$k}{item}{"Arme de Precision"},
+                                $rps{$k}{item}{Bouffe},
+                                $rps{$k}{item}{"Casque (Rune 1)"},
+                                $rps{$k}{item}{"Vehicule"},
+                                $rps{$k}{item}{"Rune de Force"},
+                                $rps{$k}{item}{Jouet},
+                                $rps{$k}{item}{"Rune de Precision"},
+                                $rps{$k}{item}{"Accessoire (Rune 2)"},
+                                $rps{$k}{item}{"Tenue (Rune 3)"},
+                                $rps{$k}{item}{"Arme de CaC"},
+                                $rps{$k}{alignment})."\n";
+        }
+    }
+    close(RPS);
+}
+
+sub readconfig {
+    if (! -e ".irpg.conf") {
+        debug("Error: Cannot find .irpg.conf. Copy it to this directory, ".
+              "please.",1);
+    }
+    else {
+        open(CONF,"<.irpg.conf") or do {
+            debug("Failed to open config file .irpg.conf: $!",1);
+        };
+        my($line,$key,$val);
+        while ($line=<CONF>) {
+            next() if $line =~ /^#/; # skip comments
+            $line =~ s/[\r\n]//g;
+            $line =~ s/^\s+//g;
+            next() if !length($line); # skip blank lines
+            ($key,$val) = split(/\s+/,$line,2);
+            $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; }
+            if ($key eq "die") {
+                die("Please edit the file .irpg.conf to setup your bot's ".
+                    "options. Also, read the README file if you haven't ".
+                    "yet.\n");
+            }
+            elsif ($key eq "server") { push(@{$opts{servers}},$val); }
+            elsif ($key eq "okurl") { push(@{$opts{okurl}},$val); }
+            else { $opts{$key} = $val; }
+        }
+    }
+}
Index: /irpg/trunk/bot/bot.v3.1.2.pl
===================================================================
--- /irpg/trunk/bot/bot.v3.1.2.pl	(revision 1444)
+++ /irpg/trunk/bot/bot.v3.1.2.pl	(revision 1444)
@@ -0,0 +1,2827 @@
+#!/usr/local/bin/perl
+# irpg bot v3.1.2 by jotun, jotun@idlerpg.net, et al. See http://idlerpg.net/
+#
+# Some code within this file was written by authors other than myself. As such,
+# distributing this code or distributing modified versions of this code is
+# strictly prohibited without written authorization from the authors. Contact
+# jotun@idlerpg.net. Please note that this may change (at any time, no less) if
+# authorization for distribution is given by patch submitters.
+#
+# As a side note, patches submitted for this project are automatically taken to
+# be freely distributable and modifiable for any use, public or private, though
+# I make no claim to ownership; original copyrights will be retained.. except as
+# I've just stated.
+#
+# Please mail bugs, etc. to me. Patches are welcome to fix bugs or clean up
+# the code, but please do not use a radically different coding style. Thanks
+# to everyone that's contributed!
+#
+# NOTE: This code should NOT be run as root. You deserve anything that happens
+#       to you if you run this code as a superuser. Also, note that giving a
+#       user admin access to the bot effectively gives them full access to the
+#       user under which your bot runs, as they can use the PEVAL command to
+#       execute any command, or possibly even change your password. I sincerely
+#       suggest that you exercise extreme caution when giving someone admin
+#       access to your bot, or that you disable the PEVAL command for non-owner
+#       accounts in your config file, .irpg.conf
+
+use strict;
+use warnings;
+use IO::Socket;
+use IO::Socket::INET6;
+use IO::Select;
+use Data::Dumper;
+use Getopt::Long;
+use Locale::gettext;
+use POSIX;
+
+my %opts;
+
+readconfig();
+
+my $version = "3.1.2+johm+chschu+gettext";
+
+# command line overrides .irpg.conf
+GetOptions(\%opts,
+    "help|h",
+    "verbose|v",
+    "ipv6",
+    "lang",
+    "debug",
+    "debugfile=s",
+    "server|s=s",
+    "botnick|n=s",
+    "botuser|u=s",
+    "botrlnm|r=s",
+    "botchan|c=s",
+    "botident|p=s",
+    "botmodes|m=s",
+    "botopcmd|o=s",
+    "localaddr=s",
+    "botghostcmd|g=s",
+    "helpurl=s",
+    "admincommurl=s",
+    "doban",
+    "silentmode=i",
+    "writequestfile",
+    "questfilename=s",
+    "voiceonlogin",
+    "noccodes",
+    "nononp",
+    "mapurl=s",
+    "statuscmd",
+    "pidfile=s",
+    "reconnect",
+    "reconnect_wait=i",
+    "self_clock=i",
+    "modsfile=s",
+    "casematters",
+    "detectsplits",
+    "autologin",
+    "splitwait=i",
+    "allowuserinfo",
+    "noscale",
+    "phonehome",
+    "owner=s",
+    "owneraddonly",
+    "ownerdelonly",
+    "ownerpevalonly",
+    "checkupdates",
+    "senduserlist",
+    "limitpen=i",
+    "mapx=i",
+    "mapy=i",
+    "modesperline=i",
+    "okurl|k=s@",
+    "eventsfile=s",
+    "rpstep=f",
+    "rpbase=i",
+    "rppenstep=f",
+    "dbfile|irpgdb|db|d=s",
+) or debug("Error: Could not parse command line. Try $0 --help\n",1);
+
+$opts{help} and do { help(); exit 0; };
+
+debug("Config: read $_: ".Dumper($opts{$_})) for keys(%opts);
+
+setlocale(LC_MESSAGES, $opts{lang});
+bindtextdomain("irpg","lang");
+textdomain("irpg");
+
+my $outbytes = 0; # sent bytes
+my $primnick = $opts{botnick}; # for regain or register checks
+my $inbytes = 0; # received bytes
+my %onchan; # users on game channel
+my %rps; # role-players
+my %quest = (
+    questers => [],
+    p1 => [], # point 1 for q2
+    p2 => [], # point 2 for q2
+    qtime => time() + int(rand(21600)), # first quest starts in <=6 hours
+    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
+my $pausemode = 0; # pausemode on/off flag
+my $silentmode = 0; # silent mode 0/1/2/3, see head of file
+my @queue; # outgoing message queue
+my $lastreg = 0; # holds the time of the last reg. cleared every second.
+                 # prevents more than one account being registered / second
+my $registrations = 0; # count of registrations this period
+my $sel; # IO::Select object
+my $lasttime = 1; # last time that rpcheck() was run
+my $buffer; # buffer for socket stuff
+my $conn_tries = 0; # number of connection tries. gives up after trying each
+                    # server twice
+my $sock; # IO::Socket::INET object
+my %split; # holds nick!user@hosts for clients that have been netsplit
+my $freemessages = 4; # number of "free" privmsgs we can send. 0..$freemessages
+
+sub daemonize(); # prototype to avoid warnings
+
+if (! -e $opts{dbfile}) {
+    $|=1;
+    %rps = ();
+    printf gettext("%s does not appear to exist. I'm guessing this is your ".
+          "first time using IRPG. Please give an account name that you would ".
+          "like to have admin access [%s]: "), $opts{dbfile}, $opts{owner};
+    chomp(my $uname = <STDIN>);
+    $uname =~ s/\s.*//g;
+    $uname = length($uname)?$uname:$opts{owner};
+    print gettext("Enter a character class for this account: ");
+    chomp(my $uclass = <STDIN>);
+    $rps{$uname}{class} = substr($uclass,0,30);
+    print gettext("Enter a password for this account: ");
+    if ($^O ne "MSWin32") {
+        system("stty -echo");
+    }
+    chomp(my $upass = <STDIN>);
+    if ($^O ne "MSWin32") {
+        system("stty echo");
+    }
+    $rps{$uname}{pass} = crypt($upass,mksalt());
+    $rps{$uname}{next} = $opts{rpbase};
+    $rps{$uname}{nick} = "";
+    $rps{$uname}{userhost} = "";
+    $rps{$uname}{level} = 0;
+    $rps{$uname}{online} = 0;
+    $rps{$uname}{idled} = 0;
+    $rps{$uname}{created} = time();
+    $rps{$uname}{lastlogin} = time();
+    $rps{$uname}{x} = int(rand($opts{mapx}));
+    $rps{$uname}{y} = int(rand($opts{mapy}));
+    $rps{$uname}{alignment}="n";
+    $rps{$uname}{isadmin} = 1;
+    for my $item ("ring","amulet","charm","weapon","helm",
+                  "tunic","pair of gloves","shield",
+                  "set of leggings","pair of boots") {
+        $rps{$uname}{item}{$item} = 0;
+    }
+    for my $pen ("pen_mesg","pen_nick","pen_part",
+                 "pen_kick","pen_quit","pen_quest",
+                 "pen_logout","pen_logout") {
+        $rps{$uname}{$pen} = 0;
+    }
+    writedb();
+    printf gettext("OK, wrote you into %s \n"),$opts{dbfile};
+}
+
+# this is almost silly...
+if ($opts{checkupdates}) {
+    print gettext("Checking for updates...\n\n");
+    my $tempsock = IO::Socket::INET->new(PeerAddr=>"jotun.ultrazone.org:80",
+                                         Timeout => 15);
+    if ($tempsock) {
+        print $tempsock "GET /g7/version.php?version=$version HTTP/1.1\r\n".
+                        "Host: jotun.ultrazone.org:80\r\n\r\n";
+        my($line,$newversion);
+        while ($line=<$tempsock>) {
+            chomp($line);
+            next() unless $line;
+            if ($line =~ /^Current version : (\S+)/) {
+                if ($version ne $1) {
+                    print gettext("There is an update available! Changes include:\n");
+                    $newversion=1;
+                }
+                else {
+                    printf gettext("You are running the latest version (v%s).".
+				   "\n"), $1;
+                    close($tempsock);
+                    last();
+                }
+            }
+            elsif ($newversion && $line =~ /^(  -? .+)/) { print "$1\n"; }
+            elsif ($newversion && $line =~ /^URL: (.+)/) {
+                printf gettext("\nGet the newest version from %s!\n"), $1;
+                close($tempsock);
+                last();
+            }
+        }
+    }
+    else { print gettext(debug("Could not connect to update server."))."\n"; }
+}
+
+print "\n".gettext(debug("Becoming a daemon..."))."\n";
+daemonize();
+
+$SIG{HUP} = "readconfig"; # sighup = reread config file
+
+CONNECT: # cheese.
+
+loaddb();
+
+while (!$sock && $conn_tries < 2*@{$opts{servers}}) {
+    debug("Connecting to $opts{servers}->[0]...");
+    my %sockinfo = (PeerAddr => $opts{servers}->[0]);
+    if ($opts{localaddr}) { $sockinfo{LocalAddr} = $opts{localaddr}; }
+
+    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
+        push(@{$opts{servers}},shift(@{$opts{servers}}));
+    }
+    else { debug("Connected."); }
+}
+
+if (!$sock) {
+    debug("Error: Too many connection failures, exhausted server list.\n",1);
+}
+
+$conn_tries=0;
+
+$sel = IO::Select->new($sock);
+
+sts("NICK $opts{botnick}");
+sts("USER $opts{botuser} 0 0 :$opts{botrlnm}");
+
+while (1) {
+    my($readable) = IO::Select->select($sel,undef,undef,0.5);
+    if (defined($readable)) {
+        my $fh = $readable->[0];
+        my $buffer2;
+        $fh->recv($buffer2,512,0);
+        if (length($buffer2)) {
+            $buffer .= $buffer2;
+            while (index($buffer,"\n") != -1) {
+                my $line = substr($buffer,0,index($buffer,"\n")+1);
+                $buffer = substr($buffer,length($line));
+                parse($line);
+            }
+        }
+        else {
+            # uh oh, we've been disconnected from the server, possibly before
+            # we've logged in the users in %auto_login. so, we'll set those
+            # users' online flags to 1, rewrite db, and attempt to reconnect
+            # (if that's wanted of us)
+            $rps{$_}{online}=1 for keys(%auto_login);
+            writedb();
+
+            close($fh);
+            $sel->remove($fh);
+
+            if ($opts{reconnect}) {
+                undef(@queue);
+                undef($sock);
+                debug("Socket closed; disconnected. Cleared outgoing message ".
+                      "queue. Waiting $opts{reconnect_wait}s before next ".
+                      "connection attempt...");
+                sleep($opts{reconnect_wait});
+                goto CONNECT;
+            }
+            else { debug("Socket closed; disconnected.",1); }
+        }
+    }
+    else { select(undef,undef,undef,1); }
+    if ((time()-$lasttime) >= $opts{self_clock}) { rpcheck(); }
+}
+
+
+sub parse {
+    my($in) = shift;
+    $inbytes += length($in); # increase parsed byte count
+    $in =~ s/[\r\n]//g; # strip all \r and \n
+    debug("<- $in");
+    my @arg = split(/\s/,$in); # split into "words"
+    my $usernick = substr((split(/!/,$arg[0]))[0],1);
+    # logged in char name of nickname, or undef if nickname is not online
+    my $username = finduser($usernick);
+    if (lc($arg[0]) eq 'ping') { sts("PONG $arg[1]",1); }
+    elsif (lc($arg[0]) eq 'error') {
+        # uh oh, we've been disconnected from the server, possibly before we've
+        # logged in the users in %auto_login. so, we'll set those users' online
+        # flags to 1, rewrite db, and attempt to reconnect (if that's wanted of
+        # us)
+        $rps{$_}{online}=1 for keys(%auto_login);
+        writedb();
+        return;
+    }
+    $arg[1] = lc($arg[1]); # original case no longer matters
+    if ($arg[1] eq '433' && $opts{botnick} eq $arg[3]) {
+        $opts{botnick} .= 0;
+        sts("NICK $opts{botnick}");
+    }
+    elsif ($arg[1] eq 'join') {
+        # %onchan holds time user joined channel. used for the advertisement ban
+        $onchan{$usernick}=time();
+        if ($opts{'detectsplits'} && exists($split{substr($arg[0],1)})) {
+            delete($split{substr($arg[0],1)});
+        }
+        elsif ($opts{botnick} eq $usernick) {
+            sts("WHO $opts{botchan}");
+            (my $opcmd = $opts{botopcmd}) =~ s/%botnick%/$opts{botnick}/eg;
+            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(sprintf(gettext("%s, the level %u ".
+                                            "%s, is now online from ".
+                                            "nickname %s. Next level in %s."),
+				    $k,$rps{$k}{level},$rps{$k}{class},
+				    $usernick,duration($rps{$k}{next})));       
+                    notice(sprintf(gettext("Logon successful. Next level in ".
+					   "%s."),duration($rps{$k}{next})),
+			   $usernick);
+                }
+            }
+        }
+    }
+    elsif ($arg[1] eq 'quit') {
+        # if we see our nick come open, grab it (skipping queue)
+        if ($usernick eq $primnick) { sts("NICK $primnick",1); }
+        elsif ($opts{'detectsplits'} &&
+               "@arg[2..$#arg]" =~ /^:\S+\.\S+ \S+\.\S+$/) {
+            if (defined($username)) { # user was online
+                $split{substr($arg[0],1)}{time}=time();
+                $split{substr($arg[0],1)}{account}=$username;
+            }
+        }
+        else {
+            penalize($username,"quit");
+        }
+        delete($onchan{$usernick});
+    }
+    elsif ($arg[1] eq 'nick') {
+        # if someone (nickserv) changes our nick for us, update $opts{botnick}
+        if ($usernick eq $opts{botnick}) {
+            $opts{botnick} = substr($arg[2],1);
+        }
+        # if we see our nick come open, grab it (skipping queue), unless it was
+        # us who just lost it
+        elsif ($usernick eq $primnick) { sts("NICK $primnick",1); }
+        else {
+            penalize($username,"nick",$arg[2]);
+            $onchan{substr($arg[2],1)} = delete($onchan{$usernick});
+        }
+    }
+    elsif ($arg[1] eq 'part') {
+        penalize($username,"part");
+        delete($onchan{$usernick});
+    }
+    elsif ($arg[1] eq 'kick') {
+        $usernick = $arg[3];
+        penalize(finduser($usernick),"kick");
+        delete($onchan{$usernick});
+    }
+    # don't penalize /notices to the bot
+    elsif ($arg[1] eq 'notice' && $arg[2] ne $opts{botnick}) {
+        penalize($username,"notice",length("@arg[3..$#arg]")-1);
+    }
+    elsif ($arg[1] eq '001') {
+        # send our identify command, set our usermode, join channel
+        sts($opts{botident});
+        sts("MODE $opts{botnick} :$opts{botmodes}");
+        sts("JOIN $opts{botchan}");
+        $opts{botchan} =~ s/ .*//; # strip channel key if present
+    }
+    elsif ($arg[1] eq '315') {
+        # 315 is /WHO end. report who we automagically signed online iff it will
+        # print < 1k of text
+        if (keys(%auto_login)) {
+            # not a true measure of size, but easy
+            if (length("%auto_login") < 1024 && $opts{senduserlist}) {
+                chanmsg(sprintf(gettext("%u users matching %u hosts ".
+					"automatically logged in; accounts: "),
+				scalar(keys(%auto_login)),
+				scalar(keys(%prev_online))).
+			join(", ",keys(%auto_login)));
+            }
+            else {
+                chanmsg(sprintf(gettext("%u users matching %u hosts ".
+					"automatically logged in."),
+				scalar(keys(%auto_login)),
+				scalar(keys(%prev_online))));
+            }
+            if ($opts{voiceonlogin}) {
+                my @vnicks = map { $rps{$_}{nick} } keys(%auto_login);
+                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(gettext("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; }
+    }
+    elsif ($arg[1] eq '352') {
+        my $user;
+        # 352 is one line of /WHO. check that the nick!user@host exists as a key
+        # in %prev_online, the list generated in loaddb(). the value is the user
+        # to login
+        $onchan{$arg[7]}=time();
+        if (exists($prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]})) {
+            $rps{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}{online} = 1;
+            $auto_login{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}=1;
+        }
+    }
+    elsif ($arg[1] eq 'privmsg') {
+        $arg[0] = substr($arg[0],1); # strip leading : from privmsgs
+        if (lc($arg[2]) eq lc($opts{botnick})) { # to us, not channel
+            $arg[3] = lc(substr($arg[3],1)); # lowercase, strip leading :
+            if ($arg[3] eq "\1version\1") {
+                notice("\1VERSION IRPG bot v$version by jotun; ".
+                       "http://idlerpg.net/\1",$usernick);
+            }
+            elsif ($arg[3] eq "peval") {
+                if (!ha($username) || ($opts{ownerpevalonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+				    "PEVAL"), $usernick);
+                }
+                else {
+                    my @peval = eval "@arg[4..$#arg]";
+                    if (@peval >= 4 || length("@peval") > 1024) {
+                        privmsg("Command produced too much output to send ".
+                                "outright; queueing ".length("@peval").
+                                " bytes in ".scalar(@peval)." items. Use ".
+                                "CLEARQ to clear queue if needed.",$usernick,1);
+                        privmsg($_,$usernick) for @peval;
+                    }
+                    else { privmsg($_,$usernick, 1) for @peval; }
+                    privmsg("EVAL ERROR: $@", $usernick, 1) if $@;
+                }
+            }
+            elsif ($arg[3] eq "register") {
+                if (defined $username) {
+                    privmsg(sprintf(gettext("Sorry, you are already online as ".
+					    "%s."), $username), $usernick);
+                }
+                else {
+                    if ($#arg < 6 || $arg[6] eq "") {
+                        privmsg(gettext("Try: REGISTER <char name> <password> ".
+					"<class>"), $usernick);
+                        privmsg(gettext("IE : REGISTER Poseidon MyPassword God ".
+					"of the Sea"), $usernick);
+                    }
+                    elsif ($pausemode) {
+                        privmsg(gettext("Sorry, new accounts may not be ".
+					"registered while the bot is in pause ".
+					"mode; please wait a few minutes and ".
+					"try again."),$usernick);
+                    }
+                    elsif (exists $rps{$arg[4]} || ($opts{casematters} &&
+                           scalar(grep { lc($arg[4]) eq lc($_) } keys(%rps)))) {
+                        privmsg(gettext("Sorry, that character name is already ".
+					"in use."), $usernick);
+                    }
+                    elsif (lc($arg[4]) eq lc($opts{botnick}) ||
+                           lc($arg[4]) eq lc($primnick)) {
+                        privmsg(gettext("Sorry, that character name cannot be ".
+					"registered."), $usernick);
+                    }
+                    elsif (!exists($onchan{$usernick})) {
+                        privmsg(sprintf(gettext("Sorry, you're not in %s."),
+					$opts{botchan}), $usernick);
+                    }
+                    elsif (length($arg[4]) > 16 || length($arg[4]) < 1) {
+                        privmsg(gettext("Sorry, character names must be < 17 ".
+					"and > 0 chars long."), $usernick);
+                    }
+                    elsif ($arg[4] =~ /^#/) {
+                        privmsg(gettext("Sorry, character names may not begin ".
+					"with #."), $usernick);
+                    }
+                    elsif ($arg[4] =~ /\001/) {
+                        privmsg(gettext("Sorry, character names may not include".
+                                " character \\001."),$usernick);
+                    }
+                    elsif ($opts{noccodes} && ($arg[4] =~ /[[:cntrl:]]/ ||
+                           "@arg[6..$#arg]" =~ /[[:cntrl:]]/)) {
+                        privmsg(gettext("Sorry, neither character names nor ".
+					"classes may include control codes."),
+				$usernick);
+                    }
+                    elsif ($opts{nononp} && ($arg[4] =~ /[[:^print:]]/ ||
+                           "@arg[6..$#arg]" =~ /[[:^print:]]/)) {
+                        privmsg(gettext("Sorry, neither character names nor ".
+					"classes may include non-printable ".
+					"chars."), $usernick);
+                    }
+                    elsif (length("@arg[6..$#arg]") > 30) {
+                        privmsg(gettext("Sorry, character classes must be < 31".
+					" chars long."), $usernick);
+                    }
+                    elsif (time() == $lastreg) {
+                        privmsg(gettext("Wait 1 second and try again."),
+				$usernick);                
+                    }
+                    else {
+                        if ($opts{voiceonlogin}) {
+                            sts("MODE $opts{botchan} +v :$usernick");
+                        }
+                        ++$registrations;
+                        $lastreg = time();
+                        $rps{$arg[4]}{next} = $opts{rpbase};
+                        $rps{$arg[4]}{class} = "@arg[6..$#arg]";
+                        $rps{$arg[4]}{level} = 0;
+                        $rps{$arg[4]}{online} = 1;
+                        $rps{$arg[4]}{nick} = $usernick;
+                        $rps{$arg[4]}{userhost} = $arg[0];
+                        $rps{$arg[4]}{created} = time();
+                        $rps{$arg[4]}{lastlogin} = time();
+                        $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
+                        $rps{$arg[4]}{x} = int(rand($opts{mapx}));
+                        $rps{$arg[4]}{y} = int(rand($opts{mapy}));
+                        $rps{$arg[4]}{alignment}="n";
+                        $rps{$arg[4]}{isadmin} = 0;
+                        for my $item ("ring","amulet","charm","weapon","helm",
+                                      "tunic","pair of gloves","shield",
+                                      "set of leggings","pair of boots") {
+                            $rps{$arg[4]}{item}{$item} = 0;
+                        }
+                        for my $pen ("pen_mesg","pen_nick","pen_part",
+                                     "pen_kick","pen_quit","pen_quest",
+                                     "pen_logout","pen_logout") {
+                            $rps{$arg[4]}{$pen} = 0;
+                        }
+                        chanmsg(sprintf(gettext("Welcome %s\'s new player %s, ".
+						"the %s! Next level in %s."),
+					$usernick,$arg[4],(@arg[6..$#arg]),
+					duration($opts{rpbase})));
+                        privmsg(sprintf(gettext("Success! Account %s created. ".
+						"You have %u seconds idleness ".
+						"until you reach level 1."),
+					$arg[4],$opts{rpbase}), $usernick);
+                        privmsg(gettext("NOTE: The point of the game is to see ".
+					"who can idle the longest. As such, ".
+					"talking in the channel, parting, ".
+					"quitting, and changing nicks all ".
+					"penalize you."),$usernick);
+                        if ($opts{phonehome}) {
+                            my $tempsock = IO::Socket::INET->new(PeerAddr=>
+                                "jotun.ultrazone.org:80");
+                            if ($tempsock) {
+                                print $tempsock
+                                    "GET /g7/count.php?new=1 HTTP/1.1\r\n".
+                                    "Host: jotun.ultrazone.org:80\r\n\r\n";
+                                sleep(1);
+                                close($tempsock);
+                            }
+                        }
+                    }
+                }
+            }
+            elsif ($arg[3] eq "delold") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+				    "DELOLD"), $usernick);
+                }
+                # insure it is a number
+                elsif ($arg[4] !~ /^[\d\.]+$/) {
+                    privmsg(gettext("Try: DELOLD <# of days>"), $usernick, 1);
+                }
+                else {
+                    my @oldaccounts = grep { (time()-$rps{$_}{lastlogin}) >
+                                             ($arg[4] * 86400) &&
+                                             !$rps{$_}{online} } keys(%rps);
+                    delete(@rps{@oldaccounts});
+                    chanmsg(sprintf(gettext("%u accounts not accessed in ".
+					    "the last %u days removed by %s."),
+				    scalar(@oldaccounts),$arg[4],$arg[0]));
+                }
+            }
+            elsif ($arg[3] eq "del") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+				    "DEL"), $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                   privmsg(gettext("Try: DEL <char name>"), $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such account %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                else {
+                    delete($rps{$arg[4]});
+                    chanmsg(sprintf(gettext("Account %s removed by %s."),
+				    $arg[4],$arg[0]));
+                }
+            }
+            elsif ($arg[3] eq "mkadmin") {
+                if (!ha($username) || ($opts{owneraddonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+				    "MKADMIN"), $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg(gettext("Try: MKADMIN <char name>"), $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such account %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{isadmin}=1;
+                    privmsg(sprintf(gettext("Account %s is now a bot admin."),
+				    $arg[4]),$usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "deladmin") {
+                if (!ha($username) || ($opts{ownerdelonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+			    "DELADMIN"), $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg(gettext("Try: DELADMIN <char name>"), $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such account %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                elsif ($arg[4] eq $opts{owner}) {
+                    privmsg(gettext("Cannot DELADMIN owner account."),
+			    $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{isadmin}=0;
+                    privmsg(sprintf(gettext("Account %s is no longer a bot ".
+					    "admin."),$arg[4]), $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "hog") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+				    "HOG"), $usernick);
+                }
+                else {
+                    chanmsg(sprintf(gettext("%s has summoned the Hand of God."),
+				    $usernick));
+                    hog();
+                }
+            }
+            elsif ($arg[3] eq "rehash") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+			    "REHASH"), $usernick);
+                }
+                else {
+                    readconfig();
+                    privmsg(gettext("Reread config file."),$usernick,1);
+                    $opts{botchan} =~ s/ .*//; # strip channel key if present
+                }
+            }
+            elsif ($arg[3] eq "chpass") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+			    "CHPASS"), $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg(gettext("Try: CHPASS <char name> <new pass>"),
+			    $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such username %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
+                    privmsg(sprintf(gettext("Password for %s changed."),
+				    $arg[4]), $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "chuser") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+			    "CHUSER"), $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg(gettext("Try: CHUSER <char name> <new char name>"),
+                            $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such username %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                elsif (exists($rps{$arg[5]})) {
+                    privmsg(sprintf(gettext("Username %s is already taken."),
+				    $arg[5]), $usernick,1);
+                }
+                else {
+                    $rps{$arg[5]} = delete($rps{$arg[4]});
+                    privmsg(sprintf(gettext("Username for %s changed to %s."),
+					    $arg[4],$arg[5]), $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "chclass") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+			    "CHCLASS"), $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg(gettext("Try: CHCLASS <char name> <new char class>"),
+                            $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such username %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{class} = "@arg[5..$#arg]";
+                    privmsg(sprintf(gettext("Class for %s changed to %s."),
+				    $arg[4],(@arg[5..$#arg])), $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "push") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You don't have access to %s."),
+			    "PUSH"), $usernick);
+                }
+                # insure it's a positive or negative, integral number of seconds
+                elsif ($arg[5] !~ /^\-?\d+$/) {
+                    privmsg(gettext("Try: PUSH <char name> <seconds>"),
+			    $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg(sprintf(gettext("No such username %s."),$arg[4]),
+			    $usernick, 1);
+                }
+                elsif ($arg[5] > $rps{$arg[4]}{next}) {
+                    privmsg(sprintf(gettext("Time to level for %s (%u s) is ".
+					    "lower than %d; setting TTL to 0."),
+				    $arg[4],$rps{$arg[4]}{next},$arg[5]),
+                            $usernick, 1);
+                    chanmsg(sprintf(gettext("%s has pushed %s %d seconds ".
+					    "toward level %u"),
+				    $usernick,$arg[4],$rps{$arg[4]}{next},
+				    ($rps{$arg[4]}{level}+1)));
+                    $rps{$arg[4]}{next}=0;
+                }
+                else {
+                    $rps{$arg[4]}{next} -= $arg[5];
+                     chanmsg(sprintf(gettext("%s has pushed %s %d seconds ".
+					     "toward level %u. %2\$s reaches ".
+					     "next level in %5\$s."),
+				     $usernick,$arg[4],$arg[5],
+				     ($rps{$arg[4]}{level}+1),
+				     duration($rps{$arg[4]}{next})));
+                }
+            }   
+            elsif ($arg[3] eq "logout") {
+                if (defined($username)) {
+                    penalize($username,"logout");
+                }
+                else {
+                    privmsg(gettext("You are not logged in."), $usernick);
+                }
+            }
+            elsif ($arg[3] eq "quest") {
+                if (!@{$quest{questers}}) {
+                    privmsg(gettext("There is no active quest."),$usernick);
+                }
+                elsif ($quest{type} == 1) {
+                    privmsg(sprintf(gettext("%s, and %s are on a quest to %s".
+					    ". Quest to complete in %s."),
+				    join(", ",(@{$quest{questers}})[0..2]),
+				    $quest{questers}->[3],$quest{text},
+				    duration($quest{qtime}-time()))
+                            ,$usernick);
+                }
+                elsif ($quest{type} == 2) {
+                    privmsg(sprintf(gettext("%s, and %s are on a quest to ".
+					    "%s. Participants must first reach ".
+					    "[%u,%u], then [%u,%u]."),
+				    join(", ",(@{$quest{questers}})[0..2]),
+				    $quest{questers}->[3],$quest{text},
+				    $quest{p1}->[0],$quest{p1}->[1],
+				    $quest{p2}->[0],$quest{p2}->[1]).
+                            ($opts{mapurl}?sprintf(gettext(" See %s to monitor ".
+							   "their journey's ".
+							   "progress."),
+						   $opts{mapurl}):""),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "status" && $opts{statuscmd}) {
+                if (!defined($username)) {
+                    privmsg(gettext("You are not logged in."), $usernick);
+                }
+                # argument is optional
+                elsif ($arg[4] && !exists($rps{$arg[4]})) {
+                    privmsg(gettext("No such user."),$usernick);
+                }
+                elsif ($arg[4]) { # optional 'user' argument
+                    privmsg("$arg[4]: Level $rps{$arg[4]}{level} ".
+                            "$rps{$arg[4]}{class}; Status: O".
+                            ($rps{$arg[4]}{online}?"n":"ff")."line; ".
+                            "TTL: ".duration($rps{$arg[4]}{next})."; ".
+                            "Idled: ".duration($rps{$arg[4]}{idled}).
+                            "; Item sum: ".itemsum($arg[4]),$usernick);
+                }
+                else { # no argument, look up this user
+                    privmsg("$username: Level $rps{$username}{level} ".
+                            "$rps{$username}{class}; Status: O".
+                            ($rps{$username}{online}?"n":"ff")."line; ".
+                            "TTL: ".duration($rps{$username}{next})."; ".
+                            "Idled: ".duration($rps{$username}{idled})."; ".
+                            "Item sum: ".itemsum($username),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "whoami") {
+                if (!defined($username)) {
+                    privmsg(gettext("You are not logged in."), $usernick);
+                }
+                else {
+                    privmsg(sprintf(gettext("You are %s, the level %u %s. ".
+					    "Next level in %s"),
+				    $username,$rps{$username}{level},
+				    $rps{$username}{class},
+				    duration($rps{$username}{next})), $usernick);
+                }
+            }
+            elsif ($arg[3] eq "newpass") {
+                if (!defined($username)) {
+                    privmsg(gettext("You are not logged in."), $usernick)
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg(gettext("Try: NEWPASS <new password>"), $usernick);
+                }
+                else {
+                    $rps{$username}{pass} = crypt($arg[4],mksalt());
+                    privmsg(gettext("Your password was changed."),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "align") {
+                if (!defined($username)) {
+                    privmsg(gettext("You are not logged in."), $usernick)
+                }
+                elsif (!defined($arg[4]) || (lc($arg[4]) ne "good" && 
+                       lc($arg[4]) ne "neutral" && lc($arg[4]) ne "evil")) {
+                    privmsg(gettext("Try: ALIGN <good|neutral|evil>"),
+			    $usernick);
+                }
+                else {
+                    $rps{$username}{alignment} = substr(lc($arg[4]),0,1);
+                    chanmsg(sprintf(gettext("%s has changed alignment to: "),
+				    $username).gettext(lc($arg[4])).".");
+                    privmsg(gettext("Your alignment was changed to ").
+			    gettext(lc($arg[4])).".", $usernick);
+                }
+            }
+            elsif ($arg[3] eq "removeme") {
+                if (!defined($username)) {
+                    privmsg(gettext("You are not logged in."), $usernick)
+                }
+                else {
+                    privmsg(sprintf(gettext("Account %s removed."),$username),
+			    $usernick);
+                    chanmsg(sprintf(gettext("%s removed his account, %s, the %s".
+					    "."),
+				    $arg[0],$username,$rps{$username}{class}));
+                    delete($rps{$username});
+                }
+            }
+            elsif ($arg[3] eq "help") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("For information on IRPG bot ".
+					    "commands, see %s"), $opts{helpurl}),
+			    $usernick);
+                }
+                else {
+                    privmsg(sprintf(gettext("Help URL is %s"),$opts{helpurl}),
+			    $usernick, 1);
+                    privmsg(sprintf(gettext("Admin commands URL is %s"),
+				    $opts{admincommurl}), $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "die") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "DIE"), $usernick);
+                }
+                else {
+                    $opts{reconnect} = 0;
+                    writedb();
+                    sts(sprintf(gettext("QUIT :DIE from %s"),$arg[0]),1);
+                }
+            }
+            elsif ($arg[3] eq "reloaddb") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "RELOADDB"), $usernick);
+                }
+                elsif (!$pausemode) {
+                    privmsg(gettext("ERROR: Can only use LOADDB while in ".
+				    "PAUSE mode."), $usernick, 1);
+                }
+                else {
+                    loaddb();
+                    privmsg(sprintf(gettext("Reread player database file; %u ".
+					    "accounts loaded."),
+				    scalar(keys(%rps))), $usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "backup") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "BACKUP"), $usernick);
+                }
+                else {
+                    backup();
+                    privmsg(sprintf(gettext("%s copied to .dbbackup/%s"),
+				    $opts{dbfile},$opts{dbfile}.time()),
+			    $usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "pause") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "PAUSE"), $usernick);
+                }
+                else {
+                    $pausemode = $pausemode ? 0 : 1;
+                    privmsg(sprintf(gettext("PAUSE_MODE set to %s."),$pausemode),
+			    $usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "silent") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "SILENT"), $usernick);
+                }
+                elsif (!defined($arg[4]) || $arg[4] < 0 || $arg[4] > 3) {
+                    privmsg(gettext("Try: SILENT <mode>"), $usernick,1);
+                }
+                else {
+                    $silentmode = $arg[4];
+                    privmsg(sprintf(gettext("SILENT_MODE set to %s."),
+				    $silentmode), $usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "jump") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "JUMP"), $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg(gettext("Try: JUMP <server[:port]>"), $usernick, 1);
+                }
+                else {
+                    writedb();
+                    sts(sprintf(gettext("QUIT :JUMP to %s from %s"),
+				$arg[4],$arg[0]));
+                    unshift(@{$opts{servers}},$arg[4]);
+                    close($sock);
+                    sleep(3);
+                    goto CONNECT;
+                }
+            }
+            elsif ($arg[3] eq "restart") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "RESTART"), $usernick);
+                }
+                else {
+                    writedb();
+                    sts(sprintf(gettext("QUIT :RESTART from %s"),$arg[0]),1);
+                    close($sock);
+                    exec("perl $0");
+                }
+            }
+            elsif ($arg[3] eq "clearq") {
+                if (!ha($username)) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "CLEARQ"), $usernick);
+                }
+                else {
+                    undef(@queue);
+                    chanmsg(sprintf(gettext("Outgoing message queue cleared by ".
+					    "%s."),$arg[0]));
+                    privmsg(gettext("Outgoing message queue cleared."),
+			    $usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "info") {
+                my $info;
+                if (!ha($username) && $opts{allowuserinfo}) {
+                    $info = sprintf(gettext("IRPG bot v%s by jotun, ".
+					    "http://idlerpg.net/. On via ".
+					    "server: %s. Admins online: %s."),
+				    $version,$opts{servers}->[0],
+				    join(", ", map { $rps{$_}{nick} }
+					 grep { $rps{$_}{isadmin} &&
+						$rps{$_}{online} } keys(%rps)));
+                    privmsg($info, $usernick);
+                }
+                elsif (!ha($username) && !$opts{allowuserinfo}) {
+                    privmsg(sprintf(gettext("You do not have access to %s."),
+				    "INFO"), $usernick);
+                }
+                else {
+                    my $queuedbytes = 0;
+                    $queuedbytes += (length($_)+2) for @queue; # +2 = \r\n
+                    $info = sprintf(gettext(
+                        "%.2fkb sent, %.2fkb received in %s. %d IRPG users ".
+                        "online of %d total users. %d accounts created since ".
+                        "startup. PAUSE_MODE is %d, SILENT_MODE is %d. ".
+                        "Outgoing queue is %d bytes in %d items. On via: %s. ".
+                        "Admins online: %s."),
+                        $outbytes/1024,
+                        $inbytes/1024,
+                        duration(time()-$^T),
+                        scalar(grep { $rps{$_}{online} } keys(%rps)),
+                        scalar(keys(%rps)),
+                        $registrations,
+                        $pausemode,
+                        $silentmode,
+                        $queuedbytes,
+                        scalar(@queue),
+                        $opts{servers}->[0],
+                        join(", ",map { $rps{$_}{nick} }
+                                  grep { $rps{$_}{isadmin} && $rps{$_}{online} }
+                                  keys(%rps)));
+                    privmsg($info, $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "login") {
+                if (defined($username)) {
+                    notice(sprintf(gettext("Sorry, you are already online as ".
+					   "%s."),$username), $usernick);
+                }
+                else {
+                    if ($#arg < 5 || $arg[5] eq "") {
+                        notice(gettext("Try: LOGIN <username> <password>"),
+			       $usernick);
+                    }
+                    elsif (!exists $rps{$arg[4]}) {
+                        notice(gettext("Sorry, no such account name. Note that ".
+				       "account names are case sensitive."),
+			       $usernick);
+                    }
+                    elsif (!exists $onchan{$usernick}) {
+                        notice(sprintf(gettext("Sorry, you're not in %s."),
+				       $opts{botchan}), $usernick);
+                    }
+                    elsif ($rps{$arg[4]}{pass} ne
+                           crypt($arg[5],$rps{$arg[4]}{pass})) {
+                        notice(gettext("Wrong password."), $usernick);
+                    }
+                    else {
+                        if ($opts{voiceonlogin}) {
+                            sts("MODE $opts{botchan} +v :$usernick");
+                        }
+                        $rps{$arg[4]}{online} = 1;
+                        $rps{$arg[4]}{nick} = $usernick;
+                        $rps{$arg[4]}{userhost} = $arg[0];
+                        $rps{$arg[4]}{lastlogin} = time();
+                        chanmsg(sprintf(gettext("%s, the level %u %s, is now ".
+						"online from nickname %s. Next ".
+						"level in %s."),
+					$arg[4],$rps{$arg[4]}{level},
+					$rps{$arg[4]}{class},$usernick,
+					duration($rps{$arg[4]}{next})));
+                        notice(gettext("Logon successful. Next level in ").
+                               duration($rps{$arg[4]}{next}).".", $usernick);
+                    }
+                }
+            }
+        }
+        # penalize returns true if user was online and successfully penalized.
+        # if the user is not logged in, then penalize() fails. so, if user is
+        # offline, and they say something including "http:", and they've been on
+        # the channel less than 90 seconds, and the http:-style ban is on, then
+        # check to see if their url is in @{$opts{okurl}}. if not, kickban them
+        elsif (!penalize($username,"privmsg",length("@arg[3..$#arg]")) &&
+               index(lc("@arg[3..$#arg]"),"http:") != -1 &&
+               (time()-$onchan{$usernick}) < 90 && $opts{doban}) {
+            my $isokurl = 0;
+            for (@{$opts{okurl}}) {
+                if (index(lc("@arg[3..$#arg]"),lc($_)) != -1) { $isokurl = 1; }
+            }
+            if (!$isokurl) {
+                sts("MODE $opts{botchan} +b $arg[0]");
+                sts("KICK $opts{botchan} $usernick :No advertising; ban will ".
+                    "be lifted within the hour.");
+                push(@bans,$arg[0]) if @bans < 12;
+            }
+        }
+    }
+}
+
+sub sts { # send to server
+    my($text,$skipq) = @_;
+    if ($skipq) {
+        if ($sock) {
+            print $sock "$text\r\n";
+            $outbytes += length($text) + 2;
+            debug("-> $text");
+        }
+        else {
+            # something is wrong. the socket is closed. clear the queue
+            undef(@queue);
+            debug("\$sock isn't writeable in sts(), cleared outgoing queue.\n");
+            return;
+        }
+    }
+    else {
+        push(@queue,$text);
+        debug(sprintf("(q%03d) = %s\n",$#queue,$text));
+    }
+}
+
+sub fq { # deliver message(s) from queue
+    if (!@queue) {
+        ++$freemessages if $freemessages < 4;
+        return undef;
+    }
+    my $sentbytes = 0;
+    for (0..$freemessages) {
+        last() if !@queue; # no messages left to send
+        # lower number of "free" messages we have left
+        my $line=shift(@queue);
+        # if we have already sent one message, and the next message to be sent
+        # plus the previous messages we have sent this call to fq() > 768 bytes,
+        # then requeue this message and return. we don't want to flood off,
+        # after all
+        if ($_ != 0 && (length($line)+$sentbytes) > 768) {
+            unshift(@queue,$line);
+            last();
+        }
+        if ($sock) {
+            debug("(fm$freemessages) -> $line");
+            --$freemessages if $freemessages > 0;
+            print $sock "$line\r\n";
+            $sentbytes += length($line) + 2;
+        }
+        else {
+            undef(@queue);
+            debug("Disconnected: cleared outgoing message queue.");
+            last();
+        }
+        $outbytes += length($line) + 2;
+    }
+}
+
+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+$/;
+    return sprintf(ngettext("%d day, %02d:%02d:%02d",
+			    "%d days, %02d:%02d:%02d",int($s/86400)),
+		   $s/86400,($s%86400)/3600,($s%3600)/60,($s%60));
+}
+
+sub ts { # timestamp
+    my @ts = localtime(time());
+    return sprintf("[%02d/%02d/%02d %02d:%02d:%02d] ",
+                   $ts[4]+1,$ts[3],$ts[5]%100,$ts[2],$ts[1],$ts[0]);
+}
+
+sub hog { # summon the hand of god
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    my $player = $players[rand(@players)];
+    my $win = int(rand(5));
+    my $time = int(((5 + int(rand(71)))/100) * $rps{$player}{next});
+    if ($win) {
+        chanmsg(clog(sprintf(gettext("Verily I say unto thee, the Heavens have ".
+				     "burst forth, and the blessed hand of God ".
+				     "carried %s %s toward level %u."),
+			     $player,duration($time),($rps{$player}{level}+1))));
+        $rps{$player}{next} -= $time;
+    }
+    else {
+        chanmsg(clog(sprintf(gettext("Thereupon He stretched out His little ".
+				     "finger among them and consumed %s with ".
+				     "fire, slowing the heathen %s from level ".
+				     "%u."),
+			     $player,duration($time),($rps{$player}{level}+1))));
+        $rps{$player}{next} += $time;
+    }
+    chanmsg(sprintf(gettext("%s reaches next level in %s."),
+		    $player,duration($rps{$player}{next})));
+}
+
+sub rpcheck { # check levels, update database
+    # check splits hash to see if any split users have expired
+    checksplits() if $opts{detectsplits};
+    # send out $freemessages lines of text from the outgoing message queue
+    fq();
+    # clear registration limiting
+    $lastreg = 0;
+    my $online = scalar(grep { $rps{$_}{online} } keys(%rps));
+    # there's really nothing to do here if there are no online users
+    return unless $online;
+    my $onlineevil = scalar(grep { $rps{$_}{online} &&
+                                   $rps{$_}{alignment} eq "e" } keys(%rps));
+    my $onlinegood = scalar(grep { $rps{$_}{online} &&
+                                   $rps{$_}{alignment} eq "g" } keys(%rps));
+    if (!$opts{noscale}) {
+        if (rand((20*86400)/$opts{self_clock}) < $online) { hog(); }
+        if (rand((24*86400)/$opts{self_clock}) < $online) { team_battle(); }
+        if (rand((8*86400)/$opts{self_clock}) < $online) { calamity(); }
+        if (rand((4*86400)/$opts{self_clock}) < $online) { godsend(); }
+    }
+    else {
+        hog() if rand(4000) < 1;
+        team_battle() if rand(4000) < 1;
+        calamity() if rand(4000) < 1;
+        godsend() if rand(2000) < 1;
+    }
+    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 < $oldrpreport%120) && $opts{writequestfile}) { writequestfile(); }
+    if (time() > $quest{qtime}) {
+        if (!@{$quest{questers}}) { quest(); }
+        elsif ($quest{type} == 1) {
+            chanmsg(clog(sprintf(gettext("%s, and %s have blessed the realm by ".
+					 "completing their quest! 25%% of their ".
+					 "burden is eliminated."),
+				 join(", ",(@{$quest{questers}})[0..2]),
+				 $quest{questers}->[3])));
+            for (@{$quest{questers}}) {
+                $rps{$_}{next} = int($rps{$_}{next} * .75);
+            }
+            undef(@{$quest{questers}});
+            $quest{qtime} = time() + 21600;
+            writequestfile();
+        }
+        # quest type 2 awards are handled in moveplayers()
+    }
+    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(gettext("Idle RPG Top Players:")) if @u;
+        for my $i (0..2) {
+            $#u >= $i and
+            chanmsg(sprintf(gettext("%s, the level %u %s, is #%u! Next level in".
+				    " %s."),
+			    $u[$i],$rps{$u[$i]}{level},
+			    $rps{$u[$i]}{class},($i + 1),
+			    (duration($rps{$u[$i]}{next}))));
+        }
+        backup();
+    }
+    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+
+        if ((scalar(@players)/scalar(grep { $rps{$_}{online} } keys(%rps))) > .15) {
+            challenge_opp($players[int(rand(@players))]);
+        }
+        while (@bans) {
+            sts("MODE $opts{botchan} -bbbb :@bans[0..3]");
+            splice(@bans,0,4);
+        }
+    }
+    if ($rpreport%1800 < $oldrpreport%1800) { # 30 mins
+        if ($opts{botnick} ne $primnick) {
+            sts($opts{botghostcmd}) if $opts{botghostcmd};
+            sts("NICK $primnick");
+        }
+    }
+    if (($rpreport%600 < $oldrpreport%600) && $pausemode) { # warn every 10m
+        chanmsg(gettext("WARNING: Cannot write database in PAUSE mode!"));
+    }
+    # do not write in pause mode, and do not write if not yet connected. (would
+    # log everyone out if the bot failed to connect. $lasttime = time() on
+    # successful join to $opts{botchan}, initial value is 1). if fails to open
+    # $opts{dbfile}, will not update $lasttime and so should have correct values
+    # on next rpcheck(). 
+    if ($lasttime != 1) {
+        my $curtime=time();
+        for my $k (keys(%rps)) {
+            if ($rps{$k}{online} && exists $rps{$k}{nick} &&
+                $rps{$k}{nick} && exists $onchan{$rps{$k}{nick}}) {
+                $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}++;
+                    $rps{$k}{next} += $ttl;
+                    chanmsg(sprintf(gettext("%s, the %s, has attained level ".
+					    "%u! Next level in %s."),
+				    $k,$rps{$k}{class},$rps{$k}{level},
+				    duration($ttl)));
+                    find_item($k);
+                    challenge_opp($k);
+                }
+            }
+            # attempt to make sure this is an actual user, and not just an
+            # artifact of a bad PEVAL
+        }
+        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; }
+    my @opps = grep { $rps{$_}{online} && $u ne $_ } keys(%rps);
+    return unless @opps;
+    my $opp = $opps[int(rand(@opps))];
+    $opp = $primnick if rand(@opps+1) < 1;
+    my $mysum = itemsum($u,1);
+    my $oppsum = itemsum($opp,1);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        my $gain = ($opp eq $primnick)?20:int($rps{$opp}{level}/4);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog(sprintf(gettext("%s [%u/%u] has challenged %s [%u/%u] in ".
+				     "combat and won! %s is removed from ".
+				     "%1\$s\'s clock."),
+			     $u,$myroll,$mysum,$opp,$opproll,$oppsum,
+			     duration($gain))));
+        $rps{$u}{next} -= $gain;
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			$u,duration($rps{$u}{next})));
+        my $csfactor = $rps{$u}{alignment} eq "g" ? 50 :
+                       $rps{$u}{alignment} eq "e" ? 20 :
+                       35;
+        if (rand($csfactor) < 1 && $opp ne $primnick) {
+            $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
+            chanmsg(clog(sprintf(gettext("%s has dealt %s a Critical Strike! %s".
+					 " is added to %2\$s\'s clock."),
+				 $u,$opp,duration($gain))));
+            $rps{$opp}{next} += $gain;
+            chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			    $opp,duration($rps{$opp}{next})));
+        }
+        elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
+            my @items = ("ring","amulet","charm","weapon","helm","tunic",
+                         "pair of gloves","set of leggings","shield",
+                         "pair of boots");
+            my $type = $items[rand(@items)];
+            if (itemlevel($rps{$opp}{item}{$type}) > itemlevel($rps{$u}{item}{$type})) {
+                chanmsg(clog(sprintf(gettext("In the fierce battle, %s dropped ".
+					     "his level %u %s! %s picks it up, ".
+					     "tossing his old level %u %3\$s to".
+					     " %1\$s."),
+				     $opp,itemlevel($rps{$opp}{item}{$type}),
+				     gettext($type),$u,
+				     itemlevel($rps{$u}{item}{$type}))));
+                my $tempitem = $rps{$u}{item}{$type};
+                $rps{$u}{item}{$type}=$rps{$opp}{item}{$type};
+                $rps{$opp}{item}{$type} = $tempitem;
+            }
+        }
+    }
+    else {
+        my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog(sprintf(gettext("%s [%u/%u] has challenged %s [%u/%u] in ".
+				     "combat and lost! %s is added to %1\$s\'s ".
+				     "clock."),$u,$myroll,$mysum,$opp,$opproll,
+			     $oppsum,duration($gain))));
+        $rps{$u}{next} += $gain;
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			$u,duration($rps{$u}{next})));
+    }
+}
+
+sub team_battle { # pit three players against three other players
+    my @opp = grep { $rps{$_}{online} } keys(%rps);
+    return if @opp < 6;
+    # 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};
+    for my $p (1,2) {
+        $gain = $rps{$opp[$p]}{next} if $gain > $rps{$opp[$p]}{next};
+    }
+    $gain = int($gain*.20);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        chanmsg(clog(sprintf(gettext("%s, %s and %s [%u/%u] have team battled ".
+				     "%s, %s and %s [%u/%u] at [%u,%u] and won!".
+				     " %s is removed from their clocks."),
+			     $opp[0],$opp[1],$opp[2],$myroll,$mysum,
+			     $opp[3],$opp[4],$opp[5],$opproll,$oppsum,
+			     $x,$y,duration($gain))));
+        $rps{$opp[0]}{next} -= $gain;
+        $rps{$opp[1]}{next} -= $gain;
+        $rps{$opp[2]}{next} -= $gain;
+    }
+    else {
+        chanmsg(clog(sprintf(gettext("%s, %s and %s [%u/%u] have team battled ".
+				     "%s, %s and %s [%u/%u] at [%u,%u] and ".
+				     "lost! %s is added to their clocks."),
+			     $opp[0],$opp[1],$opp[2],$myroll,$mysum,
+			     $opp[3],$opp[4],$opp[5],$opproll,$oppsum,
+			     $x,$y,duration($gain))));
+        $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(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Mattt's ".
+			       "Omniscience Grand Crown! Your enemies fall ".
+			       "before you as you anticipate their every move."),
+		       $ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'b') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Res0's ".
+			       "Protectorate Plate Mail! Your enemies cower in".
+			       " fear as their attacks have no effect on you."),
+		       $ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'c') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Dwyn's Storm Magic ".
+			       "Amulet! Your enemies are swept away by an".
+			       "elemental fury before the war has even begun"),
+		       $ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'd') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Jotun's Fury ".
+			       "Colossal Sword! Your enemies' hatred is brought".
+			       "to a quick end as you arc your wrist, dealing ".
+			       "the crushing blow."),$ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'e') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Drdink's Cane of ".
+			       "Blind Rage! Your enemies are tossed aside as ".
+			       "you blindly swing your arm around hitting ".
+			       "stuff."),$ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'f') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Mrquick's Magical ".
+			       "Boots of Swiftness! Your enemies are left ".
+			       "choking on your dust as you run from them ".
+			       "very, very quickly."),$ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'g') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Jeff's Cluehammer ".
+			       "of Doom! Your enemies are left with a sudden ".
+			       "and intense clarity of mind... even as you ".
+			       "relieve them of it."),$ulevel),$rps{$u}{nick});
+    }
+    elsif ($tag eq 'h') {
+        notice(sprintf(gettext("The light of the gods shines down upon you! ".
+			       "You have found the level %u Juliet's Glorious ".
+			       "Ring of Sparkliness! You enemies are blinded ".
+			       "by both its glory and their greed as you bring ".
+			       "desolation upon them."),$ulevel),
+               $rps{$u}{nick});
+    }
+    else {
+        notice(sprintf(gettext("You found a level %u %s! Your current %2\$s is ".
+			       "only level %u, so it seems Luck is with you!"),
+		       $ulevel,gettext($type),itemlevel($rps{$u}{item}{$type})),
+	       $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",
+                 "pair of gloves","set of leggings","shield","pair of boots");
+    my $type = $items[rand(@items)];
+    my $level = 1;
+    my $ulevel;
+    for my $num (1 .. int($rps{$u}{level}*1.5)) {
+        if (rand(1.4**($num/4)) < 1) {
+            $level = $num;
+        }
+    }
+    if ($rps{$u}{level} >= 25 && rand(40) < 1) {
+        $ulevel = 50+int(rand(25));
+        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 > 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 > 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 > 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 > 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 > 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 >
+            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 > itemlevel($rps{$u}{item}{weapon})) {
+            exchange_item($u,"weapon",$ulevel."g");
+            return;
+        }
+    }
+    if ($level > itemlevel($rps{$u}{item}{$type})) {
+        exchange_item($u,$type,$level);
+    }
+    else {
+        notice(sprintf(gettext("You found a level %u %s. Your current %2\$s is ".
+			       "level %3\$u, so it seems Luck is against you. ".
+			       "You toss the %2\$s."),
+		       $level,gettext($type),itemlevel($rps{$u}{item}{$type})),
+	       $rps{$u}{nick});
+        drop_item($u,$type,$level);
+    }
+}
+
+sub loaddb { # load the players and items database
+    backup();
+    my $l;
+    %rps = ();
+    if (!open(RPS,$opts{dbfile}) && -e $opts{dbfile}) {
+        sts("QUIT :loaddb() failed: $!");
+    }
+    while ($l=<RPS>) {
+        chomp($l);
+        next if $l =~ /^#/; # skip comments
+        my @i = split("\t",$l);
+        print Dumper(@i) if @i != 32;
+        if (@i != 32) {
+            sts("QUIT: Anomaly in loaddb(); line $. of $opts{dbfile} has ".
+                "wrong fields (".scalar(@i).")");
+            debug("Anomaly in loaddb(); line $. of $opts{dbfile} has wrong ".
+                "fields (".scalar(@i).")",1);
+        }
+        if (!$sock) { # if not RELOADDB
+            if ($i[8]) { $prev_online{$i[7]}=$i[0]; } # log back in
+        }
+        ($rps{$i[0]}{pass},
+        $rps{$i[0]}{isadmin},
+        $rps{$i[0]}{level},
+        $rps{$i[0]}{class},
+        $rps{$i[0]}{next},
+        $rps{$i[0]}{nick},
+        $rps{$i[0]}{userhost},
+        $rps{$i[0]}{online},
+        $rps{$i[0]}{idled},
+        $rps{$i[0]}{x},
+        $rps{$i[0]}{y},
+        $rps{$i[0]}{pen_mesg},
+        $rps{$i[0]}{pen_nick},
+        $rps{$i[0]}{pen_part},
+        $rps{$i[0]}{pen_kick},
+        $rps{$i[0]}{pen_quit},
+        $rps{$i[0]}{pen_quest},
+        $rps{$i[0]}{pen_logout},
+        $rps{$i[0]}{created},
+        $rps{$i[0]}{lastlogin},
+        $rps{$i[0]}{item}{amulet},
+        $rps{$i[0]}{item}{charm},
+        $rps{$i[0]}{item}{helm},
+        $rps{$i[0]}{item}{"pair of boots"},
+        $rps{$i[0]}{item}{"pair of gloves"},
+        $rps{$i[0]}{item}{ring},
+        $rps{$i[0]}{item}{"set of leggings"},
+        $rps{$i[0]}{item}{shield},
+        $rps{$i[0]}{item}{tunic},
+        $rps{$i[0]}{item}{weapon},
+        $rps{$i[0]}{alignment}) = (@i[1..7],($sock?$i[8]:0),@i[9..$#i]);
+    }
+    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 {
+    return unless $lasttime > 1;
+    my $onlinecount = grep { $rps{$_}{online} } keys %rps;
+    return unless $onlinecount;
+    for (my $i=0;$i<$opts{self_clock};++$i) {
+        # temporary hash to hold player positions, detect collisions
+        my %positions = ();
+        if ($quest{type} == 2 && @{$quest{questers}}) {
+            my $allgo = 1; # have all users reached <p1|p2>?
+            for (@{$quest{questers}}) {
+                if ($quest{stage}==1) {
+                    if ($rps{$_}{x} != $quest{p1}->[0] ||
+                        $rps{$_}{y} != $quest{p1}->[1]) {
+                        $allgo=0;
+                        last();
+                    }
+                }
+                else {
+                    if ($rps{$_}{x} != $quest{p2}->[0] ||
+                        $rps{$_}{y} != $quest{p2}->[1]) {
+                        $allgo=0;
+                        last();
+                    }
+                }
+            }
+            # all participants have reached point 1, now point 2
+            if ($quest{stage}==1 && $allgo) {
+                $quest{stage}=2;
+                $allgo=0; # have not all reached p2 yet
+            }
+            elsif ($quest{stage} == 2 && $allgo) {
+                chanmsg(clog(sprintf(gettext("%s, and %s have completed their ".
+					     "journey! 25%% of their burden is ".
+					     "eliminated."),
+				     join(", ",(@{$quest{questers}})[0..2]),
+				     $quest{questers}->[3])));
+                for (@{$quest{questers}}) {
+                    $rps{$_}{next} = int($rps{$_}{next} * .75);
+                }
+                undef(@{$quest{questers}});
+                $quest{qtime} = time() + 21600; # next quest starts in 6 hours
+                $quest{type} = 1; # probably not needed
+                writequestfile();
+            }
+            else {
+                my(%temp,$player);
+                # load keys of %temp with online users
+                ++@temp{grep { $rps{$_}{online} } keys(%rps)};
+                # delete questers from list
+                delete(@temp{@{$quest{questers}}});
+                while ($player = each(%temp)) {
+                    $rps{$player}{x} += int(rand(3))-1;
+                    $rps{$player}{y} += int(rand(3))-1;
+                    # if player goes over edge, wrap them back around
+                    if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x}=0; }
+                    if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y}=0; }
+                    if ($rps{$player}{x} < 0) { $rps{$player}{x}=$opts{mapx}; }
+                    if ($rps{$player}{y} < 0) { $rps{$player}{y}=$opts{mapy}; }
+                    
+                    if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
+                        !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
+                        if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
+                            !$rps{$player}{isadmin} && rand(100) < 1) {
+                            chanmsg(sprintf(gettext("%s encounters %s and bows".
+						    " humbly."),
+					    $player,
+					    $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}));
+                        }
+                        if (rand($onlinecount) < 1) {
+                            $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
+                            collision_fight($player,
+                                $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
+                        }
+                    }
+                    else {
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
+                    }
+                }
+                for (@{$quest{questers}}) {
+                    if ($quest{stage} == 1) {
+                        if (rand(100) < 1) {
+                            if ($rps{$_}{x} != $quest{p1}->[0]) {
+                                $rps{$_}{x} += ($rps{$_}{x} < $quest{p1}->[0] ?
+                                                1 : -1);
+                            }
+                            if ($rps{$_}{y} != $quest{p1}->[1]) {
+                                $rps{$_}{y} += ($rps{$_}{y} < $quest{p1}->[1] ?
+                                                1 : -1);
+                            }
+                        }
+                    }
+                    elsif ($quest{stage}==2) {
+                        if (rand(100) < 1) {
+                            if ($rps{$_}{x} != $quest{p2}->[0]) {
+                                $rps{$_}{x} += ($rps{$_}{x} < $quest{p2}->[0] ?
+                                                1 : -1);
+                            }
+                            if ($rps{$_}{y} != $quest{p2}->[1]) {
+                                $rps{$_}{y} += ($rps{$_}{y} < $quest{p2}->[1] ?
+                                                1 : -1);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        else {
+            for my $player (keys(%rps)) {
+                next unless $rps{$player}{online};
+                $rps{$player}{x} += int(rand(3))-1;
+                $rps{$player}{y} += int(rand(3))-1;
+                # if player goes over edge, wrap them back around
+                if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x} = 0; }
+                if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y} = 0; }
+                if ($rps{$player}{x} < 0) { $rps{$player}{x} = $opts{mapx}; }
+                if ($rps{$player}{y} < 0) { $rps{$player}{y} = $opts{mapy}; }
+                if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
+                    !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
+                    if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
+                        !$rps{$player}{isadmin} && rand(100) < 1) {
+                        chanmsg(sprintf(gettext("%s encounters %s and bows ".
+						"humbly."),$player,
+					$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}));
+                    }
+                    if (rand($onlinecount) < 1) {
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
+                        collision_fight($player,
+                            $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
+                    }
+                }
+                else {
+                    $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
+                    $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
+                }
+            }
+        }
+        # 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);
+                }
+            }
+        }
+    }
+}
+
+sub mksalt { # generate a random salt for passwds
+    join '',('a'..'z','A'..'Z','0'..'9','/','.')[rand(64), rand(64)];
+}
+
+sub chanmsg { # send a message to the channel
+    my $msg = shift or return undef;
+    if ($silentmode & 1) { return undef; }
+    privmsg($msg, $opts{botchan}, shift);
+}
+
+sub privmsg { # send a message to an arbitrary entity
+    my $msg = shift or return undef;
+    my $target = shift or return undef;
+    my $force = shift;
+    if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
+        && !$force) {
+        return undef;
+    }
+    while (length($msg)) {
+        sts("PRIVMSG $target :".substr($msg,0,450),$force);
+        substr($msg,0,450)="";
+    }
+}
+
+sub notice { # send a notice to an arbitrary entity
+    my $msg = shift or return undef;
+    my $target = shift or return undef;
+    my $force = shift;
+    if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
+        && !$force) {
+        return undef;
+    }
+    while (length($msg)) {
+        sts("NOTICE $target :".substr($msg,0,450),$force);
+        substr($msg,0,450)="";
+    }
+}
+
+sub help { # print help message
+    (my $prog = $0) =~ s/^.*\///;
+
+    print "
+usage: $prog [OPTIONS]
+  --help, -h           Print this message
+  --verbose, -v        Print verbose messages
+  --server, -s         Specify IRC server:port to connect to
+  --botnick, -n        Bot's IRC nick
+  --botuser, -u        Bot's username
+  --botrlnm, -r        Bot's real name
+  --botchan, -c        IRC channel to join
+  --botident, -p       Specify identify-to-services command
+  --botmodes, -m       Specify usermodes for the bot to set upon connect
+  --botopcmd, -o       Specify command to send to server on successful connect
+  --botghostcmd, -g    Specify command to send to server to regain primary
+                       nickname when in use
+  --doban              Advertisement ban on/off flag
+  --okurl, -k          Bot will not ban for web addresses that contain these
+                       strings
+  --debug              Debug on/off flag
+  --helpurl            URL to refer new users to
+  --admincommurl       URL to refer admins to
+
+  Timing parameters:
+  --rpbase             Base time to level up
+  --rpstep             Time to next level = rpbase * (rpstep ** CURRENT_LEVEL)
+  --rppenstep          PENALTY_SECS=(PENALTY*(RPPENSTEP**CURRENT_LEVEL))
+
+";
+}
+
+sub itemsum {
+    my $user = shift;
+    # is this for a battle? if so, good users get a 10% boost and evil users get
+    # a 10% detriment
+    my $battle = shift;
+    return -1 unless defined $user;
+    my $sum = 0;
+    if ($user eq $primnick) {
+        for my $u (keys(%rps)) {
+            $sum = itemsum($u) if $sum < itemsum($u);
+        }
+        return $sum+1;
+    }
+    if (!exists($rps{$user})) { return -1; }
+    $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) :
+               $sum;
+    }
+    return $sum;
+}
+
+sub daemonize() {
+    # win32 doesn't daemonize (this way?)
+    if ($^O eq "MSWin32") {
+        print debug("Nevermind, this is Win32, no I'm not.")."\n";
+        return;
+    }
+    use POSIX 'setsid';
+    $SIG{CHLD} = sub { };
+    fork() && exit(0); # kill parent
+    POSIX::setsid() || debug("POSIX::setsid() failed: $!",1);
+    $SIG{CHLD} = sub { };
+    fork() && exit(0); # kill the parent as the process group leader
+    $SIG{CHLD} = sub { };
+    open(STDIN,'/dev/null') || debug("Cannot read /dev/null: $!",1);
+    open(STDOUT,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
+    open(STDERR,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
+    # write our PID to $opts{pidfile}, or return semi-silently on failure
+    open(PIDFILE,">$opts{pidfile}") || do {
+        debug("Error: failed opening pid file: $!");
+        return;
+    };
+    print PIDFILE $$;
+    close(PIDFILE);
+}
+
+sub calamity { # suffer a little one
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    return unless @players;
+    my $player = $players[rand(@players)];
+    if (rand(10) < 1) {
+        my @items = ("amulet","charm","weapon","tunic","set of leggings",
+                     "shield");
+        my $type = $items[rand(@items)];
+        if ($type eq "amulet") {
+            chanmsg(clog(sprintf(gettext("%s fell, chipping the stone in his ".
+					 "amulet! %1\$s\'s %s loses 10%% of its".
+					 " effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "charm") {
+            chanmsg(clog(sprintf(gettext("%s slipped and dropped his charm in a".
+					 " dirty bog! %1\$s\'s %s loses 10%% of".
+					 " its effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "weapon") {
+            chanmsg(clog(sprintf(gettext("%s left his weapon out in the rain to".
+					 " rust! %1\$s\'s %s loses 10%% of its ".
+					 "effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "tunic") {
+            chanmsg(clog(sprintf(gettext("%s spilled a level 7 shrinking potion".
+					 " on his tunic! %1\$s\'s %s loses 10%%".
+					 " of its effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "shield") {
+            chanmsg(clog(sprintf(gettext("%s\'s shield was damaged by a ".
+					 "dragon's fiery breath! %1\$s\'s %s ".
+					 "loses 10%% of its effectiveness."),
+				 $player,gettext($type))));
+        }
+        else {
+            chanmsg(clog(sprintf(gettext("%s burned a hole through his leggings".
+					 " while ironing them! %1\$s\'s %s ".
+					 "loses 10%% of its effectiveness."),
+				$player,gettext($type))));
+        }
+        my $suffix="";
+        if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
+        $rps{$player}{item}{$type} = int(itemlevel($rps{$player}{item}{$type}) * .9);
+        $rps{$player}{item}{$type}.=$suffix;
+    }
+    else {
+        my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
+        if (!open(Q,$opts{eventsfile})) {
+            return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+        }
+        my($i,$actioned);
+        while (my $line = <Q>) {
+            chomp($line);
+            if ($line =~ /^C (.*)/ && rand(++$i) < 1) { $actioned = $1; }
+        }
+        chanmsg(clog(sprintf(gettext("%s %s. This terrible calamity has slowed ".
+				     "them %s from level %u."),
+			     $player,$actioned,duration($time),
+			     ($rps{$player}{level}+1))));
+        $rps{$player}{next} += $time;
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			$player,duration($rps{$player}{next})));
+    }
+}
+
+sub godsend { # bless the unworthy
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    return unless @players;
+    my $player = $players[rand(@players)];
+    if (rand(10) < 1) {
+        my @items = ("amulet","charm","weapon","tunic","set of leggings",
+                     "shield");
+        my $type = $items[rand(@items)];
+        if ($type eq "amulet") {
+            chanmsg(clog(sprintf(gettext("%s\'s %s was blessed by a passing".
+					 " cleric! %1\$s\'s %2\$s gains 10%% ".
+					 "effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "charm") {
+            chanmsg(clog(sprintf(gettext("%s\'s %s ate a bolt of lightning! ".
+					 "%1\$s\'s %2\$s gains 10%% ".
+					 "effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "weapon") {
+            chanmsg(clog(sprintf(gettext("%s sharpened the edge of his %s! ".
+					 "%1\$s\'s %2\$s gains 10%% ".
+					 "effectiveness."),
+				$player,gettext($type))));
+        }
+        elsif ($type eq "tunic") {
+            chanmsg(clog(sprintf(gettext("A magician cast a spell of Rigidity ".
+					 "on %s\'s %s! %1\$s\'s %2\$s gains ".
+					 "10%% effectiveness."),
+				 $player,gettext($type))));
+        }
+        elsif ($type eq "shield") {
+            chanmsg(clog(sprintf(gettext("%s reinforced his %s with a ".
+					 "dragon's scales! %1\$s\'s %2\$s gains".
+					 " 10%% effectiveness."),
+				$player,gettext($type))));
+        }
+        else {
+            chanmsg(clog(sprintf(gettext("The local wizard imbued %s\'s pants ".
+					 "with a Spirit of Fortitude! %1\$s\'s ".
+					 "%2\$s gains 10%% effectiveness."),
+				$player,gettext($type))));
+        }
+        my $suffix="";
+        if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
+        $rps{$player}{item}{$type} = int(itemlevel($rps{$player}{item}{$type}) * 1.1);
+        $rps{$player}{item}{$type}.=$suffix;
+    }
+    else {
+        my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
+        my $actioned;
+        if (!open(Q,$opts{eventsfile})) {
+            return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+        }
+        my $i;
+        while (my $line = <Q>) {
+            chomp($line);
+            if ($line =~ /^G (.*)/ && rand(++$i) < 1) {
+                $actioned = $1;
+            }
+        }
+        chanmsg(clog(sprintf(gettext("%s %s! This wondrous godsend has ".
+				     "accelerated them %s towards level %u."),
+			     $player,$actioned,duration($time),
+			     ($rps{$player}{level}+1))));
+        $rps{$player}{next} -= $time;
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			$player,duration($rps{$player}{next})));
+    }
+}
+
+sub quest {
+    @{$quest{questers}} = grep { $rps{$_}{online} && $rps{$_}{level} > 39 &&
+                                 time()-$rps{$_}{lastlogin}>36000 } keys(%rps);
+    if (@{$quest{questers}} < 4) { return undef(@{$quest{questers}}); }
+    while (@{$quest{questers}} > 4) {
+        splice(@{$quest{questers}},int(rand(@{$quest{questers}})),1);
+    }
+    if (!open(Q,$opts{eventsfile})) {
+        return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+    }
+    my $i;
+    while (my $line = <Q>) {
+        chomp($line);
+        if ($line =~ /^Q/ && rand(++$i) < 1) {
+            if ($line =~ /^Q1 (.*)/) {
+                $quest{text} = $1;
+                $quest{type} = 1;
+                $quest{qtime} = time() + 43200 + int(rand(43201)); # 12-24 hours
+            }
+            elsif ($line =~ /^Q2 (\d+) (\d+) (\d+) (\d+) (.*)/) {
+                $quest{p1} = [$1,$2];
+                $quest{p2} = [$3,$4];
+                $quest{text} = $5;
+                $quest{type} = 2;
+                $quest{stage} = 1;
+            }
+        }
+    }
+    close(Q);
+    if ($quest{type} == 1) {
+        chanmsg(sprintf(gettext("%s, and %s have been chosen by the gods to ".
+				"%s. Quest to end in %s."),
+			join(", ",(@{$quest{questers}})[0..2]),
+			$quest{questers}->[3],$quest{text},
+			duration($quest{qtime}-time())));    
+    }
+    elsif ($quest{type} == 2) {
+        chanmsg(sprintf(gettext("%s, and %s have been chosen by the gods to ".
+				"%s. Participants must first reach [%u,%u], ".
+				"then [%u,%u]."),
+			join(", ",(@{$quest{questers}})[0..2]),
+			$quest{questers}->[3],$quest{text},
+			$quest{p1}->[0],$quest{p1}->[1],
+			$quest{p2}->[0],$quest{p2}->[1]).
+                ($opts{mapurl}?sprintf(gettext(" See %s to monitor their ".
+					       "journey's progress."),
+				       $opts{mapurl}):""));
+    }
+    writequestfile();
+}
+
+sub questpencheck {
+    my $k = shift;
+    my ($quester,$player);
+    for $quester (@{$quest{questers}}) {
+        if ($quester eq $k) {
+            chanmsg(clog(sprintf(gettext(
+			 "%s\'s prudence and self-regard has brought the ".
+                         "wrath of the gods upon the realm. All your great ".
+                         "wickedness makes you as it were heavy with lead, ".
+                         "and to tend downwards with great weight and ".
+                         "pressure towards hell. Therefore have you drawn ".
+                         "yourselves 15 steps closer to that gaping maw.")
+				 ,$k)));
+            for $player (grep { $rps{$_}{online} } keys %rps) {
+                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;
+        }
+    }
+}
+
+sub clog {
+    my $mesg = shift;
+    open(B,">>$opts{modsfile}") or do {
+        debug("Error: Cannot open $opts{modsfile}: $!");
+        chanmsg("Error: Cannot open $opts{modsfile}: $!");
+        return $mesg;
+    };
+    print B ts()."$mesg\n";
+    close(B);
+    return $mesg;
+}
+
+sub backup() {
+    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());
+    }
+}
+
+sub penalize {
+    my $username = shift;
+    return 0 if !defined($username);
+    return 0 if !exists($rps{$username});
+    my $type = shift;
+    my $pen = 0;
+    questpencheck($username);
+    if ($type eq "quit") {
+        $pen = int(20 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_quit}+=$pen;
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "nick") {
+        my $newnick = shift;
+        $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);
+        $rps{$username}{userhost} =~ s/^[^!]+/$rps{$username}{nick}/e;
+        notice(sprintf(gettext("Penalty of %s added to your timer for ".
+               "nick change."),duration($pen)),$rps{$username}{nick});
+    }
+    elsif ($type eq "privmsg" || $type eq "notice") {
+        $pen = int(shift(@_) * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_mesg}+=$pen;
+        notice(sprintf(gettext("Penalty of %s added to your timer for %s."),
+		       duration($pen),$type),$rps{$username}{nick});
+    }
+    elsif ($type eq "part") {
+        $pen = int(200 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_part}+=$pen;
+        notice(sprintf(gettext("Penalty of %s added to your timer for parting."),
+		       duration($pen)),$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "kick") {
+        $pen = int(250 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_kick}+=$pen;
+        notice(sprintf(gettext("Penalty of %s added to your timer for ".
+               "being kicked."),duration($pen)),$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "logout") {
+        $pen = int(20 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_logout} += $pen;
+        notice(sprintf(gettext("Penalty of %s added to your timer for ".
+               "LOGOUT command."),duration($pen)),$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    $rps{$username}{next} += $pen;
+    return 1; # successfully penalized a user! woohoo!
+}
+
+sub debug {
+    (my $text = shift) =~ s/[\r\n]//g;
+    my $die = shift;
+    if ($opts{debug} || $opts{verbose}) {
+        open(DBG,">>$opts{debugfile}") or do {
+            chanmsg("Error: Cannot open debug file: $!");
+            return;
+        };
+        print DBG ts()."$text\n";
+        close(DBG);
+    }
+    if ($die) { die("$text\n"); }
+    return $text;
+}
+
+sub finduser {
+    my $nick = shift;
+    return undef if !defined($nick);
+    for my $user (keys(%rps)) {
+        next unless $rps{$user}{online};
+        if ($rps{$user}{nick} eq $nick) { return $user; }
+    }
+    return undef;
+}
+
+sub ha { # return 0/1 if username has access
+    my $user = shift;
+    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;
+    }
+    return $rps{$user}{isadmin};
+}
+
+sub checksplits { # removed expired split hosts from the hash
+    my $host;
+    while ($host = each(%split)) {
+        if (time()-$split{$host}{time} > $opts{splitwait}) {
+            $rps{$split{$host}{account}}{online} = 0;
+            delete($split{$host});
+        }
+    }
+}
+
+sub collision_fight {
+    my($u,$opp) = @_;
+    my $mysum = itemsum($u,1);
+    my $oppsum = itemsum($opp,1);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        my $gain = int($rps{$opp}{level}/4);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog(sprintf(gettext("%s [%u/%u] has come upon %s [%u/%u] and ".
+				     "taken them in combat! %s is removed from ".
+				     "%1\$s\'s clock."),
+			     $u,$myroll,$mysum,$opp,$opproll,$oppsum,
+			     duration($gain))));
+        $rps{$u}{next} -= $gain;
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			$u,duration($rps{$u}{next})));
+        if (rand(35) < 1 && $opp ne $primnick) {
+            $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
+            chanmsg(clog(sprintf(gettext("%s has dealt %s a Critical Strike! %s".
+					 " is added to %2\$s\'s clock."),
+				 $u,$opp,duration($gain))));
+            $rps{$opp}{next} += $gain;
+            chanmsg(sprintf(gettext("%s reaches next level in %s."),$opp,duration($rps{$opp}{next})));
+        }
+        elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
+            my @items = ("ring","amulet","charm","weapon","helm","tunic",
+                         "pair of gloves","set of leggings","shield",
+                         "pair of boots");
+            my $type = $items[rand(@items)];
+            if (itemlevel($rps{$opp}{item}{$type}) > itemlevel($rps{$u}{item}{$type})) {
+                chanmsg(sprintf(gettext("In the fierce battle, %s dropped his ".
+					"level %u %s! %s picks it up, tossing ".
+					"his old level %u %3\$s to %1\$s."),
+				$opp,itemlevel($rps{$opp}{item}{$type}),
+				gettext($type),$u,
+				itemlevel($rps{$u}{item}{$type})));
+                my $tempitem = $rps{$u}{item}{$type};
+                $rps{$u}{item}{$type}=$rps{$opp}{item}{$type};
+                $rps{$opp}{item}{$type} = $tempitem;
+            }
+        }
+    }
+    else {
+        my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog(sprintf(gettext("%s [%u/%u] has come upon %s [%u/%u] and ".
+				     "been defeated in combat! %s is added to ".
+				     "%1\$s\'s clock."),
+			     $u,$myroll,$mysum,$opp,$opproll,$oppsum,
+			     duration($gain))));
+        $rps{$u}{next} += $gain;
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),$u,duration($rps{$u}{next})));
+    }
+}
+
+sub writequestfile {
+    return unless $opts{writequestfile};
+    open(QF,">$opts{questfilename}") or do {
+        chanmsg("Error: Cannot open $opts{questfilename}: $!");
+        return;
+    };
+    # if no active quest, just empty questfile. otherwise, write it
+    if (@{$quest{questers}}) {
+        if ($quest{type}==1) {
+            print QF "T $quest{text}\n".
+                     "Y 1\n".
+                     "S $quest{qtime}\n".
+                     "P1 $quest{questers}->[0]\n".
+                     "P2 $quest{questers}->[1]\n".
+                     "P3 $quest{questers}->[2]\n".
+                     "P4 $quest{questers}->[3]\n";
+        }
+        elsif ($quest{type}==2) {
+            print QF "T $quest{text}\n".
+                     "Y 2\n".
+                     "S $quest{stage}\n".
+                     "P $quest{p1}->[0] $quest{p1}->[1] $quest{p2}->[0] ".
+                        "$quest{p2}->[1]\n".
+                     "P1 $quest{questers}->[0] $rps{$quest{questers}->[0]}{x} ".
+                         "$rps{$quest{questers}->[0]}{y}\n".
+                     "P2 $quest{questers}->[1] $rps{$quest{questers}->[1]}{x} ".
+                         "$rps{$quest{questers}->[1]}{y}\n".
+                     "P3 $quest{questers}->[2] $rps{$quest{questers}->[2]}{x} ".
+                         "$rps{$quest{questers}->[2]}{y}\n".
+                     "P4 $quest{questers}->[3] $rps{$quest{questers}->[3]}{x} ".
+                         "$rps{$quest{questers}->[3]}{y}\n";
+        }
+    }
+    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);
+    return unless @players > 1;
+    splice(@players,int(rand(@players)),1) while @players > 2;
+    my $gain = 5 + int(rand(8));
+    chanmsg(clog(sprintf(gettext("%s and %s have not let the iniquities of ".
+                 "evil men poison them. Together have they prayed to their ".
+                 "god, and it is his light that now shines upon them. %u%% ".
+                 "of their time is removed from their clocks."),
+			 $players[0],$players[1],$gain)));
+    $rps{$players[0]}{next} = int($rps{$players[0]}{next}*(1 - ($gain/100)));
+    $rps{$players[1]}{next} = int($rps{$players[1]}{next}*(1 - ($gain/100)));
+    chanmsg(sprintf(gettext("%s reaches next level in %s."),
+            $players[0],duration($rps{$players[0]}{next})));
+    chanmsg(sprintf(gettext("%s reaches next level in %s."),
+            $players[1],duration($rps{$players[1]}{next})));
+}
+
+sub evilness {
+    my @evil = grep { $rps{$_}{alignment} eq "e" &&
+                      $rps{$_}{online} } keys(%rps);
+    return unless @evil;
+    my $me = $evil[rand(@evil)];
+    if (int(rand(2)) < 1) {
+        # evil only steals from good :^(
+        my @good = grep { $rps{$_}{alignment} eq "g" &&
+                          $rps{$_}{online} } keys(%rps);
+        my $target = $good[rand(@good)];
+        my @items = ("ring","amulet","charm","weapon","helm","tunic",
+                     "pair of gloves","set of leggings","shield",
+                     "pair of boots");
+        my $type = $items[rand(@items)];
+        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(sprintf(gettext("%s stole %s\'s level %u %s while ".
+					 "they were sleeping! %1\$s leaves his ".
+					 "old level %u %4\$s behind, which ".
+					 "%2\$s then takes."),
+				 $me,$target,itemlevel($rps{$me}{item}{$type}),
+				 gettext($type),
+				 itemlevel($rps{$target}{item}{$type}))));
+        }
+        else {
+            notice(sprintf(gettext("You made to steal %s\'s %s, but realized ".
+				   "it was lower level than your own. You ".
+				   "creep back into the shadows."),
+			   $target,gettext($type)),$rps{$me}{nick});
+        }
+    }
+    else { # being evil only pays about half of the time...
+        my $gain = 1 + int(rand(5));
+        chanmsg(clog(sprintf(gettext("%s is forsaken by his evil god. %s".
+				     " is added to his clock."),$me,
+			     duration(int($rps{$me}{next} * ($gain/100))))));
+        $rps{$me}{next} = int($rps{$me}{next} * (1 + ($gain/100)));
+        chanmsg(sprintf(gettext("%s reaches next level in %s."),
+			$me,duration($rps{$me}{next})));
+    }
+}
+
+sub writedb {
+    open(RPS,">$opts{dbfile}") or do {
+        chanmsg("ERROR: Cannot write $opts{dbfile}: $!");
+        return 0;
+    };
+    print RPS join("\t","# username",
+                        "pass",
+                        "is admin",
+                        "level",
+                        "class",
+                        "next ttl",
+                        "nick",
+                        "userhost",
+                        "online",
+                        "idled",
+                        "x pos",
+                        "y pos",
+                        "pen_mesg",
+                        "pen_nick",
+                        "pen_part",
+                        "pen_kick",
+                        "pen_quit",
+                        "pen_quest",
+                        "pen_logout",
+                        "created",
+                        "last login",
+                        "amulet",
+                        "charm",
+                        "helm",
+                        "boots",
+                        "gloves",
+                        "ring",
+                        "leggings",
+                        "shield",
+                        "tunic",
+                        "weapon",
+                        "alignment")."\n";
+    my $k;
+    keys(%rps); # reset internal pointer
+    while ($k=each(%rps)) {
+        if (exists($rps{$k}{next}) && defined($rps{$k}{next})) {
+            print RPS join("\t",$k,
+                                $rps{$k}{pass},
+                                $rps{$k}{isadmin},
+                                $rps{$k}{level},
+                                $rps{$k}{class},
+                                $rps{$k}{next},
+                                $rps{$k}{nick},
+                                $rps{$k}{userhost},
+                                $rps{$k}{online},
+                                $rps{$k}{idled},
+                                $rps{$k}{x},
+                                $rps{$k}{y},
+                                $rps{$k}{pen_mesg},
+                                $rps{$k}{pen_nick},
+                                $rps{$k}{pen_part},
+                                $rps{$k}{pen_kick},
+                                $rps{$k}{pen_quit},
+                                $rps{$k}{pen_quest},
+                                $rps{$k}{pen_logout},
+                                $rps{$k}{created},
+                                $rps{$k}{lastlogin},
+                                $rps{$k}{item}{amulet},
+                                $rps{$k}{item}{charm},
+                                $rps{$k}{item}{helm},
+                                $rps{$k}{item}{"pair of boots"},
+                                $rps{$k}{item}{"pair of gloves"},
+                                $rps{$k}{item}{ring},
+                                $rps{$k}{item}{"set of leggings"},
+                                $rps{$k}{item}{shield},
+                                $rps{$k}{item}{tunic},
+                                $rps{$k}{item}{weapon},
+                                $rps{$k}{alignment})."\n";
+        }
+    }
+    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 {
+    if (! -e ".irpg.conf") {
+        debug("Error: Cannot find .irpg.conf. Copy it to this directory, ".
+              "please.",1);
+    }
+    else {
+        open(CONF,"<.irpg.conf") or do {
+            debug("Failed to open config file .irpg.conf: $!",1);
+        };
+        my($line,$key,$val);
+        while ($line=<CONF>) {
+            next() if $line =~ /^#/; # skip comments
+            $line =~ s/[\r\n]//g;
+            $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; }
+            if ($key eq "die") {
+                die("Please edit the file .irpg.conf to setup your bot's ".
+                    "options. Also, read the README file if you haven't ".
+                    "yet.\n");
+            }
+            elsif ($key eq "server") { push(@{$opts{servers}},$val); }
+            elsif ($key eq "okurl") { push(@{$opts{okurl}},$val); }
+            else { $opts{$key} = $val; }
+        }
+    }
+}
Index: /irpg/trunk/bot/bot.v3.1.2.pl.backup
===================================================================
--- /irpg/trunk/bot/bot.v3.1.2.pl.backup	(revision 1444)
+++ /irpg/trunk/bot/bot.v3.1.2.pl.backup	(revision 1444)
@@ -0,0 +1,2668 @@
+#!/usr/local/bin/perl
+# irpg bot v3.1.2 by jotun, jotun@idlerpg.net, et al. See http://idlerpg.net/
+#
+# Some code within this file was written by authors other than myself. As such,
+# distributing this code or distributing modified versions of this code is
+# strictly prohibited without written authorization from the authors. Contact
+# jotun@idlerpg.net. Please note that this may change (at any time, no less) if
+# authorization for distribution is given by patch submitters.
+#
+# As a side note, patches submitted for this project are automatically taken to
+# be freely distributable and modifiable for any use, public or private, though
+# I make no claim to ownership; original copyrights will be retained.. except as
+# I've just stated.
+#
+# Please mail bugs, etc. to me. Patches are welcome to fix bugs or clean up
+# the code, but please do not use a radically different coding style. Thanks
+# to everyone that's contributed!
+#
+# NOTE: This code should NOT be run as root. You deserve anything that happens
+#       to you if you run this code as a superuser. Also, note that giving a
+#       user admin access to the bot effectively gives them full access to the
+#       user under which your bot runs, as they can use the PEVAL command to
+#       execute any command, or possibly even change your password. I sincerely
+#       suggest that you exercise extreme caution when giving someone admin
+#       access to your bot, or that you disable the PEVAL command for non-owner
+#       accounts in your config file, .irpg.conf
+
+use strict;
+use warnings;
+use IO::Socket;
+use IO::Socket::INET6;
+use IO::Select;
+use Data::Dumper;
+use Getopt::Long;
+
+my %opts;
+
+readconfig();
+
+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",
+    "botnick|n=s",
+    "botuser|u=s",
+    "botrlnm|r=s",
+    "botchan|c=s",
+    "botident|p=s",
+    "botmodes|m=s",
+    "botopcmd|o=s",
+    "localaddr=s",
+    "botghostcmd|g=s",
+    "helpurl=s",
+    "admincommurl=s",
+    "doban",
+    "silentmode=i",
+    "writequestfile",
+    "questfilename=s",
+    "voiceonlogin",
+    "noccodes",
+    "nononp",
+    "mapurl=s",
+    "statuscmd",
+    "pidfile=s",
+    "reconnect",
+    "reconnect_wait=i",
+    "self_clock=i",
+    "modsfile=s",
+    "casematters",
+    "detectsplits",
+    "autologin",
+    "splitwait=i",
+    "allowuserinfo",
+    "noscale",
+    "phonehome",
+    "owner=s",
+    "owneraddonly",
+    "ownerdelonly",
+    "ownerpevalonly",
+    "checkupdates",
+    "senduserlist",
+    "limitpen=i",
+    "mapx=i",
+    "mapy=i",
+    "modesperline=i",
+    "okurl|k=s@",
+    "eventsfile=s",
+    "rpstep=f",
+    "rpbase=i",
+    "rppenstep=f",
+    "dbfile|irpgdb|db|d=s",
+) or debug("Error: Could not parse command line. Try $0 --help\n",1);
+
+$opts{help} and do { help(); exit 0; };
+
+debug("Config: read $_: ".Dumper($opts{$_})) for keys(%opts);
+
+my $outbytes = 0; # sent bytes
+my $primnick = $opts{botnick}; # for regain or register checks
+my $inbytes = 0; # received bytes
+my %onchan; # users on game channel
+my %rps; # role-players
+my %quest = (
+    questers => [],
+    p1 => [], # point 1 for q2
+    p2 => [], # point 2 for q2
+    qtime => time() + int(rand(21600)), # first quest starts in <=6 hours
+    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
+my $pausemode = 0; # pausemode on/off flag
+my $silentmode = 0; # silent mode 0/1/2/3, see head of file
+my @queue; # outgoing message queue
+my $lastreg = 0; # holds the time of the last reg. cleared every second.
+                 # prevents more than one account being registered / second
+my $registrations = 0; # count of registrations this period
+my $sel; # IO::Select object
+my $lasttime = 1; # last time that rpcheck() was run
+my $buffer; # buffer for socket stuff
+my $conn_tries = 0; # number of connection tries. gives up after trying each
+                    # server twice
+my $sock; # IO::Socket::INET object
+my %split; # holds nick!user@hosts for clients that have been netsplit
+my $freemessages = 4; # number of "free" privmsgs we can send. 0..$freemessages
+
+sub daemonize(); # prototype to avoid warnings
+
+if (! -e $opts{dbfile}) {
+    $|=1;
+    %rps = ();
+    print "$opts{dbfile} does not appear to exist. I'm guessing this is your ".
+          "first time using IRPG. Please give an account name that you would ".
+          "like to have admin access [$opts{owner}]: ";
+    chomp(my $uname = <STDIN>);
+    $uname =~ s/\s.*//g;
+    $uname = length($uname)?$uname:$opts{owner};
+    print "Enter a character class for this account: ";
+    chomp(my $uclass = <STDIN>);
+    $rps{$uname}{class} = substr($uclass,0,30);
+    print "Enter a password for this account: ";
+    if ($^O ne "MSWin32") {
+        system("stty -echo");
+    }
+    chomp(my $upass = <STDIN>);
+    if ($^O ne "MSWin32") {
+        system("stty echo");
+    }
+    $rps{$uname}{pass} = crypt($upass,mksalt());
+    $rps{$uname}{next} = $opts{rpbase};
+    $rps{$uname}{nick} = "";
+    $rps{$uname}{userhost} = "";
+    $rps{$uname}{level} = 0;
+    $rps{$uname}{online} = 0;
+    $rps{$uname}{idled} = 0;
+    $rps{$uname}{created} = time();
+    $rps{$uname}{lastlogin} = time();
+    $rps{$uname}{x} = int(rand($opts{mapx}));
+    $rps{$uname}{y} = int(rand($opts{mapy}));
+    $rps{$uname}{alignment}="n";
+    $rps{$uname}{isadmin} = 1;
+    for my $item ("ring","amulet","charm","weapon","helm",
+                  "tunic","pair of gloves","shield",
+                  "set of leggings","pair of boots") {
+        $rps{$uname}{item}{$item} = 0;
+    }
+    for my $pen ("pen_mesg","pen_nick","pen_part",
+                 "pen_kick","pen_quit","pen_quest",
+                 "pen_logout","pen_logout") {
+        $rps{$uname}{$pen} = 0;
+    }
+    writedb();
+    print "OK, wrote you into $opts{dbfile}.\n";
+}
+
+# this is almost silly...
+if ($opts{checkupdates}) {
+    print "Checking for updates...\n\n";
+    my $tempsock = IO::Socket::INET->new(PeerAddr=>"jotun.ultrazone.org:80",
+                                         Timeout => 15);
+    if ($tempsock) {
+        print $tempsock "GET /g7/version.php?version=$version HTTP/1.1\r\n".
+                        "Host: jotun.ultrazone.org:80\r\n\r\n";
+        my($line,$newversion);
+        while ($line=<$tempsock>) {
+            chomp($line);
+            next() unless $line;
+            if ($line =~ /^Current version : (\S+)/) {
+                if ($version ne $1) {
+                    print "There is an update available! Changes include:\n";
+                    $newversion=1;
+                }
+                else {
+                    print "You are running the latest version (v$1).\n";
+                    close($tempsock);
+                    last();
+                }
+            }
+            elsif ($newversion && $line =~ /^(  -? .+)/) { print "$1\n"; }
+            elsif ($newversion && $line =~ /^URL: (.+)/) {
+                print "\nGet the newest version from $1!\n";
+                close($tempsock);
+                last();
+            }
+        }
+    }
+    else { print debug("Could not connect to update server.")."\n"; }
+}
+
+print "\n".debug("Becoming a daemon...")."\n";
+daemonize();
+
+$SIG{HUP} = "readconfig"; # sighup = reread config file
+
+CONNECT: # cheese.
+
+loaddb();
+
+while (!$sock && $conn_tries < 2*@{$opts{servers}}) {
+    debug("Connecting to $opts{servers}->[0]...");
+    my %sockinfo = (PeerAddr => $opts{servers}->[0]);
+    if ($opts{localaddr}) { $sockinfo{LocalAddr} = $opts{localaddr}; }
+
+    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
+        push(@{$opts{servers}},shift(@{$opts{servers}}));
+    }
+    else { debug("Connected."); }
+}
+
+if (!$sock) {
+    debug("Error: Too many connection failures, exhausted server list.\n",1);
+}
+
+$conn_tries=0;
+
+$sel = IO::Select->new($sock);
+
+sts("NICK $opts{botnick}");
+sts("USER $opts{botuser} 0 0 :$opts{botrlnm}");
+
+while (1) {
+    my($readable) = IO::Select->select($sel,undef,undef,0.5);
+    if (defined($readable)) {
+        my $fh = $readable->[0];
+        my $buffer2;
+        $fh->recv($buffer2,512,0);
+        if (length($buffer2)) {
+            $buffer .= $buffer2;
+            while (index($buffer,"\n") != -1) {
+                my $line = substr($buffer,0,index($buffer,"\n")+1);
+                $buffer = substr($buffer,length($line));
+                parse($line);
+            }
+        }
+        else {
+            # uh oh, we've been disconnected from the server, possibly before
+            # we've logged in the users in %auto_login. so, we'll set those
+            # users' online flags to 1, rewrite db, and attempt to reconnect
+            # (if that's wanted of us)
+            $rps{$_}{online}=1 for keys(%auto_login);
+            writedb();
+
+            close($fh);
+            $sel->remove($fh);
+
+            if ($opts{reconnect}) {
+                undef(@queue);
+                undef($sock);
+                debug("Socket closed; disconnected. Cleared outgoing message ".
+                      "queue. Waiting $opts{reconnect_wait}s before next ".
+                      "connection attempt...");
+                sleep($opts{reconnect_wait});
+                goto CONNECT;
+            }
+            else { debug("Socket closed; disconnected.",1); }
+        }
+    }
+    else { select(undef,undef,undef,1); }
+    if ((time()-$lasttime) >= $opts{self_clock}) { rpcheck(); }
+}
+
+
+sub parse {
+    my($in) = shift;
+    $inbytes += length($in); # increase parsed byte count
+    $in =~ s/[\r\n]//g; # strip all \r and \n
+    debug("<- $in");
+    my @arg = split(/\s/,$in); # split into "words"
+    my $usernick = substr((split(/!/,$arg[0]))[0],1);
+    # logged in char name of nickname, or undef if nickname is not online
+    my $username = finduser($usernick);
+    if (lc($arg[0]) eq 'ping') { sts("PONG $arg[1]",1); }
+    elsif (lc($arg[0]) eq 'error') {
+        # uh oh, we've been disconnected from the server, possibly before we've
+        # logged in the users in %auto_login. so, we'll set those users' online
+        # flags to 1, rewrite db, and attempt to reconnect (if that's wanted of
+        # us)
+        $rps{$_}{online}=1 for keys(%auto_login);
+        writedb();
+        return;
+    }
+    $arg[1] = lc($arg[1]); # original case no longer matters
+    if ($arg[1] eq '433' && $opts{botnick} eq $arg[3]) {
+        $opts{botnick} .= 0;
+        sts("NICK $opts{botnick}");
+    }
+    elsif ($arg[1] eq 'join') {
+        # %onchan holds time user joined channel. used for the advertisement ban
+        $onchan{$usernick}=time();
+        if ($opts{'detectsplits'} && exists($split{substr($arg[0],1)})) {
+            delete($split{substr($arg[0],1)});
+        }
+        elsif ($opts{botnick} eq $usernick) {
+            sts("WHO $opts{botchan}");
+            (my $opcmd = $opts{botopcmd}) =~ s/%botnick%/$opts{botnick}/eg;
+            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)
+        if ($usernick eq $primnick) { sts("NICK $primnick",1); }
+        elsif ($opts{'detectsplits'} &&
+               "@arg[2..$#arg]" =~ /^:\S+\.\S+ \S+\.\S+$/) {
+            if (defined($username)) { # user was online
+                $split{substr($arg[0],1)}{time}=time();
+                $split{substr($arg[0],1)}{account}=$username;
+            }
+        }
+        else {
+            penalize($username,"quit");
+        }
+        delete($onchan{$usernick});
+    }
+    elsif ($arg[1] eq 'nick') {
+        # if someone (nickserv) changes our nick for us, update $opts{botnick}
+        if ($usernick eq $opts{botnick}) {
+            $opts{botnick} = substr($arg[2],1);
+        }
+        # if we see our nick come open, grab it (skipping queue), unless it was
+        # us who just lost it
+        elsif ($usernick eq $primnick) { sts("NICK $primnick",1); }
+        else {
+            penalize($username,"nick",$arg[2]);
+            $onchan{substr($arg[2],1)} = delete($onchan{$usernick});
+        }
+    }
+    elsif ($arg[1] eq 'part') {
+        penalize($username,"part");
+        delete($onchan{$usernick});
+    }
+    elsif ($arg[1] eq 'kick') {
+        $usernick = $arg[3];
+        penalize(finduser($usernick),"kick");
+        delete($onchan{$usernick});
+    }
+    # don't penalize /notices to the bot
+    elsif ($arg[1] eq 'notice' && $arg[2] ne $opts{botnick}) {
+        penalize($username,"notice",length("@arg[3..$#arg]")-1);
+    }
+    elsif ($arg[1] eq '001') {
+        # send our identify command, set our usermode, join channel
+        sts($opts{botident});
+        sts("MODE $opts{botnick} :$opts{botmodes}");
+        sts("JOIN $opts{botchan}");
+        $opts{botchan} =~ s/ .*//; # strip channel key if present
+    }
+    elsif ($arg[1] eq '315') {
+        # 315 is /WHO end. report who we automagically signed online iff it will
+        # print < 1k of text
+        if (keys(%auto_login)) {
+            # not a true measure of size, but easy
+            if (length("%auto_login") < 1024 && $opts{senduserlist}) {
+                chanmsg(scalar(keys(%auto_login))." users matching ".
+                        scalar(keys(%prev_online))." hosts automatically ".
+                        "logged in; accounts: ".join(", ",keys(%auto_login)));
+            }
+            else {
+                chanmsg(scalar(keys(%auto_login))." users matching ".
+                        scalar(keys(%prev_online))." hosts automatically ".
+                        "logged in.");
+            }
+            if ($opts{voiceonlogin}) {
+                my @vnicks = map { $rps{$_}{nick} } keys(%auto_login);
+                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; }
+    }
+    elsif ($arg[1] eq '352') {
+        my $user;
+        # 352 is one line of /WHO. check that the nick!user@host exists as a key
+        # in %prev_online, the list generated in loaddb(). the value is the user
+        # to login
+        $onchan{$arg[7]}=time();
+        if (exists($prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]})) {
+            $rps{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}{online} = 1;
+            $auto_login{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}=1;
+        }
+    }
+    elsif ($arg[1] eq 'privmsg') {
+        $arg[0] = substr($arg[0],1); # strip leading : from privmsgs
+        if (lc($arg[2]) eq lc($opts{botnick})) { # to us, not channel
+            $arg[3] = lc(substr($arg[3],1)); # lowercase, strip leading :
+            if ($arg[3] eq "\1version\1") {
+                notice("\1VERSION IRPG bot v$version by jotun; ".
+                       "http://idlerpg.net/\1",$usernick);
+            }
+            elsif ($arg[3] eq "peval") {
+                if (!ha($username) || ($opts{ownerpevalonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg("You don't have access to PEVAL.", $usernick);
+                }
+                else {
+                    my @peval = eval "@arg[4..$#arg]";
+                    if (@peval >= 4 || length("@peval") > 1024) {
+                        privmsg("Command produced too much output to send ".
+                                "outright; queueing ".length("@peval").
+                                " bytes in ".scalar(@peval)." items. Use ".
+                                "CLEARQ to clear queue if needed.",$usernick,1);
+                        privmsg($_,$usernick) for @peval;
+                    }
+                    else { privmsg($_,$usernick, 1) for @peval; }
+                    privmsg("EVAL ERROR: $@", $usernick, 1) if $@;
+                }
+            }
+            elsif ($arg[3] eq "register") {
+                if (defined $username) {
+                    privmsg("Sorry, you are already online as $username.",
+                            $usernick);
+                }
+                else {
+                    if ($#arg < 6 || $arg[6] eq "") {
+                        privmsg("Try: REGISTER <char name> <password> <class>",
+                                $usernick);
+                        privmsg("IE : REGISTER Poseidon MyPassword God of the ".
+                                "Sea",$usernick);
+                    }
+                    elsif ($pausemode) {
+                        privmsg("Sorry, new accounts may not be registered ".
+                                "while the bot is in pause mode; please wait ".
+                                "a few minutes and try again.",$usernick);
+                    }
+                    elsif (exists $rps{$arg[4]} || ($opts{casematters} &&
+                           scalar(grep { lc($arg[4]) eq lc($_) } keys(%rps)))) {
+                        privmsg("Sorry, that character name is already in use.",
+                                $usernick);
+                    }
+                    elsif (lc($arg[4]) eq lc($opts{botnick}) ||
+                           lc($arg[4]) eq lc($primnick)) {
+                        privmsg("Sorry, that character name cannot be ".
+                                "registered.",$usernick);
+                    }
+                    elsif (!exists($onchan{$usernick})) {
+                        privmsg("Sorry, you're not in $opts{botchan}.",
+                                $usernick);
+                    }
+                    elsif (length($arg[4]) > 16 || length($arg[4]) < 1) {
+                        privmsg("Sorry, character names must be < 17 and > 0 ".
+                                "chars long.", $usernick);
+                    }
+                    elsif ($arg[4] =~ /^#/) {
+                        privmsg("Sorry, character names may not begin with #.",
+                                $usernick);
+                    }
+                    elsif ($arg[4] =~ /\001/) {
+                        privmsg("Sorry, character names may not include ".
+                                "character \\001.",$usernick);
+                    }
+                    elsif ($opts{noccodes} && ($arg[4] =~ /[[:cntrl:]]/ ||
+                           "@arg[6..$#arg]" =~ /[[:cntrl:]]/)) {
+                        privmsg("Sorry, neither character names nor classes ".
+                                "may include control codes.",$usernick);
+                    }
+                    elsif ($opts{nononp} && ($arg[4] =~ /[[:^print:]]/ ||
+                           "@arg[6..$#arg]" =~ /[[:^print:]]/)) {
+                        privmsg("Sorry, neither character names nor classes ".
+                                "may include non-printable chars.",$usernick);
+                    }
+                    elsif (length("@arg[6..$#arg]") > 30) {
+                        privmsg("Sorry, character classes must be < 31 chars ".
+                                "long.",$usernick);
+                    }
+                    elsif (time() == $lastreg) {
+                        privmsg("Wait 1 second and try again.",$usernick);                
+                    }
+                    else {
+                        if ($opts{voiceonlogin}) {
+                            sts("MODE $opts{botchan} +v :$usernick");
+                        }
+                        ++$registrations;
+                        $lastreg = time();
+                        $rps{$arg[4]}{next} = $opts{rpbase};
+                        $rps{$arg[4]}{class} = "@arg[6..$#arg]";
+                        $rps{$arg[4]}{level} = 0;
+                        $rps{$arg[4]}{online} = 1;
+                        $rps{$arg[4]}{nick} = $usernick;
+                        $rps{$arg[4]}{userhost} = $arg[0];
+                        $rps{$arg[4]}{created} = time();
+                        $rps{$arg[4]}{lastlogin} = time();
+                        $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
+                        $rps{$arg[4]}{x} = int(rand($opts{mapx}));
+                        $rps{$arg[4]}{y} = int(rand($opts{mapy}));
+                        $rps{$arg[4]}{alignment}="n";
+                        $rps{$arg[4]}{isadmin} = 0;
+                        for my $item ("ring","amulet","charm","weapon","helm",
+                                      "tunic","pair of gloves","shield",
+                                      "set of leggings","pair of boots") {
+                            $rps{$arg[4]}{item}{$item} = 0;
+                        }
+                        for my $pen ("pen_mesg","pen_nick","pen_part",
+                                     "pen_kick","pen_quit","pen_quest",
+                                     "pen_logout","pen_logout") {
+                            $rps{$arg[4]}{$pen} = 0;
+                        }
+                        chanmsg("Welcome $usernick\'s new player $arg[4], the ".
+                                "@arg[6..$#arg]! Next level in ".
+                                duration($opts{rpbase}).".");
+                        privmsg("Success! Account $arg[4] created. You have ".
+                                "$opts{rpbase} seconds idleness until you ".
+                                "reach level 1. ", $usernick);
+                        privmsg("NOTE: The point of the game is to see who ".
+                                "can idle the longest. As such, talking in ".
+                                "the channel, parting, quitting, and changing ".
+                                "nicks all penalize you.",$usernick);
+                        if ($opts{phonehome}) {
+                            my $tempsock = IO::Socket::INET->new(PeerAddr=>
+                                "jotun.ultrazone.org:80");
+                            if ($tempsock) {
+                                print $tempsock
+                                    "GET /g7/count.php?new=1 HTTP/1.1\r\n".
+                                    "Host: jotun.ultrazone.org:80\r\n\r\n";
+                                sleep(1);
+                                close($tempsock);
+                            }
+                        }
+                    }
+                }
+            }
+            elsif ($arg[3] eq "delold") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to DELOLD.", $usernick);
+                }
+                # insure it is a number
+                elsif ($arg[4] !~ /^[\d\.]+$/) {
+                    privmsg("Try: DELOLD <# of days>", $usernick, 1);
+                }
+                else {
+                    my @oldaccounts = grep { (time()-$rps{$_}{lastlogin}) >
+                                             ($arg[4] * 86400) &&
+                                             !$rps{$_}{online} } keys(%rps);
+                    delete(@rps{@oldaccounts});
+                    chanmsg(scalar(@oldaccounts)." accounts not accessed in ".
+                            "the last $arg[4] days removed by $arg[0].");
+                }
+            }
+            elsif ($arg[3] eq "del") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to DEL.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                   privmsg("Try: DEL <char name>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such account $arg[4].", $usernick, 1);
+                }
+                else {
+                    delete($rps{$arg[4]});
+                    chanmsg("Account $arg[4] removed by $arg[0].");
+                }
+            }
+            elsif ($arg[3] eq "mkadmin") {
+                if (!ha($username) || ($opts{owneraddonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg("You don't have access to MKADMIN.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Try: MKADMIN <char name>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such account $arg[4].", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{isadmin}=1;
+                    privmsg("Account $arg[4] is now a bot admin.",$usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "deladmin") {
+                if (!ha($username) || ($opts{ownerdelonly} &&
+                    $opts{owner} ne $username)) {
+                    privmsg("You don't have access to DELADMIN.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Try: DELADMIN <char name>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such account $arg[4].", $usernick, 1);
+                }
+                elsif ($arg[4] eq $opts{owner}) {
+                    privmsg("Cannot DELADMIN owner account.", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{isadmin}=0;
+                    privmsg("Account $arg[4] is no longer a bot admin.",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "hog") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to HOG.", $usernick);
+                }
+                else {
+                    chanmsg("$usernick has summoned the Hand of God.");
+                    hog();
+                }
+            }
+            elsif ($arg[3] eq "rehash") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to REHASH.", $usernick);
+                }
+                else {
+                    readconfig();
+                    privmsg("Reread config file.",$usernick,1);
+                    $opts{botchan} =~ s/ .*//; # strip channel key if present
+                }
+            }
+            elsif ($arg[3] eq "chpass") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to CHPASS.", $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg("Try: CHPASS <char name> <new pass>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such username $arg[4].", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
+                    privmsg("Password for $arg[4] changed.", $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "chuser") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to CHUSER.", $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg("Try: CHUSER <char name> <new char name>",
+                            $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such username $arg[4].", $usernick, 1);
+                }
+                elsif (exists($rps{$arg[5]})) {
+                    privmsg("Username $arg[5] is already taken.", $usernick,1);
+                }
+                else {
+                    $rps{$arg[5]} = delete($rps{$arg[4]});
+                    privmsg("Username for $arg[4] changed to $arg[5].",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "chclass") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to CHCLASS.", $usernick);
+                }
+                elsif (!defined($arg[5])) {
+                    privmsg("Try: CHCLASS <char name> <new char class>",
+                            $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such username $arg[4].", $usernick, 1);
+                }
+                else {
+                    $rps{$arg[4]}{class} = "@arg[5..$#arg]";
+                    privmsg("Class for $arg[4] changed to @arg[5..$#arg].",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "push") {
+                if (!ha($username)) {
+                    privmsg("You don't have access to PUSH.", $usernick);
+                }
+                # insure it's a positive or negative, integral number of seconds
+                elsif ($arg[5] !~ /^\-?\d+$/) {
+                    privmsg("Try: PUSH <char name> <seconds>", $usernick, 1);
+                }
+                elsif (!exists($rps{$arg[4]})) {
+                    privmsg("No such username $arg[4].", $usernick, 1);
+                }
+                elsif ($arg[5] > $rps{$arg[4]}{next}) {
+                    privmsg("Time to level for $arg[4] ($rps{$arg[4]}{next}s) ".
+                            "is lower than $arg[5]; setting TTL to 0.",
+                            $usernick, 1);
+                    chanmsg("$usernick has pushed $arg[4] $rps{$arg[4]}{next} ".
+                            "seconds toward level ".($rps{$arg[4]}{level}+1));
+                    $rps{$arg[4]}{next}=0;
+                }
+                else {
+                    $rps{$arg[4]}{next} -= $arg[5];
+                     chanmsg("$usernick has pushed $arg[4] $arg[5] seconds ".
+                             "toward level ".($rps{$arg[4]}{level}+1).". ".
+                             "$arg[4] reaches next level in ".
+                             duration($rps{$arg[4]}{next}).".");
+                }
+            }   
+            elsif ($arg[3] eq "logout") {
+                if (defined($username)) {
+                    penalize($username,"logout");
+                }
+                else {
+                    privmsg("You are not logged in.", $usernick);
+                }
+            }
+            elsif ($arg[3] eq "quest") {
+                if (!@{$quest{questers}}) {
+                    privmsg("There is no active quest.",$usernick);
+                }
+                elsif ($quest{type} == 1) {
+                    privmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                            "$quest{questers}->[3] are on a quest to ".
+                            "$quest{text}. Quest to complete in ".
+                            duration($quest{qtime}-time()).".",$usernick);
+                }
+                elsif ($quest{type} == 2) {
+                    privmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                            "$quest{questers}->[3] are on a quest to ".
+                            "$quest{text}. Participants must first reach ".
+                            "[$quest{p1}->[0],$quest{p1}->[1]], then ".
+                            "[$quest{p2}->[0],$quest{p2}->[1]].".
+                            ($opts{mapurl}?" See $opts{mapurl} to monitor ".
+                            "their journey's progress.":""),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "status" && $opts{statuscmd}) {
+                if (!defined($username)) {
+                    privmsg("You are not logged in.", $usernick);
+                }
+                # argument is optional
+                elsif ($arg[4] && !exists($rps{$arg[4]})) {
+                    privmsg("No such user.",$usernick);
+                }
+                elsif ($arg[4]) { # optional 'user' argument
+                    privmsg("$arg[4]: Level $rps{$arg[4]}{level} ".
+                            "$rps{$arg[4]}{class}; Status: O".
+                            ($rps{$arg[4]}{online}?"n":"ff")."line; ".
+                            "TTL: ".duration($rps{$arg[4]}{next})."; ".
+                            "Idled: ".duration($rps{$arg[4]}{idled}).
+                            "; Item sum: ".itemsum($arg[4]),$usernick);
+                }
+                else { # no argument, look up this user
+                    privmsg("$username: Level $rps{$username}{level} ".
+                            "$rps{$username}{class}; Status: O".
+                            ($rps{$username}{online}?"n":"ff")."line; ".
+                            "TTL: ".duration($rps{$username}{next})."; ".
+                            "Idled: ".duration($rps{$username}{idled})."; ".
+                            "Item sum: ".itemsum($username),$usernick);
+                }
+            }
+            elsif ($arg[3] eq "whoami") {
+                if (!defined($username)) {
+                    privmsg("You are not logged in.", $usernick);
+                }
+                else {
+                    privmsg("You are $username, the level ".
+                            $rps{$username}{level}." $rps{$username}{class}. ".
+                            "Next level in ".duration($rps{$username}{next}),
+                            $usernick);
+                }
+            }
+            elsif ($arg[3] eq "newpass") {
+                if (!defined($username)) {
+                    privmsg("You are not logged in.", $usernick)
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Try: NEWPASS <new password>", $usernick);
+                }
+                else {
+                    $rps{$username}{pass} = crypt($arg[4],mksalt());
+                    privmsg("Your password was changed.",$usernick);
+                }
+            }
+            elsif ($arg[3] eq "align") {
+                if (!defined($username)) {
+                    privmsg("You are not logged in.", $usernick)
+                }
+                elsif (!defined($arg[4]) || (lc($arg[4]) ne "good" && 
+                       lc($arg[4]) ne "neutral" && lc($arg[4]) ne "evil")) {
+                    privmsg("Try: ALIGN <good|neutral|evil>", $usernick);
+                }
+                else {
+                    $rps{$username}{alignment} = substr(lc($arg[4]),0,1);
+                    chanmsg("$username has changed alignment to: ".lc($arg[4]).
+                            ".");
+                    privmsg("Your alignment was changed to ".lc($arg[4]).".",
+                            $usernick);
+                }
+            }
+            elsif ($arg[3] eq "removeme") {
+                if (!defined($username)) {
+                    privmsg("You are not logged in.", $usernick)
+                }
+                else {
+                    privmsg("Account $username removed.",$usernick);
+                    chanmsg("$arg[0] removed his account, $username, the ".
+                            $rps{$username}{class}.".");
+                    delete($rps{$username});
+                }
+            }
+            elsif ($arg[3] eq "help") {
+                if (!ha($username)) {
+                    privmsg("For information on IRPG bot commands, see ".
+                            $opts{helpurl}, $usernick);
+                }
+                else {
+                    privmsg("Help URL is $opts{helpurl}", $usernick, 1);
+                    privmsg("Admin commands URL is $opts{admincommurl}",
+                            $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "die") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to DIE.", $usernick);
+                }
+                else {
+                    $opts{reconnect} = 0;
+                    writedb();
+                    sts("QUIT :DIE from $arg[0]",1);
+                }
+            }
+            elsif ($arg[3] eq "reloaddb") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to RELOADDB.", $usernick);
+                }
+                elsif (!$pausemode) {
+                    privmsg("ERROR: Can only use LOADDB while in PAUSE mode.",
+                            $usernick, 1);
+                }
+                else {
+                    loaddb();
+                    privmsg("Reread player database file; ".scalar(keys(%rps)).
+                            " accounts loaded.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "backup") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to BACKUP.", $usernick);
+                }
+                else {
+                    backup();
+                    privmsg("$opts{dbfile} copied to ".
+                            ".dbbackup/$opts{dbfile}".time(),$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "pause") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to PAUSE.", $usernick);
+                }
+                else {
+                    $pausemode = $pausemode ? 0 : 1;
+                    privmsg("PAUSE_MODE set to $pausemode.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "silent") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to SILENT.", $usernick);
+                }
+                elsif (!defined($arg[4]) || $arg[4] < 0 || $arg[4] > 3) {
+                    privmsg("Try: SILENT <mode>", $usernick,1);
+                }
+                else {
+                    $silentmode = $arg[4];
+                    privmsg("SILENT_MODE set to $silentmode.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "jump") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to JUMP.", $usernick);
+                }
+                elsif (!defined($arg[4])) {
+                    privmsg("Try: JUMP <server[:port]>", $usernick, 1);
+                }
+                else {
+                    writedb();
+                    sts("QUIT :JUMP to $arg[4] from $arg[0]");
+                    unshift(@{$opts{servers}},$arg[4]);
+                    close($sock);
+                    sleep(3);
+                    goto CONNECT;
+                }
+            }
+            elsif ($arg[3] eq "restart") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to RESTART.", $usernick);
+                }
+                else {
+                    writedb();
+                    sts("QUIT :RESTART from $arg[0]",1);
+                    close($sock);
+                    exec("perl $0");
+                }
+            }
+            elsif ($arg[3] eq "clearq") {
+                if (!ha($username)) {
+                    privmsg("You do not have access to CLEARQ.", $usernick);
+                }
+                else {
+                    undef(@queue);
+                    chanmsg("Outgoing message queue cleared by $arg[0].");
+                    privmsg("Outgoing message queue cleared.",$usernick,1);
+                }
+            }
+            elsif ($arg[3] eq "info") {
+                my $info;
+                if (!ha($username) && $opts{allowuserinfo}) {
+                    $info = "IRPG bot v$version by jotun, ".
+                            "http://idlerpg.net/. On via server: ".
+                            $opts{servers}->[0].". Admins online: ".
+                            join(", ", map { $rps{$_}{nick} }
+                                      grep { $rps{$_}{isadmin} &&
+                                             $rps{$_}{online} } keys(%rps)).".";
+                    privmsg($info, $usernick);
+                }
+                elsif (!ha($username) && !$opts{allowuserinfo}) {
+                    privmsg("You do not have access to INFO.", $usernick);
+                }
+                else {
+                    my $queuedbytes = 0;
+                    $queuedbytes += (length($_)+2) for @queue; # +2 = \r\n
+                    $info = sprintf(
+                        "%.2fkb sent, %.2fkb received in %s. %d IRPG users ".
+                        "online of %d total users. %d accounts created since ".
+                        "startup. PAUSE_MODE is %d, SILENT_MODE is %d. ".
+                        "Outgoing queue is %d bytes in %d items. On via: %s. ".
+                        "Admins online: %s.",
+                        $outbytes/1024,
+                        $inbytes/1024,
+                        duration(time()-$^T),
+                        scalar(grep { $rps{$_}{online} } keys(%rps)),
+                        scalar(keys(%rps)),
+                        $registrations,
+                        $pausemode,
+                        $silentmode,
+                        $queuedbytes,
+                        scalar(@queue),
+                        $opts{servers}->[0],
+                        join(", ",map { $rps{$_}{nick} }
+                                  grep { $rps{$_}{isadmin} && $rps{$_}{online} }
+                                  keys(%rps)));
+                    privmsg($info, $usernick, 1);
+                }
+            }
+            elsif ($arg[3] eq "login") {
+                if (defined($username)) {
+                    notice("Sorry, you are already online as $username.",
+                            $usernick);
+                }
+                else {
+                    if ($#arg < 5 || $arg[5] eq "") {
+                        notice("Try: LOGIN <username> <password>", $usernick);
+                    }
+                    elsif (!exists $rps{$arg[4]}) {
+                        notice("Sorry, no such account name. Note that ".
+                                "account names are case sensitive.",$usernick);
+                    }
+                    elsif (!exists $onchan{$usernick}) {
+                        notice("Sorry, you're not in $opts{botchan}.",
+                                $usernick);
+                    }
+                    elsif ($rps{$arg[4]}{pass} ne
+                           crypt($arg[5],$rps{$arg[4]}{pass})) {
+                        notice("Wrong password.", $usernick);
+                    }
+                    else {
+                        if ($opts{voiceonlogin}) {
+                            sts("MODE $opts{botchan} +v :$usernick");
+                        }
+                        $rps{$arg[4]}{online} = 1;
+                        $rps{$arg[4]}{nick} = $usernick;
+                        $rps{$arg[4]}{userhost} = $arg[0];
+                        $rps{$arg[4]}{lastlogin} = time();
+                        chanmsg("$arg[4], the level $rps{$arg[4]}{level} ".
+                                "$rps{$arg[4]}{class}, is now online from ".
+                                "nickname $usernick. Next level in ".
+                                duration($rps{$arg[4]}{next}).".");
+                        notice("Logon successful. Next level in ".
+                               duration($rps{$arg[4]}{next}).".", $usernick);
+                    }
+                }
+            }
+        }
+        # penalize returns true if user was online and successfully penalized.
+        # if the user is not logged in, then penalize() fails. so, if user is
+        # offline, and they say something including "http:", and they've been on
+        # the channel less than 90 seconds, and the http:-style ban is on, then
+        # check to see if their url is in @{$opts{okurl}}. if not, kickban them
+        elsif (!penalize($username,"privmsg",length("@arg[3..$#arg]")) &&
+               index(lc("@arg[3..$#arg]"),"http:") != -1 &&
+               (time()-$onchan{$usernick}) < 90 && $opts{doban}) {
+            my $isokurl = 0;
+            for (@{$opts{okurl}}) {
+                if (index(lc("@arg[3..$#arg]"),lc($_)) != -1) { $isokurl = 1; }
+            }
+            if (!$isokurl) {
+                sts("MODE $opts{botchan} +b $arg[0]");
+                sts("KICK $opts{botchan} $usernick :No advertising; ban will ".
+                    "be lifted within the hour.");
+                push(@bans,$arg[0]) if @bans < 12;
+            }
+        }
+    }
+}
+
+sub sts { # send to server
+    my($text,$skipq) = @_;
+    if ($skipq) {
+        if ($sock) {
+            print $sock "$text\r\n";
+            $outbytes += length($text) + 2;
+            debug("-> $text");
+        }
+        else {
+            # something is wrong. the socket is closed. clear the queue
+            undef(@queue);
+            debug("\$sock isn't writeable in sts(), cleared outgoing queue.\n");
+            return;
+        }
+    }
+    else {
+        push(@queue,$text);
+        debug(sprintf("(q%03d) = %s\n",$#queue,$text));
+    }
+}
+
+sub fq { # deliver message(s) from queue
+    if (!@queue) {
+        ++$freemessages if $freemessages < 4;
+        return undef;
+    }
+    my $sentbytes = 0;
+    for (0..$freemessages) {
+        last() if !@queue; # no messages left to send
+        # lower number of "free" messages we have left
+        my $line=shift(@queue);
+        # if we have already sent one message, and the next message to be sent
+        # plus the previous messages we have sent this call to fq() > 768 bytes,
+        # then requeue this message and return. we don't want to flood off,
+        # after all
+        if ($_ != 0 && (length($line)+$sentbytes) > 768) {
+            unshift(@queue,$line);
+            last();
+        }
+        if ($sock) {
+            debug("(fm$freemessages) -> $line");
+            --$freemessages if $freemessages > 0;
+            print $sock "$line\r\n";
+            $sentbytes += length($line) + 2;
+        }
+        else {
+            undef(@queue);
+            debug("Disconnected: cleared outgoing message queue.");
+            last();
+        }
+        $outbytes += length($line) + 2;
+    }
+}
+
+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+$/;
+    return sprintf("%d day%s, %02d:%02d:%02d",$s/86400,int($s/86400)==1?"":"s",
+                   ($s%86400)/3600,($s%3600)/60,($s%60));
+}
+
+sub ts { # timestamp
+    my @ts = localtime(time());
+    return sprintf("[%02d/%02d/%02d %02d:%02d:%02d] ",
+                   $ts[4]+1,$ts[3],$ts[5]%100,$ts[2],$ts[1],$ts[0]);
+}
+
+sub hog { # summon the hand of god
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    my $player = $players[rand(@players)];
+    my $win = int(rand(5));
+    my $time = int(((5 + int(rand(71)))/100) * $rps{$player}{next});
+    if ($win) {
+        chanmsg(clog("Verily I say unto thee, the Heavens have burst forth, ".
+                     "and the blessed hand of God carried $player ".
+                     duration($time)." toward level ".($rps{$player}{level}+1).
+                     "."));
+        $rps{$player}{next} -= $time;
+    }
+    else {
+        chanmsg(clog("Thereupon He stretched out His little finger among them ".
+                     "and consumed $player with fire, slowing the heathen ".
+                     duration($time)." from level ".($rps{$player}{level}+1).
+                     "."));
+        $rps{$player}{next} += $time;
+    }
+    chanmsg("$player reaches next level in ".duration($rps{$player}{next}).".");
+}
+
+sub rpcheck { # check levels, update database
+    # check splits hash to see if any split users have expired
+    checksplits() if $opts{detectsplits};
+    # send out $freemessages lines of text from the outgoing message queue
+    fq();
+    # clear registration limiting
+    $lastreg = 0;
+    my $online = scalar(grep { $rps{$_}{online} } keys(%rps));
+    # there's really nothing to do here if there are no online users
+    return unless $online;
+    my $onlineevil = scalar(grep { $rps{$_}{online} &&
+                                   $rps{$_}{alignment} eq "e" } keys(%rps));
+    my $onlinegood = scalar(grep { $rps{$_}{online} &&
+                                   $rps{$_}{alignment} eq "g" } keys(%rps));
+    if (!$opts{noscale}) {
+        if (rand((20*86400)/$opts{self_clock}) < $online) { hog(); }
+        if (rand((24*86400)/$opts{self_clock}) < $online) { team_battle(); }
+        if (rand((8*86400)/$opts{self_clock}) < $online) { calamity(); }
+        if (rand((4*86400)/$opts{self_clock}) < $online) { godsend(); }
+    }
+    else {
+        hog() if rand(4000) < 1;
+        team_battle() if rand(4000) < 1;
+        calamity() if rand(4000) < 1;
+        godsend() if rand(2000) < 1;
+    }
+    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 < $oldrpreport%120) && $opts{writequestfile}) { writequestfile(); }
+    if (time() > $quest{qtime}) {
+        if (!@{$quest{questers}}) { quest(); }
+        elsif ($quest{type} == 1) {
+            chanmsg(clog(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                         "$quest{questers}->[3] have blessed the realm by ".
+                         "completing their quest! 25% of their burden is ".
+                         "eliminated."));
+            for (@{$quest{questers}}) {
+                $rps{$_}{next} = int($rps{$_}{next} * .75);
+            }
+            undef(@{$quest{questers}});
+            $quest{qtime} = time() + 21600;
+            writequestfile();
+        }
+        # quest type 2 awards are handled in moveplayers()
+    }
+    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;
+        for my $i (0..2) {
+            $#u >= $i and
+            chanmsg("$u[$i], the level $rps{$u[$i]}{level} ".
+                    "$rps{$u[$i]}{class}, is #" . ($i + 1) . "! Next level in ".
+                    (duration($rps{$u[$i]}{next})).".");
+        }
+        backup();
+    }
+    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+
+        if ((scalar(@players)/scalar(grep { $rps{$_}{online} } keys(%rps))) > .15) {
+            challenge_opp($players[int(rand(@players))]);
+        }
+        while (@bans) {
+            sts("MODE $opts{botchan} -bbbb :@bans[0..3]");
+            splice(@bans,0,4);
+        }
+    }
+    if ($rpreport%1800 < $oldrpreport%1800) { # 30 mins
+        if ($opts{botnick} ne $primnick) {
+            sts($opts{botghostcmd}) if $opts{botghostcmd};
+            sts("NICK $primnick");
+        }
+    }
+    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
+    # log everyone out if the bot failed to connect. $lasttime = time() on
+    # successful join to $opts{botchan}, initial value is 1). if fails to open
+    # $opts{dbfile}, will not update $lasttime and so should have correct values
+    # on next rpcheck(). 
+    if ($lasttime != 1) {
+        my $curtime=time();
+        for my $k (keys(%rps)) {
+            if ($rps{$k}{online} && exists $rps{$k}{nick} &&
+                $rps{$k}{nick} && exists $onchan{$rps{$k}{nick}}) {
+                $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}++;
+                    $rps{$k}{next} += $ttl;
+                    chanmsg("$k, the $rps{$k}{class}, has attained level ".
+                            "$rps{$k}{level}! Next level in ".
+                            duration($ttl).".");
+                    find_item($k);
+                    challenge_opp($k);
+                }
+            }
+            # attempt to make sure this is an actual user, and not just an
+            # artifact of a bad PEVAL
+        }
+        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; }
+    my @opps = grep { $rps{$_}{online} && $u ne $_ } keys(%rps);
+    return unless @opps;
+    my $opp = $opps[int(rand(@opps))];
+    $opp = $primnick if rand(@opps+1) < 1;
+    my $mysum = itemsum($u,1);
+    my $oppsum = itemsum($opp,1);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        my $gain = ($opp eq $primnick)?20:int($rps{$opp}{level}/4);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] has challenged $opp [$opproll/".
+                     "$oppsum] in combat and won! ".duration($gain)." is ".
+                     "removed from $u\'s clock."));
+        $rps{$u}{next} -= $gain;
+        chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
+        my $csfactor = $rps{$u}{alignment} eq "g" ? 50 :
+                       $rps{$u}{alignment} eq "e" ? 20 :
+                       35;
+        if (rand($csfactor) < 1 && $opp ne $primnick) {
+            $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
+            chanmsg(clog("$u has dealt $opp a Critical Strike! ".
+                         duration($gain)." is added to $opp\'s clock."));
+            $rps{$opp}{next} += $gain;
+            chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).
+                    ".");
+        }
+        elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
+            my @items = ("ring","amulet","charm","weapon","helm","tunic",
+                         "pair of gloves","set of leggings","shield",
+                         "pair of boots");
+            my $type = $items[rand(@items)];
+            if (itemlevel($rps{$opp}{item}{$type}) > itemlevel($rps{$u}{item}{$type})) {
+                chanmsg(clog("In the fierce battle, $opp dropped his level ".
+                             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};
+                $rps{$opp}{item}{$type} = $tempitem;
+            }
+        }
+    }
+    else {
+        my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] has challenged $opp [$opproll/".
+                     "$oppsum] in combat and lost! ".duration($gain)." is ".
+                     "added to $u\'s clock."));
+        $rps{$u}{next} += $gain;
+        chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
+    }
+}
+
+sub team_battle { # pit three players against three other players
+    my @opp = grep { $rps{$_}{online} } keys(%rps);
+    return if @opp < 6;
+    # 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};
+    for my $p (1,2) {
+        $gain = $rps{$opp[$p]}{next} if $gain > $rps{$opp[$p]}{next};
+    }
+    $gain = int($gain*.20);
+    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] ".
+                     "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] ".
+                     "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",
+                 "pair of gloves","set of leggings","shield","pair of boots");
+    my $type = $items[rand(@items)];
+    my $level = 1;
+    my $ulevel;
+    for my $num (1 .. int($rps{$u}{level}*1.5)) {
+        if (rand(1.4**($num/4)) < 1) {
+            $level = $num;
+        }
+    }
+    if ($rps{$u}{level} >= 25 && rand(40) < 1) {
+        $ulevel = 50+int(rand(25));
+        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 > 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 > 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 > 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 > 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 > 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 >
+            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 > itemlevel($rps{$u}{item}{weapon})) {
+            exchange_item($u,"weapon",$ulevel."g");
+            return;
+        }
+    }
+    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 ".
+               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 and items database
+    backup();
+    my $l;
+    %rps = ();
+    if (!open(RPS,$opts{dbfile}) && -e $opts{dbfile}) {
+        sts("QUIT :loaddb() failed: $!");
+    }
+    while ($l=<RPS>) {
+        chomp($l);
+        next if $l =~ /^#/; # skip comments
+        my @i = split("\t",$l);
+        print Dumper(@i) if @i != 32;
+        if (@i != 32) {
+            sts("QUIT: Anomaly in loaddb(); line $. of $opts{dbfile} has ".
+                "wrong fields (".scalar(@i).")");
+            debug("Anomaly in loaddb(); line $. of $opts{dbfile} has wrong ".
+                "fields (".scalar(@i).")",1);
+        }
+        if (!$sock) { # if not RELOADDB
+            if ($i[8]) { $prev_online{$i[7]}=$i[0]; } # log back in
+        }
+        ($rps{$i[0]}{pass},
+        $rps{$i[0]}{isadmin},
+        $rps{$i[0]}{level},
+        $rps{$i[0]}{class},
+        $rps{$i[0]}{next},
+        $rps{$i[0]}{nick},
+        $rps{$i[0]}{userhost},
+        $rps{$i[0]}{online},
+        $rps{$i[0]}{idled},
+        $rps{$i[0]}{x},
+        $rps{$i[0]}{y},
+        $rps{$i[0]}{pen_mesg},
+        $rps{$i[0]}{pen_nick},
+        $rps{$i[0]}{pen_part},
+        $rps{$i[0]}{pen_kick},
+        $rps{$i[0]}{pen_quit},
+        $rps{$i[0]}{pen_quest},
+        $rps{$i[0]}{pen_logout},
+        $rps{$i[0]}{created},
+        $rps{$i[0]}{lastlogin},
+        $rps{$i[0]}{item}{amulet},
+        $rps{$i[0]}{item}{charm},
+        $rps{$i[0]}{item}{helm},
+        $rps{$i[0]}{item}{"pair of boots"},
+        $rps{$i[0]}{item}{"pair of gloves"},
+        $rps{$i[0]}{item}{ring},
+        $rps{$i[0]}{item}{"set of leggings"},
+        $rps{$i[0]}{item}{shield},
+        $rps{$i[0]}{item}{tunic},
+        $rps{$i[0]}{item}{weapon},
+        $rps{$i[0]}{alignment}) = (@i[1..7],($sock?$i[8]:0),@i[9..$#i]);
+    }
+    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 {
+    return unless $lasttime > 1;
+    my $onlinecount = grep { $rps{$_}{online} } keys %rps;
+    return unless $onlinecount;
+    for (my $i=0;$i<$opts{self_clock};++$i) {
+        # temporary hash to hold player positions, detect collisions
+        my %positions = ();
+        if ($quest{type} == 2 && @{$quest{questers}}) {
+            my $allgo = 1; # have all users reached <p1|p2>?
+            for (@{$quest{questers}}) {
+                if ($quest{stage}==1) {
+                    if ($rps{$_}{x} != $quest{p1}->[0] ||
+                        $rps{$_}{y} != $quest{p1}->[1]) {
+                        $allgo=0;
+                        last();
+                    }
+                }
+                else {
+                    if ($rps{$_}{x} != $quest{p2}->[0] ||
+                        $rps{$_}{y} != $quest{p2}->[1]) {
+                        $allgo=0;
+                        last();
+                    }
+                }
+            }
+            # all participants have reached point 1, now point 2
+            if ($quest{stage}==1 && $allgo) {
+                $quest{stage}=2;
+                $allgo=0; # have not all reached p2 yet
+            }
+            elsif ($quest{stage} == 2 && $allgo) {
+                chanmsg(clog(join(", ",(@{$quest{questers}})[0..2]).", ".
+                             "and $quest{questers}->[3] have completed their ".
+                             "journey! 25% of their burden is eliminated."));
+                for (@{$quest{questers}}) {
+                    $rps{$_}{next} = int($rps{$_}{next} * .75);
+                }
+                undef(@{$quest{questers}});
+                $quest{qtime} = time() + 21600; # next quest starts in 6 hours
+                $quest{type} = 1; # probably not needed
+                writequestfile();
+            }
+            else {
+                my(%temp,$player);
+                # load keys of %temp with online users
+                ++@temp{grep { $rps{$_}{online} } keys(%rps)};
+                # delete questers from list
+                delete(@temp{@{$quest{questers}}});
+                while ($player = each(%temp)) {
+                    $rps{$player}{x} += int(rand(3))-1;
+                    $rps{$player}{y} += int(rand(3))-1;
+                    # if player goes over edge, wrap them back around
+                    if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x}=0; }
+                    if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y}=0; }
+                    if ($rps{$player}{x} < 0) { $rps{$player}{x}=$opts{mapx}; }
+                    if ($rps{$player}{y} < 0) { $rps{$player}{y}=$opts{mapy}; }
+                    
+                    if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
+                        !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
+                        if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
+                            !$rps{$player}{isadmin} && rand(100) < 1) {
+                            chanmsg("$player encounters ".
+                               $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}.
+                                    " and bows humbly.");
+                        }
+                        if (rand($onlinecount) < 1) {
+                            $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
+                            collision_fight($player,
+                                $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
+                        }
+                    }
+                    else {
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
+                    }
+                }
+                for (@{$quest{questers}}) {
+                    if ($quest{stage} == 1) {
+                        if (rand(100) < 1) {
+                            if ($rps{$_}{x} != $quest{p1}->[0]) {
+                                $rps{$_}{x} += ($rps{$_}{x} < $quest{p1}->[0] ?
+                                                1 : -1);
+                            }
+                            if ($rps{$_}{y} != $quest{p1}->[1]) {
+                                $rps{$_}{y} += ($rps{$_}{y} < $quest{p1}->[1] ?
+                                                1 : -1);
+                            }
+                        }
+                    }
+                    elsif ($quest{stage}==2) {
+                        if (rand(100) < 1) {
+                            if ($rps{$_}{x} != $quest{p2}->[0]) {
+                                $rps{$_}{x} += ($rps{$_}{x} < $quest{p2}->[0] ?
+                                                1 : -1);
+                            }
+                            if ($rps{$_}{y} != $quest{p2}->[1]) {
+                                $rps{$_}{y} += ($rps{$_}{y} < $quest{p2}->[1] ?
+                                                1 : -1);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        else {
+            for my $player (keys(%rps)) {
+                next unless $rps{$player}{online};
+                $rps{$player}{x} += int(rand(3))-1;
+                $rps{$player}{y} += int(rand(3))-1;
+                # if player goes over edge, wrap them back around
+                if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x} = 0; }
+                if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y} = 0; }
+                if ($rps{$player}{x} < 0) { $rps{$player}{x} = $opts{mapx}; }
+                if ($rps{$player}{y} < 0) { $rps{$player}{y} = $opts{mapy}; }
+                if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
+                    !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
+                    if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
+                        !$rps{$player}{isadmin} && rand(100) < 1) {
+                        chanmsg("$player encounters ".
+                           $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}.
+                                " and bows humbly.");
+                    }
+                    if (rand($onlinecount) < 1) {
+                        $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
+                        collision_fight($player,
+                            $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
+                    }
+                }
+                else {
+                    $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
+                    $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
+                }
+            }
+        }
+        # 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);
+                }
+            }
+        }
+    }
+}
+
+sub mksalt { # generate a random salt for passwds
+    join '',('a'..'z','A'..'Z','0'..'9','/','.')[rand(64), rand(64)];
+}
+
+sub chanmsg { # send a message to the channel
+    my $msg = shift or return undef;
+    if ($silentmode & 1) { return undef; }
+    privmsg($msg, $opts{botchan}, shift);
+}
+
+sub privmsg { # send a message to an arbitrary entity
+    my $msg = shift or return undef;
+    my $target = shift or return undef;
+    my $force = shift;
+    if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
+        && !$force) {
+        return undef;
+    }
+    while (length($msg)) {
+        sts("PRIVMSG $target :".substr($msg,0,450),$force);
+        substr($msg,0,450)="";
+    }
+}
+
+sub notice { # send a notice to an arbitrary entity
+    my $msg = shift or return undef;
+    my $target = shift or return undef;
+    my $force = shift;
+    if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
+        && !$force) {
+        return undef;
+    }
+    while (length($msg)) {
+        sts("NOTICE $target :".substr($msg,0,450),$force);
+        substr($msg,0,450)="";
+    }
+}
+
+sub help { # print help message
+    (my $prog = $0) =~ s/^.*\///;
+
+    print "
+usage: $prog [OPTIONS]
+  --help, -h           Print this message
+  --verbose, -v        Print verbose messages
+  --server, -s         Specify IRC server:port to connect to
+  --botnick, -n        Bot's IRC nick
+  --botuser, -u        Bot's username
+  --botrlnm, -r        Bot's real name
+  --botchan, -c        IRC channel to join
+  --botident, -p       Specify identify-to-services command
+  --botmodes, -m       Specify usermodes for the bot to set upon connect
+  --botopcmd, -o       Specify command to send to server on successful connect
+  --botghostcmd, -g    Specify command to send to server to regain primary
+                       nickname when in use
+  --doban              Advertisement ban on/off flag
+  --okurl, -k          Bot will not ban for web addresses that contain these
+                       strings
+  --debug              Debug on/off flag
+  --helpurl            URL to refer new users to
+  --admincommurl       URL to refer admins to
+
+  Timing parameters:
+  --rpbase             Base time to level up
+  --rpstep             Time to next level = rpbase * (rpstep ** CURRENT_LEVEL)
+  --rppenstep          PENALTY_SECS=(PENALTY*(RPPENSTEP**CURRENT_LEVEL))
+
+";
+}
+
+sub itemsum {
+    my $user = shift;
+    # is this for a battle? if so, good users get a 10% boost and evil users get
+    # a 10% detriment
+    my $battle = shift;
+    return -1 unless defined $user;
+    my $sum = 0;
+    if ($user eq $primnick) {
+        for my $u (keys(%rps)) {
+            $sum = itemsum($u) if $sum < itemsum($u);
+        }
+        return $sum+1;
+    }
+    if (!exists($rps{$user})) { return -1; }
+    $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) :
+               $sum;
+    }
+    return $sum;
+}
+
+sub daemonize() {
+    # win32 doesn't daemonize (this way?)
+    if ($^O eq "MSWin32") {
+        print debug("Nevermind, this is Win32, no I'm not.")."\n";
+        return;
+    }
+    use POSIX 'setsid';
+    $SIG{CHLD} = sub { };
+    fork() && exit(0); # kill parent
+    POSIX::setsid() || debug("POSIX::setsid() failed: $!",1);
+    $SIG{CHLD} = sub { };
+    fork() && exit(0); # kill the parent as the process group leader
+    $SIG{CHLD} = sub { };
+    open(STDIN,'/dev/null') || debug("Cannot read /dev/null: $!",1);
+    open(STDOUT,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
+    open(STDERR,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
+    # write our PID to $opts{pidfile}, or return semi-silently on failure
+    open(PIDFILE,">$opts{pidfile}") || do {
+        debug("Error: failed opening pid file: $!");
+        return;
+    };
+    print PIDFILE $$;
+    close(PIDFILE);
+}
+
+sub calamity { # suffer a little one
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    return unless @players;
+    my $player = $players[rand(@players)];
+    if (rand(10) < 1) {
+        my @items = ("amulet","charm","weapon","tunic","set of leggings",
+                     "shield");
+        my $type = $items[rand(@items)];
+        if ($type eq "amulet") {
+            chanmsg(clog("$player fell, chipping the stone in his amulet! ".
+                         "$player\'s $type loses 10% of its effectiveness."));
+        }
+        elsif ($type eq "charm") {
+            chanmsg(clog("$player slipped and dropped his charm in a dirty ".
+                         "bog! $player\'s $type loses 10% of its ".
+                         "effectiveness."));
+        }
+        elsif ($type eq "weapon") {
+            chanmsg(clog("$player left his weapon out in the rain to rust! ".
+                         "$player\'s $type loses 10% of its effectiveness."));
+        }
+        elsif ($type eq "tunic") {
+            chanmsg(clog("$player spilled a level 7 shrinking potion on his ".
+                         "tunic! $player\'s $type loses 10% of its ".
+                         "effectiveness."));
+        }
+        elsif ($type eq "shield") {
+            chanmsg(clog("$player\'s shield was damaged by a dragon's fiery ".
+                         "breath! $player\'s $type loses 10% of its ".
+                         "effectiveness."));
+        }
+        else {
+            chanmsg(clog("$player burned a hole through his leggings while ".
+                         "ironing them! $player\'s $type loses 10% of its ".
+                         "effectiveness."));
+        }
+        my $suffix="";
+        if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
+        $rps{$player}{item}{$type} = int(itemlevel($rps{$player}{item}{$type}) * .9);
+        $rps{$player}{item}{$type}.=$suffix;
+    }
+    else {
+        my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
+        if (!open(Q,$opts{eventsfile})) {
+            return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+        }
+        my($i,$actioned);
+        while (my $line = <Q>) {
+            chomp($line);
+            if ($line =~ /^C (.*)/ && rand(++$i) < 1) { $actioned = $1; }
+        }
+        chanmsg(clog("$player $actioned. This terrible calamity has slowed ".
+                     "them ".duration($time)." from level ".
+                     ($rps{$player}{level}+1)."."));
+        $rps{$player}{next} += $time;
+        chanmsg("$player reaches next level in ".duration($rps{$player}{next}).
+                ".");
+    }
+}
+
+sub godsend { # bless the unworthy
+    my @players = grep { $rps{$_}{online} } keys(%rps);
+    return unless @players;
+    my $player = $players[rand(@players)];
+    if (rand(10) < 1) {
+        my @items = ("amulet","charm","weapon","tunic","set of leggings",
+                     "shield");
+        my $type = $items[rand(@items)];
+        if ($type eq "amulet") {
+            chanmsg(clog("$player\'s amulet was blessed by a passing cleric! ".
+                         "$player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "charm") {
+            chanmsg(clog("$player\'s charm ate a bolt of lightning! ".
+                         "$player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "weapon") {
+            chanmsg(clog("$player sharpened the edge of his weapon! ".
+                         "$player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "tunic") {
+            chanmsg(clog("A magician cast a spell of Rigidity on $player\'s ".
+                         "tunic! $player\'s $type gains 10% effectiveness."));
+        }
+        elsif ($type eq "shield") {
+            chanmsg(clog("$player reinforced his shield with a dragon's ".
+                         "scales! $player\'s $type gains 10% effectiveness."));
+        }
+        else {
+            chanmsg(clog("The local wizard imbued $player\'s pants with a ".
+                         "Spirit of Fortitude! $player\'s $type gains 10% ".
+                         "effectiveness."));
+        }
+        my $suffix="";
+        if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
+        $rps{$player}{item}{$type} = int(itemlevel($rps{$player}{item}{$type}) * 1.1);
+        $rps{$player}{item}{$type}.=$suffix;
+    }
+    else {
+        my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
+        my $actioned;
+        if (!open(Q,$opts{eventsfile})) {
+            return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+        }
+        my $i;
+        while (my $line = <Q>) {
+            chomp($line);
+            if ($line =~ /^G (.*)/ && rand(++$i) < 1) {
+                $actioned = $1;
+            }
+        }
+        chanmsg(clog("$player $actioned! This wondrous godsend has ".
+                     "accelerated them ".duration($time)." towards level ".
+                     ($rps{$player}{level}+1)."."));
+        $rps{$player}{next} -= $time;
+        chanmsg("$player reaches next level in ".duration($rps{$player}{next}).
+                ".");
+    }
+}
+
+sub quest {
+    @{$quest{questers}} = grep { $rps{$_}{online} && $rps{$_}{level} > 39 &&
+                                 time()-$rps{$_}{lastlogin}>36000 } keys(%rps);
+    if (@{$quest{questers}} < 4) { return undef(@{$quest{questers}}); }
+    while (@{$quest{questers}} > 4) {
+        splice(@{$quest{questers}},int(rand(@{$quest{questers}})),1);
+    }
+    if (!open(Q,$opts{eventsfile})) {
+        return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
+    }
+    my $i;
+    while (my $line = <Q>) {
+        chomp($line);
+        if ($line =~ /^Q/ && rand(++$i) < 1) {
+            if ($line =~ /^Q1 (.*)/) {
+                $quest{text} = $1;
+                $quest{type} = 1;
+                $quest{qtime} = time() + 43200 + int(rand(43201)); # 12-24 hours
+            }
+            elsif ($line =~ /^Q2 (\d+) (\d+) (\d+) (\d+) (.*)/) {
+                $quest{p1} = [$1,$2];
+                $quest{p2} = [$3,$4];
+                $quest{text} = $5;
+                $quest{type} = 2;
+                $quest{stage} = 1;
+            }
+        }
+    }
+    close(Q);
+    if ($quest{type} == 1) {
+        chanmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                "$quest{questers}->[3] have been chosen by the gods to ".
+                "$quest{text}. Quest to end in ".duration($quest{qtime}-time()).
+                ".");    
+    }
+    elsif ($quest{type} == 2) {
+        chanmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
+                "$quest{questers}->[3] have been chosen by the gods to ".
+                "$quest{text}. Participants must first reach [$quest{p1}->[0],".
+                "$quest{p1}->[1]], then [$quest{p2}->[0],$quest{p2}->[1]].".
+                ($opts{mapurl}?" See $opts{mapurl} to monitor their journey's ".
+                "progress.":""));
+    }
+    writequestfile();
+}
+
+sub questpencheck {
+    my $k = shift;
+    my ($quester,$player);
+    for $quester (@{$quest{questers}}) {
+        if ($quester eq $k) {
+            chanmsg(clog("$k\'s prudence and self-regard has brought the ".
+                         "wrath of the gods upon the realm. All your great ".
+                         "wickedness makes you as it were heavy with lead, ".
+                         "and to tend downwards with great weight and ".
+                         "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 * 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;
+        }
+    }
+}
+
+sub clog {
+    my $mesg = shift;
+    open(B,">>$opts{modsfile}") or do {
+        debug("Error: Cannot open $opts{modsfile}: $!");
+        chanmsg("Error: Cannot open $opts{modsfile}: $!");
+        return $mesg;
+    };
+    print B ts()."$mesg\n";
+    close(B);
+    return $mesg;
+}
+
+sub backup() {
+    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());
+    }
+}
+
+sub penalize {
+    my $username = shift;
+    return 0 if !defined($username);
+    return 0 if !exists($rps{$username});
+    my $type = shift;
+    my $pen = 0;
+    questpencheck($username);
+    if ($type eq "quit") {
+        $pen = int(20 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_quit}+=$pen;
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "nick") {
+        my $newnick = shift;
+        $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);
+        $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(@_) * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_mesg}+=$pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               $type.".",$rps{$username}{nick});
+    }
+    elsif ($type eq "part") {
+        $pen = int(200 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_part}+=$pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               "parting.",$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "kick") {
+        $pen = int(250 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_kick}+=$pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               "being kicked.",$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    elsif ($type eq "logout") {
+        $pen = int(20 * penttl($rps{$username}{level}) / $opts{rpbase});
+        if ($opts{limitpen} && $pen > $opts{limitpen}) {
+            $pen = $opts{limitpen};
+        }
+        $rps{$username}{pen_logout} += $pen;
+        notice("Penalty of ".duration($pen)." added to your timer for ".
+               "LOGOUT command.",$rps{$username}{nick});
+        $rps{$username}{online}=0;
+    }
+    $rps{$username}{next} += $pen;
+    return 1; # successfully penalized a user! woohoo!
+}
+
+sub debug {
+    (my $text = shift) =~ s/[\r\n]//g;
+    my $die = shift;
+    if ($opts{debug} || $opts{verbose}) {
+        open(DBG,">>$opts{debugfile}") or do {
+            chanmsg("Error: Cannot open debug file: $!");
+            return;
+        };
+        print DBG ts()."$text\n";
+        close(DBG);
+    }
+    if ($die) { die("$text\n"); }
+    return $text;
+}
+
+sub finduser {
+    my $nick = shift;
+    return undef if !defined($nick);
+    for my $user (keys(%rps)) {
+        next unless $rps{$user}{online};
+        if ($rps{$user}{nick} eq $nick) { return $user; }
+    }
+    return undef;
+}
+
+sub ha { # return 0/1 if username has access
+    my $user = shift;
+    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;
+    }
+    return $rps{$user}{isadmin};
+}
+
+sub checksplits { # removed expired split hosts from the hash
+    my $host;
+    while ($host = each(%split)) {
+        if (time()-$split{$host}{time} > $opts{splitwait}) {
+            $rps{$split{$host}{account}}{online} = 0;
+            delete($split{$host});
+        }
+    }
+}
+
+sub collision_fight {
+    my($u,$opp) = @_;
+    my $mysum = itemsum($u,1);
+    my $oppsum = itemsum($opp,1);
+    my $myroll = int(rand($mysum));
+    my $opproll = int(rand($oppsum));
+    if ($myroll >= $opproll) {
+        my $gain = int($rps{$opp}{level}/4);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] has come upon $opp [$opproll/$oppsum".
+                     "] and taken them in combat! ".duration($gain)." is ".
+                     "removed from $u\'s clock."));
+        $rps{$u}{next} -= $gain;
+        chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
+        if (rand(35) < 1 && $opp ne $primnick) {
+            $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
+            chanmsg(clog("$u has dealt $opp a Critical Strike! ".
+                         duration($gain)." is added to $opp\'s clock."));
+            $rps{$opp}{next} += $gain;
+            chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).
+                    ".");
+        }
+        elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
+            my @items = ("ring","amulet","charm","weapon","helm","tunic",
+                         "pair of gloves","set of leggings","shield",
+                         "pair of boots");
+            my $type = $items[rand(@items)];
+            if (itemlevel($rps{$opp}{item}{$type}) > itemlevel($rps{$u}{item}{$type})) {
+                chanmsg("In the fierce battle, $opp dropped his level ".
+                        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};
+                $rps{$opp}{item}{$type} = $tempitem;
+            }
+        }
+    }
+    else {
+        my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
+        $gain = 7 if $gain < 7;
+        $gain = int(($gain/100)*$rps{$u}{next});
+        chanmsg(clog("$u [$myroll/$mysum] has come upon $opp [$opproll/$oppsum".
+                     "] and been defeated in combat! ".duration($gain)." is ".
+                     "added to $u\'s clock."));
+        $rps{$u}{next} += $gain;
+        chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
+    }
+}
+
+sub writequestfile {
+    return unless $opts{writequestfile};
+    open(QF,">$opts{questfilename}") or do {
+        chanmsg("Error: Cannot open $opts{questfilename}: $!");
+        return;
+    };
+    # if no active quest, just empty questfile. otherwise, write it
+    if (@{$quest{questers}}) {
+        if ($quest{type}==1) {
+            print QF "T $quest{text}\n".
+                     "Y 1\n".
+                     "S $quest{qtime}\n".
+                     "P1 $quest{questers}->[0]\n".
+                     "P2 $quest{questers}->[1]\n".
+                     "P3 $quest{questers}->[2]\n".
+                     "P4 $quest{questers}->[3]\n";
+        }
+        elsif ($quest{type}==2) {
+            print QF "T $quest{text}\n".
+                     "Y 2\n".
+                     "S $quest{stage}\n".
+                     "P $quest{p1}->[0] $quest{p1}->[1] $quest{p2}->[0] ".
+                        "$quest{p2}->[1]\n".
+                     "P1 $quest{questers}->[0] $rps{$quest{questers}->[0]}{x} ".
+                         "$rps{$quest{questers}->[0]}{y}\n".
+                     "P2 $quest{questers}->[1] $rps{$quest{questers}->[1]}{x} ".
+                         "$rps{$quest{questers}->[1]}{y}\n".
+                     "P3 $quest{questers}->[2] $rps{$quest{questers}->[2]}{x} ".
+                         "$rps{$quest{questers}->[2]}{y}\n".
+                     "P4 $quest{questers}->[3] $rps{$quest{questers}->[3]}{x} ".
+                         "$rps{$quest{questers}->[3]}{y}\n";
+        }
+    }
+    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);
+    return unless @players > 1;
+    splice(@players,int(rand(@players)),1) while @players > 2;
+    my $gain = 5 + int(rand(8));
+    chanmsg(clog("$players[0] and $players[1] have not let the iniquities of ".
+                 "evil men poison them. Together have they prayed to their ".
+                 "god, and it is his light that now shines upon them. $gain\% ".
+                 "of their time is removed from their clocks."));
+    $rps{$players[0]}{next} = int($rps{$players[0]}{next}*(1 - ($gain/100)));
+    $rps{$players[1]}{next} = int($rps{$players[1]}{next}*(1 - ($gain/100)));
+    chanmsg("$players[0] reaches next level in ".
+            duration($rps{$players[0]}{next}).".");
+    chanmsg("$players[1] reaches next level in ".
+            duration($rps{$players[1]}{next}).".");
+}
+
+sub evilness {
+    my @evil = grep { $rps{$_}{alignment} eq "e" &&
+                      $rps{$_}{online} } keys(%rps);
+    return unless @evil;
+    my $me = $evil[rand(@evil)];
+    if (int(rand(2)) < 1) {
+        # evil only steals from good :^(
+        my @good = grep { $rps{$_}{alignment} eq "g" &&
+                          $rps{$_}{online} } keys(%rps);
+        my $target = $good[rand(@good)];
+        my @items = ("ring","amulet","charm","weapon","helm","tunic",
+                     "pair of gloves","set of leggings","shield",
+                     "pair of boots");
+        my $type = $items[rand(@items)];
+        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 ".
+                         itemlevel($rps{$me}{item}{$type})." $type while they were ".
+                         "sleeping! $me leaves his old level ".
+                         itemlevel($rps{$target}{item}{$type})." $type behind, ".
+                         "which $target then takes."));
+        }
+        else {
+            notice("You made to steal $target\'s $type, but realized it was ".
+                   "lower level than your own. You creep back into the ".
+                   "shadows.",$rps{$me}{nick});
+        }
+    }
+    else { # being evil only pays about half of the time...
+        my $gain = 1 + int(rand(5));
+        chanmsg(clog("$me is forsaken by his evil god. ".
+                     duration(int($rps{$me}{next} * ($gain/100)))." is added ".
+                     "to his clock."));
+        $rps{$me}{next} = int($rps{$me}{next} * (1 + ($gain/100)));
+        chanmsg("$me reaches next level in ".duration($rps{$me}{next}).".");
+    }
+}
+
+sub writedb {
+    open(RPS,">$opts{dbfile}") or do {
+        chanmsg("ERROR: Cannot write $opts{dbfile}: $!");
+        return 0;
+    };
+    print RPS join("\t","# username",
+                        "pass",
+                        "is admin",
+                        "level",
+                        "class",
+                        "next ttl",
+                        "nick",
+                        "userhost",
+                        "online",
+                        "idled",
+                        "x pos",
+                        "y pos",
+                        "pen_mesg",
+                        "pen_nick",
+                        "pen_part",
+                        "pen_kick",
+                        "pen_quit",
+                        "pen_quest",
+                        "pen_logout",
+                        "created",
+                        "last login",
+                        "amulet",
+                        "charm",
+                        "helm",
+                        "boots",
+                        "gloves",
+                        "ring",
+                        "leggings",
+                        "shield",
+                        "tunic",
+                        "weapon",
+                        "alignment")."\n";
+    my $k;
+    keys(%rps); # reset internal pointer
+    while ($k=each(%rps)) {
+        if (exists($rps{$k}{next}) && defined($rps{$k}{next})) {
+            print RPS join("\t",$k,
+                                $rps{$k}{pass},
+                                $rps{$k}{isadmin},
+                                $rps{$k}{level},
+                                $rps{$k}{class},
+                                $rps{$k}{next},
+                                $rps{$k}{nick},
+                                $rps{$k}{userhost},
+                                $rps{$k}{online},
+                                $rps{$k}{idled},
+                                $rps{$k}{x},
+                                $rps{$k}{y},
+                                $rps{$k}{pen_mesg},
+                                $rps{$k}{pen_nick},
+                                $rps{$k}{pen_part},
+                                $rps{$k}{pen_kick},
+                                $rps{$k}{pen_quit},
+                                $rps{$k}{pen_quest},
+                                $rps{$k}{pen_logout},
+                                $rps{$k}{created},
+                                $rps{$k}{lastlogin},
+                                $rps{$k}{item}{amulet},
+                                $rps{$k}{item}{charm},
+                                $rps{$k}{item}{helm},
+                                $rps{$k}{item}{"pair of boots"},
+                                $rps{$k}{item}{"pair of gloves"},
+                                $rps{$k}{item}{ring},
+                                $rps{$k}{item}{"set of leggings"},
+                                $rps{$k}{item}{shield},
+                                $rps{$k}{item}{tunic},
+                                $rps{$k}{item}{weapon},
+                                $rps{$k}{alignment})."\n";
+        }
+    }
+    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 {
+    if (! -e ".irpg.conf") {
+        debug("Error: Cannot find .irpg.conf. Copy it to this directory, ".
+              "please.",1);
+    }
+    else {
+        open(CONF,"<.irpg.conf") or do {
+            debug("Failed to open config file .irpg.conf: $!",1);
+        };
+        my($line,$key,$val);
+        while ($line=<CONF>) {
+            next() if $line =~ /^#/; # skip comments
+            $line =~ s/[\r\n]//g;
+            $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; }
+            if ($key eq "die") {
+                die("Please edit the file .irpg.conf to setup your bot's ".
+                    "options. Also, read the README file if you haven't ".
+                    "yet.\n");
+            }
+            elsif ($key eq "server") { push(@{$opts{servers}},$val); }
+            elsif ($key eq "okurl") { push(@{$opts{okurl}},$val); }
+            else { $opts{$key} = $val; }
+        }
+    }
+}
Index: /irpg/trunk/bot/events.txt
===================================================================
--- /irpg/trunk/bot/events.txt	(revision 1444)
+++ /irpg/trunk/bot/events.txt	(revision 1444)
@@ -0,0 +1,71 @@
+C was bitten by drdink
+C fell into a hole
+C bit their tongue
+C set themself on fire
+C ate a poisonous fruit
+C lost their mind
+C died, temporarily..
+C was caught in a terrible snowstorm
+C EXPLODED, somewhat..
+C got knifed in a dark alley
+C saw an episode of Ally McBeal
+C got turned INSIDE OUT, practically
+C ate a very disagreeable fruit, getting a terrible case of heartburn
+C met up with a mob hitman for not paying his bills
+C has fallen ill with the black plague
+C was struck by lightning
+C was attacked by a rabid cow
+C was attacked by a rabid wolverine
+C was set on fire
+C was decapitated, temporarily..
+C was tipped by a cow
+C was bucked from a horse
+C was bitten by a møøse
+C was sat on by a giant
+C ate a plate of discounted, day-old sushi
+C got harassed by peer
+C got lost in the woods
+C misplaced his map
+C broke his compass
+C lost his glasses
+C walked face-first into a tree
+G found a pair of Nikes
+G caught a unicorn
+G discovered a secret, underground passage
+G was taught to run quickly by a secret tribe of pygmies that know how to, among other things, run quickly
+G discovered caffeinated coffee
+G grew an extra leg
+G was visited by a very pretty nymph
+G found kitten
+G learned Perl
+G found an exploit in the IRPG code
+G tamed a wild horse
+G found a one-time-use spell of quickness
+G bought a faster computer
+G bribed the local IRPG administrator
+G stopped using dial-up
+G invented the wheel
+G gained a sixth sense
+G got a kiss from drwiii
+G had his clothes laundered by a passing fairy
+G was rejuvenated by drinking from a magic stream
+G was bitten by a radioactive spider
+G hit it off with a drunk sorority chick named Jenny
+G was accepted into Pi Beta Phi
+Q2 225 415 280 460 lay waste to the Towers of Ankh-Allor, wherein lies the terrible sorceror Croocq
+Q1 locate the centuries-lost tomes of the grim prophet Haplashak Mhadhu
+Q2 400 475 480 380 explore and chart the dark lands of T'rnalvph
+Q1 locate the ancient writings of Ahmo, prophet of the blind god Io, namely his last and hidden work, Time as Deity, thought to answer all of mankind's greater wonders
+Q2 290 65 325 270 slay the great and horrible troll, Dokt'r Wiii
+Q2 480 415 325 270 return the stolen relics of Iao-Sabao to the city of Velvragh, quieting the religious riot that has sprung up from their loss
+Q2 70 315 325 270 guard the secret passage to Bharash until the full moon has passed, and the evil returned to its resting place
+Q2 50 350 325 270 destroy the bandits terrorizing the roads passing through the Great Shahlil mountains
+Q1 locate and destroy the immensely powerful Eyeless Amulet of the evil sorceress, Ankh B'loht
+Q2 167 458 325 270 rescue the beautiful princess Juliet from the grasp of the beast Grabthul
+Q1 locate the herbs and brew the elixir to rid the realm of the Normonic Plague
+Q2 160 480 160 380 hunt down the over-abundance of mountain wolves that are slaying the regions' cows
+Q2 35 40 325 270 assassinate the general, Ronald Ashur, of the invading army of Denmark
+Q2 235 125 430 60 setup a trade route through the mountains to the neighboring land of Qwok and arrange correspondence with their leader, Cuincey-Love Vikk'l
+Q2 155 155 325 270 live among and learn the ancient magick of the tribe of pygmie people, the Jow Botzi
+Q2 70 125 170 100 kill the resurrected Jow Botzian zombies produced by a young wizard's wayward spell
+Q1 worship the sacred Cow until such time as she is satiated
Index: /irpg/trunk/bot/eventsnainwak.txt
===================================================================
--- /irpg/trunk/bot/eventsnainwak.txt	(revision 1444)
+++ /irpg/trunk/bot/eventsnainwak.txt	(revision 1444)
@@ -0,0 +1,75 @@
+C a ete mordu par un chapin enrage
+C s est fait aspire par un bouglou à sens giratoire inverse
+C est traumatise : un coup de vent a emporte son bonnet
+C visitait MdS lorsqu il a glisse et est tombe dans une riviere de lave
+C a mange un papillon avarie
+C a errer dans Anarchy World et y a perdu la raison
+C est mort...temporairement
+C vient de repeter l'experience revelatrice de Newton mais avec un pot de fleur a la place de la pomme
+C a explose avec son X wing
+C s est fait agresse par un rampant dans une sombre allee de Ville 
+C ne sait pas rappeler qu il ne savait pas nager avant de glisser vers Water World
+C s est retrouve seul face a un rouleau compresseur des guildes ennemis
+C a bu de l eau : son estomac habitue uniquement a la biere n a pas supporte
+C vient de se faire tuer par un gars de son camp qui etait jaloux de sa caltos
+C etait en train de nettoyer son Desert Eagle quand le coup est parti tout seul
+C a ete frappe par un eclair de Schlavbeuk
+C a oublie que lorsqu on lance un boomerang, il revient
+C s est perdu en s aventurant sur Lumiere sans lampe de poche
+C aurait du faire le plein de sa fusee nasale avant de partir pour Espace
+C vient d etre decapite...temporairement
+C a voulu faire une cascade avec sa Ford Torino mais il ne s appelle pas Starky ou Hutch
+C s est pris une amende pour vitesse trop lente sur Naindianapolis
+C maladroit a la batte, a envoye son adversaire dans un bunker au lieu du trou n°8...il perd un tour
+C s est fait agresser par une bande de holligans saouls sur Foot World
+C a fait une overdose d EPO...il est immediatement transporte sur Eau-Pital
+C se fait ecraser par un geant
+C avait oublie sa gourde de biere avant de partir explorer le desert...Fatale erreur
+C est victime d une sortie de route a Monainco
+C tombe sur le repaire des vamps sur Naine World qui en font leur esclave
+C vient de perdre sa Desansis en pariant au Puits Sans Squat
+C Riorim ednom ua sirpmoc neir a n
+G vient de s anabolyser !
+G a trouve une F1 verte...VROUM ! VROUM !
+G a perce le mystere d Origine
+G a suivi un stage d entrainement intensif pour participer aux Jeux Nainlympiques
+G a trouve a trouve un Kine d'Heure Surprise...Vite vite la surprise !
+G fait mumuse avec sa main collante de gosse et gagne un bonus
+G a gagne le quizz IRC sur Nainwak
+G fait sortir un chapin de son chapeau de magicien (libre a lui d en faire ce qu il veut)
+G etait connecte lors de la derniere pluie d objets
+G a rencontre son ame soeur a Pacific
+G a trouve un passage secret dans la Forteresse et trouver le tresor cache
+G passe des vacances tranquilles avec son amster a Awai
+G a trouve une brouette spatio temporelle et en a profite pour se teleporter
+G a rencontre son ami Mario (plombier de son etat) qui lui a file le bon tuyau pour sortir de Pipe World
+G est un grand explorateur et a reussi a sortir de la Foret de (Perceuse) Amazonienne indemne
+G a longtemps voyage sur Echiquier et est devenu un Grand Maitre
+G a trouve le coffre de Nain Pitoyable le Rouge, le celebre pirate de l Ile aux Tresors
+G a trouve l Oasis au milieu du Dessert
+G a gagne le tournoi de Ronain Graou
+G est un pro de la Game Boy : Tetris n a pas de secret pour lui !
+G a profite de la tournee generale sur Biere World
+G a fume le calumet de la paix avec les Naindiens
+G est arrive au bout de l Arc en Ciel et a trouve le chaudron rempli d or
+Q2 225 415 280 460 trouve le dernier objet qui lui manque pour completer sa Naincyclopedie
+Q1 trouve les livres sacres de fabrication de la biere de Nain Bibe le celebre Aubergiste
+Q2 400 475 480 380 allume la Lumiere et decouvre le monde secret qui s'y cacher
+Q1 gagne le grand prix de Naindianapolis
+Q2 290 65 325 270 ramene Bubulle chez lui
+Q2 480 415 325 270 retrouve les combinaisons de Bionains
+Q2 70 315 325 270 retrouve le Badge de Starflot et crie "SCOOTY BEAM ME UP !"
+Q2 50 350 325 270 tue le GMM sur Monster Park
+Q1 repeins son monde natal de la bonne couleur pour faire plaisir a Schlavbeuk
+Q2 167 458 325 270 nettoie Ville de ses dechets
+Q1 ramene l otage sur son monde
+Q2 160 480 160 380 fabrique un nouveau Goldorak pour aider son camp
+Q2 35 40 325 270 rassemble les 7 Dragon Balls et invoque le Dragon sacré !
+Q2 235 125 430 60 mene son camp a la victoire lors d'un affrontement de plus de 400 nains et est couvert de gloire
+Q2 155 155 325 270 trouve l inutile ultime : le poil de cul magique !
+Q2 70 125 170 100 resoud la super mega quete ultra mysterieuse qui n est pas encore implementee tellement secrete que les admins ne la connaissaient meme pas !
+Q1 tue sa cible
+
+
+
+
Index: /irpg/trunk/bot/fr_NW.po
===================================================================
--- /irpg/trunk/bot/fr_NW.po	(revision 1444)
+++ /irpg/trunk/bot/fr_NW.po	(revision 1444)
@@ -0,0 +1,957 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-01-09 23:14+0100\n"
+"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n>1;\n"
+
+#: bot.v3.1.2.pl:151
+#, perl-format
+msgid ""
+"%s does not appear to exist. I'm guessing this is your first time using "
+"IRPG. Please give an account name that you would like to have admin access [%"
+"s]: "
+msgstr ""
+"Le fichier %s n'existe pas. Je suppose que c'est votre première utilisation "
+"de IRPG. Merci de donner le nom du compte qui aura les droit "
+"d'administration [%s]"
+
+#: bot.v3.1.2.pl:157
+msgid "Enter a character class for this account: "
+msgstr "Entrez la profession du personnage de ce compte : "
+
+#: bot.v3.1.2.pl:160
+msgid "Enter a password for this account: "
+msgstr "Entre le mot de passe de ce compte : "
+
+#: bot.v3.1.2.pl:192
+#, perl-format
+msgid "OK, wrote you into %s \n"
+msgstr ""
+
+#: bot.v3.1.2.pl:197
+msgid ""
+"Checking for updates...\n"
+"\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:209
+msgid "There is an update available! Changes include:\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:213
+#, perl-format
+msgid "You are running the latest version (v%s).\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:221
+#, perl-format
+msgid ""
+"\n"
+"Get the newest version from %s!\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:227
+msgid "Could not connect to update server."
+msgstr ""
+
+#: bot.v3.1.2.pl:230
+msgid "Becoming a daemon..."
+msgstr ""
+
+#: bot.v3.1.2.pl:359 bot.v3.1.2.pl:1119
+#, perl-format
+msgid "%s, the level %u %s, is now online from nickname %s. Next level in %s."
+msgstr ""
+"%s, le %3$s de niveau %2$u, est joué par %4$s. Prochain niveau dans %5$s"
+
+#: bot.v3.1.2.pl:364
+#, perl-format
+msgid "Logon successful. Next level in %s."
+msgstr "Identification réussie. Prochain niveau dans %s."
+
+#: bot.v3.1.2.pl:425
+#, perl-format
+msgid "%u users matching %u hosts automatically logged in; accounts: "
+msgstr ""
+"%u joueurs correspondant à %u hôtes ont été identifiés automatiquement; Il "
+"s'agit de : "
+
+#: bot.v3.1.2.pl:432
+#, perl-format
+msgid "%u users matching %u hosts automatically logged in."
+msgstr ""
+"%u joueurs correspondant à %u hôtes ont été identifiés automatiquement."
+
+#: bot.v3.1.2.pl:450
+msgid "0 users qualified for auto login."
+msgstr "Aucun joueur n'as été identifié automatiquement"
+
+#: bot.v3.1.2.pl:480 bot.v3.1.2.pl:618 bot.v3.1.2.pl:637 bot.v3.1.2.pl:656
+#: bot.v3.1.2.pl:675 bot.v3.1.2.pl:697 bot.v3.1.2.pl:708 bot.v3.1.2.pl:719
+#: bot.v3.1.2.pl:738 bot.v3.1.2.pl:761 bot.v3.1.2.pl:780
+#, perl-format
+msgid "You don't have access to %s."
+msgstr "Vous n'avez pas accès à %s."
+
+#: bot.v3.1.2.pl:498 bot.v3.1.2.pl:1090
+#, perl-format
+msgid "Sorry, you are already online as %s."
+msgstr "Désolé, vous êtes déjà identifié en tant que %s."
+
+#: bot.v3.1.2.pl:503
+msgid "Try: REGISTER <char name> <password> <class>"
+msgstr "Essayez : REGISTER <nom du personnage> <mot de passe> <classe>"
+
+#: bot.v3.1.2.pl:505
+msgid "IE : REGISTER Poseidon MyPassword God of the Sea"
+msgstr "exemple : REGISTER Poseidon Monmotdepasse Roi des océans"
+
+#: bot.v3.1.2.pl:509
+msgid ""
+"Sorry, new accounts may not be registered while the bot is in pause mode; "
+"please wait a few minutes and try again."
+msgstr ""
+"Désolé, il n'est pas possible de créer de nouveaux personnages pendant que "
+"le bot est en mode pause. Merci de réessayer dans quelques minutes."
+
+#: bot.v3.1.2.pl:516
+msgid "Sorry, that character name is already in use."
+msgstr "Désolé, il existe déjà un personnage portant ce nom."
+
+#: bot.v3.1.2.pl:521
+msgid "Sorry, that character name cannot be registered."
+msgstr "Désolé, il n'est pas possible de réserver ce nom de personnage."
+
+#: bot.v3.1.2.pl:525 bot.v3.1.2.pl:1104
+#, perl-format
+msgid "Sorry, you're not in %s."
+msgstr "Désolé, vous n'êtes pas dans %s."
+
+#: bot.v3.1.2.pl:529
+msgid "Sorry, character names must be < 17 and > 0 chars long."
+msgstr ""
+"Désolé, le nom des personnages doivent avoir entre 1 et 16 caractères de long"
+
+#: bot.v3.1.2.pl:533
+msgid "Sorry, character names may not begin with #."
+msgstr "Désolé, il n'est pas possible de débuter un nom de personnage par #."
+
+#: bot.v3.1.2.pl:537
+msgid "Sorry, character names may not include character \\001."
+msgstr ""
+"Désolé, il n'est pas possible d'utiliser \\001 dans un nom de personnage."
+
+#: bot.v3.1.2.pl:542
+msgid "Sorry, neither character names nor classes may include control codes."
+msgstr ""
+"Désolé, ni le nom du personnage, ni sa profession ne doivent comprendre de "
+"caractères de contrôle."
+
+#: bot.v3.1.2.pl:548
+msgid ""
+"Sorry, neither character names nor classes may include non-printable chars."
+msgstr ""
+"Désolé, il n'est pas possible d'utiliser des caractères non-visibles ni dans "
+"le nom du personnage, ni dans sa profession."
+
+#: bot.v3.1.2.pl:553
+msgid "Sorry, character classes must be < 31 chars long."
+msgstr ""
+"Désolé, la profession d'un personnage ne peut avoir plus de 30 caractères."
+
+#: bot.v3.1.2.pl:557
+msgid "Wait 1 second and try again."
+msgstr "Attendez une seconde et réessayez."
+
+#: bot.v3.1.2.pl:589
+#, perl-format
+msgid "Welcome %s's new player %s, the %s! Next level in %s."
+msgstr ""
+"Bienvenue à %2$s le %3$s personnage de %1$s! Prochain niveau dans %4$s."
+
+#: bot.v3.1.2.pl:593
+#, perl-format
+msgid ""
+"Success! Account %s created. You have %u seconds idleness until you reach "
+"level 1."
+msgstr ""
+"Réussite! Le personnage %s est créé. Vous devrez attendre %u secondes avant "
+"d'atteindre le niveau 1."
+
+#: bot.v3.1.2.pl:597
+msgid ""
+"NOTE: The point of the game is to see who can idle the longest. As such, "
+"talking in the channel, parting, quitting, and changing nicks all penalize "
+"you."
+msgstr ""
+"NOTE : Le but du jeu est de voir qui peut rester à rien faire le plus "
+"longtemps. Aussi, parler dans le salon, le quitter, se déconnecter et "
+"changer de pseudo vous pénalise."
+
+#: bot.v3.1.2.pl:623
+msgid "Try: DELOLD <# of days>"
+msgstr "Essayez: DELOLD <# de jours>"
+
+#: bot.v3.1.2.pl:630
+#, perl-format
+msgid "%u accounts not accessed in the last %u days removed by %s."
+msgstr ""
+"%u personnages qui n'ont pas été joués ces %u derniers jours ont été "
+"supprimés par %s."
+
+#: bot.v3.1.2.pl:641
+msgid "Try: DEL <char name>"
+msgstr "Essayez: DEL <nom de personnage>"
+
+#: bot.v3.1.2.pl:644 bot.v3.1.2.pl:663 bot.v3.1.2.pl:682
+#, perl-format
+msgid "No such account %s."
+msgstr "Le personnage %s n'existe pas."
+
+#: bot.v3.1.2.pl:649
+#, perl-format
+msgid "Account %s removed by %s."
+msgstr "Le personnage %s a été supprimé par %s."
+
+#: bot.v3.1.2.pl:660
+msgid "Try: MKADMIN <char name>"
+msgstr "Essayez: MKADMIN <nom de personnage>"
+
+#: bot.v3.1.2.pl:668
+#, perl-format
+msgid "Account %s is now a bot admin."
+msgstr "Le personnage %s est maintenant un administrateur du bot."
+
+#: bot.v3.1.2.pl:679
+msgid "Try: DELADMIN <char name>"
+msgstr "Essayez: DELADMIN <nom de personnage>"
+
+#: bot.v3.1.2.pl:686
+msgid "Cannot DELADMIN owner account."
+msgstr "Il n'est pas possible de supprimer le personnage du propriétaire."
+
+#: bot.v3.1.2.pl:691
+#, perl-format
+msgid "Account %s is no longer a bot admin."
+msgstr "Le personnage %s n'est plus un administrateur du bot."
+
+#: bot.v3.1.2.pl:701
+#, perl-format
+msgid "%s has summoned the Hand of God."
+msgstr "%s a invoqué la main de Schlavbeuk."
+
+#: bot.v3.1.2.pl:713
+msgid "Reread config file."
+msgstr "Relecture du fichier de configuration."
+
+#: bot.v3.1.2.pl:723
+msgid "Try: CHPASS <char name> <new pass>"
+msgstr "Essayez: CHPASS <nom de personnage> <nouveau mot de passe>"
+
+#: bot.v3.1.2.pl:727 bot.v3.1.2.pl:746 bot.v3.1.2.pl:769 bot.v3.1.2.pl:789
+#, perl-format
+msgid "No such username %s."
+msgstr "Le personnage %s n'existe pas."
+
+#: bot.v3.1.2.pl:732
+#, perl-format
+msgid "Password for %s changed."
+msgstr "Le mot de passe de %s a été changé."
+
+#: bot.v3.1.2.pl:742
+msgid "Try: CHUSER <char name> <new char name>"
+msgstr "Essayez: CHUSER <nom de personnage> <nouveau nom>"
+
+#: bot.v3.1.2.pl:750
+#, perl-format
+msgid "Username %s is already taken."
+msgstr "Il existe déjà un personnage nomé %s."
+
+#: bot.v3.1.2.pl:755
+#, perl-format
+msgid "Username for %s changed to %s."
+msgstr "Le personnage %s a changé de nom pour %s."
+
+#: bot.v3.1.2.pl:765
+msgid "Try: CHCLASS <char name> <new char class>"
+msgstr "Essayez: CHCLASS <nom du personnage> <nouvelle profession>"
+
+#: bot.v3.1.2.pl:774
+#, perl-format
+msgid "Class for %s changed to %s."
+msgstr "La profession de %s est maintenant %s."
+
+#: bot.v3.1.2.pl:785
+msgid "Try: PUSH <char name> <seconds>"
+msgstr "Essayez: PUSH <nom de personnage> <secondes>"
+
+#: bot.v3.1.2.pl:793
+#, perl-format
+msgid "Time to level for %s (%u s) is lower than %d; setting TTL to 0."
+msgstr ""
+"La durée avant passage de niveau pour %s (%u s) est plus courte que %d; La "
+"durée de passage de niveau est mise à 0."
+
+#: bot.v3.1.2.pl:797
+#, perl-format
+msgid "%s has pushed %s %d seconds toward level %u"
+msgstr "%s a propulsé %s %d secondes vers le niveau %u"
+
+#: bot.v3.1.2.pl:805
+#, perl-format
+msgid ""
+"%s has pushed %s %d seconds toward level %u. %2$s reaches next level in %5$s."
+msgstr ""
+"%s a propulsé %s %d secondes vers le niveau %u. %2$s atteindra le prochain "
+"niveau dans %5$s."
+
+#: bot.v3.1.2.pl:818 bot.v3.1.2.pl:849 bot.v3.1.2.pl:874 bot.v3.1.2.pl:886
+#: bot.v3.1.2.pl:898 bot.v3.1.2.pl:915
+msgid "You are not logged in."
+msgstr "Vous n'êtes pas identifié."
+
+#: bot.v3.1.2.pl:823
+msgid "There is no active quest."
+msgstr "Il n'y a pas de quête en cours."
+
+#: bot.v3.1.2.pl:826
+#, perl-format
+msgid "%s, and %s are on a quest to %s. Quest to complete in %s."
+msgstr ""
+"%s et %s sont en train de faire la quête consistant à %s. Cette quête se "
+"terminera dans %s."
+
+#: bot.v3.1.2.pl:834
+#, perl-format
+msgid ""
+"%s, and %s are on a quest to %s. Participants must first reach [%u,%u], then "
+"[%u,%u]."
+msgstr ""
+"%s et %s participent à la quête consistant à %s. Il doivent pour cela se "
+"rendre [%u,%u] et puis ensuite en [%u,%u]."
+
+#: bot.v3.1.2.pl:841 bot.v3.1.2.pl:2327
+#, perl-format
+msgid " See %s to monitor their journey's progress."
+msgstr " Consultez %s pour suivre leur avancement dans cette quête."
+
+#: bot.v3.1.2.pl:853
+msgid "No such user."
+msgstr "Cet utilisateur n'existe pas."
+
+#: bot.v3.1.2.pl:877
+#, perl-format
+msgid "You are %s, the level %u %s. Next level in %s"
+msgstr "Vous êtes %s, %3s de niveau %2u. Prochain niveau dans %s"
+
+#: bot.v3.1.2.pl:889
+msgid "Try: NEWPASS <new password>"
+msgstr "Essayez: NEWPASS <nouveau mot de passe>"
+
+#: bot.v3.1.2.pl:893
+msgid "Your password was changed."
+msgstr "Votre mot de passe a été changé."
+
+#: bot.v3.1.2.pl:902
+msgid "Try: ALIGN <good|neutral|evil>"
+msgstr "Essayez: ALIGN <good|neutral|evil>"
+
+#: bot.v3.1.2.pl:907
+#, perl-format
+msgid "%s has changed alignment to: "
+msgstr "%s a changé de coté pour : "
+
+#: bot.v3.1.2.pl:909
+msgid "Your alignment was changed to "
+msgstr "Vous avez changé de coté pour "
+
+#: bot.v3.1.2.pl:918
+#, perl-format
+msgid "Account %s removed."
+msgstr "Le personnage %s est supprimé."
+
+#: bot.v3.1.2.pl:920
+#, perl-format
+msgid "%s removed his account, %s, the %s."
+msgstr "%s a supprimé son personnage %s, le %s."
+
+#: bot.v3.1.2.pl:928
+#, perl-format
+msgid "For information on IRPG bot commands, see %s"
+msgstr "Pour avoir des information sur les commandes du bot IRPG, consultez %s"
+
+#: bot.v3.1.2.pl:933
+#, perl-format
+msgid "Help URL is %s"
+msgstr "L'URL de l'aide est %s"
+
+#: bot.v3.1.2.pl:935
+#, perl-format
+msgid "Admin commands URL is %s"
+msgstr "L'URL des commandes pour les administrateurs est %s"
+
+#: bot.v3.1.2.pl:941 bot.v3.1.2.pl:952 bot.v3.1.2.pl:968 bot.v3.1.2.pl:980
+#: bot.v3.1.2.pl:991 bot.v3.1.2.pl:1005 bot.v3.1.2.pl:1023 bot.v3.1.2.pl:1035
+#: bot.v3.1.2.pl:1059
+#, perl-format
+msgid "You do not have access to %s."
+msgstr "Vous n'avez pas accès à %s."
+
+#: bot.v3.1.2.pl:947
+#, perl-format
+msgid "QUIT :DIE from %s"
+msgstr "QUIT :DIE de la part de %s"
+
+#: bot.v3.1.2.pl:956
+msgid "ERROR: Can only use LOADDB while in PAUSE mode."
+msgstr "ERREUR: Il n'est possible d'utiliser LOADDB qu'en mode PAUSE."
+
+#: bot.v3.1.2.pl:961
+#, perl-format
+msgid "Reread player database file; %u accounts loaded."
+msgstr "Relecture de la base de donnée des joueurs; %u comptes chargés."
+
+#: bot.v3.1.2.pl:973
+#, perl-format
+msgid "%s copied to .dbbackup/%s"
+msgstr "%s copié vers .dbbackup/%s"
+
+#: bot.v3.1.2.pl:985
+#, perl-format
+msgid "PAUSE_MODE set to %s."
+msgstr "PAUSE_MODE mis à %s."
+
+#: bot.v3.1.2.pl:995
+msgid "Try: SILENT <mode>"
+msgstr "Essayez: SILENT <mode>"
+
+#: bot.v3.1.2.pl:999
+#, perl-format
+msgid "SILENT_MODE set to %s."
+msgstr "SILENT-MODE mis à %s."
+
+#: bot.v3.1.2.pl:1009
+msgid "Try: JUMP <server[:port]>"
+msgstr "Essayez: JUMP <serveur[:port]>"
+
+#: bot.v3.1.2.pl:1013
+#, perl-format
+msgid "QUIT :JUMP to %s from %s"
+msgstr "QUIT :JUMP vers %s par %s"
+
+#: bot.v3.1.2.pl:1028
+#, perl-format
+msgid "QUIT :RESTART from %s"
+msgstr "QUIT :RESTART par %s"
+
+#: bot.v3.1.2.pl:1040
+#, perl-format
+msgid "Outgoing message queue cleared by %s."
+msgstr "File de messages en sortie vidée par %s."
+
+#: bot.v3.1.2.pl:1042
+msgid "Outgoing message queue cleared."
+msgstr "File de messages en sortie vidée."
+
+#: bot.v3.1.2.pl:1049
+#, perl-format
+msgid ""
+"IRPG bot v%s by jotun, http://idlerpg.net/. On via server: %s. Admins "
+"online: %s."
+msgstr ""
+"bot IRPG v%s par jotun, http://idlerpg.net/. hébergé sur le serveur: %s."
+"Administrateurs connectés: %s."
+
+#: bot.v3.1.2.pl:1066
+#, perl-format
+msgid ""
+"%.2fkb sent, %.2fkb received in %s. %d IRPG users online of %d total users. %"
+"d accounts created since startup. PAUSE_MODE is %d, SILENT_MODE is %d. "
+"Outgoing queue is %d bytes in %d items. On via: %s. Admins online: %s."
+msgstr ""
+"%.2fkb envoyés, %.2fkb reçus en %s. %d personnages IRPG actifs pour un total "
+"de %d personnages. %d personnages créés depuis le lancement. PAUSE_MODE est %"
+"d, SILENT_MODE est %d. La file de sortie fait %d octets pour %d items. Accès "
+"par %s. Les administrateurs actifs sont: %s."
+
+#: bot.v3.1.2.pl:1095
+msgid "Try: LOGIN <username> <password>"
+msgstr "Essayez: LOGIN <nom de personnage> <mot de passe>"
+
+#: bot.v3.1.2.pl:1099
+msgid ""
+"Sorry, no such account name. Note that account names are case sensitive."
+msgstr ""
+"Désolé aucun personnage ne porte ce nom. Notez que les noms de personnages "
+"sont sensibles à la casse."
+
+#: bot.v3.1.2.pl:1109
+msgid "Wrong password."
+msgstr "Mauvais mot de passe."
+
+#: bot.v3.1.2.pl:1125
+msgid "Logon successful. Next level in "
+msgstr "Identification réussie. Prochain niveau dans "
+
+#: bot.v3.1.2.pl:1224
+#, perl-format
+msgid "%d day, %02d:%02d:%02d"
+msgid_plural "%d days, %02d:%02d:%02d"
+msgstr[0] "%d jour, %02dh%02dm%02ds"
+msgstr[1] "%d jours, %02dh%02dm%02ds"
+
+#: bot.v3.1.2.pl:1241
+#, perl-format
+msgid ""
+"Verily I say unto thee, the Heavens have burst forth, and the blessed hand "
+"of God carried %s %s toward level %u."
+msgstr ""
+"En vérité, je vous le dis, le monde des braves s'est agrandi, et la main "
+"bénie de Schlavbeuk a poussé %s %s vers le niveau %u."
+
+#: bot.v3.1.2.pl:1248
+#, perl-format
+msgid ""
+"Thereupon He stretched out His little finger among them and consumed %s with "
+"fire, slowing the heathen %s from level %u."
+msgstr ""
+
+#: bot.v3.1.2.pl:1255 bot.v3.1.2.pl:1477 bot.v3.1.2.pl:1488 bot.v3.1.2.pl:1519
+#: bot.v3.1.2.pl:2206 bot.v3.1.2.pl:2278 bot.v3.1.2.pl:2518 bot.v3.1.2.pl:2526
+#: bot.v3.1.2.pl:2556 bot.v3.1.2.pl:2648 bot.v3.1.2.pl:2650 bot.v3.1.2.pl:2693
+#, perl-format
+msgid "%s reaches next level in %s."
+msgstr "%s atteindra le prochain niveau dans %s."
+
+#: bot.v3.1.2.pl:1298
+#, fuzzy, perl-format
+msgid ""
+"%s, and %s have blessed the realm by completing their quest! 25%% of their "
+"burden is eliminated."
+msgstr ""
+"%s et %s ont réjouis leur courgette en complétant leur quête! 25% de leur "
+"fardeau est éliminé."
+
+#: bot.v3.1.2.pl:1315
+msgid "Idle RPG Top Players:"
+msgstr "Meilleurs joueurs de Idle RPG :"
+
+#: bot.v3.1.2.pl:1318
+#, perl-format
+msgid "%s, the level %u %s, is #%u! Next level in %s."
+msgstr ""
+"%s le %3$s de niveau %2$u est classé n°%4$u! Il atteindra le prochain niveau "
+"dans %5$s."
+
+#: bot.v3.1.2.pl:1345
+msgid "WARNING: Cannot write database in PAUSE mode!"
+msgstr "ATTENTION: Impossible d'écrire dans la base de donnée en mode PAUSE !"
+
+#: bot.v3.1.2.pl:1363
+#, perl-format
+msgid "%s, the %s, has attained level %u! Next level in %s."
+msgstr "%s le %s a atteint le niveau %u! Prochain niveau dans %s."
+
+#: bot.v3.1.2.pl:1471
+#, perl-format
+msgid ""
+"%s [%u/%u] has challenged %s [%u/%u] in combat and won! %s is removed from %1"
+"$s's clock."
+msgstr ""
+"%s [%u/%u] a défié %s [%u/%u] en combat et a gagné! %s est retiré du "
+"compteur de %1$s."
+
+#: bot.v3.1.2.pl:1484 bot.v3.1.2.pl:2522
+#, perl-format
+msgid "%s has dealt %s a Critical Strike! %s is added to %2$s's clock."
+msgstr "%s a placé un coup critique sur %s! %s est ajouté au compteur de %2$s."
+
+#: bot.v3.1.2.pl:1497 bot.v3.1.2.pl:2534
+#, perl-format
+msgid ""
+"In the fierce battle, %s dropped his level %u %s! %s picks it up, tossing "
+"his old level %u %3$s to %1$s."
+msgstr ""
+"Durant la bataille acharnée, %s a laché son %3$s de niveau %2$u! %4$s l'a "
+"ramassé, jettant son ancien %6$s de niveau %5$u à %1$s."
+
+#: bot.v3.1.2.pl:1514
+#, perl-format
+msgid ""
+"%s [%u/%u] has challenged %s [%u/%u] in combat and lost! %s is added to %1"
+"$s's clock."
+msgstr ""
+"%s [%u/%u] a défié %s [%u/%u] en combat et a perdu! %s est ajouté au "
+"compteur de %1$s."
+
+#: bot.v3.1.2.pl:1562
+#, perl-format
+msgid ""
+"%s, %s and %s [%u/%u] have team battled %s, %s and %s [%u/%u] at [%u,%u] and "
+"won! %s is removed from their clocks."
+msgstr ""
+"%s, %s et %s [%u/u] ont fait une bataille en rang contre %s, %s et %s [%u/%"
+"u] en [%u,%u] et ont gagné! %s est retiré de leur compteur."
+
+#: bot.v3.1.2.pl:1573
+#, perl-format
+msgid ""
+"%s, %s and %s [%u/%u] have team battled %s, %s and %s [%u/%u] at [%u,%u] and "
+"lost! %s is added to their clocks."
+msgstr ""
+"%s, %s et %s [%u/u] ont fait une bataille en rang contre %s, %s et %s [%u/%"
+"u] en [%u,%u] et ont perdu! %s est ajouté à leur compteur."
+
+#: bot.v3.1.2.pl:1642
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Mattt's Omniscience Grand Crown! Your enemies fall before you as you "
+"anticipate their every move."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez le Casque de "
+"niveau %u de Hunnin Avherty Envodeux le Viking Nain ! Vous anticipez les "
+"mouvements de tous vos ennemis et vous en debarrassez aisément."
+
+#: bot.v3.1.2.pl:1649
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Res0's Protectorate Plate Mail! Your enemies cower in fear as their attacks "
+"have no effect on you."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la Combinaison "
+"de Bionain Caméléon de niveau %u. Plus rien ne vous atteint avec cette "
+"protection intégrale (jusqu'à la coquille en titane pour les parties "
+"sensibles)."
+
+#: bot.v3.1.2.pl:1656
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Dwyn's Storm Magic Amulet! Your enemies are swept away by anelemental fury "
+"before the war has even begun"
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la Patate "
+"Nucléaire de niveau %u. Grâce à sa portée hors du commun, plus personne "
+"n'échappe à votre puissance"
+
+#: bot.v3.1.2.pl:1663
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Jotun's Fury Colossal Sword! Your enemies' hatred is broughtto a quick end "
+"as you arc your wrist, dealing the crushing blow."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez le pistolet à "
+"rétro-propulsion plasmatique de SPOOK de niveau %u. Vous envoyez tous vos "
+"ennemis valdaguer ailleurs voir si vous y êtes."
+
+#: bot.v3.1.2.pl:1670
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Drdink's Cane of Blind Rage! Your enemies are tossed aside as you blindly "
+"swing your arm around hitting stuff."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la calculette "
+"scientifique en or rétro-éclairée TI-9239073195 de niveau %u. Avec elle vous "
+"vous sentez capable de découvrir la valeur exacte de Pi et donnez mal à la "
+"tête à vos ennemis faibles en math."
+
+#: bot.v3.1.2.pl:1677
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Mrquick's Magical Boots of Swiftness! Your enemies are left choking on your "
+"dust as you run from them very, very quickly."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez le légendaire "
+"Mega giga super gros coup de pied au cul de niveau %u. Vous commencez à "
+"infliger la douleur suprême à tous les derrières de vos ennemis."
+
+#: bot.v3.1.2.pl:1684
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Jeff's Cluehammer of Doom! Your enemies are left with a sudden and intense "
+"clarity of mind... even as you relieve them of it."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la brouette "
+"spatio-temporelle édition limitée Einst-Nain lui même de niveau %u. Avec ça "
+"vous pouvez attaquer et fuir avant même que vos ennemis réagissent."
+
+#: bot.v3.1.2.pl:1691
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Juliet's Glorious Ring of Sparkliness! You enemies are blinded by both its "
+"glory and their greed as you bring desolation upon them."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez Le glaive de "
+"Schlavbeuk de niveau %u. C'est l'ultime glaive : il a été béni par un prêtre "
+"dans le vomi sacré de Schlavbeuk après une autre soirée trop arrosée."
+
+#: bot.v3.1.2.pl:1699
+#, perl-format
+msgid ""
+"You found a level %u %s! Your current %2$s is only level %u, so it seems "
+"Luck is with you!"
+msgstr ""
+"Vous trouvez un %2$s de niveau %1$u! votre %2$s actuel est seulement de "
+"niveau %3$u, il semble donc que la chance soit avec vous!"
+
+#: bot.v3.1.2.pl:1782
+#, perl-format
+msgid ""
+"You found a level %u %s. Your current %2$s is level %3$u, so it seems Luck "
+"is against you. You toss the %2$s."
+msgstr ""
+"Vous trouvez un %2$s de niveau %1$u. Votre %2$s actuel est de niveau %3$u, "
+"il semble donc que la chance soit contre vous. Vous jettez le %2$s."
+
+#: bot.v3.1.2.pl:1902
+#, fuzzy, perl-format
+msgid ""
+"%s, and %s have completed their journey! 25%% of their burden is eliminated."
+msgstr "%s et %s ont terminé leur périple! 25% de leur charge est supprimé."
+
+#: bot.v3.1.2.pl:1934 bot.v3.1.2.pl:1992
+#, perl-format
+msgid "%s encounters %s and bows humbly."
+msgstr ""
+"%s a recontré un bord du monde en %s et a changé de direction, tout penaud."
+
+#: bot.v3.1.2.pl:2151
+#, perl-format
+msgid ""
+"%s fell, chipping the stone in his amulet! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2157
+#, perl-format
+msgid ""
+"%s slipped and dropped his charm in a dirty bog! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2163
+#, perl-format
+msgid ""
+"%s left his weapon out in the rain to rust! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2169
+#, perl-format
+msgid ""
+"%s spilled a level 7 shrinking potion on his tunic! %1$s's %s loses 10%% of "
+"its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2175
+#, perl-format
+msgid ""
+"%s's shield was damaged by a dragon's fiery breath! %1$s's %s loses 10%% of "
+"its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2181
+#, perl-format
+msgid ""
+"%s burned a hole through his leggings while ironing them! %1$s's %s loses 10%"
+"% of its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2201
+#, perl-format
+msgid "%s %s. This terrible calamity has slowed them %s from level %u."
+msgstr "%s %s. Cette terrible calamité l'a retardé de %s vers le niveau %u."
+
+#: bot.v3.1.2.pl:2220
+#, perl-format
+msgid ""
+"%s's %s was blessed by a passing cleric! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2226
+#, perl-format
+msgid "%s's %s ate a bolt of lightning! %1$s's %2$s gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2232
+#, perl-format
+msgid "%s sharpened the edge of his %s! %1$s's %2$s gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2238
+#, perl-format
+msgid ""
+"A magician cast a spell of Rigidity on %s's %s! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2244
+#, perl-format
+msgid ""
+"%s reinforced his %s with a dragon's scales! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2250
+#, perl-format
+msgid ""
+"The local wizard imbued %s's pants with a Spirit of Fortitude! %1$s's %2$s "
+"gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2273
+#, perl-format
+msgid "%s %s! This wondrous godsend has accelerated them %s towards level %u."
+msgstr ""
+"%s %s! Ce merveilleux don de Schlavbeuk l'a propulsé de %s vers le niveau %u."
+
+#: bot.v3.1.2.pl:2313
+#, perl-format
+msgid "%s, and %s have been chosen by the gods to %s. Quest to end in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:2320
+#, perl-format
+msgid ""
+"%s, and %s have been chosen by the gods to %s. Participants must first reach "
+"[%u,%u], then [%u,%u]."
+msgstr ""
+"%s et %s ont été choisis par Schlavbeuk pour %s. Les participants doivent "
+"tout d'abord se rendre en [%u,%u] et enfin en [%u,%u]."
+
+#: bot.v3.1.2.pl:2340
+#, perl-format
+msgid ""
+"%s's prudence and self-regard has brought the wrath of the gods upon the "
+"realm. All your great wickedness makes you as it were heavy with lead, and "
+"to tend downwards with great weight and pressure towards hell. Therefore "
+"have you drawn yourselves 15 steps closer to that gaping maw."
+msgstr ""
+
+#: bot.v3.1.2.pl:2408
+#, perl-format
+msgid "Penalty of %s added to your timer for nick change."
+msgstr ""
+"Une pénalité de %s est ajoutée à votre compteur pour changement de surnom."
+
+#: bot.v3.1.2.pl:2417
+#, perl-format
+msgid "Penalty of %s added to your timer for %s."
+msgstr "Une pénalité de %s est ajoutée à votre compteur pour %s"
+
+#: bot.v3.1.2.pl:2426
+#, perl-format
+msgid "Penalty of %s added to your timer for parting."
+msgstr ""
+"Une pénalité de %s est ajoutée à votre compteur pour avoir quitté le salon."
+
+#: bot.v3.1.2.pl:2436
+#, perl-format
+msgid "Penalty of %s added to your timer for being kicked."
+msgstr "Une pénalité de %s est ajoutée à votre compteur pour avoir été kické."
+
+#: bot.v3.1.2.pl:2446
+#, perl-format
+msgid "Penalty of %s added to your timer for LOGOUT command."
+msgstr ""
+"Une pénalité de %s est ajoutée à votre compteur pour avoir utilisé la "
+"commande LOGOUT."
+
+#: bot.v3.1.2.pl:2512
+#, perl-format
+msgid ""
+"%s [%u/%u] has come upon %s [%u/%u] and taken them in combat! %s is removed "
+"from %1$s's clock."
+msgstr ""
+"%s [%u/%u] est venu au devant de %s [%u/%u] et l'a ridiculisé au combat! %s "
+"est retiré du compteur de %1$s."
+
+#: bot.v3.1.2.pl:2550
+#, perl-format
+msgid ""
+"%s [%u/%u] has come upon %s [%u/%u] and been defeated in combat! %s is added "
+"to %1$s's clock."
+msgstr ""
+"%s [%u/%u] est venu au devant de %s [%u/%u] et s'est fait éclaté au combat! %"
+"s est ajouté au compteur de %1$s."
+
+#: bot.v3.1.2.pl:2641
+#, perl-format
+msgid ""
+"%s and %s have not let the iniquities of evil men poison them. Together have "
+"they prayed to their god, and it is his light that now shines upon them. %u%"
+"% of their time is removed from their clocks."
+msgstr ""
+
+#: bot.v3.1.2.pl:2672
+#, perl-format
+msgid ""
+"%s stole %s's level %u %s while they were sleeping! %1$s leaves his old "
+"level %u %4$s behind, which %2$s then takes."
+msgstr ""
+"%s a volé le %4$s de niveau %3$u de %2$s pendant qu'il hibernait! %1$s a "
+"laissé son ancien %4$s de niveau %5$u derrière lui et %2$s l'a pris."
+
+#: bot.v3.1.2.pl:2681
+#, perl-format
+msgid ""
+"You made to steal %s's %s, but realized it was lower level than your own. "
+"You creep back into the shadows."
+msgstr ""
+"Vous alliez voler le %2$s de $1$s, mais vous vous êtes apperçus que son "
+"niveau était plus faible que le votre. Vous retournez dans l'ombre."
+
+#: bot.v3.1.2.pl:2689
+#, perl-format
+msgid "%s is forsaken by his evil god. %s is added to his clock."
+msgstr "%s est reniée par Satan. %s est ajouté à son compteur."
+
+#~ msgid "ring"
+#~ msgstr "inutile"
+
+#~ msgid "amulet"
+#~ msgstr "jouet de Schlavbeuk"
+
+#~ msgid "charm"
+#~ msgstr "bouffe"
+
+#~ msgid "weapon"
+#~ msgstr "arme de snipe"
+
+#~ msgid "helm"
+#~ msgstr "rune d'intelligence"
+
+#~ msgid "tunic"
+#~ msgstr "rune de vie"
+
+#~ msgid "pair of gloves"
+#~ msgstr "arme de bourrin"
+
+#~ msgid "set of leggings"
+#~ msgstr "rune de force"
+
+#~ msgid "shield"
+#~ msgstr "rune de précision"
+
+#~ msgid "pair of boots"
+#~ msgstr "véhicule"
+
+#~ msgid "good"
+#~ msgstr "Brave"
+
+#~ msgid "neutral"
+#~ msgstr "Nain-déci"
+
+#~ msgid "evil"
+#~ msgstr "Sadique"
Index: /irpg/trunk/bot/irpg.db
===================================================================
--- /irpg/trunk/bot/irpg.db	(revision 1444)
+++ /irpg/trunk/bot/irpg.db	(revision 1444)
@@ -0,0 +1,2 @@
+# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
+DT	mYUNQtOnudT.s	1	0	bot	680	DT	DT!~tereutes@DT.registered.netrusk.net	1	120	253	435	0	0	200	0	0	0	0	1136636702	1136744418	0	0	0	0	0	0	0	0	0	0	n
Index: /irpg/trunk/bot/irpgdbtool
===================================================================
--- /irpg/trunk/bot/irpgdbtool	(revision 1444)
+++ /irpg/trunk/bot/irpgdbtool	(revision 1444)
@@ -0,0 +1,469 @@
+# IRPG db conversion tool; converts db version 2.4 -> 3.0
+# Jon Honeycutt, jotun@idlerpg.net, http://idlerpg.net
+# Free for all use, public and private, with retention of copyright notice.
+
+use strict;
+use IO::Socket;
+
+my %rps = ();
+my $temp;
+
+$|=1;
+
+print "\nIRPG db conversion tool; version 2.4 -> 3.0\n\n";
+
+do {
+    print "Read from file [irpg.db]: ";
+    chomp($temp=<STDIN>);
+    $temp ||= "irpg.db";
+    if (! -e $temp) { print "Error: No such file\n"; }
+} until (-e $temp);
+
+loaddb($temp);
+
+print "Loaded ".scalar(keys(%rps))." accounts from $temp.\n";
+
+do {
+    print "\nBackup old irpg.db file? [yes]: ";
+    chomp($temp=<STDIN>);
+    $temp||="yes";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    do {
+        print "\nBackup filename [irpg.db.old]: ";
+        chomp($temp=<STDIN>);
+        $temp||="irpg.db.old";
+    } until (defined($temp));
+    open(RPS,">$temp") or die("Cannot write $temp: $!");
+    print RPS "# username\tpass\tlevel\tclass\tnext\tnick\tuserhost\tonline\t".
+              "idled\tpen_mesg\tpen_nick\tpen_part\tpen_kick\tpen_quit\t".
+              "pen_quest\tpen_logout\tcreated\tlast login\tamulet\tcharm\t".
+              "helm\tboots\tgloves\tring\tleggings\tshield\ttunic\tweapon\n";
+    for my $k (keys %rps) {
+        print RPS join("\t",
+          $k,
+          $rps{$k}{pass},
+          $rps{$k}{level},
+          $rps{$k}{class},
+          $rps{$k}{next},
+          $rps{$k}{nick}||"",
+          $rps{$k}{userhost}||"",
+          $rps{$k}{online}||0,
+          $rps{$k}{idled}||0,
+          $rps{$k}{pen_mesg}||0,
+          $rps{$k}{pen_nick}||0,
+          $rps{$k}{pen_part}||0,
+          $rps{$k}{pen_kick}||0,
+          $rps{$k}{pen_quit}||0,
+          $rps{$k}{pen_quest}||0,
+          $rps{$k}{pen_logout}||0,
+          $rps{$k}{created},
+          $rps{$k}{lastlogin},
+          $rps{$k}{item}{amulet}||0,
+          $rps{$k}{item}{charm}||0,
+          $rps{$k}{item}{helm}||0,
+          $rps{$k}{item}{"pair of boots"}||0,
+          $rps{$k}{item}{"pair of gloves"}||0,
+          $rps{$k}{item}{ring}||0,
+          $rps{$k}{item}{"set of leggings"}||0,
+          $rps{$k}{item}{shield}||0,
+          $rps{$k}{item}{tunic}||0,
+          $rps{$k}{item}{weapon}||0)."\n";
+    }
+    close(RPS);
+    print "Wrote $temp.\n";
+}
+
+do {
+    print "\nReset all user levels to 0, all times to level to 0, all items ".
+          "to 0, all penalties to 0, all online flags to 0, all idled times ".
+          "to 0, all creation dates and last login times to today (i.e., ".
+          "reset game)? [no]: ";
+    chomp($temp=<STDIN>);
+    $temp||="no";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    for my $k (keys(%rps)) {
+        $rps{$k}{next}=0;
+        $rps{$k}{level}=0;
+        $rps{$k}{online}=0;
+        $rps{$k}{idled}=0;
+        $rps{$k}{item}{amulet}=0;
+        $rps{$k}{item}{charm}=0;
+        $rps{$k}{item}{helm}=0;
+        $rps{$k}{item}{"pair of boots"}=0;
+        $rps{$k}{item}{"pair of gloves"}=0;
+        $rps{$k}{item}{ring}=0;
+        $rps{$k}{item}{"set of leggings"}=0;
+        $rps{$k}{item}{shield}=0;
+        $rps{$k}{item}{tunic}=0;
+        $rps{$k}{item}{weapon}=0;
+        $rps{$k}{pen_mesg}=0;
+        $rps{$k}{pen_nick}=0;
+        $rps{$k}{pen_part}=0;
+        $rps{$k}{pen_kick}=0;
+        $rps{$k}{pen_quit}=0;
+        $rps{$k}{pen_quest}=0;
+        $rps{$k}{pen_logout}=0;
+        $rps{$k}{created}=time();
+        $rps{$k}{lastlogin}=time();
+    }
+    print "Game reset.\n";
+}
+
+do {
+    print "\nStrip all control codes from character names and classes? [no]: ";
+    chomp($temp=<STDIN>);
+    $temp ||="no";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    my(@usernames,@classes);
+    for my $k (keys(%rps)) {
+        if ($k =~ /[[:cntrl:]]/) {
+            my $newusername = $k;
+            $newusername =~ s/[[:cntrl:]]//g;
+            if (exists($rps{$newusername}) || !defined($newusername) ||
+                !length($newusername)) {
+                print "\nError: While trying to strip control codes from $k, ".
+                      "found stripped version ($newusername) already exists ".
+                      "in database or is undefined. Skipping this user, so ".
+                      "sorry.\n";
+            }
+            else {
+                $rps{$newusername}=delete($rps{$k});
+                push(@usernames,"$k is now: $newusername");
+                $k = $newusername;
+            }
+        }
+        if ($rps{$k}{class} =~ /[[:cntrl:]]/) {
+            $rps{$k}{class} =~ s/[[:cntrl:]]//g;
+            push(@classes,"$k is now: $rps{$k}{class}");
+        }
+    }
+    if (@usernames) {
+        print "\nUsernames changed (would be good to alert these users):\n";
+        print "User $_\n" for @usernames;
+        print "\n";
+    }
+    if (@classes) {
+        print "\nClass names changed (might be good to alert these users):\n";
+        print "User $_\n" for @classes;
+        print "\n";
+    }
+}
+
+do {
+    print "\nStrip all non-printable characters from character names and ".
+          "classes? [no]: ";
+    chomp($temp=<STDIN>);
+    $temp ||="no";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    my(@usernames,@classes);
+    for my $k (keys(%rps)) {
+        if ($k =~ /[[:^print:]]/) {
+            my $newusername = $k;
+            $newusername =~ s/[[:^print:]]//g;
+            if (exists($rps{$newusername}) || !defined($newusername) ||
+                !length($newusername)) {
+                print "\nError: While trying to strip non-printable chars ".
+                      "from $k, found stripped version ($newusername) already ".
+                      "exists in database or is undefined. Skipping this ".
+                      "user, so sorry.\n";
+            }
+            else {
+                $rps{$newusername}=delete($rps{$k});
+                push(@usernames,"$k is now: $newusername");
+                $k = $newusername;
+            }
+        }
+        if ($rps{$k}{class} =~ /[[:^print:]]/) {
+            $rps{$k}{class} =~ s/[[:^print:]]//g;
+            push(@classes,"$k\'s class is now: $rps{$k}{class}");
+        }
+    }
+    if (@usernames) {
+        print "\nUsernames changed (would be good to alert these users):\n";
+        print "User $_\n" for @usernames;
+        print "\n";
+    }
+    if (@classes) {
+        print "\nClass names changed (might be good to alert these users):\n";
+        print "User $_\n" for @classes;
+        print "\n";
+    }
+}
+
+do {
+    print "\nVersion 3.0 supports 'named items,' or a method of marking ".
+          "unique items as being unique. Attempt to name existing items that ".
+          "are known uniques? [yes]: ";
+    chomp($temp=<STDIN>);
+    $temp ||="yes";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    for my $k (keys(%rps)) {
+        for my $item (keys(%{$rps{$k}{item}})) {
+            if ($rps{$k}{item}{$item} > int(1.5*$rps{$k}{level})) {
+                if ($item eq "helm") {
+                    print "$k\'s $item named as Mattt's Omniscience.\n";
+                    $rps{$k}{item}{$item} .= "a";
+                }
+                elsif ($item eq "tunic") {
+                    print "$k\'s $item named as Res0's Protectorate.\n";
+                    $rps{$k}{item}{$item} .= "b";
+                }
+                elsif ($item eq "amulet") {
+                    print "$k\'s $item named as Dwyn's Storm.\n";
+                    $rps{$k}{item}{$item} .= "c";
+                }
+                elsif ($item eq "weapon" && $rps{$k}{item}{$item} < 175) {
+                    print "$k\'s $item named as Jotun's Fury.\n";
+                    $rps{$k}{item}{$item} .= "d";
+                }
+                elsif ($item eq "weapon" && $rps{$k}{item}{$item} > 175 &&
+                       $rps{$k}{item}{$item} < 201) {
+                    print "$k\'s $item named as Drdink's Cane of Blind Rage.\n";
+                    $rps{$k}{item}{$item} .= "e";
+                }
+                else {
+                    print "$k has unknown unique of level ".
+                          "$rps{$k}{item}{$item}.\n";
+                }
+            }
+        }
+    }
+}
+
+do {
+    print "\nThere exist new items in version 3.0 that some of your clients ".
+          "may already have had the chance to find. I.E., there is a new item ".
+          "with a required level of 48. Simulate an item find for all users ".
+          "above 48 for this and other new items to make the game fair for ".
+          "older users? [yes]: ";
+    chomp($temp=<STDIN>);
+    $temp ||="yes";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    for my $k (keys(%rps)) {
+        if ($rps{$k}{level} >= 48) {
+            for (48..$rps{$k}{level}) {
+                # approximately equal to normal item find, i believe
+                if (rand(100) < 2.25) {
+                    my $ulevel = 250+int(rand(51));
+                    if ($ulevel > int($rps{$k}{item}{"pair of boots"})) {
+                        print "$k found level $ulevel Mrquick's Magical Boots ".
+                              "of Swiftness.\n";
+                        $rps{$k}{item}{"pair of boots"} = $ulevel."f";
+                    }
+                }
+            }
+        }
+        if ($rps{$k}{level} >= 52) {
+            for (52..$rps{$k}{level}) {
+                # approximately equal to normal item find, i believe
+                if (rand(100) < 2.15) {
+                    my $ulevel = 300+int(rand(51));
+                    if ($ulevel > int($rps{$k}{item}{weapon})) {
+                        print "$k found level $ulevel Jeff's Cluehammer of ".
+                              "Doom.\n";
+                        $rps{$k}{item}{weapon} = $ulevel."g";
+                    }
+                }
+            }
+        }
+        if ($rps{$k}{level} >= 25) {
+            for (25..$rps{$k}{level}) {
+                # approximately equal to normal item find, i believe
+                if (rand(100) < 2.43) {
+                    my $ulevel = 50+int(rand(25));
+                    if ($ulevel > int($rps{$k}{item}{ring})) {
+                        print "$k found level $ulevel Juliet's Glorious Ring ".
+                              "of Sparkliness.\n";
+                        $rps{$k}{item}{ring} = $ulevel."h";
+                    }
+                }
+            }
+        }
+    }
+}
+
+for my $k (keys(%rps)) {
+    $rps{$k}{x} = int(rand(500));
+    $rps{$k}{y} = int(rand(500));
+    $rps{$k}{isadmin}=0;
+    $rps{$k}{alignment}="n";
+}
+
+print "\nUsernames that you would like to have admin status (separate with ".
+      "commas, use proper CaSe): ";
+chomp($temp=<STDIN>);
+$temp =~ s/\s//g;
+for my $k (split(/,/,$temp)) {
+    if (!exists($rps{$k})) {
+        print "\nError: Account name '$k' does not exist. Remember that ".
+              "account names are case sensitive. Skipping this username. Edit ".
+              "the database manually, or use the MKADMIN command after the ".
+              "bot connects to add this user.\n\n";
+    }
+    else {
+        print "$k is now admin.\n";
+        $rps{$k}{isadmin}=1;
+    }
+}
+print "\nYou can add more admins later with the MKADMIN command.\n";
+
+do {
+    print "\nWrite to new db file [irpg.db]: ";
+    chomp($temp=<STDIN>);
+    $temp ||= "irpg.db";
+} until (defined($temp));
+
+open(RPS,">$temp") or die "Cannot open $temp: $!";
+
+print RPS join("\t","# username",
+    "pass",
+    "is admin",
+    "level",
+    "class",
+    "next ttl",
+    "nick",
+    "userhost",
+    "online",
+    "idled",
+    "x pos",
+    "y pos",
+    "pen_mesg",
+    "pen_nick",
+    "pen_part",
+    "pen_kick",
+    "pen_quit",
+    "pen_quest",
+    "pen_logout",
+    "created",
+    "last login",
+    "amulet",
+    "charm",
+    "helm",
+    "boots",
+    "gloves",
+    "ring",
+    "leggings",
+    "shield",
+    "tunic",
+    "weapon",
+    "alignment")."\n";
+
+for my $k (keys(%rps)) {
+    print RPS join("\t",
+        $k,
+        $rps{$k}{pass},
+        $rps{$k}{isadmin},
+        $rps{$k}{level},
+        $rps{$k}{class},
+        $rps{$k}{next},
+        $rps{$k}{nick},
+        $rps{$k}{userhost},
+        $rps{$k}{online},
+        $rps{$k}{idled},
+        $rps{$k}{x},
+        $rps{$k}{y},
+        $rps{$k}{pen_mesg},
+        $rps{$k}{pen_nick},
+        $rps{$k}{pen_part},
+        $rps{$k}{pen_kick},
+        $rps{$k}{pen_quit},
+        $rps{$k}{pen_quest},
+        $rps{$k}{pen_logout},
+        $rps{$k}{created},
+        $rps{$k}{lastlogin},
+        $rps{$k}{item}{amulet},
+        $rps{$k}{item}{charm},
+        $rps{$k}{item}{helm},
+        $rps{$k}{item}{"pair of boots"},
+        $rps{$k}{item}{"pair of gloves"},
+        $rps{$k}{item}{ring},
+        $rps{$k}{item}{"set of leggings"},
+        $rps{$k}{item}{shield},
+        $rps{$k}{item}{tunic},
+        $rps{$k}{item}{weapon},
+        $rps{$k}{alignment})."\n";
+}
+close(RPS);
+
+do {
+    print "\nDone writing $temp! Thanks for your interest in the Idle RPG. May ".
+          "I send an (anonymous) user count to idlerpg.net? jotun is ".
+          "interested in knowing how many people play his game :^) [yes]: ";
+    chomp($temp=<STDIN>);
+    $temp||="yes";
+    $temp=lc($temp);
+} until ($temp eq "yes" || $temp eq "no");
+
+if ($temp eq "yes") {
+    print "Sending...\n";
+    my $sock = IO::Socket::INET->new(PeerAddr=>"jotun.ultrazone.org:80");
+    if ($sock) {
+        print $sock "GET /g7/count.php?converted=".scalar(keys(%rps)).
+                        " HTTP/1.1\r\n".
+                    "Host: jotun.ultrazone.org:80\r\n\r\n";
+        1 while <$sock>;
+    }
+    print "\nDone! Thanks a million! Enjoy Idle RPG. :^)\n";
+}
+else {
+    print "\nI'm setting your chance of evil HoG to 100%, then. Just kidding. ".
+          "Thanks anyway.\n";
+}
+
+sub loaddb { # load the players database
+  open(RPS,shift(@_)) or die("loaddb() failed: $!");
+  while (my $l=<RPS>) {
+    chomp $l;
+    next if $l =~ /^#/; # skip comments
+    my @i = split("\t",$l);
+    print Dumper @i if @i != 28;
+    die("Anomaly in loaddb(); line $. of database has wrong fields (".
+      scalar(@i).")") if @i != 28;
+    ($rps{$i[0]}{pass},
+    $rps{$i[0]}{level},
+    $rps{$i[0]}{class},
+    $rps{$i[0]}{next},
+    $rps{$i[0]}{nick},
+    $rps{$i[0]}{userhost},
+    $rps{$i[0]}{online},
+    $rps{$i[0]}{idled},
+    $rps{$i[0]}{pen_mesg},
+    $rps{$i[0]}{pen_nick},
+    $rps{$i[0]}{pen_part},
+    $rps{$i[0]}{pen_kick},
+    $rps{$i[0]}{pen_quit},
+    $rps{$i[0]}{pen_quest},
+    $rps{$i[0]}{pen_logout},
+    $rps{$i[0]}{created},
+    $rps{$i[0]}{lastlogin},
+    $rps{$i[0]}{item}{amulet},
+    $rps{$i[0]}{item}{charm},
+    $rps{$i[0]}{item}{helm},
+    $rps{$i[0]}{item}{"pair of boots"},
+    $rps{$i[0]}{item}{"pair of gloves"},
+    $rps{$i[0]}{item}{ring},
+    $rps{$i[0]}{item}{"set of leggings"},
+    $rps{$i[0]}{item}{shield},
+    $rps{$i[0]}{item}{tunic},
+    $rps{$i[0]}{item}{weapon}) = (@i[1..$#i]);
+  }
+  close RPS;
+}
Index: /irpg/trunk/bot/mapitems.db
===================================================================
--- /irpg/trunk/bot/mapitems.db	(revision 1444)
+++ /irpg/trunk/bot/mapitems.db	(revision 1444)
@@ -0,0 +1,1 @@
+# x pos	y pos	type	level	age
Index: /irpg/trunk/bot/messages.po
===================================================================
--- /irpg/trunk/bot/messages.po	(revision 1444)
+++ /irpg/trunk/bot/messages.po	(revision 1444)
@@ -0,0 +1,826 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-01-09 23:14+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: bot.v3.1.2.pl:151
+#, perl-format
+msgid ""
+"%s does not appear to exist. I'm guessing this is your first time using "
+"IRPG. Please give an account name that you would like to have admin access [%"
+"s]: "
+msgstr ""
+
+#: bot.v3.1.2.pl:157
+msgid "Enter a character class for this account: "
+msgstr ""
+
+#: bot.v3.1.2.pl:160
+msgid "Enter a password for this account: "
+msgstr ""
+
+#: bot.v3.1.2.pl:192
+#, perl-format
+msgid "OK, wrote you into %s \n"
+msgstr ""
+
+#: bot.v3.1.2.pl:197
+msgid ""
+"Checking for updates...\n"
+"\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:209
+msgid "There is an update available! Changes include:\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:213
+#, perl-format
+msgid "You are running the latest version (v%s).\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:221
+#, perl-format
+msgid ""
+"\n"
+"Get the newest version from %s!\n"
+msgstr ""
+
+#: bot.v3.1.2.pl:227
+msgid "Could not connect to update server."
+msgstr ""
+
+#: bot.v3.1.2.pl:230
+msgid "Becoming a daemon..."
+msgstr ""
+
+#: bot.v3.1.2.pl:359 bot.v3.1.2.pl:1119
+#, perl-format
+msgid "%s, the level %u %s, is now online from nickname %s. Next level in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:364
+#, perl-format
+msgid "Logon successful. Next level in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:425
+#, perl-format
+msgid "%u users matching %u hosts automatically logged in; accounts: "
+msgstr ""
+
+#: bot.v3.1.2.pl:432
+#, perl-format
+msgid "%u users matching %u hosts automatically logged in."
+msgstr ""
+
+#: bot.v3.1.2.pl:450
+msgid "0 users qualified for auto login."
+msgstr ""
+
+#: bot.v3.1.2.pl:480 bot.v3.1.2.pl:618 bot.v3.1.2.pl:637 bot.v3.1.2.pl:656
+#: bot.v3.1.2.pl:675 bot.v3.1.2.pl:697 bot.v3.1.2.pl:708 bot.v3.1.2.pl:719
+#: bot.v3.1.2.pl:738 bot.v3.1.2.pl:761 bot.v3.1.2.pl:780
+#, perl-format
+msgid "You don't have access to %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:498 bot.v3.1.2.pl:1090
+#, perl-format
+msgid "Sorry, you are already online as %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:503
+msgid "Try: REGISTER <char name> <password> <class>"
+msgstr ""
+
+#: bot.v3.1.2.pl:505
+msgid "IE : REGISTER Poseidon MyPassword God of the Sea"
+msgstr ""
+
+#: bot.v3.1.2.pl:509
+msgid ""
+"Sorry, new accounts may not be registered while the bot is in pause mode; "
+"please wait a few minutes and try again."
+msgstr ""
+
+#: bot.v3.1.2.pl:516
+msgid "Sorry, that character name is already in use."
+msgstr ""
+
+#: bot.v3.1.2.pl:521
+msgid "Sorry, that character name cannot be registered."
+msgstr ""
+
+#: bot.v3.1.2.pl:525 bot.v3.1.2.pl:1104
+#, perl-format
+msgid "Sorry, you're not in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:529
+msgid "Sorry, character names must be < 17 and > 0 chars long."
+msgstr ""
+
+#: bot.v3.1.2.pl:533
+msgid "Sorry, character names may not begin with #."
+msgstr ""
+
+#: bot.v3.1.2.pl:537
+msgid "Sorry, character names may not include character \\001."
+msgstr ""
+
+#: bot.v3.1.2.pl:542
+msgid "Sorry, neither character names nor classes may include control codes."
+msgstr ""
+
+#: bot.v3.1.2.pl:548
+msgid ""
+"Sorry, neither character names nor classes may include non-printable chars."
+msgstr ""
+
+#: bot.v3.1.2.pl:553
+msgid "Sorry, character classes must be < 31 chars long."
+msgstr ""
+
+#: bot.v3.1.2.pl:557
+msgid "Wait 1 second and try again."
+msgstr ""
+
+#: bot.v3.1.2.pl:589
+#, perl-format
+msgid "Welcome %s's new player %s, the %s! Next level in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:593
+#, perl-format
+msgid ""
+"Success! Account %s created. You have %u seconds idleness until you reach "
+"level 1."
+msgstr ""
+
+#: bot.v3.1.2.pl:597
+msgid ""
+"NOTE: The point of the game is to see who can idle the longest. As such, "
+"talking in the channel, parting, quitting, and changing nicks all penalize "
+"you."
+msgstr ""
+
+#: bot.v3.1.2.pl:623
+msgid "Try: DELOLD <# of days>"
+msgstr ""
+
+#: bot.v3.1.2.pl:630
+#, perl-format
+msgid "%u accounts not accessed in the last %u days removed by %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:641
+msgid "Try: DEL <char name>"
+msgstr ""
+
+#: bot.v3.1.2.pl:644 bot.v3.1.2.pl:663 bot.v3.1.2.pl:682
+#, perl-format
+msgid "No such account %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:649
+#, perl-format
+msgid "Account %s removed by %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:660
+msgid "Try: MKADMIN <char name>"
+msgstr ""
+
+#: bot.v3.1.2.pl:668
+#, perl-format
+msgid "Account %s is now a bot admin."
+msgstr ""
+
+#: bot.v3.1.2.pl:679
+msgid "Try: DELADMIN <char name>"
+msgstr ""
+
+#: bot.v3.1.2.pl:686
+msgid "Cannot DELADMIN owner account."
+msgstr ""
+
+#: bot.v3.1.2.pl:691
+#, perl-format
+msgid "Account %s is no longer a bot admin."
+msgstr ""
+
+#: bot.v3.1.2.pl:701
+#, perl-format
+msgid "%s has summoned the Hand of God."
+msgstr ""
+
+#: bot.v3.1.2.pl:713
+msgid "Reread config file."
+msgstr ""
+
+#: bot.v3.1.2.pl:723
+msgid "Try: CHPASS <char name> <new pass>"
+msgstr ""
+
+#: bot.v3.1.2.pl:727 bot.v3.1.2.pl:746 bot.v3.1.2.pl:769 bot.v3.1.2.pl:789
+#, perl-format
+msgid "No such username %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:732
+#, perl-format
+msgid "Password for %s changed."
+msgstr ""
+
+#: bot.v3.1.2.pl:742
+msgid "Try: CHUSER <char name> <new char name>"
+msgstr ""
+
+#: bot.v3.1.2.pl:750
+#, perl-format
+msgid "Username %s is already taken."
+msgstr ""
+
+#: bot.v3.1.2.pl:755
+#, perl-format
+msgid "Username for %s changed to %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:765
+msgid "Try: CHCLASS <char name> <new char class>"
+msgstr ""
+
+#: bot.v3.1.2.pl:774
+#, perl-format
+msgid "Class for %s changed to %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:785
+msgid "Try: PUSH <char name> <seconds>"
+msgstr ""
+
+#: bot.v3.1.2.pl:793
+#, perl-format
+msgid "Time to level for %s (%u s) is lower than %d; setting TTL to 0."
+msgstr ""
+
+#: bot.v3.1.2.pl:797
+#, perl-format
+msgid "%s has pushed %s %d seconds toward level %u"
+msgstr ""
+
+#: bot.v3.1.2.pl:805
+#, perl-format
+msgid ""
+"%s has pushed %s %d seconds toward level %u. %2$s reaches next level in %5$s."
+msgstr ""
+
+#: bot.v3.1.2.pl:818 bot.v3.1.2.pl:849 bot.v3.1.2.pl:874 bot.v3.1.2.pl:886
+#: bot.v3.1.2.pl:898 bot.v3.1.2.pl:915
+msgid "You are not logged in."
+msgstr ""
+
+#: bot.v3.1.2.pl:823
+msgid "There is no active quest."
+msgstr ""
+
+#: bot.v3.1.2.pl:826
+#, perl-format
+msgid "%s, and %s are on a quest to %s. Quest to complete in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:834
+#, perl-format
+msgid ""
+"%s, and %s are on a quest to %s. Participants must first reach [%u,%u], then "
+"[%u,%u]."
+msgstr ""
+
+#: bot.v3.1.2.pl:841 bot.v3.1.2.pl:2327
+#, perl-format
+msgid " See %s to monitor their journey's progress."
+msgstr ""
+
+#: bot.v3.1.2.pl:853
+msgid "No such user."
+msgstr ""
+
+#: bot.v3.1.2.pl:877
+#, perl-format
+msgid "You are %s, the level %u %s. Next level in %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:889
+msgid "Try: NEWPASS <new password>"
+msgstr ""
+
+#: bot.v3.1.2.pl:893
+msgid "Your password was changed."
+msgstr ""
+
+#: bot.v3.1.2.pl:902
+msgid "Try: ALIGN <good|neutral|evil>"
+msgstr ""
+
+#: bot.v3.1.2.pl:907
+#, perl-format
+msgid "%s has changed alignment to: "
+msgstr ""
+
+#: bot.v3.1.2.pl:909
+msgid "Your alignment was changed to "
+msgstr ""
+
+#: bot.v3.1.2.pl:918
+#, perl-format
+msgid "Account %s removed."
+msgstr ""
+
+#: bot.v3.1.2.pl:920
+#, perl-format
+msgid "%s removed his account, %s, the %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:928
+#, perl-format
+msgid "For information on IRPG bot commands, see %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:933
+#, perl-format
+msgid "Help URL is %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:935
+#, perl-format
+msgid "Admin commands URL is %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:941 bot.v3.1.2.pl:952 bot.v3.1.2.pl:968 bot.v3.1.2.pl:980
+#: bot.v3.1.2.pl:991 bot.v3.1.2.pl:1005 bot.v3.1.2.pl:1023 bot.v3.1.2.pl:1035
+#: bot.v3.1.2.pl:1059
+#, perl-format
+msgid "You do not have access to %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:947
+#, perl-format
+msgid "QUIT :DIE from %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:956
+msgid "ERROR: Can only use LOADDB while in PAUSE mode."
+msgstr ""
+
+#: bot.v3.1.2.pl:961
+#, perl-format
+msgid "Reread player database file; %u accounts loaded."
+msgstr ""
+
+#: bot.v3.1.2.pl:973
+#, perl-format
+msgid "%s copied to .dbbackup/%s"
+msgstr ""
+
+#: bot.v3.1.2.pl:985
+#, perl-format
+msgid "PAUSE_MODE set to %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:995
+msgid "Try: SILENT <mode>"
+msgstr ""
+
+#: bot.v3.1.2.pl:999
+#, perl-format
+msgid "SILENT_MODE set to %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1009
+msgid "Try: JUMP <server[:port]>"
+msgstr ""
+
+#: bot.v3.1.2.pl:1013
+#, perl-format
+msgid "QUIT :JUMP to %s from %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:1028
+#, perl-format
+msgid "QUIT :RESTART from %s"
+msgstr ""
+
+#: bot.v3.1.2.pl:1040
+#, perl-format
+msgid "Outgoing message queue cleared by %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1042
+msgid "Outgoing message queue cleared."
+msgstr ""
+
+#: bot.v3.1.2.pl:1049
+#, perl-format
+msgid ""
+"IRPG bot v%s by jotun, http://idlerpg.net/. On via server: %s. Admins "
+"online: %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1066
+#, perl-format
+msgid ""
+"%.2fkb sent, %.2fkb received in %s. %d IRPG users online of %d total users. %"
+"d accounts created since startup. PAUSE_MODE is %d, SILENT_MODE is %d. "
+"Outgoing queue is %d bytes in %d items. On via: %s. Admins online: %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1095
+msgid "Try: LOGIN <username> <password>"
+msgstr ""
+
+#: bot.v3.1.2.pl:1099
+msgid ""
+"Sorry, no such account name. Note that account names are case sensitive."
+msgstr ""
+
+#: bot.v3.1.2.pl:1109
+msgid "Wrong password."
+msgstr ""
+
+#: bot.v3.1.2.pl:1125
+msgid "Logon successful. Next level in "
+msgstr ""
+
+#: bot.v3.1.2.pl:1224
+#, perl-format
+msgid "%d day, %02d:%02d:%02d"
+msgid_plural "%d days, %02d:%02d:%02d"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bot.v3.1.2.pl:1241
+#, perl-format
+msgid ""
+"Verily I say unto thee, the Heavens have burst forth, and the blessed hand "
+"of God carried %s %s toward level %u."
+msgstr ""
+
+#: bot.v3.1.2.pl:1248
+#, perl-format
+msgid ""
+"Thereupon He stretched out His little finger among them and consumed %s with "
+"fire, slowing the heathen %s from level %u."
+msgstr ""
+
+#: bot.v3.1.2.pl:1255 bot.v3.1.2.pl:1477 bot.v3.1.2.pl:1488 bot.v3.1.2.pl:1519
+#: bot.v3.1.2.pl:2206 bot.v3.1.2.pl:2278 bot.v3.1.2.pl:2518 bot.v3.1.2.pl:2526
+#: bot.v3.1.2.pl:2556 bot.v3.1.2.pl:2648 bot.v3.1.2.pl:2650 bot.v3.1.2.pl:2693
+#, perl-format
+msgid "%s reaches next level in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1298
+#, perl-format
+msgid ""
+"%s, and %s have blessed the realm by completing their quest! 25%% of their "
+"burden is eliminated."
+msgstr ""
+
+#: bot.v3.1.2.pl:1315
+msgid "Idle RPG Top Players:"
+msgstr ""
+
+#: bot.v3.1.2.pl:1318
+#, perl-format
+msgid "%s, the level %u %s, is #%u! Next level in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1345
+msgid "WARNING: Cannot write database in PAUSE mode!"
+msgstr ""
+
+#: bot.v3.1.2.pl:1363
+#, perl-format
+msgid "%s, the %s, has attained level %u! Next level in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1471
+#, perl-format
+msgid ""
+"%s [%u/%u] has challenged %s [%u/%u] in combat and won! %s is removed from %1"
+"$s's clock."
+msgstr ""
+
+#: bot.v3.1.2.pl:1484 bot.v3.1.2.pl:2522
+#, perl-format
+msgid "%s has dealt %s a Critical Strike! %s is added to %2$s's clock."
+msgstr ""
+
+#: bot.v3.1.2.pl:1497 bot.v3.1.2.pl:2534
+#, perl-format
+msgid ""
+"In the fierce battle, %s dropped his level %u %s! %s picks it up, tossing "
+"his old level %u %3$s to %1$s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1514
+#, perl-format
+msgid ""
+"%s [%u/%u] has challenged %s [%u/%u] in combat and lost! %s is added to %1"
+"$s's clock."
+msgstr ""
+
+#: bot.v3.1.2.pl:1562
+#, perl-format
+msgid ""
+"%s, %s and %s [%u/%u] have team battled %s, %s and %s [%u/%u] at [%u,%u] and "
+"won! %s is removed from their clocks."
+msgstr ""
+
+#: bot.v3.1.2.pl:1573
+#, perl-format
+msgid ""
+"%s, %s and %s [%u/%u] have team battled %s, %s and %s [%u/%u] at [%u,%u] and "
+"lost! %s is added to their clocks."
+msgstr ""
+
+#: bot.v3.1.2.pl:1642
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Mattt's Omniscience Grand Crown! Your enemies fall before you as you "
+"anticipate their every move."
+msgstr ""
+
+#: bot.v3.1.2.pl:1649
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Res0's Protectorate Plate Mail! Your enemies cower in fear as their attacks "
+"have no effect on you."
+msgstr ""
+
+#: bot.v3.1.2.pl:1656
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Dwyn's Storm Magic Amulet! Your enemies are swept away by anelemental fury "
+"before the war has even begun"
+msgstr ""
+
+#: bot.v3.1.2.pl:1663
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Jotun's Fury Colossal Sword! Your enemies' hatred is broughtto a quick end "
+"as you arc your wrist, dealing the crushing blow."
+msgstr ""
+
+#: bot.v3.1.2.pl:1670
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Drdink's Cane of Blind Rage! Your enemies are tossed aside as you blindly "
+"swing your arm around hitting stuff."
+msgstr ""
+
+#: bot.v3.1.2.pl:1677
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Mrquick's Magical Boots of Swiftness! Your enemies are left choking on your "
+"dust as you run from them very, very quickly."
+msgstr ""
+
+#: bot.v3.1.2.pl:1684
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Jeff's Cluehammer of Doom! Your enemies are left with a sudden and intense "
+"clarity of mind... even as you relieve them of it."
+msgstr ""
+
+#: bot.v3.1.2.pl:1691
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Juliet's Glorious Ring of Sparkliness! You enemies are blinded by both its "
+"glory and their greed as you bring desolation upon them."
+msgstr ""
+
+#: bot.v3.1.2.pl:1699
+#, perl-format
+msgid ""
+"You found a level %u %s! Your current %2$s is only level %u, so it seems "
+"Luck is with you!"
+msgstr ""
+
+#: bot.v3.1.2.pl:1782
+#, perl-format
+msgid ""
+"You found a level %u %s. Your current %2$s is level %3$u, so it seems Luck "
+"is against you. You toss the %2$s."
+msgstr ""
+
+#: bot.v3.1.2.pl:1902
+#, perl-format
+msgid ""
+"%s, and %s have completed their journey! 25%% of their burden is eliminated."
+msgstr ""
+
+#: bot.v3.1.2.pl:1934 bot.v3.1.2.pl:1992
+#, perl-format
+msgid "%s encounters %s and bows humbly."
+msgstr ""
+
+#: bot.v3.1.2.pl:2151
+#, perl-format
+msgid ""
+"%s fell, chipping the stone in his amulet! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2157
+#, perl-format
+msgid ""
+"%s slipped and dropped his charm in a dirty bog! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2163
+#, perl-format
+msgid ""
+"%s left his weapon out in the rain to rust! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2169
+#, perl-format
+msgid ""
+"%s spilled a level 7 shrinking potion on his tunic! %1$s's %s loses 10%% of "
+"its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2175
+#, perl-format
+msgid ""
+"%s's shield was damaged by a dragon's fiery breath! %1$s's %s loses 10%% of "
+"its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2181
+#, perl-format
+msgid ""
+"%s burned a hole through his leggings while ironing them! %1$s's %s loses 10%"
+"% of its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2201
+#, perl-format
+msgid "%s %s. This terrible calamity has slowed them %s from level %u."
+msgstr ""
+
+#: bot.v3.1.2.pl:2220
+#, perl-format
+msgid ""
+"%s's %s was blessed by a passing cleric! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2226
+#, perl-format
+msgid "%s's %s ate a bolt of lightning! %1$s's %2$s gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2232
+#, perl-format
+msgid "%s sharpened the edge of his %s! %1$s's %2$s gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2238
+#, perl-format
+msgid ""
+"A magician cast a spell of Rigidity on %s's %s! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2244
+#, perl-format
+msgid ""
+"%s reinforced his %s with a dragon's scales! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2250
+#, perl-format
+msgid ""
+"The local wizard imbued %s's pants with a Spirit of Fortitude! %1$s's %2$s "
+"gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2273
+#, perl-format
+msgid "%s %s! This wondrous godsend has accelerated them %s towards level %u."
+msgstr ""
+
+#: bot.v3.1.2.pl:2313
+#, perl-format
+msgid "%s, and %s have been chosen by the gods to %s. Quest to end in %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:2320
+#, perl-format
+msgid ""
+"%s, and %s have been chosen by the gods to %s. Participants must first reach "
+"[%u,%u], then [%u,%u]."
+msgstr ""
+
+#: bot.v3.1.2.pl:2340
+#, perl-format
+msgid ""
+"%s's prudence and self-regard has brought the wrath of the gods upon the "
+"realm. All your great wickedness makes you as it were heavy with lead, and "
+"to tend downwards with great weight and pressure towards hell. Therefore "
+"have you drawn yourselves 15 steps closer to that gaping maw."
+msgstr ""
+
+#: bot.v3.1.2.pl:2408
+#, perl-format
+msgid "Penalty of %s added to your timer for nick change."
+msgstr ""
+
+#: bot.v3.1.2.pl:2417
+#, perl-format
+msgid "Penalty of %s added to your timer for %s."
+msgstr ""
+
+#: bot.v3.1.2.pl:2426
+#, perl-format
+msgid "Penalty of %s added to your timer for parting."
+msgstr ""
+
+#: bot.v3.1.2.pl:2436
+#, perl-format
+msgid "Penalty of %s added to your timer for being kicked."
+msgstr ""
+
+#: bot.v3.1.2.pl:2446
+#, perl-format
+msgid "Penalty of %s added to your timer for LOGOUT command."
+msgstr ""
+
+#: bot.v3.1.2.pl:2512
+#, perl-format
+msgid ""
+"%s [%u/%u] has come upon %s [%u/%u] and taken them in combat! %s is removed "
+"from %1$s's clock."
+msgstr ""
+
+#: bot.v3.1.2.pl:2550
+#, perl-format
+msgid ""
+"%s [%u/%u] has come upon %s [%u/%u] and been defeated in combat! %s is added "
+"to %1$s's clock."
+msgstr ""
+
+#: bot.v3.1.2.pl:2641
+#, perl-format
+msgid ""
+"%s and %s have not let the iniquities of evil men poison them. Together have "
+"they prayed to their god, and it is his light that now shines upon them. %u%"
+"% of their time is removed from their clocks."
+msgstr ""
+
+#: bot.v3.1.2.pl:2672
+#, perl-format
+msgid ""
+"%s stole %s's level %u %s while they were sleeping! %1$s leaves his old "
+"level %u %4$s behind, which %2$s then takes."
+msgstr ""
+
+#: bot.v3.1.2.pl:2681
+#, perl-format
+msgid ""
+"You made to steal %s's %s, but realized it was lower level than your own. "
+"You creep back into the shadows."
+msgstr ""
+
+#: bot.v3.1.2.pl:2689
+#, perl-format
+msgid "%s is forsaken by his evil god. %s is added to his clock."
+msgstr ""
Index: /irpg/trunk/bot/modifiers.txt
===================================================================
--- /irpg/trunk/bot/modifiers.txt	(revision 1444)
+++ /irpg/trunk/bot/modifiers.txt	(revision 1444)
@@ -0,0 +1,43 @@
+[01/06/06 12:09:16] faty [0/4] has challenged DT [0/2] in combat and won! 0 days, 00:00:56 is removed from faty's clock.
+[01/06/06 12:18:17] DT [4/6] has challenged Ksempac [0/1] in combat and won! 0 days, 00:01:05 is removed from DT's clock.
+[01/06/06 12:18:17] DT has dealt Ksempac a Critical Strike! 0 days, 00:00:02 is added to Ksempac's clock.
+[01/06/06 12:21:47] faty [4/5] has challenged DT [1/6] in combat and won! 0 days, 00:01:05 is removed from faty's clock.
+[01/06/06 12:32:22] Ksempac [3/8] has challenged Questmaster [3/9] in combat and won! 0 days, 00:03:07 is removed from Ksempac's clock.
+[01/06/06 13:24:07] Ksempac [11/19] has challenged DT [12/20] in combat and lost! 0 days, 00:01:42 is added to Ksempac's clock.
+[01/06/06 13:33:59] Colar [4/14] has challenged faty [18/21] in combat and lost! 0 days, 00:01:16 is added to Colar's clock.
+[01/06/06 13:50:10] Ksempac [7/30] has challenged Colar [0/14] in combat and won! 0 days, 00:01:58 is removed from Ksempac's clock.
+[01/06/06 14:38:35] skalp learned Perl! This wondrous godsend has accelerated them 0 days, 00:00:12 towards level 5.
+[01/06/06 15:19:14] faty [3/45] has challenged Ksempac [18/45] in combat and lost! 0 days, 00:03:05 is added to faty's clock.
+[01/06/06 15:39:30] Ksempac a fait une overdose d EPO...il est immediatement transporte sur Eau-Pital. This terrible calamity has slowed them 0 days, 00:01:36 from level 11.
+[01/06/06 15:40:00] Colar [24/36] has challenged Ksempac [31/47] in combat and lost! 0 days, 00:02:39 is added to Colar's clock.
+[01/06/06 15:42:18] Ethernum [1/8] has challenged faty [4/45] in combat and lost! 0 days, 00:01:05 is added to Ethernum's clock.
+[01/06/06 16:57:44] yom [10/18] has challenged Ethernum [12/29] in combat and lost! 0 days, 00:01:42 is added to yom's clock.
+[01/06/06 17:49:07] skalp [1/68] has challenged Ksempac [10/59] in combat and lost! 0 days, 00:03:34 is added to skalp's clock.
+[01/06/06 17:50:16] DT [38/102] has challenged Ksempac [13/59] in combat and won! 0 days, 00:04:49 is removed from DT's clock.
+[01/06/06 18:41:34] Ethernum [6/61] has challenged xOu [0/1] in combat and won! 0 days, 00:03:05 is removed from Ethernum's clock.
+[01/06/06 20:20:09] Colar [7/110] has challenged xOu [1/29] in combat and won! 0 days, 00:04:49 is removed from Colar's clock.
+[01/06/06 20:27:28] Tiseb [0/1] has challenged DT [30/110] in combat and lost! 0 days, 00:00:42 is added to Tiseb's clock.
+[01/06/06 20:42:03] katcha [22/24] has challenged Tiseb [1/4] in combat and won! 0 days, 00:01:28 is removed from katcha's clock.
+[01/06/06 20:59:14] xOu [22/41] has challenged Schlavbeuk [47/106] in combat and lost! 0 days, 00:02:49 is added to xOu's clock.
+[01/06/06 21:19:04] Tiseb [1/18] has challenged Colar [50/110] in combat and lost! 0 days, 00:01:15 is added to Tiseb's clock.
+[01/06/06 21:19:17] Tiseb stole xOu's level 7 set of leggings while they were sleeping! Tiseb leaves his old level 0 set of leggings behind, which xOu then takes.
+[01/06/06 21:38:26] Tiseb [19/32] has challenged rophle [10/31] in combat and won! 0 days, 00:01:28 is removed from Tiseb's clock.
+[01/06/06 21:54:11] katcha [40/58] has challenged skalp [1/88] in combat and won! 0 days, 00:02:17 is removed from katcha's clock.
+[01/06/06 21:55:33] Mickey [0/1] has challenged yom [38/49] in combat and lost! 0 days, 00:00:42 is added to Mickey's clock.
+[01/06/06 21:58:41] DT [41/124] has challenged yom [10/49] in combat and won! 0 days, 00:06:28 is removed from DT's clock.
+[01/06/06 22:03:07] xOu [46/61] has challenged Madj [2/4] in combat and won! 0 days, 00:02:39 is removed from xOu's clock.
+[01/06/06 22:06:15] Mickey [0/3] has challenged Ksempac [39/81] in combat and lost! 0 days, 00:00:48 is added to Mickey's clock.
+[01/06/06 22:20:32] skalp [74/88] has challenged Madj [7/8] in combat and won! 0 days, 00:05:35 is removed from skalp's clock.
+[01/06/06 22:32:07] Mickey [6/12] has challenged Colar [69/132] in combat and lost! 0 days, 00:01:05 is added to Mickey's clock.
+[01/06/06 22:50:34] Tiseb [18/50] has challenged Mickey [15/18] in combat and won! 0 days, 00:02:17 is removed from Tiseb's clock.
+[01/06/06 23:02:42] katcha [82/83] has challenged Ksempac [19/91] in combat and won! 0 days, 00:03:05 is removed from katcha's clock.
+[01/06/06 23:07:02] rophle [54/58] has challenged Ksempac [41/91] in combat and won! 0 days, 00:03:05 is removed from rophle's clock.
+[01/06/06 23:27:53] Mickey [7/34] has challenged Tiseb [8/50] in combat and lost! 0 days, 00:01:42 is added to Mickey's clock.
+[01/06/06 23:47:45] Ksempac [41/100] has challenged skalp [54/100] in combat and lost! 0 days, 00:07:31 is added to Ksempac's clock.
+[01/06/06 23:53:56] Mickey [29/40] has challenged skalp [18/100] in combat and won! 0 days, 00:01:58 is removed from Mickey's clock.
+[01/07/06 00:20:13] Mickey [6/49] has challenged rophle [58/66] in combat and lost! 0 days, 00:02:17 is added to Mickey's clock.
+[01/07/06 01:42:44] Ksempac [71/105] has challenged yom [34/87] in combat and won! 0 days, 00:08:43 is removed from Ksempac's clock.
+[01/07/06 02:44:07] yom [75/95] has challenged skalp [50/120] in combat and won! 0 days, 00:06:29 is removed from yom's clock.
+[01/07/06 03:08:35] Mickey [36/83] has challenged skalp [84/120] in combat and lost! 0 days, 00:04:09 is added to Mickey's clock.
+[01/07/06 03:44:32] xOu [90/124] has challenged katcha [8/119] in combat and won! 0 days, 00:06:29 is removed from xOu's clock.
+[01/07/06 05:10:45] xOu [3/126] has challenged Mickey [64/93] in combat and lost! 0 days, 00:07:31 is added to xOu's clock.
Index: /irpg/trunk/bot/po/absent.po
===================================================================
--- /irpg/trunk/bot/po/absent.po	(revision 1444)
+++ /irpg/trunk/bot/po/absent.po	(revision 1444)
@@ -0,0 +1,52 @@
+#, perl-format
+msgid "ring"
+msgstr "inutile"
+
+#, perl-format
+msgid "amulet"
+msgstr "jouet de Schlavbeuk"
+
+#, perl-format
+msgid "charm"
+msgstr "bouffe"
+
+#, perl-format
+msgid "weapon"
+msgstr "arme de snipe"
+
+#, perl-format
+msgid "helm"
+msgstr "rune d'intelligence"
+
+#, perl-format
+msgid "tunic"
+msgstr "rune de vie"
+
+#, perl-format
+msgid "pair of gloves"
+msgstr "arme de bourrin"
+
+#, perl-format
+msgid "set of leggings"
+msgstr "rune de force"
+
+#, perl-format
+msgid "shield"
+msgstr "rune de précision"
+
+#, perl-format
+msgid "pair of boots"
+msgstr "véhicule"
+
+#, perl-format
+msgid "good"
+msgstr "Brave"
+
+#, perl-format
+msgid "neutral"
+msgstr "Nain-déci"
+
+#, perl-format
+msgid "evil"
+msgstr "Sadique"
+
Index: /irpg/trunk/bot/po/fr_NW.po
===================================================================
--- /irpg/trunk/bot/po/fr_NW.po	(revision 1444)
+++ /irpg/trunk/bot/po/fr_NW.po	(revision 1444)
@@ -0,0 +1,970 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-01-09 23:14+0100\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n>1;\n"
+
+#: bot.v3.1.2.pl:151
+#, perl-format
+msgid ""
+"%s does not appear to exist. I'm guessing this is your first time using "
+"IRPG. Please give an account name that you would like to have admin access [%"
+"s]: "
+msgstr ""
+"Le fichier %s n'existe pas. Je suppose que c'est votre première utilisation "
+"de IRPG. Merci de donner le nom du compte qui aura les droit "
+"d'administration [%s]"
+
+#: bot.v3.1.2.pl:157
+msgid "Enter a character class for this account: "
+msgstr "Entrez la profession du personnage de ce compte : "
+
+#: bot.v3.1.2.pl:160
+msgid "Enter a password for this account: "
+msgstr "Entre le mot de passe de ce compte : "
+
+#: bot.v3.1.2.pl:192
+#, perl-format
+msgid "OK, wrote you into %s \n"
+msgstr "Parfait, vous êtes inscris dans %s \n"
+
+#: bot.v3.1.2.pl:197
+msgid ""
+"Checking for updates...\n"
+"\n"
+msgstr ""
+"Recherche de mise à jour...\n"
+"\n"
+
+#: bot.v3.1.2.pl:209
+msgid "There is an update available! Changes include:\n"
+msgstr "Une mise à jour est disponible! Liste des changements:\n"
+
+#: bot.v3.1.2.pl:213
+#, perl-format
+msgid "You are running the latest version (v%s).\n"
+msgstr "Vous utilisez la dernière version (v%s).\n"
+
+#: bot.v3.1.2.pl:221
+#, perl-format
+msgid ""
+"\n"
+"Get the newest version from %s!\n"
+msgstr ""
+"\n"
+"Récupérez la nouvelle version depuis %s!\n"
+
+#: bot.v3.1.2.pl:227
+msgid "Could not connect to update server."
+msgstr "Impossible de se connecter au serveur de mise à jour."
+
+#: bot.v3.1.2.pl:230
+msgid "Becoming a daemon..."
+msgstr ""
+
+#: bot.v3.1.2.pl:359 bot.v3.1.2.pl:1119
+#, perl-format
+msgid "%s, the level %u %s, is now online from nickname %s. Next level in %s."
+msgstr ""
+"%s, le %3$s de niveau %2$u, est joué par %4$s. Prochain niveau dans %5$s"
+
+#: bot.v3.1.2.pl:364
+#, perl-format
+msgid "Logon successful. Next level in %s."
+msgstr "Identification réussie. Prochain niveau dans %s."
+
+#: bot.v3.1.2.pl:425
+#, perl-format
+msgid "%u users matching %u hosts automatically logged in; accounts: "
+msgstr ""
+"%u joueurs correspondant à %u hôtes ont été identifiés automatiquement. Il "
+"s'agit de : "
+
+#: bot.v3.1.2.pl:432
+#, perl-format
+msgid "%u users matching %u hosts automatically logged in."
+msgstr ""
+"%u joueurs correspondant à %u hôtes ont été identifiés automatiquement."
+
+#: bot.v3.1.2.pl:450
+msgid "0 users qualified for auto login."
+msgstr "Aucun joueur n'as été identifié automatiquement."
+
+#: bot.v3.1.2.pl:480 bot.v3.1.2.pl:618 bot.v3.1.2.pl:637 bot.v3.1.2.pl:656
+#: bot.v3.1.2.pl:675 bot.v3.1.2.pl:697 bot.v3.1.2.pl:708 bot.v3.1.2.pl:719
+#: bot.v3.1.2.pl:738 bot.v3.1.2.pl:761 bot.v3.1.2.pl:780
+#, perl-format
+msgid "You don't have access to %s."
+msgstr "Vous n'avez pas accès à %s."
+
+#: bot.v3.1.2.pl:498 bot.v3.1.2.pl:1090
+#, perl-format
+msgid "Sorry, you are already online as %s."
+msgstr "Désolé, vous êtes déjà identifié en tant que %s."
+
+#: bot.v3.1.2.pl:503
+msgid "Try: REGISTER <char name> <password> <class>"
+msgstr "Essayez : REGISTER <nom du personnage> <mot de passe> <profession>"
+
+#: bot.v3.1.2.pl:505
+msgid "IE : REGISTER Poseidon MyPassword God of the Sea"
+msgstr "exemple : REGISTER Poseidon Monmotdepasse Roi des océans"
+
+#: bot.v3.1.2.pl:509
+msgid ""
+"Sorry, new accounts may not be registered while the bot is in pause mode; "
+"please wait a few minutes and try again."
+msgstr ""
+"Désolé, il n'est pas possible de créer de nouveaux personnages pendant que "
+"le bot est en mode pause. Merci de réessayer dans quelques minutes."
+
+#: bot.v3.1.2.pl:516
+msgid "Sorry, that character name is already in use."
+msgstr "Désolé, il existe déjà un personnage portant ce nom."
+
+#: bot.v3.1.2.pl:521
+msgid "Sorry, that character name cannot be registered."
+msgstr "Désolé, il n'est pas possible de réserver ce nom de personnage."
+
+#: bot.v3.1.2.pl:525 bot.v3.1.2.pl:1104
+#, perl-format
+msgid "Sorry, you're not in %s."
+msgstr "Désolé, vous n'êtes pas dans %s."
+
+#: bot.v3.1.2.pl:529
+msgid "Sorry, character names must be < 17 and > 0 chars long."
+msgstr ""
+"Désolé, le nom des personnages doivent avoir entre 1 et 16 caractères de long"
+
+#: bot.v3.1.2.pl:533
+msgid "Sorry, character names may not begin with #."
+msgstr "Désolé, il n'est pas possible de débuter un nom de personnage par #."
+
+#: bot.v3.1.2.pl:537
+msgid "Sorry, character names may not include character \\001."
+msgstr ""
+"Désolé, il n'est pas possible d'utiliser \\001 dans un nom de personnage."
+
+#: bot.v3.1.2.pl:542
+msgid "Sorry, neither character names nor classes may include control codes."
+msgstr ""
+"Désolé, ni le nom du personnage, ni sa profession ne doivent comprendre de "
+"caractères de contrôle."
+
+#: bot.v3.1.2.pl:548
+msgid ""
+"Sorry, neither character names nor classes may include non-printable chars."
+msgstr ""
+"Désolé, il n'est pas possible d'utiliser des caractères non-visibles ni dans "
+"le nom du personnage, ni dans sa profession."
+
+#: bot.v3.1.2.pl:553
+msgid "Sorry, character classes must be < 31 chars long."
+msgstr ""
+"Désolé, la profession d'un personnage ne peut avoir plus de 30 caractères."
+
+#: bot.v3.1.2.pl:557
+msgid "Wait 1 second and try again."
+msgstr "Attendez une seconde et réessayez."
+
+#: bot.v3.1.2.pl:589
+#, perl-format
+msgid "Welcome %s's new player %s, the %s! Next level in %s."
+msgstr ""
+"Bienvenue à %2$s le %3$s personnage de %1$s! Prochain niveau dans %4$s."
+
+#: bot.v3.1.2.pl:593
+#, perl-format
+msgid ""
+"Success! Account %s created. You have %u seconds idleness until you reach "
+"level 1."
+msgstr ""
+"Réussite ! Le personnage %s est créé. Vous devrez attendre %u secondes avant "
+"d'atteindre le premier niveau."
+
+#: bot.v3.1.2.pl:597
+msgid ""
+"NOTE: The point of the game is to see who can idle the longest. As such, "
+"talking in the channel, parting, quitting, and changing nicks all penalize "
+"you."
+msgstr ""
+"NOTE : Le but du jeu est de voir qui peut rester à ne rien faire le plus "
+"longtemps. Aussi, parler dans ce salon, le quitter, se déconnecter ou "
+"changer de pseudo vous pénalise."
+
+#: bot.v3.1.2.pl:623
+msgid "Try: DELOLD <# of days>"
+msgstr "Essayez: DELOLD <nbr. de jours>"
+
+#: bot.v3.1.2.pl:630
+#, perl-format
+msgid "%u accounts not accessed in the last %u days removed by %s."
+msgstr ""
+"%u personnages qui n'ont pas été joués ces %u derniers jours ont été "
+"supprimés par %s."
+
+#: bot.v3.1.2.pl:641
+msgid "Try: DEL <char name>"
+msgstr "Essayez: DEL <nom de personnage>"
+
+#: bot.v3.1.2.pl:644 bot.v3.1.2.pl:663 bot.v3.1.2.pl:682
+#, perl-format
+msgid "No such account %s."
+msgstr "Le personnage %s n'existe pas."
+
+#: bot.v3.1.2.pl:649
+#, perl-format
+msgid "Account %s removed by %s."
+msgstr "Le personnage %s a été supprimé par %s."
+
+#: bot.v3.1.2.pl:660
+msgid "Try: MKADMIN <char name>"
+msgstr "Essayez: MKADMIN <nom de personnage>"
+
+#: bot.v3.1.2.pl:668
+#, perl-format
+msgid "Account %s is now a bot admin."
+msgstr "Le personnage %s est maintenant un administrateur du bot."
+
+#: bot.v3.1.2.pl:679
+msgid "Try: DELADMIN <char name>"
+msgstr "Essayez : DELADMIN <nom de personnage>"
+
+#: bot.v3.1.2.pl:686
+msgid "Cannot DELADMIN owner account."
+msgstr "Il n'est pas possible de supprimer le personnage du propriétaire."
+
+#: bot.v3.1.2.pl:691
+#, perl-format
+msgid "Account %s is no longer a bot admin."
+msgstr "Le personnage %s n'est plus un administrateur du bot."
+
+#: bot.v3.1.2.pl:701
+#, perl-format
+msgid "%s has summoned the Hand of God."
+msgstr "%s a invoqué la main de Schlavbeuk."
+
+#: bot.v3.1.2.pl:713
+msgid "Reread config file."
+msgstr "Relecture du fichier de configuration."
+
+#: bot.v3.1.2.pl:723
+msgid "Try: CHPASS <char name> <new pass>"
+msgstr "Essayez : CHPASS <nom de personnage> <nouveau mot de passe>"
+
+#: bot.v3.1.2.pl:727 bot.v3.1.2.pl:746 bot.v3.1.2.pl:769 bot.v3.1.2.pl:789
+#, perl-format
+msgid "No such username %s."
+msgstr "Le personnage %s n'existe pas."
+
+#: bot.v3.1.2.pl:732
+#, perl-format
+msgid "Password for %s changed."
+msgstr "Le mot de passe de %s a été changé."
+
+#: bot.v3.1.2.pl:742
+msgid "Try: CHUSER <char name> <new char name>"
+msgstr "Essayez : CHUSER <nom de personnage> <nouveau nom>"
+
+#: bot.v3.1.2.pl:750
+#, perl-format
+msgid "Username %s is already taken."
+msgstr "Il existe déjà un personnage nommé %s."
+
+#: bot.v3.1.2.pl:755
+#, perl-format
+msgid "Username for %s changed to %s."
+msgstr "Le personnage %s a changé de nom pour %s."
+
+#: bot.v3.1.2.pl:765
+msgid "Try: CHCLASS <char name> <new char class>"
+msgstr "Essayez : CHCLASS <nom du personnage> <nouvelle profession>"
+
+#: bot.v3.1.2.pl:774
+#, perl-format
+msgid "Class for %s changed to %s."
+msgstr "La profession de %s est maintenant %s."
+
+#: bot.v3.1.2.pl:785
+msgid "Try: PUSH <char name> <seconds>"
+msgstr "Essayez : PUSH <nom de personnage> <secondes>"
+
+#: bot.v3.1.2.pl:793
+#, perl-format
+msgid "Time to level for %s (%u s) is lower than %d; setting TTL to 0."
+msgstr ""
+"La durée avant passage de niveau pour %s (%u s) est plus courte que %d ; La "
+"durée de passage de niveau est mise à 0."
+
+#: bot.v3.1.2.pl:797
+#, perl-format
+msgid "%s has pushed %s %d seconds toward level %u"
+msgstr "%s a propulsé %s %d secondes vers le niveau %u"
+
+#: bot.v3.1.2.pl:805
+#, perl-format
+msgid ""
+"%s has pushed %s %d seconds toward level %u. %2$s reaches next level in %5$s."
+msgstr ""
+"%s a pris %s en stop, ce qui lui fait gagner %d secondes vers le niveau %u. "
+"%2$s atteindra le prochain niveau dans %5$s."
+
+#: bot.v3.1.2.pl:818 bot.v3.1.2.pl:849 bot.v3.1.2.pl:874 bot.v3.1.2.pl:886
+#: bot.v3.1.2.pl:898 bot.v3.1.2.pl:915
+msgid "You are not logged in."
+msgstr "Vous n'êtes pas identifié."
+
+#: bot.v3.1.2.pl:823
+msgid "There is no active quest."
+msgstr "Il n'y a pas de quête en cours."
+
+#: bot.v3.1.2.pl:826
+#, perl-format
+msgid "%s, and %s are on a quest to %s. Quest to complete in %s."
+msgstr ""
+"%s et %s sont en train de faire la quête consistant à %s. Cette quête se "
+"terminera dans %s."
+
+#: bot.v3.1.2.pl:834
+#, perl-format
+msgid ""
+"%s, and %s are on a quest to %s. Participants must first reach [%u,%u], then "
+"[%u,%u]."
+msgstr ""
+"%s et %s participent à la quête consistant à %s. Il doivent pour cela se "
+"rendre en [%u,%u] et ensuite en [%u,%u]."
+
+#: bot.v3.1.2.pl:841 bot.v3.1.2.pl:2327
+#, perl-format
+msgid " See %s to monitor their journey's progress."
+msgstr " Consultez %s pour suivre leur avancement dans cette quête."
+
+#: bot.v3.1.2.pl:853
+msgid "No such user."
+msgstr "Ce personnage n'existe pas."
+
+#: bot.v3.1.2.pl:877
+#, perl-format
+msgid "You are %s, the level %u %s. Next level in %s"
+msgstr "Vous êtes %s, %3$s de niveau %2$u. Prochain niveau dans %4$s"
+
+#: bot.v3.1.2.pl:889
+msgid "Try: NEWPASS <new password>"
+msgstr "Essayez: NEWPASS <nouveau mot de passe>"
+
+#: bot.v3.1.2.pl:893
+msgid "Your password was changed."
+msgstr "Votre mot de passe a été changé."
+
+#: bot.v3.1.2.pl:902
+msgid "Try: ALIGN <good|neutral|evil>"
+msgstr "Essayez: ALIGN <good|neutral|evil>"
+
+#: bot.v3.1.2.pl:907
+#, perl-format
+msgid "%s has changed alignment to: "
+msgstr "%s a changé de coté pour : "
+
+#: bot.v3.1.2.pl:909
+msgid "Your alignment was changed to "
+msgstr "Vous avez changé de coté pour "
+
+#: bot.v3.1.2.pl:918
+#, perl-format
+msgid "Account %s removed."
+msgstr "Le personnage %s est supprimé."
+
+#: bot.v3.1.2.pl:920
+#, perl-format
+msgid "%s removed his account, %s, the %s."
+msgstr "%s a supprimé son personnage %s le %s."
+
+#: bot.v3.1.2.pl:928
+#, perl-format
+msgid "For information on IRPG bot commands, see %s"
+msgstr "Pour avoir des informations sur les commandes du bot IRPG, consultez %s"
+
+#: bot.v3.1.2.pl:933
+#, perl-format
+msgid "Help URL is %s"
+msgstr "L'URL de l'aide est %s"
+
+#: bot.v3.1.2.pl:935
+#, perl-format
+msgid "Admin commands URL is %s"
+msgstr "L'URL des commandes pour les administrateurs est %s"
+
+#: bot.v3.1.2.pl:941 bot.v3.1.2.pl:952 bot.v3.1.2.pl:968 bot.v3.1.2.pl:980
+#: bot.v3.1.2.pl:991 bot.v3.1.2.pl:1005 bot.v3.1.2.pl:1023 bot.v3.1.2.pl:1035
+#: bot.v3.1.2.pl:1059
+#, perl-format
+msgid "You do not have access to %s."
+msgstr "Vous n'avez pas accès à %s."
+
+#: bot.v3.1.2.pl:947
+#, perl-format
+msgid "QUIT :DIE from %s"
+msgstr "QUIT :DIE demandé par %s"
+
+#: bot.v3.1.2.pl:956
+msgid "ERROR: Can only use LOADDB while in PAUSE mode."
+msgstr "ERREUR: Il n'est possible d'utiliser LOADDB qu'en mode PAUSE."
+
+#: bot.v3.1.2.pl:961
+#, perl-format
+msgid "Reread player database file; %u accounts loaded."
+msgstr "Relecture de la base de donnée des joueurs ; %u comptes chargés."
+
+#: bot.v3.1.2.pl:973
+#, perl-format
+msgid "%s copied to .dbbackup/%s"
+msgstr "%s copié vers .dbbackup/%s"
+
+#: bot.v3.1.2.pl:985
+#, perl-format
+msgid "PAUSE_MODE set to %s."
+msgstr "PAUSE_MODE mis à %s."
+
+#: bot.v3.1.2.pl:995
+msgid "Try: SILENT <mode>"
+msgstr "Essayez : SILENT <mode>"
+
+#: bot.v3.1.2.pl:999
+#, perl-format
+msgid "SILENT_MODE set to %s."
+msgstr "SILENT_MODE mis à %s."
+
+#: bot.v3.1.2.pl:1009
+msgid "Try: JUMP <server[:port]>"
+msgstr "Essayez : JUMP <serveur[:port]>"
+
+#: bot.v3.1.2.pl:1013
+#, perl-format
+msgid "QUIT :JUMP to %s from %s"
+msgstr "QUIT :JUMP vers %s demandé par %s"
+
+#: bot.v3.1.2.pl:1028
+#, perl-format
+msgid "QUIT :RESTART from %s"
+msgstr "QUIT :RESTART demandé par %s"
+
+#: bot.v3.1.2.pl:1040
+#, perl-format
+msgid "Outgoing message queue cleared by %s."
+msgstr "File de messages en sortie vidée par %s."
+
+#: bot.v3.1.2.pl:1042
+msgid "Outgoing message queue cleared."
+msgstr "File de messages en sortie vidée."
+
+#: bot.v3.1.2.pl:1049
+#, perl-format
+msgid ""
+"IRPG bot v%s by jotun, http://idlerpg.net/. On via server: %s. Admins "
+"online: %s."
+msgstr ""
+"bot IRPG v%s par jotun, http://idlerpg.net/. hébergé sur le serveur: %s."
+"Administrateurs connectés: %s."
+
+#: bot.v3.1.2.pl:1066
+#, perl-format
+msgid ""
+"%.2fkb sent, %.2fkb received in %s. %d IRPG users online of %d total users. %"
+"d accounts created since startup. PAUSE_MODE is %d, SILENT_MODE is %d. "
+"Outgoing queue is %d bytes in %d items. On via: %s. Admins online: %s."
+msgstr ""
+"%.2fkb envoyés, %.2fkb reçus en %s. %d personnages IRPG actifs pour un total "
+"de %d personnages. %d personnages créés depuis le lancement. PAUSE_MODE est %"
+"d, SILENT_MODE est %d. La file de sortie fait %d octets pour %d items. Accès "
+"par %s. Les administrateurs actifs sont: %s."
+
+#: bot.v3.1.2.pl:1095
+msgid "Try: LOGIN <username> <password>"
+msgstr "Essayez : LOGIN <nom de personnage> <mot de passe>"
+
+#: bot.v3.1.2.pl:1099
+msgid ""
+"Sorry, no such account name. Note that account names are case sensitive."
+msgstr ""
+"Désolé aucun personnage ne porte ce nom. Notez que les noms de personnages "
+"sont sensibles à la casse."
+
+#: bot.v3.1.2.pl:1109
+msgid "Wrong password."
+msgstr "Mauvais mot de passe."
+
+#: bot.v3.1.2.pl:1125
+msgid "Logon successful. Next level in "
+msgstr "Identification réussie. Prochain niveau dans "
+
+#: bot.v3.1.2.pl:1224
+#, perl-format
+msgid "%d day, %02d:%02d:%02d"
+msgid_plural "%d days, %02d:%02d:%02d"
+msgstr[0] "%d jour, %02dh %02dm %02ds"
+msgstr[1] "%d jours, %02dh %02dm %02ds"
+
+#: bot.v3.1.2.pl:1241
+#, perl-format
+msgid ""
+"Verily I say unto thee, the Heavens have burst forth, and the blessed hand "
+"of God carried %s %s toward level %u."
+msgstr ""
+"Schlavbeuk a pris %s en stop dans son X-wing, ce qui l'a rapproché de %s "
+"du niveau %u."
+
+#: bot.v3.1.2.pl:1248
+#, perl-format
+msgid ""
+"Thereupon He stretched out His little finger among them and consumed %s with "
+"fire, slowing the heathen %s from level %u."
+msgstr ""
+"Schlavbeuk a pris %s en stop dans sa Formule 1 mauve... qui a explosée. Ce "
+"qui a éloigné %1$s de $2$s du niveau %3$u."
+
+#: bot.v3.1.2.pl:1255 bot.v3.1.2.pl:1477 bot.v3.1.2.pl:1488 bot.v3.1.2.pl:1519
+#: bot.v3.1.2.pl:2206 bot.v3.1.2.pl:2278 bot.v3.1.2.pl:2518 bot.v3.1.2.pl:2526
+#: bot.v3.1.2.pl:2556 bot.v3.1.2.pl:2648 bot.v3.1.2.pl:2650 bot.v3.1.2.pl:2693
+#, perl-format
+msgid "%s reaches next level in %s."
+msgstr "%s atteindra le prochain niveau dans %s."
+
+#: bot.v3.1.2.pl:1298
+#, fuzzy, perl-format
+msgid ""
+"%s, and %s have blessed the realm by completing their quest! 25%% of their "
+"burden is eliminated."
+msgstr ""
+"%s et %s sont acclamés par leur courgette pour avoir complété leur quête ! "
+"Ils sont portés en triomphe sur 25%% de leur route."
+
+#: bot.v3.1.2.pl:1315
+msgid "Idle RPG Top Players:"
+msgstr "Meilleurs joueurs de Idle RPG :"
+
+#: bot.v3.1.2.pl:1318
+#, perl-format
+msgid "%s, the level %u %s, is #%u! Next level in %s."
+msgstr ""
+"%s le %3$s de niveau %2$u est classé n°%4$u ! Il atteindra le prochain "
+"niveau dans %5$s."
+
+#: bot.v3.1.2.pl:1345
+msgid "WARNING: Cannot write database in PAUSE mode!"
+msgstr "ATTENTION : Impossible d'écrire dans la base de donnée en mode PAUSE !"
+
+#: bot.v3.1.2.pl:1363
+#, perl-format
+msgid "%s, the %s, has attained level %u! Next level in %s."
+msgstr "%s le %s a atteint le niveau %u ! Prochain niveau dans %s."
+
+#: bot.v3.1.2.pl:1471
+#, perl-format
+msgid ""
+"%s [%u/%u] has challenged %s [%u/%u] in combat and won! %s is removed from %1"
+"$s's clock."
+msgstr ""
+"%s [%u/%u] a défié %s [%u/%u] en combat et a gagné! %s est retiré du "
+"compteur de %1$s."
+
+#: bot.v3.1.2.pl:1484 bot.v3.1.2.pl:2522
+#, perl-format
+msgid "%s has dealt %s a Critical Strike! %s is added to %2$s's clock."
+msgstr "%s a placé un coup critique sur %s ! %s est ajouté au compteur de %2$s."
+
+#: bot.v3.1.2.pl:1497 bot.v3.1.2.pl:2534
+#, perl-format
+msgid ""
+"In the fierce battle, %s dropped his level %u %s! %s picks it up, tossing "
+"his old level %u %3$s to %1$s."
+msgstr ""
+"Au cours de cette bataille acharnée, %s a laché son %3$s de niveau %2$u ! "
+"%4$s l'a ramassé, jettant son ancien %6$s de niveau %5$u à %1$s."
+
+#: bot.v3.1.2.pl:1514
+#, perl-format
+msgid ""
+"%s [%u/%u] has challenged %s [%u/%u] in combat and lost! %s is added to %1"
+"$s's clock."
+msgstr ""
+"%s [%u/%u] a défié %s [%u/%u] en combat et a perdu ! %s est ajouté au "
+"compteur de %1$s."
+
+#: bot.v3.1.2.pl:1562
+#, perl-format
+msgid ""
+"%s, %s and %s [%u/%u] have team battled %s, %s and %s [%u/%u] at [%u,%u] and "
+"won! %s is removed from their clocks."
+msgstr ""
+"%s, %s et %s [%u/u] ont fait une course-en-sac contre %s, %s et %s "
+"[%u/%u] en [%u,%u] et ont gagné ! Un passage secret leur est indiqué, leur "
+"faisant gagner %s."
+
+#: bot.v3.1.2.pl:1573
+#, perl-format
+msgid ""
+"%s, %s and %s [%u/%u] have team battled %s, %s and %s [%u/%u] at [%u,%u] and "
+"lost! %s is added to their clocks."
+msgstr ""
+"%s, %s et %s [%u/%u] ont joué au laser-game contre %s, %s et %s [%u/%"
+"u] en [%u,%u] et ont perdu ! Ils doivent attendre %s de plus."
+
+#: bot.v3.1.2.pl:1642
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Mattt's Omniscience Grand Crown! Your enemies fall before you as you "
+"anticipate their every move."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez le Casque de "
+"niveau %u de Hunnin Avherty Envodeux le Viking Nain ! Vous anticipez les "
+"mouvements de tous vos ennemis et vous en debarrassez aisément."
+
+#: bot.v3.1.2.pl:1649
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Res0's Protectorate Plate Mail! Your enemies cower in fear as their attacks "
+"have no effect on you."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la Combinaison "
+"de Bionain Caméléon de niveau %u. Plus rien ne vous atteint avec cette "
+"protection intégrale (jusqu'à la coquille en titane pour les parties "
+"sensibles)."
+
+#: bot.v3.1.2.pl:1656
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Dwyn's Storm Magic Amulet! Your enemies are swept away by anelemental fury "
+"before the war has even begun"
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la Patate "
+"Nucléaire de niveau %u. Grâce à sa portée hors du commun, plus personne "
+"n'échappe à votre puissance"
+
+#: bot.v3.1.2.pl:1663
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Jotun's Fury Colossal Sword! Your enemies' hatred is broughtto a quick end "
+"as you arc your wrist, dealing the crushing blow."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez le pistolet à "
+"rétro-propulsion plasmatique de SPOOK de niveau %u. Vous envoyez tous vos "
+"ennemis valdaguer ailleurs voir si vous y êtes."
+
+#: bot.v3.1.2.pl:1670
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Drdink's Cane of Blind Rage! Your enemies are tossed aside as you blindly "
+"swing your arm around hitting stuff."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la calculette "
+"scientifique en or rétro-éclairée TI-9239073195 de niveau %u. Avec elle vous "
+"vous sentez capable de découvrir la valeur exacte de Pi et donnez mal à la "
+"tête à vos ennemis faibles en math."
+
+#: bot.v3.1.2.pl:1677
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Mrquick's Magical Boots of Swiftness! Your enemies are left choking on your "
+"dust as you run from them very, very quickly."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez le légendaire "
+"Mega giga super gros coup de pied au cul de niveau %u. Vous commencez à "
+"infliger la douleur suprême à tous les derrières de vos ennemis."
+
+#: bot.v3.1.2.pl:1684
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Jeff's Cluehammer of Doom! Your enemies are left with a sudden and intense "
+"clarity of mind... even as you relieve them of it."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez la brouette "
+"spatio-temporelle édition limitée Einst-Nain lui même de niveau %u. Avec ça "
+"vous pouvez attaquer et fuir avant même que vos ennemis réagissent."
+
+#: bot.v3.1.2.pl:1691
+#, perl-format
+msgid ""
+"The light of the gods shines down upon you! You have found the level %u "
+"Juliet's Glorious Ring of Sparkliness! You enemies are blinded by both its "
+"glory and their greed as you bring desolation upon them."
+msgstr ""
+"La divine lumière de Schlavbeuk est sur vous ! Vous trouvez Le glaive de "
+"Schlavbeuk de niveau %u. C'est l'ultime glaive : il a été béni par un prêtre "
+"dans le vomi sacré de Schlavbeuk après une autre soirée trop arrosée."
+
+#: bot.v3.1.2.pl:1699
+#, perl-format
+msgid ""
+"You found a level %u %s! Your current %2$s is only level %u, so it seems "
+"Luck is with you!"
+msgstr ""
+"Vous trouvez un %2$s de niveau %1$u! votre %2$s actuel est seulement de "
+"niveau %3$u, il semble donc que la chance soit avec vous!"
+
+#: bot.v3.1.2.pl:1782
+#, perl-format
+msgid ""
+"You found a level %u %s. Your current %2$s is level %3$u, so it seems Luck "
+"is against you. You toss the %2$s."
+msgstr ""
+"Vous trouvez un %2$s de niveau %1$u. Votre %2$s actuel est de niveau %3$u, "
+"il semble donc que la chance soit contre vous. Vous jettez le %2$s."
+
+#: bot.v3.1.2.pl:1902
+#, fuzzy, perl-format
+msgid ""
+"%s, and %s have completed their journey! 25%% of their burden is eliminated."
+msgstr "%s et %s ont terminé leur périple! 25%% de leur charge est supprimé."
+
+#: bot.v3.1.2.pl:1934 bot.v3.1.2.pl:1992
+#, perl-format
+msgid "%s encounters %s and bows humbly."
+msgstr ""
+"%s a recontré un bord du monde en %s et a changé de direction, tout penaud."
+
+#: bot.v3.1.2.pl:2151
+#, perl-format
+msgid ""
+"%s fell, chipping the stone in his amulet! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2157
+#, perl-format
+msgid ""
+"%s slipped and dropped his charm in a dirty bog! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2163
+#, perl-format
+msgid ""
+"%s left his weapon out in the rain to rust! %1$s's %s loses 10%% of its "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2169
+#, perl-format
+msgid ""
+"%s spilled a level 7 shrinking potion on his tunic! %1$s's %s loses 10%% of "
+"its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2175
+#, perl-format
+msgid ""
+"%s's shield was damaged by a dragon's fiery breath! %1$s's %s loses 10%% of "
+"its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2181
+#, perl-format
+msgid ""
+"%s burned a hole through his leggings while ironing them! %1$s's %s loses 10%"
+"% of its effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2201
+#, perl-format
+msgid "%s %s. This terrible calamity has slowed them %s from level %u."
+msgstr "%s %s. Cette terrible calamité l'a retardé de %s vers le niveau %u."
+
+#: bot.v3.1.2.pl:2220
+#, perl-format
+msgid ""
+"%s's %s was blessed by a passing cleric! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2226
+#, perl-format
+msgid "%s's %s ate a bolt of lightning! %1$s's %2$s gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2232
+#, perl-format
+msgid "%s sharpened the edge of his %s! %1$s's %2$s gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2238
+#, perl-format
+msgid ""
+"A magician cast a spell of Rigidity on %s's %s! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2244
+#, perl-format
+msgid ""
+"%s reinforced his %s with a dragon's scales! %1$s's %2$s gains 10%% "
+"effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2250
+#, perl-format
+msgid ""
+"The local wizard imbued %s's pants with a Spirit of Fortitude! %1$s's %2$s "
+"gains 10%% effectiveness."
+msgstr ""
+
+#: bot.v3.1.2.pl:2273
+#, perl-format
+msgid "%s %s! This wondrous godsend has accelerated them %s towards level %u."
+msgstr ""
+"%s %s ! Cette bénédiction de Schlavbeuk l'a avancé de %s vers le niveau %u."
+
+#: bot.v3.1.2.pl:2313
+#, perl-format
+msgid "%s, and %s have been chosen by the gods to %s. Quest to end in %s."
+msgstr ""
+"%s et %s ont été choisis par Schlavbeuk pour %s. Il leur faudra %s pour "
+"compléter cette quête."
+
+#: bot.v3.1.2.pl:2320
+#, perl-format
+msgid ""
+"%s, and %s have been chosen by the gods to %s. Participants must first reach "
+"[%u,%u], then [%u,%u]."
+msgstr ""
+"%s et %s ont été choisis par Schlavbeuk pour %s. Les participants doivent "
+"tout d'abord se rendre en [%u,%u] et enfin en [%u,%u]."
+
+#: bot.v3.1.2.pl:2340
+#, perl-format
+msgid ""
+"%s's prudence and self-regard has brought the wrath of the gods upon the "
+"realm. All your great wickedness makes you as it were heavy with lead, and "
+"to tend downwards with great weight and pressure towards hell. Therefore "
+"have you drawn yourselves 15 steps closer to that gaping maw."
+msgstr ""
+
+#: bot.v3.1.2.pl:2408
+#, perl-format
+msgid "Penalty of %s added to your timer for nick change."
+msgstr ""
+"Une pénalité de %s est ajoutée à votre compteur pour changement de pseudo."
+
+#: bot.v3.1.2.pl:2417
+#, perl-format
+msgid "Penalty of %s added to your timer for %s."
+msgstr "Une pénalité de %s est ajoutée à votre compteur pour %s."
+
+#: bot.v3.1.2.pl:2426
+#, perl-format
+msgid "Penalty of %s added to your timer for parting."
+msgstr ""
+"Une pénalité de %s est ajoutée à votre compteur pour avoir quitté le salon."
+
+#: bot.v3.1.2.pl:2436
+#, perl-format
+msgid "Penalty of %s added to your timer for being kicked."
+msgstr "Une pénalité de %s est ajoutée à votre compteur pour avoir été kické."
+
+#: bot.v3.1.2.pl:2446
+#, perl-format
+msgid "Penalty of %s added to your timer for LOGOUT command."
+msgstr ""
+"Une pénalité de %s est ajoutée à votre compteur pour avoir utilisé la "
+"commande LOGOUT."
+
+#: bot.v3.1.2.pl:2512
+#, perl-format
+msgid ""
+"%s [%u/%u] has come upon %s [%u/%u] and taken them in combat! %s is removed "
+"from %1$s's clock."
+msgstr ""
+"%s [%u/%u] est venu au devant de %s [%u/%u] et lui a jeté une tarte à la "
+"crême en pleine figure ! %s est retiré du compteur de %1$s."
+
+#: bot.v3.1.2.pl:2550
+#, perl-format
+msgid ""
+"%s [%u/%u] has come upon %s [%u/%u] and been defeated in combat! %s is added "
+"to %1$s's clock."
+msgstr ""
+"%s [%u/%u] est venu au devant de %s [%u/%u] et s'est pris un Cocktail "
+"\"mal aux touffes\" en pleine poire ! %s est ajouté au compteur de %1$s."
+
+#: bot.v3.1.2.pl:2641
+#, perl-format
+msgid ""
+"%s and %s have not let the iniquities of evil men poison them. Together have "
+"they prayed to their god, and it is his light that now shines upon them. %u%"
+"% of their time is removed from their clocks."
+msgstr ""
+"%s et %s ne se sont pas laissé corrompre par la haine des sadiques. "
+"Ensembles, ils ont priés Schlavbeuk et il leur a prêté sa Deçansis. "
+"Leur voyage a été raccourcis de %u%%."
+
+#: bot.v3.1.2.pl:2672
+#, perl-format
+msgid ""
+"%s stole %s's level %u %s while they were sleeping! %1$s leaves his old "
+"level %u %4$s behind, which %2$s then takes."
+msgstr ""
+"%1$s a volé le %4$s de niveau %3$u de %2$s pendant qu'il hibernait ! %1$s a "
+"laissé son ancien %4$s de niveau %5$u derrière lui et %2$s l'a pris."
+
+#: bot.v3.1.2.pl:2681
+#, perl-format
+msgid ""
+"You made to steal %s's %s, but realized it was lower level than your own. "
+"You creep back into the shadows."
+msgstr ""
+"Vous alliez voler le %2$s de $1$s, mais vous vous êtes apperçus que son "
+"niveau était plus faible que le votre. Vous retournez dans l'ombre."
+
+#: bot.v3.1.2.pl:2689
+#, perl-format
+msgid "%s is forsaken by his evil god. %s is added to his clock."
+msgstr "%s a piqué la catapulte de Schlavbeuk... malheureusement, elle "
+"n'était pas certifiée gourde. il s'est trompé de direction et s'est "
+"rallongé de %s."
+
+msgid "ring"
+msgstr "inutile"
+
+msgid "amulet"
+msgstr "jouet de Schlavbeuk"
+
+msgid "charm"
+msgstr "bouffe"
+
+msgid "weapon"
+msgstr "arme de snipe"
+
+msgid "helm"
+msgstr "rune d'intelligence"
+
+msgid "tunic"
+msgstr "rune de vie"
+
+msgid "pair of gloves"
+msgstr "arme de bourrin"
+
+msgid "set of leggings"
+msgstr "rune de force"
+
+msgid "shield"
+msgstr "rune de précision"
+
+msgid "pair of boots"
+msgstr "véhicule"
+
+msgid "good"
+msgstr "Brave"
+
+msgid "neutral"
+msgstr "Nain-déci"
+
+msgid "evil"
+msgstr "Sadique"
