diff --git a/IdleRPG.sln b/IdleRPG.sln new file mode 100644 index 0000000..759a0c4 --- /dev/null +++ b/IdleRPG.sln @@ -0,0 +1,57 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bot.v3.1.2", "bot.v3.1.2", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + bot.v3.1.2\.irpg.conf = bot.v3.1.2\.irpg.conf + bot.v3.1.2\bot.v3.1.2.pl = bot.v3.1.2\bot.v3.1.2.pl + bot.v3.1.2\ChangeLog.txt = bot.v3.1.2\ChangeLog.txt + bot.v3.1.2\events.txt = bot.v3.1.2\events.txt + bot.v3.1.2\irpgdbtool = bot.v3.1.2\irpgdbtool + bot.v3.1.2\modifiers.txt = bot.v3.1.2\modifiers.txt + bot.v3.1.2\questinfo.txt = bot.v3.1.2\questinfo.txt + bot.v3.1.2\README = bot.v3.1.2\README + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "irpg", "irpg", "{440DEC4B-57C9-4361-9949-7CF55F9CFED8}" + ProjectSection(SolutionItems) = preProject + irpg\admincomms.txt = irpg\admincomms.txt + irpg\ChangeLog = irpg\ChangeLog + irpg\commonfunctions.php = irpg\commonfunctions.php + irpg\config.php = irpg\config.php + irpg\contact.php = irpg\contact.php + irpg\db.php = irpg\db.php + irpg\down.png = irpg\down.png + irpg\dump.php = irpg\dump.php + irpg\footer.php = irpg\footer.php + irpg\g7.css = irpg\g7.css + irpg\head.png = irpg\head.png + irpg\header.php = irpg\header.php + irpg\hits.db = irpg\hits.db + irpg\idlerpg-adv.txt = irpg\idlerpg-adv.txt + irpg\index.php = irpg\index.php + irpg\makemap.php = irpg\makemap.php + irpg\makequestmap.php = irpg\makequestmap.php + irpg\makeworldmap.php = irpg\makeworldmap.php + irpg\maperror.png = irpg\maperror.png + irpg\newmap.png = irpg\newmap.png + irpg\players.php = irpg\players.php + irpg\playerview.php = irpg\playerview.php + irpg\quest.php = irpg\quest.php + irpg\README = irpg\README + irpg\tablegrad.gif = irpg\tablegrad.gif + irpg\up.png = irpg\up.png + irpg\worldmap.php = irpg\worldmap.php + irpg\xml.php = irpg\xml.php + EndProjectSection +EndProject +Global + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8C959837-E00F-4F71-9966-96BB12DA3FC4} + EndGlobalSection +EndGlobal diff --git a/bot.v3.1.2/.irpg.conf b/bot.v3.1.2/.irpg.conf new file mode 100644 index 0000000..b8a4d6d --- /dev/null +++ b/bot.v3.1.2/.irpg.conf @@ -0,0 +1,241 @@ +# 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 + +# 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 miami.fl.us.irc-network.org:6667 +server dallas.tx.us.irc-network.org:7000 +server brussels.be.eu.irc-network.org:6660 + +# bot's nickname +botnick bot + +# bot's username +botuser bot + +# real name field +botrlnm http://www.slashnet.org/~bot/ + +# channel name (followed by key, if your channel uses a key +botchan #irpg s3cr3t.p4ss + +# (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 ilovedink + +# 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 #idlerpg %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 bot ilovedink + +# URL to send users to for help +helpurl http://idlerpg.net/ + +# admin commands list (for admin help) +admincommurl http://idlerpg.net/admincomms.txt + +# base time to level up, 600 = 10 minutes +rpbase 600 + +# 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 + +# where quests/godsends/calamities are stored +eventsfile events.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 ultrazone.org +okurl idlerpg.net + +# 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://idlerpg.net/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 + +# 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 jotun + +# 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 on + +# 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 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 diff --git a/bot.v3.1.2/ChangeLog.txt b/bot.v3.1.2/ChangeLog.txt new file mode 100644 index 0000000..2985918 --- /dev/null +++ b/bot.v3.1.2/ChangeLog.txt @@ -0,0 +1,651 @@ +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: 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 + 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 + 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 + 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 + 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 + 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 diff --git a/bot.v3.1.2/README b/bot.v3.1.2/README new file mode 100644 index 0000000..b07a016 --- /dev/null +++ b/bot.v3.1.2/README @@ -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. diff --git a/bot.v3.1.2/bot.v3.1.2.pl b/bot.v3.1.2/bot.v3.1.2.pl new file mode 100644 index 0000000..11d0bb1 --- /dev/null +++ b/bot.v3.1.2/bot.v3.1.2.pl @@ -0,0 +1,2352 @@ +#!/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 = ); + $uname =~ s/\s.*//g; + $uname = length($uname)?$uname:$opts{owner}; + print "Enter a character class for this account: "; + chomp(my $uclass = ); + $rps{$uname}{class} = substr($uclass,0,30); + print "Enter a password for this account: "; + if ($^O ne "MSWin32") { + system("stty -echo"); + } + chomp(my $upass = ); + 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], + 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 ", + $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 ", $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 ", $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 ", $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 ", $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 ", + $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 ", + $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 ", $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 ", $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 ", $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 ", $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 ", $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 ", $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 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(); } + + 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] 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 (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) { + chanmsg(clog("In the fierce battle, $opp dropped his level ". + int($rps{$opp}{item}{$type})." $type! $u picks ". + "it up, tossing his old level ". + int($rps{$u}{item}{$type})." $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; + 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] have ". + "team battled $opp[3], $opp[4], and $opp[5] [$opproll/". + "$oppsum] and won! ".duration($gain)." is removed from ". + "their clocks.")); + $rps{$opp[0]}{next} -= $gain; + $rps{$opp[1]}{next} -= $gain; + $rps{$opp[2]}{next} -= $gain; + } + else { + chanmsg(clog("$opp[0], $opp[1], and $opp[2] [$myroll/$mysum] have ". + "team battled $opp[3], $opp[4], and $opp[5] [$opproll/". + "$oppsum] and lost! ".duration($gain)." is added to ". + "their clocks.")); + $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 = ("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 > int($rps{$u}{item}{helm})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Mattt's Omniscience Grand Crown! ". + "Your enemies fall before you as you anticipate their ". + "every move.",$rps{$u}{nick}); + $rps{$u}{item}{helm} = $ulevel."a"; + return; + } + } + elsif ($rps{$u}{level} >= 25 && rand(40) < 1) { + $ulevel = 50+int(rand(25)); + if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{ring})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Juliet's Glorious Ring of ". + "Sparkliness! You enemies are blinded by both its glory ". + "and their greed as you bring desolation upon them.", + $rps{$u}{nick}); + $rps{$u}{item}{ring} = $ulevel."h"; + return; + } + } + elsif ($rps{$u}{level} >= 30 && rand(40) < 1) { + $ulevel = 75+int(rand(25)); + if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{tunic})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Res0's Protectorate Plate Mail! ". + "Your enemies cower in fear as their attacks have no ". + "effect on you.",$rps{$u}{nick}); + $rps{$u}{item}{tunic} = $ulevel."b"; + return; + } + } + elsif ($rps{$u}{level} >= 35 && rand(40) < 1) { + $ulevel = 100+int(rand(25)); + if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{amulet})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Dwyn's Storm Magic Amulet! Your ". + "enemies are swept away by an elemental fury before the ". + "war has even begun",$rps{$u}{nick}); + $rps{$u}{item}{amulet} = $ulevel."c"; + return; + } + } + elsif ($rps{$u}{level} >= 40 && rand(40) < 1) { + $ulevel = 150+int(rand(25)); + if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Jotun's Fury Colossal Sword! Your ". + "enemies' hatred is brought to a quick end as you arc your ". + "wrist, dealing the crushing blow.",$rps{$u}{nick}); + $rps{$u}{item}{weapon} = $ulevel."d"; + return; + } + } + elsif ($rps{$u}{level} >= 45 && rand(40) < 1) { + $ulevel = 175+int(rand(26)); + if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Drdink's Cane of Blind Rage! Your ". + "enemies are tossed aside as you blindly swing your arm ". + "around hitting stuff.",$rps{$u}{nick}); + $rps{$u}{item}{weapon} = $ulevel."e"; + return; + } + } + elsif ($rps{$u}{level} >= 48 && rand(40) < 1) { + $ulevel = 250+int(rand(51)); + if ($ulevel >= $level && $ulevel > + int($rps{$u}{item}{"pair of boots"})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Mrquick's Magical Boots of ". + "Swiftness! Your enemies are left choking on your dust as ". + "you run from them very, very quickly.",$rps{$u}{nick}); + $rps{$u}{item}{"pair of boots"} = $ulevel."f"; + return; + } + } + elsif ($rps{$u}{level} >= 52 && rand(40) < 1) { + $ulevel = 300+int(rand(51)); + if ($ulevel >= $level && $ulevel > int($rps{$u}{item}{weapon})) { + notice("The light of the gods shines down upon you! You have ". + "found the level $ulevel Jeff's Cluehammer of Doom! Your ". + "enemies are left with a sudden and intense clarity of ". + "mind... even as you relieve them of it.",$rps{$u}{nick}); + $rps{$u}{item}{weapon} = $ulevel."g"; + return; + } + } + if ($level > int($rps{$u}{item}{$type})) { + notice("You found a level $level $type! Your current $type is only ". + "level ".int($rps{$u}{item}{$type}).", so it seems Luck is ". + "with you!",$rps{$u}{nick}); + $rps{$u}{item}{$type} = $level; + } + else { + notice("You found a level $level $type. Your current $type is level ". + int($rps{$u}{item}{$type}).", so it seems Luck is against you. ". + "You toss the $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=) { + 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."); +} + +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 ? + 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 = ("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(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 = ) { + 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(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 = ) { + 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 = ) { + 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 * ($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 = ("ring","amulet","charm","weapon","helm","tunic", + "pair of gloves","set of leggings","shield", + "pair of boots"); + my $type = $items[rand(@items)]; + if (int($rps{$opp}{item}{$type}) > int($rps{$u}{item}{$type})) { + chanmsg("In the fierce battle, $opp dropped his level ". + int($rps{$opp}{item}{$type})." $type! $u picks it up, ". + "tossing his old level ".int($rps{$u}{item}{$type}). + " $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 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 (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("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 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", + "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); +} + +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=) { + 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; } + } + } +} diff --git a/bot.v3.1.2/events.txt b/bot.v3.1.2/events.txt new file mode 100644 index 0000000..becb890 --- /dev/null +++ b/bot.v3.1.2/events.txt @@ -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 diff --git a/bot.v3.1.2/irpgdbtool b/bot.v3.1.2/irpgdbtool new file mode 100644 index 0000000..c339a6c --- /dev/null +++ b/bot.v3.1.2/irpgdbtool @@ -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=); + $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=); + $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=); + $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=); + $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=); + $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=); + $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=); + $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=); + $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=); +$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=); + $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=); + $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=) { + 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; +} diff --git a/bot.v3.1.2/modifiers.txt b/bot.v3.1.2/modifiers.txt new file mode 100644 index 0000000..e69de29 diff --git a/bot.v3.1.2/questinfo.txt b/bot.v3.1.2/questinfo.txt new file mode 100644 index 0000000..e69de29 diff --git a/irpg/ChangeLog b/irpg/ChangeLog new file mode 100644 index 0000000..570b698 --- /dev/null +++ b/irpg/ChangeLog @@ -0,0 +1,108 @@ +This is the ChangeLog for the Idle RPG Website Code. I'm not as religious about +making sure my updates make it here as I am about changes to the IRPG code +making it into that ChangeLog, so there may be changes between versions that you +don't see listed here. + +Also, please note that any use of "incowrect" or other cow-related "typos" are +NOT typos. I like cattle. A lot. Please stop fixing them :^) + +2004-07-17 Jon Honeycutt + * huge modification of the entire source by the original coder, res0 + . res0 has really helped this project through his many + contributions; it wouldn't be where it is today without him. thanks, res0! + * New site layout (valid XHTML 1.1 strict) using CSS + * Cleaned up PHP + * User maps do not display unless visitor clicks 'display' link + * changed fgets() calls to add the optional second parameter (for old PHPs) + * removed incorrect header() in makequestmap() (mikegrb) + * checked to make sure key $_SESSION['time'] was set before attempting to + reference it (HarK0nNeN) + * db.php's table tag now specifies the number of rows and columns to make + drawing faster in browsers that pay attention to it (res0) + * header("Location: ") directives now give full URIs + * made better use of htmlentities() and urlencode()s as relates to display + of usernames and classes + * users on quest and world maps are now clickable, leading to their player + info page (John Nielsen) + +2004-05-31 Jon Honeycutt + * changed players.php to show offline users as gray, this style is in + header.php, patch from Chewie + * changed map-drawing PHP scripts to draw a transparent image which is then + overlayed (using CSS) over the static newmap.png image. changed the map + drawing frequency to 1/user/20s as I think this greatly decreases CPU + usage. changed the maperror.png to something more appropriate. $mapx + and $mapy in config.php describe the dimensions of your map file (Rick) + * fixed quest.php to show proper coordinates; another evil c/p error (Rick) + +2004-05-23 Jon Honeycutt + * changed most everything to make sense with the new database + * edited dump.php to display data in the same format, despite the new + database format. + * added an xml.php page that takes a 'player' argument and displays + information on that user in xml 1.0 format. should allow a better API + as far as compatibility with newer dbs goes + * changed playerview.php to use fgets() to read from modifiers.txt. + hopefully this is much faster + * edited playerview.php to show map and XML data link + * changed the counter in footer.php to strip the leading path information + from $_SERVER['PHP_SELF'] before using it to identify the page + * removed some unused $i variable from players.php + * created quest.php which shows active quest info + * created makequestmap.php which draws a quest map if quest type is 2, + redirects otherwise + * created worldmap.php which shows the active state of the irpg world + * created makeworldmap.php which draws the world map + * created makemap.php which takes a 'player' argument and draws them on the + map + * commonfunctions.php now has functions to sort by isadmin flag and player + alignment + * index.php updated with information on the new game features + * changed titles on several pages to use $irpg_chan instead of hard-coded + #G7 + * updated header.php to link to new pages and idlerpg.net forum + * changed 'time modifiers' to 'character modifiers' as item modifiers are + now stored, also + +2004-05-23 Alexander Hirzel + * submitted a series of patches to correct, among other things, the horrible + use of where was more appropriate + +2004-04-22 Jon Honeycutt + * if $_GET['alltime'] wasn't set, < and > in time modifiers were not + translated to < and > (thanks DinTx) + * attempted to fix playerview.php from showing others' stats, traded + stristr() for strstr() matching either "^name ", " name ", or "^name's " + +2004-04-23 Jonathan Attwell + * created README, detailing how users are to install and what to edit. + * created config.php, which holds all the setup variables. + * cleaned up some of the php coding. + +2004-04-15 Jon Honeycutt + * we now stripslashes() on $_GET['player'] for players.php, + playerview.php, dump.php (thanks Diab) + +2004-04-05 Jon Honeycutt + * removed some of parallax's tabs :^) + * edited players.php to forward requests for specific players to the proper + playerview.php + * changed links in sitesource.php to be fully-qualified + * removed preg_grep in playerview.php, which insecurely used tainted + data. switched to stristr. (thanks mike@ethernal.org) + * stripped < and > from modifiers.txt output (thanks mike@ethernal.org) + * changed link to idlerpg-adv.txt in players.php to be fully-qualified. + (thanks daxxar) + * may've made other changes, not sure. + +2004-02-15 Jon Honeycutt + * edited source.php to provide fully-qualified URLs to source files; + easier for users to follow license. + * playerview.php's 'View all Time Modifiers' link now displays the + number of time modifiers. + * contact.php now sends a From: field in its headers parameter. + +2004-01-31 Titus Barik + * created ChangeLog, following Debian ChangeLog format. + * modified header.php to be location independent via $BASE_URL var. + * players.php and playerview.php are now separate files. diff --git a/irpg/README b/irpg/README new file mode 100644 index 0000000..ff5b259 --- /dev/null +++ b/irpg/README @@ -0,0 +1,42 @@ +------------------------------------------ +IRPG Website Code README v1.1 (2004-06-27) +------------------------------------------ + +----------- + Notes +----------- + +Homepage: http://idlerpg.net +License : Public Domain + +----------- + Versions +----------- + +Site: v0.5 [current/changes courtesy of res0], Released July 17th, 2004. +Bot : v3.1.2 [current], Released June 6th, 2004. + +------------ +Installation +------------ + +1. Make sure you have the bot functional and running. +2. Copy all the files here to your public_html or some folder. +3. Edit config.php with your favorite editor. +4. chmod 644 * && chmod 666 hits.db +5. If you change the default settings in the IRPG bot (for example, if you turn + off the option to write quest info to file, you'll have to manually edit + some scripts to take this into account. If you disable the INFO command, + you might want to remove it from the index.php page. +6. Some code in this package requires that your system have GD 2.0+ (or have it + enabled in your php.ini, on Win32). If you don't want this functionality to + be available, edit the script playerview.php to remove the use of the map + and header.php to remove the links to the world map and the quest info page. + You can also delete the worldmap.php, makeworldmap.php, makemap.php, + quest.php, and makequestmap.php scripts. +7. Edit the website ANY WAY you see fit. You don't have to keep all of the links + to me, I just thought they might be useful or interesting to users :^) +8. The best way to receive support for these scripts is to post to the Help + section of the forum, http://idlerpg.net/forum.php, not via e-mail or IRC. + If you notice a bug, however, you may use any method to contact me. + Thanks :^) diff --git a/irpg/admincomms.txt b/irpg/admincomms.txt new file mode 100644 index 0000000..1f9e5f2 --- /dev/null +++ b/irpg/admincomms.txt @@ -0,0 +1,65 @@ +This is not the full list of commands for the Idle RPG bot, but only the list +of admin commands. For more information on the Idle RPG bot, visit +http://idlerpg.net/ + +INFO, retrieve some fairly useless stats about the bot. + +DIE, kills the bot. + +HOG, summon the Hand of God spell. See the main help file. + +RESTART, restarts the bot. + +CHPASS , change a character's pass in the IRPG. + +CHCLASS , change a character's class in the IRPG. + +CHUSER , change a character's username in the IRPG. + Please only use in very special circumstances; otherwise, have them form + a new player and DEL the old one. This should not let you overwrite an + existing account, but is untested. + +PUSH , push a player toward his goal by subtracting time + from his next time to level. Please use this only if bot has mistakenly + penalized someone. You could also use this to punish a user by setting + the number of seconds to a negative number. Don't do that. + +DEL , remove a user's account. + +JUMP , move the bot to another server. + +SILENT , switch bot between 4 modes of silence. + - mode 0, bot sends all privmsgs. + - mode 1, only chanmsg() is disabled. + - mode 2, only privmsg()/notice() to non-channels is disabled. + - mode 3, privmsgs/notices to users and channels are disabled. + +BACKUP, tell bot to copy $opts{'dbfile'} to .dbbackup/$opts{'dbfile'}TIMESTAMP + +RELOADDB, force bot to reload player database file, rewriting all memory. + RELOADDB can only be used while in pause mode. + +PAUSE, toggle pause mode. + +PEVAL , execute arbitrary argument as Perl code. Queues output > 3 lines + or >1k of text. Some useful PEVAL commands: + - Delete all accounts not logged in in 4 weeks (See also: DELOLD): + /msg bot PEVAL delete $rps{$_} for grep { time()-$rps{$_}{lastlogin} > 3600*24*7*4 && !$rps{$_}{online} } keys %rps; + - Remove one hour from everyone's clocks: + /msg bot PEVAL $rps{$_}{next} -= 3600 for keys %rps; + - List all online users, separated by commas: + /msg bot PEVAL join(', ',grep { $rps{$_}{online} } keys %rps); + - View contents of a file on remote host: + /msg bot peval `cat file` + - Turn on debug mode: + /msg bot peval $opts{debug}=1; + - Force write-out of database: + /msg bot peval writedb(); + +DELOLD , remove all non-logged-in accounts inactive in the last + days. + +CLEARQ, clear the outgoing message queue. Useful to use if someone floods the + bot with a lot of text that it plans to respond to. + +MKADMIN , set the isadmin flag for a given username. diff --git a/irpg/commonfunctions.php b/irpg/commonfunctions.php new file mode 100644 index 0000000..13a72fe --- /dev/null +++ b/irpg/commonfunctions.php @@ -0,0 +1,94 @@ + $level2) ? -1 : 1; + } + function cmp_alignment_asc($a,$b) { return cmp_alignment_desc($b,$a); } + function cmp_alignment_desc($a,$b) { + list(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,$a1)=explode("\t",trim($a)); + list(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,$a2)=explode("\t",trim($b)); + if ($a1 == "g" || $a2 == "e") return -1; + if ($a1 == "e" || $a2 == "g") return 1; + return 0; + } + function cmp_isadmin_asc($a,$b) { return cmp_isadmin_desc($b,$a); } + function cmp_isadmin_desc($a,$b) { + list(,,$o1)=explode("\t",trim($a)); + list(,,$o2)=explode("\t",trim($b)); + return ($o1 > $o2) ? -1 : 1; + } + function cmp_ttl_asc($a,$b) { return cmp_ttl_desc($b,$a); } + function cmp_ttl_desc($a,$b) { + list(,,,,,$time1)=explode("\t",trim($a)); + list(,,,,,$time2)=explode("\t",trim($b)); + return ($time2 < $time1) ? -1 : 1; + } + function cmp_user_asc($a,$b) { return cmp_user_desc($b,$a); } + function cmp_user_desc($a,$b) { + list($u1)=explode("\t",trim($a)); + list($u2)=explode("\t",trim($b)); + return (strtolower($u1) > strtolower($u2)) ? -1 : 1; + } + function cmp_online_asc($a,$b) { return cmp_online_desc($b,$a); } + function cmp_online_desc($a,$b) { + list(,,,,,,,,$o1)=explode("\t",trim($a)); + list(,,,,,,,,$o2)=explode("\t",trim($b)); + return ($o1 > $o2) ? -1 : 1; + } + function cmp_idled_asc($a,$b) { return cmp_idled_desc($b,$a); } + function cmp_idled_desc($a,$b) { + list(,,,,,,,,,$i1)=explode("\t",trim($a)); + list(,,,,,,,,,$i2)=explode("\t",trim($b)); + return ($i1 > $i2) ? -1 : 1; + } + function cmp_created_asc($a,$b) { return cmp_created_desc($b,$a); } + function cmp_created_desc($a,$b) { + list(,,,,,,,,,,,,,,,,,,,$i1)=explode("\t",trim($a)); + list(,,,,,,,,,,,,,,,,,,,$i2)=explode("\t",trim($b)); + return ($i1 > $i2) ? -1 : 1; + } + function cmp_lastlogin_asc($a,$b) { return cmp_lastlogin_desc($b,$a); } + function cmp_lastlogin_desc($a,$b) { + list(,,,,,,,,,,,,,,,,,,,,$i1)=explode("\t",trim($a)); + list(,,,,,,,,,,,,,,,,,,,,$i2)=explode("\t",trim($b)); + return ($i1 > $i2) ? -1 : 1; + } + function cmp_uhost_asc($a,$b) { return cmp_uhost_desc($b,$a); } + function cmp_uhost_desc($a,$b) { + list(,,,,,,,$u1)=explode("\t",trim($a)); + list(,,,,,,,$u2)=explode("\t",trim($b)); + return (strtolower($u1) > strtolower($u2)) ? -1 : 1; + } + function cmp_pen_asc($a,$b) { return cmp_pen_desc($b,$a); } + function cmp_pen_desc($a,$b) { + list(,,,,,,,,,,,,$p1[0],$p1[1],$p1[2],$p1[3],$p1[4],$p1[5], + $p1[6])=explode("\t",trim($a)); + list(,,,,,,,,,,,,$p2[0],$p2[1],$p2[2],$p2[3],$p2[4],$p2[5], + $p2[6])=explode("\t",trim($b)); + $s1 = $s2 = 0; + foreach ($p1 as $pen) $s1 += $pen; + foreach ($p2 as $pen) $s2 += $pen; + return ($s1 > $s2) ? -1 : 1; + } + function cmp_sum_asc($a,$b) { return cmp_sum_desc($b,$a); } + function cmp_sum_desc($a,$b) { + list(,,,,,,,,,,,,,,,,,,,,,$i1[0],$i1[1],$i1[2],$i1[3],$i1[4],$i1[5], + $i1[6],$i1[7],$i1[8],$i1[9])=explode("\t",trim($a)); + list(,,,,,,,,,,,,,,,,,,,,,$i2[0],$i2[1],$i2[2],$i2[3],$i2[4],$i2[5], + $i2[6],$i2[7],$i2[8],$i2[9])=explode("\t",trim($b)); + $s1 = $s2 = 0; + foreach ($i1 as $item) { $s1 += $item; } + foreach ($i2 as $item) $s2 += $item; + return ($s1 > $s2) ? -1 : 1; + } +?> diff --git a/irpg/config.php b/irpg/config.php new file mode 100644 index 0000000..10e17f0 --- /dev/null +++ b/irpg/config.php @@ -0,0 +1,39 @@ + diff --git a/irpg/contact.php b/irpg/contact.php new file mode 100644 index 0000000..bc29914 --- /dev/null +++ b/irpg/contact.php @@ -0,0 +1,47 @@ +Contact"; + if ($_POST['from'] && $_POST['text']) { + mail($admin_email,"IRPG: ".$_POST['from'], + "Name: ".$_POST['name']."\nE-mail: ".$_POST['from']."\n\n". + $_POST['text'],"From: ".$_POST['from']."\r\n"); + echo('
Thanks for your submission.
'); + } + else { + echo(' +
+ + + + + + + + + + + + + + + +
: + +
: + +
+
+
+ +
+
+'); + } + include("footer.php"); +?> diff --git a/irpg/db.php b/irpg/db.php new file mode 100644 index 0000000..78ba930 --- /dev/null +++ b/irpg/db.php @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +",">",$class); + $user = str_replace(">",">",$user); + $sum = 0; + foreach ($item as $k => $v) $sum += $v; + $pentot = 0; + foreach ($pen as $k => $v) $pentot += $v; + echo " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n". + " \n"; + } + + echo(' +
+ User + ( + + + / + + + ) + + Level + ( + + + / + + + ) + + Admin + ( + + + / + + + ) + Class + TTL + ( + + + / + + + ) + + Nick!User@Host + ( + + + / + + + ) + + Online + ( + + + / + + + ) + + Total Time Idled + ( + + + / + + + ) + X PosY PosMesg Pen.Nick Pen.Part Pen.Kick Pen.Quit Pen.Quest Pen.LOGOUT Pen. + Total Pen. + ( + + + / + + + ) + + Acct. Created + ( + + + / + + + ) + + Last Login + ( + + + / + + + ) + AmuletCharmHelmBootsGlovesRingLeggingsShieldTunicWeapon + Sum + ( + + + / + + + ) + + Alignment + ( + + + / + + + ) +
$user$level".($isadmin?"Yes":"No")."$class".duration($secs)."$uhost".(($online == 1) ? "Yes" : "No")."".duration($idled)."$x$y".duration($pen['mesg'])."".duration($pen['nick'])."".duration($pen['part'])."".duration($pen['kick'])."".duration($pen['quit'])."".duration($pen['quest'])."".duration($pen['logout'])."".duration($pentot)."".date("D M j H:i:s Y",$created)."".date("D M j H:i:s Y",$lastlogin)."".$item['amulet']."".$item['charm']."".$item['helm']."".$item['boots']."".$item['gloves']."".$item['ring']."".$item['leggings']."".$item['shield']."".$item['tunic']."".$item['weapon']."$sum".($alignment=='e'?"Evil":($alignment=='n'?"Neutral":"Good"))."
+

