#!/usr/bin/env python """ wmlunits -- tool to output information on all units in HTML Run without arguments to see usage. """ # Makes things faster on 32-bit systems try: import psyco; psyco.full() except ImportError: pass import sys, os, re, glob, shutil, copy, urllib2 import wesnoth.wmldata as wmldata import wesnoth.wmlparser as wmlparser import wesnoth.wmltools as wmltools sys.path.append(".") import unit_tree.helpers as helpers html_header = ''' '''.strip() html_footer = ''' '''.strip() class GroupByRace: def __init__(self, wesnoth, campaign): self.wesnoth = wesnoth self.campaign = campaign def unitfilter(self, unit): if not self.campaign: return True return unit.campaign == self.campaign def groups(self, unit): return [unit.race] def group_name(self, group): if not group: return "None" return group.get_text_val("plural_name") class GroupByFaction: def __init__(self, wesnoth, era): self.wesnoth = wesnoth self.era = era def unitfilter(self, unit): return self.era in unit.eras def groups(self, unit): return [x for x in unit.factions if x[0] == self.era] def group_name(self, group): era = self.wesnoth.era_lookup[group[0]] faction = era.faction_lookup[group[1]] name = faction.get_text_val("name") name = name[name.rfind("=") + 1:] name = era.get_text_val("name") + " / " + name return name class HTMLOutput: def __init__(self, isocode, output, campaign, wesnoth, verbose = False): self.isocode = isocode self.output = output self.campaign = campaign self.verbose = verbose self.target = "index.html" self.wesnoth = wesnoth self.forest = None def get_translation(self, textdomain, key): return self.wesnoth.parser.translations.get(textdomain, self.isocode, key, key) def analyze_units(self, grouper): """ This takes all units belonging to a campaign, then groups them either by race or faction, and creates an advancements tree out of it. """ # Build an advancement tree forest of all units. forest = self.forest = helpers.UnitForest() units_added = {} for uid, u in self.wesnoth.unit_lookup.items(): if grouper.unitfilter(u): forest.add_node(helpers.UnitNode(u)) units_added[uid] = u # Always add any child units, even if they have been filtered out.. for uid, u in units_added.items(): for auid in u.advance: if not auid in units_added: try: au = self.wesnoth.unit_lookup[auid] except KeyError: # FIXME: write error message continue forest.add_node(helpers.UnitNode(au)) units_added[auid] = au forest.update() # Partition trees by race/faction of first unit. groups = {} breadth = 0 for tree in forest.trees.values(): u = tree.unit ugroups = grouper.groups(u) for group in ugroups: groups[group] = groups.get(group, []) + [tree] breadth += tree.breadth thelist = groups.keys() thelist.sort() rows_count = breadth + len(thelist) # Create empty grid. rows = [] for j in range(rows_count): column = [] for i in range(6): column.append((1, 1, None)) rows.append(column) # Sort advancement trees by name of first unit and place into the grid. def by_name(t1, t2): u1 = t1.unit u2 = t2.unit u1name = u1.get_text_val("name") u2name = u2.get_text_val("name") return cmp(u1name, u2name) def grid_place(nodes, x): nodes.sort(by_name) for node in nodes: level = node.unit.level rows[x][level] = (1, node.breadth, node) for i in range(1, node.breadth): rows[x + i][level] = (0, 0, node) grid_place(node.children, x) x += node.breadth return x x = 0 for group in thelist: node = helpers.GroupNode(group) node.name = grouper.group_name(group) rows[x][0] = (6, 1, node) for i in range(1, 6): rows[x][i] = (0, 0, None) nodes = groups[group] x += 1 x = grid_place(nodes, x) self.unitgrid = rows def write_navbar(self): def write(x): self.output.write(x) def _(x): return self.get_translation("wesnoth", x) languages = find_languages() langlist = languages.keys() langlist.sort() write("""
Wesnoth logo
Wesnoth Units database
""") write("") write("\n") def abbrev(name): abbrev = name[0] for i in range(1, len(name)): if name[i - 1] in [" ", "_"]: abbrev += name[i] return abbrev # Campaigns x = _("TitleScreen button^Campaign") write("\n") write("\n") # Eras write("\n") x = _("Era") write("\n") write("\n") # Races if self.campaign == "mainline": write("\n") x = _("Race") write("\n") write("\n") # Languages write("\n") x = _("Language") write("\n") write("\n") write("
%s
" % x) cids = [[], []] for cid in self.wesnoth.campaign_lookup.keys(): if cid in self.wesnoth.is_mainline_campaign: cids[0].append(cid) else: cids[1].append(cid) for i in range(2): cnames = cids[i] cnames.sort() for cname in cnames: lang = self.isocode if cname == "mainline": campname = self.get_translation("wesnoth", "Multiplayer") campabbrev = campname else: campname = self.wesnoth.campaign_lookup[cname].get_text_val("name") if not campname: campname = cname campabbrev = self.wesnoth.campaign_lookup[cname].get_text_val("abbrev") if not campabbrev: campabbrev = abbrev(campname) write(" %s
\n" % ( campname, lang, cname, campabbrev)) if i == 0 and cids[1]: write("-
\n") write("
%s
" % x) eids = [[], []] for eid in self.wesnoth.era_lookup.keys(): if eid in self.wesnoth.is_mainline_era: eids[0].append(eid) else: eids[1].append(eid) for i in range(2): eranames = [(self.wesnoth.era_lookup[eid].get_text_val("name"), eid) for eid in eids[i]] eranames.sort() for eraname, eid in eranames: write(" %s
" % ( eraname, lang, eid, abbrev(eraname))) if i == 0 and eids[1]: write("-
\n") write("
%s
" % x) write("%s
\n" % ( self.get_translation("wesnoth-editor", "all"))) racenames = {} for u in self.wesnoth.unit_lookup.values(): if u.campaign != self.campaign: continue race = u.race racename = race.get_text_val("plural_name") racenames[racename] = race.get_text_val("id") racenames = racenames.items() racenames.sort() for racename, rid in racenames: write(" %s
" % ( racename, racename)) write("
%s
" % x) for lang in langlist: labb = lang underscore = labb.find("_") if underscore > 0: labb = labb[:underscore] write(" %s
\n" % ( languages[lang], lang, self.target, labb)) write("
\n") def pic(self, u, x): image = self.wesnoth.get_unit_value(x, "image") if not image: if x.name == "female": baseunit = self.wesnoth.get_base_unit(u) if baseunit: female = baseunit.get_first("female") return self.pic(u, female) sys.stderr.write( "Warning: Missing image for unit %s(%s).\n" % ( u.get_text_val("id"), x.name)) return None picname = image_collector.add_image(u.campaign, image) image = os.path.join("../pics", picname) return image def write_units(self): def write(x): self.output.write(x) rows = self.unitgrid write("\n") write("") for i in range(6): write("" % i) write("") for row in range(len(rows)): write("\n") for column in range(6): hspan, vspan, un = rows[row][column] if vspan: attributes = "" if hspan == 1 and vspan == 1: pass elif hspan == 1: attributes += " rowspan=%d" % vspan elif vspan == 1: attributes += " colspan=%d" % hspan if un and isinstance(un, helpers.GroupNode): racename = un.name attributes += " class=\"raceheader\"" write("" % attributes) write("%s" % (racename, racename)) write("\n") elif un: u = un.unit attributes += " class=\"unitcell\"" write("" % attributes) uid = u.get_text_val("id") name = self.wesnoth.get_unit_value(u, "name") cost = self.wesnoth.get_unit_value(u, "cost") hp = self.wesnoth.get_unit_value(u, "hitpoints") mp = self.wesnoth.get_unit_value(u, "movement") xp = self.wesnoth.get_unit_value(u, "experience") level = self.wesnoth.get_unit_value(u, "level") write("
L%s
" % level) link = "../%s/%s.html" % (self.isocode, uid) write("%s
" % (link, name)) write('
') image = self.pic(u, u) write('(image)\n' % ( link, image)) write('
\n') write("
") write("%s%s
" % ( self.get_translation("wesnoth", "Cost: "), cost)) write("%s%s
" % ( self.get_translation("wesnoth", "HP: "), hp)) write("%s%s
" % ( self.get_translation("wesnoth", "MP: "), mp)) write("%s%s
" % ( self.get_translation("wesnoth", "XP: "), xp)) write("
") write("\n") else: write("
") write("\n") write("
\n") def write_units_tree(self, grouper, title): self.output.write(html_header % {"path" : "../"}) self.analyze_units(grouper) self.write_navbar() self.output.write("

%s

" % title) self.write_units() self.output.write(html_footer) def write_unit_report(self, output, unit): def write(x): self.output.write(x) def _(x): return self.get_translation("wesnoth", x) def find_attr(what, key): if unit.movetype: mtx = unit.movetype.get_first(what) mty = None if mtx: mty = mtx.get_text_val(key) x = unit.get_first(what) y = None if x: y = x.get_text_val(key) if y: return True, y if unit.movetype and mty != None: return False, mty return False, "-" self.output = output write(html_header % {"path" : "../"}) self.write_navbar() # Write unit name, picture and description. uid = unit.get_text_val("id") uname = self.wesnoth.get_unit_value(unit, "name") display_name = uname female = unit.get_first("female") if female: fname = female.get_text_val("name") if fname and fname != uname: display_name += "
" + fname write("

%s

\n" % display_name) write('
') if female: mimage = self.pic(unit, unit) fimage = self.pic(unit, female) if not fimage: fimage = mimage write('(image)\n' % mimage) write('(image)\n' % fimage) else: image = self.pic(unit, unit) write('(image)\n' % image) write('
\n') description = unit.get_text_val("description") if not description: description = unit.get_text_val("unit_description") if not description: description = "-" write("

%s

\n" % description) # Base info. hp = self.wesnoth.get_unit_value(unit, "hitpoints") mp = self.wesnoth.get_unit_value(unit, "movement") xp = self.wesnoth.get_unit_value(unit, "experience") level = self.wesnoth.get_unit_value(unit, "level") alignment = self.wesnoth.get_unit_value(unit, "alignment") write("\n") write("\n") write("\n") write("\n") write("\n") write("\n") for val, text in [ ("cost", _("Cost: ")), ("hitpoints", _("HP: ")), ("movement", _("Movement") + ": "), ("experience", _("XP: ")), ("level", _("Level") + ": "), ("alignment", _("Alignment: "))]: write("\n") write("" % text) x = self.wesnoth.get_unit_value(unit, val) if val == "alignment": x = self.get_translation("wesnoth", x) write("" % x) write("\n") write("
%s" % _("Advances from: ")) write("\n") for pid in self.forest.get_parents(uid): punit = self.wesnoth.unit_lookup[pid] if unit.campaign == "mainline" and punit.campaign != "mainline": continue link = "../%s/%s.html" % (self.isocode, pid) name = self.wesnoth.get_unit_value(punit, "name") write("\n%s" % (link, name)) write("
%s" % self.get_translation("wesnoth", "Advances to: ")) write("\n") for cid in self.forest.get_children(uid): link = "../%s/%s.html" % (self.isocode, cid) try: cunit = self.wesnoth.unit_lookup[cid] if unit.campaign == "mainline" and cunit.campaign != "mainline": continue name = self.wesnoth.get_unit_value(cunit, "name") except KeyError: sys.stderr.write("Warning: Unit %s not found.\n" % cid) name = cid if unit.campaign == "mainline": continue write("\n%s" % (link, name)) write("
%s%s
\n") # Write info about attacks. write("\n") for attack in unit.get_all("attack"): write("") aid = attack.get_text_val("name") aname = attack.get_text_val("description") icon = attack.get_text_val("icon") if not icon: icon = "attacks/%s.png" % aid picname = image_collector.add_image(unit.campaign, icon) icon = os.path.join("../pics", picname) write("" % icon) write("" % self.get_translation("wesnoth", t)) n = attack.get_text_val("number") x = attack.get_text_val("damage") x = "%s - %s" % (x, n) write("" % self.get_translation("wesnoth", r)) s = [] specials = attack.get_first("specials") if specials: for special in specials.children(): sname = special.get_text_val("name") if sname: s.append(sname) else: sys.stderr.write( "Warning: Weapon special %s has no name for %s.\n" % ( special.name, uid)) s = "
".join(s) write("" % s) write("") write("
\"(image)\"/%s" % aname) t = attack.get_text_val("type") write("
%s
%s" % x) r = attack.get_text_val("range") write("
%s
%s
\n") # Write info about resistances. resistances = [ "blade", "pierce", "impact", "fire", "cold", "arcane"] write("\n") write("\n") write("\n" % self.get_translation("wesnoth", "Resistances: ")) write("\n") for rid in resistances: special, r = find_attr("resistance", rid) if r == "-": r = 0 try: r = "%d%%" % (100 - int(r)) except ValueError: sys.stderr.write("Warning: Invalid resistance %s for %s.\n" % ( r, uid)) rcell = "td" if special: rcell += ' class="special"' write("\n") write("\n" % ( self.get_translation("wesnoth", rid), r)) write("\n") write("
%s
%s%s
\n") # Write info about movement costs and terrain defense. write("\n") write("\n") write("\n" % ( _("Terrain"), _("Movement Cost"), _("Defense") )) write("\n") terrains = self.wesnoth.terrain_lookup terrainlist = [] for tid, t in terrains.items(): if tid in ["off_map", "fog", "shroud"]: continue if t.get_first("aliasof"): continue name = t.get_text_val("name") terrainlist.append((name, tid)) terrainlist.sort() for tname, tid in terrainlist: not_from_race, c = find_attr("movement_costs", tid) ccell = "td" if c == "99": ccell += ' class="grayed"' dcell = "td" not_from_race, d = find_attr("defense", tid) if d == "-": d = 0 try: d = "%d%%" % (100 - int(d)) except ValueError: sys.stderr.write("Warning: Invalid defense %s for %s.\n" % ( d, uid)) write("\n") write("<%s>%s<%s>%s\n" % ( tname, ccell, c, dcell, d)) write("\n") write("
%s%s%s
%s
\n") write(html_footer) languages_found = {} def find_languages(): """ Returns a dictionary mapping isocodes to languages. """ global languages if languages_found: return languages_found parser = wmlparser.Parser(datadir) WML = wmldata.DataSub("WML") parser.parse_text("{languages}\n") parser.parse_top(WML) for locale in WML.get_all("locale"): isocode = locale.get_text_val("locale") name = locale.get_text_val("name") languages_found[isocode] = name return languages_found class MyFile: """ I don't understand why this is needed.. """ def __init__(self, filename, mode): self.f = open(filename, mode) def write(self, x): x = x.encode("utf8") self.f.write(x) def close(self): self.f.close() def generate_campaign_report(out_path, isocode, campaign, wesnoth): print "Generating report for %s_%s." % (isocode, campaign) path = os.path.join(out_path, isocode ) if not os.path.isdir(path): os.mkdir(path) output = MyFile(os.path.join(path, "%s.html" % campaign), "w") html = HTMLOutput(isocode, output, campaign, wesnoth) html.target = "%s.html" % campaign grouper = GroupByRace(wesnoth, campaign) if campaign == "mainline": title = html.get_translation("wesnoth", "Multiplayer") else: title = wesnoth.campaign_lookup[campaign].get_text_val("name") if not title: title = campaign html.write_units_tree(grouper, title) def generate_era_report(out_path, isocode, eid, wesnoth): print "Generating report for %s_%s." % (isocode, eid) path = os.path.join(out_path, isocode) if not os.path.isdir(path): os.mkdir(path) era = wesnoth.era_lookup[eid] ename = era.get_text_val("name") output = MyFile(os.path.join(path, "%s.html" % eid), "w") html = HTMLOutput(isocode, output, eid, wesnoth) html.target = "%s.html" % eid grouper = GroupByFaction(wesnoth, eid) title = ename html.write_units_tree(grouper, title) def generate_single_unit_reports(out_path, isocode, wesnoth): odir = os.path.join(out_path, isocode) if not os.path.isdir(odir): os.mkdir(odir) html = HTMLOutput(isocode, None, "units", wesnoth) grouper = GroupByRace(wesnoth, None) html.analyze_units(grouper) for uid, unit in wesnoth.unit_lookup.items(): output = MyFile(os.path.join(odir, "%s.html" % uid), "w") html.target = "%s.html" % uid html.write_unit_report(output, unit) def write_index(out_path): output = MyFile(os.path.join(out_path, "index.html"), "w") output.write(""" Redirecting to Wesnoth units database... """) def copy_images(): if not options.nocopy: print "Copying files." image_collector.copy_and_color_images(options.output) shutil.copy2(os.path.join("data", "tools/unit_tree/style.css"), options.output) for grab in [ "http://www.wesnoth.org/mw/skins/glamdrol/headerbg.jpg", "http://www.wesnoth.org/mw/skins/glamdrol/wesnoth-logo.jpg", "http://www.wesnoth.org/mw/skins/glamdrol/navbg.png"]: local = os.path.join(options.output, grab[grab.rfind("/") + 1:]) if not os.path.exists(local): print "Fetching", grab url = urllib2.urlopen(grab) file(local, "w").write(url.read()) def output(isocode): """ We output: * All mainline units sorted by race * Each mainline era's units sorted by faction * Each mainline campaign's units sorted by race * Each addon era's units sorted by faction * Each addon campaign's units sorted by race """ print "WML parser language reset to %s." % isocode stuff = helpers.WesnothList(isocode, datadir, userdir, transdir) # Parse some stuff we may need. print "Parsing terrains ...", n = stuff.add_terrains() print "%d terrains found." % n print "Parsing mainline eras ...", n = stuff.add_mainline_eras() print "%d mainline eras found." % n stuff.is_mainline_era = {} for c in stuff.era_lookup.keys(): stuff.is_mainline_era[c] = True print "Parsing mainline campaigns ...", n = stuff.add_mainline_campaigns() print "%d mainline campaigns found." % n stuff.is_mainline_campaign = {} for c in stuff.campaign_lookup.keys(): stuff.is_mainline_campaign[c] = True # Parse all unit data # This reads in units.cfg, giving us all the mainline units. print "Parsing mainline units ...", sys.stdout.flush() WML = stuff.parser.parse("{core/units.cfg}") n = stuff.add_units(WML, "mainline") print n, "mainline units found." # Now we read each campaign in turn to get its units. cnames = stuff.campaign_lookup.keys() for cname in cnames: print "Parsing %s units ..." % cname, sys.stdout.flush() campaign = stuff.campaign_lookup[cname] define = campaign.get_text_val("define") WML = stuff.parser.parse(""" #define %s\n#enddef {campaigns}""" % define, ignore_macros = lambda x: x.find("/scenarios") == -1) n = stuff.add_units(WML, cname) image_collector.add_binary_pathes_from_WML(cname, WML) print n, "units found." print "Parsing addons ...", n = stuff.add_addons(image_collector) print "%d units found." % n stuff.find_unit_factions() # Report generation if not os.path.isdir(options.output): os.mkdir(options.output) write_index(options.output) campaigns = stuff.campaign_lookup.keys() generate_campaign_report(options.output, isocode, "mainline", stuff) for campaign in campaigns: generate_campaign_report(options.output, isocode, campaign, stuff) for eid in stuff.era_lookup.keys(): generate_era_report(options.output, isocode, eid, stuff) # Write a list with all unit ids, just for fun. uids = stuff.unit_lookup.keys() uids.sort() f = MyFile(os.path.join(options.output, "uids.html"), "w") f.write("") f.write("