From 6fcd7f6a1ccc4f067024a9762014f05d3ba5395c Mon Sep 17 00:00:00 2001 From: Dominik Neise Date: Sat, 29 Jun 2019 20:27:39 +0200 Subject: [PATCH] reformat codebase using python/black (#87) * reformat codebase using python/black * black -l 80 * run black --check * mention coding style * just use black without options * run black only on py3 * use yaml merge key to define a few special steps for py3 * lists cannot be merged ... my YAML foo is weak :-( * maybe conditions do the trick * bad old copy & paste * define a new job, only for style checks * fix typo; add link to black editor integration * blacken setup.py * remove accidentally added test_script --- .circleci/config.yml | 31 +- CONTRIBUTING.md | 15 + docs/source/conf.py | 104 +- examples/sc2autosave.py | 351 +++-- examples/sc2store.py | 118 +- generate_build_data.py | 216 ++- new_units.py | 40 +- sc2reader/__init__.py | 4 +- sc2reader/constants.py | 181 +-- sc2reader/data/__init__.py | 136 +- sc2reader/data/create_lookup.py | 16 +- sc2reader/decoders.py | 62 +- sc2reader/engine/__init__.py | 1 + sc2reader/engine/engine.py | 49 +- sc2reader/engine/events.py | 6 +- sc2reader/engine/plugins/__init__.py | 1 - sc2reader/engine/plugins/apm.py | 13 +- sc2reader/engine/plugins/context.py | 160 +- sc2reader/engine/plugins/creeptracker.py | 313 ++-- sc2reader/engine/plugins/gameheart.py | 29 +- sc2reader/engine/plugins/selection.py | 27 +- sc2reader/engine/plugins/supply.py | 191 ++- sc2reader/engine/utils.py | 2 +- sc2reader/events/base.py | 2 +- sc2reader/events/game.py | 233 ++- sc2reader/events/message.py | 18 +- sc2reader/events/tracker.py | 121 +- sc2reader/exceptions.py | 2 +- sc2reader/factories/plugins/replay.py | 189 ++- sc2reader/factories/plugins/utils.py | 29 +- sc2reader/factories/sc2factory.py | 64 +- sc2reader/log_utils.py | 16 +- sc2reader/objects.py | 151 +- sc2reader/readers.py | 1705 ++++++++++++---------- sc2reader/resources.py | 736 +++++++--- sc2reader/scripts/sc2attributes.py | 59 +- sc2reader/scripts/sc2json.py | 32 +- sc2reader/scripts/sc2parse.py | 114 +- sc2reader/scripts/sc2printer.py | 138 +- sc2reader/scripts/sc2replayer.py | 53 +- sc2reader/scripts/utils.py | 16 +- sc2reader/utils.py | 142 +- setup.py | 65 +- test_replays/test_replays.py | 181 ++- test_s2gs/test_all.py | 14 +- 45 files changed, 3811 insertions(+), 2335 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 07526b6a..1344edfc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,31 +1,44 @@ version: 2.0 -my-steps: &steps +build_and_test: &build_and_test_steps - checkout - run: sudo pip install -r requirements.txt - - run: sudo pip install flake8 pytest - - run: python --version ; pip --version ; pwd ; ls -l - run: pip install --user . - # stop the build if there are Python syntax errors or undefined names - - run: flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - run: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - run: sudo pip install pytest + - run: python --version ; pip --version ; pwd ; ls -l - run: pytest + jobs: + StyleCheck: + docker: + - image: circleci/python:3.7 + steps: + - checkout + - run: sudo pip install flake8 black + - run: python --version ; pip --version ; pwd ; ls -l + # stop the build if there are Python syntax errors or undefined names + - run: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + - run: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - run: black . --check + + Python2: docker: - image: circleci/python:2.7.15 - steps: *steps + steps: *build_and_test_steps Python3: docker: - image: circleci/python:3.7 - steps: *steps + steps: *build_and_test_steps + workflows: version: 2 build: jobs: + - StyleCheck - Python2 - Python3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce923408..53017ede 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,3 +18,18 @@ Please submit patches by pull request where possible. Patches should add a test If you are having trouble running/add/fixing tests for your patch let me know and I'll see if I can help. + +Coding Style +============== + +We'd like our code to follow PEP8 coding style in this project. +We use [python/black](https://github.com/python/black) in order to make our lives easier. +We propose you do the same within this project, otherwise you might be asked to +reformat your pull requests. + +It's really simple just: + + pip install black + black . + +And [there are plugins for many editors](https://black.readthedocs.io/en/stable/editor_integration.html). diff --git a/docs/source/conf.py b/docs/source/conf.py index 91b8a826..15623ce9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,32 +19,32 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.pngmath', 'sphinx.ext.viewcode'] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.pngmath", "sphinx.ext.viewcode"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'sc2reader' -copyright = u'2011-2013' +project = u"sc2reader" +copyright = u"2011-2013" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -58,163 +58,159 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'nature' +html_theme = "nature" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'sc2readerdoc' +htmlhelp_basename = "sc2readerdoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'sc2reader.tex', u'sc2reader Documentation', - u'Graylin Kim', 'manual'), + ("index", "sc2reader.tex", u"sc2reader Documentation", u"Graylin Kim", "manual") ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'sc2reader', u'sc2reader Documentation', - [u'Graylin Kim'], 1) -] +man_pages = [("index", "sc2reader", u"sc2reader Documentation", [u"Graylin Kim"], 1)] diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index eb6b48e4..74bb1567 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -'''sc2autosave is a utility for reorganizing and renaming Starcraft II files. +"""sc2autosave is a utility for reorganizing and renaming Starcraft II files. Overview ============== @@ -158,7 +158,7 @@ POST-Parse filtering vs preparse filtering? POST-Parse, how to do it?!?!?!?! -''' +""" import argparse import cPickle import os @@ -169,91 +169,91 @@ import sc2reader try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 def run(args): - #Reset wipes the destination clean so we can start over. + # Reset wipes the destination clean so we can start over. if args.reset: reset(args) - #Set up validates the destination and source directories. - #It also loads the previous state or creates one as necessary. + # Set up validates the destination and source directories. + # It also loads the previous state or creates one as necessary. state = setup(args) - #We break out of this loop in batch mode and on KeyboardInterrupt + # We break out of this loop in batch mode and on KeyboardInterrupt while True: - #The file scan uses the arguments and the state to filter down to - #only new (since the last sync time) files. + # The file scan uses the arguments and the state to filter down to + # only new (since the last sync time) files. for path in scan(args, state): try: - #Read the file and expose useful aspects for renaming/filtering + # Read the file and expose useful aspects for renaming/filtering replay = sc2reader.load_replay(path, load_level=2) except KeyboardInterrupt: raise except: - #Failure to parse + # Failure to parse file_name = os.path.basename(path) - directory = make_directory(args, ('parse_error',)) + directory = make_directory(args, ("parse_error",)) new_path = os.path.join(directory, file_name) - source_path = path[len(args.source):] + source_path = path[len(args.source) :] args.log.write("Error parsing replay: {0}".format(source_path)) if not args.dryrun: args.action.run(path, new_path) - #Skip to the next replay + # Skip to the next replay continue aspects = generate_aspects(args, replay) - #Use the filter args to select files based on replay attributes + # Use the filter args to select files based on replay attributes if filter_out_replay(args, replay): continue - #Apply the aspects to the rename formatting. + # Apply the aspects to the rename formatting. #'/' is a special character for creation of subdirectories. - #TODO: Handle duplicate replay names, its possible.. - path_parts = args.rename.format(**aspects).split('/') - filename = path_parts.pop()+'.SC2Replay' + # TODO: Handle duplicate replay names, its possible.. + path_parts = args.rename.format(**aspects).split("/") + filename = path_parts.pop() + ".SC2Replay" - #Construct the directory and file paths; create needed directories + # Construct the directory and file paths; create needed directories directory = make_directory(args, path_parts) new_path = os.path.join(directory, filename) - #Find the source relative to the source directory for reporting - dest_path = new_path[len(args.dest):] - source_path = path[len(args.source):] + # Find the source relative to the source directory for reporting + dest_path = new_path[len(args.dest) :] + source_path = path[len(args.source) :] - #Log the action and run it if we are live + # Log the action and run it if we are live msg = "{0}:\n\tSource: {1}\n\tDest: {2}\n" args.log.write(msg.format(args.action.type, source_path, dest_path)) if not args.dryrun: args.action.run(path, new_path) - #After every batch completes, save the state and flush the log - #TODO: modify the state to include a list of remaining files + # After every batch completes, save the state and flush the log + # TODO: modify the state to include a list of remaining files args.log.flush() save_state(state, args) - #We only run once in batch mode! - if args.mode == 'BATCH': + # We only run once in batch mode! + if args.mode == "BATCH": break - #Since new replays come in fairly infrequently, reduce system load - #by sleeping for an acceptable response time before the next scan. + # Since new replays come in fairly infrequently, reduce system load + # by sleeping for an acceptable response time before the next scan. time.sleep(args.period) - args.log.write('Batch Completed') + args.log.write("Batch Completed") def filter_out_replay(args, replay): player_names = set([player.name for player in replay.players]) filter_out_player = not set(args.filter_player) & player_names - if args.filter_rule == 'ALLOW': + if args.filter_rule == "ALLOW": return filter_out_player else: return not filter_out_player @@ -268,8 +268,8 @@ def player_compare(player1, player2): # Normalize the player names and generate our key metrics player1_name = player1.name.lower() player2_name = player2.name.lower() - player1_favored = (player1_name in favored_set) - player2_favored = (player2_name in favored_set) + player1_favored = player1_name in favored_set + player2_favored = player2_name in favored_set # The favored player always comes first in the ordering if player1_favored and not player2_favored: @@ -286,7 +286,7 @@ def player_compare(player1, player2): # If neither is favored, we'll order by number for now # TODO: Allow command line specification of other orderings (maybe?) else: - return player1.pid-player2.pid + return player1.pid - player2.pid def team_compare(team1, team2): # Normalize the team name lists and generate our key metrics @@ -310,7 +310,7 @@ def team_compare(team1, team2): # If neither is favored, we'll order by number for now # TODO: Allow command line specification of other orderings (maybe?) else: - return team1.number-team2.number + return team1.number - team2.number return team_compare, player_compare @@ -321,8 +321,8 @@ def generate_aspects(args, replay): for team in teams: team.players = sorted(team.players, args.player_compare) composition = sorted(p.play_race[0].upper() for p in team.players) - matchups.append(''.join(composition)) - string = ', '.join(p.format(args.player_format) for p in team.players) + matchups.append("".join(composition)) + string = ", ".join(p.format(args.player_format) for p in team.players) team_strings.append(string) return sc2reader.utils.AttributeDict( @@ -331,8 +331,8 @@ def generate_aspects(args, replay): map=replay.map, type=replay.type, date=replay.date.strftime(args.date_format), - matchup='v'.join(matchups), - teams=' vs '.join(team_strings) + matchup="v".join(matchups), + teams=" vs ".join(team_strings), ) @@ -341,11 +341,11 @@ def make_directory(args, path_parts): for part in path_parts: directory = os.path.join(directory, part) if not os.path.exists(directory): - args.log.write('Creating subfolder: {0}\n'.format(directory)) + args.log.write("Creating subfolder: {0}\n".format(directory)) if not args.dryrun: os.mkdir(directory) elif not os.path.isdir(directory): - exit('Cannot create subfolder. Path is occupied: {0}', directory) + exit("Cannot create subfolder. Path is occupied: {0}", directory) return directory @@ -358,12 +358,13 @@ def scan(args, state): allow=False, exclude=args.exclude_dirs, depth=args.depth, - followlinks=args.follow_links) + followlinks=args.follow_links, + ) return filter(lambda f: os.path.getctime(f) > state.last_sync, files) def exit(msg, *args, **kwargs): - sys.exit(msg.format(*args, **kwargs)+"\n\nScript Aborted.") + sys.exit(msg.format(*args, **kwargs) + "\n\nScript Aborted.") def reset(args): @@ -372,10 +373,14 @@ def reset(args): elif not os.path.isdir(args.dest): exit("Cannot reset, destination must be directory: {0}", args.dest) - print('About to reset directory: {0}\nAll files and subdirectories will be removed.'.format(args.dest)) - choice = raw_input('Proceed anyway? (y/n) ') - if choice.lower() == 'y': - args.log.write('Removing old directory: {0}\n'.format(args.dest)) + print( + "About to reset directory: {0}\nAll files and subdirectories will be removed.".format( + args.dest + ) + ) + choice = raw_input("Proceed anyway? (y/n) ") + if choice.lower() == "y": + args.log.write("Removing old directory: {0}\n".format(args.dest)) if not args.dryrun: print(args.dest) shutil.rmtree(args.dest) @@ -385,25 +390,27 @@ def reset(args): def setup(args): args.team_compare, args.player_compare = create_compare_funcs(args) - args.action = sc2reader.utils.AttributeDict(type=args.action, run=shutil.copy if args.action == 'COPY' else shutil.move) + args.action = sc2reader.utils.AttributeDict( + type=args.action, run=shutil.copy if args.action == "COPY" else shutil.move + ) if not os.path.exists(args.source): - msg = 'Source does not exist: {0}.\n\nScript Aborted.' + msg = "Source does not exist: {0}.\n\nScript Aborted." sys.exit(msg.format(args.source)) elif not os.path.isdir(args.source): - msg = 'Source is not a directory: {0}.\n\nScript Aborted.' + msg = "Source is not a directory: {0}.\n\nScript Aborted." sys.exit(msg.format(args.source)) if not os.path.exists(args.dest): if not args.dryrun: os.mkdir(args.dest) else: - args.log.write('Creating destination: {0}\n'.format(args.dest)) + args.log.write("Creating destination: {0}\n".format(args.dest)) elif not os.path.isdir(args.dest): - sys.exit('Destination must be a directory.\n\nScript Aborted') + sys.exit("Destination must be a directory.\n\nScript Aborted") - data_file = os.path.join(args.dest, 'sc2autosave.dat') + data_file = os.path.join(args.dest, "sc2autosave.dat") - args.log.write('Loading state from file: {0}\n'.format(data_file)) + args.log.write("Loading state from file: {0}\n".format(data_file)) if os.path.isfile(data_file) and not args.reset: with open(data_file) as file: return cPickle.load(file) @@ -413,67 +420,115 @@ def setup(args): def save_state(state, args): state.last_sync = time.time() - data_file = os.path.join(args.dest, 'sc2autosave.dat') + data_file = os.path.join(args.dest, "sc2autosave.dat") if not args.dryrun: - with open(data_file, 'w') as file: + with open(data_file, "w") as file: cPickle.dump(state, file) else: - args.log.write('Writing state to file: {0}\n'.format(data_file)) + args.log.write("Writing state to file: {0}\n".format(data_file)) def main(): parser = argparse.ArgumentParser( - description='Automatically copy new replays to directory', - fromfile_prefix_chars='@', + description="Automatically copy new replays to directory", + fromfile_prefix_chars="@", formatter_class=sc2reader.scripts.utils.Formatter.new(max_help_position=35), - epilog="And that's all folks") - - required = parser.add_argument_group('Required Arguments') - required.add_argument('source', type=str, - help='The source directory to poll') - required.add_argument('dest', type=str, - help='The destination directory to copy to') - - general = parser.add_argument_group('General Options') - general.add_argument('--mode', dest='mode', - type=str, choices=['BATCH', 'CYCLE'], default='BATCH', - help='The operating mode for the organizer') - - general.add_argument('--action', dest='action', - choices=['COPY', 'MOVE'], default="COPY", type=str, - help='Have the organizer move your files instead of copying') - general.add_argument('--period', - dest='period', type=int, default=0, - help='The period of time to wait between scans.') - general.add_argument('--log', dest='log', metavar='LOGFILE', - type=argparse.FileType('w'), default=sys.stdout, - help='Destination file for log information') - general.add_argument('--dryrun', - dest='dryrun', action="store_true", - help="Don't do anything. Only simulate the output") - general.add_argument('--reset', - dest='reset', action='store_true', default=False, - help='Wipe the destination directory clean and start over.') - - fileargs = parser.add_argument_group('File Options') - fileargs.add_argument('--depth', - dest='depth', type=int, default=-1, - help='Maximum recussion depth. -1 (default) is unlimited.') - fileargs.add_argument('--exclude-dirs', dest='exclude_dirs', - type=str, metavar='NAME', nargs='+', default=[], - help='A list of directory names to exclude during recursion') - fileargs.add_argument('--exclude-files', dest='exclude_files', - type=str, metavar='REGEX', default="", - help='An expression to match excluded files') - fileargs.add_argument('--follow-links', - dest='follow_links', action="store_true", default=False, - help="Enable following of symbolic links while scanning") - - renaming = parser.add_argument_group('Renaming Options') - renaming.add_argument('--rename', - dest='rename', type=str, metavar='FORMAT', nargs='?', + epilog="And that's all folks", + ) + + required = parser.add_argument_group("Required Arguments") + required.add_argument("source", type=str, help="The source directory to poll") + required.add_argument("dest", type=str, help="The destination directory to copy to") + + general = parser.add_argument_group("General Options") + general.add_argument( + "--mode", + dest="mode", + type=str, + choices=["BATCH", "CYCLE"], + default="BATCH", + help="The operating mode for the organizer", + ) + + general.add_argument( + "--action", + dest="action", + choices=["COPY", "MOVE"], + default="COPY", + type=str, + help="Have the organizer move your files instead of copying", + ) + general.add_argument( + "--period", + dest="period", + type=int, + default=0, + help="The period of time to wait between scans.", + ) + general.add_argument( + "--log", + dest="log", + metavar="LOGFILE", + type=argparse.FileType("w"), + default=sys.stdout, + help="Destination file for log information", + ) + general.add_argument( + "--dryrun", + dest="dryrun", + action="store_true", + help="Don't do anything. Only simulate the output", + ) + general.add_argument( + "--reset", + dest="reset", + action="store_true", + default=False, + help="Wipe the destination directory clean and start over.", + ) + + fileargs = parser.add_argument_group("File Options") + fileargs.add_argument( + "--depth", + dest="depth", + type=int, + default=-1, + help="Maximum recussion depth. -1 (default) is unlimited.", + ) + fileargs.add_argument( + "--exclude-dirs", + dest="exclude_dirs", + type=str, + metavar="NAME", + nargs="+", + default=[], + help="A list of directory names to exclude during recursion", + ) + fileargs.add_argument( + "--exclude-files", + dest="exclude_files", + type=str, + metavar="REGEX", + default="", + help="An expression to match excluded files", + ) + fileargs.add_argument( + "--follow-links", + dest="follow_links", + action="store_true", + default=False, + help="Enable following of symbolic links while scanning", + ) + + renaming = parser.add_argument_group("Renaming Options") + renaming.add_argument( + "--rename", + dest="rename", + type=str, + metavar="FORMAT", + nargs="?", default="{length} {type} on {map}", - help='''\ + help="""\ The renaming format string. can have the following values: * {length} - The length of the replay ([H:]MM:SS) @@ -482,41 +537,73 @@ def main(): * {match} - Race matchup in team order, alphabetically by race. * {date} - The date the replay was played on * {teams} - The player line up - ''') - - renaming.add_argument('--length-format', - dest='length_format', type=str, metavar='FORMAT', default='%M.%S', - help='The length format string. See the python time module for details') - renaming.add_argument('--player-format', - dest='player_format', type=str, metavar='FORMAT', default='{name} ({play_race})', - help='The player format string used to render the :teams content item.') - renaming.add_argument('--date-format', - dest='date_format', type=str, metavar='FORMAT', default='%m-%d-%Y', - help='The date format string used to render the :date content item.') - ''' + """, + ) + + renaming.add_argument( + "--length-format", + dest="length_format", + type=str, + metavar="FORMAT", + default="%M.%S", + help="The length format string. See the python time module for details", + ) + renaming.add_argument( + "--player-format", + dest="player_format", + type=str, + metavar="FORMAT", + default="{name} ({play_race})", + help="The player format string used to render the :teams content item.", + ) + renaming.add_argument( + "--date-format", + dest="date_format", + type=str, + metavar="FORMAT", + default="%m-%d-%Y", + help="The date format string used to render the :date content item.", + ) + """ renaming.add_argument('--team-order-by', dest='team_order', type=str, metavar='FIELD', default='NUMBER', help='The field by which teams are ordered.') renaming.add_argument('--player-order-by', dest='player_order', type=str, metavar='FIELD', default='NAME', help='The field by which players are ordered on teams.') - ''' - renaming.add_argument('--favored', dest='favored', - type=str, default=[], metavar='NAME', nargs='+', - help='A list of the players to favor in ordering teams and players') - - filterargs = parser.add_argument_group('Filtering Options') - filterargs.add_argument('--filter-rule', dest='filter_rule', - choices=["ALLOW","DENY"], - help="The filters can either be used as a white list or a black list") - filterargs.add_argument('--filter-player', metavar='NAME', - dest='filter_player', nargs='+', type=str, default=[], - help="A list of players to filter on") + """ + renaming.add_argument( + "--favored", + dest="favored", + type=str, + default=[], + metavar="NAME", + nargs="+", + help="A list of the players to favor in ordering teams and players", + ) + + filterargs = parser.add_argument_group("Filtering Options") + filterargs.add_argument( + "--filter-rule", + dest="filter_rule", + choices=["ALLOW", "DENY"], + help="The filters can either be used as a white list or a black list", + ) + filterargs.add_argument( + "--filter-player", + metavar="NAME", + dest="filter_player", + nargs="+", + type=str, + default=[], + help="A list of players to filter on", + ) try: run(parser.parse_args()) except KeyboardInterrupt: print("\n\nScript Interupted. Process Aborting") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/examples/sc2store.py b/examples/sc2store.py index 55ff22b0..0a072f43 100755 --- a/examples/sc2store.py +++ b/examples/sc2store.py @@ -11,6 +11,7 @@ import sc2reader from pprint import PrettyPrinter + pprint = PrettyPrinter(indent=2).pprint from sqlalchemy import create_engine @@ -22,50 +23,54 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy + Base = declarative_base() -party_member = Table('party_member', Base.metadata, - Column('person_id', Integer, ForeignKey('person.id')), - Column('party_id', Integer, ForeignKey('party.id')), +party_member = Table( + "party_member", + Base.metadata, + Column("person_id", Integer, ForeignKey("person.id")), + Column("party_id", Integer, ForeignKey("party.id")), ) + class Person(Base): - __tablename__ = 'person' - id = Column(Integer, Sequence('person_id_seq'), primary_key=True) + __tablename__ = "person" + id = Column(Integer, Sequence("person_id_seq"), primary_key=True) name = Column(String(50)) url = Column(String(50)) - parties = relationship('Party', secondary=party_member) - players = relationship('Player') + parties = relationship("Party", secondary=party_member) + players = relationship("Player") class Party(Base): - __tablename__ = 'party' - id = Column(Integer, Sequence('party_id_seq'), primary_key=True) + __tablename__ = "party" + id = Column(Integer, Sequence("party_id_seq"), primary_key=True) player_names = Column(String(255)) - members = relationship('Person', secondary=party_member) - teams = relationship('Team') + members = relationship("Person", secondary=party_member) + teams = relationship("Team") def __init__(self, *players): - self.player_names = '' + self.player_names = "" self.members = list() self.add_players(*players) def add_players(self, *players): for player in players: - self.player_names += '['+player.name+']' + self.player_names += "[" + player.name + "]" self.members.append(player.person) @classmethod def make_player_names(self, players): - return ''.join(sorted('['+player.name+']' for player in players)) + return "".join(sorted("[" + player.name + "]" for player in players)) class Game(Base): - __tablename__ = 'game' - id = Column(Integer, Sequence('game_id_seq'), primary_key=True) + __tablename__ = "game" + id = Column(Integer, Sequence("game_id_seq"), primary_key=True) map = Column(String(255)) file_name = Column(String(255)) @@ -77,8 +82,8 @@ class Game(Base): build = Column(String(25)) release_string = Column(String(50)) - teams = relationship('Team') - players = relationship('Player') + teams = relationship("Team") + players = relationship("Player") def __init__(self, replay, db): self.map = replay.map @@ -90,54 +95,58 @@ def __init__(self, replay, db): self.winner_known = replay.winner_known self.build = replay.build self.release_string = replay.release_string - self.teams = [Team(team,db) for team in replay.teams] - self.matchup = 'v'.join(sorted(team.lineup for team in self.teams)) + self.teams = [Team(team, db) for team in replay.teams] + self.matchup = "v".join(sorted(team.lineup for team in self.teams)) self.players = sum((team.players for team in self.teams), []) class Team(Base): - __tablename__ = 'team' - id = Column(Integer, Sequence('team_id_seq'), primary_key=True) - game_id = Column(Integer, ForeignKey('game.id')) - party_id = Column(Integer, ForeignKey('party.id')) + __tablename__ = "team" + id = Column(Integer, Sequence("team_id_seq"), primary_key=True) + game_id = Column(Integer, ForeignKey("game.id")) + party_id = Column(Integer, ForeignKey("party.id")) result = Column(String(50)) number = Column(Integer) lineup = Column(String(10)) - players = relationship('Player') - party = relationship('Party') - game = relationship('Game') + players = relationship("Player") + party = relationship("Party") + game = relationship("Game") def __init__(self, team, db): self.number = team.number self.result = team.result - self.players = [Player(player,db) for player in team.players] - self.lineup = ''.join(sorted(player.play_race[0].upper() for player in self.players)) + self.players = [Player(player, db) for player in team.players] + self.lineup = "".join( + sorted(player.play_race[0].upper() for player in self.players) + ) try: player_names = Party.make_player_names(self.players) - self.party = db.query(Party).filter(Party.player_names == player_names).one() + self.party = ( + db.query(Party).filter(Party.player_names == player_names).one() + ) except NoResultFound as e: self.party = Party(*self.players) class Player(Base): - __tablename__ = 'player' - id = Column(Integer, Sequence('player_id_seq'), primary_key=True) - game_id = Column(Integer, ForeignKey('game.id')) - team_id = Column(Integer, ForeignKey('team.id')) - person_id = Column(Integer, ForeignKey('person.id')) + __tablename__ = "player" + id = Column(Integer, Sequence("player_id_seq"), primary_key=True) + game_id = Column(Integer, ForeignKey("game.id")) + team_id = Column(Integer, ForeignKey("team.id")) + person_id = Column(Integer, ForeignKey("person.id")) play_race = Column(String(20)) pick_race = Column(String(20)) color_str = Column(String(20)) color_hex = Column(String(20)) - name = association_proxy('person','name') - person = relationship('Person') - team = relationship('Team') - game = relationship('Game') + name = association_proxy("person", "name") + person = relationship("Person") + team = relationship("Team") + game = relationship("Game") def __init__(self, player, db): try: @@ -154,18 +163,33 @@ def __init__(self, player, db): class Message(Base): - __tablename__ = 'message' - id = Column(Integer, Sequence('message_id_seq'), primary_key=True) - player_id = Column(Integer, ForeignKey('player.id')) + __tablename__ = "message" + id = Column(Integer, Sequence("message_id_seq"), primary_key=True) + player_id = Column(Integer, ForeignKey("player.id")) def parse_args(): import argparse - parser = argparse.ArgumentParser(description='Stores replay meta data into an SQL database') - parser.add_argument('--storage', default='sqlite:///:memory:', type=str, help='Path to the sql storage file of choice') - parser.add_argument('paths', metavar='PATH', type=str, nargs='+', help='Path to a replay file or a folder of replays') + + parser = argparse.ArgumentParser( + description="Stores replay meta data into an SQL database" + ) + parser.add_argument( + "--storage", + default="sqlite:///:memory:", + type=str, + help="Path to the sql storage file of choice", + ) + parser.add_argument( + "paths", + metavar="PATH", + type=str, + nargs="+", + help="Path to a replay file or a folder of replays", + ) return parser.parse_args() + def main(): args = parse_args() db = load_session(args) @@ -179,7 +203,7 @@ def main(): print(list(db.query(distinct(Person.name)).all())) - #for row in db.query(distinct(Person.name)).all(): + # for row in db.query(distinct(Person.name)).all(): # print(row) @@ -190,5 +214,5 @@ def load_session(args): return Session() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/generate_build_data.py b/generate_build_data.py index b56a72eb..32bdbbd2 100644 --- a/generate_build_data.py +++ b/generate_build_data.py @@ -35,7 +35,9 @@ def generate_build_data(balance_data_path): while len(ability_lookup[ability_name]) <= command_index: ability_lookup[ability_name].append("") - command_name = command_id if command_id != "Execute" else ability_name + command_name = ( + command_id if command_id != "Execute" else ability_name + ) ability_lookup[ability_name][command_index] = command_name unit_id = root.get("id") @@ -76,7 +78,9 @@ def generate_build_data(balance_data_path): ability_lookup[build_ability_name].append("") build_command_name = "Build{}".format(built_unit_id) - ability_lookup[build_ability_name][command_index] = build_command_name + ability_lookup[build_ability_name][ + command_index + ] = build_command_name train_unit_elements = root.findall("./trains/unit") if train_unit_elements: @@ -90,43 +94,53 @@ def generate_build_data(balance_data_path): ability_lookup[train_ability_name] = [] for element in train_unit_elements: - element_ability_index = element.get("ability") - trained_unit_name = element.get("id") - - if trained_unit_name: - # Handle cases where a unit can train other units using multiple ability indices. - # The Nexus is currently the only known example. - if element_ability_index != train_ability_index: - train_ability_index = element_ability_index - - train_ability_name = "{}Train{}".format(unit_id, trained_unit_name) - abilities[train_ability_index] = train_ability_name - - if train_ability_name not in ability_lookup: - ability_lookup[train_ability_name] = [] - - command_index_str = element.get("index") - - if command_index_str: - command_index = int(command_index_str) - - # Pad potential gaps in command indices with empty strings - while len(ability_lookup[train_ability_name]) <= command_index: - ability_lookup[train_ability_name].append("") - - ability_lookup[train_ability_name][command_index] = train_ability_name - else: - command_index_str = element.get("index") - - if command_index_str: - command_index = int(command_index_str) - - # Pad potential gaps in command indices with empty strings - while len(ability_lookup[train_ability_name]) <= command_index: - ability_lookup[train_ability_name].append("") - - train_command_name = "Train{}".format(trained_unit_name) - ability_lookup[train_ability_name][command_index] = train_command_name + element_ability_index = element.get("ability") + trained_unit_name = element.get("id") + + if trained_unit_name: + # Handle cases where a unit can train other units using multiple ability indices. + # The Nexus is currently the only known example. + if element_ability_index != train_ability_index: + train_ability_index = element_ability_index + + train_ability_name = "{}Train{}".format( + unit_id, trained_unit_name + ) + abilities[train_ability_index] = train_ability_name + + if train_ability_name not in ability_lookup: + ability_lookup[train_ability_name] = [] + + command_index_str = element.get("index") + + if command_index_str: + command_index = int(command_index_str) + + # Pad potential gaps in command indices with empty strings + while ( + len(ability_lookup[train_ability_name]) <= command_index + ): + ability_lookup[train_ability_name].append("") + + ability_lookup[train_ability_name][ + command_index + ] = train_ability_name + else: + command_index_str = element.get("index") + + if command_index_str: + command_index = int(command_index_str) + + # Pad potential gaps in command indices with empty strings + while ( + len(ability_lookup[train_ability_name]) <= command_index + ): + ability_lookup[train_ability_name].append("") + + train_command_name = "Train{}".format(trained_unit_name) + ability_lookup[train_ability_name][ + command_index + ] = train_command_name research_upgrade_elements = root.findall("./researches/upgrade") if research_upgrade_elements: @@ -150,17 +164,25 @@ def generate_build_data(balance_data_path): ability_lookup[research_ability_name].append("") research_command_name = "Research{}".format(researched_upgrade_id) - ability_lookup[research_ability_name][command_index] = research_command_name + ability_lookup[research_ability_name][ + command_index + ] = research_command_name - sorted_units = collections.OrderedDict(sorted(units.items(), key=lambda x: int(x[0]))) - sorted_abilities = collections.OrderedDict(sorted(abilities.items(), key=lambda x: int(x[0]))) + sorted_units = collections.OrderedDict( + sorted(units.items(), key=lambda x: int(x[0])) + ) + sorted_abilities = collections.OrderedDict( + sorted(abilities.items(), key=lambda x: int(x[0])) + ) unit_lookup = dict((unit_name, unit_name) for _, unit_name in sorted_units.items()) return sorted_units, sorted_abilities, unit_lookup, ability_lookup -def combine_lookups(old_unit_lookup, old_ability_lookup, new_unit_lookup, new_ability_lookup): +def combine_lookups( + old_unit_lookup, old_ability_lookup, new_unit_lookup, new_ability_lookup +): unit_lookup = collections.OrderedDict(old_unit_lookup) ability_lookup = collections.OrderedDict(old_ability_lookup) @@ -190,21 +212,42 @@ def combine_lookups(old_unit_lookup, old_ability_lookup, new_unit_lookup, new_ab def main(): - parser = argparse.ArgumentParser(description='Generate and install new [BUILD_VERSION]_abilities.csv, ' - '[BUILD_VERSION]_units.csv, and update ability_lookup.csv and ' - 'unit_lookup.csv files with any new units and ability commands.') - parser.add_argument('expansion', metavar='EXPANSION', type=str, choices=['WoL', 'HotS', 'LotV'], - help='the expansion level of the balance data export, one of \'WoL\', \'HotS\', or \'LotV\'') - parser.add_argument('build_version', metavar='BUILD_VERSION', type=int, - help='the build version of the balance data export') - parser.add_argument('balance_data_path', metavar='BALANCE_DATA_PATH', type=str, - help='the path to the balance data export') - parser.add_argument('project_path', metavar='SC2READER_PROJECT_PATH', type=str, - help='the path to the root of the sc2reader project directory') + parser = argparse.ArgumentParser( + description="Generate and install new [BUILD_VERSION]_abilities.csv, " + "[BUILD_VERSION]_units.csv, and update ability_lookup.csv and " + "unit_lookup.csv files with any new units and ability commands." + ) + parser.add_argument( + "expansion", + metavar="EXPANSION", + type=str, + choices=["WoL", "HotS", "LotV"], + help="the expansion level of the balance data export, one of 'WoL', 'HotS', or 'LotV'", + ) + parser.add_argument( + "build_version", + metavar="BUILD_VERSION", + type=int, + help="the build version of the balance data export", + ) + parser.add_argument( + "balance_data_path", + metavar="BALANCE_DATA_PATH", + type=str, + help="the path to the balance data export", + ) + parser.add_argument( + "project_path", + metavar="SC2READER_PROJECT_PATH", + type=str, + help="the path to the root of the sc2reader project directory", + ) args = parser.parse_args() - units, abilities, new_unit_lookup, new_ability_lookup = generate_build_data(args.balance_data_path) + units, abilities, new_unit_lookup, new_ability_lookup = generate_build_data( + args.balance_data_path + ) if not units or not abilities: parser.print_help() @@ -212,46 +255,67 @@ def main(): raise ValueError("No balance data found at provided balance data path.") - unit_lookup_path = os.path.join(args.project_path, 'sc2reader', 'data', 'unit_lookup.csv') - with open(unit_lookup_path, 'r') as file: - csv_reader = csv.reader(file, delimiter=',', lineterminator=os.linesep) - old_unit_lookup = collections.OrderedDict([(row[0], row[1]) for row in csv_reader if len(row) > 1]) - - ability_lookup_path = os.path.join(args.project_path, 'sc2reader', 'data', 'ability_lookup.csv') - with open(ability_lookup_path, 'r') as file: - csv_reader = csv.reader(file, delimiter=',', lineterminator=os.linesep) - old_ability_lookup = collections.OrderedDict([(row[0], row[1:]) for row in csv_reader if len(row) > 0]) + unit_lookup_path = os.path.join( + args.project_path, "sc2reader", "data", "unit_lookup.csv" + ) + with open(unit_lookup_path, "r") as file: + csv_reader = csv.reader(file, delimiter=",", lineterminator=os.linesep) + old_unit_lookup = collections.OrderedDict( + [(row[0], row[1]) for row in csv_reader if len(row) > 1] + ) + + ability_lookup_path = os.path.join( + args.project_path, "sc2reader", "data", "ability_lookup.csv" + ) + with open(ability_lookup_path, "r") as file: + csv_reader = csv.reader(file, delimiter=",", lineterminator=os.linesep) + old_ability_lookup = collections.OrderedDict( + [(row[0], row[1:]) for row in csv_reader if len(row) > 0] + ) if not old_unit_lookup or not old_ability_lookup: parser.print_help() print("\n") - raise ValueError("Could not find existing unit or ability lookups. Is the sc2reader project path correct?") + raise ValueError( + "Could not find existing unit or ability lookups. Is the sc2reader project path correct?" + ) unit_lookup, ability_lookup = combine_lookups( - old_unit_lookup, old_ability_lookup, new_unit_lookup, new_ability_lookup) + old_unit_lookup, old_ability_lookup, new_unit_lookup, new_ability_lookup + ) units_file_path = os.path.join( - args.project_path, 'sc2reader', 'data', args.expansion, '{}_units.csv'.format(args.build_version)) - with open(units_file_path, 'w') as file: - csv_writer = csv.writer(file, delimiter=',', lineterminator=os.linesep) + args.project_path, + "sc2reader", + "data", + args.expansion, + "{}_units.csv".format(args.build_version), + ) + with open(units_file_path, "w") as file: + csv_writer = csv.writer(file, delimiter=",", lineterminator=os.linesep) for unit_index, unit_name in units.items(): csv_writer.writerow([unit_index, unit_name]) abilities_file_path = os.path.join( - args.project_path, 'sc2reader', 'data', args.expansion, '{}_abilities.csv'.format(args.build_version)) - with open(abilities_file_path, 'w') as file: - csv_writer = csv.writer(file, delimiter=',', lineterminator=os.linesep) + args.project_path, + "sc2reader", + "data", + args.expansion, + "{}_abilities.csv".format(args.build_version), + ) + with open(abilities_file_path, "w") as file: + csv_writer = csv.writer(file, delimiter=",", lineterminator=os.linesep) for ability_index, ability_name in abilities.items(): csv_writer.writerow([ability_index, ability_name]) - with open(unit_lookup_path, 'w') as file: - csv_writer = csv.writer(file, delimiter=',', lineterminator=os.linesep) + with open(unit_lookup_path, "w") as file: + csv_writer = csv.writer(file, delimiter=",", lineterminator=os.linesep) for entry in unit_lookup.items(): csv_writer.writerow(list(entry)) - with open(ability_lookup_path, 'w') as file: - csv_writer = csv.writer(file, delimiter=',', lineterminator=os.linesep) + with open(ability_lookup_path, "w") as file: + csv_writer = csv.writer(file, delimiter=",", lineterminator=os.linesep) for ability_name, commands in ability_lookup.items(): csv_writer.writerow([ability_name] + commands) diff --git a/new_units.py b/new_units.py index e0d80118..dcc609a0 100644 --- a/new_units.py +++ b/new_units.py @@ -9,28 +9,30 @@ import sys UNIT_LOOKUP = dict() -for entry in pkgutil.get_data('sc2reader.data', 'unit_lookup.csv').split('\n'): - if not entry: continue - str_id, title = entry.strip().split(',') +for entry in pkgutil.get_data("sc2reader.data", "unit_lookup.csv").split("\n"): + if not entry: + continue + str_id, title = entry.strip().split(",") UNIT_LOOKUP[str_id] = title -with open(sys.argv[1],'r') as new_units: - for line in new_units: - new_unit_name = line.strip().split(',')[1] - if new_unit_name not in UNIT_LOOKUP: - print("{0},{1}".format(new_unit_name,new_unit_name)) +with open(sys.argv[1], "r") as new_units: + for line in new_units: + new_unit_name = line.strip().split(",")[1] + if new_unit_name not in UNIT_LOOKUP: + print("{0},{1}".format(new_unit_name, new_unit_name)) -print('') -print('') +print("") +print("") ABIL_LOOKUP = dict() -for entry in pkgutil.get_data('sc2reader.data', 'ability_lookup.csv').split('\n'): - if not entry: continue - str_id, abilities = entry.split(',',1) - ABIL_LOOKUP[str_id] = abilities.split(',') +for entry in pkgutil.get_data("sc2reader.data", "ability_lookup.csv").split("\n"): + if not entry: + continue + str_id, abilities = entry.split(",", 1) + ABIL_LOOKUP[str_id] = abilities.split(",") -with open(sys.argv[2], 'r') as new_abilities: - for line in new_abilities: - new_ability_name = line.strip().split(',')[1] - if new_ability_name not in ABIL_LOOKUP: - print("{0},{1}".format(new_ability_name,new_ability_name)) +with open(sys.argv[2], "r") as new_abilities: + for line in new_abilities: + new_ability_name = line.strip().split(",")[1] + if new_ability_name not in ABIL_LOOKUP: + print("{0},{1}".format(new_ability_name, new_ability_name)) diff --git a/sc2reader/__init__.py b/sc2reader/__init__.py index 0bcf512a..68c429f1 100644 --- a/sc2reader/__init__.py +++ b/sc2reader/__init__.py @@ -99,8 +99,8 @@ def useDoubleCache(cache_dir, cache_max_size=0, **options): # Allow environment variables to activate caching -cache_dir = os.getenv('SC2READER_CACHE_DIR') -cache_max_size = os.getenv('SC2READER_CACHE_MAX_SIZE') +cache_dir = os.getenv("SC2READER_CACHE_DIR") +cache_max_size = os.getenv("SC2READER_CACHE_MAX_SIZE") if cache_dir and cache_max_size: useDoubleCache(cache_dir, cache_max_size) elif cache_dir: diff --git a/sc2reader/constants.py b/sc2reader/constants.py index aa3e0ade..1edfa70e 100644 --- a/sc2reader/constants.py +++ b/sc2reader/constants.py @@ -7,43 +7,34 @@ # The ??? means that I don't know what language it is. # If multiple languages use the same set they should be comma separated LOCALIZED_RACES = { - # enUS - 'Terran': 'Terran', - 'Protoss': 'Protoss', - 'Zerg': 'Zerg', - + "Terran": "Terran", + "Protoss": "Protoss", + "Zerg": "Zerg", # ruRU - 'Терран': 'Terran', - 'Протосс': 'Protoss', - 'Зерг': 'Zerg', - + "Терран": "Terran", + "Протосс": "Protoss", + "Зерг": "Zerg", # koKR - '테란': 'Terran', - '프로토스': 'Protoss', - '저그': 'Zerg', - + "테란": "Terran", + "프로토스": "Protoss", + "저그": "Zerg", # plPL - 'Terranie': 'Terran', - 'Protosi': 'Protoss', - 'Zergi': 'Zerg', - + "Terranie": "Terran", + "Protosi": "Protoss", + "Zergi": "Zerg", # zhCH - '人类': 'Terran', - '星灵': 'Protoss', - '异虫': 'Zerg', - + "人类": "Terran", + "星灵": "Protoss", + "异虫": "Zerg", # zhTW - '人類': 'Terran', - '神族': 'Protoss', - '蟲族': 'Zerg', - + "人類": "Terran", + "神族": "Protoss", + "蟲族": "Zerg", # ??? - 'Terrano': 'Terran', - + "Terrano": "Terran", # deDE - 'Terraner': 'Terran', - + "Terraner": "Terran", # esES - Spanish # esMX - Latin American # frFR - French - France @@ -51,131 +42,73 @@ # ptBR - Brazilian Portuguese } -MESSAGE_CODES = { - '0': 'All', - '2': 'Allies', - '128': 'Header', - '125': 'Ping', -} +MESSAGE_CODES = {"0": "All", "2": "Allies", "128": "Header", "125": "Ping"} GAME_SPEED_FACTOR = { - 'WoL': { - 'Slower': 0.6, - 'Slow': 0.8, - 'Normal': 1.0, - 'Fast': 1.2, - 'Faster': 1.4 - }, - 'HotS': { - 'Slower': 0.6, - 'Slow': 0.8, - 'Normal': 1.0, - 'Fast': 1.2, - 'Faster': 1.4 - }, - 'LotV': { - 'Slower': 0.2, - 'Slow': 0.4, - 'Normal': 0.6, - 'Fast': 0.8, - 'Faster': 1.0 - }, + "WoL": {"Slower": 0.6, "Slow": 0.8, "Normal": 1.0, "Fast": 1.2, "Faster": 1.4}, + "HotS": {"Slower": 0.6, "Slow": 0.8, "Normal": 1.0, "Fast": 1.2, "Faster": 1.4}, + "LotV": {"Slower": 0.2, "Slow": 0.4, "Normal": 0.6, "Fast": 0.8, "Faster": 1.0}, } GATEWAY_CODES = { - 'US': 'Americas', - 'KR': 'Asia', - 'EU': 'Europe', - 'SG': 'South East Asia', - 'XX': 'Public Test', + "US": "Americas", + "KR": "Asia", + "EU": "Europe", + "SG": "South East Asia", + "XX": "Public Test", } -GATEWAY_LOOKUP = { - 0: '', - 1: 'us', - 2: 'eu', - 3: 'kr', - 5: 'cn', - 6: 'sea', - 98: 'xx', -} +GATEWAY_LOOKUP = {0: "", 1: "us", 2: "eu", 3: "kr", 5: "cn", 6: "sea", 98: "xx"} COLOR_CODES = { - 'B4141E': 'Red', - '0042FF': 'Blue', - '1CA7EA': 'Teal', - 'EBE129': 'Yellow', - '540081': 'Purple', - 'FE8A0E': 'Orange', - '168000': 'Green', - 'CCA6FC': 'Light Pink', - '1F01C9': 'Violet', - '525494': 'Light Grey', - '106246': 'Dark Green', - '4E2A04': 'Brown', - '96FF91': 'Light Green', - '232323': 'Dark Grey', - 'E55BB0': 'Pink', - 'FFFFFF': 'White', - '000000': 'Black', + "B4141E": "Red", + "0042FF": "Blue", + "1CA7EA": "Teal", + "EBE129": "Yellow", + "540081": "Purple", + "FE8A0E": "Orange", + "168000": "Green", + "CCA6FC": "Light Pink", + "1F01C9": "Violet", + "525494": "Light Grey", + "106246": "Dark Green", + "4E2A04": "Brown", + "96FF91": "Light Green", + "232323": "Dark Grey", + "E55BB0": "Pink", + "FFFFFF": "White", + "000000": "Black", } COLOR_CODES_INV = dict(zip(COLOR_CODES.values(), COLOR_CODES.keys())) SUBREGIONS = { # United States - 'us': { - 1: 'us', - 2: 'la', - }, - + "us": {1: "us", 2: "la"}, # Europe - 'eu': { - 1: 'eu', - 2: 'ru', - }, - + "eu": {1: "eu", 2: "ru"}, # Korea - appear to both map to same place - 'kr': { - 1: 'kr', - 2: 'tw', - }, - + "kr": {1: "kr", 2: "tw"}, # Taiwan - appear to both map to same place - 'tw': { - 1: 'kr', - 2: 'tw', - }, - + "tw": {1: "kr", 2: "tw"}, # China - different url scheme (www.battlenet.com.cn)? - 'cn': { - 1: 'cn', - }, - + "cn": {1: "cn"}, # South East Asia - 'sea': { - 1: 'sea', - }, - + "sea": {1: "sea"}, # Singapore - 'sg': { - 1: 'sg', - }, - + "sg": {1: "sg"}, # Public Test - 'xx': { - 1: 'xx', - }, + "xx": {1: "xx"}, } import json import pkgutil -attributes_json = pkgutil.get_data('sc2reader.data', 'attributes.json').decode('utf8') +attributes_json = pkgutil.get_data("sc2reader.data", "attributes.json").decode("utf8") attributes_dict = json.loads(attributes_json) LOBBY_PROPERTIES = dict() -for key, value in attributes_dict.get('attributes', dict()).items(): +for key, value in attributes_dict.get("attributes", dict()).items(): LOBBY_PROPERTIES[int(key)] = value diff --git a/sc2reader/data/__init__.py b/sc2reader/data/__init__.py index 7a3d4ecb..dd5ff207 100755 --- a/sc2reader/data/__init__.py +++ b/sc2reader/data/__init__.py @@ -12,33 +12,38 @@ from sc2reader.log_utils import loggable try: - cmp # Python 2 + cmp # Python 2 except NameError: cmp = lambda a, b: (a > b) - (a < b) # noqa Python 3 ABIL_LOOKUP = dict() -for entry in pkgutil.get_data('sc2reader.data', 'ability_lookup.csv').decode('utf8').split('\n'): +for entry in ( + pkgutil.get_data("sc2reader.data", "ability_lookup.csv").decode("utf8").split("\n") +): if not entry: continue - str_id, abilities = entry.split(',', 1) - ABIL_LOOKUP[str_id] = abilities.split(',') + str_id, abilities = entry.split(",", 1) + ABIL_LOOKUP[str_id] = abilities.split(",") UNIT_LOOKUP = dict() -for entry in pkgutil.get_data('sc2reader.data', 'unit_lookup.csv').decode('utf8').split('\n'): +for entry in ( + pkgutil.get_data("sc2reader.data", "unit_lookup.csv").decode("utf8").split("\n") +): if not entry: continue - str_id, title = entry.strip().split(',') + str_id, title = entry.strip().split(",") UNIT_LOOKUP[str_id] = title -unit_data = pkgutil.get_data('sc2reader.data', 'unit_info.json').decode('utf8') +unit_data = pkgutil.get_data("sc2reader.data", "unit_info.json").decode("utf8") unit_lookup = json.loads(unit_data) -command_data = pkgutil.get_data('sc2reader.data', 'train_commands.json').decode('utf8') +command_data = pkgutil.get_data("sc2reader.data", "train_commands.json").decode("utf8") train_commands = json.loads(command_data) class Unit(object): """Represents an in-game unit.""" + def __init__(self, unit_id): #: A reference to the player that currently owns this unit. Only available for 2.0.8+ replays. self.owner = None @@ -120,14 +125,18 @@ def is_type(self, unit_type, strict=True): else: if isinstance(unit_type, int): if self._type_class: - return unit_type in [utype.id for utype in self.type_history.values()] + return unit_type in [ + utype.id for utype in self.type_history.values() + ] else: return unit_type == 0 elif isinstance(unit_type, Unit): return unit_type in self.type_history.values() else: if self._type_class: - return unit_type in [utype.str_id for utype in self.type_history.values()] + return unit_type in [ + utype.str_id for utype in self.type_history.values() + ] else: return unit_type is None @@ -213,8 +222,21 @@ def __repr__(self): class UnitType(object): """ Represents an in game unit type """ - def __init__(self, type_id, str_id=None, name=None, title=None, race=None, minerals=0, - vespene=0, supply=0, is_building=False, is_worker=False, is_army=False): + + def __init__( + self, + type_id, + str_id=None, + name=None, + title=None, + race=None, + minerals=0, + vespene=0, + supply=0, + is_building=False, + is_worker=False, + is_army=False, + ): #: The internal integer id representing this unit type self.id = type_id @@ -251,7 +273,10 @@ def __init__(self, type_id, str_id=None, name=None, title=None, race=None, miner class Ability(object): """ Represents an in-game ability """ - def __init__(self, id, name=None, title=None, is_build=False, build_time=0, build_unit=None): + + def __init__( + self, id, name=None, title=None, is_build=False, build_time=0, build_unit=None + ): #: The internal integer id representing this ability. self.id = id @@ -283,6 +308,7 @@ class Build(object): All build data is valid for standard games only. For arcade maps milage may vary. """ + def __init__(self, build_id): #: The integer id of the build self.id = build_id @@ -315,21 +341,46 @@ def change_type(self, unit, new_type, frame): unit_type = self.units[new_type] unit.set_type(unit_type, frame) else: - self.logger.error("Unable to change type of {0} to {1} [frame {2}]; unit type not found in build {3}".format(unit, new_type, frame, self.id)) + self.logger.error( + "Unable to change type of {0} to {1} [frame {2}]; unit type not found in build {3}".format( + unit, new_type, frame, self.id + ) + ) - def add_ability(self, ability_id, name, title=None, is_build=False, build_time=None, build_unit=None): + def add_ability( + self, + ability_id, + name, + title=None, + is_build=False, + build_time=None, + build_unit=None, + ): ability = Ability( ability_id, name=name, title=title or name, is_build=is_build, build_time=build_time, - build_unit=build_unit + build_unit=build_unit, ) setattr(self, name, ability) self.abilities[ability_id] = ability - def add_unit_type(self, type_id, str_id, name, title=None, race='Neutral', minerals=0, vespene=0, supply=0, is_building=False, is_worker=False, is_army=False): + def add_unit_type( + self, + type_id, + str_id, + name, + title=None, + race="Neutral", + minerals=0, + vespene=0, + supply=0, + is_building=False, + is_worker=False, + is_army=False, + ): unit = UnitType( type_id, str_id=str_id, @@ -351,40 +402,46 @@ def add_unit_type(self, type_id, str_id, name, title=None, race='Neutral', miner def load_build(expansion, version): build = Build(version) - unit_file = '{0}/{1}_units.csv'.format(expansion, version) - for entry in pkgutil.get_data('sc2reader.data', unit_file).decode('utf8').split('\n'): + unit_file = "{0}/{1}_units.csv".format(expansion, version) + for entry in ( + pkgutil.get_data("sc2reader.data", unit_file).decode("utf8").split("\n") + ): if not entry: continue - int_id, str_id = entry.strip().split(',') + int_id, str_id = entry.strip().split(",") unit_type = int(int_id, 10) title = UNIT_LOOKUP[str_id] values = dict(type_id=unit_type, str_id=str_id, name=title) - for race in ('Protoss', 'Terran', 'Zerg'): + for race in ("Protoss", "Terran", "Zerg"): if title.lower() in unit_lookup[race]: values.update(unit_lookup[race][title.lower()]) - values['race'] = race + values["race"] = race break build.add_unit_type(**values) - abil_file = '{0}/{1}_abilities.csv'.format(expansion, version) - build.add_ability(ability_id=0, name='RightClick', title='Right Click') - for entry in pkgutil.get_data('sc2reader.data', abil_file).decode('utf8').split('\n'): + abil_file = "{0}/{1}_abilities.csv".format(expansion, version) + build.add_ability(ability_id=0, name="RightClick", title="Right Click") + for entry in ( + pkgutil.get_data("sc2reader.data", abil_file).decode("utf8").split("\n") + ): if not entry: continue - int_id_base, str_id = entry.strip().split(',') + int_id_base, str_id = entry.strip().split(",") int_id_base = int(int_id_base, 10) << 5 abils = ABIL_LOOKUP[str_id] - real_abils = [(i, abil) for i, abil in enumerate(abils) if abil.strip() != ''] + real_abils = [(i, abil) for i, abil in enumerate(abils) if abil.strip() != ""] if len(real_abils) == 0: real_abils = [(0, str_id)] for index, ability_name in real_abils: - unit_name, build_time = train_commands.get(ability_name, ('', 0)) - if 'Hallucinated' in unit_name: # Not really sure how to handle hallucinations + unit_name, build_time = train_commands.get(ability_name, ("", 0)) + if ( + "Hallucinated" in unit_name + ): # Not really sure how to handle hallucinations unit_name = unit_name[12:] build.add_ability( @@ -392,26 +449,27 @@ def load_build(expansion, version): name=ability_name, is_build=bool(unit_name), build_unit=getattr(build, unit_name, None), - build_time=build_time + build_time=build_time, ) return build + # Load the WoL Data wol_builds = dict() -for version in ('16117', '17326', '18092', '19458', '22612', '24944'): - wol_builds[version] = load_build('WoL', version) +for version in ("16117", "17326", "18092", "19458", "22612", "24944"): + wol_builds[version] = load_build("WoL", version) # Load HotS Data hots_builds = dict() -for version in ('base', '23925', '24247', '24764'): - hots_builds[version] = load_build('HotS', version) -hots_builds['38215'] = load_build('LotV', 'base') -hots_builds['38215'].id = '38215' +for version in ("base", "23925", "24247", "24764"): + hots_builds[version] = load_build("HotS", version) +hots_builds["38215"] = load_build("LotV", "base") +hots_builds["38215"].id = "38215" # Load LotV Data lotv_builds = dict() -for version in ('base', '44401', '47185', '48258', '53644', '54724', '59587', '70154'): - lotv_builds[version] = load_build('LotV', version) +for version in ("base", "44401", "47185", "48258", "53644", "54724", "59587", "70154"): + lotv_builds[version] = load_build("LotV", version) -datapacks = builds = {'WoL': wol_builds, 'HotS': hots_builds, 'LotV': lotv_builds} +datapacks = builds = {"WoL": wol_builds, "HotS": hots_builds, "LotV": lotv_builds} diff --git a/sc2reader/data/create_lookup.py b/sc2reader/data/create_lookup.py index 73623836..2ffa2560 100755 --- a/sc2reader/data/create_lookup.py +++ b/sc2reader/data/create_lookup.py @@ -1,14 +1,14 @@ abilities = dict() -with open('hots_abilities.csv', 'r') as f: +with open("hots_abilities.csv", "r") as f: for line in f: - num, ability = line.strip('\r\n ').split(',') - abilities[ability] = [""]*32 + num, ability = line.strip("\r\n ").split(",") + abilities[ability] = [""] * 32 -with open('command_lookup.csv', 'r') as f: +with open("command_lookup.csv", "r") as f: for line in f: - ability, commands = line.strip('\r\n ').split('|', 1) - abilities[ability] = commands.split('|') + ability, commands = line.strip("\r\n ").split("|", 1) + abilities[ability] = commands.split("|") -with open('new_lookup.csv', 'w') as out: +with open("new_lookup.csv", "w") as out: for ability, commands in sorted(abilities.items()): - out.write(','.join([ability]+commands)+'\n') + out.write(",".join([ability] + commands) + "\n") diff --git a/sc2reader/decoders.py b/sc2reader/decoders.py index b2ff35f9..78372e8f 100644 --- a/sc2reader/decoders.py +++ b/sc2reader/decoders.py @@ -34,7 +34,7 @@ def __init__(self, contents, endian): """ Accepts both strings and files implementing ``read()`` and decodes them in the specified endian format. """ - if hasattr(contents, 'read'): + if hasattr(contents, "read"): self._contents = contents.read() else: self._contents = contents @@ -49,18 +49,21 @@ def __init__(self, contents, endian): # decode the endian value if necessary self.endian = endian.lower() - if self.endian.lower() == 'little': + if self.endian.lower() == "little": self.endian = "<" - elif self.endian.lower() == 'big': + elif self.endian.lower() == "big": self.endian = ">" - elif self.endian not in ('<', '>'): - raise ValueError("Endian must be one of 'little', '<', 'big', or '>' but was: "+self.endian) + elif self.endian not in ("<", ">"): + raise ValueError( + "Endian must be one of 'little', '<', 'big', or '>' but was: " + + self.endian + ) # Pre-compiling - self._unpack_int = struct.Struct(str(self.endian+'I')).unpack - self._unpack_short = struct.Struct(str(self.endian+'H')).unpack - self._unpack_longlong = struct.Struct(str(self.endian+'Q')).unpack - self._unpack_bytes = lambda bytes: bytes if self.endian == '>' else bytes[::-1] + self._unpack_int = struct.Struct(str(self.endian + "I")).unpack + self._unpack_short = struct.Struct(str(self.endian + "H")).unpack + self._unpack_longlong = struct.Struct(str(self.endian + "Q")).unpack + self._unpack_bytes = lambda bytes: bytes if self.endian == ">" else bytes[::-1] def done(self): """ Returns true when all bytes have been decoded """ @@ -73,7 +76,7 @@ def read_range(self, start, end): def peek(self, count): """ Returns the raw byte string for the next ``count`` bytes """ start = self.tell() - return self._contents[start:start+count] + return self._contents[start : start + count] def read_uint8(self): """ Returns the next byte as an unsigned integer """ @@ -97,17 +100,17 @@ def read_bytes(self, count): def read_uint(self, count): """ Returns the next ``count`` bytes as an unsigned integer """ - unpack = struct.Struct(str(self.endian+'B'*count)).unpack + unpack = struct.Struct(str(self.endian + "B" * count)).unpack uint = 0 for byte in unpack(self.read(count)): uint = uint << 8 | byte return uint - def read_string(self, count, encoding='utf8'): + def read_string(self, count, encoding="utf8"): """ Read a string in given encoding (default utf8) that is ``count`` bytes long """ return self.read_bytes(count).decode(encoding) - def read_cstring(self, encoding='utf8'): + def read_cstring(self, encoding="utf8"): """ Read a NULL byte terminated character string decoded with given encoding (default utf8). Ignores endian. """ cstring = BytesIO() while True: @@ -128,6 +131,7 @@ class BitPackedDecoder(object): bits and not in bytes. """ + #: The ByteDecoder used internally to read byte #: aligned values. _buffer = None @@ -153,7 +157,7 @@ class BitPackedDecoder(object): _bit_masks = list(zip(_lo_masks, _hi_masks)) def __init__(self, contents): - self._buffer = ByteDecoder(contents, endian='BIG') + self._buffer = ByteDecoder(contents, endian="BIG") # Partially expose the ByteBuffer interface self.length = self._buffer.length @@ -194,8 +198,8 @@ def read_uint16(self): if self._bit_shift != 0: lo_mask, hi_mask = self._bit_masks[self._bit_shift] hi_bits = (self._next_byte & hi_mask) << 8 - mi_bits = (data & 0xFF00) >> (8-self._bit_shift) - lo_bits = (data & lo_mask) + mi_bits = (data & 0xFF00) >> (8 - self._bit_shift) + lo_bits = data & lo_mask self._next_byte = data & 0xFF data = hi_bits | mi_bits | lo_bits @@ -208,8 +212,8 @@ def read_uint32(self): if self._bit_shift != 0: lo_mask, hi_mask = self._bit_masks[self._bit_shift] hi_bits = (self._next_byte & hi_mask) << 24 - mi_bits = (data & 0xFFFFFF00) >> (8-self._bit_shift) - lo_bits = (data & lo_mask) + mi_bits = (data & 0xFFFFFF00) >> (8 - self._bit_shift) + lo_bits = data & lo_mask self._next_byte = data & 0xFF data = hi_bits | mi_bits | lo_bits @@ -222,8 +226,8 @@ def read_uint64(self): if self._bit_shift != 0: lo_mask, hi_mask = self._bit_masks[self._bit_shift] hi_bits = (self._next_byte & hi_mask) << 56 - mi_bits = (data & 0xFFFFFFFFFFFFFF00) >> (8-self._bit_shift) - lo_bits = (data & lo_mask) + mi_bits = (data & 0xFFFFFFFFFFFFFF00) >> (8 - self._bit_shift) + lo_bits = data & lo_mask self._next_byte = data & 0xFF data = hi_bits | mi_bits | lo_bits @@ -246,7 +250,7 @@ def read_aligned_bytes(self, count): self.byte_align() return self._buffer.read_bytes(count) - def read_aligned_string(self, count, encoding='utf8'): + def read_aligned_string(self, count, encoding="utf8"): """ Skips to the beginning of the next byte and returns the next ``count`` bytes decoded with encoding (default utf8) """ self.byte_align() return self._buffer.read_string(count, encoding) @@ -259,8 +263,10 @@ def read_bytes(self, count): temp_buffer = BytesIO() prev_byte = self._next_byte lo_mask, hi_mask = self._bit_masks[self._bit_shift] - for next_byte in struct.unpack(str("B")*count, data): - temp_buffer.write(struct.pack(str("B"), prev_byte & hi_mask | next_byte & lo_mask)) + for next_byte in struct.unpack(str("B") * count, data): + temp_buffer.write( + struct.pack(str("B"), prev_byte & hi_mask | next_byte & lo_mask) + ) prev_byte = next_byte self._next_byte = prev_byte @@ -277,7 +283,7 @@ def read_bits(self, count): # If we've got a byte in progress use it first if bit_shift != 0: - bits_left = 8-bit_shift + bits_left = 8 - bit_shift if bits_left < bits: bits -= bits_left @@ -291,7 +297,7 @@ def read_bits(self, count): # Then grab any additional whole bytes as needed if bits >= 8: - bytes = int(bits/8) + bytes = int(bits / 8) if bytes == 1: bits -= 8 @@ -306,7 +312,7 @@ def read_bits(self, count): result |= self._buffer.read_uint32() << bits else: - for byte in struct.unpack(str("B")*bytes, self._read(bytes)): + for byte in struct.unpack(str("B") * bytes, self._read(bytes)): bits -= 8 result |= byte << bits @@ -359,7 +365,9 @@ def read_struct(self, datatype=None): elif datatype == 0x05: # Struct entries = self.read_vint() - data = dict([(self.read_vint(), self.read_struct()) for i in range(entries)]) + data = dict( + [(self.read_vint(), self.read_struct()) for i in range(entries)] + ) elif datatype == 0x06: # u8 data = ord(self._buffer.read(1)) diff --git a/sc2reader/engine/__init__.py b/sc2reader/engine/__init__.py index cd72973c..b3b8ad87 100644 --- a/sc2reader/engine/__init__.py +++ b/sc2reader/engine/__init__.py @@ -15,6 +15,7 @@ def setGameEngine(engine): module.register_plugin = engine.register_plugin module.register_plugins = engine.register_plugins + _default_engine = GameEngine() _default_engine.register_plugin(plugins.GameHeartNormalizer()) _default_engine.register_plugin(plugins.ContextLoader()) diff --git a/sc2reader/engine/engine.py b/sc2reader/engine/engine.py index 65caced1..e091fa3a 100644 --- a/sc2reader/engine/engine.py +++ b/sc2reader/engine/engine.py @@ -112,6 +112,7 @@ def handleCommandEvent(self, event, replay): message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code) yield PluginExit(self, code=1, details=dict(msg=message)) """ + def __init__(self, plugins=[]): self._plugins = list() self.register_plugins(*plugins) @@ -124,7 +125,7 @@ def register_plugins(self, *plugins): self.register_plugin(plugin) def plugins(self): - return self._plugins + return self._plugins def run(self, replay): # A map of [event.name] => event handlers in plugin registration order @@ -152,7 +153,7 @@ def run(self, replay): while len(event_queue) > 0: event = event_queue.popleft() - if event.name == 'PluginExit': + if event.name == "PluginExit": # Remove the plugin and reset the handlers. plugins.remove(event.plugin) handlers.clear() @@ -174,18 +175,20 @@ def run(self, replay): new_events = collections.deque() for event_handler in event_handlers: try: - for new_event in (event_handler(event, replay) or []): - if new_event.name == 'PluginExit': + for new_event in event_handler(event, replay) or []: + if new_event.name == "PluginExit": new_events.append(new_event) break else: new_events.appendleft(new_event) except Exception as e: - if event_handler.__self__.name in ['ContextLoader']: + if event_handler.__self__.name in ["ContextLoader"]: # Certain built in plugins should probably still cause total failure raise # Maybe?? else: - new_event = PluginExit(event_handler.__self__, code=1, details=dict(error=e)) + new_event = PluginExit( + event_handler.__self__, code=1, details=dict(error=e) + ) new_events.append(new_event) event_queue.extendleft(new_events) @@ -195,22 +198,26 @@ def run(self, replay): replay.plugin_result[plugin.name] = (0, dict()) def _get_event_handlers(self, event, plugins): - return sum([self._get_plugin_event_handlers(plugin, event) for plugin in plugins], []) + return sum( + [self._get_plugin_event_handlers(plugin, event) for plugin in plugins], [] + ) def _get_plugin_event_handlers(self, plugin, event): handlers = list() - if isinstance(event, Event) and hasattr(plugin, 'handleEvent'): - handlers.append(getattr(plugin, 'handleEvent', None)) - if isinstance(event, MessageEvent) and hasattr(plugin, 'handleMessageEvent'): - handlers.append(getattr(plugin, 'handleMessageEvent', None)) - if isinstance(event, GameEvent) and hasattr(plugin, 'handleGameEvent'): - handlers.append(getattr(plugin, 'handleGameEvent', None)) - if isinstance(event, TrackerEvent) and hasattr(plugin, 'handleTrackerEvent'): - handlers.append(getattr(plugin, 'handleTrackerEvent', None)) - if isinstance(event, CommandEvent) and hasattr(plugin, 'handleCommandEvent'): - handlers.append(getattr(plugin, 'handleCommandEvent', None)) - if isinstance(event, ControlGroupEvent) and hasattr(plugin, 'handleControlGroupEvent'): - handlers.append(getattr(plugin, 'handleControlGroupEvent', None)) - if hasattr(plugin, 'handle'+event.name): - handlers.append(getattr(plugin, 'handle'+event.name, None)) + if isinstance(event, Event) and hasattr(plugin, "handleEvent"): + handlers.append(getattr(plugin, "handleEvent", None)) + if isinstance(event, MessageEvent) and hasattr(plugin, "handleMessageEvent"): + handlers.append(getattr(plugin, "handleMessageEvent", None)) + if isinstance(event, GameEvent) and hasattr(plugin, "handleGameEvent"): + handlers.append(getattr(plugin, "handleGameEvent", None)) + if isinstance(event, TrackerEvent) and hasattr(plugin, "handleTrackerEvent"): + handlers.append(getattr(plugin, "handleTrackerEvent", None)) + if isinstance(event, CommandEvent) and hasattr(plugin, "handleCommandEvent"): + handlers.append(getattr(plugin, "handleCommandEvent", None)) + if isinstance(event, ControlGroupEvent) and hasattr( + plugin, "handleControlGroupEvent" + ): + handlers.append(getattr(plugin, "handleControlGroupEvent", None)) + if hasattr(plugin, "handle" + event.name): + handlers.append(getattr(plugin, "handle" + event.name, None)) return handlers diff --git a/sc2reader/engine/events.py b/sc2reader/engine/events.py index 44387e52..8857a4b1 100644 --- a/sc2reader/engine/events.py +++ b/sc2reader/engine/events.py @@ -3,15 +3,15 @@ class InitGameEvent(object): - name = 'InitGame' + name = "InitGame" class EndGameEvent(object): - name = 'EndGame' + name = "EndGame" class PluginExit(object): - name = 'PluginExit' + name = "PluginExit" def __init__(self, plugin, code=0, details=None): self.plugin = plugin diff --git a/sc2reader/engine/plugins/__init__.py b/sc2reader/engine/plugins/__init__.py index 15e77ff3..7ddff085 100644 --- a/sc2reader/engine/plugins/__init__.py +++ b/sc2reader/engine/plugins/__init__.py @@ -7,4 +7,3 @@ from sc2reader.engine.plugins.supply import SupplyTracker from sc2reader.engine.plugins.creeptracker import CreepTracker from sc2reader.engine.plugins.gameheart import GameHeartNormalizer - diff --git a/sc2reader/engine/plugins/apm.py b/sc2reader/engine/plugins/apm.py index 526bf028..ec6fee7c 100644 --- a/sc2reader/engine/plugins/apm.py +++ b/sc2reader/engine/plugins/apm.py @@ -15,7 +15,8 @@ class APMTracker(object): APM is 0 for games under 1 minute in length. """ - name = 'APMTracker' + + name = "APMTracker" def handleInitGame(self, event, replay): for human in replay.humans: @@ -25,15 +26,15 @@ def handleInitGame(self, event, replay): def handleControlGroupEvent(self, event, replay): event.player.aps[event.second] += 1.4 - event.player.apm[int(event.second/60)] += 1.4 + event.player.apm[int(event.second / 60)] += 1.4 def handleSelectionEvent(self, event, replay): event.player.aps[event.second] += 1.4 - event.player.apm[int(event.second/60)] += 1.4 + event.player.apm[int(event.second / 60)] += 1.4 def handleCommandEvent(self, event, replay): event.player.aps[event.second] += 1.4 - event.player.apm[int(event.second/60)] += 1.4 + event.player.apm[int(event.second / 60)] += 1.4 def handlePlayerLeaveEvent(self, event, replay): event.player.seconds_played = event.second @@ -41,6 +42,8 @@ def handlePlayerLeaveEvent(self, event, replay): def handleEndGame(self, event, replay): for human in replay.humans: if len(human.apm.keys()) > 0: - human.avg_apm = sum(human.aps.values())/float(human.seconds_played)*60 + human.avg_apm = ( + sum(human.aps.values()) / float(human.seconds_played) * 60 + ) else: human.avg_apm = 0 diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index 6b5362bc..dd846634 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -8,7 +8,7 @@ @loggable class ContextLoader(object): - name = 'ContextLoader' + name = "ContextLoader" def handleInitGame(self, event, replay): replay.units = set() @@ -32,17 +32,26 @@ def handleCommandEvent(self, event, replay): if event.player.pid in self.last_target_ability_event: del self.last_target_ability_event[event.player.pid] - if not getattr(replay, 'marked_error', None): + if not getattr(replay, "marked_error", None): replay.marked_error = True event.logger.error(replay.filename) event.logger.error("Release String: " + replay.release_string) for player in replay.players: try: - event.logger.error("\t"+unicode(player).encode('ascii', 'ignore')) + event.logger.error( + "\t" + unicode(player).encode("ascii", "ignore") + ) except NameError: # unicode() is not defined in Python 3 - event.logger.error("\t"+player.__str__()) + event.logger.error("\t" + player.__str__()) - self.logger.error("{0}\t{1}\tMissing ability {2:X} from {3}".format(event.frame, event.player.name, event.ability_id, replay.datapack.__class__.__name__)) + self.logger.error( + "{0}\t{1}\tMissing ability {2:X} from {3}".format( + event.frame, + event.player.name, + event.ability_id, + replay.datapack.__class__.__name__, + ) + ) else: event.ability = replay.datapack.abilities[event.ability_id] @@ -61,13 +70,19 @@ def handleTargetUnitCommandEvent(self, event, replay): if event.target_unit_id in replay.objects: event.target = replay.objects[event.target_unit_id] - if not replay.tracker_events and not event.target.is_type(event.target_unit_type): - replay.datapack.change_type(event.target, event.target_unit_type, event.frame) + if not replay.tracker_events and not event.target.is_type( + event.target_unit_type + ): + replay.datapack.change_type( + event.target, event.target_unit_type, event.frame + ) else: # Often when the target_unit_id is not in replay.objects it is 0 because it # is a target building/destructable hidden by fog of war. Perhaps we can match # it through the fog using location? - unit = replay.datapack.create_unit(event.target_unit_id, event.target_unit_type, event.frame) + unit = replay.datapack.create_unit( + event.target_unit_id, event.target_unit_type, event.frame + ) event.target = unit replay.objects[event.target_unit_id] = unit @@ -78,7 +93,9 @@ def handleUpdateTargetUnitCommandEvent(self, event, replay): if event.player.pid in self.last_target_ability_event: # store corresponding TargetUnitCommandEvent data in this event # currently using for *MacroTracker only, so only need ability name - event.ability_name = self.last_target_ability_event[event.player.pid].ability_name + event.ability_name = self.last_target_ability_event[ + event.player.pid + ].ability_name self.handleTargetUnitCommandEvent(event, replay) @@ -88,14 +105,23 @@ def handleSelectionEvent(self, event, replay): units = list() # TODO: Blizzard calls these subgroup flags but that doesn't make sense right now - for (unit_id, unit_type, subgroup_flags, intra_subgroup_flags) in event.new_unit_info: + for ( + unit_id, + unit_type, + subgroup_flags, + intra_subgroup_flags, + ) in event.new_unit_info: # If we don't have access to tracker events, use selection events to create # new units and track unit type changes. It won't be perfect, but it is better # than nothing. if not replay.tracker_events: # Starting at 23925 the default viking mode is assault. Most people expect # the default viking mode to be figher so fudge it a bit here. - if replay.versions[1] == 2 and replay.build >= 23925 and unit_type == 71: + if ( + replay.versions[1] == 2 + and replay.build >= 23925 + and unit_type == 71 + ): unit_type = 72 if unit_id in replay.objects: @@ -109,13 +135,12 @@ def handleSelectionEvent(self, event, replay): # If we have tracker events, the unit must already exist and must already # have the correct unit type. elif unit_id in replay.objects: - unit = replay.objects[unit_id] + unit = replay.objects[unit_id] # Except when it doesn't. else: - unit = replay.datapack.create_unit(unit_id, unit_type, event.frame) - replay.objects[unit_id] = unit - + unit = replay.datapack.create_unit(unit_id, unit_type, event.frame) + replay.objects[unit_id] = unit # Selection events hold flags on units (like hallucination) unit.apply_flags(intra_subgroup_flags) @@ -148,7 +173,9 @@ def handleUnitBornEvent(self, event, replay): event.unit = replay.objects[event.unit_id] else: # TODO: How to tell if something is hallucination? - event.unit = replay.datapack.create_unit(event.unit_id, event.unit_type_name, event.frame) + event.unit = replay.datapack.create_unit( + event.unit_id, event.unit_type_name, event.frame + ) replay.objects[event.unit_id] = event.unit replay.active_units[event.unit_id_index] = event.unit @@ -171,9 +198,17 @@ def handleUnitDiedEvent(self, event, replay): if event.unit_id_index in replay.active_units: del replay.active_units[event.unit_id_index] else: - self.logger.error("Unable to delete unit index {0} at {1} [{2}], index not active.".format(event.killer_pid, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unable to delete unit index {0} at {1} [{2}], index not active.".format( + event.killer_pid, Length(seconds=event.second), event.frame + ) + ) else: - self.logger.error("Unit {0} died at {1} [{2}] before it was born!".format(event.unit_id, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unit {0} died at {1} [{2}] before it was born!".format( + event.unit_id, Length(seconds=event.second), event.frame + ) + ) if event.killing_player_id in replay.player: event.killing_player = event.killer = replay.player[event.killing_player_id] @@ -181,7 +216,11 @@ def handleUnitDiedEvent(self, event, replay): event.unit.killing_player = event.unit.killed_by = event.killing_player event.killing_player.killed_units.append(event.unit) elif event.killing_player_id: - self.logger.error("Unknown killing player id {0} at {1} [{2}]".format(event.killing_player_id, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unknown killing player id {0} at {1} [{2}]".format( + event.killing_player_id, Length(seconds=event.second), event.frame + ) + ) if event.killing_unit_id in replay.objects: event.killing_unit = replay.objects[event.killing_unit_id] @@ -189,7 +228,11 @@ def handleUnitDiedEvent(self, event, replay): event.unit.killing_unit = event.killing_unit event.killing_unit.killed_units.append(event.unit) elif event.killing_unit_id: - self.logger.error("Unknown killing unit id {0} at {1} [{2}]".format(event.killing_unit_id, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unknown killing unit id {0} at {1} [{2}]".format( + event.killing_unit_id, Length(seconds=event.second), event.frame + ) + ) def handleUnitOwnerChangeEvent(self, event, replay): self.load_tracker_controller(event, replay) @@ -201,7 +244,11 @@ def handleUnitOwnerChangeEvent(self, event, replay): if event.unit_id in replay.objects: event.unit = replay.objects[event.unit_id] else: - self.logger.error("Unit {0} owner changed at {1} [{2}] before it was born!".format(event.unit_id, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unit {0} owner changed at {1} [{2}] before it was born!".format( + event.unit_id, Length(seconds=event.second), event.frame + ) + ) if event.unit_upkeeper: if event.unit.owner: @@ -217,7 +264,11 @@ def handleUnitTypeChangeEvent(self, event, replay): event.unit = replay.objects[event.unit_id] replay.datapack.change_type(event.unit, event.unit_type_name, event.frame) else: - self.logger.error("Unit {0} type changed at {1} [{2}] before it was born!".format(event.unit_id, Length(seconds=event.second))) + self.logger.error( + "Unit {0} type changed at {1} [{2}] before it was born!".format( + event.unit_id, Length(seconds=event.second) + ) + ) def handleUpgradeCompleteEvent(self, event, replay): self.load_tracker_player(event, replay) @@ -235,7 +286,9 @@ def handleUnitInitEvent(self, event, replay): event.unit = replay.objects[event.unit_id] else: # TODO: How to tell if something is hallucination? - event.unit = replay.datapack.create_unit(event.unit_id, event.unit_type_name, event.frame) + event.unit = replay.datapack.create_unit( + event.unit_id, event.unit_type_name, event.frame + ) replay.objects[event.unit_id] = event.unit replay.active_units[event.unit_id_index] = event.unit @@ -254,7 +307,11 @@ def handleUnitDoneEvent(self, event, replay): event.unit = replay.objects[event.unit_id] event.unit.finished_at = event.frame else: - self.logger.error("Unit {0} done at {1} [{2}] before it was started!".format(event.killer_pid, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unit {0} done at {1} [{2}] before it was started!".format( + event.killer_pid, Length(seconds=event.second), event.frame + ) + ) def handleUnitPositionsEvent(self, event, replay): if not replay.datapack: @@ -266,15 +323,28 @@ def handleUnitPositionsEvent(self, event, replay): unit.location = (x, y) event.units[unit] = unit.location else: - self.logger.error("Unit at active_unit index {0} moved at {1} [{2}] but it doesn't exist!".format(event.killer_pid, Length(seconds=event.second), event.frame)) + self.logger.error( + "Unit at active_unit index {0} moved at {1} [{2}] but it doesn't exist!".format( + event.killer_pid, Length(seconds=event.second), event.frame + ) + ) def load_message_game_player(self, event, replay): - if replay.versions[1] == 1 or (replay.versions[1] == 2 and replay.build < 24247): + if replay.versions[1] == 1 or ( + replay.versions[1] == 2 and replay.build < 24247 + ): if event.pid in replay.entity: event.player = replay.entity[event.pid] event.player.events.append(event) elif event.pid != 16: - self.logger.error("Bad pid ({0}) for event {1} at {2} [{3}].".format(event.pid, event.__class__, Length(seconds=event.second), event.frame)) + self.logger.error( + "Bad pid ({0}) for event {1} at {2} [{3}].".format( + event.pid, + event.__class__, + Length(seconds=event.second), + event.frame, + ) + ) else: pass # This is a global event @@ -283,7 +353,14 @@ def load_message_game_player(self, event, replay): event.player = replay.human[event.pid] event.player.events.append(event) elif event.pid != 16: - self.logger.error("Bad pid ({0}) for event {1} at {2} [{3}].".format(event.pid, event.__class__, Length(seconds=event.second), event.frame)) + self.logger.error( + "Bad pid ({0}) for event {1} at {2} [{3}].".format( + event.pid, + event.__class__, + Length(seconds=event.second), + event.frame, + ) + ) else: pass # This is a global event @@ -291,16 +368,37 @@ def load_tracker_player(self, event, replay): if event.pid in replay.entity: event.player = replay.entity[event.pid] else: - self.logger.error("Bad pid ({0}) for event {1} at {2} [{3}].".format(event.pid, event.__class__, Length(seconds=event.second), event.frame)) + self.logger.error( + "Bad pid ({0}) for event {1} at {2} [{3}].".format( + event.pid, + event.__class__, + Length(seconds=event.second), + event.frame, + ) + ) def load_tracker_upkeeper(self, event, replay): if event.upkeep_pid in replay.entity: event.unit_upkeeper = replay.entity[event.upkeep_pid] elif event.upkeep_pid != 0: - self.logger.error("Bad upkeep_pid ({0}) for event {1} at {2} [{3}].".format(event.upkeep_pid, event.__class__, Length(seconds=event.second), event.frame)) + self.logger.error( + "Bad upkeep_pid ({0}) for event {1} at {2} [{3}].".format( + event.upkeep_pid, + event.__class__, + Length(seconds=event.second), + event.frame, + ) + ) def load_tracker_controller(self, event, replay): if event.control_pid in replay.entity: event.unit_controller = replay.entity[event.control_pid] elif event.control_pid != 0: - self.logger.error("Bad control_pid ({0}) for event {1} at {2} [{3}].".format(event.control_pid, event.__class__, Length(seconds=event.second), event.frame)) + self.logger.error( + "Bad control_pid ({0}) for event {1} at {2} [{3}].".format( + event.control_pid, + event.__class__, + Length(seconds=event.second), + event.frame, + ) + ) diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index 7f35f868..98f0148f 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -22,80 +22,94 @@ # The creep tracker plugin class CreepTracker(object): - ''' + """ The Creep tracker populates player.max_creep_spread and player.creep_spread by minute This uses the creep_tracker class to calculate the features - ''' - name = 'CreepTracker' + """ + + name = "CreepTracker" def handleInitGame(self, event, replay): - try: - if len( replay.tracker_events) ==0 : - return - if replay.map is None: - replay.load_map() - self.creepTracker = creep_tracker(replay) - for player in replay.players: - if player.play_race[0] == 'Z': - self.creepTracker.init_cgu_lists(player.pid) - except Exception as e: - print("Whoa! {}".format(e)) - pass + try: + if len(replay.tracker_events) == 0: + return + if replay.map is None: + replay.load_map() + self.creepTracker = creep_tracker(replay) + for player in replay.players: + if player.play_race[0] == "Z": + self.creepTracker.init_cgu_lists(player.pid) + except Exception as e: + print("Whoa! {}".format(e)) + pass def handleUnitDiedEvent(self, event, replay): - try: - self.creepTracker.remove_from_list(event.unit_id,event.second) - except Exception as e: - print("Whoa! {}".format(e)) - pass - + try: + self.creepTracker.remove_from_list(event.unit_id, event.second) + except Exception as e: + print("Whoa! {}".format(e)) + pass - def handleUnitInitEvent(self,event,replay): - try: - if event.unit_type_name in ["CreepTumor", "Hatchery","NydusCanal"] : - self.creepTracker.add_to_list(event.control_pid,event.unit_id,\ - (event.x, event.y), event.unit_type_name,event.second) - except Exception as e: - print("Whoa! {}".format(e)) - pass + def handleUnitInitEvent(self, event, replay): + try: + if event.unit_type_name in ["CreepTumor", "Hatchery", "NydusCanal"]: + self.creepTracker.add_to_list( + event.control_pid, + event.unit_id, + (event.x, event.y), + event.unit_type_name, + event.second, + ) + except Exception as e: + print("Whoa! {}".format(e)) + pass - def handleUnitBornEvent(self,event,replay): - try: - if event.unit_type_name== "Hatchery": - self.creepTracker.add_to_list(event.control_pid, event.unit_id,\ - (event.x,event.y),event.unit_type_name,event.second) - except Exception as e: - print("Whoa! {}".format(e)) - pass + def handleUnitBornEvent(self, event, replay): + try: + if event.unit_type_name == "Hatchery": + self.creepTracker.add_to_list( + event.control_pid, + event.unit_id, + (event.x, event.y), + event.unit_type_name, + event.second, + ) + except Exception as e: + print("Whoa! {}".format(e)) + pass def handleEndGame(self, event, replay): - try: - if len( replay.tracker_events) ==0 : - return - for player in replay.players: - if player.play_race[0] == 'Z': - self.creepTracker.reduce_cgu_per_minute(player.pid) - player.creep_spread_by_minute = self.creepTracker.get_creep_spread_area(player.pid) - # note that player.max_creep_spread may be a tuple or an int - if player.creep_spread_by_minute: - player.max_creep_spread = max(player.creep_spread_by_minute.items(),key=lambda x:x[1]) - else: - ## Else statement is for players with no creep spread(ie: not Zerg) - player.max_creep_spread = 0 - except Exception as e: - print("Whoa! {}".format(e)) - pass + try: + if len(replay.tracker_events) == 0: + return + for player in replay.players: + if player.play_race[0] == "Z": + self.creepTracker.reduce_cgu_per_minute(player.pid) + player.creep_spread_by_minute = self.creepTracker.get_creep_spread_area( + player.pid + ) + # note that player.max_creep_spread may be a tuple or an int + if player.creep_spread_by_minute: + player.max_creep_spread = max( + player.creep_spread_by_minute.items(), key=lambda x: x[1] + ) + else: + ## Else statement is for players with no creep spread(ie: not Zerg) + player.max_creep_spread = 0 + except Exception as e: + print("Whoa! {}".format(e)) + pass ## The class used to used to calculate the creep spread -class creep_tracker(): - def __init__(self,replay): - #if the debug option is selected, minimaps will be printed to a file +class creep_tracker: + def __init__(self, replay): + # if the debug option is selected, minimaps will be printed to a file ##and a stringIO containing the minimap image will be saved for ##every minite in the game and the minimap with creep highlighted ## will be printed out. - self.debug = replay.opt['debug'] + self.debug = replay.opt["debug"] ##This list contains creep spread area for each player self.creep_spread_by_minute = dict() ## this list contains a minimap highlighted with creep spread for each player @@ -104,62 +118,63 @@ def __init__(self,replay): ## This list contains all the active cgus in every time frame self.creep_gen_units = dict() ## Thist list corresponds to creep_gen_units storing the time of each CGU - self.creep_gen_units_times= dict() + self.creep_gen_units_times = dict() ## convert all possible cgu radii into a sets of coordinates centred around the origin, ## in order to use this with the CGUs, the centre point will be ## subtracted with coordinates of cgus created in game - self.unit_name_to_radius={'CreepTumor': 10,"Hatchery":8,"NydusCanal": 5} - self.radius_to_coordinates= dict() + self.unit_name_to_radius = {"CreepTumor": 10, "Hatchery": 8, "NydusCanal": 5} + self.radius_to_coordinates = dict() for x in self.unit_name_to_radius: - self.radius_to_coordinates[self.unit_name_to_radius[x]] =\ - self.radius_to_map_positions(self.unit_name_to_radius[x]) - #Get map information + self.radius_to_coordinates[ + self.unit_name_to_radius[x] + ] = self.radius_to_map_positions(self.unit_name_to_radius[x]) + # Get map information replayMap = replay.map # extract image from replay package mapsio = BytesIO(replayMap.minimap) im = PIL_open(mapsio) ##remove black box around minimap -# https://github.com/jonomon/sc2reader/commit/2a793475c0358989e7fda4a75642035a810e2274 -# cropped = im.crop(im.getbbox()) -# cropsize = cropped.size + # https://github.com/jonomon/sc2reader/commit/2a793475c0358989e7fda4a75642035a810e2274 + # cropped = im.crop(im.getbbox()) + # cropsize = cropped.size cropsizeX = replay.map.map_info.camera_right - replay.map.map_info.camera_left cropsizeY = replay.map.map_info.camera_top - replay.map.map_info.camera_bottom - cropsize = (cropsizeX,cropsizeY) + cropsize = (cropsizeX, cropsizeY) self.map_height = 100.0 # resize height to MAPHEIGHT, and compute new width that # would preserve aspect ratio self.map_width = int(cropsize[0] * (float(self.map_height) / cropsize[1])) - self.mapSize =self.map_height * self.map_width + self.mapSize = self.map_height * self.map_width ## the following parameters are only needed if minimaps have to be printed -# minimapSize = ( self.map_width,int(self.map_height) ) -# self.minimap_image = cropped.resize(minimapSize, ANTIALIAS) + # minimapSize = ( self.map_width,int(self.map_height) ) + # self.minimap_image = cropped.resize(minimapSize, ANTIALIAS) - mapOffsetX= replayMap.map_info.camera_left + mapOffsetX = replayMap.map_info.camera_left mapOffsetY = replayMap.map_info.camera_bottom - mapCenter = [mapOffsetX+ cropsize[0]/2.0, mapOffsetY + cropsize[1]/2.0] + mapCenter = [mapOffsetX + cropsize[0] / 2.0, mapOffsetY + cropsize[1] / 2.0] # this is the center of the minimap image, in pixel coordinates - imageCenter = [(self.map_width/2), self.map_height/2] + imageCenter = [(self.map_width / 2), self.map_height / 2] # this is the scaling factor to go from the SC2 coordinate # system to pixel coordinates self.image_scale = float(self.map_height) / cropsize[1] - self.transX =imageCenter[0] + self.image_scale * (mapCenter[0]) + self.transX = imageCenter[0] + self.image_scale * (mapCenter[0]) self.transY = imageCenter[1] + self.image_scale * (mapCenter[1]) - def radius_to_map_positions(self,radius): + def radius_to_map_positions(self, radius): ## this function converts all radius into map coordinates ## centred around the origin that the creep can exist ## the cgu_radius_to_map_position function will simply ## substract every coordinate with the centre point of the tumour output_coordinates = list() # Sample a square area using the radius - for x in range (-radius,radius): - for y in range (-radius, radius): - if (x**2 + y**2) <= (radius * radius): - output_coordinates.append((x,y)) + for x in range(-radius, radius): + for y in range(-radius, radius): + if (x ** 2 + y ** 2) <= (radius * radius): + output_coordinates.append((x, y)) return output_coordinates def init_cgu_lists(self, player_id): @@ -169,139 +184,155 @@ def init_cgu_lists(self, player_id): self.creep_gen_units[player_id] = list() self.creep_gen_units_times[player_id] = list() - def add_to_list(self,player_id,unit_id,unit_location,unit_type,event_time): - # This functions adds a new time frame to creep_generating_units_list - # Each time frame contains a list of all CGUs that are alive + def add_to_list(self, player_id, unit_id, unit_location, unit_type, event_time): + # This functions adds a new time frame to creep_generating_units_list + # Each time frame contains a list of all CGUs that are alive length_cgu_list = len(self.creep_gen_units[player_id]) - if length_cgu_list==0: - self.creep_gen_units[player_id].append([(unit_id, unit_location,unit_type)]) + if length_cgu_list == 0: + self.creep_gen_units[player_id].append( + [(unit_id, unit_location, unit_type)] + ) self.creep_gen_units_times[player_id].append(event_time) else: - #if the list is not empty, take the previous time frame, + # if the list is not empty, take the previous time frame, # add the new CGU to it and append it as a new time frame - previous_list = self.creep_gen_units[player_id][length_cgu_list-1][:] - previous_list.append((unit_id, unit_location,unit_type)) + previous_list = self.creep_gen_units[player_id][length_cgu_list - 1][:] + previous_list.append((unit_id, unit_location, unit_type)) self.creep_gen_units[player_id].append(previous_list) self.creep_gen_units_times[player_id].append(event_time) - def remove_from_list(self,unit_id,time_frame): + def remove_from_list(self, unit_id, time_frame): ## This function searches is given a unit ID for every unit who died ## the unit id will be searched in cgu_gen_units for matches ## if there are any, that unit will be removed from active CGUs ## and appended as a new time frame for player_id in self.creep_gen_units: length_cgu_list = len(self.creep_gen_units[player_id]) - if length_cgu_list ==0: - break - cgu_per_player = self.creep_gen_units[player_id] [length_cgu_list-1] - creep_generating_died =filter(lambda x:x[0]==unit_id,cgu_per_player) + if length_cgu_list == 0: + break + cgu_per_player = self.creep_gen_units[player_id][length_cgu_list - 1] + creep_generating_died = filter(lambda x: x[0] == unit_id, cgu_per_player) for creep_generating_died_unit in creep_generating_died: - new_cgu_per_player = list(filter(lambda x:x != creep_generating_died_unit, cgu_per_player )) + new_cgu_per_player = list( + filter(lambda x: x != creep_generating_died_unit, cgu_per_player) + ) self.creep_gen_units[player_id].append(new_cgu_per_player) self.creep_gen_units_times[player_id].append(time_frame) - def cgu_gen_times_to_chunks(self,cgu_time_list): + def cgu_gen_times_to_chunks(self, cgu_time_list): ## this function returns the index and value of every cgu time maximum_cgu_time = max(cgu_time_list) for i in range(0, maximum_cgu_time): - a = list(filter(lambda x_y: x_y[1]//60==i , enumerate(cgu_time_list))) - if len(a)>0: + a = list(filter(lambda x_y: x_y[1] // 60 == i, enumerate(cgu_time_list))) + if len(a) > 0: yield a - def cgu_in_min_to_cgu_units(self,player_id,cgu_in_minutes): - ## this function takes index and value of CGU times and returns - ## the cgu units with the maximum length + def cgu_in_min_to_cgu_units(self, player_id, cgu_in_minutes): + ## this function takes index and value of CGU times and returns + ## the cgu units with the maximum length for cgu_per_minute in cgu_in_minutes: - indexes = map(lambda x:x[0], cgu_per_minute) + indexes = map(lambda x: x[0], cgu_per_minute) cgu_units = list() for index in indexes: cgu_units.append(self.creep_gen_units[player_id][index]) - cgu_max_in_minute = max(cgu_units,key = len) + cgu_max_in_minute = max(cgu_units, key=len) yield cgu_max_in_minute - def reduce_cgu_per_minute(self,player_id): - #the creep_gen_units_lists contains every single time frame - #where a CGU is added, - #To reduce the calculations required, the time frame containing - #the most cgus every minute will be used to represent that minute - cgu_per_minute1, cgu_per_minute2 = tee (self.cgu_gen_times_to_chunks(self.creep_gen_units_times[player_id])) - cgu_unit_max_per_minute = self.cgu_in_min_to_cgu_units(player_id,cgu_per_minute1) - minutes = map(lambda x:int(x[0][1]//60)*60, cgu_per_minute2) - self.creep_gen_units[player_id] = list(cgu_unit_max_per_minute) - self.creep_gen_units_times[player_id] = list(minutes) + def reduce_cgu_per_minute(self, player_id): + # the creep_gen_units_lists contains every single time frame + # where a CGU is added, + # To reduce the calculations required, the time frame containing + # the most cgus every minute will be used to represent that minute + cgu_per_minute1, cgu_per_minute2 = tee( + self.cgu_gen_times_to_chunks(self.creep_gen_units_times[player_id]) + ) + cgu_unit_max_per_minute = self.cgu_in_min_to_cgu_units( + player_id, cgu_per_minute1 + ) + minutes = map(lambda x: int(x[0][1] // 60) * 60, cgu_per_minute2) + self.creep_gen_units[player_id] = list(cgu_unit_max_per_minute) + self.creep_gen_units_times[player_id] = list(minutes) - def get_creep_spread_area(self,player_id): + def get_creep_spread_area(self, player_id): ## iterates through all cgus and and calculate the area - for index,cgu_per_player in enumerate(self.creep_gen_units[player_id]): + for index, cgu_per_player in enumerate(self.creep_gen_units[player_id]): # convert cgu list into centre of circles and radius - cgu_radius = map(lambda x: (x[1], self.unit_name_to_radius[x[2]]),\ - cgu_per_player) + cgu_radius = map( + lambda x: (x[1], self.unit_name_to_radius[x[2]]), cgu_per_player + ) # convert event coords to minimap coords cgu_radius = self.convert_cgu_radius_event_to_map_coord(cgu_radius) - creep_area_positions = self.cgu_radius_to_map_positions(cgu_radius,self.radius_to_coordinates) + creep_area_positions = self.cgu_radius_to_map_positions( + cgu_radius, self.radius_to_coordinates + ) cgu_event_time = self.creep_gen_units_times[player_id][index] - cgu_event_time_str=str(int(cgu_event_time//60))+":"+str(cgu_event_time%60) + cgu_event_time_str = ( + str(int(cgu_event_time // 60)) + ":" + str(cgu_event_time % 60) + ) if self.debug: - self.print_image(creep_area_positions,player_id,cgu_event_time_str) + self.print_image(creep_area_positions, player_id, cgu_event_time_str) creep_area = len(creep_area_positions) - self.creep_spread_by_minute[player_id][cgu_event_time]=\ - float(creep_area)/self.mapSize*100 + self.creep_spread_by_minute[player_id][cgu_event_time] = ( + float(creep_area) / self.mapSize * 100 + ) return self.creep_spread_by_minute[player_id] - def cgu_radius_to_map_positions(self,cgu_radius,radius_to_coordinates): - ## This function uses the output of radius_to_map_positions + def cgu_radius_to_map_positions(self, cgu_radius, radius_to_coordinates): + ## This function uses the output of radius_to_map_positions total_points_on_map = Set() - if len(cgu_radius)==0: + if len(cgu_radius) == 0: return [] for cgu in cgu_radius: point = cgu[0] radius = cgu[1] ## subtract all radius_to_coordinates with centre of ## cgu radius to change centre of circle - cgu_map_position = map( lambda x:(x[0]+point[0],x[1]+point[1])\ - ,self.radius_to_coordinates[radius]) - total_points_on_map= total_points_on_map | Set(cgu_map_position) + cgu_map_position = map( + lambda x: (x[0] + point[0], x[1] + point[1]), + self.radius_to_coordinates[radius], + ) + total_points_on_map = total_points_on_map | Set(cgu_map_position) return total_points_on_map - def print_image(self,total_points_on_map,player_id,time_stamp): + def print_image(self, total_points_on_map, player_id, time_stamp): minimap_copy = self.minimap_image.copy() # Convert all creeped points to white for points in total_points_on_map: x = points[0] y = points[1] - x,y = self.check_image_pixel_within_boundary(x,y) - minimap_copy.putpixel((x,y) , (255, 255, 255)) + x, y = self.check_image_pixel_within_boundary(x, y) + minimap_copy.putpixel((x, y), (255, 255, 255)) creeped_image = minimap_copy # write creeped minimap image to a string as a png creeped_imageIO = StringIO() creeped_image.save(creeped_imageIO, "png") - self.creep_spread_image_by_minute[player_id][time_stamp]=creeped_imageIO + self.creep_spread_image_by_minute[player_id][time_stamp] = creeped_imageIO ##debug for print out the images - f = open(str(player_id)+'image'+time_stamp+'.png','w') + f = open(str(player_id) + "image" + time_stamp + ".png", "w") f.write(creeped_imageIO.getvalue()) creeped_imageIO.close() f.close() - def check_image_pixel_within_boundary(self,pointX, pointY): - pointX = 0 if pointX <0 else pointX - pointY=0 if pointY <0 else pointY + def check_image_pixel_within_boundary(self, pointX, pointY): + pointX = 0 if pointX < 0 else pointX + pointY = 0 if pointY < 0 else pointY # put a minus 1 to make sure the pixel is not directly on the edge - pointX = int(self.map_width-1 if pointX >= self.map_width else pointX) - pointY = int(self.map_height-1 if pointY >= self.map_height else pointY) - return pointX,pointY + pointX = int(self.map_width - 1 if pointX >= self.map_width else pointX) + pointY = int(self.map_height - 1 if pointY >= self.map_height else pointY) + return pointX, pointY - def convert_cgu_radius_event_to_map_coord(self,cgu_radius): + def convert_cgu_radius_event_to_map_coord(self, cgu_radius): cgu_radius_new = list() for cgu in cgu_radius: x = cgu[0][0] y = cgu[0][1] - (x,y) = self.convert_event_coord_to_map_coord(x,y) - cgu = ((x,y),cgu[1]) + (x, y) = self.convert_event_coord_to_map_coord(x, y) + cgu = ((x, y), cgu[1]) cgu_radius_new.append(cgu) return cgu_radius_new - def convert_event_coord_to_map_coord(self,x,y): + def convert_event_coord_to_map_coord(self, x, y): imageX = int(self.map_width - self.transX + self.image_scale * x) imageY = int(self.transY - self.image_scale * y) return imageX, imageY diff --git a/sc2reader/engine/plugins/gameheart.py b/sc2reader/engine/plugins/gameheart.py index 08bfc67e..1e412031 100644 --- a/sc2reader/engine/plugins/gameheart.py +++ b/sc2reader/engine/plugins/gameheart.py @@ -23,7 +23,8 @@ class GameHeartNormalizer(object): * They are all 1v1's. * You can't random in GameHeart """ - name = 'GameHeartNormalizer' + + name = "GameHeartNormalizer" PRIMARY_BUILDINGS = dict(Hatchery="Zerg", Nexus="Protoss", CommandCenter="Terran") @@ -38,14 +39,20 @@ def handleInitGame(self, event, replay): for event in replay.tracker_events: if start_frame != -1 and event.frame > start_frame + 5: # fuzz it a little break - if event.name == 'UnitBornEvent' and event.control_pid and event.unit_type_name in self.PRIMARY_BUILDINGS: + if ( + event.name == "UnitBornEvent" + and event.control_pid + and event.unit_type_name in self.PRIMARY_BUILDINGS + ): # In normal replays, starting units are born on frame zero. if event.frame == 0: yield PluginExit(self, code=0, details=dict()) return else: start_frame = event.frame - actual_players[event.control_pid] = self.PRIMARY_BUILDINGS[event.unit_type_name] + actual_players[event.control_pid] = self.PRIMARY_BUILDINGS[ + event.unit_type_name + ] self.fix_entities(replay, actual_players) self.fix_events(replay, start_frame) @@ -53,8 +60,12 @@ def handleInitGame(self, event, replay): replay.frames -= start_frame replay.game_length = Length(seconds=replay.frames / 16) replay.real_type = get_real_type(replay.teams) - replay.real_length = Length(seconds=int(replay.game_length.seconds/GAME_SPEED_FACTOR[replay.speed])) - replay.start_time = datetime.utcfromtimestamp(replay.unix_timestamp-replay.real_length.seconds) + replay.real_length = Length( + seconds=int(replay.game_length.seconds / GAME_SPEED_FACTOR[replay.speed]) + ) + replay.start_time = datetime.utcfromtimestamp( + replay.unix_timestamp - replay.real_length.seconds + ) def fix_events(self, replay, start_frame): # Set back the game clock for all events @@ -70,8 +81,8 @@ def fix_entities(self, replay, actual_players): # Change the players that aren't playing into observers for p in [p for p in replay.players if p.pid not in actual_players]: # Fix the slot data to be accurate - p.slot_data['observe'] = 1 - p.slot_data['team_id'] = None + p.slot_data["observe"] = 1 + p.slot_data["team_id"] = None obs = Observer(p.sid, p.slot_data, p.uid, p.init_data, p.pid) # Because these obs start the game as players the client @@ -103,7 +114,7 @@ def fix_entities(self, replay, actual_players): replay.team = dict() replay.teams = list() for index, player in enumerate(replay.players): - team_id = index+1 + team_id = index + 1 team = Team(team_id) replay.team[team_id] = team replay.teams.append(team) @@ -113,5 +124,5 @@ def fix_entities(self, replay, actual_players): player.play_race = player.pick_race team.players = [player] team.result = player.result - if team.result == 'Win': + if team.result == "Win": replay.winner = team diff --git a/sc2reader/engine/plugins/selection.py b/sc2reader/engine/plugins/selection.py index 69aa12a7..9f741894 100644 --- a/sc2reader/engine/plugins/selection.py +++ b/sc2reader/engine/plugins/selection.py @@ -23,7 +23,8 @@ class SelectionTracker(object): # TODO: list a few error inducing sitations """ - name = 'SelectionTracker' + + name = "SelectionTracker" def handleInitGame(self, event, replay): for person in replay.entities: @@ -34,7 +35,9 @@ def handleInitGame(self, event, replay): def handleSelectionEvent(self, event, replay): selection = event.player.selection[event.control_group] - new_selection, error = self._deselect(selection, event.mask_type, event.mask_data) + new_selection, error = self._deselect( + selection, event.mask_type, event.mask_data + ) new_selection = self._select(new_selection, event.objects) event.player.selection[event.control_group] = new_selection if error: @@ -42,7 +45,9 @@ def handleSelectionEvent(self, event, replay): def handleGetControlGroupEvent(self, event, replay): selection = event.player.selection[event.control_group] - new_selection, error = self._deselect(selection, event.mask_type, event.mask_data) + new_selection, error = self._deselect( + selection, event.mask_type, event.mask_data + ) event.player.selection[10] = new_selection if error: event.player.selection_errors += 1 @@ -52,36 +57,38 @@ def handleSetControlGroupEvent(self, event, replay): def handleAddToControlGroupEvent(self, event, replay): selection = event.player.selection[event.control_group] - new_selection, error = self._deselect(selection, event.mask_type, event.mask_data) + new_selection, error = self._deselect( + selection, event.mask_type, event.mask_data + ) new_selection = self._select(new_selection, event.player.selection[10]) event.player.selection[event.control_group] = new_selection if error: event.player.selection_errors += 1 def _select(self, selection, units): - return sorted(set(selection+units)) + return sorted(set(selection + units)) def _deselect(self, selection, mode, data): """Returns false if there was a data error when deselecting""" - if mode == 'None': + if mode == "None": return selection, False selection_size, data_size = len(selection), len(data) - if mode == 'Mask': + if mode == "Mask": # Deselect objects according to deselect mask sfilter = lambda bit_u: not bit_u[0] - mask = data+[False]*(selection_size-data_size) + mask = data + [False] * (selection_size - data_size) new_selection = [u for (bit, u) in filter(sfilter, zip(mask, selection))] error = data_size > selection_size - elif mode == 'OneIndices': + elif mode == "OneIndices": # Deselect objects according to indexes clean_data = list(filter(lambda i: i < selection_size, data)) new_selection = [u for i, u in enumerate(selection) if i < selection_size] error = len(list(filter(lambda i: i >= selection_size, data))) != 0 - elif mode == 'ZeroIndices': + elif mode == "ZeroIndices": # Select objects according to indexes clean_data = list(filter(lambda i: i < selection_size, data)) new_selection = [selection[i] for i in clean_data] diff --git a/sc2reader/engine/plugins/supply.py b/sc2reader/engine/plugins/supply.py index 394a3a64..c106f757 100644 --- a/sc2reader/engine/plugins/supply.py +++ b/sc2reader/engine/plugins/supply.py @@ -3,85 +3,158 @@ from collections import defaultdict + class SupplyTracker(object): - def add_to_units_alive(self,event,replay): + def add_to_units_alive(self, event, replay): unit_name = event.unit_type_name - if unit_name in self.unit_name_to_supply: + if unit_name in self.unit_name_to_supply: supplyCount = self.unit_name_to_supply[event.unit_type_name][0] buildTime = self.unit_name_to_supply[event.unit_type_name][1] - time_built = event.second - buildTime - time_built= 0 if time_built < 0 else time_built + time_built = event.second - buildTime + time_built = 0 if time_built < 0 else time_built new_unit = (supplyCount, event.unit_id) self.units_alive[event.control_pid].append(new_unit) total_supply = sum([x[0] for x in self.units_alive[event.control_pid]]) - replay.players[event.control_pid-1].current_food_used[time_built]= total_supply - print("Second",time_built,replay.players[event.control_pid-1],"SUPPLY",replay.players[event.control_pid-1].current_food_used[time_built]) - + replay.players[event.control_pid - 1].current_food_used[ + time_built + ] = total_supply + print( + "Second", + time_built, + replay.players[event.control_pid - 1], + "SUPPLY", + replay.players[event.control_pid - 1].current_food_used[time_built], + ) + elif unit_name in self.supply_gen_unit: - ## see if the unit provides supply + ## see if the unit provides supply supply_gen_count = self.supply_gen_unit[event.unit_type_name][0] build_time = self.supply_gen_unit[event.unit_type_name][1] - time_complete = event.second+ build_time - supply_gen_unit = (supply_gen_count,event.unit_id) + time_complete = event.second + build_time + supply_gen_unit = (supply_gen_count, event.unit_id) self.supply_gen[event.control_pid].append(supply_gen_unit) total_supply_gen = sum([x[0] for x in self.supply_gen[event.control_pid]]) - replay.players[event.control_pid-1].current_food_made[time_complete]= total_supply_gen - print("Second",time_complete, replay.players[event.control_pid-1],"Built",replay.players[event.control_pid-1].current_food_made[time_complete]) + replay.players[event.control_pid - 1].current_food_made[ + time_complete + ] = total_supply_gen + print( + "Second", + time_complete, + replay.players[event.control_pid - 1], + "Built", + replay.players[event.control_pid - 1].current_food_made[time_complete], + ) else: print("Unit name {0} does not exist".format(event.unit_type_name)) return - def remove_from_units_alive(self,event,replay): + def remove_from_units_alive(self, event, replay): died_unit_id = event.unit_id for player in replay.player: - dead_unit = filter(lambda x:x[1]==died_unit_id,self.units_alive[player]) + dead_unit = filter(lambda x: x[1] == died_unit_id, self.units_alive[player]) if dead_unit: self.units_alive[player].remove(dead_unit[0]) total_supply = sum([x[0] for x in self.units_alive[player]]) - - replay.players[player-1].current_food_used[event.second] = total_supply - print("Second", event.second, "Killed", event.unit.name,"SUPPLY",replay.players[player-1].current_food_used[event.second]) - - dead_supply_gen=filter(lambda x:x[1]==died_unit_id, self.supply_gen[player]) + + replay.players[player - 1].current_food_used[ + event.second + ] = total_supply + print( + "Second", + event.second, + "Killed", + event.unit.name, + "SUPPLY", + replay.players[player - 1].current_food_used[event.second], + ) + + dead_supply_gen = filter( + lambda x: x[1] == died_unit_id, self.supply_gen[player] + ) if dead_supply_gen: self.supply_gen[player].remove(dead_supply_gen[0]) total_supply_gen = sum([x[0] for x in self.supply_gen[player]]) - replay.players[player-1].current_food_made[event.second] = total_supply_gen - print("Second", event.second, "Killed", event.unit.name,"SUPPLY",replay.players[player-1].current_food_made[event.second]) + replay.players[player - 1].current_food_made[ + event.second + ] = total_supply_gen + print( + "Second", + event.second, + "Killed", + event.unit.name, + "SUPPLY", + replay.players[player - 1].current_food_made[event.second], + ) def handleInitGame(self, event, replay): ## This dictionary contains te supply of every unit - self.unit_name_to_supply = { - #Zerg - "Drone":(1,17),"Zergling":(1,25),"Baneling":(0,20),"Queen":(2,50),\ - "Hydralisk":(2,33),"Roach":(2,27),"Infestor":(2,50),"Mutalisk":(2,33),\ - "Corruptor":(2,40),"Utralisk":(6,55),"Broodlord":(2,34),\ - "SwarmHost":(3,40), "Viper":(3,40),\ - #Terran - "SCV":(1,17),"Marine":(1,25),"Marauder":(2,30),"SiegeTank":(2,45),\ - "Reaper":(1,45),"Ghost":(2,40),"Hellion":(2,30),"Thor":(6,60),\ - "Viking":(2,42),"Medivac":(2,42),"Raven":(2,60), "Banshee":(3,60),\ - "Battlecruiser":(6,90), "Hellbat":(2,30),"WidowMine":(2,40),\ - #Protoss - "Probe":(1,17),"Zealot":(2,38),"Stalker":(2,42),"Sentry":(2,42),\ - "Observer":(1,30), "Immortal":(4,55),"WarpPrism":(2,50),\ - "Colossus":(6,75), "Phoenix":(2,35),"VoidRay":(4,60), \ - "HighTemplar":(2,55),"DarkTemplar":(2,55), "Archon":(4,12),\ - "Carrier":(6,120), "Mothership":(6,100),"MothershipCore":(2,30),\ - "Oracle":(3,50),"Tempest":(4,60)} - + self.unit_name_to_supply = { + # Zerg + "Drone": (1, 17), + "Zergling": (1, 25), + "Baneling": (0, 20), + "Queen": (2, 50), + "Hydralisk": (2, 33), + "Roach": (2, 27), + "Infestor": (2, 50), + "Mutalisk": (2, 33), + "Corruptor": (2, 40), + "Utralisk": (6, 55), + "Broodlord": (2, 34), + "SwarmHost": (3, 40), + "Viper": (3, 40), + # Terran + "SCV": (1, 17), + "Marine": (1, 25), + "Marauder": (2, 30), + "SiegeTank": (2, 45), + "Reaper": (1, 45), + "Ghost": (2, 40), + "Hellion": (2, 30), + "Thor": (6, 60), + "Viking": (2, 42), + "Medivac": (2, 42), + "Raven": (2, 60), + "Banshee": (3, 60), + "Battlecruiser": (6, 90), + "Hellbat": (2, 30), + "WidowMine": (2, 40), + # Protoss + "Probe": (1, 17), + "Zealot": (2, 38), + "Stalker": (2, 42), + "Sentry": (2, 42), + "Observer": (1, 30), + "Immortal": (4, 55), + "WarpPrism": (2, 50), + "Colossus": (6, 75), + "Phoenix": (2, 35), + "VoidRay": (4, 60), + "HighTemplar": (2, 55), + "DarkTemplar": (2, 55), + "Archon": (4, 12), + "Carrier": (6, 120), + "Mothership": (6, 100), + "MothershipCore": (2, 30), + "Oracle": (3, 50), + "Tempest": (4, 60), + } + self.supply_gen_unit = { - #overlord build time is zero because event for units are made when + # overlord build time is zero because event for units are made when # it is born not when it's created - "Overlord":(8,0),"Hatchery":(2,100), \ - "SupplyDepot":(8,30),"CommandCenter":(11,100),\ - "Pylon":(8,25),"Nexus":(10,100) + "Overlord": (8, 0), + "Hatchery": (2, 100), + "SupplyDepot": (8, 30), + "CommandCenter": (11, 100), + "Pylon": (8, 25), + "Nexus": (10, 100), } - ## This list contains a turple of the units supply and unit ID. + ## This list contains a turple of the units supply and unit ID. ## the purpose of the list is to know which user owns which unit - ## so that when a unit dies, that + ## so that when a unit dies, that self.units_alive = dict() - ## + ## self.supply_gen = dict() for player in replay.players: self.supply_gen[player.pid] = list() @@ -90,20 +163,24 @@ def handleInitGame(self, event, replay): player.current_food_made = defaultdict(int) player.time_supply_capped = int() - def handleUnitInitEvent(self,event,replay): - #print ("Init",event.unit_type_name, event.unit_id) - self.add_to_units_alive(event,replay) + def handleUnitInitEvent(self, event, replay): + # print ("Init",event.unit_type_name, event.unit_id) + self.add_to_units_alive(event, replay) - def handleUnitBornEvent(self,event,replay): - #print ("Born",event.unit_type_name,event.unit_id) - self.add_to_units_alive(event,replay) + def handleUnitBornEvent(self, event, replay): + # print ("Born",event.unit_type_name,event.unit_id) + self.add_to_units_alive(event, replay) - def handleUnitDiedEvent(self,event,replay): + def handleUnitDiedEvent(self, event, replay): if event.unit.name not in self.unit_name_to_supply: return - self.remove_from_units_alive(event,replay) + self.remove_from_units_alive(event, replay) def handleEndGame(self, event, replay): for player in replay.players: - player.current_food_used = sorted(player.current_food_used.iteritems(), key=lambda x: x[0]) - player.current_food_made = sorted(player.current_food_made.iteritems(), key=lambda x:x[0]) + player.current_food_used = sorted( + player.current_food_used.iteritems(), key=lambda x: x[0] + ) + player.current_food_made = sorted( + player.current_food_made.iteritems(), key=lambda x: x[0] + ) diff --git a/sc2reader/engine/utils.py b/sc2reader/engine/utils.py index e3a9a18c..c8ca4af5 100644 --- a/sc2reader/engine/utils.py +++ b/sc2reader/engine/utils.py @@ -31,7 +31,7 @@ def __getitem__(self, frame): else: # Copy the previous state and use it as our basis here state = self[prev_frame] - if hasattr(state, 'copy'): + if hasattr(state, "copy"): state = state.copy() self[frame] = state diff --git a/sc2reader/events/base.py b/sc2reader/events/base.py index dbea286c..89f16c5e 100644 --- a/sc2reader/events/base.py +++ b/sc2reader/events/base.py @@ -3,4 +3,4 @@ class Event(object): - name = 'Event' + name = "Event" diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 7eadebfb..9dda98b5 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -13,6 +13,7 @@ class GameEvent(Event): """ This is the base class for all game events. The attributes below are universally available. """ + def __init__(self, frame, pid): #: The id of the player generating the event. This is 16 for global non-player events. #: Prior to Heart of the Swarm this was the player id. Since HotS it is @@ -31,16 +32,18 @@ def __init__(self, frame, pid): self.second = frame >> 4 #: A flag indicating if it is a local or global event. - self.is_local = (pid != 16) + self.is_local = pid != 16 #: Short cut string for event class name self.name = self.__class__.__name__ def _str_prefix(self): - if getattr(self, 'pid', 16) == 16: + if getattr(self, "pid", 16) == 16: player_name = "Global" elif self.player and not self.player.name: - player_name = "Player {0} - ({1})".format(self.player.pid, self.player.play_race) + player_name = "Player {0} - ({1})".format( + self.player.pid, self.player.play_race + ) elif self.player: player_name = self.player.name else: @@ -56,6 +59,7 @@ class GameStartEvent(GameEvent): Recorded when the game starts and the frames start to roll. This is a global non-player event. """ + def __init__(self, frame, pid, data): super(GameStartEvent, self).__init__(frame, pid) @@ -67,6 +71,7 @@ class PlayerLeaveEvent(GameEvent): """ Recorded when a player leaves the game. """ + def __init__(self, frame, pid, data): super(PlayerLeaveEvent, self).__init__(frame, pid) @@ -79,48 +84,51 @@ class UserOptionsEvent(GameEvent): This event is recorded for each player at the very beginning of the game before the :class:`GameStartEvent`. """ + def __init__(self, frame, pid, data): super(UserOptionsEvent, self).__init__(frame, pid) #: - self.game_fully_downloaded = data['game_fully_downloaded'] + self.game_fully_downloaded = data["game_fully_downloaded"] #: - self.development_cheats_enabled = data['development_cheats_enabled'] + self.development_cheats_enabled = data["development_cheats_enabled"] #: - self.multiplayer_cheats_enabled = data['multiplayer_cheats_enabled'] + self.multiplayer_cheats_enabled = data["multiplayer_cheats_enabled"] #: - self.sync_checksumming_enabled = data['sync_checksumming_enabled'] + self.sync_checksumming_enabled = data["sync_checksumming_enabled"] #: - self.is_map_to_map_transition = data['is_map_to_map_transition'] + self.is_map_to_map_transition = data["is_map_to_map_transition"] #: - self.use_ai_beacons = data['use_ai_beacons'] + self.use_ai_beacons = data["use_ai_beacons"] #: Are workers sent to auto-mine on game start - self.starting_rally = data['starting_rally'] if 'starting_rally' in data else None + self.starting_rally = ( + data["starting_rally"] if "starting_rally" in data else None + ) #: - self.debug_pause_enabled = data['debug_pause_enabled'] + self.debug_pause_enabled = data["debug_pause_enabled"] #: - self.base_build_num = data['base_build_num'] + self.base_build_num = data["base_build_num"] def create_command_event(frame, pid, data): - ability_type = data['data'][0] - if ability_type == 'None': + ability_type = data["data"][0] + if ability_type == "None": return BasicCommandEvent(frame, pid, data) - elif ability_type == 'TargetUnit': + elif ability_type == "TargetUnit": return TargetUnitCommandEvent(frame, pid, data) - elif ability_type == 'TargetPoint': + elif ability_type == "TargetPoint": return TargetPointCommandEvent(frame, pid, data) - elif ability_type == 'Data': + elif ability_type == "Data": return DataCommandEvent(frame, pid, data) @@ -135,11 +143,12 @@ class CommandEvent(GameEvent): See :class:`TargetPointCommandEvent`, :class:`TargetUnitCommandEvent`, and :class:`DataCommandEvent` for individual details. """ + def __init__(self, frame, pid, data): super(CommandEvent, self).__init__(frame, pid) #: Flags on the command??? - self.flags = data['flags'] + self.flags = data["flags"] #: A dictionary of possible ability flags. Flags are: #: @@ -192,16 +201,20 @@ def __init__(self, frame, pid, data): ) #: Flag marking that the command had ability information - self.has_ability = data['ability'] is not None + self.has_ability = data["ability"] is not None #: Link the the ability group - self.ability_link = data['ability']['ability_link'] if self.has_ability else 0 + self.ability_link = data["ability"]["ability_link"] if self.has_ability else 0 #: The index of the ability in the ability group - self.command_index = data['ability']['ability_command_index'] if self.has_ability else 0 + self.command_index = ( + data["ability"]["ability_command_index"] if self.has_ability else 0 + ) #: Additional ability data. - self.ability_data = data['ability']['ability_command_data'] if self.has_ability else 0 + self.ability_data = ( + data["ability"]["ability_command_data"] if self.has_ability else 0 + ) #: Unique identifier for the ability self.ability_id = self.ability_link << 5 | self.command_index @@ -210,16 +223,16 @@ def __init__(self, frame, pid, data): self.ability = None #: A shortcut to the name of the ability being used - self.ability_name = '' + self.ability_name = "" #: The type of ability, one of: None (no target), TargetPoint, TargetUnit, or Data - self.ability_type = data['data'][0] + self.ability_type = data["data"][0] #: The raw data associated with this ability type - self.ability_type_data = data['data'][1] + self.ability_type_data = data["data"][1] #: Other unit id?? - self.other_unit_id = data['other_unit_tag'] + self.other_unit_id = data["other_unit_tag"] #: A reference to the other unit self.other_unit = None @@ -233,10 +246,12 @@ def __str__(self): else: string += "Right Click" - if self.ability_type == 'TargetUnit': - string += "; Target: {0} [{1:0>8X}]".format(self.target.name, self.target_unit_id) + if self.ability_type == "TargetUnit": + string += "; Target: {0} [{1:0>8X}]".format( + self.target.name, self.target_unit_id + ) - if self.ability_type in ('TargetPoint', 'TargetUnit'): + if self.ability_type in ("TargetPoint", "TargetUnit"): string += "; Location: {0}".format(str(self.location)) return string @@ -251,6 +266,7 @@ class BasicCommandEvent(CommandEvent): Note that like all CommandEvents, the event will be recorded regardless of whether or not the command was successful. """ + def __init__(self, frame, pid, data): super(BasicCommandEvent, self).__init__(frame, pid, data) @@ -266,17 +282,18 @@ class TargetPointCommandEvent(CommandEvent): Note that like all CommandEvents, the event will be recorded regardless of whether or not the command was successful. """ + def __init__(self, frame, pid, data): super(TargetPointCommandEvent, self).__init__(frame, pid, data) #: The x coordinate of the target. Available for TargetPoint and TargetUnit type events. - self.x = self.ability_type_data['point'].get('x', 0) / 4096.0 + self.x = self.ability_type_data["point"].get("x", 0) / 4096.0 #: The y coordinate of the target. Available for TargetPoint and TargetUnit type events. - self.y = self.ability_type_data['point'].get('y', 0) / 4096.0 + self.y = self.ability_type_data["point"].get("y", 0) / 4096.0 #: The z coordinate of the target. Available for TargetPoint and TargetUnit type events. - self.z = self.ability_type_data['point'].get('z', 0) + self.z = self.ability_type_data["point"].get("z", 0) #: The location of the target. Available for TargetPoint and TargetUnit type events self.location = (self.x, self.y, self.z) @@ -293,18 +310,19 @@ class TargetUnitCommandEvent(CommandEvent): Note that like all CommandEvents, the event will be recorded regardless of whether or not the command was successful. """ + def __init__(self, frame, pid, data): super(TargetUnitCommandEvent, self).__init__(frame, pid, data) #: Flags set on the target unit. Available for TargetUnit type events - self.target_flags = self.ability_type_data.get('flags', None) + self.target_flags = self.ability_type_data.get("flags", None) #: Timer?? Available for TargetUnit type events. - self.target_timer = self.ability_type_data.get('timer', None) + self.target_timer = self.ability_type_data.get("timer", None) #: Unique id of the target unit. Available for TargetUnit type events. #: This id can be 0 when the target unit is shrouded by fog of war. - self.target_unit_id = self.ability_type_data.get('unit_tag', None) + self.target_unit_id = self.ability_type_data.get("unit_tag", None) #: A reference to the targetted unit. When the :attr:`target_unit_id` is #: 0 this target unit is a generic, reused fog of war unit of the :attr:`target_unit_type` @@ -312,27 +330,28 @@ def __init__(self, frame, pid, data): self.target_unit = None #: Current integer type id of the target unit. Available for TargetUnit type events. - self.target_unit_type = self.ability_type_data.get('unit_link', None) + self.target_unit_type = self.ability_type_data.get("unit_link", None) #: Integer player id of the controlling player. Available for TargetUnit type events starting in 19595. #: When the targetted unit is under fog of war this id is zero. - self.control_player_id = self.ability_type_data.get('control_player_id', None) + self.control_player_id = self.ability_type_data.get("control_player_id", None) #: Integer player id of the player paying upkeep. Available for TargetUnit type events. - self.upkeep_player_id = self.ability_type_data.get('upkeep_player_id', None) + self.upkeep_player_id = self.ability_type_data.get("upkeep_player_id", None) #: The x coordinate of the target. Available for TargetPoint and TargetUnit type events. - self.x = self.ability_type_data['point'].get('x', 0) / 4096.0 + self.x = self.ability_type_data["point"].get("x", 0) / 4096.0 #: The y coordinate of the target. Available for TargetPoint and TargetUnit type events. - self.y = self.ability_type_data['point'].get('y', 0) / 4096.0 + self.y = self.ability_type_data["point"].get("y", 0) / 4096.0 #: The z coordinate of the target. Available for TargetPoint and TargetUnit type events. - self.z = self.ability_type_data['point'].get('z', 0) + self.z = self.ability_type_data["point"].get("z", 0) #: The location of the target. Available for TargetPoint and TargetUnit type events self.location = (self.x, self.y, self.z) + class UpdateTargetPointCommandEvent(TargetPointCommandEvent): """ Extends :class: 'TargetPointCommandEvent' @@ -342,7 +361,9 @@ class UpdateTargetPointCommandEvent(TargetPointCommandEvent): instances of this occurring. """ - name = 'UpdateTargetPointCommandEvent' + + name = "UpdateTargetPointCommandEvent" + class UpdateTargetUnitCommandEvent(TargetUnitCommandEvent): """ @@ -357,7 +378,7 @@ class UpdateTargetUnitCommandEvent(TargetUnitCommandEvent): holding shift, and then shift clicking on a second hatchery. """ - name = 'UpdateTargetUnitCommandEvent' + name = "UpdateTargetUnitCommandEvent" class DataCommandEvent(CommandEvent): @@ -370,11 +391,12 @@ class DataCommandEvent(CommandEvent): Note that like all CommandEvents, the event will be recorded regardless of whether or not the command was successful. """ + def __init__(self, frame, pid, data): super(DataCommandEvent, self).__init__(frame, pid, data) #: Other target data. Available for Data type events. - self.target_data = self.ability_type_data.get('data', None) + self.target_data = self.ability_type_data.get("data", None) @loggable @@ -389,37 +411,83 @@ class SelectionEvent(GameEvent): by non-player actions. When a player action updates a control group a :class:`ControlGroupEvent` is generated. """ + def __init__(self, frame, pid, data): super(SelectionEvent, self).__init__(frame, pid) #: The control group being modified. 10 for active selection - self.control_group = data['control_group_index'] + self.control_group = data["control_group_index"] #: Deprecated, use control_group self.bank = self.control_group #: ??? - self.subgroup_index = data['subgroup_index'] + self.subgroup_index = data["subgroup_index"] #: The type of mask to apply. One of None, Mask, OneIndices, ZeroIndices - self.mask_type = data['remove_mask'][0] + self.mask_type = data["remove_mask"][0] #: The data for the mask - self.mask_data = data['remove_mask'][1] + self.mask_data = data["remove_mask"][1] #: The unit type data for the new units - self.new_unit_types = [(d['unit_link'], d['subgroup_priority'], d['intra_subgroup_priority'], d['count']) for d in data['add_subgroups']] + self.new_unit_types = [ + ( + d["unit_link"], + d["subgroup_priority"], + d["intra_subgroup_priority"], + d["count"], + ) + for d in data["add_subgroups"] + ] #: The unit id data for the new units - self.new_unit_ids = data['add_unit_tags'] + self.new_unit_ids = data["add_unit_tags"] # This stretches out the unit types and priorities to be zipped with ids. - unit_types = chain(*[[utype]*count for (utype, subgroup_priority, intra_subgroup_priority, count) in self.new_unit_types]) - unit_subgroup_priorities = chain(*[[subgroup_priority]*count for (utype, subgroup_priority, intra_subgroup_priority, count) in self.new_unit_types]) - unit_intra_subgroup_priorities = chain(*[[intra_subgroup_priority]*count for (utype, subgroup_priority, intra_subgroup_priority, count) in self.new_unit_types]) + unit_types = chain( + *[ + [utype] * count + for ( + utype, + subgroup_priority, + intra_subgroup_priority, + count, + ) in self.new_unit_types + ] + ) + unit_subgroup_priorities = chain( + *[ + [subgroup_priority] * count + for ( + utype, + subgroup_priority, + intra_subgroup_priority, + count, + ) in self.new_unit_types + ] + ) + unit_intra_subgroup_priorities = chain( + *[ + [intra_subgroup_priority] * count + for ( + utype, + subgroup_priority, + intra_subgroup_priority, + count, + ) in self.new_unit_types + ] + ) #: The combined type and id information for new units - self.new_unit_info = list(zip(self.new_unit_ids, unit_types, unit_subgroup_priorities, unit_intra_subgroup_priorities)) + self.new_unit_info = list( + zip( + self.new_unit_ids, + unit_types, + unit_subgroup_priorities, + unit_intra_subgroup_priorities, + ) + ) #: A list of references to units added by this selection self.new_units = None @@ -429,13 +497,13 @@ def __init__(self, frame, pid, data): def __str__(self): if self.new_units: - return GameEvent.__str__(self)+str([str(u) for u in self.new_units]) + return GameEvent.__str__(self) + str([str(u) for u in self.new_units]) else: - return GameEvent.__str__(self)+str([str(u) for u in self.new_unit_info]) + return GameEvent.__str__(self) + str([str(u) for u in self.new_unit_info]) def create_control_group_event(frame, pid, data): - update_type = data['control_group_update'] + update_type = data["control_group_update"] if update_type == 0: return SetControlGroupEvent(frame, pid, data) elif update_type == 1: @@ -464,11 +532,12 @@ class ControlGroupEvent(GameEvent): All three events have the same set of data (shown below) but are interpretted differently. See the class entry for details. """ + def __init__(self, frame, pid, data): super(ControlGroupEvent, self).__init__(frame, pid) #: Index to the control group being modified - self.control_group = data['control_group_index'] + self.control_group = data["control_group_index"] #: Deprecated, use control_group self.bank = self.control_group @@ -477,13 +546,13 @@ def __init__(self, frame, pid, data): self.hotkey = self.control_group #: The type of update being performed, 0 (set),1 (add),2 (get) - self.update_type = data['control_group_update'] + self.update_type = data["control_group_update"] #: The type of mask to apply. One of None, Mask, OneIndices, ZeroIndices - self.mask_type = data['remove_mask'][0] + self.mask_type = data["remove_mask"][0] #: The data for the mask - self.mask_data = data['remove_mask'][1] + self.mask_data = data["remove_mask"][1] class SetControlGroupEvent(ControlGroupEvent): @@ -521,29 +590,32 @@ class CameraEvent(GameEvent): It does not matter why the camera changed, this event simply records the current state of the camera after changing. """ + def __init__(self, frame, pid, data): super(CameraEvent, self).__init__(frame, pid) #: The x coordinate of the center of the camera - self.x = (data['target']['x'] if data['target'] is not None else 0)/256.0 + self.x = (data["target"]["x"] if data["target"] is not None else 0) / 256.0 #: The y coordinate of the center of the camera - self.y = (data['target']['y'] if data['target'] is not None else 0)/256.0 + self.y = (data["target"]["y"] if data["target"] is not None else 0) / 256.0 #: The location of the center of the camera self.location = (self.x, self.y) #: The distance to the camera target ?? - self.distance = data['distance'] + self.distance = data["distance"] #: The current pitch of the camera - self.pitch = data['pitch'] + self.pitch = data["pitch"] #: The current yaw of the camera - self.yaw = data['yaw'] + self.yaw = data["yaw"] def __str__(self): - return self._str_prefix() + "{0} at ({1}, {2})".format(self.name, self.x, self.y) + return self._str_prefix() + "{0} at ({1}, {2})".format( + self.name, self.x, self.y + ) @loggable @@ -552,6 +624,7 @@ class ResourceTradeEvent(GameEvent): Generated when a player trades resources with another player. But not when fullfulling resource requests. """ + def __init__(self, frame, pid, data): super(ResourceTradeEvent, self).__init__(frame, pid) @@ -562,13 +635,13 @@ def __init__(self, frame, pid, data): self.sender = None #: The id of the player receiving the resources - self.recipient_id = data['recipient_id'] + self.recipient_id = data["recipient_id"] #: A reference to the player receiving the resources self.recipient = None #: An array of resources sent - self.resources = data['resources'] + self.resources = data["resources"] #: Amount minerals sent self.minerals = self.resources[0] if len(self.resources) >= 1 else None @@ -583,18 +656,21 @@ def __init__(self, frame, pid, data): self.custom_resource = self.resources[3] if len(self.resources) >= 4 else None def __str__(self): - return self._str_prefix() + " transfer {0} minerals, {1} gas, {2} terrazine, and {3} custom to {4}".format(self.minerals, self.vespene, self.terrazine, self.custom, self.recipient) + return self._str_prefix() + " transfer {0} minerals, {1} gas, {2} terrazine, and {3} custom to {4}".format( + self.minerals, self.vespene, self.terrazine, self.custom, self.recipient + ) class ResourceRequestEvent(GameEvent): """ Generated when a player creates a resource request. """ + def __init__(self, frame, pid, data): super(ResourceRequestEvent, self).__init__(frame, pid) #: An array of resources sent - self.resources = data['resources'] + self.resources = data["resources"] #: Amount minerals sent self.minerals = self.resources[0] if len(self.resources) >= 1 else None @@ -609,40 +685,45 @@ def __init__(self, frame, pid, data): self.custom_resource = self.resources[3] if len(self.resources) >= 4 else None def __str__(self): - return self._str_prefix() + " requests {0} minerals, {1} gas, {2} terrazine, and {3} custom".format(self.minerals, self.vespene, self.terrazine, self.custom) + return self._str_prefix() + " requests {0} minerals, {1} gas, {2} terrazine, and {3} custom".format( + self.minerals, self.vespene, self.terrazine, self.custom + ) class ResourceRequestFulfillEvent(GameEvent): """ Generated when a player accepts a resource request. """ + def __init__(self, frame, pid, data): super(ResourceRequestFulfillEvent, self).__init__(frame, pid) #: The id of the request being fulfilled - self.request_id = data['request_id'] + self.request_id = data["request_id"] class ResourceRequestCancelEvent(GameEvent): """ Generated when a player cancels their resource request. """ + def __init__(self, frame, pid, data): super(ResourceRequestCancelEvent, self).__init__(frame, pid) #: The id of the request being cancelled - self.request_id = data['request_id'] + self.request_id = data["request_id"] class HijackReplayGameEvent(GameEvent): """ Generated when players take over from a replay. """ + def __init__(self, frame, pid, data): super(HijackReplayGameEvent, self).__init__(frame, pid) #: The method used. Not sure what 0/1 represent - self.method = data['method'] + self.method = data["method"] #: Information on the users hijacking the game - self.user_infos = data['user_infos'] + self.user_infos = data["user_infos"] diff --git a/sc2reader/events/message.py b/sc2reader/events/message.py index e9859733..2fdb9cfd 100644 --- a/sc2reader/events/message.py +++ b/sc2reader/events/message.py @@ -11,6 +11,7 @@ class MessageEvent(Event): """ Parent class for all message events. """ + def __init__(self, frame, pid): #: The user id (or player id for older replays) of the person that generated the event. self.pid = pid @@ -25,7 +26,7 @@ def __init__(self, frame, pid): self.name = self.__class__.__name__ def _str_prefix(self): - player_name = self.player.name if getattr(self, 'pid', 16) != 16 else "Global" + player_name = self.player.name if getattr(self, "pid", 16) != 16 else "Global" return "{0}\t{1:<15} ".format(Length(seconds=int(self.frame / 16)), player_name) def __str__(self): @@ -37,6 +38,7 @@ class ChatEvent(MessageEvent): """ Records in-game chat events. """ + def __init__(self, frame, pid, target, text): super(ChatEvent, self).__init__(frame, pid) #: The numerical target type. 0 = to all; 2 = to allies; 4 = to observers. @@ -46,13 +48,13 @@ def __init__(self, frame, pid, target, text): self.text = text #: Flag marked true of message was to all. - self.to_all = (self.target == 0) + self.to_all = self.target == 0 #: Flag marked true of message was to allies. - self.to_allies = (self.target == 2) + self.to_allies = self.target == 2 #: Flag marked true of message was to observers. - self.to_observers = (self.target == 4) + self.to_observers = self.target == 4 @loggable @@ -60,6 +62,7 @@ class ProgressEvent(MessageEvent): """ Sent during the load screen to update load process for other clients. """ + def __init__(self, frame, pid, progress): super(ProgressEvent, self).__init__(frame, pid) @@ -72,6 +75,7 @@ class PingEvent(MessageEvent): """ Records pings made by players in game. """ + def __init__(self, frame, pid, target, x, y): super(PingEvent, self).__init__(frame, pid) @@ -79,13 +83,13 @@ def __init__(self, frame, pid, target, x, y): self.target = target #: Flag marked true of message was to all. - self.to_all = (self.target == 0) + self.to_all = self.target == 0 #: Flag marked true of message was to allies. - self.to_allies = (self.target == 2) + self.to_allies = self.target == 2 #: Flag marked true of message was to observers. - self.to_observers = (self.target == 4) + self.to_observers = self.target == 4 #: The x coordinate of the target location self.x = x diff --git a/sc2reader/events/tracker.py b/sc2reader/events/tracker.py index fe3e6f78..985a9107 100644 --- a/sc2reader/events/tracker.py +++ b/sc2reader/events/tracker.py @@ -13,10 +13,11 @@ class TrackerEvent(Event): """ Parent class for all tracker events. """ + def __init__(self, frames): #: The frame of the game this event was applied #: Ignore all but the lowest 32 bits of the frame - self.frame = frames % 2**32 + self.frame = frames % 2 ** 32 #: The second of the game (game time not real time) this event was applied self.second = self.frame >> 4 @@ -36,6 +37,7 @@ def __str__(self): class PlayerSetupEvent(TrackerEvent): """ Sent during game setup to help us organize players better """ + def __init__(self, frames, data, build): super(PlayerSetupEvent, self).__init__(frames) @@ -64,6 +66,7 @@ class PlayerStatsEvent(TrackerEvent): In 1v1 games, the above behavior can cause the losing player to have 2 events generated at the end of the game. One for leaving and one for the end of the game. """ + def __init__(self, frames, data, build): super(PlayerStatsEvent, self).__init__(frames) @@ -101,7 +104,11 @@ def __init__(self, frames, data, build): self.minerals_used_in_progress_technology = clamp(self.stats[7]) #: The total mineral cost of all things in progress - self.minerals_used_in_progress = self.minerals_used_in_progress_army + self.minerals_used_in_progress_economy + self.minerals_used_in_progress_technology + self.minerals_used_in_progress = ( + self.minerals_used_in_progress_army + + self.minerals_used_in_progress_economy + + self.minerals_used_in_progress_technology + ) #: The total vespene cost of army units (buildings?) currently being built/queued self.vespene_used_in_progress_army = clamp(self.stats[8]) @@ -113,10 +120,16 @@ def __init__(self, frames, data, build): self.vespene_used_in_progress_technology = clamp(self.stats[10]) #: The total vespene cost of all things in progress - self.vespene_used_in_progress = self.vespene_used_in_progress_army + self.vespene_used_in_progress_economy + self.vespene_used_in_progress_technology + self.vespene_used_in_progress = ( + self.vespene_used_in_progress_army + + self.vespene_used_in_progress_economy + + self.vespene_used_in_progress_technology + ) #: The total cost of all things in progress - self.resources_used_in_progress = self.minerals_used_in_progress + self.vespene_used_in_progress + self.resources_used_in_progress = ( + self.minerals_used_in_progress + self.vespene_used_in_progress + ) #: The total mineral cost of current army units (buildings?) self.minerals_used_current_army = clamp(self.stats[11]) @@ -128,7 +141,11 @@ def __init__(self, frames, data, build): self.minerals_used_current_technology = clamp(self.stats[13]) #: The total mineral cost of all current things - self.minerals_used_current = self.minerals_used_current_army + self.minerals_used_current_economy + self.minerals_used_current_technology + self.minerals_used_current = ( + self.minerals_used_current_army + + self.minerals_used_current_economy + + self.minerals_used_current_technology + ) #: The total vespene cost of current army units (buildings?) self.vespene_used_current_army = clamp(self.stats[14]) @@ -140,10 +157,16 @@ def __init__(self, frames, data, build): self.vespene_used_current_technology = clamp(self.stats[16]) #: The total vepsene cost of all current things - self.vespene_used_current = self.vespene_used_current_army + self.vespene_used_current_economy + self.vespene_used_current_technology + self.vespene_used_current = ( + self.vespene_used_current_army + + self.vespene_used_current_economy + + self.vespene_used_current_technology + ) #: The total cost of all things current - self.resources_used_current = self.minerals_used_current + self.vespene_used_current + self.resources_used_current = ( + self.minerals_used_current + self.vespene_used_current + ) #: The total mineral cost of all army units (buildings?) lost self.minerals_lost_army = clamp(self.stats[17]) @@ -155,7 +178,11 @@ def __init__(self, frames, data, build): self.minerals_lost_technology = clamp(self.stats[19]) #: The total mineral cost of all lost things - self.minerals_lost = self.minerals_lost_army + self.minerals_lost_economy + self.minerals_lost_technology + self.minerals_lost = ( + self.minerals_lost_army + + self.minerals_lost_economy + + self.minerals_lost_technology + ) #: The total vespene cost of all army units (buildings?) lost self.vespene_lost_army = clamp(self.stats[20]) @@ -167,7 +194,11 @@ def __init__(self, frames, data, build): self.vespene_lost_technology = clamp(self.stats[22]) #: The total vepsene cost of all lost things - self.vespene_lost = self.vespene_lost_army + self.vespene_lost_economy + self.vespene_lost_technology + self.vespene_lost = ( + self.vespene_lost_army + + self.vespene_lost_economy + + self.vespene_lost_technology + ) #: The total resource cost of all lost things self.resources_lost = self.minerals_lost + self.vespene_lost @@ -182,7 +213,11 @@ def __init__(self, frames, data, build): self.minerals_killed_technology = clamp(self.stats[25]) #: The total mineral value of all killed things - self.minerals_killed = self.minerals_killed_army + self.minerals_killed_economy + self.minerals_killed_technology + self.minerals_killed = ( + self.minerals_killed_army + + self.minerals_killed_economy + + self.minerals_killed_technology + ) #: The total vespene value of enemy army units (buildings?) killed self.vespene_killed_army = clamp(self.stats[26]) @@ -194,7 +229,11 @@ def __init__(self, frames, data, build): self.vespene_killed_technology = clamp(self.stats[28]) #: The total vespene cost of all killed things - self.vespene_killed = self.vespene_killed_army + self.vespene_killed_economy + self.vespene_killed_technology + self.vespene_killed = ( + self.vespene_killed_army + + self.vespene_killed_economy + + self.vespene_killed_technology + ) #: The total resource cost of all killed things self.resources_killed = self.minerals_killed + self.vespene_killed @@ -215,10 +254,14 @@ def __init__(self, frames, data, build): self.ff_minerals_lost_army = clamp(self.stats[33]) if build >= 26490 else None #: Minerals of economy value lost to friendly fire - self.ff_minerals_lost_economy = clamp(self.stats[34]) if build >= 26490 else None + self.ff_minerals_lost_economy = ( + clamp(self.stats[34]) if build >= 26490 else None + ) #: Minerals of technology value lost to friendly fire - self.ff_minerals_lost_technology = clamp(self.stats[35]) if build >= 26490 else None + self.ff_minerals_lost_technology = ( + clamp(self.stats[35]) if build >= 26490 else None + ) #: Vespene of army value lost to friendly fire self.ff_vespene_lost_army = clamp(self.stats[36]) if build >= 26490 else None @@ -227,7 +270,9 @@ def __init__(self, frames, data, build): self.ff_vespene_lost_economy = clamp(self.stats[37]) if build >= 26490 else None #: Vespene of technology value lost to friendly fire - self.ff_vespene_lost_technology = clamp(self.stats[38]) if build >= 26490 else None + self.ff_vespene_lost_technology = ( + clamp(self.stats[38]) if build >= 26490 else None + ) def __str__(self): return self._str_prefix() + "{0: >15} - Stats Update".format(str(self.player)) @@ -244,6 +289,7 @@ class UnitBornEvent(TrackerEvent): :class:`~sc2reader.event.game.CommandEvent` game events where the command is a train unit command. """ + def __init__(self, frames, data, build): super(UnitBornEvent, self).__init__(frames) @@ -260,7 +306,7 @@ def __init__(self, frames, data, build): self.unit = None #: The unit type name of the unit being born - self.unit_type_name = data[2].decode('utf8') + self.unit_type_name = data[2].decode("utf8") #: The id of the player that controls this unit. self.control_pid = data[3] @@ -291,7 +337,9 @@ def __init__(self, frames, data, build): self.location = (self.x, self.y) def __str__(self): - return self._str_prefix() + "{0: >15} - Unit born {1}".format(str(self.unit_upkeeper), self.unit) + return self._str_prefix() + "{0: >15} - Unit born {1}".format( + str(self.unit_upkeeper), self.unit + ) class UnitDiedEvent(TrackerEvent): @@ -299,6 +347,7 @@ class UnitDiedEvent(TrackerEvent): Generated when a unit dies or is removed from the game for any reason. Reasons include morphing, merging, and getting killed. """ + def __init__(self, frames, data, build): super(UnitDiedEvent, self).__init__(frames) @@ -358,10 +407,14 @@ def __init__(self, frames, data, build): self.killing_unit_index = data[5] self.killing_unit_recycle = data[6] if self.killing_unit_index: - self.killing_unit_id = self.killing_unit_index << 18 | self.killing_unit_recycle + self.killing_unit_id = ( + self.killing_unit_index << 18 | self.killing_unit_recycle + ) def __str__(self): - return self._str_prefix() + "{0: >15} - Unit died {1}.".format(str(self.unit.owner), self.unit) + return self._str_prefix() + "{0: >15} - Unit died {1}.".format( + str(self.unit.owner), self.unit + ) class UnitOwnerChangeEvent(TrackerEvent): @@ -369,6 +422,7 @@ class UnitOwnerChangeEvent(TrackerEvent): Generated when either ownership or control of a unit is changed. Neural Parasite is an example of an action that would generate this event. """ + def __init__(self, frames, data, build): super(UnitOwnerChangeEvent, self).__init__(frames) @@ -397,7 +451,9 @@ def __init__(self, frames, data, build): self.unit_controller = None def __str__(self): - return self._str_prefix() + "{0: >15} took {1}".format(str(self.unit_upkeeper), self.unit) + return self._str_prefix() + "{0: >15} took {1}".format( + str(self.unit_upkeeper), self.unit + ) class UnitTypeChangeEvent(TrackerEvent): @@ -406,6 +462,7 @@ class UnitTypeChangeEvent(TrackerEvent): Lair, Hive) and mode switches (Sieging Tanks, Phasing prisms, Burrowing roaches). There may be some other situations where a unit transformation is a type change and not a new unit. """ + def __init__(self, frames, data, build): super(UnitTypeChangeEvent, self).__init__(frames) @@ -422,16 +479,19 @@ def __init__(self, frames, data, build): self.unit = None #: The the new unit type name - self.unit_type_name = data[2].decode('utf8') + self.unit_type_name = data[2].decode("utf8") def __str__(self): - return self._str_prefix() + "{0: >15} - Unit {0} type changed to {1}".format(str(self.unit.owner), self.unit, self.unit_type_name) + return self._str_prefix() + "{0: >15} - Unit {0} type changed to {1}".format( + str(self.unit.owner), self.unit, self.unit_type_name + ) class UpgradeCompleteEvent(TrackerEvent): """ Generated when a player completes an upgrade. """ + def __init__(self, frames, data, build): super(UpgradeCompleteEvent, self).__init__(frames) @@ -442,13 +502,15 @@ def __init__(self, frames, data, build): self.player = None #: The name of the upgrade - self.upgrade_type_name = data[1].decode('utf8') + self.upgrade_type_name = data[1].decode("utf8") #: The number of times this upgrade as been researched self.count = data[2] def __str__(self): - return self._str_prefix() + "{0: >15} - {1} upgrade completed".format(str(self.player), self.upgrade_type_name) + return self._str_prefix() + "{0: >15} - {1} upgrade completed".format( + str(self.player), self.upgrade_type_name + ) class UnitInitEvent(TrackerEvent): @@ -457,6 +519,7 @@ class UnitInitEvent(TrackerEvent): initiated. This applies only to units which are started in game before they are finished. Primary examples being buildings and warp-in units. """ + def __init__(self, frames, data, build): super(UnitInitEvent, self).__init__(frames) @@ -473,7 +536,7 @@ def __init__(self, frames, data, build): self.unit = None #: The the new unit type name - self.unit_type_name = data[2].decode('utf8') + self.unit_type_name = data[2].decode("utf8") #: The id of the player that controls this unit. self.control_pid = data[3] @@ -504,7 +567,9 @@ def __init__(self, frames, data, build): self.location = (self.x, self.y) def __str__(self): - return self._str_prefix() + "{0: >15} - Unit initiated {1}".format(str(self.unit_upkeeper), self.unit) + return self._str_prefix() + "{0: >15} - Unit initiated {1}".format( + str(self.unit_upkeeper), self.unit + ) class UnitDoneEvent(TrackerEvent): @@ -512,6 +577,7 @@ class UnitDoneEvent(TrackerEvent): The counter part to the :class:`UnitInitEvent`, generated by the game engine when an initiated unit is completed. E.g. warp-in finished, building finished, morph complete. """ + def __init__(self, frames, data, build): super(UnitDoneEvent, self).__init__(frames) @@ -528,7 +594,9 @@ def __init__(self, frames, data, build): self.unit = None def __str__(self): - return self._str_prefix() + "{0: >15} - Unit {1} done".format(str(self.unit.owner), self.unit) + return self._str_prefix() + "{0: >15} - Unit {1} done".format( + str(self.unit.owner), self.unit + ) class UnitPositionsEvent(TrackerEvent): @@ -537,6 +605,7 @@ class UnitPositionsEvent(TrackerEvent): the last interval. If more than 255 units were damaged, then the first 255 are reported and the remaining units are carried into the next interval. """ + def __init__(self, frames, data, build): super(UnitPositionsEvent, self).__init__(frames) diff --git a/sc2reader/exceptions.py b/sc2reader/exceptions.py index caf13b86..dff2d31b 100644 --- a/sc2reader/exceptions.py +++ b/sc2reader/exceptions.py @@ -27,7 +27,7 @@ class MultipleMatchingFilesError(SC2ReaderError): class ReadError(SC2ReaderError): - def __init__(self, msg, type, location, replay=None, game_events=[], buffer=None): + def __init__(self, msg, type, location, replay=None, game_events=[], buffer=None): self.__dict__.update(locals()) super(ReadError, self).__init__(msg) diff --git a/sc2reader/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index ad465609..f7669ef7 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -6,7 +6,12 @@ from sc2reader import log_utils from sc2reader.utils import Length -from sc2reader.factories.plugins.utils import PlayerSelection, GameState, JSONDateEncoder, plugin +from sc2reader.factories.plugins.utils import ( + PlayerSelection, + GameState, + JSONDateEncoder, + plugin, +) @plugin @@ -23,70 +28,78 @@ def toDict(replay): observers = list() for observer in replay.observers: messages = list() - for message in getattr(observer, 'messages', list()): - messages.append({ - 'time': message.time.seconds, - 'text': message.text, - 'is_public': message.to_all - }) - observers.append({ - 'name': getattr(observer, 'name', None), - 'pid': getattr(observer, 'pid', None), - 'messages': messages, - }) + for message in getattr(observer, "messages", list()): + messages.append( + { + "time": message.time.seconds, + "text": message.text, + "is_public": message.to_all, + } + ) + observers.append( + { + "name": getattr(observer, "name", None), + "pid": getattr(observer, "pid", None), + "messages": messages, + } + ) # Build players into dictionary players = list() for player in replay.players: messages = list() for message in player.messages: - messages.append({ - 'time': message.time.seconds, - 'text': message.text, - 'is_public': message.to_all - }) - players.append({ - 'avg_apm': getattr(player, 'avg_apm', None), - 'color': player.color.__dict__ if hasattr(player, 'color') else None, - 'handicap': getattr(player, 'handicap', None), - 'name': getattr(player, 'name', None), - 'pick_race': getattr(player, 'pick_race', None), - 'pid': getattr(player, 'pid', None), - 'play_race': getattr(player, 'play_race', None), - 'result': getattr(player, 'result', None), - 'type': getattr(player, 'type', None), - 'uid': getattr(player, 'uid', None), - 'url': getattr(player, 'url', None), - 'messages': messages, - }) + messages.append( + { + "time": message.time.seconds, + "text": message.text, + "is_public": message.to_all, + } + ) + players.append( + { + "avg_apm": getattr(player, "avg_apm", None), + "color": player.color.__dict__ if hasattr(player, "color") else None, + "handicap": getattr(player, "handicap", None), + "name": getattr(player, "name", None), + "pick_race": getattr(player, "pick_race", None), + "pid": getattr(player, "pid", None), + "play_race": getattr(player, "play_race", None), + "result": getattr(player, "result", None), + "type": getattr(player, "type", None), + "uid": getattr(player, "uid", None), + "url": getattr(player, "url", None), + "messages": messages, + } + ) # Consolidate replay metadata into dictionary return { - 'region': getattr(replay, 'region', None), - 'map_name': getattr(replay, 'map_name', None), - 'file_time': getattr(replay, 'file_time', None), - 'filehash': getattr(replay, 'filehash', None), - 'unix_timestamp': getattr(replay, 'unix_timestamp', None), - 'date': getattr(replay, 'date', None), - 'utc_date': getattr(replay, 'utc_date', None), - 'speed': getattr(replay, 'speed', None), - 'category': getattr(replay, 'category', None), - 'type': getattr(replay, 'type', None), - 'is_ladder': getattr(replay, 'is_ladder', False), - 'is_private': getattr(replay, 'is_private', False), - 'filename': getattr(replay, 'filename', None), - 'file_time': getattr(replay, 'file_time', None), - 'frames': getattr(replay, 'frames', None), - 'build': getattr(replay, 'build', None), - 'release': getattr(replay, 'release_string', None), - 'game_fps': getattr(replay, 'game_fps', None), - 'game_length': getattr(getattr(replay, 'game_length', None), 'seconds', None), - 'players': players, - 'observers': observers, - 'real_length': getattr(getattr(replay, 'real_length', None), 'seconds', None), - 'real_type': getattr(replay, 'real_type', None), - 'time_zone': getattr(replay, 'time_zone', None), - 'versions': getattr(replay, 'versions', None) + "region": getattr(replay, "region", None), + "map_name": getattr(replay, "map_name", None), + "file_time": getattr(replay, "file_time", None), + "filehash": getattr(replay, "filehash", None), + "unix_timestamp": getattr(replay, "unix_timestamp", None), + "date": getattr(replay, "date", None), + "utc_date": getattr(replay, "utc_date", None), + "speed": getattr(replay, "speed", None), + "category": getattr(replay, "category", None), + "type": getattr(replay, "type", None), + "is_ladder": getattr(replay, "is_ladder", False), + "is_private": getattr(replay, "is_private", False), + "filename": getattr(replay, "filename", None), + "file_time": getattr(replay, "file_time", None), + "frames": getattr(replay, "frames", None), + "build": getattr(replay, "build", None), + "release": getattr(replay, "release_string", None), + "game_fps": getattr(replay, "game_fps", None), + "game_length": getattr(getattr(replay, "game_length", None), "seconds", None), + "players": players, + "observers": observers, + "real_length": getattr(getattr(replay, "real_length", None), "seconds", None), + "real_type": getattr(replay, "real_type", None), + "time_zone": getattr(replay, "time_zone", None), + "versions": getattr(replay, "versions", None), } @@ -106,23 +119,30 @@ def APMTracker(replay): player.seconds_played = replay.length.seconds for event in player.events: - if event.name == 'SelectionEvent' or 'CommandEvent' in event.name or 'ControlGroup' in event.name: + if ( + event.name == "SelectionEvent" + or "CommandEvent" in event.name + or "ControlGroup" in event.name + ): player.aps[event.second] += 1.4 - player.apm[int(event.second/60)] += 1.4 + player.apm[int(event.second / 60)] += 1.4 - elif event.name == 'PlayerLeaveEvent': + elif event.name == "PlayerLeaveEvent": player.seconds_played = event.second if len(player.apm) > 0: - player.avg_apm = sum(player.aps.values())/float(player.seconds_played)*60 + player.avg_apm = ( + sum(player.aps.values()) / float(player.seconds_played) * 60 + ) else: player.avg_apm = 0 return replay + @plugin def SelectionTracker(replay): - debug = replay.opt['debug'] + debug = replay.opt["debug"] logger = log_utils.get_logger(SelectionTracker) for person in replay.entities: @@ -131,37 +151,60 @@ def SelectionTracker(replay): player_selections = GameState(PlayerSelection()) for event in person.events: error = False - if event.name == 'SelectionEvent': + if event.name == "SelectionEvent": selections = player_selections[event.frame] control_group = selections[event.control_group].copy() error = not control_group.deselect(event.mask_type, event.mask_data) control_group.select(event.new_units) selections[event.control_group] = control_group if debug: - logger.info("[{0}] {1} selected {2} units: {3}".format(Length(seconds=event.second), person.name, len(selections[0x0A].objects), selections[0x0A])) - - elif event.name == 'SetControlGroupEvent': + logger.info( + "[{0}] {1} selected {2} units: {3}".format( + Length(seconds=event.second), + person.name, + len(selections[0x0A].objects), + selections[0x0A], + ) + ) + + elif event.name == "SetControlGroupEvent": selections = player_selections[event.frame] selections[event.control_group] = selections[0x0A].copy() if debug: - logger.info("[{0}] {1} set hotkey {2} to current selection".format(Length(seconds=event.second), person.name, event.hotkey)) + logger.info( + "[{0}] {1} set hotkey {2} to current selection".format( + Length(seconds=event.second), person.name, event.hotkey + ) + ) - elif event.name == 'AddToControlGroupEvent': + elif event.name == "AddToControlGroupEvent": selections = player_selections[event.frame] control_group = selections[event.control_group].copy() error = not control_group.deselect(event.mask_type, event.mask_data) control_group.select(selections[0x0A].objects) selections[event.control_group] = control_group if debug: - logger.info("[{0}] {1} added current selection to hotkey {2}".format(Length(seconds=event.second), person.name, event.hotkey)) + logger.info( + "[{0}] {1} added current selection to hotkey {2}".format( + Length(seconds=event.second), person.name, event.hotkey + ) + ) - elif event.name == 'GetControlGroupEvent': + elif event.name == "GetControlGroupEvent": selections = player_selections[event.frame] control_group = selections[event.control_group].copy() error = not control_group.deselect(event.mask_type, event.mask_data) selections[0xA] = control_group if debug: - logger.info("[{0}] {1} retrieved hotkey {2}, {3} units: {4}".format(Length(seconds=event.second), person.name, event.control_group, len(selections[0x0A].objects), selections[0x0A])) + logger.info( + "[{0}] {1} retrieved hotkey {2}, {3} units: {4}".format( + Length(seconds=event.second), + person.name, + event.control_group, + len(selections[0x0A].objects), + selections[0x0A], + ) + ) else: continue @@ -172,7 +215,11 @@ def SelectionTracker(replay): if error: person.selection_errors += 1 if debug: - logger.warn("Error detected in deselection mode {0}.".format(event.mask_type)) + logger.warn( + "Error detected in deselection mode {0}.".format( + event.mask_type + ) + ) person.selection = player_selections # Not a real lock, so don't change it! diff --git a/sc2reader/factories/plugins/utils.py b/sc2reader/factories/plugins/utils.py index cf3559ea..e472b86e 100644 --- a/sc2reader/factories/plugins/utils.py +++ b/sc2reader/factories/plugins/utils.py @@ -18,7 +18,9 @@ def call(*args, **kwargs): opt = kwargs.copy() opt.update(options) return func(*args, **opt) + return call + return wrapper @@ -56,7 +58,7 @@ def __getitem__(self, frame): else: # Copy the previous state and use it as our basis here state = self[prev_frame] - if hasattr(state, 'copy'): + if hasattr(state, "copy"): state = state.copy() self[frame] = state @@ -76,34 +78,41 @@ def __init__(self, objects=None): self.objects = objects or list() def select(self, new_objects): - new_set = set(self.objects+list(new_objects)) + new_set = set(self.objects + list(new_objects)) self.objects = sorted(new_set, key=lambda obj: obj.id) def deselect(self, mode, data): """Returns false if there was a data error when deselecting""" size = len(self.objects) - if mode == 'None': + if mode == "None": return True - elif mode == 'Mask': + elif mode == "Mask": """ Deselect objects according to deselect mask """ mask = data if len(mask) < size: # pad to the right - mask = mask+[False]*(len(self.objects)-len(mask)) + mask = mask + [False] * (len(self.objects) - len(mask)) self.logger.debug("Deselection Mask: {0}".format(mask)) - self.objects = [obj for (slct, obj) in filter(lambda slct_obj: not slct_obj[0], zip(mask, self.objects))] + self.objects = [ + obj + for (slct, obj) in filter( + lambda slct_obj: not slct_obj[0], zip(mask, self.objects) + ) + ] return len(mask) <= size - elif mode == 'OneIndices': + elif mode == "OneIndices": """ Deselect objects according to indexes """ clean_data = list(filter(lambda i: i < size, data)) - self.objects = [self.objects[i] for i in range(len(self.objects)) if i not in clean_data] + self.objects = [ + self.objects[i] for i in range(len(self.objects)) if i not in clean_data + ] return len(clean_data) == len(data) - elif mode == 'ZeroIndices': + elif mode == "ZeroIndices": """ Deselect objects according to indexes """ clean_data = list(filter(lambda i: i < size, data)) self.objects = [self.objects[i] for i in clean_data] @@ -113,7 +122,7 @@ def deselect(self, mode, data): return False def __str__(self): - return ', '.join(str(obj) for obj in self.objects) + return ", ".join(str(obj) for obj in self.objects) def copy(self): return UnitSelection(self.objects[:]) diff --git a/sc2reader/factories/sc2factory.py b/sc2reader/factories/sc2factory.py index 36020b2d..690797a3 100644 --- a/sc2reader/factories/sc2factory.py +++ b/sc2reader/factories/sc2factory.py @@ -64,8 +64,8 @@ class SC2Factory(object): _resource_name_map = dict(replay=Replay, map=Map) default_options = { - Resource: {'debug': False}, - Replay: {'load_level': 4, 'load_map': False}, + Resource: {"debug": False}, + Replay: {"load_level": 4, "load_map": False}, } def __init__(self, **options): @@ -86,7 +86,9 @@ def load_replay(self, source, options=None, **new_options): def load_replays(self, sources, options=None, **new_options): """Loads a collection of sc2replay files, returns a generator.""" - return self.load_all(Replay, sources, options, extension='SC2Replay', **new_options) + return self.load_all( + Replay, sources, options, extension="SC2Replay", **new_options + ) def load_localization(self, source, options=None, **new_options): """Loads a single s2ml file. Accepts file path, url, or file object.""" @@ -94,7 +96,9 @@ def load_localization(self, source, options=None, **new_options): def load_localizations(self, sources, options=None, **new_options): """Loads a collection of s2ml files, returns a generator.""" - return self.load_all(Localization, sources, options, extension='s2ml', **new_options) + return self.load_all( + Localization, sources, options, extension="s2ml", **new_options + ) def load_map(self, source, options=None, **new_options): """Loads a single s2ma file. Accepts file path, url, or file object.""" @@ -102,7 +106,7 @@ def load_map(self, source, options=None, **new_options): def load_maps(self, sources, options=None, **new_options): """Loads a collection of s2ma files, returns a generator.""" - return self.load_all(Map, sources, options, extension='s2ma', **new_options) + return self.load_all(Map, sources, options, extension="s2ma", **new_options) def load_game_summary(self, source, options=None, **new_options): """Loads a single s2gs file. Accepts file path, url, or file object.""" @@ -110,7 +114,9 @@ def load_game_summary(self, source, options=None, **new_options): def load_game_summaries(self, sources, options=None, **new_options): """Loads a collection of s2gs files, returns a generator.""" - return self.load_all(GameSummary, sources, options, extension='s2gs', **new_options) + return self.load_all( + GameSummary, sources, options, extension="s2gs", **new_options + ) def configure(self, cls=None, **options): """ Configures the factory to use the supplied options. If cls is specified @@ -144,7 +150,7 @@ def load_all(self, cls, sources, options=None, **new_options): # Internal Functions def _load(self, cls, resource, filename, options): obj = cls(resource, filename=filename, factory=self, **options) - for plugin in options.get('plugins', self._get_plugins(cls)): + for plugin in options.get("plugins", self._get_plugins(cls)): obj = plugin(obj) return obj @@ -180,7 +186,7 @@ def load_remote_resource_contents(self, resource, **options): def load_local_resource_contents(self, location, **options): # Extract the contents so we can close the file - with open(location, 'rb') as resource_file: + with open(location, "rb") as resource_file: return resource_file.read() def _load_resource(self, resource, options=None, **new_options): @@ -191,11 +197,11 @@ def _load_resource(self, resource, options=None, **new_options): resource = resource.url if isinstance(resource, basestring): - if re.match(r'https?://', resource): + if re.match(r"https?://", resource): contents = self.load_remote_resource_contents(resource, **options) else: - directory = options.get('directory', '') + directory = options.get("directory", "") location = os.path.join(directory, resource) contents = self.load_local_resource_contents(location, **options) @@ -206,31 +212,32 @@ def _load_resource(self, resource, options=None, **new_options): else: # Totally not designed for large files!! # We need a multiread resource, so wrap it in BytesIO - if not hasattr(resource, 'seek'): + if not hasattr(resource, "seek"): resource = BytesIO(resource.read()) - resource_name = getattr(resource, 'name', 'Unknown') + resource_name = getattr(resource, "name", "Unknown") - if options.get('verbose', None): + if options.get("verbose", None): print(resource_name) return (resource, resource_name) class CachedSC2Factory(SC2Factory): - def get_remote_cache_key(self, remote_resource): # Strip the port and use the domain as the bucket # and use the full path as the key parseresult = urlparse(remote_resource) - bucket = re.sub(r':.*', '', parseresult.netloc) - key = parseresult.path.strip('/') + bucket = re.sub(r":.*", "", parseresult.netloc) + key = parseresult.path.strip("/") return (bucket, key) def load_remote_resource_contents(self, remote_resource, **options): cache_key = self.get_remote_cache_key(remote_resource) if not self.cache_has(cache_key): - resource = super(CachedSC2Factory, self).load_remote_resource_contents(remote_resource, **options) + resource = super(CachedSC2Factory, self).load_remote_resource_contents( + remote_resource, **options + ) self.cache_set(cache_key, resource) else: resource = self.cache_get(cache_key) @@ -254,13 +261,20 @@ class FileCachedSC2Factory(CachedSC2Factory): Caches remote depot resources on the file system in the ``cache_dir``. """ + def __init__(self, cache_dir, **options): super(FileCachedSC2Factory, self).__init__(**options) self.cache_dir = os.path.abspath(cache_dir) if not os.path.isdir(self.cache_dir): - raise ValueError("cache_dir ({0}) must be an existing directory.".format(self.cache_dir)) + raise ValueError( + "cache_dir ({0}) must be an existing directory.".format(self.cache_dir) + ) elif not os.access(self.cache_dir, os.F_OK | os.W_OK | os.R_OK): - raise ValueError("Must have read/write access to {0} for local file caching.".format(self.cache_dir)) + raise ValueError( + "Must have read/write access to {0} for local file caching.".format( + self.cache_dir + ) + ) def cache_has(self, cache_key): return os.path.exists(self.cache_path(cache_key)) @@ -274,7 +288,7 @@ def cache_set(self, cache_key, value): if not os.path.exists(bucket_dir): os.makedirs(bucket_dir) - with open(cache_path, 'wb') as out: + with open(cache_path, "wb") as out: out.write(value) def cache_path(self, cache_key): @@ -290,6 +304,7 @@ class DictCachedSC2Factory(CachedSC2Factory): Caches remote depot resources in memory. Does not write to the file system. The cache is effectively cleared when the process exits. """ + def __init__(self, cache_max_size=0, **options): super(DictCachedSC2Factory, self).__init__(**options) self.cache_dict = dict() @@ -322,8 +337,11 @@ class DoubleCachedSC2Factory(DictCachedSC2Factory, FileCachedSC2Factory): Caches remote depot resources to the file system AND holds a subset of them in memory for more efficient access. """ + def __init__(self, cache_dir, cache_max_size=0, **options): - super(DoubleCachedSC2Factory, self).__init__(cache_max_size, cache_dir=cache_dir, **options) + super(DoubleCachedSC2Factory, self).__init__( + cache_max_size, cache_dir=cache_dir, **options + ) def load_remote_resource_contents(self, remote_resource, **options): cache_key = self.get_remote_cache_key(remote_resource) @@ -332,7 +350,9 @@ def load_remote_resource_contents(self, remote_resource, **options): return DictCachedSC2Factory.cache_get(self, cache_key) if not FileCachedSC2Factory.cache_has(self, cache_key): - resource = SC2Factory.load_remote_resource_contents(self, remote_resource, **options) + resource = SC2Factory.load_remote_resource_contents( + self, remote_resource, **options + ) FileCachedSC2Factory.cache_set(self, cache_key, resource) else: resource = FileCachedSC2Factory.cache_get(self, cache_key) diff --git a/sc2reader/log_utils.py b/sc2reader/log_utils.py index 58a442d9..a53656c8 100644 --- a/sc2reader/log_utils.py +++ b/sc2reader/log_utils.py @@ -22,6 +22,7 @@ class NullHandler(logging.Handler): a NullHandler and add it to the top-level logger of the library module or package. """ + def handle(self, record): pass @@ -31,34 +32,35 @@ def emit(self, record): def createLock(self): self.lock = None + LEVEL_MAP = dict( DEBUG=logging.DEBUG, INFO=logging.INFO, WARN=logging.WARN, ERROR=logging.ERROR, - CRITICAL=logging.CRITICAL + CRITICAL=logging.CRITICAL, ) def setup(): - logging.getLogger('sc2reader').addHandler(NullHandler()) + logging.getLogger("sc2reader").addHandler(NullHandler()) -def log_to_file(filename, level='WARN', format=None, datefmt=None, **options): +def log_to_file(filename, level="WARN", format=None, datefmt=None, **options): add_log_handler(logging.FileHandler(filename, **options), level, format, datefmt) -def log_to_console(level='WARN', format=None, datefmt=None, **options): +def log_to_console(level="WARN", format=None, datefmt=None, **options): add_log_handler(logging.StreamHandler(**options), level, format, datefmt) -def add_log_handler(handler, level='WARN', format=None, datefmt=None): +def add_log_handler(handler, level="WARN", format=None, datefmt=None): handler.setFormatter(logging.Formatter(format, datefmt)) if isinstance(level, basestring): level = LEVEL_MAP[level] - logger = logging.getLogger('sc2reader') + logger = logging.getLogger("sc2reader") logger.setLevel(level) logger.addHandler(handler) @@ -73,7 +75,7 @@ def get_logger(entity): :param entity: The entity for which we want a logger. """ try: - return logging.getLogger(entity.__module__+'.'+entity.__name__) + return logging.getLogger(entity.__module__ + "." + entity.__name__) except AttributeError: raise TypeError("Cannot retrieve logger for {0}.".format(entity)) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index eaa2e28b..04dcfc84 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -9,7 +9,7 @@ from sc2reader.decoders import ByteDecoder from sc2reader.constants import * -Location = namedtuple('Location', ['x', 'y']) +Location = namedtuple("Location", ["x", "y"]) class Team(object): @@ -48,15 +48,17 @@ def lineup(self): A string representation of the team play races like PP or TPZZ. Random pick races are not reflected in this string """ - return ''.join(sorted(p.play_race[0].upper() for p in self.players)) + return "".join(sorted(p.play_race[0].upper() for p in self.players)) @property def hash(self): - raw_hash = ','.join(sorted(p.url for p in self.players)) + raw_hash = ",".join(sorted(p.url for p in self.players)) return hashlib.sha256(raw_hash).hexdigest() def __str__(self): - return "Team {0}: {1}".format(self.number, ", ".join([str(p) for p in self.players])) + return "Team {0}: {1}".format( + self.number, ", ".join([str(p) for p in self.players]) + ) def __repr__(self): return str(self) @@ -64,7 +66,6 @@ def __repr__(self): @log_utils.loggable class Attribute(object): - def __init__(self, header, attr_id, player, value): self.header = header self.id = attr_id @@ -90,6 +91,7 @@ class Entity(object): :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity """ + def __init__(self, sid, slot_data): #: The entity's unique in-game slot id self.sid = int(sid) @@ -98,36 +100,36 @@ def __init__(self, sid, slot_data): self.slot_data = slot_data #: The player's handicap as set prior to game start, ranges from 50-100 - self.handicap = slot_data['handicap'] + self.handicap = slot_data["handicap"] #: The entity's team number. None for observers self.team_id = None - if slot_data['team_id'] is not None: - self.team_id = slot_data['team_id'] + 1 + if slot_data["team_id"] is not None: + self.team_id = slot_data["team_id"] + 1 #: A flag indicating if the person is a human or computer #: Really just a shortcut for isinstance(entity, User) - self.is_human = slot_data['control'] == 2 + self.is_human = slot_data["control"] == 2 #: A flag indicating the entity's observer status. #: Really just a shortcut for isinstance(entity, Observer). - self.is_observer = slot_data['observe'] != 0 + self.is_observer = slot_data["observe"] != 0 #: A flag marking this entity as a referee (can talk to players) - self.is_referee = slot_data['observe'] == 2 + self.is_referee = slot_data["observe"] == 2 #: - self.hero_name = slot_data['hero'] + self.hero_name = slot_data["hero"] #: - self.hero_skin = slot_data['skin'] + self.hero_skin = slot_data["skin"] #: - self.hero_mount = slot_data['mount'] + self.hero_mount = slot_data["mount"] #: The unique Battle.net account identifier in the form of #: -S2-- - self.toon_handle = slot_data['toon_handle'] + self.toon_handle = slot_data["toon_handle"] toon_handle = self.toon_handle or "0-S2-0-0" parts = toon_handle.split("-") @@ -144,7 +146,7 @@ def __init__(self, sid, slot_data): self.toon_id = int(parts[3]) #: A index to the user that is the leader of the archon team - self.archon_leader_id = slot_data['tandem_leader_user_id'] + self.archon_leader_id = slot_data["tandem_leader_user_id"] #: A list of :class:`Event` objects representing all the game events #: generated by the person over the course of the game @@ -164,6 +166,7 @@ class Player(object): :param dict detail_data: The detail data associated with this player :param dict attribute_data: The attribute data associated with this player """ + def __init__(self, pid, slot_data, detail_data, attribute_data): #: The player's unique in-game player id self.pid = int(pid) @@ -179,9 +182,9 @@ def __init__(self, pid, slot_data, detail_data, attribute_data): #: The player result, one of "Win", "Loss", or None self.result = None - if detail_data['result'] == 1: + if detail_data["result"] == 1: self.result = "Win" - elif detail_data['result'] == 2: + elif detail_data["result"] == 2: self.result = "Loss" #: A reference to the player's :class:`Team` object @@ -189,37 +192,37 @@ def __init__(self, pid, slot_data, detail_data, attribute_data): #: The race the player picked prior to the game starting. #: One of Protoss, Terran, Zerg, Random - self.pick_race = attribute_data.get('Race', 'Unknown') + self.pick_race = attribute_data.get("Race", "Unknown") #: The difficulty setting for the player. Always Medium for human players. #: Very Easy, Easy, Medium, Hard, Harder, Very hard, Elite, Insane, #: Cheater 2 (Resources), Cheater 1 (Vision) - self.difficulty = attribute_data.get('Difficulty', 'Unknown') + self.difficulty = attribute_data.get("Difficulty", "Unknown") #: The race the player played the game with. #: One of Protoss, Terran, Zerg - self.play_race = LOCALIZED_RACES.get(detail_data['race'], detail_data['race']) + self.play_race = LOCALIZED_RACES.get(detail_data["race"], detail_data["race"]) #: The co-op commander the player picked #: Kerrigan, Raynor, etc. - self.commander = slot_data['commander'] + self.commander = slot_data["commander"] if self.commander is not None: - self.commander = self.commander.decode('utf8') + self.commander = self.commander.decode("utf8") #: The level of the co-op commander #: 1-15 or None - self.commander_level = slot_data['commander_level'] + self.commander_level = slot_data["commander_level"] #: The mastery level of the co-op commander #: >0 or None - self.commander_mastery_level = slot_data['commander_mastery_talents'] + self.commander_mastery_level = slot_data["commander_mastery_talents"] #: The mastery talents picked for the co-op commander #: list of longs of length 6, each between 0 and 30 - self.commander_mastery_talents = slot_data['commander_mastery_talents'] + self.commander_mastery_talents = slot_data["commander_mastery_talents"] #: A reference to a :class:`~sc2reader.utils.Color` object representing the player's color - self.color = utils.Color(**detail_data['color']) + self.color = utils.Color(**detail_data["color"]) #: A list of references to the :class:`~sc2reader.data.Unit` objects the player had this game self.units = list() @@ -228,15 +231,15 @@ def __init__(self, pid, slot_data, detail_data, attribute_data): self.killed_units = list() #: The Battle.net region the entity is registered to - self.region = GATEWAY_LOOKUP[detail_data['bnet']['region']] + self.region = GATEWAY_LOOKUP[detail_data["bnet"]["region"]] #: The Battle.net subregion the entity is registered to - self.subregion = detail_data['bnet']['subregion'] + self.subregion = detail_data["bnet"]["subregion"] #: The Battle.net acount identifier. Used to construct the #: bnet profile url. This value can be zero for games #: played offline when a user was not logged in to battle.net. - self.toon_id = detail_data['bnet']['uid'] + self.toon_id = detail_data["bnet"]["uid"] class User(object): @@ -244,8 +247,11 @@ class User(object): :param integer uid: The user's unique user id :param dict init_data: The init data associated with this user """ + #: The Battle.net profile url template - URL_TEMPLATE = "http://{region}.battle.net/sc2/en/profile/{toon_id}/{subregion}/{name}/" + URL_TEMPLATE = ( + "http://{region}.battle.net/sc2/en/profile/{toon_id}/{subregion}/{name}/" + ) def __init__(self, uid, init_data): #: The user's unique in-game user id @@ -255,17 +261,17 @@ def __init__(self, uid, init_data): self.init_data = init_data #: The user's Battle.net clan tag at the time of the game - self.clan_tag = init_data['clan_tag'] + self.clan_tag = init_data["clan_tag"] #: The user's Battle.net name at the time of the game - self.name = init_data['name'] + self.name = init_data["name"] #: The user's combined Battle.net race levels - self.combined_race_levels = init_data['combined_race_levels'] + self.combined_race_levels = init_data["combined_race_levels"] #: The highest 1v1 league achieved by the user in the current season with 1 as Bronze and #: 7 as Grandmaster. 8 seems to indicate that there is no current season 1v1 ranking. - self.highest_league = init_data['highest_league'] + self.highest_league = init_data["highest_league"] #: A flag indicating if this person was the one who recorded the game. #: This is deprecated because it doesn't actually work. @@ -274,7 +280,9 @@ def __init__(self, uid, init_data): @property def url(self): """The player's formatted Battle.net profile url""" - return self.URL_TEMPLATE.format(**self.__dict__) # region=self.region, toon_id=self.toon_id, subregion=self.subregion, name=self.name.('utf8')) + return self.URL_TEMPLATE.format( + **self.__dict__ + ) # region=self.region, toon_id=self.toon_id, subregion=self.subregion, name=self.name.('utf8')) class Observer(Entity, User): @@ -286,6 +294,7 @@ class Observer(Entity, User): :param dict init_data: The init data associated with this user :param integer pid: The player's unique player id. """ + def __init__(self, sid, slot_data, uid, init_data, pid): Entity.__init__(self, sid, slot_data) User.__init__(self, uid, init_data) @@ -309,12 +318,13 @@ class Computer(Entity, Player): :param dict detail_data: The detail data associated with this player :param dict attribute_data: The attribute data associated with this player """ + def __init__(self, sid, slot_data, pid, detail_data, attribute_data): Entity.__init__(self, sid, slot_data) Player.__init__(self, pid, slot_data, detail_data, attribute_data) #: The auto-generated in-game name for this computer player - self.name = detail_data['name'] + self.name = detail_data["name"] def __str__(self): return "Player {0} - {1} ({2})".format(self.pid, self.name, self.play_race) @@ -334,7 +344,10 @@ class Participant(Entity, User, Player): :param dict detail_data: The detail data associated with this player :param dict attribute_data: The attribute data associated with this player """ - def __init__(self, sid, slot_data, uid, init_data, pid, detail_data, attribute_data): + + def __init__( + self, sid, slot_data, uid, init_data, pid, detail_data, attribute_data + ): Entity.__init__(self, sid, slot_data) User.__init__(self, uid, init_data) Player.__init__(self, pid, slot_data, detail_data, attribute_data) @@ -346,7 +359,7 @@ def __repr__(self): return str(self) -class PlayerSummary(): +class PlayerSummary: """ Resents a player as loaded from a :class:`~sc2reader.resources.GameSummary` file. @@ -400,25 +413,29 @@ def __init__(self, pid): def __str__(self): if not self.is_ai: - return 'User {0}-S2-{1}-{2}'.format(self.region.upper(), self.subregion, self.bnetid) + return "User {0}-S2-{1}-{2}".format( + self.region.upper(), self.subregion, self.bnetid + ) else: - return 'AI ({0})'.format(self.play_race) + return "AI ({0})".format(self.play_race) def __repr__(self): return str(self) def get_stats(self): - s = '' + s = "" for k in self.stats: - s += '{0}: {1}\n'.format(self.stats_pretty_names[k], self.stats[k]) + s += "{0}: {1}\n".format(self.stats_pretty_names[k], self.stats[k]) return s.strip() -BuildEntry = namedtuple('BuildEntry', ['supply', 'total_supply', 'time', 'order', 'build_index']) +BuildEntry = namedtuple( + "BuildEntry", ["supply", "total_supply", "time", "order", "build_index"] +) # TODO: Are there libraries with classes like this in them -class Graph(): +class Graph: """ A class to represent a graph on the score screen. Derived from data in the :class:`~sc2reader.resources.GameSummary` file. @@ -454,6 +471,7 @@ class MapInfoPlayer(object): """ Describes the player data as found in the MapInfo document of SC2Map archives. """ + def __init__(self, pid, control, color, race, unknown, start_point, ai, decal): #: The pid of the player self.pid = pid @@ -512,12 +530,13 @@ class MapInfo(object): """ Represents the data encoded into the MapInfo file inside every SC2Map archive """ + def __init__(self, contents): # According to http://www.galaxywiki.net/MapInfo_(File_Format) # With a couple small changes for version 0x20+ - data = ByteDecoder(contents, endian='LITTLE') + data = ByteDecoder(contents, endian="LITTLE") magic = data.read_string(4) - if magic != 'MapI': + if magic != "MapI": self.logger.warn("Invalid MapInfo file: {0}".format(magic)) return @@ -549,13 +568,13 @@ def __init__(self, contents): if self.large_preview_type == 2: self.large_preview_path = data.read_cstring() - if self.version >= 0x1f: + if self.version >= 0x1F: self.unknown3 = data.read_cstring() if self.version >= 0x26: self.unknown4 = data.read_cstring() - if self.version >= 0x1f: + if self.version >= 0x1F: self.unknown5 = data.read_uint32() self.unknown6 = data.read_uint32() @@ -579,7 +598,7 @@ def __init__(self, contents): self.camera_top = data.read_uint32() #: The map base height (what is that?). This value is 4096*Base Height in the editor (giving a decimal value). - self.base_height = data.read_uint32()/4096 + self.base_height = data.read_uint32() / 4096 # Leave early so we dont barf. Turns out ggtracker doesnt need # any of the map data thats loaded below. @@ -592,7 +611,7 @@ def __init__(self, contents): self.load_screen_path = data.read_cstring() #: Unknown string, usually empty - self.unknown7 = data.read_bytes(data.read_uint16()).decode('utf8') + self.unknown7 = data.read_bytes(data.read_uint16()).decode("utf8") #: Load screen image scaling strategy: 0 = normal, 1 = aspect scaling, 2 = stretch the image. self.load_screen_scaling = data.read_uint32() @@ -639,7 +658,7 @@ def __init__(self, contents): if self.version >= 0x19: self.unknown9 = data.read_bytes(8) - if self.version >= 0x1f: + if self.version >= 0x1F: self.unknown10 = data.read_bytes(9) if self.version >= 0x20: @@ -651,16 +670,18 @@ def __init__(self, contents): #: A list of references to :class:`MapInfoPlayer` objects self.players = list() for i in range(self.player_count): - self.players.append(MapInfoPlayer( - pid=data.read_uint8(), - control=data.read_uint32(), - color=data.read_uint32(), - race=data.read_cstring(), - unknown=data.read_uint32(), - start_point=data.read_uint32(), - ai=data.read_uint32(), - decal=data.read_cstring(), - )) + self.players.append( + MapInfoPlayer( + pid=data.read_uint8(), + control=data.read_uint32(), + color=data.read_uint32(), + race=data.read_cstring(), + unknown=data.read_uint32(), + start_point=data.read_uint32(), + ai=data.read_uint32(), + decal=data.read_cstring(), + ) + ) #: A list of the start location point indexes used in Basic Team Settings. #: The editor limits these to only Start Locations and not regular points. @@ -686,7 +707,9 @@ def __init__(self, contents): # } # } #: A bit array of flags mapping out the player alliances - self.alliance_flags = data.read_uint(int(math.ceil(self.alliance_flags_length/8.0))) + self.alliance_flags = data.read_uint( + int(math.ceil(self.alliance_flags_length / 8.0)) + ) #: A list of the advanced start location point indexes used in Advanced Team Settings. #: The editor limits these to only Start Locations and not regular points. @@ -722,7 +745,7 @@ def __init__(self, contents): # } # } #: A bit array of flags mapping out the player enemies. - self.enemy_flags = data.read_uint(int(math.ceil(self.enemy_flags_length/8.0))) + self.enemy_flags = data.read_uint(int(math.ceil(self.enemy_flags_length / 8.0))) if data.length != data.tell(): self.logger.warn("Not all of the MapInfo file was read!") diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 54bf3f54..8b24f60e 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -16,28 +16,54 @@ class InitDataReader(object): def __call__(self, data, replay): data = BitPackedDecoder(data) result = dict( - user_initial_data=[dict( - name=data.read_aligned_string(data.read_uint8()), - clan_tag=data.read_aligned_string(data.read_uint8()) if replay.base_build >= 24764 and data.read_bool() else None, - clan_logo=DepotFile(data.read_aligned_bytes(40)) if replay.base_build >= 27950 and data.read_bool() else None, - highest_league=data.read_uint8() if replay.base_build >= 24764 and data.read_bool() else None, - combined_race_levels=data.read_uint32() if replay.base_build >= 24764 and data.read_bool() else None, - random_seed=data.read_uint32(), - race_preference=data.read_uint8() if data.read_bool() else None, - team_preference=data.read_uint8() if replay.base_build >= 16561 and data.read_bool() else None, - test_map=data.read_bool(), - test_auto=data.read_bool(), - examine=data.read_bool() if replay.base_build >= 21955 else None, - custom_interface=data.read_bool() if replay.base_build >= 24764 else None, - test_type=data.read_uint32() if replay.base_build >= 34784 else None, - observe=data.read_bits(2), - hero=data.read_aligned_string(data.read_bits(9)) if replay.base_build >= 34784 else None, - skin=data.read_aligned_string(data.read_bits(9)) if replay.base_build >= 34784 else None, - mount=data.read_aligned_string(data.read_bits(9)) if replay.base_build >= 34784 else None, - toon_handle=data.read_aligned_string(data.read_bits(7)) if replay.base_build >= 34784 else None, - scaled_rating=data.read_uint32()-2147483648 if replay.base_build >= 54518 and data.read_bool() else None, - ) for i in range(data.read_bits(5))], - + user_initial_data=[ + dict( + name=data.read_aligned_string(data.read_uint8()), + clan_tag=data.read_aligned_string(data.read_uint8()) + if replay.base_build >= 24764 and data.read_bool() + else None, + clan_logo=DepotFile(data.read_aligned_bytes(40)) + if replay.base_build >= 27950 and data.read_bool() + else None, + highest_league=data.read_uint8() + if replay.base_build >= 24764 and data.read_bool() + else None, + combined_race_levels=data.read_uint32() + if replay.base_build >= 24764 and data.read_bool() + else None, + random_seed=data.read_uint32(), + race_preference=data.read_uint8() if data.read_bool() else None, + team_preference=data.read_uint8() + if replay.base_build >= 16561 and data.read_bool() + else None, + test_map=data.read_bool(), + test_auto=data.read_bool(), + examine=data.read_bool() if replay.base_build >= 21955 else None, + custom_interface=data.read_bool() + if replay.base_build >= 24764 + else None, + test_type=data.read_uint32() + if replay.base_build >= 34784 + else None, + observe=data.read_bits(2), + hero=data.read_aligned_string(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + skin=data.read_aligned_string(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + mount=data.read_aligned_string(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + toon_handle=data.read_aligned_string(data.read_bits(7)) + if replay.base_build >= 34784 + else None, + scaled_rating=data.read_uint32() - 2147483648 + if replay.base_build >= 54518 and data.read_bool() + else None, + ) + for i in range(data.read_bits(5)) + ], game_description=dict( random_value=data.read_uint32(), game_cache_name=data.read_aligned_string(data.read_bits(10)), @@ -48,110 +74,213 @@ def __call__(self, data, replay): random_races=data.read_bool(), battle_net=data.read_bool(), amm=data.read_bool(), - ranked=data.read_bool() if replay.base_build >= 34784 and replay.base_build < 38215 else None, + ranked=data.read_bool() + if replay.base_build >= 34784 and replay.base_build < 38215 + else None, competitive=data.read_bool(), practice=data.read_bool() if replay.base_build >= 34784 else None, - cooperative=data.read_bool() if replay.base_build >= 34784 else None, + cooperative=data.read_bool() + if replay.base_build >= 34784 + else None, no_victory_or_defeat=data.read_bool(), - hero_duplicates_allowed=data.read_bool() if replay.base_build >= 34784 else None, + hero_duplicates_allowed=data.read_bool() + if replay.base_build >= 34784 + else None, fog=data.read_bits(2), observers=data.read_bits(2), user_difficulty=data.read_bits(2), - client_debug_flags=data.read_uint64() if replay.base_build >= 22612 else None, - build_coach_enabled=data.read_bool() if replay.base_build >= 59587 else None, + client_debug_flags=data.read_uint64() + if replay.base_build >= 22612 + else None, + build_coach_enabled=data.read_bool() + if replay.base_build >= 59587 + else None, ), game_speed=data.read_bits(3), game_type=data.read_bits(3), max_users=data.read_bits(5), max_observers=data.read_bits(5), max_players=data.read_bits(5), - max_teams=data.read_bits(4)+1, - max_colors=data.read_bits(6) if replay.base_build >= 17266 else data.read_bits(5)+1, - max_races=data.read_uint8()+1, - max_controls=data.read_uint8()+(0 if replay.base_build >= 26490 else 1), + max_teams=data.read_bits(4) + 1, + max_colors=data.read_bits(6) + if replay.base_build >= 17266 + else data.read_bits(5) + 1, + max_races=data.read_uint8() + 1, + max_controls=data.read_uint8() + + (0 if replay.base_build >= 26490 else 1), map_size_x=data.read_uint8(), map_size_y=data.read_uint8(), map_file_sync_checksum=data.read_uint32(), map_file_name=data.read_aligned_string(data.read_bits(11)), map_author_name=data.read_aligned_string(data.read_uint8()), mod_file_sync_checksum=data.read_uint32(), - slot_descriptions=[dict( - allowed_colors=data.read_bits(data.read_bits(6)), - allowed_races=data.read_bits(data.read_uint8()), - allowedDifficulty=data.read_bits(data.read_bits(6)), - allowedControls=data.read_bits(data.read_uint8()), - allowed_observe_types=data.read_bits(data.read_bits(2)), - allowed_ai_builds=data.read_bits(data.read_bits(8 if replay.base_build >= 38749 else 7)) if replay.base_build >= 23925 else None, - ) for i in range(data.read_bits(5))], + slot_descriptions=[ + dict( + allowed_colors=data.read_bits(data.read_bits(6)), + allowed_races=data.read_bits(data.read_uint8()), + allowedDifficulty=data.read_bits(data.read_bits(6)), + allowedControls=data.read_bits(data.read_uint8()), + allowed_observe_types=data.read_bits(data.read_bits(2)), + allowed_ai_builds=data.read_bits( + data.read_bits(8 if replay.base_build >= 38749 else 7) + ) + if replay.base_build >= 23925 + else None, + ) + for i in range(data.read_bits(5)) + ], default_difficulty=data.read_bits(6), - default_ai_build=data.read_bits(8 if replay.base_build >= 38749 else 7) if replay.base_build >= 23925 else None, - cache_handles=[DepotFile(data.read_aligned_bytes(40)) for i in range(data.read_bits(6 if replay.base_build >= 21955 else 4))], - has_extension_mod=data.read_bool() if replay.base_build >= 27950 else None, - has_nonBlizzardExtensionMod=data.read_bool() if replay.base_build >= 42932 else None, + default_ai_build=data.read_bits(8 if replay.base_build >= 38749 else 7) + if replay.base_build >= 23925 + else None, + cache_handles=[ + DepotFile(data.read_aligned_bytes(40)) + for i in range( + data.read_bits(6 if replay.base_build >= 21955 else 4) + ) + ], + has_extension_mod=data.read_bool() + if replay.base_build >= 27950 + else None, + has_nonBlizzardExtensionMod=data.read_bool() + if replay.base_build >= 42932 + else None, is_blizzardMap=data.read_bool(), is_premade_ffa=data.read_bool(), is_coop_mode=data.read_bool() if replay.base_build >= 23925 else None, - is_realtime_mode=data.read_bool() if replay.base_build >= 54518 else None, + is_realtime_mode=data.read_bool() + if replay.base_build >= 54518 + else None, ), - lobby_state=dict( phase=data.read_bits(3), max_users=data.read_bits(5), max_observers=data.read_bits(5), - slots=[dict( - control=data.read_uint8(), - user_id=data.read_bits(4) if data.read_bool() else None, - team_id=data.read_bits(4), - colorPref=data.read_bits(5) if data.read_bool() else None, - race_pref=data.read_uint8() if data.read_bool() else None, - difficulty=data.read_bits(6), - ai_build=data.read_bits(8 if replay.base_build >= 38749 else 7) if replay.base_build >= 23925 else None, - handicap=data.read_bits(7), - observe=data.read_bits(2), - logo_index=data.read_uint32() if replay.base_build >= 32283 else None, - hero=data.read_aligned_string(data.read_bits(9)) if replay.base_build >= 34784 else None, - skin=data.read_aligned_string(data.read_bits(9)) if replay.base_build >= 34784 else None, - mount=data.read_aligned_string(data.read_bits(9)) if replay.base_build >= 34784 else None, - artifacts=[dict( - type_struct=data.read_aligned_string(data.read_bits(9)), - ) for i in range(data.read_bits(4))] if replay.base_build >= 34784 else None, - working_set_slot_id=data.read_uint8() if replay.base_build >= 24764 and data.read_bool() else None, - rewards=[data.read_uint32() for i in range(data.read_bits(17 if replay.base_build >= 34784 else 6 if replay.base_build >= 24764 else 5))], - toon_handle=data.read_aligned_string(data.read_bits(7)) if replay.base_build >= 17266 else None, - licenses=[data.read_uint32() for i in range(data.read_bits(13 if replay.base_build >= 70154 else 9))] if replay.base_build >= 19132 else [], - tandem_leader_user_id=data.read_bits(4) if replay.base_build >= 34784 and data.read_bool() else None, - commander=data.read_aligned_bytes(data.read_bits(9)) if replay.base_build >= 34784 else None, - commander_level=data.read_uint32() if replay.base_build >= 36442 else None, - has_silence_penalty=data.read_bool() if replay.base_build >= 38215 else None, - tandem_id=data.read_bits(4) if replay.base_build >= 39576 and data.read_bool() else None, - commander_mastery_level=data.read_uint32() if replay.base_build >= 42932 else None, - commander_mastery_talents=[data.read_uint32() for i in range(data.read_bits(3))] if replay.base_build >= 42932 else None, - reward_overrides=[[data.read_uint32(), [data.read_uint32() for i in range(data.read_bits(17))]] for j in range(data.read_bits(17))] if replay.base_build >= 47185 else None, - ) for i in range(data.read_bits(5))], + slots=[ + dict( + control=data.read_uint8(), + user_id=data.read_bits(4) if data.read_bool() else None, + team_id=data.read_bits(4), + colorPref=data.read_bits(5) if data.read_bool() else None, + race_pref=data.read_uint8() if data.read_bool() else None, + difficulty=data.read_bits(6), + ai_build=data.read_bits(8 if replay.base_build >= 38749 else 7) + if replay.base_build >= 23925 + else None, + handicap=data.read_bits(7), + observe=data.read_bits(2), + logo_index=data.read_uint32() + if replay.base_build >= 32283 + else None, + hero=data.read_aligned_string(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + skin=data.read_aligned_string(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + mount=data.read_aligned_string(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + artifacts=[ + dict( + type_struct=data.read_aligned_string(data.read_bits(9)) + ) + for i in range(data.read_bits(4)) + ] + if replay.base_build >= 34784 + else None, + working_set_slot_id=data.read_uint8() + if replay.base_build >= 24764 and data.read_bool() + else None, + rewards=[ + data.read_uint32() + for i in range( + data.read_bits( + 17 + if replay.base_build >= 34784 + else 6 + if replay.base_build >= 24764 + else 5 + ) + ) + ], + toon_handle=data.read_aligned_string(data.read_bits(7)) + if replay.base_build >= 17266 + else None, + licenses=[ + data.read_uint32() + for i in range( + data.read_bits(13 if replay.base_build >= 70154 else 9) + ) + ] + if replay.base_build >= 19132 + else [], + tandem_leader_user_id=data.read_bits(4) + if replay.base_build >= 34784 and data.read_bool() + else None, + commander=data.read_aligned_bytes(data.read_bits(9)) + if replay.base_build >= 34784 + else None, + commander_level=data.read_uint32() + if replay.base_build >= 36442 + else None, + has_silence_penalty=data.read_bool() + if replay.base_build >= 38215 + else None, + tandem_id=data.read_bits(4) + if replay.base_build >= 39576 and data.read_bool() + else None, + commander_mastery_level=data.read_uint32() + if replay.base_build >= 42932 + else None, + commander_mastery_talents=[ + data.read_uint32() for i in range(data.read_bits(3)) + ] + if replay.base_build >= 42932 + else None, + reward_overrides=[ + [ + data.read_uint32(), + [data.read_uint32() for i in range(data.read_bits(17))], + ] + for j in range(data.read_bits(17)) + ] + if replay.base_build >= 47185 + else None, + ) + for i in range(data.read_bits(5)) + ], random_seed=data.read_uint32(), host_user_id=data.read_bits(4) if data.read_bool() else None, is_single_player=data.read_bool(), - picked_map_tag=data.read_uint8() if replay.base_build >= 36442 else None, + picked_map_tag=data.read_uint8() + if replay.base_build >= 36442 + else None, game_duration=data.read_uint32(), default_difficulty=data.read_bits(6), - default_ai_build=data.read_bits(8 if replay.base_build >= 38749 else 7) if replay.base_build >= 24764 else None, + default_ai_build=data.read_bits(8 if replay.base_build >= 38749 else 7) + if replay.base_build >= 24764 + else None, ), ) if not data.done(): - raise ValueError("{0} bytes left!".format(data.length-data.tell())) + raise ValueError("{0} bytes left!".format(data.length - data.tell())) return result class AttributesEventsReader(object): def __call__(self, data, replay): - data = ByteDecoder(data, endian='LITTLE') + data = ByteDecoder(data, endian="LITTLE") data.read_bytes(5 if replay.base_build >= 17326 else 4) - result = [Attribute( - data.read_uint32(), - data.read_uint32(), - data.read_uint8(), - ''.join(reversed(data.read_string(4))), - ) for i in range(data.read_uint32())] + result = [ + Attribute( + data.read_uint32(), + data.read_uint32(), + data.read_uint8(), + "".join(reversed(data.read_string(4))), + ) + for i in range(data.read_uint32()) + ] if not data.done(): raise ValueError("Not all bytes used up!") return result @@ -161,44 +290,46 @@ class DetailsReader(object): def __call__(self, data, replay): details = BitPackedDecoder(data).read_struct() return dict( - players=[dict( - name=p[0].decode('utf8'), - bnet=dict( - region=p[1][0], - program_id=p[1][1], - subregion=p[1][2], - # name=p[1][3].decode('utf8'), # This is documented but never available - uid=p[1][4], - ), - race=p[2].decode('utf8'), - color=dict( - a=p[3][0], - r=p[3][1], - g=p[3][2], - b=p[3][3], - ), - control=p[4], - team=p[5], - handicap=p[6], - observe=p[7], - result=p[8], - working_set_slot=p[9] if replay.build >= 24764 else None, - hero=p[10] if replay.build >= 34784 and 10 in p else None, # hero appears to be present in Heroes replays but not StarCraft 2 replays - ) for p in details[0]], - map_name=details[1].decode('utf8'), + players=[ + dict( + name=p[0].decode("utf8"), + bnet=dict( + region=p[1][0], + program_id=p[1][1], + subregion=p[1][2], + # name=p[1][3].decode('utf8'), # This is documented but never available + uid=p[1][4], + ), + race=p[2].decode("utf8"), + color=dict(a=p[3][0], r=p[3][1], g=p[3][2], b=p[3][3]), + control=p[4], + team=p[5], + handicap=p[6], + observe=p[7], + result=p[8], + working_set_slot=p[9] if replay.build >= 24764 else None, + hero=p[10] + if replay.build >= 34784 and 10 in p + else None, # hero appears to be present in Heroes replays but not StarCraft 2 replays + ) + for p in details[0] + ], + map_name=details[1].decode("utf8"), difficulty=details[2], thumbnail=details[3][0], blizzard_map=details[4], file_time=details[5], utc_adjustment=details[6], description=details[7], - image_file_path=details[8].decode('utf8'), - map_file_name=details[9].decode('utf8'), + image_file_path=details[8].decode("utf8"), + map_file_name=details[9].decode("utf8"), cache_handles=[DepotFile(bytes) for bytes in details[10]], mini_save=details[11], game_speed=details[12], default_difficulty=details[13], - mod_paths=details[14] if (replay.build >= 22612 and replay.versions[1] == 1) else None, + mod_paths=details[14] + if (replay.build >= 22612 and replay.versions[1] == 1) + else None, campaign_index=details[15] if replay.versions[1] == 2 else None, restartAsTransitionMap=details[16] if replay.build > 26490 else None, ) @@ -223,12 +354,12 @@ def __call__(self, data, replay): elif flag == 1: # Client ping message recipient = data.read_bits(3 if replay.base_build >= 21955 else 2) - x = data.read_uint32()-2147483648 - y = data.read_uint32()-2147483648 + x = data.read_uint32() - 2147483648 + y = data.read_uint32() - 2147483648 pings.append(PingEvent(frame, pid, recipient, x, y)) elif flag == 2: # Loading progress message - progress = data.read_uint32()-2147483648 + progress = data.read_uint32() - 2147483648 packets.append(ProgressEvent(frame, pid, progress)) elif flag == 3: # Server ping message @@ -244,7 +375,6 @@ def __call__(self, data, replay): class GameEventsReader_Base(object): - def __init__(self): self.EVENT_DISPATCH = { 0: (None, self.unknown_event), @@ -318,8 +448,14 @@ def __init__(self): 90: (None, self.trigger_custom_dialog_dismissed_event), 91: (None, self.trigger_game_menu_item_selected_event), 92: (None, self.trigger_camera_move_event), - 93: (None, self.trigger_purchase_panel_selected_purchase_item_changed_event), - 94: (None, self.trigger_purchase_panel_selected_purchase_category_changed_event), + 93: ( + None, + self.trigger_purchase_panel_selected_purchase_item_changed_event, + ), + 94: ( + None, + self.trigger_purchase_panel_selected_purchase_category_changed_event, + ), 95: (None, self.trigger_button_pressed_event), 96: (None, self.trigger_game_credits_finished_event), } @@ -330,7 +466,7 @@ def __call__(self, data, replay): # method short cuts, avoid dict lookups EVENT_DISPATCH = self.EVENT_DISPATCH - debug = replay.opt['debug'] + debug = replay.opt["debug"] tell = data.tell read_frames = data.read_frames read_bits = data.read_bits @@ -358,33 +494,60 @@ def __call__(self, data, replay): # Otherwise throw a read error else: - raise ReadError("Event type {0} unknown at position {1}.".format(hex(event_type), hex(event_start)), event_type, event_start, replay, game_events, data) + raise ReadError( + "Event type {0} unknown at position {1}.".format( + hex(event_type), hex(event_start) + ), + event_type, + event_start, + replay, + game_events, + data, + ) byte_align() event_start = tell() return game_events except ParseError as e: - raise ReadError("Parse error '{0}' unknown at position {1}.".format(e.msg, hex(event_start)), event_type, event_start, replay, game_events, data) + raise ReadError( + "Parse error '{0}' unknown at position {1}.".format( + e.msg, hex(event_start) + ), + event_type, + event_start, + replay, + game_events, + data, + ) except EOFError as e: - raise ReadError("EOFError error '{0}' unknown at position {1}.".format(e.msg, hex(event_start)), event_type, event_start, replay, game_events, data) + raise ReadError( + "EOFError error '{0}' unknown at position {1}.".format( + e.msg, hex(event_start) + ), + event_type, + event_start, + replay, + game_events, + data, + ) # Don't want to do this more than once - SINGLE_BIT_MASKS = [0x1 << i for i in range(2**9)] + SINGLE_BIT_MASKS = [0x1 << i for i in range(2 ** 9)] def read_selection_bitmask(self, data, mask_length): bits_left = mask_length bits = data.read_bits(mask_length) mask = list() - shift_diff = (mask_length+data._bit_shift) % 8 - data._bit_shift + shift_diff = (mask_length + data._bit_shift) % 8 - data._bit_shift if shift_diff > 0: mask = [bits & data._lo_masks[shift_diff]] bits = bits >> shift_diff bits_left -= shift_diff elif shift_diff < 0: - mask = [bits & data._lo_masks[8+shift_diff]] - bits = bits >> (8+shift_diff) - bits_left -= 8+shift_diff + mask = [bits & data._lo_masks[8 + shift_diff]] + bits = bits >> (8 + shift_diff) + bits_left -= 8 + shift_diff # Now shift the rest of the bits off into the mask in byte-sized # chunks in reverse order. No idea why it'd be stored like this. @@ -394,7 +557,7 @@ def read_selection_bitmask(self, data, mask_length): bits_left -= 8 # Compile the finished mask into a large integer for bit checks - bit_mask = sum([c << (i*8) for i, c in enumerate(mask)]) + bit_mask = sum([c << (i * 8) for i, c in enumerate(mask)]) # Change mask representation from an int to a bit array with # True => Deselect, False => Keep @@ -402,24 +565,17 @@ def read_selection_bitmask(self, data, mask_length): class GameEventsReader_15405(GameEventsReader_Base): - def unknown_event(self, data): - return dict( - unknown=data.read_bytes(2) - ) + return dict(unknown=data.read_bytes(2)) def finished_loading_sync_event(self, data): return None def bank_file_event(self, data): - return dict( - name=data.read_aligned_string(data.read_bits(7)), - ) + return dict(name=data.read_aligned_string(data.read_bits(7))) def bank_section_event(self, data): - return dict( - name=data.read_aligned_string(data.read_bits(6)), - ) + return dict(name=data.read_aligned_string(data.read_bits(6))) def bank_key_event(self, data): return dict( @@ -473,10 +629,9 @@ def player_leave_event(self, data): def game_cheat_event(self, data): return dict( point=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, + x=data.read_uint32() - 2147483648, y=data.read_uint32() - 2147483648 ), - time=data.read_uint32()-2147483648, + time=data.read_uint32() - 2147483648, verb=data.read_aligned_string(data.read_bits(10)), arguments=data.read_aligned_string(data.read_bits(10)), ) @@ -488,23 +643,25 @@ def command_event(self, data): ability_command_index=data.read_uint8(), ability_command_data=data.read_uint8(), ) - target_data = ('TargetUnit', dict( - flags=data.read_uint8(), - timer=data.read_uint8(), - )) + target_data = ( + "TargetUnit", + dict(flags=data.read_uint8(), timer=data.read_uint8()), + ) other_unit_tag = data.read_uint32() - target_data[1].update(dict( - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, - z=data.read_uint32()-2147483648, - ), - )) + target_data[1].update( + dict( + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=None, + upkeep_player_id=data.read_bits(4) if data.read_bool() else None, + point=dict( + x=data.read_uint32() - 2147483648, + y=data.read_uint32() - 2147483648, + z=data.read_uint32() - 2147483648, + ), + ) + ) return dict( flags=flags, ability=ability, @@ -516,13 +673,16 @@ def selection_delta_event(self, data): return dict( control_group_index=data.read_bits(4), subgroup_index=data.read_uint8(), - remove_mask=('Mask', self.read_selection_bitmask(data, data.read_uint8())), - add_subgroups=[dict( - unit_link=data.read_uint16(), - subgroup_priority=None, - intra_subgroup_priority=data.read_uint8(), - count=data.read_uint8(), - ) for i in range(data.read_uint8())], + remove_mask=("Mask", self.read_selection_bitmask(data, data.read_uint8())), + add_subgroups=[ + dict( + unit_link=data.read_uint16(), + subgroup_priority=None, + intra_subgroup_priority=data.read_uint8(), + count=data.read_uint8(), + ) + for i in range(data.read_uint8()) + ], add_unit_tags=[data.read_uint32() for i in range(data.read_uint8())], ) @@ -530,7 +690,9 @@ def control_group_update_event(self, data): return dict( control_group_index=data.read_bits(4), control_group_update=data.read_bits(2), - remove_mask=('Mask', self.read_selection_bitmask(data, data.read_uint8())) if data.read_bool() else ('None', None), + remove_mask=("Mask", self.read_selection_bitmask(data, data.read_uint8())) + if data.read_bool() + else ("None", None), ) def selection_sync_check_event(self, data): @@ -543,46 +705,42 @@ def selection_sync_check_event(self, data): unit_tags_checksum=data.read_uint32(), subgroup_indices_checksum=data.read_uint32(), subgroups_checksum=data.read_uint32(), - ) + ), ) def resource_trade_event(self, data): return dict( recipient_id=data.read_bits(4), - resources=[data.read_uint32()-2147483648 for i in range(data.read_bits(3))], + resources=[ + data.read_uint32() - 2147483648 for i in range(data.read_bits(3)) + ], ) def trigger_chat_message_event(self, data): - return dict( - message=data.read_aligned_string(data.read_bits(10)), - ) + return dict(message=data.read_aligned_string(data.read_bits(10))) def ai_communicate_event(self, data): return dict( - beacon=data.read_uint8()-128, - ally=data.read_uint8()-128, - flags=data.read_uint8()-128, + beacon=data.read_uint8() - 128, + ally=data.read_uint8() - 128, + flags=data.read_uint8() - 128, build=None, target_unit_tag=data.read_uint32(), target_unit_link=data.read_uint16(), target_upkeep_player_id=data.read_bits(4) if data.read_bool() else None, target_control_player_id=None, target_point=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, - z=data.read_uint32()-2147483648, + x=data.read_uint32() - 2147483648, + y=data.read_uint32() - 2147483648, + z=data.read_uint32() - 2147483648, ), ) def set_absolute_game_speed_event(self, data): - return dict( - speed=data.read_bits(3), - ) + return dict(speed=data.read_bits(3)) def add_absolute_game_speed_event(self, data): - return dict( - delta=data.read_uint8()-128, - ) + return dict(delta=data.read_uint8() - 128) def broadcast_cheat_event(self, data): return dict( @@ -591,58 +749,38 @@ def broadcast_cheat_event(self, data): ) def alliance_event(self, data): - return dict( - alliance=data.read_uint32(), - control=data.read_uint32(), - ) + return dict(alliance=data.read_uint32(), control=data.read_uint32()) def unit_click_event(self, data): - return dict( - unit_tag=data.read_uint32(), - ) + return dict(unit_tag=data.read_uint32()) def unit_highlight_event(self, data): - return dict( - unit_tag=data.read_uint32(), - flags=data.read_uint8(), - ) + return dict(unit_tag=data.read_uint32(), flags=data.read_uint8()) def trigger_reply_selected_event(self, data): return dict( - conversation_id=data.read_uint32()-2147483648, - reply_id=data.read_uint32()-2147483648, + conversation_id=data.read_uint32() - 2147483648, + reply_id=data.read_uint32() - 2147483648, ) def trigger_skipped_event(self, data): return None def trigger_sound_length_query_event(self, data): - return dict( - sound_hash=data.read_uint32(), - length=data.read_uint32(), - ) + return dict(sound_hash=data.read_uint32(), length=data.read_uint32()) def trigger_sound_offset_event(self, data): - return dict( - sound=data.read_uint32(), - ) + return dict(sound=data.read_uint32()) def trigger_transmission_offset_event(self, data): - return dict( - transmission_id=data.read_uint32()-2147483648, - ) + return dict(transmission_id=data.read_uint32() - 2147483648) def trigger_transmission_complete_event(self, data): - return dict( - transmission_id=data.read_uint32()-2147483648, - ) + return dict(transmission_id=data.read_uint32() - 2147483648) def camera_update_event(self, data): return dict( - target=dict( - x=data.read_uint16(), - y=data.read_uint16(), - ), + target=dict(x=data.read_uint16(), y=data.read_uint16()), distance=data.read_uint16() if data.read_bool() else None, pitch=data.read_uint16() if data.read_bool() else None, yaw=data.read_uint16() if data.read_bool() else None, @@ -653,31 +791,30 @@ def trigger_abort_mission_event(self, data): return None def trigger_purchase_made_event(self, data): - return dict( - purchase_item_id=data.read_uint32()-2147483648, - ) + return dict(purchase_item_id=data.read_uint32() - 2147483648) def trigger_purchase_exit_event(self, data): return None def trigger_planet_mission_launched_event(self, data): - return dict( - difficulty_level=data.read_uint32()-2147483648, - ) + return dict(difficulty_level=data.read_uint32() - 2147483648) def trigger_planet_panel_canceled_event(self, data): return None def trigger_dialog_control_event(self, data): return dict( - control_id=data.read_uint32()-2147483648, - event_type=data.read_uint32()-2147483648, + control_id=data.read_uint32() - 2147483648, + event_type=data.read_uint32() - 2147483648, event_data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Checked', data.read_bool()), - 2: lambda: ('ValueChanged', data.read_uint32()), - 3: lambda: ('SelectionChanged', data.read_uint32()-2147483648), - 4: lambda: ('TextChanged', data.read_aligned_string(data.read_bits(11))), + 0: lambda: ("None", None), + 1: lambda: ("Checked", data.read_bool()), + 2: lambda: ("ValueChanged", data.read_uint32()), + 3: lambda: ("SelectionChanged", data.read_uint32() - 2147483648), + 4: lambda: ( + "TextChanged", + data.read_aligned_string(data.read_bits(11)), + ), }[data.read_bits(3)](), ) @@ -690,22 +827,17 @@ def trigger_sound_length_sync_event(self, data): ) def trigger_conversation_skipped_event(self, data): - return dict( - skip_type=data.read_int(1), - ) + return dict(skip_type=data.read_int(1)) def trigger_mouse_clicked_event(self, data): return dict( button=data.read_uint32(), down=data.read_bool(), - position_ui=dict( - x=data.read_uint32(), - y=data.read_uint32(), - ), + position_ui=dict(x=data.read_uint32(), y=data.read_uint32()), position_world=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, - z=data.read_uint32()-2147483648, + x=data.read_uint32() - 2147483648, + y=data.read_uint32() - 2147483648, + z=data.read_uint32() - 2147483648, ), ) @@ -713,25 +845,16 @@ def trigger_planet_panel_replay_event(self, data): return None def trigger_soundtrack_done_event(self, data): - return dict( - soundtrack=data.read_uint32(), - ) + return dict(soundtrack=data.read_uint32()) def trigger_planet_mission_selected_event(self, data): - return dict( - planet_id=data.read_uint32()-2147483648, - ) + return dict(planet_id=data.read_uint32() - 2147483648) def trigger_key_pressed_event(self, data): - return dict( - key=data.read_uint8()-128, - flags=data.read_uint8()-128, - ) + return dict(key=data.read_uint8() - 128, flags=data.read_uint8() - 128) def trigger_movie_function_event(self, data): - return dict( - function_name=data.read_aligned_string(data.read_bits(7)), - ) + return dict(function_name=data.read_aligned_string(data.read_bits(7))) def trigger_planet_panel_birth_complete_event(self, data): return None @@ -741,18 +864,16 @@ def trigger_planet_panel_death_complete_event(self, data): def resource_request_event(self, data): return dict( - resources=[data.read_uint32()-2147483648 for i in range(data.read_bits(3))], + resources=[ + data.read_uint32() - 2147483648 for i in range(data.read_bits(3)) + ] ) def resource_request_fulfill_event(self, data): - return dict( - request_id=data.read_uint32()-2147483648, - ) + return dict(request_id=data.read_uint32() - 2147483648) def resource_request_cancel_event(self, data): - return dict( - request_id=data.read_uint32()-2147483648, - ) + return dict(request_id=data.read_uint32() - 2147483648) def trigger_research_panel_exit_event(self, data): return None @@ -761,14 +882,10 @@ def trigger_research_panel_purchase_event(self, data): return None def trigger_research_panel_selection_changed_event(self, data): - return dict( - item_id=data.read_uint32()-2147483648, - ) + return dict(item_id=data.read_uint32() - 2147483648) def lag_message_event(self, data): - return dict( - player_id=data.read_bits(4), - ) + return dict(player_id=data.read_bits(4)) def trigger_mercenary_panel_exit_event(self, data): return None @@ -777,9 +894,7 @@ def trigger_mercenary_panel_purchase_event(self, data): return None def trigger_mercenary_panel_selection_changed_event(self, data): - return dict( - item_id=data.read_uint32()-2147483648, - ) + return dict(item_id=data.read_uint32() - 2147483648) def trigger_victory_panel_exit_event(self, data): return None @@ -789,24 +904,18 @@ def trigger_battle_report_panel_exit_event(self, data): def trigger_battle_report_panel_play_mission_event(self, data): return dict( - battle_report_id=data.read_uint32()-2147483648, - difficulty_level=data.read_uint32()-2147483648, + battle_report_id=data.read_uint32() - 2147483648, + difficulty_level=data.read_uint32() - 2147483648, ) def trigger_battle_report_panel_play_scene_event(self, data): - return dict( - battle_report_id=data.read_uint32()-2147483648, - ) + return dict(battle_report_id=data.read_uint32() - 2147483648) def trigger_battle_report_panel_selection_changed_event(self, data): - return dict( - battle_report_id=data.read_uint32()-2147483648, - ) + return dict(battle_report_id=data.read_uint32() - 2147483648) def trigger_victory_panel_play_mission_again_event(self, data): - return dict( - difficulty_level=data.read_uint32()-2147483648, - ) + return dict(difficulty_level=data.read_uint32() - 2147483648) def trigger_movie_started_event(self, data): return None @@ -815,51 +924,34 @@ def trigger_movie_finished_event(self, data): return None def decrement_game_time_remaining_event(self, data): - return dict( - decrement_ms=data.read_uint32(), - ) + return dict(decrement_ms=data.read_uint32()) def trigger_portrait_loaded_event(self, data): - return dict( - portrait_id=data.read_uint32()-2147483648, - ) + return dict(portrait_id=data.read_uint32() - 2147483648) def trigger_custom_dialog_dismissed_event(self, data): - return dict( - result=data.read_uint32()-2147483648, - ) + return dict(result=data.read_uint32() - 2147483648) def trigger_game_menu_item_selected_event(self, data): - return dict( - game_menu_item_index=data.read_uint32()-2147483648, - ) + return dict(game_menu_item_index=data.read_uint32() - 2147483648) def trigger_camera_move_event(self, data): - return dict( - reason=data.read_uint8()-128, - ) + return dict(reason=data.read_uint8() - 128) def trigger_purchase_panel_selected_purchase_item_changed_event(self, data): - return dict( - item_id=data.read_uint32()-2147483648, - ) + return dict(item_id=data.read_uint32() - 2147483648) def trigger_purchase_panel_selected_purchase_category_changed_event(self, data): - return dict( - category_id=data.read_uint32()-2147483648, - ) + return dict(category_id=data.read_uint32() - 2147483648) def trigger_button_pressed_event(self, data): - return dict( - button=data.read_uint16(), - ) + return dict(button=data.read_uint16()) def trigger_game_credits_finished_event(self, data): return None class GameEventsReader_16561(GameEventsReader_15405): - def command_event(self, data): return dict( flags=data.read_bits(17), @@ -867,32 +959,42 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint8(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) + ), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint8(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), ), - )), - 3: lambda: ('Data', dict(data=data.read_uint32())), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), - other_unit_tag=data.read_uint32() if data.read_bool() else None + other_unit_tag=data.read_uint32() if data.read_bool() else None, ) def selection_delta_event(self, data): @@ -900,17 +1002,29 @@ def selection_delta_event(self, data): control_group_index=data.read_bits(4), subgroup_index=data.read_uint8(), remove_mask={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Mask', self.read_selection_bitmask(data, data.read_uint8())), - 2: lambda: ('OneIndices', [data.read_uint8() for i in range(data.read_uint8())]), - 3: lambda: ('ZeroIndices', [data.read_uint8() for i in range(data.read_uint8())]), + 0: lambda: ("None", None), + 1: lambda: ( + "Mask", + self.read_selection_bitmask(data, data.read_uint8()), + ), + 2: lambda: ( + "OneIndices", + [data.read_uint8() for i in range(data.read_uint8())], + ), + 3: lambda: ( + "ZeroIndices", + [data.read_uint8() for i in range(data.read_uint8())], + ), }[data.read_bits(2)](), - add_subgroups=[dict( - unit_link=data.read_uint16(), - subgroup_priority=None, - intra_subgroup_priority=data.read_uint8(), - count=data.read_uint8(), - ) for i in range(data.read_uint8())], + add_subgroups=[ + dict( + unit_link=data.read_uint16(), + subgroup_priority=None, + intra_subgroup_priority=data.read_uint8(), + count=data.read_uint8(), + ) + for i in range(data.read_uint8()) + ], add_unit_tags=[data.read_uint32() for i in range(data.read_uint8())], ) @@ -919,19 +1033,26 @@ def control_group_update_event(self, data): control_group_index=data.read_bits(4), control_group_update=data.read_bits(2), remove_mask={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Mask', self.read_selection_bitmask(data, data.read_uint8())), - 2: lambda: ('OneIndices', [data.read_uint8() for i in range(data.read_uint8())]), - 3: lambda: ('ZeroIndices', [data.read_uint8() for i in range(data.read_uint8())]), + 0: lambda: ("None", None), + 1: lambda: ( + "Mask", + self.read_selection_bitmask(data, data.read_uint8()), + ), + 2: lambda: ( + "OneIndices", + [data.read_uint8() for i in range(data.read_uint8())], + ), + 3: lambda: ( + "ZeroIndices", + [data.read_uint8() for i in range(data.read_uint8())], + ), }[data.read_bits(2)](), ) def decrement_game_time_remaining_event(self, data): # really this should be set to 19, and a new GameEventsReader_41743 should be introduced that specifies 32 bits. # but I dont care about ability to read old replays. - return dict( - decrement_ms=data.read_bits(32) - ) + return dict(decrement_ms=data.read_bits(32)) class GameEventsReader_16605(GameEventsReader_16561): @@ -947,13 +1068,10 @@ class GameEventsReader_16939(GameEventsReader_16755): class GameEventsReader_17326(GameEventsReader_16939): - def __init__(self): super(GameEventsReader_17326, self).__init__() - self.EVENT_DISPATCH.update({ - 59: (None, self.trigger_mouse_moved_event), - }) + self.EVENT_DISPATCH.update({59: (None, self.trigger_mouse_moved_event)}) def bank_signature_event(self, data): return dict( @@ -965,10 +1083,7 @@ def trigger_mouse_clicked_event(self, data): return dict( button=data.read_uint32(), down=data.read_bool(), - position_ui=dict( - x=data.read_bits(11), - y=data.read_bits(11), - ), + position_ui=dict(x=data.read_bits(11), y=data.read_bits(11)), position_world=dict( x=data.read_bits(20), y=data.read_bits(20), @@ -978,14 +1093,11 @@ def trigger_mouse_clicked_event(self, data): def trigger_mouse_moved_event(self, data): return dict( - position_ui=dict( - x=data.read_bits(11), - y=data.read_bits(11), - ), + position_ui=dict(x=data.read_bits(11), y=data.read_bits(11)), position_world=dict( x=data.read_bits(20), y=data.read_bits(20), - z=data.read_uint32()-2147483648, + z=data.read_uint32() - 2147483648, ), ) @@ -995,7 +1107,6 @@ class GameEventsReader_18092(GameEventsReader_17326): class GameEventsReader_18574(GameEventsReader_18092): - def command_event(self, data): return dict( flags=data.read_bits(18), @@ -1003,32 +1114,42 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint8(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) ), - )), - 3: lambda: ('Data', dict(data=data.read_uint32())), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint8(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), + ), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), - other_unit_tag=data.read_uint32() if data.read_bool() else None + other_unit_tag=data.read_uint32() if data.read_bool() else None, ) @@ -1037,7 +1158,6 @@ class GameEventsReader_19132(GameEventsReader_18574): class GameEventsReader_19595(GameEventsReader_19132): - def command_event(self, data): return dict( flags=data.read_bits(18), @@ -1045,48 +1165,60 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint8(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=data.read_bits(4) if data.read_bool() else None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) ), - )), - 3: lambda: ('Data', dict(data=data.read_uint32())), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint8(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=data.read_bits(4) + if data.read_bool() + else None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), + ), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), - other_unit_tag=data.read_uint32() if data.read_bool() else None + other_unit_tag=data.read_uint32() if data.read_bool() else None, ) def ai_communicate_event(self, data): return dict( - beacon=data.read_uint8()-128, - ally=data.read_uint8()-128, - flags=data.read_uint8()-128, # autocast?? + beacon=data.read_uint8() - 128, + ally=data.read_uint8() - 128, + flags=data.read_uint8() - 128, # autocast?? build=None, target_unit_tag=data.read_uint32(), target_unit_link=data.read_uint16(), target_upkeep_player_id=data.read_bits(4) if data.read_bool() else None, target_control_player_id=data.read_bits(4) if data.read_bool() else None, target_point=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, - z=data.read_uint32()-2147483648, + x=data.read_uint32() - 2147483648, + y=data.read_uint32() - 2147483648, + z=data.read_uint32() - 2147483648, ), ) @@ -1096,18 +1228,19 @@ class GameEventsReader_21029(GameEventsReader_19595): class GameEventsReader_22612(GameEventsReader_21029): - def __init__(self): super(GameEventsReader_22612, self).__init__() - self.EVENT_DISPATCH.update({ - 36: (None, self.trigger_ping_event), - 60: (None, self.achievement_awarded_event), - 97: (None, self.trigger_cutscene_bookmark_fired_event), - 98: (None, self.trigger_cutscene_end_scene_fired_event), - 99: (None, self.trigger_cutscene_conversation_line_event), - 100: (None, self.trigger_cutscene_conversation_line_missing_event), - }) + self.EVENT_DISPATCH.update( + { + 36: (None, self.trigger_ping_event), + 60: (None, self.achievement_awarded_event), + 97: (None, self.trigger_cutscene_bookmark_fired_event), + 98: (None, self.trigger_cutscene_end_scene_fired_event), + 99: (None, self.trigger_cutscene_conversation_line_event), + 100: (None, self.trigger_cutscene_conversation_line_missing_event), + } + ) def user_options_event(self, data): return dict( @@ -1129,32 +1262,44 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint8(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=data.read_bits(4) if data.read_bool() else None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32()-2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) + ), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint8(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=data.read_bits(4) + if data.read_bool() + else None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), ), - )), - 3: lambda: ('Data', dict(data=data.read_uint32())), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), - other_unit_tag=data.read_uint32() if data.read_bool() else None + other_unit_tag=data.read_uint32() if data.read_bool() else None, ) def selection_delta_event(self, data): @@ -1162,17 +1307,29 @@ def selection_delta_event(self, data): control_group_index=data.read_bits(4), subgroup_index=data.read_bits(9), remove_mask={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Mask', self.read_selection_bitmask(data, data.read_bits(9))), - 2: lambda: ('OneIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), - 3: lambda: ('ZeroIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), + 0: lambda: ("None", None), + 1: lambda: ( + "Mask", + self.read_selection_bitmask(data, data.read_bits(9)), + ), + 2: lambda: ( + "OneIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), + 3: lambda: ( + "ZeroIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), }[data.read_bits(2)](), - add_subgroups=[dict( - unit_link=data.read_uint16(), - subgroup_priority=None, - intra_subgroup_priority=data.read_uint8(), - count=data.read_bits(9), - ) for i in range(data.read_bits(9))], + add_subgroups=[ + dict( + unit_link=data.read_uint16(), + subgroup_priority=None, + intra_subgroup_priority=data.read_uint8(), + count=data.read_bits(9), + ) + for i in range(data.read_bits(9)) + ], add_unit_tags=[data.read_uint32() for i in range(data.read_bits(9))], ) @@ -1181,10 +1338,19 @@ def control_group_update_event(self, data): control_group_index=data.read_bits(4), control_group_update=data.read_bits(2), remove_mask={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Mask', self.read_selection_bitmask(data, data.read_bits(9))), - 2: lambda: ('OneIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), - 3: lambda: ('ZeroIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), + 0: lambda: ("None", None), + 1: lambda: ( + "Mask", + self.read_selection_bitmask(data, data.read_bits(9)), + ), + 2: lambda: ( + "OneIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), + 3: lambda: ( + "ZeroIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), }[data.read_bits(2)](), ) @@ -1198,31 +1364,30 @@ def selection_sync_check_event(self, data): unit_tags_checksum=data.read_uint32(), subgroup_indices_checksum=data.read_uint32(), subgroups_checksum=data.read_uint32(), - ) + ), ) def ai_communicate_event(self, data): return dict( - beacon=data.read_uint8()-128, - ally=data.read_uint8()-128, - flags=data.read_uint8()-128, - build=data.read_uint8()-128, + beacon=data.read_uint8() - 128, + ally=data.read_uint8() - 128, + flags=data.read_uint8() - 128, + build=data.read_uint8() - 128, target_unit_tag=data.read_uint32(), target_unit_link=data.read_uint16(), target_upkeep_player_id=data.read_uint8(), target_control_player_id=data.read_uint8(), target_point=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, - z=data.read_uint32()-2147483648, + x=data.read_uint32() - 2147483648, + y=data.read_uint32() - 2147483648, + z=data.read_uint32() - 2147483648, ), ) def trigger_ping_event(self, data): return dict( point=dict( - x=data.read_uint32()-2147483648, - y=data.read_uint32()-2147483648, + x=data.read_uint32() - 2147483648, y=data.read_uint32() - 2147483648 ), unit_tag=data.read_uint32(), pinged_minimap=data.read_bool(), @@ -1231,42 +1396,36 @@ def trigger_ping_event(self, data): def trigger_transmission_offset_event(self, data): # I'm not actually sure when this second int is introduced.. return dict( - transmission_id=data.read_uint32()-2147483648, - thread=data.read_uint32(), + transmission_id=data.read_uint32() - 2147483648, thread=data.read_uint32() ) def achievement_awarded_event(self, data): - return dict( - achievement_link=data.read_uint16(), - ) + return dict(achievement_link=data.read_uint16()) def trigger_cutscene_bookmark_fired_event(self, data): return dict( - cutscene_id=data.read_uint32()-2147483648, + cutscene_id=data.read_uint32() - 2147483648, bookmark_name=data.read_aligned_string(data.read_bits(7)), ) def trigger_cutscene_end_scene_fired_event(self, data): - return dict( - cutscene_id=data.read_uint32()-2147483648, - ) + return dict(cutscene_id=data.read_uint32() - 2147483648) def trigger_cutscene_conversation_line_event(self, data): return dict( - cutscene_id=data.read_uint32()-2147483648, + cutscene_id=data.read_uint32() - 2147483648, conversation_line=data.read_aligned_string(data.read_bits(7)), alt_conversation_line=data.read_aligned_string(data.read_bits(7)), ) def trigger_cutscene_conversation_line_missing_event(self, data): return dict( - cutscene_id=data.read_uint32()-2147483648, + cutscene_id=data.read_uint32() - 2147483648, conversation_line=data.read_aligned_string(data.read_bits(7)), ) class GameEventsReader_23260(GameEventsReader_22612): - def trigger_sound_length_sync_event(self, data): return dict( sync_info=dict( @@ -1290,7 +1449,6 @@ def user_options_event(self, data): class GameEventsReader_HotSBeta(GameEventsReader_23260): - def user_options_event(self, data): return dict( game_fully_downloaded=data.read_bool(), @@ -1309,26 +1467,37 @@ def selection_delta_event(self, data): control_group_index=data.read_bits(4), subgroup_index=data.read_bits(9), remove_mask={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Mask', self.read_selection_bitmask(data, data.read_bits(9))), - 2: lambda: ('OneIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), - 3: lambda: ('ZeroIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), + 0: lambda: ("None", None), + 1: lambda: ( + "Mask", + self.read_selection_bitmask(data, data.read_bits(9)), + ), + 2: lambda: ( + "OneIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), + 3: lambda: ( + "ZeroIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), }[data.read_bits(2)](), - add_subgroups=[dict( - unit_link=data.read_uint16(), - subgroup_priority=data.read_uint8(), - intra_subgroup_priority=data.read_uint8(), - count=data.read_bits(9), - ) for i in range(data.read_bits(9))], + add_subgroups=[ + dict( + unit_link=data.read_uint16(), + subgroup_priority=data.read_uint8(), + intra_subgroup_priority=data.read_uint8(), + count=data.read_bits(9), + ) + for i in range(data.read_bits(9)) + ], add_unit_tags=[data.read_uint32() for i in range(data.read_bits(9))], ) def camera_update_event(self, data): return dict( - target=dict( - x=data.read_uint16(), - y=data.read_uint16(), - ) if data.read_bool() else None, + target=dict(x=data.read_uint16(), y=data.read_uint16()) + if data.read_bool() + else None, distance=data.read_uint16() if data.read_bool() else None, pitch=data.read_uint16() if data.read_bool() else None, yaw=data.read_uint16() if data.read_bool() else None, @@ -1336,40 +1505,44 @@ def camera_update_event(self, data): def trigger_dialog_control_event(self, data): return dict( - control_id=data.read_uint32()-2147483648, - event_type=data.read_uint32()-2147483648, + control_id=data.read_uint32() - 2147483648, + event_type=data.read_uint32() - 2147483648, event_data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Checked', data.read_bool()), - 2: lambda: ('ValueChanged', data.read_uint32()), - 3: lambda: ('SelectionChanged', data.read_uint32()-2147483648), - 4: lambda: ('TextChanged', data.read_aligned_string(data.read_bits(11))), - 5: lambda: ('MouseButton', data.read_uint32()) + 0: lambda: ("None", None), + 1: lambda: ("Checked", data.read_bool()), + 2: lambda: ("ValueChanged", data.read_uint32()), + 3: lambda: ("SelectionChanged", data.read_uint32() - 2147483648), + 4: lambda: ( + "TextChanged", + data.read_aligned_string(data.read_bits(11)), + ), + 5: lambda: ("MouseButton", data.read_uint32()), }[data.read_bits(3)](), ) class GameEventsReader_24247(GameEventsReader_HotSBeta): - def __init__(self): super(GameEventsReader_24247, self).__init__() - self.EVENT_DISPATCH.update({ - 7: (UserOptionsEvent, self.user_options_event), # Override - 9: (None, self.bank_file_event), # Override - 10: (None, self.bank_section_event), # Override - 11: (None, self.bank_key_event), # Override - 12: (None, self.bank_value_event), # Override - 13: (None, self.bank_signature_event), # New - 14: (None, self.camera_save_event), # New - 21: (None, self.save_game_event), # New - 22: (None, self.save_game_done_event), # Override - 23: (None, self.load_game_done_event), # Override - 43: (HijackReplayGameEvent, self.hijack_replay_game_event), # New - 62: (None, self.trigger_target_mode_update_event), # New - 101: (PlayerLeaveEvent, self.game_user_leave_event), # New - 102: (None, self.game_user_join_event), # New - }) + self.EVENT_DISPATCH.update( + { + 7: (UserOptionsEvent, self.user_options_event), # Override + 9: (None, self.bank_file_event), # Override + 10: (None, self.bank_section_event), # Override + 11: (None, self.bank_key_event), # Override + 12: (None, self.bank_value_event), # Override + 13: (None, self.bank_signature_event), # New + 14: (None, self.camera_save_event), # New + 21: (None, self.save_game_event), # New + 22: (None, self.save_game_done_event), # Override + 23: (None, self.load_game_done_event), # Override + 43: (HijackReplayGameEvent, self.hijack_replay_game_event), # New + 62: (None, self.trigger_target_mode_update_event), # New + 101: (PlayerLeaveEvent, self.game_user_leave_event), # New + 102: (None, self.game_user_join_event), # New + } + ) del self.EVENT_DISPATCH[8] del self.EVENT_DISPATCH[25] del self.EVENT_DISPATCH[76] @@ -1377,16 +1550,13 @@ def __init__(self): def bank_signature_event(self, data): return dict( signature=[data.read_uint8() for i in range(data.read_bits(5))], - toon_handle=data.read_aligned_string(data.read_bits(7)) + toon_handle=data.read_aligned_string(data.read_bits(7)), ) def camera_save_event(self, data): return dict( which=data.read_bits(3), - target=dict( - x=data.read_uint16(), - y=data.read_uint16(), - ) + target=dict(x=data.read_uint16(), y=data.read_uint16()), ) def load_game_done_event(self, data): @@ -1394,23 +1564,29 @@ def load_game_done_event(self, data): def hijack_replay_game_event(self, data): return dict( - user_infos=[dict( - game_user_id=data.read_bits(4), - observe=data.read_bits(2), - name=data.read_aligned_string(data.read_uint8()), - toon_handle=data.read_aligned_string(data.read_bits(7)) if data.read_bool() else None, - clan_tag=data.read_aligned_string(data.read_uint8()) if data.read_bool() else None, - clan_logo=None, - ) for i in range(data.read_bits(5))], + user_infos=[ + dict( + game_user_id=data.read_bits(4), + observe=data.read_bits(2), + name=data.read_aligned_string(data.read_uint8()), + toon_handle=data.read_aligned_string(data.read_bits(7)) + if data.read_bool() + else None, + clan_tag=data.read_aligned_string(data.read_uint8()) + if data.read_bool() + else None, + clan_logo=None, + ) + for i in range(data.read_bits(5)) + ], method=data.read_bits(1), ) def camera_update_event(self, data): return dict( - target=dict( - x=data.read_uint16(), - y=data.read_uint16(), - ) if data.read_bool() else None, + target=dict(x=data.read_uint16(), y=data.read_uint16()) + if data.read_bool() + else None, distance=data.read_uint16() if data.read_bool() else None, pitch=data.read_uint16() if data.read_bool() else None, yaw=data.read_uint16() if data.read_bool() else None, @@ -1421,7 +1597,7 @@ def trigger_target_mode_update_event(self, data): return dict( ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), - state=data.read_uint8()-128, + state=data.read_uint8() - 128, ) def game_user_leave_event(self, data): @@ -1431,14 +1607,17 @@ def game_user_join_event(self, data): return dict( observe=data.read_bits(2), name=data.read_aligned_string(data.read_bits(8)), - toon_handle=data.read_aligned_string(data.read_bits(7)) if data.read_bool() else None, - clan_tag=data.read_aligned_string(data.read_uint8()) if data.read_bool() else None, + toon_handle=data.read_aligned_string(data.read_bits(7)) + if data.read_bool() + else None, + clan_tag=data.read_aligned_string(data.read_uint8()) + if data.read_bool() + else None, clan_log=None, ) class GameEventsReader_26490(GameEventsReader_24247): - def user_options_event(self, data): return dict( game_fully_downloaded=data.read_bool(), @@ -1456,10 +1635,7 @@ def trigger_mouse_clicked_event(self, data): return dict( button=data.read_uint32(), down=data.read_bool(), - position_ui=dict( - x=data.read_bits(11), - y=data.read_bits(11), - ), + position_ui=dict(x=data.read_bits(11), y=data.read_bits(11)), position_world=dict( x=data.read_bits(20) - 2147483648, y=data.read_bits(20) - 2147483648, @@ -1470,10 +1646,7 @@ def trigger_mouse_clicked_event(self, data): def trigger_mouse_moved_event(self, data): return dict( - position_ui=dict( - x=data.read_bits(11), - y=data.read_bits(11), - ), + position_ui=dict(x=data.read_bits(11), y=data.read_bits(11)), position_world=dict( x=data.read_bits(20), y=data.read_bits(20), @@ -1484,26 +1657,33 @@ def trigger_mouse_moved_event(self, data): class GameEventsReader_27950(GameEventsReader_26490): - def hijack_replay_game_event(self, data): return dict( - user_infos=[dict( - game_user_id=data.read_bits(4), - observe=data.read_bits(2), - name=data.read_aligned_string(data.read_uint8()), - toon_handle=data.read_aligned_string(data.read_bits(7)) if data.read_bool() else None, - clan_tag=data.read_aligned_string(data.read_uint8()) if data.read_bool() else None, - clan_logo=DepotFile(data.read_aligned_bytes(40)) if data.read_bool() else None, - ) for i in range(data.read_bits(5))], + user_infos=[ + dict( + game_user_id=data.read_bits(4), + observe=data.read_bits(2), + name=data.read_aligned_string(data.read_uint8()), + toon_handle=data.read_aligned_string(data.read_bits(7)) + if data.read_bool() + else None, + clan_tag=data.read_aligned_string(data.read_uint8()) + if data.read_bool() + else None, + clan_logo=DepotFile(data.read_aligned_bytes(40)) + if data.read_bool() + else None, + ) + for i in range(data.read_bits(5)) + ], method=data.read_bits(1), ) def camera_update_event(self, data): return dict( - target=dict( - x=data.read_uint16(), - y=data.read_uint16(), - ) if data.read_bool() else None, + target=dict(x=data.read_uint16(), y=data.read_uint16()) + if data.read_bool() + else None, distance=data.read_uint16() if data.read_bool() else None, pitch=data.read_uint16() if data.read_bool() else None, yaw=data.read_uint16() if data.read_bool() else None, @@ -1514,43 +1694,56 @@ def game_user_join_event(self, data): return dict( observe=data.read_bits(2), name=data.read_aligned_string(data.read_bits(8)), - toon_handle=data.read_aligned_string(data.read_bits(7)) if data.read_bool() else None, - clan_tag=data.read_aligned_string(data.read_uint8()) if data.read_bool() else None, - clan_logo=DepotFile(data.read_aligned_bytes(40)) if data.read_bool() else None, + toon_handle=data.read_aligned_string(data.read_bits(7)) + if data.read_bool() + else None, + clan_tag=data.read_aligned_string(data.read_uint8()) + if data.read_bool() + else None, + clan_logo=DepotFile(data.read_aligned_bytes(40)) + if data.read_bool() + else None, ) -class GameEventsReader_34784(GameEventsReader_27950): +class GameEventsReader_34784(GameEventsReader_27950): def __init__(self): super(GameEventsReader_34784, self).__init__() - self.EVENT_DISPATCH.update({ - 25: (None, self.command_manager_reset_event), # Re-using this old number - 61: (None, self.trigger_hotkey_pressed_event), - 103: (None, self.command_manager_state_event), - 104: (UpdateTargetPointCommandEvent, self.command_update_target_point_event), - 105: (UpdateTargetUnitCommandEvent, self.command_update_target_unit_event), - 106: (None, self.trigger_anim_length_query_by_name_event), - 107: (None, self.trigger_anim_length_query_by_props_event), - 108: (None, self.trigger_anim_offset_event), - 109: (None, self.catalog_modify_event), - 110: (None, self.hero_talent_tree_selected_event), - 111: (None, self.trigger_profiler_logging_finished_event), - 112: (None, self.hero_talent_tree_selection_panel_toggled_event), - }) + self.EVENT_DISPATCH.update( + { + 25: ( + None, + self.command_manager_reset_event, + ), # Re-using this old number + 61: (None, self.trigger_hotkey_pressed_event), + 103: (None, self.command_manager_state_event), + 104: ( + UpdateTargetPointCommandEvent, + self.command_update_target_point_event, + ), + 105: ( + UpdateTargetUnitCommandEvent, + self.command_update_target_unit_event, + ), + 106: (None, self.trigger_anim_length_query_by_name_event), + 107: (None, self.trigger_anim_length_query_by_props_event), + 108: (None, self.trigger_anim_offset_event), + 109: (None, self.catalog_modify_event), + 110: (None, self.hero_talent_tree_selected_event), + 111: (None, self.trigger_profiler_logging_finished_event), + 112: (None, self.hero_talent_tree_selection_panel_toggled_event), + } + ) def hero_talent_tree_selection_panel_toggled_event(self, data): - return dict( - shown=data.read_bool(), - ) + return dict(shown=data.read_bool()) def trigger_profiler_logging_finished_event(self, data): return dict() def hero_talent_tree_selected_event(self, data): - return dict( - index=data.read_uint32() - ) + return dict(index=data.read_uint32()) def catalog_modify_event(self, data): return dict( @@ -1561,15 +1754,10 @@ def catalog_modify_event(self, data): ) def trigger_anim_offset_event(self, data): - return dict( - anim_wait_query_id=data.read_uint16(), - ) + return dict(anim_wait_query_id=data.read_uint16()) def trigger_anim_length_query_by_props_event(self, data): - return dict( - query_id=data.read_uint16(), - length_ms=data.read_uint32(), - ) + return dict(query_id=data.read_uint16(), length_ms=data.read_uint32()) def trigger_anim_length_query_by_name_event(self, data): return dict( @@ -1579,9 +1767,7 @@ def trigger_anim_length_query_by_name_event(self, data): ) def command_manager_reset_event(self, data): - return dict( - sequence=data.read_uint32(), - ) + return dict(sequence=data.read_uint32()) def command_manager_state_event(self, data): return dict( @@ -1593,13 +1779,16 @@ def command_update_target_point_event(self, data): return dict( flags=0, # fill me with previous TargetPointEvent.flags ability=None, # fill me with previous TargetPointEvent.ability - data=('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_bits(32) - 2147483648, + data=( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_bits(32) - 2147483648, + ) ), - )), + ), sequence=0, # fill me with previous TargetPointEvent.flags other_unit_tag=None, # fill me with previous TargetPointEvent.flags unit_group=None, # fill me with previous TargetPointEvent.flags @@ -1607,24 +1796,27 @@ def command_update_target_point_event(self, data): def command_update_target_unit_event(self, data): return dict( - flags=0, # fill me with previous TargetUnitEvent.flags + flags=0, # fill me with previous TargetUnitEvent.flags ability=None, # fill me with previous TargetUnitEvent.ability - data=('TargetUnit', dict( - flags=data.read_uint16(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=data.read_bits(4) if data.read_bool() else None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_bits(32) - 2147483648, + data=( + "TargetUnit", + dict( + flags=data.read_uint16(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=data.read_bits(4) if data.read_bool() else None, + upkeep_player_id=data.read_bits(4) if data.read_bool() else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_bits(32) - 2147483648, + ), ), - )), - sequence=0, # fill me with previous TargetUnitEvent.flags - other_unit_tag=None, # fill me with previous TargetUnitEvent.flags - unit_group=None, # fill me with previous TargetUnitEvent.flags + ), + sequence=0, # fill me with previous TargetUnitEvent.flags + other_unit_tag=None, # fill me with previous TargetUnitEvent.flags + unit_group=None, # fill me with previous TargetUnitEvent.flags ) def command_event(self, data): @@ -1634,32 +1826,42 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32() - 2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint16(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=data.read_bits(4) if data.read_bool() else None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32() - 2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) ), - )), - 3: lambda: ('Data', dict( - data=data.read_uint32() - )), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint16(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=data.read_bits(4) + if data.read_bool() + else None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), + ), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), sequence=data.read_uint32() + 1, other_unit_tag=data.read_uint32() if data.read_bool() else None, @@ -1689,8 +1891,7 @@ def user_options_event(self, data): def trigger_ping_event(self, data): return dict( point=dict( - x=data.read_uint32() - 2147483648, - y=data.read_uint32() - 2147483648, + x=data.read_uint32() - 2147483648, y=data.read_uint32() - 2147483648 ), unit_tag=data.read_uint32(), pinged_minimap=data.read_bool(), @@ -1699,10 +1900,9 @@ def trigger_ping_event(self, data): def camera_update_event(self, data): return dict( - target=dict( - x=data.read_uint16(), - y=data.read_uint16(), - ) if data.read_bool() else None, + target=dict(x=data.read_uint16(), y=data.read_uint16()) + if data.read_bool() + else None, distance=data.read_uint16() if data.read_bool() else None, pitch=data.read_uint16() if data.read_bool() else None, yaw=data.read_uint16() if data.read_bool() else None, @@ -1711,50 +1911,62 @@ def camera_update_event(self, data): ) def trigger_hotkey_pressed_event(self, data): - return dict( - hotkey=data.read_uint32(), - down=data.read_bool(), - ) + return dict(hotkey=data.read_uint32(), down=data.read_bool()) def game_user_join_event(self, data): return dict( observe=data.read_bits(2), name=data.read_aligned_string(data.read_bits(8)), - toon_handle=data.read_aligned_string(data.read_bits(7)) if data.read_bool() else None, - clan_tag=data.read_aligned_string(data.read_uint8()) if data.read_bool() else None, - clan_logo=DepotFile(data.read_aligned_bytes(40)) if data.read_bool() else None, + toon_handle=data.read_aligned_string(data.read_bits(7)) + if data.read_bool() + else None, + clan_tag=data.read_aligned_string(data.read_uint8()) + if data.read_bool() + else None, + clan_logo=DepotFile(data.read_aligned_bytes(40)) + if data.read_bool() + else None, hijack=data.read_bool(), hijack_clone_game_user_id=data.read_bits(4) if data.read_bool() else None, ) def game_user_leave_event(self, data): - return dict( - leave_reason=data.read_bits(4) - ) + return dict(leave_reason=data.read_bits(4)) -class GameEventsReader_36442(GameEventsReader_34784): +class GameEventsReader_36442(GameEventsReader_34784): def control_group_update_event(self, data): return dict( control_group_index=data.read_bits(4), control_group_update=data.read_bits(3), remove_mask={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('Mask', self.read_selection_bitmask(data, data.read_bits(9))), - 2: lambda: ('OneIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), - 3: lambda: ('ZeroIndices', [data.read_bits(9) for i in range(data.read_bits(9))]), + 0: lambda: ("None", None), + 1: lambda: ( + "Mask", + self.read_selection_bitmask(data, data.read_bits(9)), + ), + 2: lambda: ( + "OneIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), + 3: lambda: ( + "ZeroIndices", + [data.read_bits(9) for i in range(data.read_bits(9))], + ), }[data.read_bits(2)](), ) -class GameEventsReader_38215(GameEventsReader_36442): +class GameEventsReader_38215(GameEventsReader_36442): def __init__(self): super(GameEventsReader_38215, self).__init__() - self.EVENT_DISPATCH.update({ - 76: (None, self.trigger_command_error_event), - 92: (None, self.trigger_mousewheel_event), # 172 in protocol38125.py - }) + self.EVENT_DISPATCH.update( + { + 76: (None, self.trigger_command_error_event), + 92: (None, self.trigger_mousewheel_event), # 172 in protocol38125.py + } + ) def trigger_command_error_event(self, data): return dict( @@ -1763,14 +1975,16 @@ def trigger_command_error_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, ) def trigger_mousewheel_event(self, data): # 172 in protocol38125.py return dict( - wheelspin=data.read_uint16()-32768, # 171 in protocol38125.py - flags=data.read_uint8() - 128, # 112 in protocol38125.py + wheelspin=data.read_uint16() - 32768, # 171 in protocol38125.py + flags=data.read_uint8() - 128, # 112 in protocol38125.py ) def command_event(self, data): @@ -1782,32 +1996,42 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32() - 2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint16(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=data.read_bits(4) if data.read_bool() else None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32() - 2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) + ), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint16(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=data.read_bits(4) + if data.read_bool() + else None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), ), - )), - 3: lambda: ('Data', dict( - data=data.read_uint32() - )), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), sequence=data.read_uint32() + 1, other_unit_tag=data.read_uint32() if data.read_bool() else None, @@ -1834,49 +2058,48 @@ def user_options_event(self, data): use_ai_beacons=None, ) -class GameEventsReader_38749(GameEventsReader_38215): +class GameEventsReader_38749(GameEventsReader_38215): def trigger_ping_event(self, data): return dict( point=dict( - x=data.read_uint32() - 2147483648, - y=data.read_uint32() - 2147483648, + x=data.read_uint32() - 2147483648, y=data.read_uint32() - 2147483648 ), unit_tag=data.read_uint32(), unit_link=data.read_uint16(), unit_control_player_id=(data.read_bits(4) if data.read_bool() else None), unit_upkeep_player_id=(data.read_bits(4) if data.read_bool() else None), unit_position=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_bits(32) - 2147483648, - ), + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_bits(32) - 2147483648, + ), pinged_minimap=data.read_bool(), option=data.read_uint32() - 2147483648, ) -class GameEventsReader_38996(GameEventsReader_38749): +class GameEventsReader_38996(GameEventsReader_38749): def trigger_ping_event(self, data): return dict( point=dict( - x=data.read_uint32() - 2147483648, - y=data.read_uint32() - 2147483648, + x=data.read_uint32() - 2147483648, y=data.read_uint32() - 2147483648 ), unit_tag=data.read_uint32(), unit_link=data.read_uint16(), unit_control_player_id=(data.read_bits(4) if data.read_bool() else None), unit_upkeep_player_id=(data.read_bits(4) if data.read_bool() else None), unit_position=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_bits(32) - 2147483648, - ), + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_bits(32) - 2147483648, + ), unit_is_under_construction=data.read_bool(), pinged_minimap=data.read_bool(), option=data.read_uint32() - 2147483648, ) + class GameEventsReader_64469(GameEventsReader_38996): # this function is exactly the same as command_event() from GameEventsReader_38996 @@ -1888,32 +2111,42 @@ def command_event(self, data): ability_link=data.read_uint16(), ability_command_index=data.read_bits(5), ability_command_data=data.read_uint8() if data.read_bool() else None, - ) if data.read_bool() else None, + ) + if data.read_bool() + else None, data={ # Choice - 0: lambda: ('None', None), - 1: lambda: ('TargetPoint', dict( - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32() - 2147483648, - ) - )), - 2: lambda: ('TargetUnit', dict( - flags=data.read_uint16(), - timer=data.read_uint8(), - unit_tag=data.read_uint32(), - unit_link=data.read_uint16(), - control_player_id=data.read_bits(4) if data.read_bool() else None, - upkeep_player_id=data.read_bits(4) if data.read_bool() else None, - point=dict( - x=data.read_bits(20), - y=data.read_bits(20), - z=data.read_uint32() - 2147483648, + 0: lambda: ("None", None), + 1: lambda: ( + "TargetPoint", + dict( + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ) + ), + ), + 2: lambda: ( + "TargetUnit", + dict( + flags=data.read_uint16(), + timer=data.read_uint8(), + unit_tag=data.read_uint32(), + unit_link=data.read_uint16(), + control_player_id=data.read_bits(4) + if data.read_bool() + else None, + upkeep_player_id=data.read_bits(4) + if data.read_bool() + else None, + point=dict( + x=data.read_bits(20), + y=data.read_bits(20), + z=data.read_uint32() - 2147483648, + ), ), - )), - 3: lambda: ('Data', dict( - data=data.read_uint32() - )), + ), + 3: lambda: ("Data", dict(data=data.read_uint32())), }[data.read_bits(2)](), sequence=data.read_uint32() + 1, other_unit_tag=data.read_uint32() if data.read_bool() else None, @@ -1929,24 +2162,18 @@ class GameEventsReader_65895(GameEventsReader_64469): def __init__(self): super(GameEventsReader_65895, self).__init__() - self.EVENT_DISPATCH.update({ - 116: (None, self.set_sync_loading), - 117: (None, self.set_sync_playing), - }) + self.EVENT_DISPATCH.update( + {116: (None, self.set_sync_loading), 117: (None, self.set_sync_playing)} + ) def set_sync_loading(self, data): - return dict( - sync_load=data.read_uint32() - ) + return dict(sync_load=data.read_uint32()) def set_sync_playing(self, data): - return dict( - sync_load=data.read_uint32() - ) + return dict(sync_load=data.read_uint32()) class TrackerEventsReader(object): - def __init__(self): self.EVENT_DISPATCH = { 0: PlayerStatsEvent, diff --git a/sc2reader/resources.py b/sc2reader/resources.py index 32ac05bc..8c1d7110 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -16,7 +16,16 @@ from sc2reader import exceptions from sc2reader.data import datapacks from sc2reader.exceptions import SC2ReaderLocalizationError, CorruptTrackerFileError -from sc2reader.objects import Participant, Observer, Computer, Team, PlayerSummary, Graph, BuildEntry, MapInfo +from sc2reader.objects import ( + Participant, + Observer, + Computer, + Team, + PlayerSummary, + Graph, + BuildEntry, + MapInfo, +) from sc2reader.constants import GAME_SPEED_FACTOR, LOBBY_PROPERTIES @@ -25,9 +34,9 @@ def __init__(self, file_object, filename=None, factory=None, **options): self.factory = factory self.opt = options self.logger = log_utils.get_logger(self.__class__) - self.filename = filename or getattr(file_object, 'name', 'Unavailable') + self.filename = filename or getattr(file_object, "name", "Unavailable") - if hasattr(file_object, 'seek'): + if hasattr(file_object, "seek"): file_object.seek(0) self.filehash = hashlib.sha256(file_object.read()).hexdigest() file_object.seek(0) @@ -192,7 +201,15 @@ class Replay(Resource): #: Lists info for each user that is resuming from replay. resume_user_info = None - def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.engine, do_tracker_events=True, **options): + def __init__( + self, + replay_file, + filename=None, + load_level=4, + engine=sc2reader.engine, + do_tracker_events=True, + **options + ): super(Replay, self).__init__(replay_file, filename, **options) self.datapack = None self.raw_data = dict() @@ -254,7 +271,7 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en except Exception as e: raise exceptions.MPQError("Unable to construct the MPQArchive", e) - header_content = self.archive.header['user_data_header']['content'] + header_content = self.archive.header["user_data_header"]["content"] header_data = BitPackedDecoder(header_content).read_struct() self.versions = list(header_data[1].values()) self.frames = header_data[3] @@ -262,21 +279,23 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en self.base_build = self.versions[5] self.release_string = "{0}.{1}.{2}.{3}".format(*self.versions[1:5]) fps = self.game_fps - if (34784 <= self.build): # lotv replay, adjust time + if 34784 <= self.build: # lotv replay, adjust time fps = self.game_fps * 1.4 - self.length = self.game_length = self.real_length = utils.Length(seconds=int(self.frames/fps)) + self.length = self.game_length = self.real_length = utils.Length( + seconds=int(self.frames / fps) + ) # Load basic details if requested # .backup files are read in case the main files are missing or removed if load_level >= 1: self.load_level = 1 files = [ - 'replay.initData.backup', - 'replay.details.backup', - 'replay.attributes.events', - 'replay.initData', - 'replay.details' + "replay.initData.backup", + "replay.details.backup", + "replay.attributes.events", + "replay.initData", + "replay.details", ] for data_file in files: self._read_data(data_file, self._get_reader(data_file)) @@ -284,13 +303,13 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en self.datapack = self._get_datapack() # Can only be effective if map data has been loaded - if options.get('load_map', False): + if options.get("load_map", False): self.load_map() # Load players if requested if load_level >= 2: self.load_level = 2 - for data_file in ['replay.message.events']: + for data_file in ["replay.message.events"]: self._read_data(data_file, self._get_reader(data_file)) self.load_message_events() self.load_players() @@ -298,87 +317,103 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en # Load tracker events if requested if load_level >= 3 and do_tracker_events: self.load_level = 3 - for data_file in ['replay.tracker.events']: + for data_file in ["replay.tracker.events"]: self._read_data(data_file, self._get_reader(data_file)) self.load_tracker_events() # Load events if requested if load_level >= 4: self.load_level = 4 - for data_file in ['replay.game.events']: + for data_file in ["replay.game.events"]: self._read_data(data_file, self._get_reader(data_file)) self.load_game_events() # Run this replay through the engine as indicated if engine: - resume_events = [ev for ev in self.game_events if ev.name == 'HijackReplayGameEvent'] - if self.base_build <= 26490 and self.tracker_events and len(resume_events) > 0: + resume_events = [ + ev for ev in self.game_events if ev.name == "HijackReplayGameEvent" + ] + if ( + self.base_build <= 26490 + and self.tracker_events + and len(resume_events) > 0 + ): raise CorruptTrackerFileError( - "Cannot run engine on resumed games with tracker events. Run again with the " + - "do_tracker_events=False option to generate context without tracker events.") + "Cannot run engine on resumed games with tracker events. Run again with the " + + "do_tracker_events=False option to generate context without tracker events." + ) engine.run(self) def load_init_data(self): - if 'replay.initData' in self.raw_data: - initData = self.raw_data['replay.initData'] - elif 'replay.initData.backup' in self.raw_data: - initData = self.raw_data['replay.initData.backup'] + if "replay.initData" in self.raw_data: + initData = self.raw_data["replay.initData"] + elif "replay.initData.backup" in self.raw_data: + initData = self.raw_data["replay.initData.backup"] else: return - options = initData['game_description']['game_options'] - self.amm = options['amm'] - self.ranked = options['ranked'] - self.competitive = options['competitive'] - self.practice = options['practice'] - self.cooperative = options['cooperative'] - self.battle_net = options['battle_net'] - self.hero_duplicates_allowed = options['hero_duplicates_allowed'] + options = initData["game_description"]["game_options"] + self.amm = options["amm"] + self.ranked = options["ranked"] + self.competitive = options["competitive"] + self.practice = options["practice"] + self.cooperative = options["cooperative"] + self.battle_net = options["battle_net"] + self.hero_duplicates_allowed = options["hero_duplicates_allowed"] def load_attribute_events(self): - if 'replay.attributes.events' in self.raw_data: + if "replay.attributes.events" in self.raw_data: # Organize the attribute data to be useful self.attributes = defaultdict(dict) - attributesEvents = self.raw_data['replay.attributes.events'] + attributesEvents = self.raw_data["replay.attributes.events"] for attr in attributesEvents: self.attributes[attr.player][attr.name] = attr.value # Populate replay with attributes - self.speed = self.attributes[16]['Game Speed'] - self.category = self.attributes[16]['Game Mode'] - self.type = self.game_type = self.attributes[16]['Teams'] - self.is_ladder = (self.category == "Ladder") - self.is_private = (self.category == "Private") + self.speed = self.attributes[16]["Game Speed"] + self.category = self.attributes[16]["Game Mode"] + self.type = self.game_type = self.attributes[16]["Teams"] + self.is_ladder = self.category == "Ladder" + self.is_private = self.category == "Private" def load_details(self): - if 'replay.details' in self.raw_data: - details = self.raw_data['replay.details'] - elif 'replay.details.backup' in self.raw_data: - details = self.raw_data['replay.details.backup'] + if "replay.details" in self.raw_data: + details = self.raw_data["replay.details"] + elif "replay.details.backup" in self.raw_data: + details = self.raw_data["replay.details.backup"] else: return - self.map_name = details['map_name'] - self.region = details['cache_handles'][0].server.lower() - self.map_hash = details['cache_handles'][-1].hash - self.map_file = details['cache_handles'][-1] + self.map_name = details["map_name"] + self.region = details["cache_handles"][0].server.lower() + self.map_hash = details["cache_handles"][-1].hash + self.map_file = details["cache_handles"][-1] # Expand this special case mapping - if self.region == 'sg': - self.region = 'sea' - - dependency_hashes = [d.hash for d in details['cache_handles']] - if hashlib.sha256('Standard Data: Void.SC2Mod'.encode('utf8')).hexdigest() in dependency_hashes: - self.expansion = 'LotV' - elif hashlib.sha256('Standard Data: Swarm.SC2Mod'.encode('utf8')).hexdigest() in dependency_hashes: - self.expansion = 'HotS' - elif hashlib.sha256('Standard Data: Liberty.SC2Mod'.encode('utf8')).hexdigest() in dependency_hashes: - self.expansion = 'WoL' + if self.region == "sg": + self.region = "sea" + + dependency_hashes = [d.hash for d in details["cache_handles"]] + if ( + hashlib.sha256("Standard Data: Void.SC2Mod".encode("utf8")).hexdigest() + in dependency_hashes + ): + self.expansion = "LotV" + elif ( + hashlib.sha256("Standard Data: Swarm.SC2Mod".encode("utf8")).hexdigest() + in dependency_hashes + ): + self.expansion = "HotS" + elif ( + hashlib.sha256("Standard Data: Liberty.SC2Mod".encode("utf8")).hexdigest() + in dependency_hashes + ): + self.expansion = "WoL" else: - self.expansion = '' + self.expansion = "" - self.windows_timestamp = details['file_time'] + self.windows_timestamp = details["file_time"] self.unix_timestamp = utils.windows_to_unix(self.windows_timestamp) self.end_time = datetime.utcfromtimestamp(self.unix_timestamp) @@ -386,14 +421,22 @@ def load_details(self): # the value required to get the adjusted timestamp. We know the upper # limit for any adjustment number so use that to distinguish between # the two cases. - if details['utc_adjustment'] < 10**7*60*60*24: - self.time_zone = details['utc_adjustment']/(10**7*60*60) + if details["utc_adjustment"] < 10 ** 7 * 60 * 60 * 24: + self.time_zone = details["utc_adjustment"] / (10 ** 7 * 60 * 60) else: - self.time_zone = (details['utc_adjustment']-details['file_time'])/(10**7*60*60) + self.time_zone = (details["utc_adjustment"] - details["file_time"]) / ( + 10 ** 7 * 60 * 60 + ) self.game_length = self.length - self.real_length = utils.Length(seconds=int(self.length.seconds/GAME_SPEED_FACTOR[self.expansion][self.speed])) - self.start_time = datetime.utcfromtimestamp(self.unix_timestamp-self.real_length.seconds) + self.real_length = utils.Length( + seconds=int( + self.length.seconds / GAME_SPEED_FACTOR[self.expansion][self.speed] + ) + ) + self.start_time = datetime.utcfromtimestamp( + self.unix_timestamp - self.real_length.seconds + ) self.date = self.end_time # backwards compatibility def load_all_details(self): @@ -407,18 +450,18 @@ def load_map(self): def load_players(self): # If we don't at least have details and attributes_events we can go no further # We can use the backup detail files if the main files have been removed - if 'replay.details' in self.raw_data: - details = self.raw_data['replay.details'] - elif 'replay.details.backup' in self.raw_data: - details = self.raw_data['replay.details.backup'] + if "replay.details" in self.raw_data: + details = self.raw_data["replay.details"] + elif "replay.details.backup" in self.raw_data: + details = self.raw_data["replay.details.backup"] else: return - if 'replay.attributes.events' not in self.raw_data: + if "replay.attributes.events" not in self.raw_data: return - if 'replay.initData' in self.raw_data: - initData = self.raw_data['replay.initData'] - elif 'replay.initData.backup' in self.raw_data: - initData = self.raw_data['replay.initData.backup'] + if "replay.initData" in self.raw_data: + initData = self.raw_data["replay.initData"] + elif "replay.initData.backup" in self.raw_data: + initData = self.raw_data["replay.initData.backup"] else: return @@ -433,30 +476,56 @@ def load_players(self): # Assume that the first X map slots starting at 1 are player slots # so that we can assign player ids without the map self.entities = list() - for slot_id, slot_data in enumerate(initData['lobby_state']['slots']): - user_id = slot_data['user_id'] - - if slot_data['control'] == 2: - if slot_data['observe'] == 0: - self.entities.append(Participant(slot_id, slot_data, user_id, initData['user_initial_data'][user_id], player_id, details['players'][detail_id], self.attributes.get(player_id, dict()))) + for slot_id, slot_data in enumerate(initData["lobby_state"]["slots"]): + user_id = slot_data["user_id"] + + if slot_data["control"] == 2: + if slot_data["observe"] == 0: + self.entities.append( + Participant( + slot_id, + slot_data, + user_id, + initData["user_initial_data"][user_id], + player_id, + details["players"][detail_id], + self.attributes.get(player_id, dict()), + ) + ) detail_id += 1 player_id += 1 else: - self.entities.append(Observer(slot_id, slot_data, user_id, initData['user_initial_data'][user_id], player_id)) + self.entities.append( + Observer( + slot_id, + slot_data, + user_id, + initData["user_initial_data"][user_id], + player_id, + ) + ) player_id += 1 - elif slot_data['control'] == 3 and detail_id < len(details['players']): + elif slot_data["control"] == 3 and detail_id < len(details["players"]): # detail_id check needed for coop - self.entities.append(Computer(slot_id, slot_data, player_id, details['players'][detail_id], self.attributes.get(player_id, dict()))) + self.entities.append( + Computer( + slot_id, + slot_data, + player_id, + details["players"][detail_id], + self.attributes.get(player_id, dict()), + ) + ) detail_id += 1 player_id += 1 def get_team(team_id): if team_id is not None and team_id not in self.team: - team = Team(team_id) - self.team[team_id] = team - self.teams.append(team) + team = Team(team_id) + self.team[team_id] = team + self.teams.append(team) return self.team[team_id] # Set up all our cross reference data structures @@ -485,11 +554,13 @@ def get_team(team_id): results = set([p.result for p in team.players]) if len(results) == 1: team.result = list(results)[0] - if team.result == 'Win': + if team.result == "Win": self.winner = team else: - self.logger.warn("Conflicting results for Team {0}: {1}".format(team.number, results)) - team.result = 'Unknown' + self.logger.warn( + "Conflicting results for Team {0}: {1}".format(team.number, results) + ) + team.result = "Unknown" self.teams.sort(key=lambda t: t.number) @@ -513,8 +584,8 @@ def get_team(team_id): self.recorder = None entity_names = sorted(map(lambda p: p.name, self.entities)) - hash_input = self.region+":"+','.join(entity_names) - self.people_hash = hashlib.sha256(hash_input.encode('utf8')).hexdigest() + hash_input = self.region + ":" + ",".join(entity_names) + self.people_hash = hashlib.sha256(hash_input.encode("utf8")).hexdigest() # The presence of observers and/or computer players makes this not actually ladder # This became an issue in HotS where Training, vs AI, Unranked, and Ranked @@ -523,35 +594,39 @@ def get_team(team_id): self.is_ladder = False def load_message_events(self): - if 'replay.message.events' not in self.raw_data: + if "replay.message.events" not in self.raw_data: return - self.messages = self.raw_data['replay.message.events']['messages'] - self.pings = self.raw_data['replay.message.events']['pings'] - self.packets = self.raw_data['replay.message.events']['packets'] + self.messages = self.raw_data["replay.message.events"]["messages"] + self.pings = self.raw_data["replay.message.events"]["pings"] + self.packets = self.raw_data["replay.message.events"]["packets"] - self.message_events = self.messages+self.pings+self.packets + self.message_events = self.messages + self.pings + self.packets self.events = sorted(self.events + self.message_events, key=lambda e: e.frame) def load_game_events(self): # Copy the events over # TODO: the events need to be fixed both on the reader and processor side - if 'replay.game.events' not in self.raw_data: + if "replay.game.events" not in self.raw_data: return - self.game_events = self.raw_data['replay.game.events'] - self.events = sorted(self.events+self.game_events, key=lambda e: e.frame) + self.game_events = self.raw_data["replay.game.events"] + self.events = sorted(self.events + self.game_events, key=lambda e: e.frame) # hideous hack for HotS 2.0.0.23925, see https://github.com/GraylinKim/sc2reader/issues/87 - if self.base_build == 23925 and self.events and self.events[-1].frame > self.frames: + if ( + self.base_build == 23925 + and self.events + and self.events[-1].frame > self.frames + ): self.frames = self.events[-1].frame - self.length = utils.Length(seconds=int(self.frames/self.game_fps)) + self.length = utils.Length(seconds=int(self.frames / self.game_fps)) def load_tracker_events(self): - if 'replay.tracker.events' not in self.raw_data: + if "replay.tracker.events" not in self.raw_data: return - self.tracker_events = self.raw_data['replay.tracker.events'] + self.tracker_events = self.raw_data["replay.tracker.events"] self.events = sorted(self.tracker_events + self.events, key=lambda e: e.frame) def register_reader(self, data_file, reader, filterfunc=lambda r: True): @@ -596,55 +671,195 @@ def register_datapack(self, datapack, filterfunc=lambda r: True): # Override points def register_default_readers(self): """Registers factory default readers.""" - self.register_reader('replay.details', readers.DetailsReader(), lambda r: True) - self.register_reader('replay.initData', readers.InitDataReader(), lambda r: True) - self.register_reader('replay.details.backup', readers.DetailsReader(), lambda r: True) - self.register_reader('replay.initData.backup', readers.InitDataReader(), lambda r: True) - self.register_reader('replay.tracker.events', readers.TrackerEventsReader(), lambda r: True) - self.register_reader('replay.message.events', readers.MessageEventsReader(), lambda r: True) - self.register_reader('replay.attributes.events', readers.AttributesEventsReader(), lambda r: True) - - self.register_reader('replay.game.events', readers.GameEventsReader_15405(), lambda r: 15405 <= r.base_build < 16561) - self.register_reader('replay.game.events', readers.GameEventsReader_16561(), lambda r: 16561 <= r.base_build < 17326) - self.register_reader('replay.game.events', readers.GameEventsReader_17326(), lambda r: 17326 <= r.base_build < 18574) - self.register_reader('replay.game.events', readers.GameEventsReader_18574(), lambda r: 18574 <= r.base_build < 19595) - self.register_reader('replay.game.events', readers.GameEventsReader_19595(), lambda r: 19595 <= r.base_build < 22612) - self.register_reader('replay.game.events', readers.GameEventsReader_22612(), lambda r: 22612 <= r.base_build < 23260) - self.register_reader('replay.game.events', readers.GameEventsReader_23260(), lambda r: 23260 <= r.base_build < 24247) - self.register_reader('replay.game.events', readers.GameEventsReader_24247(), lambda r: 24247 <= r.base_build < 26490) - self.register_reader('replay.game.events', readers.GameEventsReader_26490(), lambda r: 26490 <= r.base_build < 27950) - self.register_reader('replay.game.events', readers.GameEventsReader_27950(), lambda r: 27950 <= r.base_build < 34784) - self.register_reader('replay.game.events', readers.GameEventsReader_34784(), lambda r: 34784 <= r.base_build < 36442) - self.register_reader('replay.game.events', readers.GameEventsReader_36442(), lambda r: 36442 <= r.base_build < 38215) - self.register_reader('replay.game.events', readers.GameEventsReader_38215(), lambda r: 38215 <= r.base_build < 38749) - self.register_reader('replay.game.events', readers.GameEventsReader_38749(), lambda r: 38749 <= r.base_build < 38996) - self.register_reader('replay.game.events', readers.GameEventsReader_38996(), lambda r: 38996 <= r.base_build < 64469) - self.register_reader('replay.game.events', readers.GameEventsReader_64469(), lambda r: 64469 <= r.base_build < 65895) - self.register_reader('replay.game.events', readers.GameEventsReader_65895(), lambda r: 65895 <= r.base_build) - self.register_reader('replay.game.events', readers.GameEventsReader_HotSBeta(), lambda r: r.versions[1] == 2 and r.build < 24247) + self.register_reader("replay.details", readers.DetailsReader(), lambda r: True) + self.register_reader( + "replay.initData", readers.InitDataReader(), lambda r: True + ) + self.register_reader( + "replay.details.backup", readers.DetailsReader(), lambda r: True + ) + self.register_reader( + "replay.initData.backup", readers.InitDataReader(), lambda r: True + ) + self.register_reader( + "replay.tracker.events", readers.TrackerEventsReader(), lambda r: True + ) + self.register_reader( + "replay.message.events", readers.MessageEventsReader(), lambda r: True + ) + self.register_reader( + "replay.attributes.events", readers.AttributesEventsReader(), lambda r: True + ) + + self.register_reader( + "replay.game.events", + readers.GameEventsReader_15405(), + lambda r: 15405 <= r.base_build < 16561, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_16561(), + lambda r: 16561 <= r.base_build < 17326, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_17326(), + lambda r: 17326 <= r.base_build < 18574, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_18574(), + lambda r: 18574 <= r.base_build < 19595, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_19595(), + lambda r: 19595 <= r.base_build < 22612, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_22612(), + lambda r: 22612 <= r.base_build < 23260, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_23260(), + lambda r: 23260 <= r.base_build < 24247, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_24247(), + lambda r: 24247 <= r.base_build < 26490, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_26490(), + lambda r: 26490 <= r.base_build < 27950, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_27950(), + lambda r: 27950 <= r.base_build < 34784, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_34784(), + lambda r: 34784 <= r.base_build < 36442, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_36442(), + lambda r: 36442 <= r.base_build < 38215, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_38215(), + lambda r: 38215 <= r.base_build < 38749, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_38749(), + lambda r: 38749 <= r.base_build < 38996, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_38996(), + lambda r: 38996 <= r.base_build < 64469, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_64469(), + lambda r: 64469 <= r.base_build < 65895, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_65895(), + lambda r: 65895 <= r.base_build, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_HotSBeta(), + lambda r: r.versions[1] == 2 and r.build < 24247, + ) def register_default_datapacks(self): """Registers factory default datapacks.""" - self.register_datapack(datapacks['WoL']['16117'], lambda r: r.expansion == 'WoL' and 16117 <= r.build < 17326) - self.register_datapack(datapacks['WoL']['17326'], lambda r: r.expansion == 'WoL' and 17326 <= r.build < 18092) - self.register_datapack(datapacks['WoL']['18092'], lambda r: r.expansion == 'WoL' and 18092 <= r.build < 19458) - self.register_datapack(datapacks['WoL']['19458'], lambda r: r.expansion == 'WoL' and 19458 <= r.build < 22612) - self.register_datapack(datapacks['WoL']['22612'], lambda r: r.expansion == 'WoL' and 22612 <= r.build < 24944) - self.register_datapack(datapacks['WoL']['24944'], lambda r: r.expansion == 'WoL' and 24944 <= r.build) - self.register_datapack(datapacks['HotS']['base'], lambda r: r.expansion == 'HotS' and r.build < 23925) - self.register_datapack(datapacks['HotS']['23925'], lambda r: r.expansion == 'HotS' and 23925 <= r.build < 24247) - self.register_datapack(datapacks['HotS']['24247'], lambda r: r.expansion == 'HotS' and 24247 <= r.build < 24764) - self.register_datapack(datapacks['HotS']['24764'], lambda r: r.expansion == 'HotS' and 24764 <= r.build < 38215) - self.register_datapack(datapacks['HotS']['38215'], lambda r: r.expansion == 'HotS' and 38215 <= r.build) - self.register_datapack(datapacks['LotV']['base'], lambda r: r.expansion == 'LotV' and 34784 <= r.build) - self.register_datapack(datapacks['LotV']['44401'], lambda r: r.expansion == 'LotV' and 44401 <= r.build < 47185) - self.register_datapack(datapacks['LotV']['47185'], lambda r: r.expansion == 'LotV' and 47185 <= r.build < 48258) - self.register_datapack(datapacks['LotV']['48258'], lambda r: r.expansion == 'LotV' and 48258 <= r.build < 53644) - self.register_datapack(datapacks['LotV']['53644'], lambda r: r.expansion == 'LotV' and 53644 <= r.build < 54724) - self.register_datapack(datapacks['LotV']['54724'], lambda r: r.expansion == 'LotV' and 54724 <= r.build < 59587) - self.register_datapack(datapacks['LotV']['59587'], lambda r: r.expansion == 'LotV' and 59587 <= r.build < 70154) - self.register_datapack(datapacks['LotV']['70154'], lambda r: r.expansion == 'LotV' and 70154 <= r.build) - + self.register_datapack( + datapacks["WoL"]["16117"], + lambda r: r.expansion == "WoL" and 16117 <= r.build < 17326, + ) + self.register_datapack( + datapacks["WoL"]["17326"], + lambda r: r.expansion == "WoL" and 17326 <= r.build < 18092, + ) + self.register_datapack( + datapacks["WoL"]["18092"], + lambda r: r.expansion == "WoL" and 18092 <= r.build < 19458, + ) + self.register_datapack( + datapacks["WoL"]["19458"], + lambda r: r.expansion == "WoL" and 19458 <= r.build < 22612, + ) + self.register_datapack( + datapacks["WoL"]["22612"], + lambda r: r.expansion == "WoL" and 22612 <= r.build < 24944, + ) + self.register_datapack( + datapacks["WoL"]["24944"], + lambda r: r.expansion == "WoL" and 24944 <= r.build, + ) + self.register_datapack( + datapacks["HotS"]["base"], + lambda r: r.expansion == "HotS" and r.build < 23925, + ) + self.register_datapack( + datapacks["HotS"]["23925"], + lambda r: r.expansion == "HotS" and 23925 <= r.build < 24247, + ) + self.register_datapack( + datapacks["HotS"]["24247"], + lambda r: r.expansion == "HotS" and 24247 <= r.build < 24764, + ) + self.register_datapack( + datapacks["HotS"]["24764"], + lambda r: r.expansion == "HotS" and 24764 <= r.build < 38215, + ) + self.register_datapack( + datapacks["HotS"]["38215"], + lambda r: r.expansion == "HotS" and 38215 <= r.build, + ) + self.register_datapack( + datapacks["LotV"]["base"], + lambda r: r.expansion == "LotV" and 34784 <= r.build, + ) + self.register_datapack( + datapacks["LotV"]["44401"], + lambda r: r.expansion == "LotV" and 44401 <= r.build < 47185, + ) + self.register_datapack( + datapacks["LotV"]["47185"], + lambda r: r.expansion == "LotV" and 47185 <= r.build < 48258, + ) + self.register_datapack( + datapacks["LotV"]["48258"], + lambda r: r.expansion == "LotV" and 48258 <= r.build < 53644, + ) + self.register_datapack( + datapacks["LotV"]["53644"], + lambda r: r.expansion == "LotV" and 53644 <= r.build < 54724, + ) + self.register_datapack( + datapacks["LotV"]["54724"], + lambda r: r.expansion == "LotV" and 54724 <= r.build < 59587, + ) + self.register_datapack( + datapacks["LotV"]["59587"], + lambda r: r.expansion == "LotV" and 59587 <= r.build < 70154, + ) + self.register_datapack( + datapacks["LotV"]["70154"], + lambda r: r.expansion == "LotV" and 70154 <= r.build, + ) # Internal Methods def _get_reader(self, data_file): @@ -652,7 +867,11 @@ def _get_reader(self, data_file): if callback(self): return reader else: - raise ValueError("Valid {0} reader could not found for build {1}".format(data_file, self.build)) + raise ValueError( + "Valid {0} reader could not found for build {1}".format( + data_file, self.build + ) + ) def _get_datapack(self): for callback, datapack in self.registered_datapacks: @@ -665,18 +884,21 @@ def _read_data(self, data_file, reader): data = utils.extract_data_file(data_file, self.archive) if data: self.raw_data[data_file] = reader(data, self) - elif self.opt['debug'] and data_file not in ['replay.message.events', 'replay.tracker.events']: + elif self.opt["debug"] and data_file not in [ + "replay.message.events", + "replay.tracker.events", + ]: raise ValueError("{0} not found in archive".format(data_file)) def __getstate__(self): state = self.__dict__.copy() - del state['registered_readers'] - del state['registered_datapacks'] + del state["registered_readers"] + del state["registered_datapacks"] return state class Map(Resource): - url_template = 'http://{0}.depot.battle.net:1119/{1}.s2ma' + url_template = "http://{0}.depot.battle.net:1119/{1}.s2ma" def __init__(self, map_file, filename=None, region=None, map_hash=None, **options): super(Map, self).__init__(map_file, filename, **options) @@ -706,48 +928,54 @@ def __init__(self, map_file, filename=None, region=None, map_hash=None, **option self.archive = mpyq.MPQArchive(map_file) #: A byte string representing the minimap in tga format. - self.minimap = self.archive.read_file('Minimap.tga') + self.minimap = self.archive.read_file("Minimap.tga") # This will only populate the fields for maps with enUS localizations. # Clearly this isn't a great solution but we can't be throwing exceptions # just because US English wasn't a concern of the map author. # TODO: Make this work regardless of the localizations available. - game_strings_file = self.archive.read_file('enUS.SC2Data\LocalizedData\GameStrings.txt') + game_strings_file = self.archive.read_file( + "enUS.SC2Data\LocalizedData\GameStrings.txt" + ) if game_strings_file: - for line in game_strings_file.decode('utf8').split('\r\n'): + for line in game_strings_file.decode("utf8").split("\r\n"): if len(line) == 0: continue - key, value = line.split('=', 1) - if key == 'DocInfo/Name': + key, value = line.split("=", 1) + if key == "DocInfo/Name": self.name = value - elif key == 'DocInfo/Author': + elif key == "DocInfo/Author": self.author = value - elif key == 'DocInfo/DescLong': + elif key == "DocInfo/DescLong": self.description = value - elif key == 'DocInfo/Website': + elif key == "DocInfo/Website": self.website = value #: A reference to the map's :class:`~sc2reader.objects.MapInfo` object self.map_info = None - map_info_file = self.archive.read_file('MapInfo') + map_info_file = self.archive.read_file("MapInfo") if map_info_file: self.map_info = MapInfo(map_info_file) - doc_info_file = self.archive.read_file('DocumentInfo') + doc_info_file = self.archive.read_file("DocumentInfo") if doc_info_file: - doc_info = ElementTree.fromstring(doc_info_file.decode('utf8')) + doc_info = ElementTree.fromstring(doc_info_file.decode("utf8")) - icon_path_node = doc_info.find('Icon/Value') + icon_path_node = doc_info.find("Icon/Value") #: (Optional) The path to the icon for the map, relative to the archive root self.icon_path = icon_path_node.text if icon_path_node is not None else None #: (Optional) The icon image for the map in tga format - self.icon = self.archive.read_file(self.icon_path) if self.icon_path is not None else None + self.icon = ( + self.archive.read_file(self.icon_path) + if self.icon_path is not None + else None + ) #: A list of module names this map depends on self.dependencies = list() - for dependency_node in doc_info.findall('Dependencies/Value'): + for dependency_node in doc_info.findall("Dependencies/Value"): self.dependencies.append(dependency_node.text) @classmethod @@ -755,24 +983,23 @@ def get_url(cls, region, map_hash): """Builds a download URL for the map from its components.""" if region and map_hash: # it seems like sea maps are stored on us depots. - region = 'us' if region == 'sea' else region + region = "us" if region == "sea" else region return cls.url_template.format(region, map_hash) else: return None class Localization(Resource, dict): - def __init__(self, s2ml_file, **options): Resource.__init__(self, s2ml_file, **options) xml = ElementTree.parse(s2ml_file) - for entry in xml.findall('e'): - self[int(entry.attrib['id'])] = entry.text + for entry in xml.findall("e"): + self[int(entry.attrib["id"])] = entry.text class GameSummary(Resource): - url_template = 'http://{0}.depot.battle.net:1119/{1}.s2gs' + url_template = "http://{0}.depot.battle.net:1119/{1}.s2gs" #: Game speed game_speed = str() @@ -801,7 +1028,7 @@ class GameSummary(Resource): #: Map localization urls localization_urls = dict() - def __init__(self, summary_file, filename=None, lang='enUS', **options): + def __init__(self, summary_file, filename=None, lang="enUS", **options): super(GameSummary, self).__init__(summary_file, filename, lang=lang, **options) #: A dict of team# -> teams @@ -842,32 +1069,40 @@ def __init__(self, summary_file, filename=None, lang='enUS', **options): self.parts.append(buffer.read_struct()) self.load_translations() - dependencies = [sheet[1] for sheet in self.lang_sheets['enUS']] - if 'Swarm (Mod)' in dependencies: - self.expansion = 'HotS' - elif 'Liberty (Mod)' in dependencies: - self.expansion = 'WoL' + dependencies = [sheet[1] for sheet in self.lang_sheets["enUS"]] + if "Swarm (Mod)" in dependencies: + self.expansion = "HotS" + elif "Liberty (Mod)" in dependencies: + self.expansion = "WoL" else: - self.expansion = '' + self.expansion = "" self.end_time = datetime.utcfromtimestamp(self.parts[0][8]) - self.game_speed = LOBBY_PROPERTIES[0xBB8][1][self.parts[0][0][1].decode('utf8')] + self.game_speed = LOBBY_PROPERTIES[0xBB8][1][self.parts[0][0][1].decode("utf8")] self.game_length = utils.Length(seconds=self.parts[0][7]) - self.real_length = utils.Length(seconds=int(self.parts[0][7]/GAME_SPEED_FACTOR[self.expansion][self.game_speed])) - self.start_time = datetime.utcfromtimestamp(self.parts[0][8] - self.real_length.seconds) + self.real_length = utils.Length( + seconds=int( + self.parts[0][7] / GAME_SPEED_FACTOR[self.expansion][self.game_speed] + ) + ) + self.start_time = datetime.utcfromtimestamp( + self.parts[0][8] - self.real_length.seconds + ) self.load_map_info() self.load_settings() self.load_player_stats() self.load_players() - self.game_type = self.settings['Teams'].replace(" ", "") + self.game_type = self.settings["Teams"].replace(" ", "") self.real_type = utils.get_real_type(self.teams) # The s2gs file also keeps reference to a series of s2mv files # Some of these appear to be encoded bytes and others appear to be # the preview images that authors may bundle with their maps. - self.s2mv_urls = [str(utils.DepotFile(file_hash)) for file_hash in self.parts[0][6][7]] + self.s2mv_urls = [ + str(utils.DepotFile(file_hash)) for file_hash in self.parts[0][6][7] + ] def load_translations(self): # This section of the file seems to map numerical ids to their @@ -908,11 +1143,11 @@ def load_translations(self): # # Sometimes these byte strings are all NULLed out and need to be ignored. for localization in self.parts[0][6][8]: - language = localization[0].decode('utf8') + language = localization[0].decode("utf8") files = list() for file_hash in localization[1]: - if file_hash[:4].decode('utf8') != '\x00\x00\x00\x00': + if file_hash[:4].decode("utf8") != "\x00\x00\x00\x00": files.append(utils.DepotFile(file_hash)) self.localization_urls[language] = files @@ -928,7 +1163,7 @@ def load_translations(self): self.lang_sheets = dict() self.translations = dict() for lang, files in self.localization_urls.items(): - if lang != self.opt['lang']: + if lang != self.opt["lang"]: continue sheets = list() @@ -939,9 +1174,11 @@ def load_translations(self): for uid, (sheet, item) in self.id_map.items(): if sheet < len(sheets) and item in sheets[sheet]: translation[uid] = sheets[sheet][item] - elif self.opt['debug']: + elif self.opt["debug"]: msg = "No {0} translation for sheet {1}, item {2}" - raise SC2ReaderLocalizationError(msg.format(self.opt['lang'], sheet, item)) + raise SC2ReaderLocalizationError( + msg.format(self.opt["lang"], sheet, item) + ) else: translation[uid] = "Unknown" @@ -949,17 +1186,21 @@ def load_translations(self): self.translations[lang] = translation def load_map_info(self): - map_strings = self.lang_sheets[self.opt['lang']][-1] + map_strings = self.lang_sheets[self.opt["lang"]][-1] self.map_name = map_strings[1] self.map_description = map_strings[2] self.map_tileset = map_strings[3] def load_settings(self): - Property = namedtuple('Property', ['id', 'values', 'requirements', 'defaults', 'is_lobby']) + Property = namedtuple( + "Property", ["id", "values", "requirements", "defaults", "is_lobby"] + ) properties = dict() for p in self.parts[0][5]: - properties[p[0][1]] = Property(p[0][1], p[1], p[3], p[8], isinstance(p[8], dict)) + properties[p[0][1]] = Property( + p[0][1], p[1], p[3], p[8], isinstance(p[8], dict) + ) settings = dict() for setting in self.parts[0][6][6]: @@ -1008,7 +1249,7 @@ def use_property(prop, player=None): activated[(prop.id, player)] = use return use - translation = self.translations[self.opt['lang']] + translation = self.translations[self.opt["lang"]] for uid, prop in properties.items(): name = translation.get(uid, "Unknown") if prop.is_lobby: @@ -1022,7 +1263,7 @@ def use_property(prop, player=None): self.player_settings[index][name] = translation[(uid, value)] def load_player_stats(self): - translation = self.translations[self.opt['lang']] + translation = self.translations[self.opt["lang"]] stat_items = sum([p[0] for p in self.parts[3:]], []) @@ -1043,7 +1284,12 @@ def load_player_stats(self): if not value: continue - if stat_name in ('Army Value', 'Resource Collection Rate', 'Upgrade Spending', 'Workers Active'): + if stat_name in ( + "Army Value", + "Resource Collection Rate", + "Upgrade Spending", + "Workers Active", + ): # Each point entry for the graph is laid out as follows # # {0:Value, 1:0, 2:Time} @@ -1062,13 +1308,15 @@ def load_player_stats(self): # up to the first 64 successful actions in the game. for pindex, commands in enumerate(item[1]): for command in commands: - self.build_orders[pindex].append(BuildEntry( - supply=command[0], - total_supply=command[1] & 0xff, - time=int((command[2] >> 8) / 16), - order=stat_name, - build_index=command[1] >> 16 - )) + self.build_orders[pindex].append( + BuildEntry( + supply=command[0], + total_supply=command[1] & 0xFF, + time=int((command[2] >> 8) / 16), + order=stat_name, + build_index=command[1] >> 16, + ) + ) elif stat_id != 83886080: # We know this one is always bad. self.logger.warn("Untranslatable key = {0}".format(stat_id)) @@ -1094,7 +1342,7 @@ def load_players(self): player.unknown2 = struct[0][1][1] # Either a referee or a spectator, nothing else to do - if settings.get('Participant Role', '') != 'Participant': + if settings.get("Participant Role", "") != "Participant": self.observers.append(player) continue @@ -1104,7 +1352,7 @@ def load_players(self): if player.is_winner: self.winners.append(player.pid) - team_id = int(settings['Team'].split(' ')[1]) + team_id = int(settings["Team"].split(" ")[1]) if team_id not in self.team: self.team[team_id] = Team(team_id) self.teams.append(self.team[team_id]) @@ -1113,49 +1361,51 @@ def load_players(self): self.team[team_id].players.append(player) # We can just copy these settings right over - player.color = utils.Color(name=settings.get('Color', None)) - player.pick_race = settings.get('Race', None) - player.handicap = settings.get('Handicap', None) + player.color = utils.Color(name=settings.get("Color", None)) + player.pick_race = settings.get("Race", None) + player.handicap = settings.get("Handicap", None) # Overview Tab - player.resource_score = stats.get('Resources', None) - player.structure_score = stats.get('Structures', None) - player.unit_score = stats.get('Units', None) - player.overview_score = stats.get('Overview', None) + player.resource_score = stats.get("Resources", None) + player.structure_score = stats.get("Structures", None) + player.unit_score = stats.get("Units", None) + player.overview_score = stats.get("Overview", None) # Units Tab - player.units_killed = stats.get('Killed Unit Count', None) - player.structures_built = stats.get('Structures Built', None) - player.units_trained = stats.get('Units Trained', None) - player.structures_razed = stats.get('Structures Razed Count', None) + player.units_killed = stats.get("Killed Unit Count", None) + player.structures_built = stats.get("Structures Built", None) + player.units_trained = stats.get("Units Trained", None) + player.structures_razed = stats.get("Structures Razed Count", None) # Graphs Tab # Keep income_graph for backwards compatibility - player.army_graph = stats.get('Army Value') - player.resource_collection_graph = stats.get('Resource Collection Rate', None) + player.army_graph = stats.get("Army Value") + player.resource_collection_graph = stats.get( + "Resource Collection Rate", None + ) player.income_graph = player.resource_collection_graph # HotS Stats - player.upgrade_spending_graph = stats.get('Upgrade Spending', None) - player.workers_active_graph = stats.get('Workers Active', None) - player.enemies_destroyed = stats.get('Enemies Destroyed:', None) - player.time_supply_capped = stats.get('Time Supply Capped', None) - player.idle_production_time = stats.get('Idle Production Time', None) - player.resources_spent = stats.get('Resources Spent:', None) - player.apm = stats.get('APM', None) + player.upgrade_spending_graph = stats.get("Upgrade Spending", None) + player.workers_active_graph = stats.get("Workers Active", None) + player.enemies_destroyed = stats.get("Enemies Destroyed:", None) + player.time_supply_capped = stats.get("Time Supply Capped", None) + player.idle_production_time = stats.get("Idle Production Time", None) + player.resources_spent = stats.get("Resources Spent:", None) + player.apm = stats.get("APM", None) # Economic Breakdown Tab if isinstance(player.income_graph, Graph): values = player.income_graph.values - player.resource_collection_rate = int(sum(values)/len(values)) + player.resource_collection_rate = int(sum(values) / len(values)) else: # In old s2gs files the field with this name was actually a number not a graph player.resource_collection_rate = player.income_graph player.resource_collection_graph = None player.income_graph = None - player.avg_unspent_resources = stats.get('Average Unspent Resources', None) - player.workers_created = stats.get('Workers Created', None) + player.avg_unspent_resources = stats.get("Average Unspent Resources", None) + player.workers_created = stats.get("Workers Created", None) # Build Orders Tab player.build_order = self.build_orders.get(index, None) @@ -1164,15 +1414,21 @@ def load_players(self): self.player[player.pid] = player def __str__(self): - return "{0} - {1} {2}".format(self.start_time, self.game_length, 'v'.join(''.join(p.play_race[0] for p in team.players) for team in self.teams)) + return "{0} - {1} {2}".format( + self.start_time, + self.game_length, + "v".join( + "".join(p.play_race[0] for p in team.players) for team in self.teams + ), + ) class MapHeader(Resource): """**Experimental**""" - base_url_template = 'http://{0}.depot.battle.net:1119/{1}.{2}' - url_template = 'http://{0}.depot.battle.net:1119/{1}.s2mh' - image_url_template = 'http://{0}.depot.battle.net:1119/{1}.s2mv' + base_url_template = "http://{0}.depot.battle.net:1119/{1}.{2}" + url_template = "http://{0}.depot.battle.net:1119/{1}.s2mh" + image_url_template = "http://{0}.depot.battle.net:1119/{1}.s2mv" #: The name of the map name = str() @@ -1203,20 +1459,26 @@ def __init__(self, header_file, filename=None, **options): self.name = self.data[0][1] # Blizzard - self.blizzard = (self.data[0][11] == 'BLIZ') + self.blizzard = self.data[0][11] == "BLIZ" # Parse image hash parsed_hash = utils.parse_hash(self.data[0][1]) - self.image_hash = parsed_hash['hash'] - self.image_url = self.image_url_template.format(parsed_hash['server'], parsed_hash['hash']) + self.image_hash = parsed_hash["hash"] + self.image_url = self.image_url_template.format( + parsed_hash["server"], parsed_hash["hash"] + ) # Parse map hash parsed_hash = utils.parse_hash(self.data[0][2]) - self.map_hash = parsed_hash['hash'] - self.map_url = self.base_url_template.format(parsed_hash['server'], parsed_hash['hash'], parsed_hash['type']) + self.map_hash = parsed_hash["hash"] + self.map_url = self.base_url_template.format( + parsed_hash["server"], parsed_hash["hash"], parsed_hash["type"] + ) # Parse localization hashes l18n_struct = self.data[0][4][8] for l in l18n_struct: parsed_hash = utils.parse_hash(l[1][0]) - self.localization_urls[l[0]] = self.base_url_template.format(parsed_hash['server'], parsed_hash['hash'], parsed_hash['type']) + self.localization_urls[l[0]] = self.base_url_template.format( + parsed_hash["server"], parsed_hash["hash"], parsed_hash["type"] + ) diff --git a/sc2reader/scripts/sc2attributes.py b/sc2reader/scripts/sc2attributes.py index da0160e9..109cf30b 100644 --- a/sc2reader/scripts/sc2attributes.py +++ b/sc2reader/scripts/sc2attributes.py @@ -39,7 +39,7 @@ import sc2reader try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 @@ -49,51 +49,68 @@ def main(): global decisions - parser = argparse.ArgumentParser(description="Recursively parses replay files, inteded for debugging parse issues.") - parser.add_argument('folders', metavar='folder', type=str, nargs='+', help="Path to a folder") + parser = argparse.ArgumentParser( + description="Recursively parses replay files, inteded for debugging parse issues." + ) + parser.add_argument( + "folders", metavar="folder", type=str, nargs="+", help="Path to a folder" + ) args = parser.parse_args() scripts_dir = os.path.dirname(os.path.abspath(__file__)) - data_path = os.path.normpath(os.path.join(scripts_dir, '..', 'data', 'attributes.json')) + data_path = os.path.normpath( + os.path.join(scripts_dir, "..", "data", "attributes.json") + ) attributes = dict() if os.path.exists(data_path): - with open(data_path, 'r') as data_file: + with open(data_path, "r") as data_file: data = json.load(data_file) - attributes = data.get('attributes', attributes) - decisions = pickle.loads(data.get('decisions', '(dp0\n.')) + attributes = data.get("attributes", attributes) + decisions = pickle.loads(data.get("decisions", "(dp0\n.")) for folder in args.folders: - for path in sc2reader.utils.get_files(folder, extension='s2gs'): + for path in sc2reader.utils.get_files(folder, extension="s2gs"): try: summary = sc2reader.load_game_summary(path) for prop in summary.parts[0][5]: group_key = prop[0][1] - group_name = summary.translations['enUS'][group_key] + group_name = summary.translations["enUS"][group_key] attribute_values = dict() if str(group_key) in attributes: attribute_name, attribute_values = attributes[str(group_key)] if attribute_name != group_name: - group_name = get_choice(group_key, attribute_name, group_name) + group_name = get_choice( + group_key, attribute_name, group_name + ) for value in prop[1]: - value_key = value[0].strip("\x00 ").replace(' v ', 'v') - value_name = summary.lang_sheets['enUS'][value[1][0][1]][value[1][0][2]] + value_key = value[0].strip("\x00 ").replace(" v ", "v") + value_name = summary.lang_sheets["enUS"][value[1][0][1]][ + value[1][0][2] + ] if str(value_key) in attribute_values: attribute_value_name = attribute_values[str(value_key)] if value_name != attribute_value_name: - value_name = get_choice((group_key, value_key), attribute_value_name, value_name) + value_name = get_choice( + (group_key, value_key), + attribute_value_name, + value_name, + ) attribute_values[str(value_key)] = value_name - attributes["{0:0>4}".format(group_key)] = (group_name, attribute_values) + attributes["{0:0>4}".format(group_key)] = ( + group_name, + attribute_values, + ) except Exception as e: if isinstance(e, KeyboardInterrupt): raise else: traceback.print_exc() - with open(data_path, 'w') as data_file: + with open(data_path, "w") as data_file: data = dict(attributes=attributes, decisions=pickle.dumps(decisions)) json.dump(data, data_file, indent=2, sort_keys=True) @@ -104,20 +121,22 @@ def get_choice(s2gs_key, old_value, new_value): # This way old/new values can be swapped and decision is remembered key = frozenset([s2gs_key, old_value, new_value]) if key not in decisions: - print("Naming conflict on {0}: {1} != {2}".format(s2gs_key, old_value, new_value)) + print( + "Naming conflict on {0}: {1} != {2}".format(s2gs_key, old_value, new_value) + ) print("Which do you want to use?") print(" (o) Old value '{0}'".format(old_value)) print(" (n) New value '{0}'".format(new_value)) while True: answer = raw_input("Choose 'o' or 'n' then press enter: ").lower() - if answer not in ('o', 'n'): - print('Invalid choice `{0}`'.format(answer)) + if answer not in ("o", "n"): + print("Invalid choice `{0}`".format(answer)) else: break - decisions[key] = {'o': old_value, 'n': new_value}[answer] + decisions[key] = {"o": old_value, "n": new_value}[answer] print("") return decisions[key] -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/sc2reader/scripts/sc2json.py b/sc2reader/scripts/sc2json.py index bd42178f..7cc2f331 100755 --- a/sc2reader/scripts/sc2json.py +++ b/sc2reader/scripts/sc2json.py @@ -8,16 +8,38 @@ def main(): import argparse + parser = argparse.ArgumentParser(description="Prints replay data to a json string.") - parser.add_argument('--indent', '-i', type=int, default=None, help="The per-line indent to use when printing a human readable json string") - parser.add_argument('--encoding', '-e', type=str, default='UTF-8', help="The character encoding use..") - parser.add_argument('path', metavar='path', type=str, nargs=1, help="Path to the replay to serialize.") + parser.add_argument( + "--indent", + "-i", + type=int, + default=None, + help="The per-line indent to use when printing a human readable json string", + ) + parser.add_argument( + "--encoding", + "-e", + type=str, + default="UTF-8", + help="The character encoding use..", + ) + parser.add_argument( + "path", + metavar="path", + type=str, + nargs=1, + help="Path to the replay to serialize.", + ) args = parser.parse_args() factory = sc2reader.factories.SC2Factory() - factory.register_plugin("Replay", toJSON(encoding=args.encoding, indent=args.indent)) + factory.register_plugin( + "Replay", toJSON(encoding=args.encoding, indent=args.indent) + ) replay_json = factory.load_replay(args.path[0]) print(replay_json) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/sc2reader/scripts/sc2parse.py b/sc2reader/scripts/sc2parse.py index 571b0c05..311db343 100755 --- a/sc2reader/scripts/sc2parse.py +++ b/sc2reader/scripts/sc2parse.py @@ -22,20 +22,32 @@ import sc2reader import traceback -sc2reader.log_utils.log_to_console('INFO') +sc2reader.log_utils.log_to_console("INFO") def main(): - parser = argparse.ArgumentParser(description="Recursively parses replay files, inteded for debugging parse issues.") - parser.add_argument('--one_each', help="Attempt to parse only one Ladder replay for each release_string", action="store_true") - parser.add_argument('--ladder_only', help="If a non-ladder game fails, ignore it", action="store_true") - parser.add_argument('folders', metavar='folder', type=str, nargs='+', help="Path to a folder") + parser = argparse.ArgumentParser( + description="Recursively parses replay files, inteded for debugging parse issues." + ) + parser.add_argument( + "--one_each", + help="Attempt to parse only one Ladder replay for each release_string", + action="store_true", + ) + parser.add_argument( + "--ladder_only", + help="If a non-ladder game fails, ignore it", + action="store_true", + ) + parser.add_argument( + "folders", metavar="folder", type=str, nargs="+", help="Path to a folder" + ) args = parser.parse_args() releases_parsed = set() for folder in args.folders: print("dealing with {0}".format(folder)) - for path in sc2reader.utils.get_files(folder, extension='SC2Replay'): + for path in sc2reader.utils.get_files(folder, extension="SC2Replay"): try: rs = sc2reader.load_replay(path, load_level=0).release_string already_did = rs in releases_parsed @@ -46,18 +58,58 @@ def main(): replay = sc2reader.load_replay(path, debug=True) human_pids = set([human.pid for human in replay.humans]) - event_pids = set([event.player.pid for event in replay.events if getattr(event, 'player', None)]) - player_pids = set([player.pid for player in replay.players if player.is_human]) - ability_pids = set([event.player.pid for event in replay.events if 'CommandEvent' in event.name]) + event_pids = set( + [ + event.player.pid + for event in replay.events + if getattr(event, "player", None) + ] + ) + player_pids = set( + [player.pid for player in replay.players if player.is_human] + ) + ability_pids = set( + [ + event.player.pid + for event in replay.events + if "CommandEvent" in event.name + ] + ) if human_pids != event_pids: - print('Event Pid problem! pids={pids} but event pids={event_pids}'.format(pids=human_pids, event_pids=event_pids)) - print(' with {path}: {build} - {real_type} on {map_name} - Played {start_time}'.format(path=path, **replay.__dict__)) + print( + "Event Pid problem! pids={pids} but event pids={event_pids}".format( + pids=human_pids, event_pids=event_pids + ) + ) + print( + " with {path}: {build} - {real_type} on {map_name} - Played {start_time}".format( + path=path, **replay.__dict__ + ) + ) elif player_pids != ability_pids: - print('Ability Pid problem! pids={pids} but event pids={event_pids}'.format(pids=player_pids, event_pids=ability_pids)) - print(' with {path}: {build} - {real_type} on {map_name} - Played {start_time}'.format(path=path, **replay.__dict__)) + print( + "Ability Pid problem! pids={pids} but event pids={event_pids}".format( + pids=player_pids, event_pids=ability_pids + ) + ) + print( + " with {path}: {build} - {real_type} on {map_name} - Played {start_time}".format( + path=path, **replay.__dict__ + ) + ) else: - print('No problems with {path}: {build} - {real_type} on {map_name} - Played {start_time}'.format(path=path, **replay.__dict__)) - print('Units were: {units}'.format(units=set([obj.name for obj in replay.objects.values()]))) + print( + "No problems with {path}: {build} - {real_type} on {map_name} - Played {start_time}".format( + path=path, **replay.__dict__ + ) + ) + print( + "Units were: {units}".format( + units=set( + [obj.name for obj in replay.objects.values()] + ) + ) + ) except sc2reader.exceptions.ReadError as e: if args.ladder_only and not e.replay.is_ladder: @@ -65,19 +117,27 @@ def main(): print("") print(path) - print('{build} - {real_type} on {map_name} - Played {start_time}'.format(**e.replay.__dict__)) - print('[ERROR] {}', e) + print( + "{build} - {real_type} on {map_name} - Played {start_time}".format( + **e.replay.__dict__ + ) + ) + print("[ERROR] {}", e) for event in e.game_events[-5:]: - print('{0}'.format(event)) - print(e.buffer.read_range(e.location, e.location + 50).encode('hex')) + print("{0}".format(event)) + print(e.buffer.read_range(e.location, e.location + 50).encode("hex")) print except Exception as e: print("") print(path) try: replay = sc2reader.load_replay(path, debug=True, load_level=2) - print('{build} - {real_type} on {map_name} - Played {start_time}'.format(**replay.__dict__)) - print('[ERROR] {0}'.format(e)) + print( + "{build} - {real_type} on {map_name} - Played {start_time}".format( + **replay.__dict__ + ) + ) + print("[ERROR] {0}".format(e)) for pid, attributes in replay.attributes.items(): print("{0} {1}".format(pid, attributes)) for pid, info in enumerate(replay.players): @@ -88,12 +148,16 @@ def main(): print("") except Exception as e2: replay = sc2reader.load_replay(path, debug=True, load_level=0) - print('Total failure parsing {release_string}'.format(**replay.__dict__)) - print('[ERROR] {0}'.format(e)) - print('[ERROR] {0}'.format(e2)) + print( + "Total failure parsing {release_string}".format( + **replay.__dict__ + ) + ) + print("[ERROR] {0}".format(e)) + print("[ERROR] {0}".format(e2)) traceback.print_exc() print -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/sc2reader/scripts/sc2printer.py b/sc2reader/scripts/sc2printer.py index c780d16e..92cfe686 100755 --- a/sc2reader/scripts/sc2printer.py +++ b/sc2reader/scripts/sc2printer.py @@ -25,9 +25,17 @@ def printReplay(filepath, arguments): lineups = [team.lineup for team in replay.teams] print(" Teams: {0}".format("v".join(lineups))) for team in replay.teams: - print(" Team {0}\t{1} ({2})".format(team.number, team.players[0].name, team.players[0].pick_race[0])) + print( + " Team {0}\t{1} ({2})".format( + team.number, team.players[0].name, team.players[0].pick_race[0] + ) + ) for player in team.players[1:]: - print(" \t{0} ({1})".format(player.name, player.pick_race[0])) + print( + " \t{0} ({1})".format( + player.name, player.pick_race[0] + ) + ) if arguments.observers: print(" Observers:") for observer in replay.observers: @@ -45,12 +53,16 @@ def printReplay(filepath, arguments): raise return prev = e.game_events[-1] - print("\nVersion {0} replay:\n\t{1}".format(e.replay.release_string, e.replay.filepath)) + print( + "\nVersion {0} replay:\n\t{1}".format( + e.replay.release_string, e.replay.filepath + ) + ) print("\t{0}, Type={1:X}".format(e.msg, e.type)) print("\tPrevious Event: {0}".format(prev.name)) - print("\t\t" + prev.bytes.encode('hex')) + print("\t\t" + prev.bytes.encode("hex")) print("\tFollowing Bytes:") - print("\t\t" + e.buffer.read_range(e.location, e.location + 30).encode('hex')) + print("\t\t" + e.buffer.read_range(e.location, e.location + 30).encode("hex")) print("Error with '{0}': ".format(filepath)) print(e) except Exception as e: @@ -80,54 +92,100 @@ def printGameSummary(filepath, arguments): print("\n== {0} ==\n".format(player)) for order in summary.build_orders[player.pid]: msg = " {0:0>2}:{1:0>2} {2:<35} {3:0>2}/{4}" - print(msg.format(order.time / 60, order.time % 60, order.order, order.supply, order.total_supply)) + print( + msg.format( + order.time / 60, + order.time % 60, + order.order, + order.supply, + order.total_supply, + ) + ) print("") def main(): parser = argparse.ArgumentParser( description="""Prints basic information from Starcraft II replay and - game summary files or directories.""") - parser.add_argument('--recursive', action="store_true", default=True, - help="Recursively read through directories of Starcraft II files [default on]") - - required = parser.add_argument_group('Required Arguments') - required.add_argument('paths', metavar='filename', type=str, nargs='+', - help="Paths to one or more Starcraft II files or directories") - - shared_args = parser.add_argument_group('Shared Arguments') - shared_args.add_argument('--date', action="store_true", default=True, - help="print game date [default on]") - shared_args.add_argument('--length', action="store_true", default=False, - help="print game duration mm:ss in game time (not real time) [default off]") - shared_args.add_argument('--map', action="store_true", default=True, - help="print map name [default on]") - shared_args.add_argument('--teams', action="store_true", default=True, - help="print teams, their players, and the race matchup [default on]") - shared_args.add_argument('--observers', action="store_true", default=True, - help="print observers") - - replay_args = parser.add_argument_group('Replay Options') - replay_args.add_argument('--messages', action="store_true", default=False, - help="print(in-game player chat messages [default off]") - replay_args.add_argument('--version', action="store_true", default=True, - help="print(the release string as seen in game [default on]") - - s2gs_args = parser.add_argument_group('Game Summary Options') - s2gs_args.add_argument('--builds', action="store_true", default=False, - help="print(player build orders (first 64 items) [default off]") + game summary files or directories.""" + ) + parser.add_argument( + "--recursive", + action="store_true", + default=True, + help="Recursively read through directories of Starcraft II files [default on]", + ) + + required = parser.add_argument_group("Required Arguments") + required.add_argument( + "paths", + metavar="filename", + type=str, + nargs="+", + help="Paths to one or more Starcraft II files or directories", + ) + + shared_args = parser.add_argument_group("Shared Arguments") + shared_args.add_argument( + "--date", action="store_true", default=True, help="print game date [default on]" + ) + shared_args.add_argument( + "--length", + action="store_true", + default=False, + help="print game duration mm:ss in game time (not real time) [default off]", + ) + shared_args.add_argument( + "--map", action="store_true", default=True, help="print map name [default on]" + ) + shared_args.add_argument( + "--teams", + action="store_true", + default=True, + help="print teams, their players, and the race matchup [default on]", + ) + shared_args.add_argument( + "--observers", action="store_true", default=True, help="print observers" + ) + + replay_args = parser.add_argument_group("Replay Options") + replay_args.add_argument( + "--messages", + action="store_true", + default=False, + help="print(in-game player chat messages [default off]", + ) + replay_args.add_argument( + "--version", + action="store_true", + default=True, + help="print(the release string as seen in game [default on]", + ) + + s2gs_args = parser.add_argument_group("Game Summary Options") + s2gs_args.add_argument( + "--builds", + action="store_true", + default=False, + help="print(player build orders (first 64 items) [default off]", + ) arguments = parser.parse_args() for path in arguments.paths: depth = -1 if arguments.recursive else 0 for filepath in utils.get_files(path, depth=depth): name, ext = os.path.splitext(filepath) - if ext.lower() == '.sc2replay': - print("\n--------------------------------------\n{0}\n".format(filepath)) + if ext.lower() == ".sc2replay": + print( + "\n--------------------------------------\n{0}\n".format(filepath) + ) printReplay(filepath, arguments) - elif ext.lower() == '.s2gs': - print("\n--------------------------------------\n{0}\n".format(filepath)) + elif ext.lower() == ".s2gs": + print( + "\n--------------------------------------\n{0}\n".format(filepath) + ) printGameSummary(filepath, arguments) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index 7a72c7ed..d8711962 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -28,6 +28,7 @@ def getch(): termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) + except ImportError as e: try: # Opps, we might be on windows, try this one @@ -49,17 +50,39 @@ def main(): key to advance through the events in sequential order.""" ) - parser.add_argument('FILE', type=str, help="The file you would like to replay") - parser.add_argument('--player', default=0, type=int, help="The number of the player you would like to watch. Defaults to 0 (All).") - parser.add_argument('--bytes', default=False, action="store_true", help="Displays the byte code of the event in hex after each event.") - parser.add_argument('--hotkeys', default=False, action="store_true", help="Shows the hotkey events in the event stream.") - parser.add_argument('--cameras', default=False, action="store_true", help="Shows the camera events in the event stream.") + parser.add_argument("FILE", type=str, help="The file you would like to replay") + parser.add_argument( + "--player", + default=0, + type=int, + help="The number of the player you would like to watch. Defaults to 0 (All).", + ) + parser.add_argument( + "--bytes", + default=False, + action="store_true", + help="Displays the byte code of the event in hex after each event.", + ) + parser.add_argument( + "--hotkeys", + default=False, + action="store_true", + help="Shows the hotkey events in the event stream.", + ) + parser.add_argument( + "--cameras", + default=False, + action="store_true", + help="Shows the camera events in the event stream.", + ) args = parser.parse_args() for filename in sc2reader.utils.get_files(args.FILE): replay = sc2reader.load_replay(filename, debug=True) print("Release {0}".format(replay.release_string)) - print("{0} on {1} at {2}".format(replay.type, replay.map_name, replay.start_time)) + print( + "{0} on {1} at {2}".format(replay.type, replay.map_name, replay.start_time) + ) print("") for team in replay.teams: print(team) @@ -77,17 +100,19 @@ def main(): # Loop through the events for event in events: - if isinstance(event, CommandEvent) or \ - isinstance(event, SelectionEvent) or \ - isinstance(event, PlayerLeaveEvent) or \ - isinstance(event, GameStartEvent) or \ - (args.hotkeys and isinstance(event, HotkeyEvent)) or \ - (args.cameras and isinstance(event, CameraEvent)): + if ( + isinstance(event, CommandEvent) + or isinstance(event, SelectionEvent) + or isinstance(event, PlayerLeaveEvent) + or isinstance(event, GameStartEvent) + or (args.hotkeys and isinstance(event, HotkeyEvent)) + or (args.cameras and isinstance(event, CameraEvent)) + ): print(event) getch() if args.bytes: - print("\t"+event.bytes.encode('hex')) + print("\t" + event.bytes.encode("hex")) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/sc2reader/scripts/utils.py b/sc2reader/scripts/utils.py index 3eb05188..cf998e5d 100644 --- a/sc2reader/scripts/utils.py +++ b/sc2reader/scripts/utils.py @@ -35,29 +35,29 @@ def new(cls, **options): def _split_lines(self, text, width): lines = list() - main_indent = len(re.match(r'( *)', text).group(1)) + main_indent = len(re.match(r"( *)", text).group(1)) # Wrap each line individually to allow for partial formatting for line in text.splitlines(): # Get this line's indent and figure out what indent to use # if the line wraps. Account for lists of small variety. - indent = len(re.match(r'( *)', line).group(1)) - list_match = re.match(r'( *)(([*-+>]+|\w+\)|\w+\.) +)', line) - if(list_match): + indent = len(re.match(r"( *)", line).group(1)) + list_match = re.match(r"( *)(([*-+>]+|\w+\)|\w+\.) +)", line) + if list_match: sub_indent = indent + len(list_match.group(2)) else: sub_indent = indent # Textwrap will do all the hard work for us - line = self._whitespace_matcher.sub(' ', line).strip() + line = self._whitespace_matcher.sub(" ", line).strip() new_lines = textwrap.wrap( text=line, width=width, - initial_indent=' '*(indent-main_indent), - subsequent_indent=' '*(sub_indent-main_indent), + initial_indent=" " * (indent - main_indent), + subsequent_indent=" " * (sub_indent - main_indent), ) # Blank lines get eaten by textwrap, put it back with [' '] - lines.extend(new_lines or [' ']) + lines.extend(new_lines or [" "]) return lines diff --git a/sc2reader/utils.py b/sc2reader/utils.py index 2ee3d382..41e4ed09 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -20,21 +20,21 @@ class DepotFile(object): """ #: The url template for all DepotFiles - url_template = 'http://{0}.depot.battle.net:1119/{1}.{2}' + url_template = "http://{0}.depot.battle.net:1119/{1}.{2}" def __init__(self, bytes): #: The server the file is hosted on - self.server = bytes[4:8].decode('utf-8').strip('\x00 ') + self.server = bytes[4:8].decode("utf-8").strip("\x00 ") # There is no SEA depot, use US instead - if self.server == 'SEA': - self.server = 'US' + if self.server == "SEA": + self.server = "US" #: The unique content based hash of the file - self.hash = binascii.b2a_hex(bytes[8:]).decode('utf8') + self.hash = binascii.b2a_hex(bytes[8:]).decode("utf8") #: The extension of the file on the server - self.type = bytes[0:4].decode('utf8') + self.type = bytes[0:4].decode("utf8") @property def url(self): @@ -71,11 +71,12 @@ class Color(object): Only standard Starcraft colors are supported. ValueErrors will be thrown on invalid names or hex values. """ + def __init__(self, name=None, r=0, g=0, b=0, a=255): if name: if name not in COLOR_CODES_INV: self.logger.warn("Invalid color name: " + name) - hexstr = COLOR_CODES_INV.get(name, '000000') + hexstr = COLOR_CODES_INV.get(name, "000000") self.r = int(hexstr[0:2], 16) self.g = int(hexstr[2:4], 16) self.b = int(hexstr[4:6], 16) @@ -114,7 +115,6 @@ def get_real_type(teams): def extract_data_file(data_file, archive): - def recovery_attempt(): try: return archive.read_file(data_file) @@ -151,7 +151,9 @@ def recovery_attempt(): raise MPQError("Unable to extract file: {0}".format(data_file), e) -def get_files(path, exclude=list(), depth=-1, followlinks=False, extension=None, **extras): +def get_files( + path, exclude=list(), depth=-1, followlinks=False, extension=None, **extras +): """ Retrieves files from the given path with configurable behavior. @@ -167,7 +169,9 @@ def get_files(path, exclude=list(), depth=-1, followlinks=False, extension=None, # If an extension is supplied, use it to do a type check if extension: - type_check = lambda path: os.path.splitext(path)[1][1:].lower() == extension.lower() + type_check = ( + lambda path: os.path.splitext(path)[1][1:].lower() == extension.lower() + ) else: type_check = lambda n: True @@ -237,68 +241,76 @@ def toDict(replay): observers = list() for observer in replay.observers: messages = list() - for message in getattr(observer, 'messages', list()): - messages.append({ - 'time': message.time.seconds, - 'text': message.text, - 'is_public': message.to_all - }) - observers.append({ - 'name': getattr(observer, 'name', None), - 'pid': getattr(observer, 'pid', None), - 'messages': messages, - }) + for message in getattr(observer, "messages", list()): + messages.append( + { + "time": message.time.seconds, + "text": message.text, + "is_public": message.to_all, + } + ) + observers.append( + { + "name": getattr(observer, "name", None), + "pid": getattr(observer, "pid", None), + "messages": messages, + } + ) # Build players into dictionary players = list() for player in replay.players: messages = list() for message in player.messages: - messages.append({ - 'time': message.time.seconds, - 'text': message.text, - 'is_public': message.to_all - }) - players.append({ - 'avg_apm': getattr(player, 'avg_apm', None), - 'color': player.color.__dict__ if hasattr(player, 'color') else None, - 'handicap': getattr(player, 'handicap', None), - 'name': getattr(player, 'name', None), - 'pick_race': getattr(player, 'pick_race', None), - 'pid': getattr(player, 'pid', None), - 'play_race': getattr(player, 'play_race', None), - 'result': getattr(player, 'result', None), - 'type': getattr(player, 'type', None), - 'uid': getattr(player, 'uid', None), - 'url': getattr(player, 'url', None), - 'messages': messages, - }) + messages.append( + { + "time": message.time.seconds, + "text": message.text, + "is_public": message.to_all, + } + ) + players.append( + { + "avg_apm": getattr(player, "avg_apm", None), + "color": player.color.__dict__ if hasattr(player, "color") else None, + "handicap": getattr(player, "handicap", None), + "name": getattr(player, "name", None), + "pick_race": getattr(player, "pick_race", None), + "pid": getattr(player, "pid", None), + "play_race": getattr(player, "play_race", None), + "result": getattr(player, "result", None), + "type": getattr(player, "type", None), + "uid": getattr(player, "uid", None), + "url": getattr(player, "url", None), + "messages": messages, + } + ) # Consolidate replay metadata into dictionary return { - 'region': getattr(replay, 'region', None), - 'map_name': getattr(replay, 'map_name', None), - 'file_time': getattr(replay, 'file_time', None), - 'filehash': getattr(replay, 'filehash', None), - 'unix_timestamp': getattr(replay, 'unix_timestamp', None), - 'date': getattr(replay, 'date', None), - 'utc_date': getattr(replay, 'utc_date', None), - 'speed': getattr(replay, 'speed', None), - 'category': getattr(replay, 'category', None), - 'type': getattr(replay, 'type', None), - 'is_ladder': getattr(replay, 'is_ladder', False), - 'is_private': getattr(replay, 'is_private', False), - 'filename': getattr(replay, 'filename', None), - 'file_time': getattr(replay, 'file_time', None), - 'frames': getattr(replay, 'frames', None), - 'build': getattr(replay, 'build', None), - 'release': getattr(replay, 'release_string', None), - 'game_fps': getattr(replay, 'game_fps', None), - 'game_length': getattr(getattr(replay, 'game_length', None), 'seconds', None), - 'players': players, - 'observers': observers, - 'real_length': getattr(getattr(replay, 'real_length', None), 'seconds', None), - 'real_type': getattr(replay, 'real_type', None), - 'time_zone': getattr(replay, 'time_zone', None), - 'versions': getattr(replay, 'versions', None) + "region": getattr(replay, "region", None), + "map_name": getattr(replay, "map_name", None), + "file_time": getattr(replay, "file_time", None), + "filehash": getattr(replay, "filehash", None), + "unix_timestamp": getattr(replay, "unix_timestamp", None), + "date": getattr(replay, "date", None), + "utc_date": getattr(replay, "utc_date", None), + "speed": getattr(replay, "speed", None), + "category": getattr(replay, "category", None), + "type": getattr(replay, "type", None), + "is_ladder": getattr(replay, "is_ladder", False), + "is_private": getattr(replay, "is_private", False), + "filename": getattr(replay, "filename", None), + "file_time": getattr(replay, "file_time", None), + "frames": getattr(replay, "frames", None), + "build": getattr(replay, "build", None), + "release": getattr(replay, "release_string", None), + "game_fps": getattr(replay, "game_fps", None), + "game_length": getattr(getattr(replay, "game_length", None), "seconds", None), + "players": players, + "observers": observers, + "real_length": getattr(getattr(replay, "real_length", None), "seconds", None), + "real_type": getattr(replay, "real_type", None), + "time_zone": getattr(replay, "time_zone", None), + "versions": getattr(replay, "versions", None), } diff --git a/setup.py b/setup.py index d067af22..7efebbad 100644 --- a/setup.py +++ b/setup.py @@ -4,52 +4,49 @@ setuptools.setup( license="MIT", name="sc2reader", - version='1.3.1', + version="1.3.1", keywords=["starcraft 2", "sc2", "replay", "parser"], description="Utility for parsing Starcraft II replay files", - long_description=open("README.rst").read()+"\n\n"+open("CHANGELOG.rst").read(), - + long_description=open("README.rst").read() + "\n\n" + open("CHANGELOG.rst").read(), author="Kevin Leung", author_email="kkleung89@gmail.com", url="https://github.com/ggtracker/sc2reader", - platforms=["any"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: Implementation :: PyPy", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Games/Entertainment", - "Topic :: Games/Entertainment :: Real Time Strategy", - "Topic :: Software Development", - "Topic :: Software Development :: Libraries", - "Topic :: Utilities", + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Games/Entertainment", + "Topic :: Games/Entertainment :: Real Time Strategy", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", ], - entry_points={ - 'console_scripts': [ - 'sc2printer = sc2reader.scripts.sc2printer:main', - 'sc2replayer = sc2reader.scripts.sc2replayer:main', - 'sc2parse = sc2reader.scripts.sc2parse:main', - 'sc2attributes = sc2reader.scripts.sc2attributes:main', - 'sc2json = sc2reader.scripts.sc2json:main', + "console_scripts": [ + "sc2printer = sc2reader.scripts.sc2printer:main", + "sc2replayer = sc2reader.scripts.sc2replayer:main", + "sc2parse = sc2reader.scripts.sc2parse:main", + "sc2attributes = sc2reader.scripts.sc2attributes:main", + "sc2json = sc2reader.scripts.sc2json:main", ] }, - - install_requires=['mpyq>=0.2.3', 'argparse', 'ordereddict', 'unittest2', 'pil'] if float(sys.version[:3]) < 2.7 else ['mpyq>=0.2.4'], + install_requires=["mpyq>=0.2.3", "argparse", "ordereddict", "unittest2", "pil"] + if float(sys.version[:3]) < 2.7 + else ["mpyq>=0.2.4"], tests_require=["pytest"], packages=setuptools.find_packages(), include_package_data=True, - zip_safe=True + zip_safe=True, ) diff --git a/test_replays/test_replays.py b/test_replays/test_replays.py index f974aaf2..d6d8a6a2 100644 --- a/test_replays/test_replays.py +++ b/test_replays/test_replays.py @@ -79,8 +79,12 @@ def test_standard_1v1(self): self.assertEqual(emperor.result, "Win") self.assertEqual(boom.result, "Loss") - self.assertEqual(emperor.url, "http://eu.battle.net/sc2/en/profile/520049/1/Emperor/") - self.assertEqual(boom.url, "http://eu.battle.net/sc2/en/profile/1694745/1/Boom/") + self.assertEqual( + emperor.url, "http://eu.battle.net/sc2/en/profile/520049/1/Emperor/" + ) + self.assertEqual( + boom.url, "http://eu.battle.net/sc2/en/profile/1694745/1/Boom/" + ) self.assertEqual(len(replay.messages), 12) self.assertEqual(replay.messages[0].text, "hf") @@ -145,16 +149,25 @@ def test_random_player(self): self.assertEqual(gogeta.play_race, "Terran") replay = sc2reader.load_replay("test_replays/1.2.2.17811/6.SC2Replay") - permafrost = next(player for player in replay.players if player.name == "Permafrost") + permafrost = next( + player for player in replay.players if player.name == "Permafrost" + ) self.assertEqual(permafrost.pick_race, "Random") self.assertEqual(permafrost.play_race, "Protoss") def test_us_realm(self): replay = sc2reader.load_replay("test_replays/1.2.2.17811/5.SC2Replay") - shadesofgray = [player for player in replay.players if player.name == "ShadesofGray"][0] + shadesofgray = [ + player for player in replay.players if player.name == "ShadesofGray" + ][0] reddawn = [player for player in replay.players if player.name == "reddawn"][0] - self.assertEqual(shadesofgray.url, "http://us.battle.net/sc2/en/profile/2358439/1/ShadesofGray/") - self.assertEqual(reddawn.url, "http://us.battle.net/sc2/en/profile/2198663/1/reddawn/") + self.assertEqual( + shadesofgray.url, + "http://us.battle.net/sc2/en/profile/2358439/1/ShadesofGray/", + ) + self.assertEqual( + reddawn.url, "http://us.battle.net/sc2/en/profile/2198663/1/reddawn/" + ) def test_kr_realm_and_tampered_messages(self): """ @@ -165,8 +178,12 @@ def test_kr_realm_and_tampered_messages(self): self.assertEqual(replay.expansion, "WoL") first = [player for player in replay.players if player.name == "명지대학교"][0] second = [player for player in replay.players if player.name == "티에스엘사기수"][0] - self.assertEqual(first.url, "http://kr.battle.net/sc2/en/profile/258945/1/명지대학교/") - self.assertEqual(second.url, "http://kr.battle.net/sc2/en/profile/102472/1/티에스엘사기수/") + self.assertEqual( + first.url, "http://kr.battle.net/sc2/en/profile/258945/1/명지대학교/" + ) + self.assertEqual( + second.url, "http://kr.battle.net/sc2/en/profile/102472/1/티에스엘사기수/" + ) self.assertEqual(replay.messages[0].text, "sc2.replays.net") self.assertEqual(replay.messages[5].text, "sc2.replays.net") @@ -205,14 +222,30 @@ def test_hots_pids(self): replay = sc2reader.load_replay(replayfilename) self.assertEqual(replay.expansion, "HotS") - player_pids = set([player.pid for player in replay.players if player.is_human]) - ability_pids = set([event.player.pid for event in replay.events if "CommandEvent" in event.name]) + player_pids = set( + [player.pid for player in replay.players if player.is_human] + ) + ability_pids = set( + [ + event.player.pid + for event in replay.events + if "CommandEvent" in event.name + ] + ) self.assertEqual(ability_pids, player_pids) def test_wol_pids(self): - replay = sc2reader.load_replay("test_replays/1.5.4.24540/ggtracker_1471849.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/1.5.4.24540/ggtracker_1471849.SC2Replay" + ) self.assertEqual(replay.expansion, "WoL") - ability_pids = set([event.player.pid for event in replay.events if "CommandEvent" in event.name]) + ability_pids = set( + [ + event.player.pid + for event in replay.events + if "CommandEvent" in event.name + ] + ) player_pids = set([player.pid for player in replay.players]) self.assertEqual(ability_pids, player_pids) @@ -223,40 +256,55 @@ def test_hots_hatchfun(self): [ event.player.pid for event in replay.events - if "TargetUnitCommandEvent" in event.name and event.ability.name == "SpawnLarva" + if "TargetUnitCommandEvent" in event.name + and event.ability.name == "SpawnLarva" ] ) self.assertTrue(spawner_pids.issubset(player_pids)) def test_hots_vs_ai(self): - replay = sc2reader.load_replay("test_replays/2.0.0.24247/Cloud Kingdom LE (13).SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.0.24247/Cloud Kingdom LE (13).SC2Replay" + ) self.assertEqual(replay.expansion, "HotS") - replay = sc2reader.load_replay("test_replays/2.0.0.24247/Korhal City (19).SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.0.24247/Korhal City (19).SC2Replay" + ) self.assertEqual(replay.expansion, "HotS") def test_oracle_parsing(self): - replay = sc2reader.load_replay("test_replays/2.0.3.24764/ggtracker_1571740.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.3.24764/ggtracker_1571740.SC2Replay" + ) self.assertEqual(replay.expansion, "HotS") oracles = [unit for unit in replay.objects.values() if unit.name == "Oracle"] self.assertEqual(len(oracles), 2) def test_resume_from_replay(self): - replay = sc2reader.load_replay("test_replays/2.0.3.24764/resume_from_replay.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.3.24764/resume_from_replay.SC2Replay" + ) self.assertTrue(replay.resume_from_replay) self.assertEqual(replay.resume_method, 0) def test_clan_players(self): - replay = sc2reader.load_replay("test_replays/2.0.4.24944/Lunar Colony V.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.4.24944/Lunar Colony V.SC2Replay" + ) self.assertEqual(replay.expansion, "WoL") self.assertEqual(len(replay.people), 4) def test_WoL_204(self): - replay = sc2reader.load_replay("test_replays/2.0.4.24944/ggtracker_1789768.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.4.24944/ggtracker_1789768.SC2Replay" + ) self.assertEqual(replay.expansion, "WoL") self.assertEqual(len(replay.people), 2) def test_send_resources(self): - replay = sc2reader.load_replay("test_replays/2.0.4.24944/Backwater Complex (15).SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.4.24944/Backwater Complex (15).SC2Replay" + ) def test_cn_replays(self): replay = sc2reader.load_replay("test_replays/2.0.5.25092/cn1.SC2Replay") @@ -266,14 +314,20 @@ def test_cn_replays(self): def test_unit_types(self): """ sc2reader#136 regression test """ replay = sc2reader.load_replay("test_replays/2.0.8.25604/issue136.SC2Replay") - hellion_times = [u.started_at for u in replay.players[0].units if u.name == "Hellion"] - hellbat_times = [u.started_at for u in replay.players[0].units if u.name == "BattleHellion"] + hellion_times = [ + u.started_at for u in replay.players[0].units if u.name == "Hellion" + ] + hellbat_times = [ + u.started_at for u in replay.players[0].units if u.name == "BattleHellion" + ] self.assertEqual(hellion_times, [5180, 5183]) self.assertEqual(hellbat_times, [6736, 6741, 7215, 7220, 12004, 12038]) @unittest.expectedFailure def test_outmatched_pids(self): - replay = sc2reader.load_replay("test_replays/2.0.8.25604/issue131_arid_wastes.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.8.25604/issue131_arid_wastes.SC2Replay" + ) self.assertEqual(replay.players[0].pid, 1) self.assertEqual(replay.players[1].pid, 3) self.assertEqual(replay.players[2].pid, 4) @@ -291,7 +345,9 @@ def test_outmatched_pids(self): @unittest.expectedFailure def test_map_info(self): - replay = sc2reader.load_replay("test_replays/1.5.3.23260/ggtracker_109233.SC2Replay", load_map=True) + replay = sc2reader.load_replay( + "test_replays/1.5.3.23260/ggtracker_109233.SC2Replay", load_map=True + ) self.assertEqual(replay.map.map_info.tile_set, "Avernus") self.assertEqual(replay.map.map_info.fog_type, "Dark") self.assertEqual(replay.map.map_info.width, 176) @@ -308,7 +364,9 @@ def test_engine_plugins(self): replay = sc2reader.load_replay( "test_replays/2.0.5.25092/cn1.SC2Replay", - engine=sc2reader.engine.GameEngine(plugins=[ContextLoader(), APMTracker(), SelectionTracker()]), + engine=sc2reader.engine.GameEngine( + plugins=[ContextLoader(), APMTracker(), SelectionTracker()] + ), ) code, details = replay.plugins["ContextLoader"] @@ -317,7 +375,11 @@ def test_engine_plugins(self): @unittest.expectedFailure def test_factory_plugins(self): - from sc2reader.factories.plugins.replay import APMTracker, SelectionTracker, toJSON + from sc2reader.factories.plugins.replay import ( + APMTracker, + SelectionTracker, + toJSON, + ) factory = sc2reader.factories.SC2Factory() factory.register_plugin("Replay", APMTracker()) @@ -349,7 +411,8 @@ def test_gameheartnormalizer_plugin(self): [ event.player.pid for event in replay.events - if "TargetUnitCommandEvent" in event.name and event.ability.name == "SpawnLarva" + if "TargetUnitCommandEvent" in event.name + and event.ability.name == "SpawnLarva" ] ) self.assertTrue(spawner_pids.issubset(player_pids)) @@ -395,7 +458,9 @@ def test_creepTracker(self): ]: factory = sc2reader.factories.SC2Factory() pluginEngine = sc2reader.engine.GameEngine(plugins=[CreepTracker()]) - replay = factory.load_replay(replayfilename, engine=pluginEngine, load_map=True, load_level=4) + replay = factory.load_replay( + replayfilename, engine=pluginEngine, load_map=True, load_level=4 + ) for player_id in replay.player: if replay.player[player_id].play_race == "Zerg": @@ -405,7 +470,10 @@ def test_creepTracker(self): # print("CSBM", replay.player[player_id].creep_spread_by_minute) replay = factory.load_replay( - "test_replays/2.0.8.25605/ggtracker_3621402.SC2Replay", load_map=True, engine=pluginEngine, load_level=4 + "test_replays/2.0.8.25605/ggtracker_3621402.SC2Replay", + load_map=True, + engine=pluginEngine, + load_level=4, ) assert replay.player[2].max_creep_spread == (840, 24.83) assert replay.player[2].creep_spread_by_minute[420] == 9.4 @@ -413,18 +481,28 @@ def test_creepTracker(self): def test_bad_unit_ids(self): with self.assertRaises(CorruptTrackerFileError): - replay = sc2reader.load_replay("test_replays/2.0.11.26825/bad_unit_ids_1.SC2Replay", load_level=4) + replay = sc2reader.load_replay( + "test_replays/2.0.11.26825/bad_unit_ids_1.SC2Replay", load_level=4 + ) with self.assertRaises(CorruptTrackerFileError): - replay = sc2reader.load_replay("test_replays/2.0.9.26147/bad_unit_ids_2.SC2Replay", load_level=4) + replay = sc2reader.load_replay( + "test_replays/2.0.9.26147/bad_unit_ids_2.SC2Replay", load_level=4 + ) def test_daedalus_point(self): - replay = sc2reader.load_replay("test_replays/2.0.11.26825/DaedalusPoint.SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.0.11.26825/DaedalusPoint.SC2Replay" + ) def test_reloaded(self): - replay = sc2reader.load_replay("test_replays/2.1.3.28667/Habitation Station LE (54).SC2Replay") + replay = sc2reader.load_replay( + "test_replays/2.1.3.28667/Habitation Station LE (54).SC2Replay" + ) def test_214(self): - replay = sc2reader.load_replay("test_replays/2.1.4/Catallena LE.SC2Replay", load_level=4) + replay = sc2reader.load_replay( + "test_replays/2.1.4/Catallena LE.SC2Replay", load_level=4 + ) def test_lotv1(self): replay = sc2reader.load_replay("test_replays/lotv/lotv1.SC2Replay") @@ -438,7 +516,9 @@ def test_lotv_creepTracker(self): for replayfilename in ["test_replays/4.0.0.59587/1.SC2Replay"]: factory = sc2reader.factories.SC2Factory() pluginEngine = sc2reader.engine.GameEngine(plugins=[CreepTracker()]) - replay = factory.load_replay(replayfilename, engine=pluginEngine, load_map=True) + replay = factory.load_replay( + replayfilename, engine=pluginEngine, load_map=True + ) is_at_least_one_zerg_in_game = False for player_id in replay.player: @@ -469,7 +549,11 @@ def test_30_map(self): replay = factory.load_replay(replayfilename, load_level=1, load_map=True) def test_30_apms(self): - from sc2reader.factories.plugins.replay import APMTracker, SelectionTracker, toJSON + from sc2reader.factories.plugins.replay import ( + APMTracker, + SelectionTracker, + toJSON, + ) factory = sc2reader.factories.SC2Factory() factory.register_plugin("Replay", APMTracker()) @@ -497,10 +581,18 @@ def test_funny_minerals(self): xmldoc = minidom.parseString(replay.map.archive.read_file("Objects")) itemlist = xmldoc.getElementsByTagName("ObjectUnit") mineralPosStrs = [ - ou.attributes["Position"].value for ou in itemlist if "MineralField" in ou.attributes["UnitType"].value + ou.attributes["Position"].value + for ou in itemlist + if "MineralField" in ou.attributes["UnitType"].value ] mineralFieldNames = list( - set([ou.attributes["UnitType"].value for ou in itemlist if "MineralField" in ou.attributes["UnitType"].value]) + set( + [ + ou.attributes["UnitType"].value + for ou in itemlist + if "MineralField" in ou.attributes["UnitType"].value + ] + ) ) # print(mineralFieldNames) self.assertTrue(len(mineralPosStrs) > 0) @@ -515,7 +607,9 @@ def test_32(self): def test_33(self): for replaynum in range(1, 4): - replay = sc2reader.load_replay("test_replays/3.3.0/{}.SC2Replay".format(replaynum)) + replay = sc2reader.load_replay( + "test_replays/3.3.0/{}.SC2Replay".format(replaynum) + ) self.assertTrue(replay is not None) def test_33_shift_click_calldown_mule(self): @@ -635,7 +729,10 @@ def test_game_event_string(self): player.pid = 1 event = GameEvent(16, 1) event.player = player - self.assertEqual("{0}\tPlayer {1} - ({2}) ".format(time, player.pid, player.play_race), event._str_prefix()) + self.assertEqual( + "{0}\tPlayer {1} - ({2}) ".format(time, player.pid, player.play_race), + event._str_prefix(), + ) class TestGameEngine(unittest.TestCase): @@ -657,7 +754,9 @@ def handleInitGame(self, event, replay): def handleTestEvent(self, event, replay): if event.value == "d": - yield sc2reader.engine.PluginExit(self, code=1, details=dict(msg="Fail!")) + yield sc2reader.engine.PluginExit( + self, code=1, details=dict(msg="Fail!") + ) else: yield TestGameEngine.TestEvent("d") diff --git a/test_s2gs/test_all.py b/test_s2gs/test_all.py index a0c06b9b..be867add 100644 --- a/test_s2gs/test_all.py +++ b/test_s2gs/test_all.py @@ -2,28 +2,30 @@ # Newer unittest features aren't built in for python 2.6 import sys + if sys.version_info[:2] < (2, 7): import unittest2 as unittest else: import unittest import sc2reader + sc2reader.log_utils.log_to_console("INFO") class TestSummaries(unittest.TestCase): - def test_a_WoL_s2gs(self): summary = sc2reader.load_game_summary("test_s2gs/s2gs1.s2gs") self.assertEqual(summary.players[0].resource_collection_rate, 1276) - self.assertEqual(summary.players[0].build_order[0].order, 'Probe') - self.assertEqual(summary.expansion, 'WoL') + self.assertEqual(summary.players[0].build_order[0].order, "Probe") + self.assertEqual(summary.expansion, "WoL") def test_a_LotV_s2gs(self): summary = sc2reader.load_game_summary("test_s2gs/lotv.s2gs") self.assertEqual(summary.players[0].resource_collection_rate, 1619) - self.assertEqual(summary.players[0].build_order[0].order, 'Probe') - self.assertEqual(summary.expansion, 'HotS') + self.assertEqual(summary.players[0].build_order[0].order, "Probe") + self.assertEqual(summary.expansion, "HotS") + """ def test_another_HotS_s2gs(self): @@ -38,5 +40,5 @@ def test_another_HotS_s2gs(self): self.assertEqual(summary.expansion, 'HotS') """ -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()