+ * Accounts created before Aug 29, 2003 may have incowrect data fields. + '); + include("footer.php"); +?> diff --git a/irpg/down.png b/irpg/down.png new file mode 100644 index 0000000..e1a249d Binary files /dev/null and b/irpg/down.png differ diff --git a/irpg/dump.php b/irpg/dump.php new file mode 100644 index 0000000..2f29898 --- /dev/null +++ b/irpg/dump.php @@ -0,0 +1,118 @@ + diff --git a/irpg/footer.php b/irpg/footer.php new file mode 100644 index 0000000..c7a33bb --- /dev/null +++ b/irpg/footer.php @@ -0,0 +1,35 @@ + + + + diff --git a/irpg/g7.css b/irpg/g7.css new file mode 100644 index 0000000..8dc6836 --- /dev/null +++ b/irpg/g7.css @@ -0,0 +1,111 @@ +body { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + background-color: #FFFFF0; + color: black; + margin: 10px; + text-align: justify; +} + +h1 { margin-bottom: 0px; margin-top: 0px; } +h2 { margin-bottom: 0px; } + +p.small { margin: 0px; font-size: smaller; } + +a { text-decoration: none; color: #C69500; } +a:hover { text-decoration: underline; color: black; } + +div.head { + border: 1px solid #c0c0c0; + text-align: left; + width: 100%; + margin-bottom: 10px; + background: #FFFFFF; +} + +div.menu { + width: 120px; + position: relative; + float: left; + border: 1px solid #c0c0c0; + background: #FFFFFF; +} +div#menu a.home { color: #000000; background: #c0c0c0; border: 1px solid #000000; } +div#menu a.home:hover { background: #c0c0c0; border: 1px solid #000000; } +div#menu a { + text-align: center; + width: 118px; + margin: 0px; + display: block; + border: 1px solid #FFFFFF +} + +div#menu a:hover { border: 1px solid #000000; background: #c0c0c0; text-decoration: none; } + +div.content { + border: 1px solid #c0c0c0; + margin-left: 130px; + margin-top: 0px; + padding: 10px; + background: #FFFFFF; +} + +div.footer { + border: 1px solid #c0c0c0; + margin-left: 130px; + margin-top: 10px; + padding: 10px; + background: #FFFFFF; +} + + +table.uniques { + border: 1px solid #c0c0c0; + padding: 5px; + text-align: left; +} +table.uniques td { + padding-left: 10px; +} + +table.penalty { + border: 1px solid #c0c0c0; + padding: 5px; + text-align: left; +} +table.penalty th { + text-align: right; +} +table.penalty td { + +} + +/* Styles for the userlist */ +li.online { font-weight: bold; } +li.offline { color: #c0c0c0; } +a.offline { color: #707070; } +#map { + width: 500px; + height: 500px; + background-image: url(newmap.png); +} + + +table.forum { + border: 1px solid #c0c0c0; + table-layout: fixed; + overflow: auto; +} + +table.forum td,tr,caption,thead,tfoot,th { + padding-left: 10px; + padding-right: 10px; +} +.tdblue { background-color: #ffffdf; } +.tdgray { background-color: #eeeee0; } +.tdred { + border: 1px solid red; + background-color: #FFCCCC; +} +.smallest { + font-size: 11px; +} diff --git a/irpg/head.png b/irpg/head.png new file mode 100644 index 0000000..5d6a7c3 Binary files /dev/null and b/irpg/head.png differ diff --git a/irpg/header.php b/irpg/header.php new file mode 100644 index 0000000..8b05113 --- /dev/null +++ b/irpg/header.php @@ -0,0 +1,47 @@ + + + + + <?echo $irpg_chan;?> Idle RPG: <?echo $irpg_page_title;?> + + + + + + + +
+'); +?> +
+ + + +
diff --git a/irpg/hits.db b/irpg/hits.db new file mode 100644 index 0000000..f05175c --- /dev/null +++ b/irpg/hits.db @@ -0,0 +1,16 @@ +contact.php 3992 Apr 22, 2003 +db.php 5869 Jun 13, 2003 +index.php 66845 Apr 22, 2003 +playerview.php 144029 Apr 5, 2004 +players.php 51366 Apr 5, 2004 +source.php 13036 Apr 5, 2004 +sitesource.php 7956 Apr 5, 2004 +others.php 13649 Apr 5, 2004 +donate.php 1333 Apr 5, 2004 +donated.php 4 Apr 6, 2004 +forum.php 1799 Jul 13, 2004 +quest.php 242 Jul 13, 2004 +worldmap.php 235 Jul 13, 2004 +1 Jul 15, 2004 +g7.css 1 Jul 15, 2004 +idlerpg.png 1 Jul 15, 2004 diff --git a/irpg/idlerpg-adv.txt b/irpg/idlerpg-adv.txt new file mode 100644 index 0000000..f6bf0f0 --- /dev/null +++ b/irpg/idlerpg-adv.txt @@ -0,0 +1,142 @@ +#!/usr/bin/perl -w +# idlerpg-adv (11-22-2003) by daxxar (http://mental.mine.nu) +# Usage: ./idlerpg-adv.pl [playernames] +# +# Call this script from the command line, or your login profile. +use strict; +use LWP::Simple; + +# Use cookies: +# %user - username, %class - class, %level - level, +# %next - time to next level, %status - online status, +# %uhost - nick!ident@host, %ca - created at, +# %llo - last logged on, %ti - total idletime, +# %items - list of items, %penalties - list of penalties (special; multiline) +# +# Each comma-separated element is printed with a newline at the end :) + +my @string = ( + '[User] %user', '[Class] %class', '[Level] %level', '[Next level] %next', + '[Status] %status', '[User@host] %uhost', '[Created at] %ca', + '[Last logged on] %llo', '[Total idle] %ti', '[Items] %items', + '[Penalties] %penalties' + ); + +### No need to change below ### +# For printing things in a columnized view +# print_col(\@list_of_entries, \@value_of_entries, $number_per_line) +sub make_col { + my $entryname = shift; + my $entryvalue = shift; + my $count = shift; + my @len; + my $ret; + # Find maximum length for each of the $count columns + for my $x (0 .. $#{$entryvalue}) { + my $col = $x % $count; + if (!defined($len[$col]) || $len[$col] < length($entryvalue->[$x] . $entryname->[$x])) { + $len[$col] = length("$entryvalue->[$x]"."$entryname->[$x]"); + } + } + for my $t (0 .. $#$entryvalue) { + if (!($t % $count)) { $ret .= "\n "; } + $ret .= "$entryname->[$t]\($entryvalue->[$t]\)"; + $ret .= ' ' x ($len[$t % $count] - length($entryname->[$t] . $entryvalue->[$t]) + 1); + } + return $ret; +} +sub time_to { + my @timeunits = ('yr', 'month', 'week', 'day', 'hr', 'min', 'sec'); + my @timecalc = (31104000, 2592000, 604800, 86400, 3600, 60, 1); + my $seconds = shift; my $output; + if ($seconds == 0) { return "0 seconds"; } + for my $x (0 .. $#timecalc) { + my $y = int($seconds / $timecalc[$x]); + if ($y != 0 && $seconds != 0 && $seconds >= $timecalc[$x]) { + $seconds = ($seconds % $timecalc[$x]); + $output .= "$y $timeunits[$x]"; + $output .= 's' unless $y == 1; + if ($seconds == 0) { last; } + $output .= ', ' if $x < $#timecalc - 1 && ($seconds % $timecalc[$x+1]) && $seconds != 0; + $output .= ' and ' if !($seconds % $timecalc[$x+1]); + } + } + return $output; +} +sub time_from { + my $seconds = shift; + my ($sec, $min, $hr, $day, $mo, $yr, $wday) = localtime($seconds); + $mo = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Oct', 'Sep', 'Nov', 'Dec')[$mo]; + $wday = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')[$wday]; + $yr += 1900; + if ($hr < 10) { $hr = "0$hr"; } + if ($min < 10) { $min = "0$min"; } + if ($sec < 10) { $sec = "0$sec"; } + if ($day < 10) { $day .= ' '; } + return "$wday $mo $day $hr:$min:$sec $yr"; +} +die "Usage: $0 \n" . + "Example: $0 daxxar cyb\n" if @ARGV == 0; + +start: +my $username = shift(@ARGV); +my $page = get "http://jotun.ultrazone.org/g7/dump.php?player=$username"; + +# Only line is commented if there is no such user +if ($page =~ /^#[^\n]+$/) { print "$username: no such user\n"; exit 1; } +($page) = ($page =~ /\n([^#].*)/); # Remove the comment + +# @ent = entries on page, \t separated. +my @ent = split(/\t/, $page); + +# Assign each tab-separated entry to its hash-key +my %values = ( + 'user' => $ent[0], 'level' => $ent[1], + 'class' => $ent[2], 'next' => $ent[3], + 'host' => $ent[4], 'status' => $ent[5], + 'totalidle'=> $ent[6], 'created'=> $ent[14], + 'lastlog' => $ent[15], + 'penaltynames' => [ qw(msg nick part kick quit quest logout) ], + 'penaltytimes' => [ @ent[7 .. 13] ], + 'itemnames' => [ qw(amulet charm helm boots gloves ring leggings shield tunic weapon sum) ], + 'itemlvls' => [ @ent[16 .. 25] ] +); +$values{'next'} = time_to ($values{'next'}); +$values{'totalidle'} = time_to ($values{'totalidle'}); +$values{'lastlog'} = time_from($values{'lastlog'}); +$values{'created'} = time_from($values{'created'}); +if ($values{'status'}) {$values{'status'} = 'Online'} +else {$values{'status'} = 'Offline'} + +foreach my $str (@{[ @string ]}) { + $str =~ s/%user/$values{'user'}/g; + $str =~ s/%class/$values{'class'}/g; + $str =~ s/%level/$values{'level'}/g; + $str =~ s/%next/$values{'next'}/g; + $str =~ s/%status/$values{'status'}/g; + $str =~ s/%uhost/$values{'host'}/g; + $str =~ s/%ca/$values{'created'}/g; + $str =~ s/%llo/$values{'lastlog'}/g; + $str =~ s/%ti/$values{'totalidle'}/g; + if ($str =~ /%penalties/) { + my @penaltyname = qw(msg nick part kick quit quest logout); + my @penaltytime = @ent[7 .. 13]; + for my $t (0 .. $#penaltytime) { $penaltytime[$t] = time_to($penaltytime[$t]); } + my $cols = make_col(\@penaltyname, \@penaltytime, 3); + $str =~ s/%penalties/$cols/g; + } + if ($str =~ /%items/) { + my @itemname = qw(amulet charm helm boots gloves ring leggings shield tunic weapon sum); + my @itemlvls = @ent[16 .. 25]; + # Yay, lets get a nice sum(sum) in output! :D + my $sum; map($sum += $_, @itemlvls); + my $cols = make_col(\@itemname, [ @itemlvls, $sum ], 3); + $str =~ s/%items/$cols/g; + } + $str =~ s///g; + $str =~ s///g; + print "$str\n"; +} + +print "\n" if @ARGV != 0; +goto start if @ARGV != 0; diff --git a/irpg/index.php b/irpg/index.php new file mode 100644 index 0000000..4ec015e --- /dev/null +++ b/irpg/index.php @@ -0,0 +1,480 @@ + +

Game Info

+

The Idle RPG is just what it sounds like: an RPG in which the players + idle. In addition to merely gaining levels, players can find items and + battle other players. However, this is all done for you; you just idle. + There are no set classes; you can name your character anything you like, and + have its class be anything you like, as well.

+ +

Location

+

+ The Idle RPG can be played on the + SlashNET IRC Network in the + channel #G7. See this + link for a list of servers. +

+ + +

Registering

+ +

To register, simply:

+ + + /msg REGISTER <char name> <password> + <char class> + + +

Where 'char name' can be up to 16 chars long, 'password' can be up + to 8 characters, and 'char class' can be up to 30 chars.

+ + +

Logging In

+ +

To login, simply:

+ + + /msg LOGIN <char name> <password> + + +

This is a p0 (see Penalties) command.

+ + +

Logging Out

+ +

To logout, simply:

+ + + /msg LOGOUT + + +

This is a p20 (see Penalties) command.

+ + +

Changing Your Password

+ +

To change your password, simply:

+ + + /msg NEWPASS <new password> + + +

This is a p0 (see Penalties) command.

+

If you have forgotten your password, please use the + INFO command to find an online admin to help you. If your + administrator does not have the INFO command enabled, then just message + an op in the channel. They can probably help you.

+ + +

Removing Your Account

