2 # irpg bot v3.1.2 by jotun, jotun@idlerpg.net, et al. See http://idlerpg.net/
4 # Some code within this file was written by authors other than myself. As such,
5 # distributing this code or distributing modified versions of this code is
6 # strictly prohibited without written authorization from the authors. Contact
7 # jotun@idlerpg.net. Please note that this may change (at any time, no less) if
8 # authorization for distribution is given by patch submitters.
10 # As a side note, patches submitted for this project are automatically taken to
11 # be freely distributable and modifiable for any use, public or private, though
12 # I make no claim to ownership; original copyrights will be retained.. except as
15 # Please mail bugs, etc. to me. Patches are welcome to fix bugs or clean up
16 # the code, but please do not use a radically different coding style. Thanks
17 # to everyone that's contributed!
19 # NOTE: This code should NOT be run as root. You deserve anything that happens
20 # to you if you run this code as a superuser. Also, note that giving a
21 # user admin access to the bot effectively gives them full access to the
22 # user under which your bot runs, as they can use the PEVAL command to
23 # execute any command, or possibly even change your password. I sincerely
24 # suggest that you exercise extreme caution when giving someone admin
25 # access to your bot, or that you disable the PEVAL command for non-owner
26 # accounts in your config file, .irpg.conf
39 my $version = "3.1.2";
41 # command line overrides .irpg.conf
94 "dbfile|irpgdb|db|d=s",
95 ) or debug("Error: Could not parse command line. Try $0 --help\n",1);
97 $opts{help} and do { help(); exit 0; };
99 debug("Config: read $_: ".Dumper($opts{$_})) for keys(%opts);
101 my $outbytes = 0; # sent bytes
102 my $primnick = $opts{botnick}; # for regain or register checks
103 my $inbytes = 0; # received bytes
104 my %onchan; # users on game channel
105 my %rps; # role-players
108 p1 => [], # point 1 for q2
109 p2 => [], # point 2 for q2
110 qtime => time() + int(rand(21600)), # first quest starts in <=6 hours
113 stage => 1); # quest info
115 my $rpreport = 0; # constant for reporting top players
116 my %prev_online; # user@hosts online on restart, die
117 my %auto_login; # users to automatically log back on
118 my @bans; # bans auto-set by the bot, saved to be removed after 1 hour
119 my $pausemode = 0; # pausemode on/off flag
120 my $silentmode = 0; # silent mode 0/1/2/3, see head of file
121 my @queue; # outgoing message queue
122 my $lastreg = 0; # holds the time of the last reg. cleared every second.
123 # prevents more than one account being registered / second
124 my $registrations = 0; # count of registrations this period
125 my $sel; # IO::Select object
126 my $lasttime = 1; # last time that rpcheck() was run
127 my $buffer; # buffer for socket stuff
128 my $conn_tries = 0; # number of connection tries. gives up after trying each
130 my $sock; # IO::Socket::INET object
131 my %split; # holds nick!user@hosts for clients that have been netsplit
132 my $freemessages = 4; # number of "free" privmsgs we can send. 0..$freemessages
134 sub daemonize(); # prototype to avoid warnings
136 if (! -e $opts{dbfile}) {
139 print "$opts{dbfile} does not appear to exist. I'm guessing this is your ".
140 "first time using IRPG. Please give an account name that you would ".
141 "like to have admin access [$opts{owner}]: ";
142 chomp(my $uname = <STDIN>);
144 $uname = length($uname)?$uname:$opts{owner};
145 print "Enter a character class for this account: ";
146 chomp(my $uclass = <STDIN>);
147 $rps{$uname}{class} = substr($uclass,0,30);
148 print "Enter a password for this account: ";
149 if ($^O ne "MSWin32") {
150 system("stty -echo");
152 chomp(my $upass = <STDIN>);
153 if ($^O ne "MSWin32") {
156 $rps{$uname}{pass} = crypt($upass,mksalt());
157 $rps{$uname}{next} = $opts{rpbase};
158 $rps{$uname}{nick} = "";
159 $rps{$uname}{userhost} = "";
160 $rps{$uname}{level} = 0;
161 $rps{$uname}{online} = 0;
162 $rps{$uname}{idled} = 0;
163 $rps{$uname}{created} = time();
164 $rps{$uname}{lastlogin} = time();
165 $rps{$uname}{x} = int(rand($opts{mapx}));
166 $rps{$uname}{y} = int(rand($opts{mapy}));
167 $rps{$uname}{alignment}="n";
168 $rps{$uname}{isadmin} = 1;
169 for my $item ("ring","amulet","charm","weapon","helm",
170 "tunic","pair of gloves","shield",
171 "set of leggings","pair of boots") {
172 $rps{$uname}{item}{$item} = 0;
174 for my $pen ("pen_mesg","pen_nick","pen_part",
175 "pen_kick","pen_quit","pen_quest",
176 "pen_logout","pen_logout") {
177 $rps{$uname}{$pen} = 0;
180 print "OK, wrote you into $opts{dbfile}.\n";
183 print "\n".debug("Becoming a daemon...")."\n";
186 $SIG{HUP} = "readconfig"; # sighup = reread config file
192 while (!$sock && $conn_tries < 2*@{$opts{servers}}) {
193 debug("Connecting to $opts{servers}->[0]...");
194 my %sockinfo = (PeerAddr => $opts{servers}->[0],
196 if ($opts{localaddr}) { $sockinfo{LocalAddr} = $opts{localaddr}; }
197 $sock = IO::Socket::INET->new(%sockinfo) or
198 debug("Error: failed to connect: $!\n");
201 # cycle front server to back if connection failed
202 push(@{$opts{servers}},shift(@{$opts{servers}}));
204 else { debug("Connected."); }
208 debug("Error: Too many connection failures, exhausted server list.\n",1);
213 $sel = IO::Select->new($sock);
215 sts("NICK $opts{botnick}");
216 sts("USER $opts{botuser} 0 0 :$opts{botrlnm}");
219 my($readable) = IO::Select->select($sel,undef,undef,0.5);
220 if (defined($readable)) {
221 my $fh = $readable->[0];
223 $fh->recv($buffer2,512,0);
224 if (length($buffer2)) {
226 while (index($buffer,"\n") != -1) {
227 my $line = substr($buffer,0,index($buffer,"\n")+1);
228 $buffer = substr($buffer,length($line));
233 # uh oh, we've been disconnected from the server, possibly before
234 # we've logged in the users in %auto_login. so, we'll set those
235 # users' online flags to 1, rewrite db, and attempt to reconnect
236 # (if that's wanted of us)
237 $rps{$_}{online}=1 for keys(%auto_login);
243 if ($opts{reconnect}) {
246 debug("Socket closed; disconnected. Cleared outgoing message ".
247 "queue. Waiting $opts{reconnect_wait}s before next ".
248 "connection attempt...");
249 sleep($opts{reconnect_wait});
252 else { debug("Socket closed; disconnected.",1); }
255 else { select(undef,undef,undef,1); }
256 if ((time()-$lasttime) >= $opts{self_clock}) { rpcheck(); }
262 $inbytes += length($in); # increase parsed byte count
263 $in =~ s/[\r\n]//g; # strip all \r and \n
265 my @arg = split(/\s/,$in); # split into "words"
266 my $usernick = substr((split(/!/,$arg[0]))[0],1);
267 # logged in char name of nickname, or undef if nickname is not online
268 my $username = finduser($usernick);
269 if (lc($arg[0]) eq 'ping') { sts("PONG $arg[1]",1); }
270 elsif (lc($arg[0]) eq 'error') {
271 # uh oh, we've been disconnected from the server, possibly before we've
272 # logged in the users in %auto_login. so, we'll set those users' online
273 # flags to 1, rewrite db, and attempt to reconnect (if that's wanted of
275 $rps{$_}{online}=1 for keys(%auto_login);
279 $arg[1] = lc($arg[1]); # original case no longer matters
280 if ($arg[1] eq '433' && $opts{botnick} eq $arg[3]) {
282 sts("NICK $opts{botnick}");
284 elsif ($arg[1] eq 'join') {
285 # %onchan holds time user joined channel. used for the advertisement ban
286 $onchan{$usernick}=time();
287 if ($opts{'detectsplits'} && exists($split{substr($arg[0],1)})) {
288 delete($split{substr($arg[0],1)});
290 elsif ($opts{botnick} eq $usernick) {
291 sts("WHO $opts{botchan}");
292 (my $opcmd = $opts{botopcmd}) =~ s/%botnick%/$opts{botnick}/eg;
294 $lasttime = time(); # start rpcheck()
297 elsif ($arg[1] eq 'quit') {
298 # if we see our nick come open, grab it (skipping queue)
299 if ($usernick eq $primnick) { sts("NICK $primnick",1); }
300 elsif ($opts{'detectsplits'} &&
301 "@arg[2..$#arg]" =~ /^:\S+\.\S+ \S+\.\S+$/) {
302 if (defined($username)) { # user was online
303 $split{substr($arg[0],1)}{time}=time();
304 $split{substr($arg[0],1)}{account}=$username;
308 penalize($username,"quit");
310 delete($onchan{$usernick});
312 elsif ($arg[1] eq 'nick') {
313 # if someone (nickserv) changes our nick for us, update $opts{botnick}
314 if ($usernick eq $opts{botnick}) {
315 $opts{botnick} = substr($arg[2],1);
317 # if we see our nick come open, grab it (skipping queue), unless it was
318 # us who just lost it
319 elsif ($usernick eq $primnick) { sts("NICK $primnick",1); }
321 penalize($username,"nick",$arg[2]);
322 $onchan{substr($arg[2],1)} = delete($onchan{$usernick});
325 elsif ($arg[1] eq 'part') {
326 penalize($username,"part");
327 delete($onchan{$usernick});
329 elsif ($arg[1] eq 'kick') {
331 penalize(finduser($usernick),"kick");
332 delete($onchan{$usernick});
334 # don't penalize /notices to the bot
335 elsif ($arg[1] eq 'notice' && $arg[2] ne $opts{botnick}) {
336 penalize($username,"notice",length("@arg[3..$#arg]")-1);
338 elsif ($arg[1] eq '001') {
339 # send our identify command, set our usermode, join channel
340 sts($opts{botident});
341 sts("MODE $opts{botnick} :$opts{botmodes}");
342 sts("JOIN $opts{botchan}");
343 $opts{botchan} =~ s/ .*//; # strip channel key if present
345 elsif ($arg[1] eq '315') {
346 # 315 is /WHO end. report who we automagically signed online iff it will
348 if (keys(%auto_login)) {
349 # not a true measure of size, but easy
350 if (length("%auto_login") < 1024 && $opts{senduserlist}) {
351 chanmsg(scalar(keys(%auto_login))." users matching ".
352 scalar(keys(%prev_online))." hosts automatically ".
353 "logged in; accounts: ".join(", ",keys(%auto_login)));
356 chanmsg(scalar(keys(%auto_login))." users matching ".
357 scalar(keys(%prev_online))." hosts automatically ".
360 if ($opts{voiceonlogin}) {
361 my @vnicks = map { $rps{$_}{nick} } keys(%auto_login);
363 sts("MODE $opts{botchan} +".
364 ('v' x $opts{modesperline})." ".
365 join(" ",@vnicks[0..$opts{modesperline}-1]));
366 splice(@vnicks,0,$opts{modesperline});
370 else { chanmsg("0 users qualified for auto login."); }
374 elsif ($arg[1] eq '005') {
375 if ("@arg" =~ /MODES=(\d+)/) { $opts{modesperline}=$1; }
377 elsif ($arg[1] eq '352') {
379 # 352 is one line of /WHO. check that the nick!user@host exists as a key
380 # in %prev_online, the list generated in loaddb(). the value is the user
382 $onchan{$arg[7]}=time();
383 if (exists($prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]})) {
384 $rps{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}{online} = 1;
385 $auto_login{$prev_online{$arg[7]."!".$arg[4]."\@".$arg[5]}}=1;
388 elsif ($arg[1] eq 'privmsg') {
389 $arg[0] = substr($arg[0],1); # strip leading : from privmsgs
390 if (lc($arg[2]) eq lc($opts{botnick})) { # to us, not channel
391 $arg[3] = lc(substr($arg[3],1)); # lowercase, strip leading :
392 if ($arg[3] eq "\1version\1") {
393 notice("\1VERSION IRPG bot v$version by jotun; ".
394 "http://idlerpg.net/\1",$usernick);
396 elsif ($arg[3] eq "peval") {
397 if (!ha($username) || ($opts{ownerpevalonly} &&
398 $opts{owner} ne $username)) {
399 privmsg("You don't have access to PEVAL.", $usernick);
402 my @peval = eval "@arg[4..$#arg]";
403 if (@peval >= 4 || length("@peval") > 1024) {
404 privmsg("Command produced too much output to send ".
405 "outright; queueing ".length("@peval").
406 " bytes in ".scalar(@peval)." items. Use ".
407 "CLEARQ to clear queue if needed.",$usernick,1);
408 privmsg($_,$usernick) for @peval;
410 else { privmsg($_,$usernick, 1) for @peval; }
411 privmsg("EVAL ERROR: $@", $usernick, 1) if $@;
414 elsif ($arg[3] eq "register") {
415 if (defined $username) {
416 privmsg("Sorry, you are already online as $username.",
420 if ($#arg < 6 || $arg[6] eq "") {
421 privmsg("Try: REGISTER <char name> <password> <class>",
423 privmsg("IE : REGISTER Poseidon MyPassword God of the ".
427 privmsg("Sorry, new accounts may not be registered ".
428 "while the bot is in pause mode; please wait ".
429 "a few minutes and try again.",$usernick);
431 elsif (exists $rps{$arg[4]} || ($opts{casematters} &&
432 scalar(grep { lc($arg[4]) eq lc($_) } keys(%rps)))) {
433 privmsg("Sorry, that character name is already in use.",
436 elsif (lc($arg[4]) eq lc($opts{botnick}) ||
437 lc($arg[4]) eq lc($primnick)) {
438 privmsg("Sorry, that character name cannot be ".
439 "registered.",$usernick);
441 elsif (!exists($onchan{$usernick})) {
442 privmsg("Sorry, you're not in $opts{botchan}.",
445 elsif (length($arg[4]) > 16 || length($arg[4]) < 1) {
446 privmsg("Sorry, character names must be < 17 and > 0 ".
447 "chars long.", $usernick);
449 elsif ($arg[4] =~ /^#/) {
450 privmsg("Sorry, character names may not begin with #.",
453 elsif ($arg[4] =~ /\001/) {
454 privmsg("Sorry, character names may not include ".
455 "character \\001.",$usernick);
457 elsif ($opts{noccodes} && ($arg[4] =~ /[[:cntrl:]]/ ||
458 "@arg[6..$#arg]" =~ /[[:cntrl:]]/)) {
459 privmsg("Sorry, neither character names nor classes ".
460 "may include control codes.",$usernick);
462 elsif ($opts{nononp} && ($arg[4] =~ /[[:^print:]]/ ||
463 "@arg[6..$#arg]" =~ /[[:^print:]]/)) {
464 privmsg("Sorry, neither character names nor classes ".
465 "may include non-printable chars.",$usernick);
467 elsif (length("@arg[6..$#arg]") > 30) {
468 privmsg("Sorry, character classes must be < 31 chars ".
471 elsif (time() == $lastreg) {
472 privmsg("Wait 1 second and try again.",$usernick);
475 if ($opts{voiceonlogin}) {
476 sts("MODE $opts{botchan} +v :$usernick");
480 $rps{$arg[4]}{next} = $opts{rpbase};
481 $rps{$arg[4]}{class} = "@arg[6..$#arg]";
482 $rps{$arg[4]}{level} = 0;
483 $rps{$arg[4]}{online} = 1;
484 $rps{$arg[4]}{nick} = $usernick;
485 $rps{$arg[4]}{userhost} = $arg[0];
486 $rps{$arg[4]}{created} = time();
487 $rps{$arg[4]}{lastlogin} = time();
488 $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
489 $rps{$arg[4]}{x} = int(rand($opts{mapx}));
490 $rps{$arg[4]}{y} = int(rand($opts{mapy}));
491 $rps{$arg[4]}{alignment}="n";
492 $rps{$arg[4]}{isadmin} = 0;
493 for my $item ("ring","amulet","charm","weapon","helm",
494 "tunic","pair of gloves","shield",
495 "set of leggings","pair of boots") {
496 $rps{$arg[4]}{item}{$item} = 0;
498 for my $pen ("pen_mesg","pen_nick","pen_part",
499 "pen_kick","pen_quit","pen_quest",
500 "pen_logout","pen_logout") {
501 $rps{$arg[4]}{$pen} = 0;
503 chanmsg("Welcome $usernick\'s new player $arg[4], the ".
504 "@arg[6..$#arg]! Next level in ".
505 duration($opts{rpbase}).".");
506 privmsg("Success! Account $arg[4] created. You have ".
507 "$opts{rpbase} seconds idleness until you ".
508 "reach level 1. ", $usernick);
509 privmsg("NOTE: The point of the game is to see who ".
510 "can idle the longest. As such, talking in ".
511 "the channel, parting, quitting, and changing ".
512 "nicks all penalize you.",$usernick);
513 if ($opts{phonehome}) {
514 my $tempsock = IO::Socket::INET->new(PeerAddr=>
515 "jotun.ultrazone.org:80");
518 "GET /g7/count.php?new=1 HTTP/1.1\r\n".
519 "Host: jotun.ultrazone.org:80\r\n\r\n";
527 elsif ($arg[3] eq "delold") {
528 if (!ha($username)) {
529 privmsg("You don't have access to DELOLD.", $usernick);
531 # insure it is a number
532 elsif ($arg[4] !~ /^[\d\.]+$/) {
533 privmsg("Try: DELOLD <# of days>", $usernick, 1);
536 my @oldaccounts = grep { (time()-$rps{$_}{lastlogin}) >
538 !$rps{$_}{online} } keys(%rps);
539 delete(@rps{@oldaccounts});
540 chanmsg(scalar(@oldaccounts)." accounts not accessed in ".
541 "the last $arg[4] days removed by $arg[0].");
544 elsif ($arg[3] eq "del") {
545 if (!ha($username)) {
546 privmsg("You don't have access to DEL.", $usernick);
548 elsif (!defined($arg[4])) {
549 privmsg("Try: DEL <char name>", $usernick, 1);
551 elsif (!exists($rps{$arg[4]})) {
552 privmsg("No such account $arg[4].", $usernick, 1);
555 delete($rps{$arg[4]});
556 chanmsg("Account $arg[4] removed by $arg[0].");
559 elsif ($arg[3] eq "mkadmin") {
560 if (!ha($username) || ($opts{owneraddonly} &&
561 $opts{owner} ne $username)) {
562 privmsg("You don't have access to MKADMIN.", $usernick);
564 elsif (!defined($arg[4])) {
565 privmsg("Try: MKADMIN <char name>", $usernick, 1);
567 elsif (!exists($rps{$arg[4]})) {
568 privmsg("No such account $arg[4].", $usernick, 1);
571 $rps{$arg[4]}{isadmin}=1;
572 privmsg("Account $arg[4] is now a bot admin.",$usernick, 1);
573 if ($opts{voiceonlogin}) {
574 sts("MODE $opts{botchan} +o :$usernick");
578 elsif ($arg[3] eq "deladmin") {
579 if (!ha($username) || ($opts{ownerdelonly} &&
580 $opts{owner} ne $username)) {
581 privmsg("You don't have access to DELADMIN.", $usernick);
583 elsif (!defined($arg[4])) {
584 privmsg("Try: DELADMIN <char name>", $usernick, 1);
586 elsif (!exists($rps{$arg[4]})) {
587 privmsg("No such account $arg[4].", $usernick, 1);
589 elsif ($arg[4] eq $opts{owner}) {
590 privmsg("Cannot DELADMIN owner account.", $usernick, 1);
593 $rps{$arg[4]}{isadmin}=0;
594 privmsg("Account $arg[4] is no longer a bot admin.",
596 if ($opts{voiceonlogin}) {
597 sts("MODE $opts{botchan} -o :$usernick");
601 elsif ($arg[3] eq "hog") {
602 if (!ha($username)) {
603 privmsg("You don't have access to HOG.", $usernick);
606 chanmsg("$usernick has summoned the Hand of God.");
610 elsif ($arg[3] eq "rehash") {
611 if (!ha($username)) {
612 privmsg("You don't have access to REHASH.", $usernick);
616 privmsg("Reread config file.",$usernick,1);
617 $opts{botchan} =~ s/ .*//; # strip channel key if present
620 elsif ($arg[3] eq "chpass") {
621 if (!ha($username)) {
622 privmsg("You don't have access to CHPASS.", $usernick);
624 elsif (!defined($arg[5])) {
625 privmsg("Try: CHPASS <char name> <new pass>", $usernick, 1);
627 elsif (!exists($rps{$arg[4]})) {
628 privmsg("No such username $arg[4].", $usernick, 1);
631 $rps{$arg[4]}{pass} = crypt($arg[5],mksalt());
632 privmsg("Password for $arg[4] changed.", $usernick, 1);
635 elsif ($arg[3] eq "chuser") {
636 if (!ha($username)) {
637 privmsg("You don't have access to CHUSER.", $usernick);
639 elsif (!defined($arg[5])) {
640 privmsg("Try: CHUSER <char name> <new char name>",
643 elsif (!exists($rps{$arg[4]})) {
644 privmsg("No such username $arg[4].", $usernick, 1);
646 elsif (exists($rps{$arg[5]})) {
647 privmsg("Username $arg[5] is already taken.", $usernick,1);
650 $rps{$arg[5]} = delete($rps{$arg[4]});
651 privmsg("Username for $arg[4] changed to $arg[5].",
655 elsif ($arg[3] eq "chclass") {
656 if (!ha($username)) {
657 privmsg("You don't have access to CHCLASS.", $usernick);
659 elsif (!defined($arg[5])) {
660 privmsg("Try: CHCLASS <char name> <new char class>",
663 elsif (!exists($rps{$arg[4]})) {
664 privmsg("No such username $arg[4].", $usernick, 1);
667 $rps{$arg[4]}{class} = "@arg[5..$#arg]";
668 privmsg("Class for $arg[4] changed to @arg[5..$#arg].",
672 elsif ($arg[3] eq "push") {
673 if (!ha($username)) {
674 privmsg("You don't have access to PUSH.", $usernick);
676 # insure it's a positive or negative, integral number of seconds
677 elsif ($arg[5] !~ /^\-?\d+$/) {
678 privmsg("Try: PUSH <char name> <seconds>", $usernick, 1);
680 elsif (!exists($rps{$arg[4]})) {
681 privmsg("No such username $arg[4].", $usernick, 1);
683 elsif ($arg[5] > $rps{$arg[4]}{next}) {
684 privmsg("Time to level for $arg[4] ($rps{$arg[4]}{next}s) ".
685 "is lower than $arg[5]; setting TTL to 0.",
687 chanmsg("$usernick has pushed $arg[4] $rps{$arg[4]}{next} ".
688 "seconds toward level ".($rps{$arg[4]}{level}+1));
689 $rps{$arg[4]}{next}=0;
692 $rps{$arg[4]}{next} -= $arg[5];
693 chanmsg("$usernick has pushed $arg[4] $arg[5] seconds ".
694 "toward level ".($rps{$arg[4]}{level}+1).". ".
695 "$arg[4] reaches next level in ".
696 duration($rps{$arg[4]}{next}).".");
699 elsif ($arg[3] eq "logout") {
700 if (defined($username)) {
701 penalize($username,"logout");
704 privmsg("You are not logged in.", $usernick);
707 elsif ($arg[3] eq "quest") {
708 if (!@{$quest{questers}}) {
709 privmsg("There is no active quest.",$usernick);
711 elsif ($quest{type} == 1) {
712 privmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
713 "$quest{questers}->[3] are on a quest to ".
714 "$quest{text}. Quest to complete in ".
715 duration($quest{qtime}-time()).".",$usernick);
717 elsif ($quest{type} == 2) {
718 privmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
719 "$quest{questers}->[3] are on a quest to ".
720 "$quest{text}. Participants must first reach ".
721 "[$quest{p1}->[0],$quest{p1}->[1]], then ".
722 "[$quest{p2}->[0],$quest{p2}->[1]].".
723 ($opts{mapurl}?" See $opts{mapurl} to monitor ".
724 "their journey's progress.":""),$usernick);
727 elsif ($arg[3] eq "status" && $opts{statuscmd}) {
728 if (!defined($username)) {
729 privmsg("You are not logged in.", $usernick);
731 # argument is optional
732 elsif ($arg[4] && !exists($rps{$arg[4]})) {
733 privmsg("No such user.",$usernick);
735 elsif ($arg[4]) { # optional 'user' argument
736 privmsg("$arg[4]: Level $rps{$arg[4]}{level} ".
737 "$rps{$arg[4]}{class}; Status: O".
738 ($rps{$arg[4]}{online}?"n":"ff")."line; ".
739 "TTL: ".duration($rps{$arg[4]}{next})."; ".
740 "Idled: ".duration($rps{$arg[4]}{idled}).
741 "; Item sum: ".itemsum($arg[4]),$usernick);
743 else { # no argument, look up this user
744 privmsg("$username: Level $rps{$username}{level} ".
745 "$rps{$username}{class}; Status: O".
746 ($rps{$username}{online}?"n":"ff")."line; ".
747 "TTL: ".duration($rps{$username}{next})."; ".
748 "Idled: ".duration($rps{$username}{idled})."; ".
749 "Item sum: ".itemsum($username),$usernick);
752 elsif ($arg[3] eq "whoami") {
753 if (!defined($username)) {
754 privmsg("You are not logged in.", $usernick);
757 privmsg("You are $username, the level ".
758 $rps{$username}{level}." $rps{$username}{class}. ".
759 "Next level in ".duration($rps{$username}{next}),
763 elsif ($arg[3] eq "newpass") {
764 if (!defined($username)) {
765 privmsg("You are not logged in.", $usernick)
767 elsif (!defined($arg[4])) {
768 privmsg("Try: NEWPASS <new password>", $usernick);
771 $rps{$username}{pass} = crypt($arg[4],mksalt());
772 privmsg("Your password was changed.",$usernick);
775 elsif ($arg[3] eq "align") {
776 if (!defined($username)) {
777 privmsg("You are not logged in.", $usernick)
779 elsif (!defined($arg[4]) || (lc($arg[4]) ne "good" &&
780 lc($arg[4]) ne "neutral" && lc($arg[4]) ne "evil")) {
781 privmsg("Try: ALIGN <good|neutral|evil>", $usernick);
784 $rps{$username}{alignment} = substr(lc($arg[4]),0,1);
785 chanmsg("$username has changed alignment to: ".lc($arg[4]).
787 privmsg("Your alignment was changed to ".lc($arg[4]).".",
791 elsif ($arg[3] eq "removeme") {
792 if (!defined($username)) {
793 privmsg("You are not logged in.", $usernick)
796 privmsg("Account $username removed.",$usernick);
797 chanmsg("$arg[0] removed his account, $username, the ".
798 $rps{$username}{class}.".");
799 delete($rps{$username});
802 elsif ($arg[3] eq "help") {
803 if (!ha($username)) {
804 privmsg("For information on IRPG bot commands, see ".
805 $opts{helpurl}, $usernick);
808 privmsg("Help URL is $opts{helpurl}", $usernick, 1);
809 privmsg("Admin commands URL is $opts{admincommurl}",
813 elsif ($arg[3] eq "die") {
814 if (!ha($username)) {
815 privmsg("You do not have access to DIE.", $usernick);
818 $opts{reconnect} = 0;
820 sts("QUIT :DIE from $arg[0]",1);
823 elsif ($arg[3] eq "reloaddb") {
824 if (!ha($username)) {
825 privmsg("You do not have access to RELOADDB.", $usernick);
827 elsif (!$pausemode) {
828 privmsg("ERROR: Can only use LOADDB while in PAUSE mode.",
833 privmsg("Reread player database file; ".scalar(keys(%rps)).
834 " accounts loaded.",$usernick,1);
837 elsif ($arg[3] eq "backup") {
838 if (!ha($username)) {
839 privmsg("You do not have access to BACKUP.", $usernick);
843 privmsg("$opts{dbfile} copied to ".
844 ".dbbackup/$opts{dbfile}".time(),$usernick,1);
847 elsif ($arg[3] eq "pause") {
848 if (!ha($username)) {
849 privmsg("You do not have access to PAUSE.", $usernick);
852 $pausemode = $pausemode ? 0 : 1;
853 privmsg("PAUSE_MODE set to $pausemode.",$usernick,1);
856 elsif ($arg[3] eq "silent") {
857 if (!ha($username)) {
858 privmsg("You do not have access to SILENT.", $usernick);
860 elsif (!defined($arg[4]) || $arg[4] < 0 || $arg[4] > 3) {
861 privmsg("Try: SILENT <mode>", $usernick,1);
864 $silentmode = $arg[4];
865 privmsg("SILENT_MODE set to $silentmode.",$usernick,1);
868 elsif ($arg[3] eq "jump") {
869 if (!ha($username)) {
870 privmsg("You do not have access to JUMP.", $usernick);
872 elsif (!defined($arg[4])) {
873 privmsg("Try: JUMP <server[:port]>", $usernick, 1);
877 sts("QUIT :JUMP to $arg[4] from $arg[0]");
878 unshift(@{$opts{servers}},$arg[4]);
884 elsif ($arg[3] eq "restart") {
885 if (!ha($username)) {
886 privmsg("You do not have access to RESTART.", $usernick);
890 sts("QUIT :RESTART from $arg[0]",1);
895 elsif ($arg[3] eq "clearq") {
896 if (!ha($username)) {
897 privmsg("You do not have access to CLEARQ.", $usernick);
901 chanmsg("Outgoing message queue cleared by $arg[0].");
902 privmsg("Outgoing message queue cleared.",$usernick,1);
905 elsif ($arg[3] eq "info") {
907 if (!ha($username) && $opts{allowuserinfo}) {
908 $info = "IRPG bot v$version by jotun, ".
909 "http://idlerpg.net/. On via server: ".
910 $opts{servers}->[0].". Admins online: ".
911 join(", ", map { $rps{$_}{nick} }
912 grep { $rps{$_}{isadmin} &&
913 $rps{$_}{online} } keys(%rps)).".";
914 privmsg($info, $usernick);
916 elsif (!ha($username) && !$opts{allowuserinfo}) {
917 privmsg("You do not have access to INFO.", $usernick);
921 $queuedbytes += (length($_)+2) for @queue; # +2 = \r\n
923 "%.2fkb sent, %.2fkb received in %s. %d IRPG users ".
924 "online of %d total users. %d accounts created since ".
925 "startup. PAUSE_MODE is %d, SILENT_MODE is %d. ".
926 "Outgoing queue is %d bytes in %d items. On via: %s. ".
927 "Admins online: %s.",
930 duration(time()-$^T),
931 scalar(grep { $rps{$_}{online} } keys(%rps)),
939 join(", ",map { $rps{$_}{nick} }
940 grep { $rps{$_}{isadmin} && $rps{$_}{online} }
942 privmsg($info, $usernick, 1);
945 elsif ($arg[3] eq "login") {
946 if (defined($username)) {
947 notice("Sorry, you are already online as $username.",
951 if ($#arg < 5 || $arg[5] eq "") {
952 notice("Try: LOGIN <username> <password>", $usernick);
954 elsif (!exists $rps{$arg[4]}) {
955 notice("Sorry, no such account name. Note that ".
956 "account names are case sensitive.",$usernick);
958 elsif (!exists $onchan{$usernick}) {
959 notice("Sorry, you're not in $opts{botchan}.",
962 elsif ($rps{$arg[4]}{pass} ne
963 crypt($arg[5],$rps{$arg[4]}{pass})) {
964 notice("Wrong password.", $usernick);
967 if ($opts{voiceonlogin}) {
968 sts("MODE $opts{botchan} +v :$usernick");
969 if($rps{$arg[4]}{isadmin} > 0){
970 sts("MODE $opts{botchan} +o :$usernick");
974 $rps{$arg[4]}{online} = 1;
975 $rps{$arg[4]}{nick} = $usernick;
976 $rps{$arg[4]}{userhost} = $arg[0];
977 $rps{$arg[4]}{lastlogin} = time();
978 chanmsg("$arg[4], the level $rps{$arg[4]}{level} ".
979 "$rps{$arg[4]}{class}, is now online from ".
980 "nickname $usernick. Next level in ".
981 duration($rps{$arg[4]}{next}).".");
982 notice("Logon successful. Next level in ".
983 duration($rps{$arg[4]}{next}).".", $usernick);
988 # penalize returns true if user was online and successfully penalized.
989 # if the user is not logged in, then penalize() fails. so, if user is
990 # offline, and they say something including "http:", and they've been on
991 # the channel less than 90 seconds, and the http:-style ban is on, then
992 # check to see if their url is in @{$opts{okurl}}. if not, kickban them
993 elsif (!penalize($username,"privmsg",length("@arg[3..$#arg]")) &&
994 index(lc("@arg[3..$#arg]"),"http:") != -1 &&
995 (time()-$onchan{$usernick}) < 90 && $opts{doban}) {
997 for (@{$opts{okurl}}) {
998 if (index(lc("@arg[3..$#arg]"),lc($_)) != -1) { $isokurl = 1; }
1001 sts("MODE $opts{botchan} +b $arg[0]");
1002 sts("KICK $opts{botchan} $usernick :No advertising; ban will ".
1003 "be lifted within the hour.");
1004 push(@bans,$arg[0]) if @bans < 12;
1010 sub sts { # send to server
1011 my($text,$skipq) = @_;
1014 print $sock "$text\r\n";
1015 $outbytes += length($text) + 2;
1019 # something is wrong. the socket is closed. clear the queue
1021 debug("\$sock isn't writeable in sts(), cleared outgoing queue.\n");
1027 debug(sprintf("(q%03d) = %s\n",$#queue,$text));
1031 sub fq { # deliver message(s) from queue
1033 ++$freemessages if $freemessages < 4;
1037 for (0..$freemessages) {
1038 last() if !@queue; # no messages left to send
1039 # lower number of "free" messages we have left
1040 my $line=shift(@queue);
1041 # if we have already sent one message, and the next message to be sent
1042 # plus the previous messages we have sent this call to fq() > 768 bytes,
1043 # then requeue this message and return. we don't want to flood off,
1045 if ($_ != 0 && (length($line)+$sentbytes) > 768) {
1046 unshift(@queue,$line);
1050 debug("(fm$freemessages) -> $line");
1051 --$freemessages if $freemessages > 0;
1052 print $sock "$line\r\n";
1053 $sentbytes += length($line) + 2;
1057 debug("Disconnected: cleared outgoing message queue.");
1060 $outbytes += length($line) + 2;
1064 sub duration { # return human duration of seconds
1066 return "NA ($s)" if $s !~ /^\d+$/;
1067 return sprintf("%d day%s, %02d:%02d:%02d",$s/86400,int($s/86400)==1?"":"s",
1068 ($s%86400)/3600,($s%3600)/60,($s%60));
1071 sub ts { # timestamp
1072 my @ts = localtime(time());
1073 return sprintf("[%02d/%02d/%02d %02d:%02d:%02d] ",
1074 $ts[4]+1,$ts[3],$ts[5]%100,$ts[2],$ts[1],$ts[0]);
1077 sub hog { # summon the hand of god
1078 my @players = grep { $rps{$_}{online} } keys(%rps);
1079 my $player = $players[rand(@players)];
1080 my $win = int(rand(5));
1081 my $time = int(((5 + int(rand(71)))/100) * $rps{$player}{next});
1083 chanmsg(clog("Verily I say unto thee, the Heavens have burst forth, ".
1084 "and the blessed hand of God carried $player ".
1085 duration($time)." toward level ".($rps{$player}{level}+1).
1087 $rps{$player}{next} -= $time;
1090 chanmsg(clog("Thereupon He stretched out His little finger among them ".
1091 "and consumed $player with fire, slowing the heathen ".
1092 duration($time)." from level ".($rps{$player}{level}+1).
1094 $rps{$player}{next} += $time;
1096 chanmsg("$player reaches next level in ".duration($rps{$player}{next}).".");
1099 sub rpcheck { # check levels, update database
1100 # check splits hash to see if any split users have expired
1101 checksplits() if $opts{detectsplits};
1102 # send out $freemessages lines of text from the outgoing message queue
1104 # clear registration limiting
1106 my $online = scalar(grep { $rps{$_}{online} } keys(%rps));
1107 # there's really nothing to do here if there are no online users
1108 return unless $online;
1109 my $onlineevil = scalar(grep { $rps{$_}{online} &&
1110 $rps{$_}{alignment} eq "e" } keys(%rps));
1111 my $onlinegood = scalar(grep { $rps{$_}{online} &&
1112 $rps{$_}{alignment} eq "g" } keys(%rps));
1113 if (!$opts{noscale}) {
1114 if (rand((20*86400)/$opts{self_clock}) < $online) { hog(); }
1115 if (rand((24*86400)/$opts{self_clock}) < $online) { team_battle(); }
1116 if (rand((8*86400)/$opts{self_clock}) < $online) { calamity(); }
1117 if (rand((4*86400)/$opts{self_clock}) < $online) { godsend(); }
1120 hog() if rand(4000) < 1;
1121 team_battle() if rand(4000) < 1;
1122 calamity() if rand(4000) < 1;
1123 godsend() if rand(2000) < 1;
1125 if (rand((8*86400)/$opts{self_clock}) < $onlineevil) { evilness(); }
1126 if (rand((12*86400)/$opts{self_clock}) < $onlinegood) { goodness(); }
1130 # statements using $rpreport do not bother with scaling by the clock because
1131 # $rpreport is adjusted by the number of seconds since last rpcheck()
1132 if ($rpreport%120==0 && $opts{writequestfile}) { writequestfile(); }
1133 if (time() > $quest{qtime}) {
1134 if (!@{$quest{questers}}) { quest(); }
1135 elsif ($quest{type} == 1) {
1136 chanmsg(clog(join(", ",(@{$quest{questers}})[0..2]).", and ".
1137 "$quest{questers}->[3] have blessed the realm by ".
1138 "completing their quest! 25% of their burden is ".
1140 for (@{$quest{questers}}) {
1141 $rps{$_}{next} = int($rps{$_}{next} * .75);
1143 undef(@{$quest{questers}});
1144 $quest{qtime} = time() + 21600;
1146 # quest type 2 awards are handled in moveplayers()
1148 if ($rpreport && $rpreport%36000==0) { # 10 hours
1149 my @u = sort { $rps{$b}{level} <=> $rps{$a}{level} ||
1150 $rps{$a}{next} <=> $rps{$b}{next} } keys(%rps);
1151 chanmsg("Idle RPG Top Players:") if @u;
1154 chanmsg("$u[$i], the level $rps{$u[$i]}{level} ".
1155 "$rps{$u[$i]}{class}, is #" . ($i + 1) . "! Next level in ".
1156 (duration($rps{$u[$i]}{next})).".");
1160 if ($rpreport%3600==0 && $rpreport) { # 1 hour
1161 my @players = grep { $rps{$_}{online} &&
1162 $rps{$_}{level} > 44 } keys(%rps);
1163 # 20% of all players must be level 45+
1164 if ((scalar(@players)/scalar(grep { $rps{$_}{online} } keys(%rps))) > .15) {
1165 challenge_opp($players[int(rand(@players))]);
1168 sts("MODE $opts{botchan} -bbbb :@bans[0..3]");
1172 if ($rpreport%1800==0) { # 30 mins
1173 if ($opts{botnick} ne $primnick) {
1174 sts($opts{botghostcmd}) if $opts{botghostcmd};
1175 sts("NICK $primnick");
1178 if ($rpreport%600==0 && $pausemode) { # warn every 10m
1179 chanmsg("WARNING: Cannot write database in PAUSE mode!");
1181 # do not write in pause mode, and do not write if not yet connected. (would
1182 # log everyone out if the bot failed to connect. $lasttime = time() on
1183 # successful join to $opts{botchan}, initial value is 1). if fails to open
1184 # $opts{dbfile}, will not update $lasttime and so should have correct values
1185 # on next rpcheck().
1186 if ($lasttime != 1) {
1188 for my $k (keys(%rps)) {
1189 if ($rps{$k}{online} && exists $rps{$k}{nick} &&
1190 $rps{$k}{nick} && exists $onchan{$rps{$k}{nick}}) {
1191 $rps{$k}{next} -= ($curtime - $lasttime);
1192 $rps{$k}{idled} += ($curtime - $lasttime);
1193 if ($rps{$k}{next} < 1) {
1195 if ($rps{$k}{level} > 60) {
1196 $rps{$k}{next} = int(($opts{rpbase} *
1197 ($opts{rpstep}**60)) +
1198 (86400*($rps{$k}{level} - 60)));
1201 $rps{$k}{next} = int($opts{rpbase} *
1202 ($opts{rpstep}**$rps{$k}{level}));
1204 chanmsg("$k, the $rps{$k}{class}, has attained level ".
1205 "$rps{$k}{level}! Next level in ".
1206 duration($rps{$k}{next}).".");
1211 # attempt to make sure this is an actual user, and not just an
1212 # artifact of a bad PEVAL
1214 if (!$pausemode && $rpreport%60==0) { writedb(); }
1215 $rpreport += $opts{self_clock};
1216 $lasttime = $curtime;
1220 sub challenge_opp { # pit argument player against random player
1222 if ($rps{$u}{level} < 25) { return unless rand(4) < 1; }
1223 my @opps = grep { $rps{$_}{online} && $u ne $_ } keys(%rps);
1224 return unless @opps;
1225 my $opp = $opps[int(rand(@opps))];
1226 $opp = $primnick if rand(@opps+1) < 1;
1227 my $mysum = itemsum($u,1);
1228 my $oppsum = itemsum($opp,1);
1229 my $myroll = int(rand($mysum));
1230 my $opproll = int(rand($oppsum));
1231 if ($myroll >= $opproll) {
1232 my $gain = ($opp eq $primnick)?20:int($rps{$opp}{level}/4);
1233 $gain = 7 if $gain < 7;
1234 $gain = int(($gain/100)*$rps{$u}{next});
1235 chanmsg(clog("$u [$myroll/$mysum] has challenged $opp [$opproll/".
1236 "$oppsum] in combat and won! ".duration($gain)." is ".
1237 "removed from $u\'s clock."));
1238 $rps{$u}{next} -= $gain;
1239 chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
1240 my $csfactor = $rps{$u}{alignment} eq "g" ? 50 :
1241 $rps{$u}{alignment} eq "e" ? 20 :
1243 if (rand($csfactor) < 1 && $opp ne $primnick) {
1244 $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
1245 chanmsg(clog("$u has dealt $opp a Critical Strike! ".
1246 duration($gain)." is added to $opp\'s clock."));
1247 $rps{$opp}{next} += $gain;
1248 chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).
1251 elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
1252 my @items = ("ring","amulet","charm","weapon","helm","tunic",
1253 "pair of gloves","set of leggings","shield",
1255 my $type = $items[rand(@items)];
1256 if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) {
1257 chanmsg(clog("In the fierce battle, $opp dropped his level ".
1258 int($rps{$opp}{item}{$type})." $type! $u picks ".
1259 "it up, tossing his old level ".
1260 int($rps{$u}{item}{$type})." $type to $opp."));
1261 my $tempitem = $rps{$u}{item}{$type};
1262 $rps{$u}{item}{$type}=$rps{$opp}{item}{$type};
1263 $rps{$opp}{item}{$type} = $tempitem;
1268 my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
1269 $gain = 7 if $gain < 7;
1270 $gain = int(($gain/100)*$rps{$u}{next});
1271 chanmsg(clog("$u [$myroll/$mysum] has challenged $opp [$opproll/".
1272 "$oppsum] in combat and lost! ".duration($gain)." is ".
1273 "added to $u\'s clock."));
1274 $rps{$u}{next} += $gain;
1275 chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
1277 debug("interrogation");
1278 my $csfactor = $rps{$opp}{alignment} eq "g" ? 50 :
1279 $rps{$opp}{alignment} eq "e" ? 20 :
1281 if (rand($csfactor) < 1) {
1282 $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
1283 chanmsg(clog("$opp has captured and interrogated $u! ".
1284 duration($gain)." is removed from $opp\'s clock."));
1285 $rps{$opp}{next} -= $gain;
1286 chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).".");
1291 sub team_battle { # pit three players against three other players
1292 my @opp = grep { $rps{$_}{online} } keys(%rps);
1294 splice(@opp,int(rand(@opp)),1) while @opp > 6;
1295 fisher_yates_shuffle(\@opp);
1296 my $mysum = itemsum($opp[0],1) + itemsum($opp[1],1) + itemsum($opp[2],1);
1297 my $oppsum = itemsum($opp[3],1) + itemsum($opp[4],1) + itemsum($opp[5],1);
1298 my $gain = $rps{$opp[0]}{next};
1300 $gain = $rps{$opp[$p]}{next} if $gain > $rps{$opp[$p]}{next};
1302 $gain = int($gain*.20);
1303 my $myroll = int(rand($mysum));
1304 my $opproll = int(rand($oppsum));
1305 if ($myroll >= $opproll) {
1306 chanmsg(clog("$opp[0], $opp[1], and $opp[2] [$myroll/$mysum] have ".
1307 "team battled $opp[3], $opp[4], and $opp[5] [$opproll/".
1308 "$oppsum] and won! ".duration($gain)." is removed from ".
1310 $rps{$opp[0]}{next} -= $gain;
1311 $rps{$opp[1]}{next} -= $gain;
1312 $rps{$opp[2]}{next} -= $gain;
1315 chanmsg(clog("$opp[0], $opp[1], and $opp[2] [$myroll/$mysum] have ".
1316 "team battled $opp[3], $opp[4], and $opp[5] [$opproll/".
1317 "$oppsum] and lost! ".duration($gain)." is added to ".
1319 $rps{$opp[0]}{next} += $gain;
1320 $rps{$opp[1]}{next} += $gain;
1321 $rps{$opp[2]}{next} += $gain;
1325 sub find_item { # find item for argument player
1327 my @items = ("ring","amulet","charm","weapon","helm","tunic",
1328 "pair of gloves","set of leggings","shield","pair of boots");
1329 my $type = $items[rand(@items)];
1332 for my $num (1 .. int($rps{$u}{level}*1.5)) {
1333 if (rand(1.4**($num/4)) < 1) {
1337 if ($rps{$u}{level} >= 25 && rand(40) < 1) {
1338 $ulevel = 50+int(rand(25));
1339 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{helm})) {
1340 notice("The light of the gods shines down upon you! You have ".
1341 "found the level $ulevel Mattt's Omniscience Grand Crown! ".
1342 "Your enemies fall before you as you anticipate their ".
1343 "every move.",$rps{$u}{nick});
1344 $rps{$u}{item}{helm} = $ulevel."a";
1348 elsif ($rps{$u}{level} >= 25 && rand(40) < 1) {
1349 $ulevel = 50+int(rand(25));
1350 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{ring})) {
1351 notice("The light of the gods shines down upon you! You have ".
1352 "found the level $ulevel Juliet's Glorious Ring of ".
1353 "Sparkliness! You enemies are blinded by both its glory ".
1354 "and their greed as you bring desolation upon them.",
1356 $rps{$u}{item}{ring} = $ulevel."h";
1360 elsif ($rps{$u}{level} >= 30 && rand(40) < 1) {
1361 $ulevel = 75+int(rand(25));
1362 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{tunic})) {
1363 notice("The light of the gods shines down upon you! You have ".
1364 "found the level $ulevel Res0's Protectorate Plate Mail! ".
1365 "Your enemies cower in fear as their attacks have no ".
1366 "effect on you.",$rps{$u}{nick});
1367 $rps{$u}{item}{tunic} = $ulevel."b";
1371 elsif ($rps{$u}{level} >= 35 && rand(40) < 1) {
1372 $ulevel = 100+int(rand(25));
1373 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{amulet})) {
1374 notice("The light of the gods shines down upon you! You have ".
1375 "found the level $ulevel Dwyn's Storm Magic Amulet! Your ".
1376 "enemies are swept away by an elemental fury before the ".
1377 "war has even begun",$rps{$u}{nick});
1378 $rps{$u}{item}{amulet} = $ulevel."c";
1382 elsif ($rps{$u}{level} >= 40 && rand(40) < 1) {
1383 $ulevel = 150+int(rand(25));
1384 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) {
1385 notice("The light of the gods shines down upon you! You have ".
1386 "found the level $ulevel Jotun's Fury Colossal Sword! Your ".
1387 "enemies' hatred is brought to a quick end as you arc your ".
1388 "wrist, dealing the crushing blow.",$rps{$u}{nick});
1389 $rps{$u}{item}{weapon} = $ulevel."d";
1393 elsif ($rps{$u}{level} >= 45 && rand(40) < 1) {
1394 $ulevel = 175+int(rand(26));
1395 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) {
1396 notice("The light of the gods shines down upon you! You have ".
1397 "found the level $ulevel Drdink's Cane of Blind Rage! Your ".
1398 "enemies are tossed aside as you blindly swing your arm ".
1399 "around hitting stuff.",$rps{$u}{nick});
1400 $rps{$u}{item}{weapon} = $ulevel."e";
1404 elsif ($rps{$u}{level} >= 48 && rand(40) < 1) {
1405 $ulevel = 250+int(rand(51));
1406 if ($ulevel >= $level && $ulevel >
1407 int($rps{$u}{item}{"pair of boots"})) {
1408 notice("The light of the gods shines down upon you! You have ".
1409 "found the level $ulevel Mrquick's Magical Boots of ".
1410 "Swiftness! Your enemies are left choking on your dust as ".
1411 "you run from them very, very quickly.",$rps{$u}{nick});
1412 $rps{$u}{item}{"pair of boots"} = $ulevel."f";
1416 elsif ($rps{$u}{level} >= 52 && rand(40) < 1) {
1417 $ulevel = 300+int(rand(51));
1418 if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) {
1419 notice("The light of the gods shines down upon you! You have ".
1420 "found the level $ulevel Jeff's Cluehammer of Doom! Your ".
1421 "enemies are left with a sudden and intense clarity of ".
1422 "mind... even as you relieve them of it.",$rps{$u}{nick});
1423 $rps{$u}{item}{weapon} = $ulevel."g";
1427 if ($level > int($rps{$u}{item}{$type})) {
1428 notice("You found a level $level $type! Your current $type is only ".
1429 "level ".int($rps{$u}{item}{$type}).", so it seems Luck is ".
1430 "with you!",$rps{$u}{nick});
1431 $rps{$u}{item}{$type} = $level;
1434 notice("You found a level $level $type. Your current $type is level ".
1435 int($rps{$u}{item}{$type}).", so it seems Luck is against you. ".
1436 "You toss the $type.",$rps{$u}{nick});
1440 sub loaddb { # load the players database
1444 if (!open(RPS,$opts{dbfile}) && -e $opts{dbfile}) {
1445 sts("QUIT :loaddb() failed: $!");
1449 next if $l =~ /^#/; # skip comments
1450 next if $l =~ /^\s*$/; # skip empty lines
1451 my @i = split("\t",$l);
1452 print Dumper(@i) if @i != 32;
1454 sts("QUIT: Anomaly in loaddb(); line $. of $opts{dbfile} has ".
1455 "wrong fields (".scalar(@i).")");
1456 debug("Anomaly in loaddb(); line $. of $opts{dbfile} has wrong ".
1457 "fields (".scalar(@i).")",1);
1459 if (!$sock) { # if not RELOADDB
1460 if ($i[8]) { $prev_online{$i[7]}=$i[0]; } # log back in
1463 $rps{$i[0]}{isadmin},
1468 $rps{$i[0]}{userhost},
1469 $rps{$i[0]}{online},
1473 $rps{$i[0]}{pen_mesg},
1474 $rps{$i[0]}{pen_nick},
1475 $rps{$i[0]}{pen_part},
1476 $rps{$i[0]}{pen_kick},
1477 $rps{$i[0]}{pen_quit},
1478 $rps{$i[0]}{pen_quest},
1479 $rps{$i[0]}{pen_logout},
1480 $rps{$i[0]}{created},
1481 $rps{$i[0]}{lastlogin},
1482 $rps{$i[0]}{item}{amulet},
1483 $rps{$i[0]}{item}{charm},
1484 $rps{$i[0]}{item}{helm},
1485 $rps{$i[0]}{item}{"pair of boots"},
1486 $rps{$i[0]}{item}{"pair of gloves"},
1487 $rps{$i[0]}{item}{ring},
1488 $rps{$i[0]}{item}{"set of leggings"},
1489 $rps{$i[0]}{item}{shield},
1490 $rps{$i[0]}{item}{tunic},
1491 $rps{$i[0]}{item}{weapon},
1492 $rps{$i[0]}{alignment}) = (@i[1..7],($sock?$i[8]:0),@i[9..$#i]);
1495 debug("loaddb(): loaded ".scalar(keys(%rps))." accounts, ".
1496 scalar(keys(%prev_online))." previously online.");
1500 return unless $lasttime > 1;
1501 my $onlinecount = grep { $rps{$_}{online} } keys %rps;
1502 return unless $onlinecount;
1503 for (my $i=0;$i<$opts{self_clock};++$i) {
1504 # temporary hash to hold player positions, detect collisions
1506 if ($quest{type} == 2 && @{$quest{questers}}) {
1507 my $allgo = 1; # have all users reached <p1|p2>?
1508 for (@{$quest{questers}}) {
1509 if ($quest{stage}==1) {
1510 if ($rps{$_}{x} != $quest{p1}->[0] ||
1511 $rps{$_}{y} != $quest{p1}->[1]) {
1517 if ($rps{$_}{x} != $quest{p2}->[0] ||
1518 $rps{$_}{y} != $quest{p2}->[1]) {
1524 # all participants have reached point 1, now point 2
1525 if ($quest{stage}==1 && $allgo) {
1527 $allgo=0; # have not all reached p2 yet
1529 elsif ($quest{stage} == 2 && $allgo) {
1530 chanmsg(clog(join(", ",(@{$quest{questers}})[0..2]).", ".
1531 "and $quest{questers}->[3] have completed their ".
1532 "journey! 25% of their burden is eliminated."));
1533 for (@{$quest{questers}}) {
1534 $rps{$_}{next} = int($rps{$_}{next} * .75);
1536 undef(@{$quest{questers}});
1537 $quest{qtime} = time() + 21600; # next quest starts in 6 hours
1538 $quest{type} = 1; # probably not needed
1543 # load keys of %temp with online users
1544 ++@temp{grep { $rps{$_}{online} } keys(%rps)};
1545 # delete questers from list
1546 delete(@temp{@{$quest{questers}}});
1547 while ($player = each(%temp)) {
1548 $rps{$player}{x} += int(rand(3))-1;
1549 $rps{$player}{y} += int(rand(3))-1;
1550 # if player goes over edge, wrap them back around
1551 if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x}=0; }
1552 if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y}=0; }
1553 if ($rps{$player}{x} < 0) { $rps{$player}{x}=$opts{mapx}; }
1554 if ($rps{$player}{y} < 0) { $rps{$player}{y}=$opts{mapy}; }
1556 if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
1557 !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
1558 if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
1559 !$rps{$player}{isadmin} && rand(100) < 1) {
1560 chanmsg("$player encounters ".
1561 $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}.
1562 " and bows humbly.");
1564 if (rand($onlinecount) < 1) {
1565 $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
1566 collision_fight($player,
1567 $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
1571 $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
1572 $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
1575 for (@{$quest{questers}}) {
1576 if ($quest{stage} == 1) {
1577 if (rand(100) < 1) {
1578 if ($rps{$_}{x} != $quest{p1}->[0]) {
1579 $rps{$_}{x} += ($rps{$_}{x} < $quest{p1}->[0] ?
1582 if ($rps{$_}{y} != $quest{p1}->[1]) {
1583 $rps{$_}{y} += ($rps{$_}{y} < $quest{p1}->[1] ?
1588 elsif ($quest{stage}==2) {
1589 if (rand(100) < 1) {
1590 if ($rps{$_}{x} != $quest{p2}->[0]) {
1591 $rps{$_}{x} += ($rps{$_}{x} < $quest{p2}->[0] ?
1594 if ($rps{$_}{y} != $quest{p2}->[1]) {
1595 $rps{$_}{y} += ($rps{$_}{y} < $quest{p2}->[1] ?
1604 for my $player (keys(%rps)) {
1605 next unless $rps{$player}{online};
1606 $rps{$player}{x} += int(rand(3))-1;
1607 $rps{$player}{y} += int(rand(3))-1;
1608 # if player goes over edge, wrap them back around
1609 if ($rps{$player}{x} > $opts{mapx}) { $rps{$player}{x} = 0; }
1610 if ($rps{$player}{y} > $opts{mapy}) { $rps{$player}{y} = 0; }
1611 if ($rps{$player}{x} < 0) { $rps{$player}{x} = $opts{mapx}; }
1612 if ($rps{$player}{y} < 0) { $rps{$player}{y} = $opts{mapy}; }
1613 if (exists($positions{$rps{$player}{x}}{$rps{$player}{y}}) &&
1614 !$positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}) {
1615 if ($rps{$positions{$rps{$player}{x}}{$rps{$player}{y}}{user}}{isadmin} &&
1616 !$rps{$player}{isadmin} && rand(100) < 1) {
1617 chanmsg("$player encounters ".
1618 $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}.
1619 " and bows humbly.");
1621 if (rand($onlinecount) < 1) {
1622 $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=1;
1623 collision_fight($player,
1624 $positions{$rps{$player}{x}}{$rps{$player}{y}}{user});
1628 $positions{$rps{$player}{x}}{$rps{$player}{y}}{battled}=0;
1629 $positions{$rps{$player}{x}}{$rps{$player}{y}}{user}=$player;
1636 sub mksalt { # generate a random salt for passwds
1637 join '',('a'..'z','A'..'Z','0'..'9','/','.')[rand(64), rand(64)];
1640 sub chanmsg { # send a message to the channel
1641 my $msg = shift or return undef;
1642 if ($silentmode & 1) { return undef; }
1643 privmsg($msg, $opts{botchan}, shift);
1646 sub privmsg { # send a message to an arbitrary entity
1647 my $msg = shift or return undef;
1648 my $target = shift or return undef;
1650 if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
1654 while (length($msg)) {
1655 sts("PRIVMSG $target :".substr($msg,0,450),$force);
1656 substr($msg,0,450)="";
1660 sub notice { # send a notice to an arbitrary entity
1661 my $msg = shift or return undef;
1662 my $target = shift or return undef;
1664 if (($silentmode == 3 || ($target !~ /^[\+\&\#]/ && $silentmode == 2))
1668 while (length($msg)) {
1669 sts("NOTICE $target :".substr($msg,0,450),$force);
1670 substr($msg,0,450)="";
1674 sub help { # print help message
1675 (my $prog = $0) =~ s/^.*\///;
1678 usage: $prog [OPTIONS]
1679 --help, -h Print this message
1680 --verbose, -v Print verbose messages
1681 --server, -s Specify IRC server:port to connect to
1682 --botnick, -n Bot's IRC nick
1683 --botuser, -u Bot's username
1684 --botrlnm, -r Bot's real name
1685 --botchan, -c IRC channel to join
1686 --botident, -p Specify identify-to-services command
1687 --botmodes, -m Specify usermodes for the bot to set upon connect
1688 --botopcmd, -o Specify command to send to server on successful connect
1689 --botghostcmd, -g Specify command to send to server to regain primary
1690 nickname when in use
1691 --doban Advertisement ban on/off flag
1692 --okurl, -k Bot will not ban for web addresses that contain these
1694 --debug Debug on/off flag
1695 --helpurl URL to refer new users to
1696 --admincommurl URL to refer admins to
1699 --rpbase Base time to level up
1700 --rpstep Time to next level = rpbase * (rpstep ** CURRENT_LEVEL)
1701 --rppenstep PENALTY_SECS=(PENALTY*(RPPENSTEP**CURRENT_LEVEL))
1708 # is this for a battle? if so, good users get a 10% boost and evil users get
1711 return -1 unless defined $user;
1713 if ($user eq $primnick) {
1714 for my $u (keys(%rps)) {
1715 $sum = itemsum($u) if $sum < itemsum($u);
1719 if (!exists($rps{$user})) { return -1; }
1720 $sum += int($rps{$user}{item}{$_}) for keys(%{$rps{$user}{item}});
1722 return $rps{$user}{alignment} eq 'e' ? int($sum*.9) :
1723 $rps{$user}{alignment} eq 'g' ? int($sum*1.1) :
1730 # win32 doesn't daemonize (this way?)
1731 if ($^O eq "MSWin32") {
1732 print debug("Nevermind, this is Win32, no I'm not.")."\n";
1736 $SIG{CHLD} = sub { };
1737 fork() && exit(0); # kill parent
1738 POSIX::setsid() || debug("POSIX::setsid() failed: $!",1);
1739 $SIG{CHLD} = sub { };
1740 fork() && exit(0); # kill the parent as the process group leader
1741 $SIG{CHLD} = sub { };
1742 open(STDIN,'/dev/null') || debug("Cannot read /dev/null: $!",1);
1743 open(STDOUT,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
1744 open(STDERR,'>/dev/null') || debug("Cannot write to /dev/null: $!",1);
1745 # write our PID to $opts{pidfile}, or return semi-silently on failure
1746 open(PIDFILE,">$opts{pidfile}") || do {
1747 debug("Error: failed opening pid file: $!");
1754 sub calamity { # suffer a little one
1755 my @players = grep { $rps{$_}{online} } keys(%rps);
1756 return unless @players;
1757 my $player = $players[rand(@players)];
1759 my @items = ("amulet","charm","weapon","tunic","set of leggings",
1761 my $type = $items[rand(@items)];
1762 if ($type eq "amulet") {
1763 chanmsg(clog("$player fell, chipping the stone in his amulet! ".
1764 "$player\'s $type loses 10% of its effectiveness."));
1766 elsif ($type eq "charm") {
1767 chanmsg(clog("$player slipped and dropped his charm in a dirty ".
1768 "bog! $player\'s $type loses 10% of its ".
1771 elsif ($type eq "weapon") {
1772 chanmsg(clog("$player left his weapon out in the rain to rust! ".
1773 "$player\'s $type loses 10% of its effectiveness."));
1775 elsif ($type eq "tunic") {
1776 chanmsg(clog("$player spilled a level 7 shrinking potion on his ".
1777 "tunic! $player\'s $type loses 10% of its ".
1780 elsif ($type eq "shield") {
1781 chanmsg(clog("$player\'s shield was damaged by a dragon's fiery ".
1782 "breath! $player\'s $type loses 10% of its ".
1786 chanmsg(clog("$player burned a hole through his leggings while ".
1787 "ironing them! $player\'s $type loses 10% of its ".
1791 if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
1792 $rps{$player}{item}{$type} = int(int($rps{$player}{item}{$type}) * .9);
1793 $rps{$player}{item}{$type}.=$suffix;
1796 my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
1797 if (!open(Q,$opts{eventsfile})) {
1798 return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
1801 while (my $line = <Q>) {
1803 if ($line =~ /^C (.*)/ && rand(++$i) < 1) { $actioned = $1; }
1805 chanmsg(clog("$player $actioned. This terrible calamity has slowed ".
1806 "them ".duration($time)." from level ".
1807 ($rps{$player}{level}+1)."."));
1808 $rps{$player}{next} += $time;
1809 chanmsg("$player reaches next level in ".duration($rps{$player}{next}).
1814 sub godsend { # bless the unworthy
1815 my @players = grep { $rps{$_}{online} } keys(%rps);
1816 return unless @players;
1817 my $player = $players[rand(@players)];
1819 my @items = ("amulet","charm","weapon","tunic","set of leggings",
1821 my $type = $items[rand(@items)];
1822 if ($type eq "amulet") {
1823 chanmsg(clog("$player\'s amulet was blessed by a passing cleric! ".
1824 "$player\'s $type gains 10% effectiveness."));
1826 elsif ($type eq "charm") {
1827 chanmsg(clog("$player\'s charm ate a bolt of lightning! ".
1828 "$player\'s $type gains 10% effectiveness."));
1830 elsif ($type eq "weapon") {
1831 chanmsg(clog("$player sharpened the edge of his weapon! ".
1832 "$player\'s $type gains 10% effectiveness."));
1834 elsif ($type eq "tunic") {
1835 chanmsg(clog("A magician cast a spell of Rigidity on $player\'s ".
1836 "tunic! $player\'s $type gains 10% effectiveness."));
1838 elsif ($type eq "shield") {
1839 chanmsg(clog("$player reinforced his shield with a dragon's ".
1840 "scales! $player\'s $type gains 10% effectiveness."));
1843 chanmsg(clog("The local wizard imbued $player\'s pants with a ".
1844 "Spirit of Fortitude! $player\'s $type gains 10% ".
1848 if ($rps{$player}{item}{$type} =~ /(\D)$/) { $suffix=$1; }
1849 $rps{$player}{item}{$type} = int(int($rps{$player}{item}{$type}) * 1.1);
1850 $rps{$player}{item}{$type}.=$suffix;
1853 my $time = int(int(5 + rand(8)) / 100 * $rps{$player}{next});
1855 if (!open(Q,$opts{eventsfile})) {
1856 return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
1859 while (my $line = <Q>) {
1861 if ($line =~ /^G (.*)/ && rand(++$i) < 1) {
1865 chanmsg(clog("$player $actioned! This wondrous godsend has ".
1866 "accelerated them ".duration($time)." towards level ".
1867 ($rps{$player}{level}+1)."."));
1868 $rps{$player}{next} -= $time;
1869 chanmsg("$player reaches next level in ".duration($rps{$player}{next}).
1875 @{$quest{questers}} = grep { $rps{$_}{online} && $rps{$_}{level} > 39 &&
1876 time()-$rps{$_}{lastlogin}>36000 } keys(%rps);
1877 if (@{$quest{questers}} < 4) { return undef(@{$quest{questers}}); }
1878 while (@{$quest{questers}} > 4) {
1879 splice(@{$quest{questers}},int(rand(@{$quest{questers}})),1);
1881 if (!open(Q,$opts{eventsfile})) {
1882 return chanmsg("ERROR: Failed to open $opts{eventsfile}: $!");
1885 while (my $line = <Q>) {
1887 if ($line =~ /^Q/ && rand(++$i) < 1) {
1888 if ($line =~ /^Q1 (.*)/) {
1891 $quest{qtime} = time() + 43200 + int(rand(43201)); # 12-24 hours
1893 elsif ($line =~ /^Q2 (\d+) (\d+) (\d+) (\d+) (.*)/) {
1894 $quest{p1} = [$1,$2];
1895 $quest{p2} = [$3,$4];
1903 if ($quest{type} == 1) {
1904 chanmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
1905 "$quest{questers}->[3] have been chosen by the gods to ".
1906 "$quest{text}. Quest to end in ".duration($quest{qtime}-time()).
1909 elsif ($quest{type} == 2) {
1910 chanmsg(join(", ",(@{$quest{questers}})[0..2]).", and ".
1911 "$quest{questers}->[3] have been chosen by the gods to ".
1912 "$quest{text}. Participants must first reach [$quest{p1}->[0],".
1913 "$quest{p1}->[1]], then [$quest{p2}->[0],$quest{p2}->[1]].".
1914 ($opts{mapurl}?" See $opts{mapurl} to monitor their journey's ".
1922 my ($quester,$player);
1923 for $quester (@{$quest{questers}}) {
1924 if ($quester eq $k) {
1925 chanmsg(clog("$k\'s prudence and self-regard has brought the ".
1926 "wrath of the gods upon the realm. All your great ".
1927 "wickedness makes you as it were heavy with lead, ".
1928 "and to tend downwards with great weight and ".
1929 "pressure towards hell. Therefore have you drawn ".
1930 "yourselves 15 steps closer to that gaping maw."));
1931 for $player (grep { $rps{$_}{online} } keys %rps) {
1932 my $gain = int(15 * ($opts{rppenstep}**$rps{$player}{level}));
1933 $rps{$player}{pen_quest} += $gain;
1934 $rps{$player}{next} += $gain;
1936 undef(@{$quest{questers}});
1937 $quest{qtime} = time() + 43200; # 12 hours
1944 open(B,">>$opts{modsfile}") or do {
1945 debug("Error: Cannot open $opts{modsfile}: $!");
1946 chanmsg("Error: Cannot open $opts{modsfile}: $!");
1949 print B ts()."$mesg\n";
1955 if (! -d ".dbbackup/") { mkdir(".dbbackup",0700); }
1956 if ($^O ne "MSWin32") {
1957 system("cp $opts{dbfile} .dbbackup/$opts{dbfile}".time());
1960 system("copy $opts{dbfile} .dbbackup\\$opts{dbfile}".time());
1965 my $username = shift;
1966 return 0 if !defined($username);
1967 return 0 if !exists($rps{$username});
1970 questpencheck($username);
1971 if ($type eq "quit") {
1972 $pen = int(20 * ($opts{rppenstep}**$rps{$username}{level}));
1973 if ($opts{limitpen} && $pen > $opts{limitpen}) {
1974 $pen = $opts{limitpen};
1976 $rps{$username}{pen_quit}+=$pen;
1977 $rps{$username}{online}=0;
1979 elsif ($type eq "nick") {
1980 my $newnick = shift;
1981 $pen = int(30 * ($opts{rppenstep}**$rps{$username}{level}));
1982 if ($opts{limitpen} && $pen > $opts{limitpen}) {
1983 $pen = $opts{limitpen};
1985 $rps{$username}{pen_nick}+=$pen;
1986 $rps{$username}{nick} = substr($newnick,1);
1987 substr($rps{$username}{userhost},0,length($rps{$username}{nick})) =
1989 notice("Penalty of ".duration($pen)." added to your timer for ".
1990 "nick change.",$rps{$username}{nick});
1992 elsif ($type eq "privmsg" || $type eq "notice") {
1993 $pen = int(shift(@_) * ($opts{rppenstep}**$rps{$username}{level}));
1994 if ($opts{limitpen} && $pen > $opts{limitpen}) {
1995 $pen = $opts{limitpen};
1997 $rps{$username}{pen_mesg}+=$pen;
1998 notice("Penalty of ".duration($pen)." added to your timer for ".
1999 $type.".",$rps{$username}{nick});
2001 elsif ($type eq "part") {
2002 $pen = int(200 * ($opts{rppenstep}**$rps{$username}{level}));
2003 if ($opts{limitpen} && $pen > $opts{limitpen}) {
2004 $pen = $opts{limitpen};
2006 $rps{$username}{pen_part}+=$pen;
2007 notice("Penalty of ".duration($pen)." added to your timer for ".
2008 "parting.",$rps{$username}{nick});
2009 $rps{$username}{online}=0;
2011 elsif ($type eq "kick") {
2012 $pen = int(250 * ($opts{rppenstep}**$rps{$username}{level}));
2013 if ($opts{limitpen} && $pen > $opts{limitpen}) {
2014 $pen = $opts{limitpen};
2016 $rps{$username}{pen_kick}+=$pen;
2017 notice("Penalty of ".duration($pen)." added to your timer for ".
2018 "being kicked.",$rps{$username}{nick});
2019 $rps{$username}{online}=0;
2021 elsif ($type eq "logout") {
2022 $pen = int(20 * ($opts{rppenstep}**$rps{$username}{level}));
2023 if ($opts{limitpen} && $pen > $opts{limitpen}) {
2024 $pen = $opts{limitpen};
2026 $rps{$username}{pen_logout} += $pen;
2027 notice("Penalty of ".duration($pen)." added to your timer for ".
2028 "LOGOUT command.",$rps{$username}{nick});
2029 $rps{$username}{online}=0;
2031 $rps{$username}{next} += $pen;
2032 return 1; # successfully penalized a user! woohoo!
2036 (my $text = shift) =~ s/[\r\n]//g;
2038 if ($opts{debug} || $opts{verbose}) {
2039 open(DBG,">>$opts{debugfile}") or do {
2040 chanmsg("Error: Cannot open debug file: $!");
2043 print DBG ts()."$text\n";
2046 if ($die) { die("$text\n"); }
2052 return undef if !defined($nick);
2053 for my $user (keys(%rps)) {
2054 next unless $rps{$user}{online};
2055 if ($rps{$user}{nick} eq $nick) { return $user; }
2060 sub ha { # return 0/1 if username has access
2062 if (!defined($user) || !exists($rps{$user})) {
2063 debug("Error: Attempted ha() for invalid username \"$user\"");
2066 return $rps{$user}{isadmin};
2069 sub checksplits { # removed expired split hosts from the hash
2071 while ($host = each(%split)) {
2072 if (time()-$split{$host}{time} > $opts{splitwait}) {
2073 $rps{$split{$host}{account}}{online} = 0;
2074 delete($split{$host});
2079 sub collision_fight {
2081 my $mysum = itemsum($u,1);
2082 my $oppsum = itemsum($opp,1);
2083 my $myroll = int(rand($mysum));
2084 my $opproll = int(rand($oppsum));
2085 if ($myroll >= $opproll) {
2086 my $gain = int($rps{$opp}{level}/4);
2087 $gain = 7 if $gain < 7;
2088 $gain = int(($gain/100)*$rps{$u}{next});
2089 chanmsg(clog("$u [$myroll/$mysum] has come upon $opp [$opproll/$oppsum".
2090 "] and taken them in combat! ".duration($gain)." is ".
2091 "removed from $u\'s clock."));
2092 $rps{$u}{next} -= $gain;
2093 chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
2094 if (rand(35) < 1 && $opp ne $primnick) {
2095 $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
2096 chanmsg(clog("$u has dealt $opp a Critical Strike! ".
2097 duration($gain)." is added to $opp\'s clock."));
2098 $rps{$opp}{next} += $gain;
2099 chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).
2102 elsif (rand(25) < 1 && $opp ne $primnick && $rps{$u}{level} > 19) {
2103 my @items = ("ring","amulet","charm","weapon","helm","tunic",
2104 "pair of gloves","set of leggings","shield",
2106 my $type = $items[rand(@items)];
2107 if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) {
2108 chanmsg("In the fierce battle, $opp dropped his level ".
2109 int($rps{$opp}{item}{$type})." $type! $u picks it up, ".
2110 "tossing his old level ".int($rps{$u}{item}{$type}).
2112 my $tempitem = $rps{$u}{item}{$type};
2113 $rps{$u}{item}{$type}=$rps{$opp}{item}{$type};
2114 $rps{$opp}{item}{$type} = $tempitem;
2119 my $gain = ($opp eq $primnick)?10:int($rps{$opp}{level}/7);
2120 $gain = 7 if $gain < 7;
2121 $gain = int(($gain/100)*$rps{$u}{next});
2122 chanmsg(clog("$u [$myroll/$mysum] has come upon $opp [$opproll/$oppsum".
2123 "] and been defeated in combat! ".duration($gain)." is ".
2124 "added to $u\'s clock."));
2125 $rps{$u}{next} += $gain;
2126 chanmsg("$u reaches next level in ".duration($rps{$u}{next}).".");
2128 debug("interrogation");
2129 my $csfactor = $rps{$opp}{alignment} eq "g" ? 50 :
2130 $rps{$opp}{alignment} eq "e" ? 20 :
2132 if (rand($csfactor) < 1) {
2133 $gain = int(((5 + int(rand(20)))/100) * $rps{$opp}{next});
2134 chanmsg(clog("$opp has captured and interrogated $u! ".
2135 duration($gain)." is removed from $opp\'s clock."));
2136 $rps{$opp}{next} -= $gain;
2137 chanmsg("$opp reaches next level in ".duration($rps{$opp}{next}).".");
2142 sub writequestfile {
2143 return unless $opts{writequestfile};
2144 open(QF,">$opts{questfilename}") or do {
2145 chanmsg("Error: Cannot open $opts{questfilename}: $!");
2148 # if no active quest, just empty questfile. otherwise, write it
2149 if (@{$quest{questers}}) {
2150 if ($quest{type}==1) {
2151 print QF "T $quest{text}\n".
2153 "S $quest{qtime}\n".
2154 "P1 $quest{questers}->[0]\n".
2155 "P2 $quest{questers}->[1]\n".
2156 "P3 $quest{questers}->[2]\n".
2157 "P4 $quest{questers}->[3]\n";
2159 elsif ($quest{type}==2) {
2160 print QF "T $quest{text}\n".
2162 "S $quest{stage}\n".
2163 "P $quest{p1}->[0] $quest{p1}->[1] $quest{p2}->[0] ".
2164 "$quest{p2}->[1]\n".
2165 "P1 $quest{questers}->[0] $rps{$quest{questers}->[0]}{x} ".
2166 "$rps{$quest{questers}->[0]}{y}\n".
2167 "P2 $quest{questers}->[1] $rps{$quest{questers}->[1]}{x} ".
2168 "$rps{$quest{questers}->[1]}{y}\n".
2169 "P3 $quest{questers}->[2] $rps{$quest{questers}->[2]}{x} ".
2170 "$rps{$quest{questers}->[2]}{y}\n".
2171 "P4 $quest{questers}->[3] $rps{$quest{questers}->[3]}{x} ".
2172 "$rps{$quest{questers}->[3]}{y}\n";
2179 my @players = grep { $rps{$_}{alignment} eq "g" &&
2180 $rps{$_}{online} } keys(%rps);
2181 return unless @players > 1;
2182 splice(@players,int(rand(@players)),1) while @players > 2;
2183 my $gain = 5 + int(rand(8));
2184 chanmsg(clog("$players[0] and $players[1] have not let the iniquities of ".
2185 "evil men poison them. Together have they prayed to their ".
2186 "god, and it is his light that now shines upon them. $gain\% ".
2187 "of their time is removed from their clocks."));
2188 $rps{$players[0]}{next} = int($rps{$players[0]}{next}*(1 - ($gain/100)));
2189 $rps{$players[1]}{next} = int($rps{$players[1]}{next}*(1 - ($gain/100)));
2190 chanmsg("$players[0] reaches next level in ".
2191 duration($rps{$players[0]}{next}).".");
2192 chanmsg("$players[1] reaches next level in ".
2193 duration($rps{$players[1]}{next}).".");
2197 my @evil = grep { $rps{$_}{alignment} eq "e" &&
2198 $rps{$_}{online} } keys(%rps);
2199 return unless @evil;
2200 my $me = $evil[rand(@evil)];
2201 if (int(rand(2)) < 1) {
2202 # evil only steals from good :^(
2203 my @good = grep { $rps{$_}{alignment} eq "g" &&
2204 $rps{$_}{online} } keys(%rps);
2205 my $target = $good[rand(@good)];
2206 my @items = ("ring","amulet","charm","weapon","helm","tunic",
2207 "pair of gloves","set of leggings","shield",
2209 my $type = $items[rand(@items)];
2210 if (int($rps{$target}{item}{$type}) > int($rps{$me}{item}{$type})) {
2211 my $tempitem = $rps{$me}{item}{$type};
2212 $rps{$me}{item}{$type} = $rps{$target}{item}{$type};
2213 $rps{$target}{item}{$type} = $tempitem;
2214 chanmsg(clog("$me stole $target\'s level ".
2215 int($rps{$me}{item}{$type})." $type while they were ".
2216 "sleeping! $me leaves his old level ".
2217 int($rps{$target}{item}{$type})." $type behind, ".
2218 "which $target then takes."));
2221 notice("You made to steal $target\'s $type, but realized it was ".
2222 "lower level than your own. You creep back into the ".
2223 "shadows.",$rps{$me}{nick});
2226 else { # being evil only pays about half of the time...
2227 my $gain = 1 + int(rand(5));
2228 chanmsg(clog("$me is forsaken by his evil god. ".
2229 duration(int($rps{$me}{next} * ($gain/100)))." is added ".
2231 $rps{$me}{next} = int($rps{$me}{next} * (1 + ($gain/100)));
2232 chanmsg("$me reaches next level in ".duration($rps{$me}{next}).".");
2236 sub fisher_yates_shuffle {
2239 for ($i = @$array; --$i; ) {
2240 my $j = int rand ($i+1);
2242 @$array[$i,$j] = @$array[$j,$i];
2247 open(RPS,">$opts{dbfile}") or do {
2248 chanmsg("ERROR: Cannot write $opts{dbfile}: $!");
2251 print RPS join("\t","# username",
2284 keys(%rps); # reset internal pointer
2285 while ($k=each(%rps)) {
2286 if (exists($rps{$k}{next}) && defined($rps{$k}{next})) {
2287 print RPS join("\t",$k,
2304 $rps{$k}{pen_quest},
2305 $rps{$k}{pen_logout},
2307 $rps{$k}{lastlogin},
2308 $rps{$k}{item}{amulet},
2309 $rps{$k}{item}{charm},
2310 $rps{$k}{item}{helm},
2311 $rps{$k}{item}{"pair of boots"},
2312 $rps{$k}{item}{"pair of gloves"},
2313 $rps{$k}{item}{ring},
2314 $rps{$k}{item}{"set of leggings"},
2315 $rps{$k}{item}{shield},
2316 $rps{$k}{item}{tunic},
2317 $rps{$k}{item}{weapon},
2318 $rps{$k}{alignment})."\n";
2325 if (! -e ".irpg.conf") {
2326 debug("Error: Cannot find .irpg.conf. Copy it to this directory, ".
2330 open(CONF,"<.irpg.conf") or do {
2331 debug("Failed to open config file .irpg.conf: $!",1);
2333 my($line,$key,$val);
2334 while ($line=<CONF>) {
2335 next() if $line =~ /^#/; # skip comments
2336 $line =~ s/[\r\n]//g;
2338 next() if !length($line); # skip blank lines
2339 ($key,$val) = split(/\s+/,$line,2);
2341 if (lc($val) eq "on" || lc($val) eq "yes") { $val = 1; }
2342 elsif (lc($val) eq "off" || lc($val) eq "no") { $val = 0; }
2343 if ($key eq "die") {
2344 die("Please edit the file .irpg.conf to setup your bot's ".
2345 "options. Also, read the README file if you haven't ".
2348 elsif ($key eq "server") { push(@{$opts{servers}},$val); }
2349 elsif ($key eq "okurl") { push(@{$opts{okurl}},$val); }
2350 else { $opts{$key} = $val; }