| 1 |
#!/usr/bin/env python
|
| 2 |
# -*- Mode: Python; tab-width: 4 -*-
|
| 3 |
#
|
| 4 |
# Copyright (C) 2001 Gianluigi Tiesi <sherpya@netfarm.it>
|
| 5 |
#
|
| 6 |
# This program is free software; you can redistribute it and/or modify
|
| 7 |
# it under the terms of the GNU General Public License as published by the
|
| 8 |
# Free Software Foundation; either version 2, or (at your option) any later
|
| 9 |
# version.
|
| 10 |
#
|
| 11 |
# This program is distributed in the hope that it will be useful, but
|
| 12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 13 |
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 14 |
# for more details.
|
| 15 |
#
|
| 16 |
# Ranking patch by <JEBs@shbe.net> 20020503
|
| 17 |
# Small "unsigned int" fix by <JEBs@shbe.net> 20020818
|
| 18 |
#
|
| 19 |
# ==========================================================================
|
| 20 |
__version__ = "0.9"
|
| 21 |
|
| 22 |
|
| 23 |
### Only if CGI_MODE = 1
|
| 24 |
CGI_MODE=0
|
| 25 |
FILE="/opt/bnetd/var/ladders/ladder.D2DV"
|
| 26 |
MAX=100
|
| 27 |
|
| 28 |
from struct import unpack,calcsize
|
| 29 |
from string import find,split,join
|
| 30 |
from os import stat
|
| 31 |
from sys import argv,exit,stdout
|
| 32 |
from getopt import getopt
|
| 33 |
|
| 34 |
|
| 35 |
#### Templates
|
| 36 |
modes = [ 'html', 'ansi', 'ascii', 'python' ]
|
| 37 |
templates = {}
|
| 38 |
for m in modes:
|
| 39 |
templates[m] = {}
|
| 40 |
|
| 41 |
|
| 42 |
### html ###
|
| 43 |
|
| 44 |
#
|
| 45 |
templates['html']['header']="""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
| 46 |
<html>
|
| 47 |
<head>
|
| 48 |
<title>D2 Closed Realm Ladder</title>
|
| 49 |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
| 50 |
</head>
|
| 51 |
<body bgcolor="#000000" text="#ffff00">
|
| 52 |
<h2 style="color: lightgreen;" align="center">D2 Closed Realm Ladder</h2>
|
| 53 |
<table style="border: solid lightblue; border-width: 1px;" align="center" border="0" width="80%" summary="">
|
| 54 |
"""
|
| 55 |
|
| 56 |
#
|
| 57 |
templates['html']['footer']=""" </table>
|
| 58 |
<p style="color: lightblue;" align="center">Generated by ladder.py v %s - © 2001 <a style="color: lightgreen;" href="mailto:sherpya@netfarm.it">Sherpya</a></p>
|
| 59 |
</body>
|
| 60 |
</html>
|
| 61 |
""" % __version__
|
| 62 |
|
| 63 |
# %s for description of ladder type
|
| 64 |
templates['html']['summary'] = """ <tr style="color: lightblue" bgcolor="#666666"><th colspan="5">Ladder for %s</th></tr>
|
| 65 |
"""
|
| 66 |
|
| 67 |
#
|
| 68 |
templates['html']['tbheader'] = """<tr style="color: lightgreen;"><th align="center">#</th><th align="left">Charname</th><th align="right">level</th><th align="center">class</th><th align="right">exp</th></tr>
|
| 69 |
"""
|
| 70 |
# %s for charname
|
| 71 |
templates['html']['normal'] = """%s"""
|
| 72 |
templates['html']['hardcore'] = { 0 : """<span style="color: red;">%s</span>""",
|
| 73 |
1 : """<span style="color: orange;">%s</span>""" }
|
| 74 |
|
| 75 |
# %s charname - %d level - %s class - %d experience
|
| 76 |
templates['html']['entry'] = """<tr bgcolor="#222222"><td align="right">%d</td><td align="left">%s</td><td align="right">%d</td><td align="center">%s</td><td align="right">%d</td></tr>
|
| 77 |
"""
|
| 78 |
|
| 79 |
#
|
| 80 |
templates['html']['separator'] = """<tr><td colspan="5"> </td></tr>
|
| 81 |
"""
|
| 82 |
|
| 83 |
#### html
|
| 84 |
|
| 85 |
#### ascii / ansi
|
| 86 |
line = '-' * 59 + '\n'
|
| 87 |
s10 = ' ' * 10
|
| 88 |
s14 = ' ' * 14
|
| 89 |
s5 = ' ' * 5
|
| 90 |
text = 'D2 Closed Ladder'
|
| 91 |
esc = '\033'
|
| 92 |
off = esc + '[0m'
|
| 93 |
|
| 94 |
colors = {
|
| 95 |
'grey': esc + '[1;30m',
|
| 96 |
'red': esc + '[1;31m',
|
| 97 |
'green': esc + '[1;32m',
|
| 98 |
'yellow': esc + '[1;33m',
|
| 99 |
'blue': esc + '[1;34m',
|
| 100 |
'purple': esc + '[1;35m',
|
| 101 |
'magenta': esc + '[1;36m',
|
| 102 |
'white': esc + '[1;37m',
|
| 103 |
'green': esc + '[1;32m'
|
| 104 |
}
|
| 105 |
|
| 106 |
templates['ascii']['header'] = line + (int((len(line) - len(text))/ 2)) * ' ' + text + '\n' + line
|
| 107 |
templates['ascii']['footer'] = 'generated by ladder.py (c) Sherpya [sherpya@netfarm.it]\n'
|
| 108 |
templates['ascii']['summary'] = 'Ladder for %s\n\n'
|
| 109 |
templates['ascii']['tbheader'] = ' # charname' + s14 + 'level' + s10 + 'class' + s10 + 'exp' + '\n\n'
|
| 110 |
templates['ascii']['normal'] = '%s'
|
| 111 |
templates['ascii']['hardcore'] = { 0 : '*%s', 1: '^%s' }
|
| 112 |
templates['ascii']['entry'] = '%3d %-23s %2d %16s %10d\n'
|
| 113 |
templates['ascii']['separator'] = line + '\n'
|
| 114 |
|
| 115 |
line = colors['blue'] + ( '-' * 59) + off + '\n'
|
| 116 |
templates['ansi']['header'] = line + (int((len(line) - len(text) - 10)/ 2)) * ' ' + colors['green'] + text + off + '\n' + line
|
| 117 |
templates['ansi']['footer'] = colors['green'] + 'generated by ' + colors['blue'] + 'ladder.py' + colors['green'] + ' (c) Sherpya [sherpya@netfarm.it]' + off + '\n'
|
| 118 |
templates['ansi']['summary'] = colors['white'] + 'Ladder for %s' + off + '\n\n'
|
| 119 |
templates['ansi']['tbheader'] = colors['green'] + ' # charname' + s14 + 'level' + s10 + 'class' + s10 + 'exp' + off + '\n\n'
|
| 120 |
templates['ansi']['normal'] = colors['yellow'] + '%s'
|
| 121 |
templates['ansi']['hardcore'] = { 0 : colors['red'] + '%s', 1: colors['grey'] + '%s' }
|
| 122 |
templates['ansi']['entry'] = colors['yellow'] + '%3d %-30s %2d %16s %10d' + off + '\n'
|
| 123 |
templates['ansi']['separator'] = line + '\n'
|
| 124 |
|
| 125 |
|
| 126 |
del text
|
| 127 |
#### ascii / ansi
|
| 128 |
|
| 129 |
|
| 130 |
|
| 131 |
### Some struct from d2cs/d2dbs source
|
| 132 |
#
|
| 133 |
# ladder header (4 + 4 = 8):
|
| 134 |
# bn_int maxtype
|
| 135 |
# bn_int checksum
|
| 136 |
LD_HEAD="<2i"
|
| 137 |
szLD_HEAD = calcsize(LD_HEAD)
|
| 138 |
|
| 139 |
#
|
| 140 |
# ladder info (4 + 2 + 1 + 1 + 16 = 24):
|
| 141 |
# bn_int experience
|
| 142 |
# bn_short status
|
| 143 |
# bn_byte level
|
| 144 |
# bn_byte class;
|
| 145 |
# char charname[16];
|
| 146 |
LD_INFO="<Ihbb16s"
|
| 147 |
szLD_INFO = calcsize(LD_INFO)
|
| 148 |
|
| 149 |
#
|
| 150 |
# ladder index (4 + 4 + 4 = 12):
|
| 151 |
# bn_int type
|
| 152 |
# bn_int offset
|
| 153 |
# bn_int number
|
| 154 |
LD_INDEX="<3i"
|
| 155 |
szLD_INDEX = calcsize(LD_INDEX)
|
| 156 |
|
| 157 |
## Status flags
|
| 158 |
S_INIT = 0x1
|
| 159 |
S_EXP = 0x20
|
| 160 |
S_HC = 0x04
|
| 161 |
S_DEAD = 0x08
|
| 162 |
|
| 163 |
|
| 164 |
classes = {
|
| 165 |
0x00 : ['Amazon', 'f'],
|
| 166 |
0x01 : ['Sorceress', 'f'],
|
| 167 |
0x02 : ['Necromancer', 'm'],
|
| 168 |
0x03 : ['Paladin', 'm'],
|
| 169 |
0x04 : ['Barbarian', 'm'],
|
| 170 |
0x05 : ['Druid', 'm'],
|
| 171 |
0x06 : ['Assassin', 'f']
|
| 172 |
}
|
| 173 |
|
| 174 |
desc = {
|
| 175 |
'nor': 'Diablo II',
|
| 176 |
'exp': 'Lord of Desctruction'
|
| 177 |
}
|
| 178 |
|
| 179 |
|
| 180 |
diff = {
|
| 181 |
'nor': {
|
| 182 |
0x1: { 0 : { 'm': 'Sir', 'f': 'Dame' },
|
| 183 |
1 : { 'm': 'Count', 'f': 'Countess' }
|
| 184 |
},
|
| 185 |
|
| 186 |
0x2: { 0 : { 'm': 'Lord', 'f': 'Lady' },
|
| 187 |
1 : { 'm': 'Duke', 'f': 'Duchess' }
|
| 188 |
},
|
| 189 |
|
| 190 |
0x3: { 0 : { 'm': 'Baron', 'f': 'Baroness' },
|
| 191 |
1 : { 'm': 'King', 'f': 'Queen' }
|
| 192 |
}
|
| 193 |
},
|
| 194 |
|
| 195 |
'exp': {
|
| 196 |
0x1: { 0 : { 'm': 'Slayer', 'f': 'Slayer' },
|
| 197 |
1 : { 'm': 'Destroyer', 'f': 'Destroyer' }
|
| 198 |
},
|
| 199 |
|
| 200 |
0x2: { 0 : { 'm': 'Champion', 'f': 'Champion' },
|
| 201 |
1 : { 'm': 'Conqueror', 'f': 'Conqueror' }
|
| 202 |
},
|
| 203 |
|
| 204 |
0x3: { 0 : { 'm': 'Patriarch', 'f': 'Matriarch' },
|
| 205 |
1 : { 'm': 'Guardian', 'f': 'Guardian' }
|
| 206 |
}
|
| 207 |
}
|
| 208 |
}
|
| 209 |
|
| 210 |
## Utils
|
| 211 |
|
| 212 |
|
| 213 |
def remove_null(text):
|
| 214 |
return split(text, chr(0))[0]
|
| 215 |
|
| 216 |
|
| 217 |
def get_ladder(file):
|
| 218 |
try:
|
| 219 |
size = stat(file)[6]
|
| 220 |
data = open(file, "rb")
|
| 221 |
except:
|
| 222 |
print "Error opening %s for read" % file
|
| 223 |
exit()
|
| 224 |
|
| 225 |
maxtype, checksum = unpack(LD_HEAD, data.read(szLD_HEAD))
|
| 226 |
|
| 227 |
size = size - szLD_HEAD
|
| 228 |
|
| 229 |
head = []
|
| 230 |
|
| 231 |
for i in range(maxtype):
|
| 232 |
type, offset, number = unpack(LD_INDEX, data.read(szLD_INDEX))
|
| 233 |
size = size - szLD_INDEX
|
| 234 |
head.append(
|
| 235 |
{
|
| 236 |
'type': type,
|
| 237 |
'offset': offset,
|
| 238 |
'number': number
|
| 239 |
})
|
| 240 |
|
| 241 |
|
| 242 |
ladder = {}
|
| 243 |
ladder['nor'] = []
|
| 244 |
ladder['exp'] = []
|
| 245 |
|
| 246 |
temp = {}
|
| 247 |
temp['nor'] = []
|
| 248 |
temp['exp'] = []
|
| 249 |
|
| 250 |
|
| 251 |
while size > 0:
|
| 252 |
try:
|
| 253 |
experience, status, level, _class, charname = unpack(LD_INFO, data.read(szLD_INFO))
|
| 254 |
except:
|
| 255 |
### Bad data
|
| 256 |
size = size - szLD_INFO
|
| 257 |
continue
|
| 258 |
|
| 259 |
size = size - szLD_INFO
|
| 260 |
|
| 261 |
## Avoid null chars
|
| 262 |
if not experience:
|
| 263 |
continue
|
| 264 |
|
| 265 |
charname = remove_null(charname)
|
| 266 |
died = 0
|
| 267 |
|
| 268 |
if status & S_EXP:
|
| 269 |
_type = 'exp'
|
| 270 |
difficulty = ((status >> 0x08) & 0x0f) / 5
|
| 271 |
else:
|
| 272 |
_type = 'nor'
|
| 273 |
difficulty = ((status >> 0x08) & 0x0f) / 5
|
| 274 |
|
| 275 |
if status & S_HC:
|
| 276 |
hc = 1
|
| 277 |
if status & S_DEAD:
|
| 278 |
died = 1
|
| 279 |
else:
|
| 280 |
hc = 0
|
| 281 |
|
| 282 |
c_class = classes[_class]
|
| 283 |
|
| 284 |
if difficulty and diff[_type].has_key(difficulty):
|
| 285 |
prefix = diff[_type][difficulty][hc][c_class[1]]
|
| 286 |
else:
|
| 287 |
prefix = None
|
| 288 |
|
| 289 |
char = (experience, {
|
| 290 |
'charname' : charname,
|
| 291 |
'prefix' : prefix,
|
| 292 |
'experience' : experience,
|
| 293 |
'class' : c_class[0],
|
| 294 |
'sex' : c_class[0],
|
| 295 |
'level' : level,
|
| 296 |
'type' : _type,
|
| 297 |
'difficulty' : difficulty,
|
| 298 |
'hc' : hc,
|
| 299 |
'died' : died
|
| 300 |
})
|
| 301 |
## Dupe char? why?
|
| 302 |
if char not in temp[_type]:
|
| 303 |
temp[_type].append(char)
|
| 304 |
|
| 305 |
data.close()
|
| 306 |
|
| 307 |
## Sorting by exp
|
| 308 |
temp['nor'].sort()
|
| 309 |
temp['nor'].reverse()
|
| 310 |
temp['exp'].sort()
|
| 311 |
temp['exp'].reverse()
|
| 312 |
|
| 313 |
for _type in temp.keys():
|
| 314 |
for ch in temp[_type]:
|
| 315 |
ladder[_type].append(ch[1])
|
| 316 |
del temp
|
| 317 |
|
| 318 |
return ladder
|
| 319 |
|
| 320 |
def generate(ladder, mode, output, max):
|
| 321 |
|
| 322 |
output.write(templates[mode]['header'])
|
| 323 |
|
| 324 |
for _type in ladder.keys():
|
| 325 |
count = 1
|
| 326 |
output.write(templates[mode]['summary'] % desc[_type])
|
| 327 |
output.write(templates[mode]['tbheader'])
|
| 328 |
|
| 329 |
for ch in ladder[_type]:
|
| 330 |
if ch['prefix']:
|
| 331 |
charname = "%s %s" % (ch['prefix'], ch['charname'])
|
| 332 |
else:
|
| 333 |
charname = ch['charname']
|
| 334 |
|
| 335 |
if ch['hc']:
|
| 336 |
charname = templates[mode]['hardcore'][ch['died']] % charname
|
| 337 |
else:
|
| 338 |
charname = templates[mode]['normal'] % charname
|
| 339 |
|
| 340 |
output.write(templates[mode]['entry'] % (count, charname, ch['level'], ch['class'], ch['experience']))
|
| 341 |
count = count + 1
|
| 342 |
if count > max:
|
| 343 |
break
|
| 344 |
|
| 345 |
output.write(templates[mode]['separator'])
|
| 346 |
|
| 347 |
output.write(templates[mode]['footer'])
|
| 348 |
|
| 349 |
|
| 350 |
def pickle_to(ladder, output):
|
| 351 |
try:
|
| 352 |
from cPickle import dump
|
| 353 |
except:
|
| 354 |
from pickle import dump
|
| 355 |
|
| 356 |
try:
|
| 357 |
out = open(output, "wb")
|
| 358 |
except:
|
| 359 |
print "Cannot open %s for pickle dump" % output
|
| 360 |
exit()
|
| 361 |
|
| 362 |
dump(ladder, out)
|
| 363 |
out.close()
|
| 364 |
|
| 365 |
|
| 366 |
### Main
|
| 367 |
|
| 368 |
### CGI MODE
|
| 369 |
if CGI_MODE:
|
| 370 |
print "Content-Type: text/html"
|
| 371 |
print
|
| 372 |
ladder = get_ladder(FILE)
|
| 373 |
generate(ladder, 'html', stdout, MAX)
|
| 374 |
exit()
|
| 375 |
|
| 376 |
args = argv[1:]
|
| 377 |
optlist, args = getopt(args, "hi:o:m:n:")
|
| 378 |
if len(args):
|
| 379 |
for bad in args:
|
| 380 |
print "%s: Unrecognized option %s" % (argv[0], bad)
|
| 381 |
exit()
|
| 382 |
|
| 383 |
### defaults
|
| 384 |
file = None
|
| 385 |
output = None # stdout
|
| 386 |
mode = modes[0]
|
| 387 |
real_max = 1000
|
| 388 |
max = 100
|
| 389 |
|
| 390 |
def show_help():
|
| 391 |
print
|
| 392 |
print "ladder.py v%s - (c) 2001 Sherpya <sherpya@netfarm.it>" % __version__
|
| 393 |
print "Usage: ladder.py -i ladder_file [-o outputfile] [-m mode] [-n max ladder chars]"
|
| 394 |
print
|
| 395 |
print " -i ladder_file, is the ladder file like ladder.D2DV"
|
| 396 |
print " -o output file, if omitted defaults to stdout"
|
| 397 |
print " -m mode, avaiables mode are: %s, defaults to %s" % (join(modes,', '), modes[0])
|
| 398 |
print " -n max_char, max char to display in each ladder, defaults to %d" % max
|
| 399 |
print
|
| 400 |
print " note: python output mode creates a python object usable by pickle module"
|
| 401 |
print
|
| 402 |
|
| 403 |
for opt in optlist:
|
| 404 |
|
| 405 |
# Help
|
| 406 |
if opt[0] == '-h':
|
| 407 |
show_help()
|
| 408 |
exit()
|
| 409 |
|
| 410 |
# Input file
|
| 411 |
if opt[0] == '-i':
|
| 412 |
file = opt[1]
|
| 413 |
continue
|
| 414 |
|
| 415 |
# Output file
|
| 416 |
if opt[0] == '-o':
|
| 417 |
output = opt[1]
|
| 418 |
continue
|
| 419 |
|
| 420 |
# Output mode (html, ansi, ascii, python)
|
| 421 |
if opt[0] == '-m':
|
| 422 |
if opt[1] in modes:
|
| 423 |
mode = opt[1]
|
| 424 |
continue
|
| 425 |
else:
|
| 426 |
print "Invalid mode %s, valid modes are %s" % (opt[1], join(modes, ', '))
|
| 427 |
exit()
|
| 428 |
|
| 429 |
# Max chars in ladder
|
| 430 |
if opt[0] == '-n':
|
| 431 |
try:
|
| 432 |
max = int(opt[1])
|
| 433 |
except:
|
| 434 |
max = 0
|
| 435 |
|
| 436 |
if (max < 2) or max > real_max:
|
| 437 |
print "Invalid value for max char in ladder must be > 1 and < %d" % real_max
|
| 438 |
exit()
|
| 439 |
continue
|
| 440 |
|
| 441 |
if not file:
|
| 442 |
show_help()
|
| 443 |
exit()
|
| 444 |
|
| 445 |
ladder = get_ladder(file)
|
| 446 |
if mode == 'python':
|
| 447 |
if output:
|
| 448 |
pickle_to(ladder, output)
|
| 449 |
else:
|
| 450 |
print "Cannot dump python object to stdout"
|
| 451 |
exit()
|
| 452 |
|
| 453 |
if output:
|
| 454 |
try:
|
| 455 |
output = open(output, "wb")
|
| 456 |
except:
|
| 457 |
print "Cannot open %s for writing" % output
|
| 458 |
exit()
|
| 459 |
else:
|
| 460 |
output = stdout
|
| 461 |
|
| 462 |
generate(ladder, mode, output, max)
|
| 463 |
|
| 464 |
|