+ +

To remove your account, simply:

+ + + /msg REMOVEME + + +

This is a p0 (see Penalties) command :^)

+ + +

Changing Your Alignment

+ +

To change your alignment, simply:

+ + + /msg ALIGN <good|neutral|evil> + + +

This is a p0 (see Penalties) command.

+

Your alignment can affect certain aspects of the game. You 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.

+

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.

+ + +

Obtaining Bot Info

+ +

To see some simple information on the bot, simply:

+ + + /msg INFO + + +

This is a p0 (see Penalties) command.

+

This command gives info such as to which server the bot is connected + and the nicknames of online bot admins.

+

This command is optional, and may be disabled by your bot admin.

+ + + +

Levelling

+ +

To gain levels, you must only be logged in and idle. The time + between levels is based on your character level, and is calculated + by the formula:

+ + 600*(1.16^YOUR_LEVEL) + +

Where ^ represents the exponentiation operator.

+

Very high levels are calculated differently as of version 3.0. Levels + after level 60 have a next time to level of:

+ + (time to level @ 60) + ((1 day) * (level - 60)) + +

The exponent method code had simply gotten to that point that levels + were taking too long to complete.

+ + +

Checking the Active Quest

+ +

To see the active quest, its users, and its time left to + completion:

+ + + /msg QUEST + + +

