2 # Copyright (c) 2010, Alex "Eevee" Munroe
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are met:
8 # * Redistributions of source code must retain the above copyright notice,
9 # this list of conditions, and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above copyright notice,
11 # this list of conditions, and the following disclaimer in the
12 # documentation and/or other materials provided with the distribution.
13 # * Neither the name of the author of this software nor the name of
14 # contributors to this software may be used to endorse or promote products
15 # derived from this software without specific prior written consent.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
31 import supybot
.utils
as utils
32 from supybot
.commands
import *
33 import supybot
.plugins
as plugins
34 import supybot
.ircutils
as ircutils
35 import supybot
.ircmsgs
as ircmsgs
36 import supybot
.callbacks
as callbacks
37 import supybot
.schedule
as schedule
44 # dnum, used in xlogfile
46 'the Dungeons of Doom',
53 'the Elemental Planes',
56 # achievements, used by livelog
58 'completed the Quest and obtained the Bell of Opening',
60 'obtained the Candelabrum of Invocation',
61 'obtained the Book of the Dead',
62 'performed the Invocation',
63 'obtained the Amulet of Yendor',
64 'reached the Elemental Planes',
65 'reached the Astral Plane',
66 'ascended to a higher plane of existence',
67 "completed Mine's End",
75 for keyval
in line
.split(':'):
76 key
, val
= keyval
.split('=', 1)
80 # Original and ending gender/alignment are tracked separately
81 if data
['gender0'] == data
['gender']:
82 data
['gender_delta'] = data
['gender']
84 data
['gender_delta'] = "%(gender0)s->%(gender)s" % data
86 if data
['align0'] == data
['align']:
87 data
['align_delta'] = data
['align']
89 data
['align_delta'] = "%(align0)s->%(align)s" % data
91 data
['level_desc'] = "%s dlvl %s" %
(dungeons
[ int(data
['deathdnum']) ],
93 if data
['deathlev'] != data
['maxlvl']:
94 data
['level_desc'] += " (deepest dlvl: %(maxlvl)s)" % data
96 # Human-readable time played
97 realtime
= int(data
['realtime'])
98 time_secs
= realtime %
60; realtime
//= 60
99 time_mins
= realtime %
60; realtime
//= 60
100 time_hrs
= realtime %
24; realtime
//= 24
105 # Construct '0d 0h 5m 12s', then lop off the 0x bits
106 data
['realtime_pretty'] = re
.sub(
109 "%dd %dh %dm" %
(time_days
, time_hrs
, time_mins
)
114 def parse_livelog(line
):
117 for keyval
in line
.split(':'):
118 key
, val
= keyval
.split('=', 1)
123 def livelog_announcement(livelog
):
125 if 'achieve_diff' in livelog
:
126 # these are stored as 0xABC
127 achieve_diff
= int(livelog
['achieve_diff'], 16)
129 # each item in the achievement list is encoded as that number bit
130 for i
, achievement
in enumerate(achievements
):
131 if achieve_diff
& (1 << i
):
132 return "{player} just {achievement}, on turn {turns}!".format(
133 achievement
=achievement
, **livelog
)
135 # achieve_diff is zero? nothing changed? can't happen, but..
136 return "{0} just accomplished nothing!".format(player
)
139 if 'wish' in livelog
:
140 return "%(player)s just wished for %(wish)s, on turn %(turns)s." % livelog
142 # kill a player ghost
143 if 'bones_killed' in livelog
:
144 return "%(player)s just killed the %(bones_monst)s of %(bones_killed)s, " \
145 "the former %(bones_rank)s, on turn %(turns)s on dlvl %(dlev)s." % livelog
147 # killed a unique monster
148 # may result in spam for the three horsemen..
149 if 'killed_uniq' in livelog
:
150 if livelog
['killed_uniq'] == 'Medusa':
151 # Medusa is already an achievement. No need to announce twice
153 return "%(player)s has just slain %(killed_uniq)s on turn %(turns)s!" % livelog
156 if 'shoplifted' in livelog
:
157 return "%(player)s just stole %(shoplifted)s zorkmids' worth of merchandise " \
158 "from %(shopkeeper)s's %(shop)s, on turn %(turns)s. Tut tut." % livelog
161 return "%(player)s just did something-or-other." % livelog
163 report_template
= "{name} ({role} {race} {gender_delta} {align_delta}): " \
164 "{death} on {level_desc}. {points} points in {turns} turns, " \
165 "wasting {realtime_pretty}. {dumplog}"
168 CONFIG_PLAYGROUND
= '/opt/nethack.veekun.com/nethack/var'
169 CONFIG_USERDATA_FILE
= '/opt/nethack.veekun.com/dgldir/userdata'
170 CONFIG_USERDATA_WEB
= 'http://nethack.veekun.com/userdata'
171 CONFIG_CHANNEL
= '#cafe'
172 class NetHack(callbacks
.Plugin
):
173 """Add the help for "@plugin help NetHack" here
174 This should describe *how* to use this plugin."""
175 def __init__(self
, irc
):
176 self
.__parent
= super(NetHack
, self
)
177 self
.__parent
.__init__(irc
)
179 self
.xlog
= open(os
.path
.join(CONFIG_PLAYGROUND
, 'xlogfile'))
180 self
.livelog
= open(os
.path
.join(CONFIG_PLAYGROUND
, 'livelog'))
181 self
.xlog
.seek(0, os
.SEEK_END
)
182 self
.livelog
.seek(0, os
.SEEK_END
)
184 # Remove the event first, in case this is a reload
185 schedule
.removePeriodicEvent('nethack-log-ping')
189 schedule
.addPeriodicEvent(callback
, 10, name
='nethack-log-ping')
191 def _checkLogs(self
, irc
):
192 """Checks the files for new lines and, if there be any, prints them to
195 Actual work is all done here.
199 self
.xlog
.seek(0, os
.SEEK_CUR
)
200 line
= self
.xlog
.readline()
202 data
= parse_xlog(line
)
205 dumplog_paths
= glob(
206 os
.path
.join(CONFIG_USERDATA_FILE
,
213 (_
, dumplog_file
) = os
.path
.split(dumplog_paths
[0])
214 dumplog_url
= '{base}/{name}/dumplog/{file}'.format(
215 base
=CONFIG_USERDATA_WEB
,
220 dumplog_url
= "Can't find dumplog :("
222 report
= report_template
.format(dumplog
=dumplog_url
, **data
)
223 msg
= ircmsgs
.privmsg(CONFIG_CHANNEL
, report
)
227 self
.livelog
.seek(0, os
.SEEK_CUR
)
228 line
= self
.livelog
.readline()
230 data
= parse_livelog(line
)
231 report
= livelog_announcement(data
)
233 msg
= ircmsgs
.privmsg(CONFIG_CHANNEL
, report
)
240 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: