# coding: latin-1 import tempfile, sys, os, struct, shutil, datetime, traceback, readline, difflib import dzRead from demo2 import Demo from demparse import * from makec import return_output coopupdaters = []#'Mandel', 'mwh', 'Stubgaard'] class FakePlayer(object): def __init__(self, name): self.name = unicode(name, 'latin-1') def fmt_players(players, span=False): if span: pnames = ['' + p.name.encode('ascii', 'xmlcharrefreplace') + '' for p in players] else: pnames = [p.name.encode('ascii', 'xmlcharrefreplace') for p in players] p0 = ', '.join(pnames[:-1]) p1 = pnames[-1] if p0: return p0 + ' and ' + p1 else: return p1 def fmt_level(level): mapurl = '/quake/mkt.pl?level:' + level.name if level.series == 'id': levelname = level.title else: levelname = level.name return '%(levelname)s'%locals() def demo_to_beat(level, cat): if cat[-1] in '2345678': n = int(cat[-1]) spcat = cat[:-1] cats = [spcat] for i in range(2, n): cats.append(spcat + str(i)) lessplayers = filter(None, map(level.records.get, cats)) if lessplayers: return min([(d.parsedtime, d) for d in lessplayers])[1] return None def group_on(f, l): d = {} for i in l: d.setdefault(f(i), []).append(i) r = d.items() r.sort(key=lambda x:l.index(x[1][0])) return r playnames = { 'Lag?Com': 'Paul Davies', 'pif': 'Pif de Mestre', 'luc': 'Luc de Mestre', '?W?Manslay': 'Dmitry Dementjev', 'mandel': 'Mathias Thore', 'timo': 'Timo Neimenen', 'Snoop': 'David Hocking', 'Kay': 'Kay Berntsen', 'Midas': 'Robert Maglic', 'MMoD': 'Abigail Skidmore', 'morfans': 'Richard Skidmore', 'Morfans': 'Richard Skidmore', 'Vile ????': 'Drew DeVore', 'Stubgaard': 'Thomas Stubgaard', 'windlash': 'Evan Wagner', 'flecklash': 'Evan Wagner', 'Eclipse': 'Cameron Engles', 'Ken': 'Arturo Garcia Lasca', 'lodis': 'Thomas Bergendorff', 'Lodis': 'Thomas Bergendorff', 'lolfu': 'Thomas Bergendorff', 'mwh': 'Michael Hudson', 'MattiasO': 'Mattias Öman', 'Joakim': 'Joakim Sarkkinen', '???': 'Jozsef Szalontai', 'optic': 'Justin Fleck', 'connor': 'Connor Fitzgerald', 'Archi': 'Kimmo Polvivaara', 'sshplur': 'Tom Nguyen', 'nahkahiir': 'Jaakko Alakopsa', 'Orbs': 'Basil de Vries', '[tna] Jonan':'Jonathan Anderson', 'the|navigator':'Jonathan Lenhardt', '?????????????': 'Laurent Mertens', 'Ankh': 'Damian Kulot', 'LLCoolDave': 'David Spickermann', '[]Lardarse':'Gregory R. Payne', 'Evan Lagner':'Evan Wagner', 'Justlin Pfluck':'Evan Wagner', 'Marvin':'Martin Selinus', 'devil_himself': 'Peter Horvath', 'MP':'Mads-Peter Stubgaard', 'anthony':'Joshua Anthony', 'GinKo':'Flavio Quadros', 'sidd':'Aleksander Osipov', 'P-B':'Jonny Andersson', 'Davy':'Damian Kulot', 'gjutarn666':'Carl Tholin', 'Cyborg':'Daniel Hansson', '???in??':'Rui Neto', 'butts':'Thomas Bergendorff', '?Runken?':'Rickard Axelsson', 'ASH': 'Daniel Andersson', 'Optic': 'Justin Fleck', 'Justolin Pfleck':'Justin Fleck', 'Doctor Worm':'Nathan Egan', '?raffz':'Anders Nordensten', 'dex':'Karol Urbanski', 'rutger':'Rutger Baks', 'dingus': 'Jason Hochreiter', '??????': 'Adam Lewandowski', 'ronzo': 'John Van Dusen', 'Ronzo': 'John Van Dusen', 'jamesssssssssss': 'James Gray', 'v1ncent':'Joshua Anthony', 'FatDude': 'Evan Wagner', "Pops": "Andre Castro", } shortnames = { 'pif': 'Pif', 'luc': 'Luc', 'mandel': 'Mathias', 'Midas': 'Maglic', 'MMoD': 'Abigail', 'morfans': 'Richard', 'Morfans': 'Richard', 'Lag?Com': 'Paul', 'Stubgaard': 'Thomas', 'windlash': 'Evan', 'Eclipse': 'Cameron', 'mwh': 'Michael', 'Archi': 'Kimmo', 'optic': 'Justin', 'flecklash': 'Evan', 'Orbs': 'Basil', 'Evan Lagner':'Evan', 'Justlin Pfluck':'Evan', 'Marvin':'MartinS', 'MP':'Madspeter', 'nahkahiir': 'Jaakko', 'the|navigator':'Jonathan', '[]Lardarse':'Gregory', 'sidd':'Alex', 'P-B':'JonnyA', 'ASH': 'DanielA', 'Cyborg':'DanielH', 'Optic': 'Evan', 'Justolin Pfleck':'Justin', '???': 'Jozsef', '?raffz':'Anders', 'rutger':'Rutgerb', 'dingus': 'Jason', '??????': 'Adam', 'Ronzo': 'John', 'ronzo': 'John', 'jamesssssssssss': 'James', 'FatDude': 'Evan', "Pops": "AndreC", } mapmap = { 'gmsp3v2':'gmsp3', 'gor1f':'gor1', 'castle':'hellctle', 'polygon8':'polygon2', 'mountbase2':'mntbase2', 'q1tm3_hrim_v2':'q1tm3_hrim_V2', 'q1tm3_hrim2_v2':'q1tm3_hrim2_V2', 'q1tm3_hrim2_v2':'q1tm3_hrim2_V2', 'q1tm3_hrim_v2':'q1tm3_hrim_V2', 'bc':'bc2', 'q1':'darkness', } def extract(dzFile, dir, filename): """Takes a DzFile, a DzDirectory from the DzFile and extracts the corresponding file to filename.""" # it does this by creating a dzip archive that just contains this # file and pointing dzip at it. header = struct.pack(' /dev/null') os.remove('temp') def scrub(n): def _(c): if ord(c) >= 128: return '?' else: return c return ''.join(map(_, n)) overrides = {} if os.path.exists('overrides.txt'): overrides = eval(open('overrides.txt').read()) class Run(object): def __init__(self, localname): self.localname = localname self.mapname = '' self.bspname = '' self.players = [] self.skill = '' self.time = '' self.secrets = 0 self.nsecrets = 0 self.kills = 0 self.nkills = 0 self.cheated = '' def normalize(self, date): if self.localname in overrides: d = overrides[self.localname] print d for k in d: setattr(self, k, d[k]) self.bspname = mapmap.get(self.bspname, self.bspname) if len(self.skill) < 2: if self.secrets == self.nsecrets and self.kills == self.nkills: self.skill += 'H' else: self.skill += 'R' self.players = filter(lambda x: x != '' and x != 'unconnected', self.players) self.players = map(scrub, self.players) if len(self.players) > 1: self.skill += str(len(self.players)) self.skill = self.cheated + self.skill if self.bspname not in config.levels: return # ROTW stuff might go around here self.level = config.levels[self.bspname] if not self.time: self.time = self.inttime d = self.time.find('.') t = self.time[:d] if ':' in t: m, s = map(int, t.split(':')) s += 60*m else: s = int(t) self.stime = '%d:%02d'%divmod(s, 60) self.filename = self.level.file + '_' + \ self.stime.replace(':', '') + '.dz' self.demurl = '/quake/demos/' + self.skill + '/' + self.filename fps = [FakePlayer(playnames.get(p, p)) for p in self.players] self.demoobj = parseHistory.Demo(level=self.level, time=self.stime, date=date.strftime("%d.%m.%y"), players=fps, cat=self.skill) self.demoobj.filename = parseHistory.computeFilename(self.demoobj) self.demoobj.computeLocation() if self.level.series == 'id': self.levelname = self.mapname else: self.levelname = self.level.name self.playerlist = fmt_players(fps, True) self.beaten = None self.longskill = catmap.get(self.skill[:2], self.skill[:2]) if len(self.players) > 1: self.longskill = '%d-Player '%len(self.players) + self.longskill def affect_config(self): if self.skill in self.level.records: rec = self.level.records[self.skill] diff = rec.parsedtime - self.demoobj.parsedtime if diff > 0: self.beaten = rec self.record = True elif rec.kills != 0 and self.kills > rec.kills: self.beaten = rec self.demoobj.kills = self.kills self.record = True else: self.record = False else: self.record = True if self.record: self.level.records[self.skill] = self.demoobj self.level.demos.setdefault(self.skill, []).append(self.demoobj) def report(self): extra = '' if self.secrets == self.nsecrets and self.kills == self.nkills: b = 'H' else: b = 'R' extra = ' (%s/%ss, %s/%sk)' % \ (self.secrets, self.nsecrets, self.kills, self.nkills) a = self.skill[0:1] while self.players[-1] == '': self.players = self.players[:-1] c = len(self.players) if c == 1: c = '' return '%s [%s] %s%s%s in %s by %s%s' % \ (self.bspname, self.mapname, a, b, c, self.time, fixit(self.players), extra) class BasilError(EOFError): pass def analyse_dem(demo, date, localname): interest = tuple(c.code for c in [Time, UpdateStat, ServerInfo, KilledMonster, Intermission, FoundSecret, Print, UpdateName]) run = Run(localname) run.inttime = '9:99' blocktime = None for block in demo: for m in block.filtered(interest): cls = m.__class__ if cls is Time: blocktime = m.time elif cls is ServerInfo: run.mapname = m.mapname run.bspname = m.models[0][5:-4] run.players = ['']*m.maxclients elif cls is UpdateStat: if m.index == 11: run.nsecrets = m.value elif m.index == 12: run.nkills = m.value elif cls is KilledMonster: run.kills += 1 elif cls is FoundSecret: run.secrets += 1 elif cls is UpdateName: if m.netname and m.netname not in run.players: run.players.append(m.netname) elif cls is Print: if m.text.startswith("Playing on"): skill = block.next().text if skill not in ('Easy', 'Nightmare'): raise BasilError(skill) run.skill = skill[0] if 'QdQ Stats patch for cheaters!' in m.text: run.cheated = 'C' if 'Quake Done Carefully edition' in m.text: run.skill = 'CAREFUL' if 'The recorded time was' in m.text: while 1: m = block.next() if m.__class__ is Stufftext: continue if m.__class__ is not Print or '\n' in m.text: break else: run.time += m.text elif cls is Intermission or cls is Finale: t = blocktime run.inttime = '%d:%02d.5'%(int(t/60), int(t)%60, ) run.normalize(date) return run catmap = { 'ER':'Easy Run', 'EH':'Easy 100%', 'NR':'Nightmare Run', 'NH':'Nightmare 100%' } @return_output def html_for(runs, players=True, level=True): run = runs[0] print '\n
  • ' if coopupdaters: print '

    ' if level: print fmt_level(run.level) if players: print '%(longskill)s in'%run.__dict__ else: print '%s in'%(catmap[run.skill[:2]]) if players: tcomma = '' else: tcomma = ',' for run2 in runs: if run2.record: print '''%(stime)s'''%run2.__dict__ + tcomma else: print '''%(stime)s'''%run2.__dict__ + tcomma if players and run2 is runs[0]: print '''by %(playerlist)s, '''%run2.__dict__ if run2.record: if run2.beaten: diff = run2.beaten.parsedtime - run2.demoobj.parsedtime if diff == 1: ss = '' else: ss = 's' victim = fmt_players(run2.beaten.players) a, b = [sorted([p.name for p in d.players]) for d in [run2.demoobj, run2.beaten]] if a == b: if len(run2.players) == 1: victim = 'himself' else: victim = 'themselves' print 'beating', victim, 'by', diff, 'second' + ss, else: d2b = demo_to_beat(run2.level, run2.skill) if d2b: if len(d2b.players) == 1: l = 'single' else: l = d2b.cat[-1] print 'tablefiller (%s player time %s).'%( l, d2b.time) else: print 'tablefiller', for updater in coopupdaters: print '
    ' print '%s:'%updater else: print 'not a record' if run2 is not runs[-1]: sys.stdout.softspace = 0 print ', then' else: sys.stdout.softspace = 0 print '.' @return_output def curl_for(run): if run.record: print '
    ' print 'curl -u sdaquake:`cat sdapass` -T %(localname)s ' \ 'ftp://speeddemosarchive.com/demos/%(skill)s/' \ '%(filename)s'%run.__dict__ print '' basecats = ['ER', 'EH', 'NR', 'NH'] def cmpskill(s, t): i = len(s) - len(t) if i < 0: return -1 elif i > 0: return 1 if len(s) == 3: i = ord(s[-1]) - ord(t[-1]) if i < 0: return -1 elif i > 0: return 1 s = s[:2] t = t[:2] i = basecats.index(s) - basecats.index(t) if i < 0: return -1 elif i > 0: return 1 else: return 0 def history_for(run): line = '' levelHadCoops = max([0] + map(len, run.level.records)) > 2 run.affect_config() if not run.record: return if run.beaten: run.demoobj.lineno = run.beaten.lineno + 1 else: levels = config.levels.values() levels.sort(key=lambda x:x.seqno) i = levels.index(run.level) - 1 if len(run.players) > 1: if levelHadCoops: n = run.skill[-1] coopskills = [s for s in run.level.records if len(s) > 2] coopskills.sort(cmpskill) j = coopskills.index(run.skill) if j == 0: while max([0] + map(len, levels[i].records)) <= 2: i -= 1 ln = 0 lvl = levels[i] for v in lvl.demos.values(): ln = max(ln, max([r.lineno for r in v])) historyLines[ln] = '\t\t'+historyLines[ln].split(None, 1)[1] levelHadCoops = False run.demoobj.lineno = ln + 1 else: run.demoobj.lineno = run.level.records[ coopskills[j-1]].lineno + 1 else: while max([0] + map(len, levels[i].records)) <= 2: i -= 1 ln = 0 lvl = levels[i] for v in lvl.demos.values(): ln = max(ln, max([r.lineno for r in v])) run.demoobj.lineno = ln + 1 else: while run.skill not in levels[i].records: i -= 1 run.demoobj.lineno = levels[i].records[run.skill].lineno + 1 if run.beaten or \ (len(run.players) > 1 and levelHadCoops): line += '\t\t' else: line += run.bspname if len(run.bspname) < 8: line += '\t\t' else: line += '\t' if len(run.players) > 1: pnames = '/'.join([shortnames.get(p, p) for p in run.players]) if len(pnames) > 22: parts = pnames.split('/') preparts = [] i = 1 while len('/'.join(parts[:-i])) + 1> 24: i += 1 preparts, postparts = parts[:-i], parts[-i:] pnames = '/'.join(preparts) + '/\n' + '\t\t' + ' '*11 + \ '%-22s'%('/'.join(postparts),) else: pnames = recordbot.fmt_players(run.demoobj.players) line += '%s %-22s %s'%(run.demoobj.date, pnames, run.stime) if len(run.players) > 1 and not run.beaten: line += ' ' + run.skill if run.demoobj.kills: if len(run.players) > 1: line += ' ' line += ' [%d]'%run.demoobj.kills assert hasattr(run.demoobj, 'lineno') lines = line.split('\n') for l in lines[::-1]: historyLines.insert(run.demoobj.lineno-1, l+'\n') run.demoobj.lineno += len(lines) - 1 for d in config.all_demos(): if d is not run.demoobj and d.lineno >= run.demoobj.lineno: d.lineno += len(lines) def make_html(runs): html = [] news = ['

    In the traditional bullet style, we have %s demos:'%len(runs), '


    ')
    
        f = open(diffoutput, 'w')
    
        for l in difflib.unified_diff(origHistoryLines, historyLines,
                                      fromfile='history.txt~',
                                      tofile='history.txt',
                                      n=3):
            html.append(l[:-1])
            f.write(l)
    
        f.close()
    
        open(histoutput, 'w').writelines(historyLines)
    
        html.append('

    ') html.extend(curl) return html def main(bigdzname, uppath): directory = tempfile.mkdtemp() os.chdir(directory) try: dzfs = [] bigdzf = dzRead.DzFile(bigdzname) dzcount = 0 print 'extracting big dzip' unknowns = [] for d in bigdzf.dirs: if d.name.endswith('.dz'): dzname = 'dz'+str(dzcount) extract(bigdzf, d, dzname) dzcount += 1 dzf = dzRead.DzFile(dzname) for d2 in dzf.dirs: if d2.name.endswith('.dem') and '_' in d2.name \ and 'egg' not in d2.name and 'wtf' not in d2.name \ and 'lag' not in d2.name \ and 'deathfromabove' not in d2.name and 'bloopers' not in d2.name \ and 'flash-ogre' not in d2.name and not d2.pak: break else: print 'not doing anything with', d.name unknowns.append(d.name) continue print d.name date = datetime.datetime(*dzRead.parseDate(d2.date)) earlyFlag = 1 if overrides.get(d.name, {}).get('early', False): earlyFlag = 0 dzfs.append((earlyFlag, date, dzf, d2, d)) dzfs.sort() runs = [] for dummy, date, dzf, demdir, dzdir in dzfs: extract(dzf, demdir, 'dem') print 'analysing', demdir.name run = analyse_dem(Demo('dem'), date, dzdir.name) runs.append(run) os.unlink('dem') #runs = sorted(runs, key=lambda x:x.demoobj.parsedtime, reverse=True) while 1: try: html = make_html(runs) print 'writing update.html' o = open(uppath, 'w') print >>o, 'Auto-Update' print >>o, '' print >>o, '' print >>o, '' print >>o, '' if unknowns: print >>o, "

    Ignoring %r"%unknowns print >>o, '\n'.join(html) print >>o, '' o.close() except: #raise print 'HTML generation failed' import pdb; pdb.post_mortem(sys.exc_traceback) traceback.print_exc() r = raw_input('Try Again? (y/n): ') if not r.startswith('y'): raise import __main__ __main__.__name__ = '' exec open(script).read() in __main__.__dict__ continue break raw_input('hit return to re-gen') import __main__ __main__.__name__ = '' exec open(script).read() in __main__.__dict__ historyLines = origHistoryLines[:] finally: shutil.rmtree(directory) if __name__ == '__main__': sys.path.append('../recordbot') import parseHistory, recordbot print 'parsing' config = parseHistory.Config() parseHistory.parseConfig(open('../recordbot/config', 'U'), config) parseHistory.parseHistory(open('../recordbot/history.txt', 'U'), config) script = os.path.abspath(sys.argv[0]) diffoutput = os.path.abspath('history.diff') histoutput = os.path.abspath('history_new.txt') historyLines = open('../recordbot/history.txt', 'U').readlines() origHistoryLines = historyLines[:] main(os.path.abspath(sys.argv[1]), os.path.abspath('update.html'))