This is a p0 (see Penalties) command.

+ + +

Checking Your Online Status

+ +

To see whether you are logged on, simply:

+ + + /msg WHOAMI + + +

This is a p0 (see Penalties) command.

+ + +

Penalties

+ + +

If you do something other than idle, like part, quit, talk in the + channel, change your nick, or notice the channel, you are + penalized. The penalties are time, in seconds, added to your next + time to level and are based on your character level. The formulae + are as follows:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nick change30*(1.14^(YOUR_LEVEL))
Part200*(1.14^(YOUR_LEVEL))
Quit20*(1.14^(YOUR_LEVEL))
LOGOUT command20*(1.14^(YOUR_LEVEL))
Being Kicked250*(1.14^(YOUR_LEVEL))
Channel privmsg[message_length]*(1.14^(YOUR_LEVEL))
Channel notice[message_length]*(1.14^(YOUR_LEVEL))
+
+

So, a level 25 character changing their nick would be penalized + 20*(1.14^25)=793 seconds towards their next level.

+

Penalty shorthand is p[num]. So, a nick change is a p30 event, + parting the channel is a p200 event, and quitting IRC is a p20 event. + Messages and notices are p[length of message in characters].

+ + +

Items

+ +

Each time you level, you find an item. You can find an item as + high as 1.5*YOUR_LEVEL (unless you find a + unique item). There are 10 types of items: rings, + amulets, charms, weapons, helms, tunics, gloves, leggings, + shields, and boots. You can find one of each type. When you find + an item with a level higher than the level of the item you already + have, you toss the old item and start using the new one. As of version + 3.0, there is an optional, p0 STATUS command that your admin may have + enabled, but you cannot see which items you have over IRC (only your + total item sum). You can, however, see which items you have on the web + here.

+ +

As you may guess, you have a higher chance of rolling an item of a + lower value than you do of rolling one of a higher value level. The exact + formula is as follows:

+ + + for each 'number' from 1 to YOUR_LEVEL*1.5
+   you have a 1 / ((1.4)^number) chance to find an + item at this level
+ end for + + +

As for item type, you have an equal chance to roll any type.

+ + + +

Battle

+ +

Each time you level, if your level is less than 25, you have a 25% + chance to challenge someone to combat. If your level is greater than or + equal to 25, you have a 100% chance to challenge someone. A pool of + opponents is chosen of all online players, and one is chosen randomly. If + there are no other online players, you fight no one. However, if you do + challenge someone, this is how the victor is decided:

+ +
    +
  • Your item levels are summed.
  • +
  • Their item levels are summed.
  • +
  • A random number between zero and your sum is taken.
  • +
  • A random number between zero and their sum is taken.
  • +
  • If your roll is larger than theirs, you win.
  • +
+ +

If you win, your time towards your next level is lowered. The amount + that it is lowered is based on your opponent's level. The formula is:

+ + + ((the larger number of (OPPONENT_LEVEL/4) and 7) / 100) * + YOUR_NEXT_TIME_TO_LEVEL + + +

This means that you lose no less than 7% from your next time to level. + If you win, your opponent is not penalized any time, unless you land a + Critical Strike.

+ +

If you lose, you will be penalized time. The penalty is calculated + using the formula:

+ + + ((the larger number of (OPPONENT_LEVEL/7) and 7) / 100) * + YOUR_NEXT_TIME_TO_LEVEL + + +

This means that you gain no less than 7% of your next time to level. + If you lose, your opponent is not awarded any time.

+ +

Battling the IRPG bot is a special case. The bot has an item sum of + 1+[highest item sum of all players]. The percent awarded if you win is a + constant 20%, and the percent penalized if you lose is a constant 10%.

+ +

As of version 3.0, if more than 15% of online players are level 45 or + higher, then a random level 45+ user will battle another random player + every hour. This is to speed up levelling among higher level players.

+ +

Also as of version 3.0, there is a grid system. The grid is a 500 x 500 + area in which players may walk. If you encounter another player on the + grid, you have a 1 / (NUMBER_OF_ONLINE_PLAYERS) chance to battle them. + Battle awards are calculated using the above formulae. More information + on the grid system is available here.

+ +

Also as of version 3.0, a successful battle may result an item being + stolen.

+ + +

Unique Items

+ +

As of v2.1.2, after level 25, you have a chance to roll items + significantly higher than items you would normally find at that level. + These are unique items, and have the following stats:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameItem Level RangeRequired User LevelChance to Roll
Mattt's Omniscience Grand Crown50-7425 or greater1 / 40
Juliet's Glorious Ring of Sparkliness50-7425 or greater1 / 40
Res0's Protectorate Plate Mail75-9930 or greater1 / 40
Dwyn's Storm Magic Amulet100-12435 or greater1 / 40
Jotun's Fury Colossal Sword150-17440 or greater1 / 40
Drdink's Cane of Blind Rage175-20045 or greater1 / 40
Mrquick's Magical Boots of Swiftness250-30048 or greater1 / 40
Jeff's Cluehammer of Doom300-35052 or greater1 / 40
+ +

The Hand of God

+ +

As of v3.0, every online user has a (roughly) 1/20 chance per day + of a "Hand of God" affecting them. A HoG can help or hurt your character + by carrying it between 5 and 75 percent towards or away from its next time + to level. The odds are in your favor, however, with an 80% chance to help + your character, and only a 20% chance of your character being smitten.

+ +

In addition to occurring randomly, admins may summon the HoG at their + whim.

+ + +

Critical Strike

+ +

As of v2.0.4, if a challenger beats his opponent in battle, he has a + 1/35 chance of landing a Critical Strike. If this occurs, his opponent + is penalized time towards his next time to level. This amount is + calculated by the formula:

+ + ((random number from 5 to 25) / 100) * OPPONENT'S_NEXT_TIME_TO_LEVEL + +

Meaning he gains no less than 5% and no more than 25% of his next time + to level.

+ + +

Team Battles

+ +

As of v3.0, every online user has (roughly) 1/4 chance per day of + being involved in a 'team battle.' Team battles pit three online + players against three other online players. Each side's items are summed, + and a winner is chosen as in regular battling. If the first group bests + the second group in combat, 20% of the lowest of the three's TTL is + removed from their clocks. If the first group loses, 20% of their lowest + member's TTL is added to their TTL.

+ + +

Calamities

+ +

As of v3.0, every online user has a (roughly) 1/8 chance per day of a + calamity occurring to them. A calamity is a bit of extremely bad luck that + either:
+ + a) slows a player 5-12% of their next time to level
+ b) lowers one of their item's value by 10% + +

+ + +

Godsends

+ +

As of v3.0, every online user has a (roughly) 1/8 chance per day of a + godsend occurring to them. A godsend is a bit of extremely good luck that + either:
+ + a) accelerates a player 5-12% of their next time to level
+ b) increases one of their item's value by 10% + +

+ + +

Quests

+ +

As of v2.3, there are Quests. Four level 40+ users that have been + online for more than ten hours are chosen to represent and assist the + Realm by going on a quest. If all four 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. As of v3.0, there are two kinds of quests: grid-based quests + and time-based quests. Time-based quests last a random time between 12 and + 24 hours. Grid-based quests are based on the grid + system and do not have a set time to completion. Rather, the questers + must reach certain points on the map for their quest to be complete. If + the quest is not completed, ALL online users are penalized a p15 as + punishment.

+ + +

Grid System

+ +

As of v3.0, the IRPG has a grid system. The grid can be considered + a 500 x 500 point map on which the players may walk. Every second, each + player has an equal chance to step up, down, or neither, and an equal + chance to step left, right, or neither. If a user encounters another + player, there is a 1/(NUMBER_OF_ONLINE_PLAYERS) chance that they will + battle one another. Normal battling rules apply.

+ +

Some quests require that users walk to certain points on the map. In + the spirit of IRPG, of course, the trek is made for you. Your character + will automatically walk in the direction that it is supposed to, although + at a much slower than normal pace (to avoid accidents, of course. you + don't want to fall down and risk a Realm-wide p15!).

+ + +

Item Stealing

+ +

As of v3.0, the IRPG has item stealing. After each battle, if the + challenger wins, he has a slightly less than 2% chance of stealing an + item from the challengee. Only items of a higher value are stolen, and + the challenger's old item is given to the challengee in a moment of pity. +

+ + + +

Credits

+ +

Many thanks to version 3.0's map creators, res0 and Jeb! The game + wouldn't be the same without you.

+

+ The IRPG would not be possible without help from a lot of people. + To jwbozzy, yawnwraith, Tosirap, res0, dwyn, Parallax, protomek, + Bert, clavicle, drdink, jeff, rasher, Sticks, Nerje, Asterax, + emad, inkblot(!), schmolli, mikegrb, mumkin, sean, Minhiriath, + and Dan, I give many thanks. Unfortunately, this list has grown too + large to maintain. More user contributions can be seen in the + ChangeLog. +

+ + + + diff --git a/irpg/makemap.php b/irpg/makemap.php new file mode 100644 index 0000000..d5ace6a --- /dev/null +++ b/irpg/makemap.php @@ -0,0 +1,60 @@ + 500) { + $stringx = $x - ((strlen($user)+1)*$width)-12; + } + if ($y+$height > 500) { + $stringy = $y - ($height/2)-2; + } + $magenta = imageColorAllocate($map,255,0,255); + imageColorTransparent($map,$magenta); + $brown = imagecolorallocate($map, 102, 51, 0); + $parchment = imagecolorallocate($map, 255, 255, 204); + + // Avoid drawing a brown dot on a brown area + $rgb = imageColorAt($map, $x, $y); + if ($rgb > 0) { // $rgb is 0 on our parchment-colored areas + $temp = $brown; + $brown = $parchment; + $parchment = $temp; + } + // YOU ARE HERE + imageFilledEllipse($map, $x, $y, 6, 6, $brown); + // background for text + imageFilledRectangle($map,$stringx+6,$stringy-($height/2),$stringx+6+$width*(strlen($user)+1),$stringy+($height/2),$brown); + // text itself + imageString($map,5,$stringx+7+($width/2),$stringy-($height/2)-1,$user,$parchment); + } + header("Content-type: image/png"); + imagePNG($map); + imageDestroy($map); +?> diff --git a/irpg/makequestmap.php b/irpg/makequestmap.php new file mode 100644 index 0000000..50bf488 --- /dev/null +++ b/irpg/makequestmap.php @@ -0,0 +1,79 @@ + diff --git a/irpg/makeworldmap.php b/irpg/makeworldmap.php new file mode 100644 index 0000000..69011a1 --- /dev/null +++ b/irpg/makeworldmap.php @@ -0,0 +1,26 @@ + diff --git a/irpg/maperror.png b/irpg/maperror.png new file mode 100644 index 0000000..11cc12b Binary files /dev/null and b/irpg/maperror.png differ diff --git a/irpg/newmap.png b/irpg/newmap.png new file mode 100644 index 0000000..46f947b Binary files /dev/null and b/irpg/newmap.png differ diff --git a/irpg/players.php b/irpg/players.php new file mode 100644 index 0000000..106bafb --- /dev/null +++ b/irpg/players.php @@ -0,0 +1,37 @@ + + +

Players

+

Pick a player to view

+

[gray=offline]

+
    +".htmlentities($user). + ", the level $level $class. Next level in $next_level.\n"; + + } +?> +
+

For a script to view player stats from a terminal, try this perl script by + daxxar.

+ +

See player stats in table format.

+ + diff --git a/irpg/playerview.php b/irpg/playerview.php new file mode 100644 index 0000000..ca201f3 --- /dev/null +++ b/irpg/playerview.php @@ -0,0 +1,156 @@ +Player Info"; + $file = fopen($irpg_db,"r"); + fgets($file,1024); // skip top comment + $found=0; + while ($line=fgets($file,1024)) { + if (substr($line,0,strlen($_GET['player'])+1) == $_GET['player']."\t") { + list($user,,$isadmin,$level,$class,$secs,,$uhost,$online,$idled, + $x,$y, + $pen['mesg'], + $pen['nick'], + $pen['part'], + $pen['kick'], + $pen['quit'], + $pen['quest'], + $pen['logout'], + $created, + $lastlogin, + $item['amulet'], + $item['charm'], + $item['helm'], + $item['boots'], + $item['gloves'], + $item['ring'], + $item['leggings'], + $item['shield'], + $item['tunic'], + $item['weapon'], + $alignment, + ) = explode("\t",trim($line)); + $found=1; + break; + } + } + if (!$found) echo "

Error

No such user.

\n"; + else { + $class=htmlentities($class); + /* if we htmlentities($user), then we cannot use links with it. */ + echo "

User: ".htmlentities($user)."
\n". + " Class: $class
\n". + " Admin?: ".($isadmin?"Yes":"No")."
\n". + " Level: $level
\n". + " Next level: ".duration($secs)."
\n". + " Status: O".($online?"n":"ff")."line
\n". + " Host: ".($uhost?$uhost:"Unknown")."
\n". + " Account Created: ".date("D M j H:i:s Y",$created)."
\n". + " Last login: ".date("D M j H:i:s Y",$lastlogin)."
\n". + " Total time idled: ".duration($idled)."
\n". + " Current position: [$x,$y]
\n". + " Alignment: ".($alignment=='e'?"Evil":($alignment=='n'?"Neutral":"Good"))."
\n". + " XML: [link]

\n". + "

Map

\n". + " ".($showmap?"
\n\n":"

Show map

\n\n")."". + "

Items

\n

"; + ksort($item); + $sum = 0; + foreach ($item as $key => $val) { + $uniquecolor="#be9256"; + if ($key == "helm" && substr($val,-1,1) == "a") { + $val = intval($val)." [Mattt's Omniscience Grand Crown]"; + } + if ($key == "tunic" && substr($val,-1,1) == "b") { + $val = intval($val)." [Res0's Protectorate Plate Mail]"; + } + if ($key == "amulet" && substr($val,-1,1) == "c") { + $val = intval($val)." [Dwyn's Storm Magic Amulet]"; + } + if ($key == "weapon" && substr($val,-1,1) == "d") { + $val = intval($val)." [Jotun's Fury Colossal Sword]"; + } + if ($key == "weapon" && substr($val,-1,1) == "e") { + $val = intval($val)." [Drdink's Cane of Blind Rage]"; + } + if ($key == "boots" && substr($val,-1,1) == "f") { + $val = intval($val)." [Mrquick's Magical Boots of Swiftness]"; + } + if ($key == "weapon" && substr($val,-1,1) == "g") { + $val = intval($val)." [Jeff's Cluehammer of Doom]"; + } + if ($key == "ring" && substr($val,-1,1) == "h") { + $val = intval($val)." [Juliet's Glorious Ring of Sparkliness]"; + } + echo " $key: $val
\n"; + $sum += $val; + } + echo "
\n sum: $sum
\n". + "

". + "

Penalties

\n". + "

\n"; + + ksort($pen); + $sum = 0; + foreach ($pen as $key => $val) { + echo " $key: ".duration($val)."
\n"; + $sum += $val; + } + echo "
\n total: ".duration($sum)."

\n"; + + $file = fopen($irpg_mod,"r"); + $temp = array(); + while ($line=fgets($file,1024)) { + if (strstr($line," ".$_GET['player']." ") || + strstr($line," ".$_GET['player'].", ") || + substr($line,0,strlen($_GET['player'])+1) == + $_GET['player']." " || + substr($line,0,strlen($_GET['player'])+3) == + $_GET['player']."'s ") { + array_push($temp,$line); + } + } + fclose($file); + if (!is_null($temp) && count($temp)) { + echo('

'); + echo $_GET['allmods']!=1?"Recent ":""; + echo('Character Modifiers

'); + if ($_GET['allmods'] == 1 || count($temp) < 6) { + foreach ($temp as $line) { + $line=htmlentities(trim($line)); + echo " $line
\n"; + } + echo "
\n"; + } + else { + end($temp); + for ($i=0;$i<4;++$i) prev($temp); + for ($line=trim(current($temp));$line;$line=trim(next($temp))) { + $line=htmlentities(trim($line)); + echo " $line
\n"; + } + } + } + if ($_GET['allmods'] != 1 && count($temp) > 5) { +?> +
+ [View all Character Modifiers ()] +

+ + diff --git a/irpg/quest.php b/irpg/quest.php new file mode 100644 index 0000000..e6aab1a --- /dev/null +++ b/irpg/quest.php @@ -0,0 +1,118 @@ +Current Quest\n"; + include("commonfunctions.php"); + $file = fopen($irpg_qfile,"r"); + $type=0; + while ($line=fgets($file,1024)) { + $arg = explode(" ",trim($line)); + if ($arg[0] == "T") { + unset($arg[0]); + $text = implode(" ",$arg); + } + elseif ($arg[0] == "Y") { + $type = $arg[1]; + } + elseif ($arg[0] == "P") { + $p1[0] = $arg[1]; + $p1[1] = $arg[2]; + $p2[0] = $arg[3]; + $p2[1] = $arg[4]; + } + elseif ($arg[0] == "S") { + if ($type == 1) $time = $arg[1]; + elseif ($type == 2) $stage = $arg[1]; + } + elseif ($arg[0] == "P1") { + $player[1]['name'] = $arg[1]; + if ($type == 2) { + $player[1]['x'] = $arg[2]; + $player[1]['y'] = $arg[3]; + } + } + elseif ($arg[0] == "P2") { + $player[2]['name'] = $arg[1]; + if ($type == 2) { + $player[2]['x'] = $arg[2]; + $player[2]['y'] = $arg[3]; + } + } + elseif ($arg[0] == "P3") { + $player[3]['name'] = $arg[1]; + if ($type == 2) { + $player[3]['x'] = $arg[2]; + $player[3]['y'] = $arg[3]; + } + } + elseif ($arg[0] == "P4") { + $player[4]['name'] = $arg[1]; + if ($type == 2) { + $player[4]['x'] = $arg[2]; + $player[4]['y'] = $arg[3]; + } + } + } + if (!$type) { + echo "

Sorry, there is no active quest.

\n"; + } + else { + echo "

Quest: To $text.

\n"; + if ($type == 1) { + echo "

Time to completion: ".duration($time-time()). + "

\n"; + } + elseif ($type == 2) { + if ($stage == 1) { + echo "

Current goal: [$p1[0],$p1[1]]

\n"; + } + else { + echo "

Current goal: [$p2[0],$p2[1]]

>\n"; + } + } + echo "

Participant 1: ".htmlentities($player[1]['name']). + "
\n"; + if ($type == 2) { + echo " Position: [".$player[1]['x'].",".$player[1]['y']."]

\n"; + } + else echo "
\n"; + echo "

Participant 2: ".htmlentities($player[2]['name']). + "
\n"; + if ($type == 2) { + echo " Position: [".$player[2]['x'].",".$player[2]['y']."]

\n"; + } + else echo "
\n"; + echo "

Participant 3: ".htmlentities($player[3]['name']). + "
\n"; + if ($type == 2) { + echo " Position: [".$player[3]['x'].",".$player[3]['y']."]

\n"; + } + else echo "
\n"; + echo "

Participant 4: ".htmlentities($player[4]['name']). + "
\n"; + if ($type == 2) { + echo " Position: [".$player[4]['x'].",".$player[4]['y']."]

\n". + "

Quest Map:

\n". + "

[Questers are shown in blue, current goal in red]

\n". + "
\"Idle
\n". + " \n". + " \"".htmlentities($player[1]['name']).\n". + " \"".htmlentities($player[2]['name']).\n". + " \"".htmlentities($player[3]['name']).\n". + " \"".htmlentities($player[4]['name']).\n". + " \n"; + } + else echo "
\n"; + } + echo "
\n"; + include("footer.php"); +?> diff --git a/irpg/tablegrad.gif b/irpg/tablegrad.gif new file mode 100644 index 0000000..71d43ca Binary files /dev/null and b/irpg/tablegrad.gif differ diff --git a/irpg/up.png b/irpg/up.png new file mode 100644 index 0000000..abcfdec Binary files /dev/null and b/irpg/up.png differ diff --git a/irpg/worldmap.php b/irpg/worldmap.php new file mode 100644 index 0000000..33e5b6b --- /dev/null +++ b/irpg/worldmap.php @@ -0,0 +1,27 @@ + + +

World Map

+

[offline users are red, online users are blue]

+ + +
+ IdleRPG World Map + +\n"; + } + fclose($file); +?> + +
+ + diff --git a/irpg/xml.php b/irpg/xml.php new file mode 100644 index 0000000..4fbc158 --- /dev/null +++ b/irpg/xml.php @@ -0,0 +1,82 @@ +"; +?> + + + + + + + + + + + + + + + $val) { + echo " <$key>$val\n"; + $sum += $val; + } + echo " $sum\n"; +?> + + + $val) { + echo " <$key>$val\n"; + $sum += $val; + } + echo " $sum\n"; +?> + +