diff --git a/.circleci/config.yml b/.circleci/config.yml index 1344edfc..85dfc97c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,36 +2,30 @@ version: 2.0 build_and_test: &build_and_test_steps - checkout - - run: sudo pip install -r requirements.txt - - run: pip install --user . - - run: sudo pip install pytest - - run: python --version ; pip --version ; pwd ; ls -l + # Do not use `sudo pip` + # pipx is already installed but `pipx list` is empty + - run: python --version ; pip --version ; pipx --version ; pwd ; ls -l + - run: pip install pytest -r requirements.txt + - run: pip install --editable . - run: pytest jobs: StyleCheck: docker: - - image: circleci/python:3.7 + - image: cimg/python:3.11 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: pip install black codespell ruff + - run: codespell -L queenland,uint,assertin + - run: ruff check - run: black . --check - Python2: - docker: - - image: circleci/python:2.7.15 - steps: *build_and_test_steps - Python3: docker: - - image: circleci/python:3.7 + - image: cimg/python:3.11 steps: *build_and_test_steps @@ -40,5 +34,4 @@ workflows: build: jobs: - StyleCheck - - Python2 - Python3 diff --git a/.gitignore b/.gitignore index 6643915a..22d56881 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *~ *.pyc +*.ipynb +*-checkpoint* dist build sc2reader.egg-info diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 69d3c035..ba71454a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,34 @@ CHANGELOG ============ +1.8.0 - May 4, 2022 +------------------- +* Fix various typos in docs #146 +* Fix various URLs for blizzard resources #151 #154 #156 +* Fix Ravager data #161 +* Add CommandManagerStateEvent #162 +* Fix participant state from gameheart #171 + + +1.7.0 - May 17, 2021 +-------------------- +* Add DOI to the README #128 +* Add various missing attributes for co-op replays #129 +* Add support for python 3.8, 3.9 #132 #136 +* Fix owner on an event with no unit #133 +* Add support for ResourceTradeEvent #135 +* Fix depot URL template #139 + +1.6.0 - July 30, 2020 +--------------------- +* Add support for protocol 80949 (StarCraft 5.0) #122 +* Fix toJson script #118 + +1.5.0 - January 18, 2020 +------------------------ +* Add support for protocol 77379 #106 #107 +* Workaround for missing data #102 #104 + 1.4.0 - August 19, 2019 ----------------------- * Add support for protocol 75689 #95 @@ -154,7 +182,7 @@ Changed Stuff (non-backwards compatible!): -------------------- * Fixes several game event parsing issues for older replays. -* Propperly maps ability ids for armory vehicle & ship armor upgrades. +* Properly maps ability ids for armory vehicle & ship armor upgrades. * Uses the US depot for SEA battle.net depot dependencies. * ``PlayerStatEvent.food_used`` and ``food_made`` are now properly divided by 4096 * ``AbilityEvent.flags`` are now processed into a dictionary mapping flag name to True/False (``AbilityEvent.flag``) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53017ede..6858ab95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ Support ========= -For real-time support, please visit #sc2reader on the FreeNode.net IRC. For all other support please use the sc2reader@googlegroups.com mailing list or open an issue in the github tracker. +As of Sept 2023, the best way to get support on sc2reader is on the GitHub project page. Preferably, it would be a new discussion https://github.com/ggtracker/sc2reader/discussions but can also be submitted as n issue Issues ========= @@ -14,7 +14,7 @@ If you can't share your code/replays publicly try to replicate with a smaller sc Patches ========= -Please submit patches by pull request where possible. Patches should add a test to confirm their fix and should not break previously working tests. Circle CI automatically runs tests on each pull request so please check https://circleci.com/gh/ggtracker/sc2reader to see the results of those tests. +Please submit patches by pull request where possible. Patches should add a test to confirm their fix and should not break previously working tests. Circle CI automatically runs tests on each pull request so please check https://circleci.com/gh/ggtracker/sc2reader to see the results of those tests. If you are having trouble running/add/fixing tests for your patch let me know and I'll see if I can help. @@ -22,7 +22,7 @@ If you are having trouble running/add/fixing tests for your patch let me know an Coding Style ============== -We'd like our code to follow PEP8 coding style in this project. +We would like our code to follow [Ruff](https://docs.astral.sh/ruff/) 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. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index aea5dd40..76299e88 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -13,5 +13,6 @@ Contributors: Kevin Leung - @StoicLoofah on github Daniele Zannotti (Durrza) Mike Anderson + Christian Clauss - @cclauss on github Special thanks to ggtracker, inc (ggtracker.com) for sponsoring sc2reader's continued development. diff --git a/README.rst b/README.rst index fa78c025..63d74a99 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,10 @@ +.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4007376.svg + :target: https://doi.org/10.5281/zenodo.4007376 What is sc2reader? ==================== -sc2reader is a python library for extracting information from various different Starcraft II resources. These resources currently include Replays, Maps, and Game Summaries; we have plans to add support for Battle.net profiles and would gladly accept adapters to the more entrenched SCII sites such as sc2ranks. +sc2reader is a Python 3 library for extracting information from various different Starcraft II resources. These resources currently include Replays, Maps, and Game Summaries; we have plans to add support for Battle.net profiles and would gladly accept adapters to the more entrenched SCII sites such as sc2ranks. There is a pressing need in the SC2 community for better statistics, better analytics, better tools for organizing and searching replays. Better websites for sharing replays and hosting tournaments. These tools can't be created without first being able to open up Starcraft II game files and analyze the data within. Our goal is to give anyone and everyone the power to construct their own tools, do their own analysis, and hack on their own Starcraft II projects under the open MIT license. @@ -16,7 +18,7 @@ sc2reader is currently powering: * Tools: `The Core`_ * Experiments: `Midi Conversion`_ -If you use sc2reader and you would like your tool, site, project, or implementation listed above, drop us a line on our `mailing list`_ or stop by our #sc2reader IRC channel and say hi! +If you use sc2reader and you would like your tool, site, project, or implementation listed above, drop us a line on our `mailing list`_. .. _gggreplays.com: http://gggreplays.com @@ -193,7 +195,7 @@ The new GameHeartNormalizerplugin is registered by default. Installation ================ -sc2reader runs on any system with Python 2.6+, 3.2+, or PyPy installed. +sc2reader runs on any system with Python 3.7+, or PyPy3 installed. From PyPI (stable) @@ -201,18 +203,14 @@ From PyPI (stable) Install from the latest release on PyPI with pip:: - pip install sc2reader - -or easy_install:: - - easy_install sc2reader + python3 -m pip install sc2reader or with setuptools (specify a valid x.x.x):: wget http://pypi.python.org/packages/source/s/sc2reader/sc2reader-x.x.x.tar.gz tar -xzf sc2reader-x.x.x.tar.gz cd sc2reader-x.x.x - python setup.py install + python3 setup.py install Releases to PyPi can be very delayed (sorry!), for the latest and greatest you are encouraged to install from Github upstream. @@ -233,7 +231,7 @@ or with setuptools:: wget -O sc2reader-upstream.tar.gz https://github.com/ggtracker/sc2reader/tarball/upstream tar -xzf sc2reader-upstream.tar.gz cd sc2reader-upstream - python setup.py install + python3 setup.py install .. _circle-ci: https://circleci.com/ .. _coveralls.io: https://coveralls.io @@ -248,7 +246,7 @@ Contributors should install from an active git repository using setuptools in `d git clone https://github.com/ggtracker/sc2reader.git cd sc2reader - python setup.py develop + python3 setup.py develop Please review the `CONTRIBUTING.md`_ file and get in touch with us before doing too much work. It'll make everyone happier in the long run. diff --git a/STYLE_GUIDE.rst b/STYLE_GUIDE.rst index 26b0bb8f..a25c04ff 100644 --- a/STYLE_GUIDE.rst +++ b/STYLE_GUIDE.rst @@ -1,11 +1,14 @@ STYLE GUIDE ============== -As a rough style guide, please lint your code with pep8:: +As a rough style guide, please lint your code with black, codespell, and ruff:: - pip install pep8 - pep8 --ignore E501,E226,E241 sc2reader + pip install black codespell ruff + codespell -L queenland,uint + ruff . + black . --check +More up-to-date checks may be detailed in `.circleci/config.yml`. All files should start with the following:: @@ -17,11 +20,4 @@ All files should start with the following:: All imports should be absolute. - -All string formatting sound be done in the following style:: - - "my {0} formatted {1} string {2}".format("super", "python", "example") - "the {x} style of {y} is also {z}".format(x="dict", y="arguments", z="acceptable") - -The format argument index numbers are important for 2.6 support. ``%`` formatting is not allowed for 3.x support - +All string formatting should be done with f-strings. See https://docs.python.org/3/reference/lexical_analysis.html#f-strings diff --git a/docs/source/articles/conceptsinsc2reader.rst b/docs/source/articles/conceptsinsc2reader.rst index d4467029..8a83704a 100644 --- a/docs/source/articles/conceptsinsc2reader.rst +++ b/docs/source/articles/conceptsinsc2reader.rst @@ -55,7 +55,7 @@ Many attributes in sc2reader are prefixed with ``game_`` and ``real_``. Game ref GameEngine ---------------- -The game engine is used to process replay events and augument the replay with new statistics and game state. It implements a plugin system that allows developers +The game engine is used to process replay events and augment the replay with new statistics and game state. It implements a plugin system that allows developers to inject their own logic into the game loop. It also allows plugins to ``yield`` new events to the event stream. This allows for basic message passing between plugins. diff --git a/docs/source/articles/creatingagameengineplugin.rst b/docs/source/articles/creatingagameengineplugin.rst index 1d371aeb..c37ad079 100644 --- a/docs/source/articles/creatingagameengineplugin.rst +++ b/docs/source/articles/creatingagameengineplugin.rst @@ -52,7 +52,7 @@ Plugins may also handle special ``InitGame`` and ``EndGame`` events. These handl replay state necessary. * handleEndGame - is called after all events have been processed and - can be used to perform post processing on aggrated data or clean up + can be used to perform post processing on aggregated data or clean up intermediate data caches. Message Passing diff --git a/docs/source/articles/whatsinareplay.rst b/docs/source/articles/whatsinareplay.rst index 5acf6ea4..466f3bf0 100644 --- a/docs/source/articles/whatsinareplay.rst +++ b/docs/source/articles/whatsinareplay.rst @@ -35,7 +35,7 @@ The last file provides a record of important events from the game. * replay.tracker.events - Records important game events and game state updates. -This file was introduced in 2.0.4 and is unncessary for the Starcraft II to reproduce the game. Instead, it records interesting game events and game state for community developers to use when analyzing replays. +This file was introduced in 2.0.4 and is unnecessary for the Starcraft II to reproduce the game. Instead, it records interesting game events and game state for community developers to use when analyzing replays. What isn't in a replay? diff --git a/docs/source/conf.py b/docs/source/conf.py index 15623ce9..4fc4f46e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # sc2reader documentation build configuration file, created by # sphinx-quickstart on Sun May 01 12:39:48 2011. @@ -11,7 +10,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys + import sc2reader autodoc_member_order = "bysource" @@ -28,7 +29,7 @@ # 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.imgmath", "sphinx.ext.viewcode"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -43,8 +44,8 @@ master_doc = "index" # General information about the project. -project = u"sc2reader" -copyright = u"2011-2013" +project = "sc2reader" +copyright = "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 @@ -182,7 +183,7 @@ # 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", "sc2reader Documentation", "Graylin Kim", "manual") ] # The name of an image file (relative to this directory) to place at the top of @@ -213,4 +214,4 @@ # 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", "sc2reader Documentation", ["Graylin Kim"], 1)] diff --git a/docs/source/tutorials/prettyprinter.rst b/docs/source/tutorials/prettyprinter.rst index a8584620..f72005a0 100644 --- a/docs/source/tutorials/prettyprinter.rst +++ b/docs/source/tutorials/prettyprinter.rst @@ -89,11 +89,11 @@ Many of the replay attributes are nested data structures which are generally all >>> replay.teams[0].players[0].color.hex 'B4141E' >>> replay.player.name('Remedy').url - 'http://us.battle.net/sc2/en/profile/2198663/1/Remedy/' + 'https://starcraft2.com/en-us/profile/1/1/2198663' Each of these nested structures can be found either on its own reference page or lumped together with the other minor structures on the Misc Structures page. -So now all we need to do is build the ouput using the available replay attributes. Lets start with the header portion. We'll use a block string formatting method that makes this clean and easy: +So now all we need to do is build the output using the available replay attributes. Lets start with the header portion. We'll use a block string formatting method that makes this clean and easy: :: @@ -182,7 +182,7 @@ So lets put it all together into the final script, ``prettyPrinter.py``: Making Improvements --------------------------- -So our script works fine for single files, but what if you want to handle multiple files or directories? sc2reader provides two functions for loading replays: :meth:`~sc2reader.factories.SC2Factory.load_replay` and :meth:`~sc2reader.factories.SC2Factory.load_replays` which return a single replay and a list respectively. :meth:`~sc2reader.factories.SC2Factory.load_replay` was used above for convenience but :meth:`~sc2reader.factories.SC2Factory.load_replays` is much more versitile. Here's the difference: +So our script works fine for single files, but what if you want to handle multiple files or directories? sc2reader provides two functions for loading replays: :meth:`~sc2reader.factories.SC2Factory.load_replay` and :meth:`~sc2reader.factories.SC2Factory.load_replays` which return a single replay and a list respectively. :meth:`~sc2reader.factories.SC2Factory.load_replay` was used above for convenience but :meth:`~sc2reader.factories.SC2Factory.load_replays` is much more versatile. Here's the difference: * :meth:`~sc2reader.factories.SC2Factory.load_replay`: accepts a file path or an opened file object. * :meth:`~sc2reader.factories.SC2Factory.load_replays`: accepts a collection of opened file objects or file paths. Can also accept a single path to a directory; files will be pulled from the directory using :func:`~sc2reader.utils.get_files` and the given options. @@ -199,7 +199,7 @@ With this in mind, lets make a slight change to our main function to support any Any time that you start dealing with directories or collections of files you run into dangers with recursion and annoyances of tedium. sc2reader provides options to mitigate these concerns. * directory: Default ''. The directory string when supplied, becomes the base of all the file paths sent into sc2reader and can save you the hassle of fully qualifying your file paths each time. -* depth: Default -1. When handling directory inputs, sc2reader searches the directory recursively until all .SC2Replay files have been loaded. By setting the maxium depth value this behavior can be mitigated. +* depth: Default -1. When handling directory inputs, sc2reader searches the directory recursively until all .SC2Replay files have been loaded. By setting the maximum depth value this behavior can be mitigated. * exclude: Default []. When recursing directories you can choose to exclude directories from the file search by directory name (not full path). * followlinks: Default false. When recursing directories, enables or disables the follow symlinks behavior. diff --git a/docs/source/utilities.rst b/docs/source/utilities.rst index e06078ac..340d87d3 100644 --- a/docs/source/utilities.rst +++ b/docs/source/utilities.rst @@ -26,19 +26,6 @@ Length :members: -PersonDict ---------------- - -.. autoclass:: PersonDict - :members: - - -AttributeDict ------------------- - -.. autoclass:: AttributeDict - :members: - get_files --------------- diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index 74bb1567..af272530 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """sc2autosave is a utility for reorganizing and renaming Starcraft II files. Overview @@ -37,7 +36,7 @@ -------------------- The --rename option allows you to specify a renaming format string. The string -is constructed the pythonic (3.0) way with {:field} indicating the substition +is constructed the pythonic (3.0) way with {:field} indicating the substitution of a field. The forward slash (/) is a special character here which terminates a folder name and allows for organization into subdirectories. All other string characters form the template into which the fields are inserted. @@ -78,16 +77,16 @@ keeps the script from looking into the 'Saved' subdirectory. sc2autosave \ - --source ~/My\ Documents/Starcraft\ II/Accounts/.../Mutliplayer \ - --dest ~/My\ Documents/Starcraft\ II/Accounts/.../Multiplater/Saved \ + --source ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer/Saved \ --period 10 \ --depth 0 This next configuration runs in batch mode using the default renaming format. sc2autosave \ - --source ~/My\ Documents/Starcraft\ II/Accounts/.../Mutliplayer \ - --dest ~/My\ Documents/Starcraft\ II/Accounts/.../Multiplater/Saved \ + --source ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer/Saved \ --rename (ZvP) Lost Temple: ShadesofGray(Z) vs Trisfall(P).SC2Replay @@ -97,8 +96,8 @@ by replay format and favors ShadesofGray in the player and team orderings. sc2autosave \ - --source ~/My\ Documents/Starcraft\ II/Accounts/.../Mutliplayer \ - --dest ~/My\ Documents/Starcraft\ II/Accounts/.../Multiplater/Saved \ + --source ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer/Saved \ --rename "{:format}/{:matchup} on {:map}: {:teams}" \ --player-format "{:name}({:play_race})" \ --team-order-by number \ @@ -113,8 +112,8 @@ length to show both minutes and seconds. sc2autosave \ - --source ~/My\ Documents/Starcraft\ II/Accounts/.../Mutliplayer \ - --dest ~/My\ Documents/Starcraft\ II/Accounts/.../Multiplater/Saved \ + --source ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplayer/Saved \ --rename "{:matchup}/({:length}) {:map}: {:teams}" \ --player-format "{:name}({:play_race})" \ --team-order-by number \ @@ -138,7 +137,7 @@ files every SECONDS seconds. --rename FORMAT :map - Inserts the map name. - :date - Inserts a string formated datetime object using --date-format. + :date - Inserts a string formatted datetime object using --date-format. :length - Inserts a string formatted time object using --length-format. :teams - Inserts a comma separated player list. Teams are separated with a ' vs ' string. Format the player with --player-format. @@ -160,18 +159,72 @@ POST-Parse, how to do it?!?!?!?! """ import argparse -import cPickle import os +import pickle +import re import shutil import sys +import textwrap import time +from functools import cmp_to_key import sc2reader -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 + +class Formatter(argparse.RawTextHelpFormatter): + """FlexiFormatter which respects new line formatting and wraps the rest + + Example: + >>> parser = argparse.ArgumentParser(formatter_class=FlexiFormatter) + >>> parser.add_argument('a',help='''\ + ... This argument's help text will have this first long line\ + ... wrapped to fit the target window size so that your text\ + ... remains flexible. + ... + ... 1. This option list + ... 2. is still persisted + ... 3. and the option strings get wrapped like this\ + ... with an indent for readability. + ... + ... You must use backslashes at the end of lines to indicate that\ + ... you want the text to wrap instead of preserving the newline. + ... ''') + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + @classmethod + def new(cls, **options): + return lambda prog: Formatter(prog, **options) + + def _split_lines(self, text, width): + lines = list() + 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: + 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() + new_lines = textwrap.wrap( + text=line, + width=width, + 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 [" "]) + + return lines def run(args): @@ -185,7 +238,6 @@ def run(args): # 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. for path in scan(args, state): @@ -194,13 +246,14 @@ def run(args): replay = sc2reader.load_replay(path, load_level=2) except KeyboardInterrupt: raise - except: + except Exception as e: # Failure to parse + args.log.write(f"{e!r}") file_name = os.path.basename(path) directory = make_directory(args, ("parse_error",)) new_path = os.path.join(directory, file_name) source_path = path[len(args.source) :] - args.log.write("Error parsing replay: {0}".format(source_path)) + args.log.write(f"Error parsing replay: {source_path}") if not args.dryrun: args.action.run(path, new_path) @@ -216,6 +269,7 @@ def run(args): # 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" @@ -229,9 +283,9 @@ def run(args): # 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)) + args.log.write(msg.format(args.action["type"], source_path, dest_path)) if not args.dryrun: - args.action.run(path, new_path) + 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 @@ -250,7 +304,7 @@ def run(args): def filter_out_replay(args, replay): - player_names = set([player.name for player in replay.players]) + player_names = {player.name for player in replay.players} filter_out_player = not set(args.filter_player) & player_names if args.filter_rule == "ALLOW": @@ -262,7 +316,7 @@ def filter_out_replay(args, replay): # We need to create these compare functions at runtime because the ordering # hinges on the --favored PLAYER options passed in from the command line. def create_compare_funcs(args): - favored_set = set(name.lower() for name in args.favored) + favored_set = {name.lower() for name in args.favored} def player_compare(player1, player2): # Normalize the player names and generate our key metrics @@ -290,8 +344,8 @@ def player_compare(player1, player2): def team_compare(team1, team2): # Normalize the team name lists and generate our key metrics - team1_names = set(p.name.lower() for p in team1.players) - team2_names = set(p.name.lower() for p in team2.players) + team1_names = {p.name.lower() for p in team1.players} + team2_names = {p.name.lower() for p in team2.players} team1_favored = team1_names & favored_set team2_favored = team2_names & favored_set @@ -316,19 +370,19 @@ def team_compare(team1, team2): def generate_aspects(args, replay): - teams = sorted(replay.teams, args.team_compare) + teams = sorted(replay.teams, key=cmp_to_key(args.team_compare)) matchups, team_strings = list(), list() for team in teams: - team.players = sorted(team.players, args.player_compare) + team.players = sorted(team.players, key=cmp_to_key(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) team_strings.append(string) - return sc2reader.utils.AttributeDict( + return dict( result=teams[0].result, length=replay.length, - map=replay.map, + map=replay.map_name.replace(":", ""), type=replay.type, date=replay.date.strftime(args.date_format), matchup="v".join(matchups), @@ -341,7 +395,7 @@ 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(f"Creating subfolder: {directory}\n") if not args.dryrun: os.mkdir(directory) elif not os.path.isdir(directory): @@ -351,7 +405,7 @@ def make_directory(args, path_parts): def scan(args, state): - args.log.write("SCANNING: {0}\n".format(args.source)) + args.log.write(f"SCANNING: {args.source}\n") files = sc2reader.utils.get_files( path=args.source, regex=args.exclude_files, @@ -360,7 +414,7 @@ def scan(args, state): depth=args.depth, followlinks=args.follow_links, ) - return filter(lambda f: os.path.getctime(f) > state.last_sync, files) + return filter(lambda f: os.path.getctime(f) > state["last_sync"], files) def exit(msg, *args, **kwargs): @@ -374,13 +428,10 @@ def reset(args): 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 - ) + f"About to reset directory: {args.dest}\nAll files and subdirectories will be removed." ) - choice = raw_input("Proceed anyway? (y/n) ") - if choice.lower() == "y": - args.log.write("Removing old directory: {0}\n".format(args.dest)) + if input("Proceed anyway? (y/n) ").strip().lower() == "y": + args.log.write(f"Removing old directory: {args.dest}\n") if not args.dryrun: print(args.dest) shutil.rmtree(args.dest) @@ -390,7 +441,7 @@ def reset(args): def setup(args): args.team_compare, args.player_compare = create_compare_funcs(args) - args.action = sc2reader.utils.AttributeDict( + args.action = dict( type=args.action, run=shutil.copy if args.action == "COPY" else shutil.move ) if not os.path.exists(args.source): @@ -404,35 +455,35 @@ def setup(args): if not args.dryrun: os.mkdir(args.dest) else: - args.log.write("Creating destination: {0}\n".format(args.dest)) + args.log.write(f"Creating destination: {args.dest}\n") elif not os.path.isdir(args.dest): sys.exit("Destination must be a directory.\n\nScript Aborted") data_file = os.path.join(args.dest, "sc2autosave.dat") - args.log.write("Loading state from file: {0}\n".format(data_file)) + args.log.write(f"Loading state from file: {data_file}\n") if os.path.isfile(data_file) and not args.reset: - with open(data_file) as file: - return cPickle.load(file) + with open(data_file, "rb") as file: + return pickle.load(file) else: - return sc2reader.utils.AttributeDict(last_sync=0) + return dict(last_sync=0) def save_state(state, args): - state.last_sync = time.time() + state["last_sync"] = time.time() data_file = os.path.join(args.dest, "sc2autosave.dat") if not args.dryrun: - with open(data_file, "w") as file: - cPickle.dump(state, file) + with open(data_file, "wb") as file: + pickle.dump(state, file) else: - args.log.write("Writing state to file: {0}\n".format(data_file)) + args.log.write(f"Writing state to file: {data_file}\n") def main(): parser = argparse.ArgumentParser( description="Automatically copy new replays to directory", fromfile_prefix_chars="@", - formatter_class=sc2reader.scripts.utils.Formatter.new(max_help_position=35), + formatter_class=Formatter.new(max_help_position=35), epilog="And that's all folks", ) @@ -602,7 +653,7 @@ def main(): try: run(parser.parse_args()) except KeyboardInterrupt: - print("\n\nScript Interupted. Process Aborting") + print("\n\nScript Interrupted. Process Aborting") if __name__ == "__main__": diff --git a/examples/sc2store.py b/examples/sc2store.py index 0a072f43..7f54a3fb 100755 --- a/examples/sc2store.py +++ b/examples/sc2store.py @@ -1,19 +1,16 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -import cPickle import os +import pickle import shutil -import sys import sqlite3 +import sys import time import sc2reader from pprint import PrettyPrinter -pprint = PrettyPrinter(indent=2).pprint - from sqlalchemy import create_engine from sqlalchemy import Column, ForeignKey, distinct, Table from sqlalchemy import Integer, String, Sequence, DateTime @@ -24,6 +21,8 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy +pprint = PrettyPrinter(indent=2).pprint + Base = declarative_base() party_member = Table( @@ -196,7 +195,7 @@ def main(): for path in args.paths: for file_name in sc2reader.utils.get_files(path, depth=0): - print("CREATING: {0}".format(file_name)) + print(f"CREATING: {file_name}") db.add(Game(sc2reader.read_file(file_name), db)) db.commit() diff --git a/generate_build_data.py b/generate_build_data.py index 32bdbbd2..7419e547 100644 --- a/generate_build_data.py +++ b/generate_build_data.py @@ -58,7 +58,7 @@ def generate_build_data(balance_data_path): elif unit_id == "Drone": build_ability_name = "ZergBuild" else: - build_ability_name = "{}Build".format(unit_id) + build_ability_name = f"{unit_id}Build" if build_ability_index: abilities[build_ability_index] = build_ability_name @@ -77,7 +77,7 @@ def generate_build_data(balance_data_path): while len(ability_lookup[build_ability_name]) <= command_index: ability_lookup[build_ability_name].append("") - build_command_name = "Build{}".format(built_unit_id) + build_command_name = f"Build{built_unit_id}" ability_lookup[build_ability_name][ command_index ] = build_command_name @@ -87,7 +87,7 @@ def generate_build_data(balance_data_path): train_ability_index = train_unit_elements[0].get("ability") if train_ability_index: - train_ability_name = "{}Train".format(unit_id) + train_ability_name = f"{unit_id}Train" abilities[train_ability_index] = train_ability_name if train_ability_name not in ability_lookup: @@ -103,9 +103,7 @@ def generate_build_data(balance_data_path): if element_ability_index != train_ability_index: train_ability_index = element_ability_index - train_ability_name = "{}Train{}".format( - unit_id, trained_unit_name - ) + train_ability_name = f"{unit_id}Train{trained_unit_name}" abilities[train_ability_index] = train_ability_name if train_ability_name not in ability_lookup: @@ -137,7 +135,7 @@ def generate_build_data(balance_data_path): ): ability_lookup[train_ability_name].append("") - train_command_name = "Train{}".format(trained_unit_name) + train_command_name = f"Train{trained_unit_name}" ability_lookup[train_ability_name][ command_index ] = train_command_name @@ -145,7 +143,7 @@ def generate_build_data(balance_data_path): research_upgrade_elements = root.findall("./researches/upgrade") if research_upgrade_elements: research_ability_index = research_upgrade_elements[0].get("ability") - research_ability_name = "{}Research".format(unit_id) + research_ability_name = f"{unit_id}Research" abilities[research_ability_index] = research_ability_name @@ -163,7 +161,7 @@ def generate_build_data(balance_data_path): while len(ability_lookup[research_ability_name]) <= command_index: ability_lookup[research_ability_name].append("") - research_command_name = "Research{}".format(researched_upgrade_id) + research_command_name = f"Research{researched_upgrade_id}" ability_lookup[research_ability_name][ command_index ] = research_command_name @@ -175,7 +173,7 @@ def generate_build_data(balance_data_path): sorted(abilities.items(), key=lambda x: int(x[0])) ) - unit_lookup = dict((unit_name, unit_name) for _, unit_name in sorted_units.items()) + unit_lookup = {unit_name: unit_name for _, unit_name in sorted_units.items()} return sorted_units, sorted_abilities, unit_lookup, ability_lookup @@ -258,7 +256,7 @@ def main(): unit_lookup_path = os.path.join( args.project_path, "sc2reader", "data", "unit_lookup.csv" ) - with open(unit_lookup_path, "r") as file: + with open(unit_lookup_path) 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] @@ -267,7 +265,7 @@ def main(): ability_lookup_path = os.path.join( args.project_path, "sc2reader", "data", "ability_lookup.csv" ) - with open(ability_lookup_path, "r") as file: + with open(ability_lookup_path) 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] @@ -290,7 +288,7 @@ def main(): "sc2reader", "data", args.expansion, - "{}_units.csv".format(args.build_version), + f"{args.build_version}_units.csv", ) with open(units_file_path, "w") as file: csv_writer = csv.writer(file, delimiter=",", lineterminator=os.linesep) @@ -302,7 +300,7 @@ def main(): "sc2reader", "data", args.expansion, - "{}_abilities.csv".format(args.build_version), + f"{args.build_version}_abilities.csv", ) with open(abilities_file_path, "w") as file: csv_writer = csv.writer(file, delimiter=",", lineterminator=os.linesep) diff --git a/new_units.py b/new_units.py index dcc609a0..26ffa133 100644 --- a/new_units.py +++ b/new_units.py @@ -9,30 +9,30 @@ import sys UNIT_LOOKUP = dict() -for entry in pkgutil.get_data("sc2reader.data", "unit_lookup.csv").split("\n"): +for entry in pkgutil.get_data("sc2reader.data", "unit_lookup.csv").splitlines(): if not entry: continue str_id, title = entry.strip().split(",") UNIT_LOOKUP[str_id] = title -with open(sys.argv[1], "r") as new_units: +with open(sys.argv[1]) 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(f"{new_unit_name},{new_unit_name}") print("") print("") ABIL_LOOKUP = dict() -for entry in pkgutil.get_data("sc2reader.data", "ability_lookup.csv").split("\n"): +for entry in pkgutil.get_data("sc2reader.data", "ability_lookup.csv").splitlines(): 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: +with open(sys.argv[2]) 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)) + print(f"{new_ability_name},{new_ability_name}") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e33c1d05 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "setuptools>=61.2", +] + +[project] +name = "sc2reader" +description = "Utility for parsing Starcraft II replay files" +keywords = [ + "parser", + "replay", + "sc2", + "starcraft 2", +] +license = { text = "MIT" } +authors = [ { name = "Kevin Leung", email = "kkleung89@gmail.com" } ] +requires-python = ">=3.9" +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 :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Games/Entertainment", + "Topic :: Games/Entertainment :: Real Time Strategy", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", +] +dynamic = [ + "readme", + "version", +] +dependencies = [ + "mpyq", + "pillow", +] +optional-dependencies.testing = [ + "pytest", +] +urls.Homepage = "https://github.com/ggtracker/sc2reader" +scripts.sc2attributes = "sc2reader.scripts.sc2attributes:main" +scripts.sc2json = "sc2reader.scripts.sc2json:main" +scripts.sc2parse = "sc2reader.scripts.sc2parse:main" +scripts.sc2printer = "sc2reader.scripts.sc2printer:main" +scripts.sc2replayer = "sc2reader.scripts.sc2replayer:main" + +[tool.setuptools] +include-package-data = true +zip-safe = true +platforms = [ "any" ] + +[tool.setuptools.dynamic] +readme = { file = [ "README.rst", "CHANGELOG.rst" ] } +version = { attr = "sc2reader.__version__" } + +[tool.setuptools.packages] +find = { namespaces = false } + +[tool.ruff] +line-length = 129 + +lint.ignore = [ + "F401", # module imported but unused; consider using `importlib.util.find_spec` to test for availability + "F403", # Run `removestar` on this codebase + "F405", # Run `removestar` on this codebase + "F841", # Run `ruff --select=F841 --fix .` +] +lint.mccabe.max-complexity = 34 diff --git a/sc2reader/__init__.py b/sc2reader/__init__.py index 68c429f1..49b76657 100644 --- a/sc2reader/__init__.py +++ b/sc2reader/__init__.py @@ -1,26 +1,24 @@ -# -*- coding: utf-8 -*- """ - sc2reader - ~~~~~~~~~~~ +sc2reader +~~~~~~~~~~~ - A library for loading data from Starcraft II game resources. +A library for loading data from Starcraft II game resources. - SC2Factory methods called on the package will be delegated to the default - SC2Factory. To default to a cached factory set one or more of the following - variables in your environment: +SC2Factory methods called on the package will be delegated to the default +SC2Factory. To default to a cached factory set one or more of the following +variables in your environment: - SC2READER_CACHE_DIR = '/absolute/path/to/existing/cache/directory/' - SC2READER_CACHE_MAX_SIZE = MAXIMUM_CACHE_ENTRIES_TO_HOLD_IN_MEMORY + SC2READER_CACHE_DIR = '/absolute/path/to/existing/cache/directory/' + SC2READER_CACHE_MAX_SIZE = MAXIMUM_CACHE_ENTRIES_TO_HOLD_IN_MEMORY - You can also set the default factory via setFactory, useFileCache, useDictCache, - or useDoubleCache functions. +You can also set the default factory via setFactory, useFileCache, useDictCache, +or useDoubleCache functions. - :copyright: (c) 2011 by Graylin Kim. - :license: MIT, see LICENSE for more details. +:copyright: (c) 2011 by Graylin Kim. +:license: MIT, see LICENSE for more details. """ -from __future__ import absolute_import, print_function, unicode_literals, division -__version__ = "0.8.0" +__version__ = "1.8.0" import os import sys diff --git a/sc2reader/constants.py b/sc2reader/constants.py index 1edfa70e..a1ae473f 100644 --- a/sc2reader/constants.py +++ b/sc2reader/constants.py @@ -1,5 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division +import json +import pkgutil # These are found in Repack-MPQ/fileset.{locale}#Mods#Core.SC2Mod#{locale}.SC2Data/LocalizedData/Editor/EditorCategoryStrings.txt # EDSTR_CATEGORY_Race @@ -104,9 +104,6 @@ } -import json -import pkgutil - attributes_json = pkgutil.get_data("sc2reader.data", "attributes.json").decode("utf8") attributes_dict = json.loads(attributes_json) LOBBY_PROPERTIES = dict() diff --git a/sc2reader/data/HOWTO.md b/sc2reader/data/HOWTO.md index ab67031d..828e5e28 100644 --- a/sc2reader/data/HOWTO.md +++ b/sc2reader/data/HOWTO.md @@ -14,3 +14,6 @@ At the time of writing, the latest build version is 53644. e.g. `python3 sc2reader/generate_build_data.py LotV 53644 balance_data/ sc2reader/` This will generate the necessary data files to support the new build version (namely, `53644_abilities.csv`, `53644_units.csv`, and updated versions of `ability_lookup.csv` and `unit_lookup.csv`). 4. Finally, modify `sc2reader/data/__init__.py` and `sc2reader/resources.py` to register support for the new build version. + +If you are not able to see the correct expansion for the balance data, you may need to authenticate. See the instructions at +https://github.com/ggtracker/sc2reader/issues/98#issuecomment-542554588 on how to do that diff --git a/sc2reader/data/LotV/76114_abilities.csv b/sc2reader/data/LotV/76114_abilities.csv new file mode 100644 index 00000000..cf1d1ae1 --- /dev/null +++ b/sc2reader/data/LotV/76114_abilities.csv @@ -0,0 +1,411 @@ +39,Taunt +40,stop +42,move +45,attack +60,SprayTerran +61,SprayZerg +62,SprayProtoss +63,SalvageShared +65,GhostHoldFire +66,GhostWeaponsFree +68,Explode +69,FleetBeaconResearch +70,FungalGrowth +71,GuardianShield +72,MULERepair +73,ZerglingTrain +74,NexusTrainMothership +75,Feedback +76,MassRecall +78,HallucinationArchon +79,HallucinationColossus +80,HallucinationHighTemplar +81,HallucinationImmortal +82,HallucinationPhoenix +83,HallucinationProbe +84,HallucinationStalker +85,HallucinationVoidRay +86,HallucinationWarpPrism +87,HallucinationZealot +88,MULEGather +90,CalldownMULE +91,GravitonBeam +95,SpawnChangeling +102,Rally +103,ProgressRally +104,RallyCommand +105,RallyNexus +106,RallyHatchery +107,RoachWarrenResearch +109,InfestedTerrans +110,NeuralParasite +111,SpawnLarva +112,StimpackMarauder +113,SupplyDrop +117,UltraliskCavernResearch +119,SCVHarvest +120,ProbeHarvest +122,que1 +123,que5 +124,que5CancelToSelection +126,que5Addon +127,BuildInProgress +128,Repair +129,TerranBuild +131,Stimpack +132,GhostCloak +134,MedivacHeal +135,SiegeMode +136,Unsiege +137,BansheeCloak +138,MedivacTransport +139,ScannerSweep +140,Yamato +141,AssaultMode +142,FighterMode +143,BunkerTransport +144,CommandCenterTransport +145,CommandCenterLiftOff +146,CommandCenterLand +147,BarracksFlyingBuild +148,BarracksLiftOff +149,FactoryFlyingBuild +150,FactoryLiftOff +151,StarportFlyingBuild +152,StarportLiftOff +153,FactoryLand +154,StarportLand +155,CommandCenterTrain +156,BarracksLand +157,SupplyDepotLower +158,SupplyDepotRaise +159,BarracksTrain +160,FactoryTrain +161,StarportTrain +162,EngineeringBayResearch +164,GhostAcademyTrain +165,BarracksTechLabResearch +166,FactoryTechLabResearch +167,StarportTechLabResearch +168,GhostAcademyResearch +169,ArmoryResearch +170,ProtossBuild +171,WarpPrismTransport +172,GatewayTrain +173,StargateTrain +174,RoboticsFacilityTrain +175,NexusTrain +176,PsiStorm +177,HangarQueue5 +179,CarrierTrain +180,ForgeResearch +181,RoboticsBayResearch +182,TemplarArchiveResearch +183,ZergBuild +184,DroneHarvest +185,EvolutionChamberResearch +186,UpgradeToLair +187,UpgradeToHive +188,UpgradeToGreaterSpire +189,HiveResearch +190,SpawningPoolResearch +191,HydraliskDenResearch +192,GreaterSpireResearch +193,LarvaTrain +194,MorphToBroodLord +195,BurrowBanelingDown +196,BurrowBanelingUp +197,BurrowDroneDown +198,BurrowDroneUp +199,BurrowHydraliskDown +200,BurrowHydraliskUp +201,BurrowRoachDown +202,BurrowRoachUp +203,BurrowZerglingDown +204,BurrowZerglingUp +205,BurrowInfestorTerranDown +206,BurrowInfestorTerranUp +207,RedstoneLavaCritterBurrow +208,RedstoneLavaCritterInjuredBurrow +209,RedstoneLavaCritterUnburrow +210,RedstoneLavaCritterInjuredUnburrow +211,OverlordTransport +214,WarpGateTrain +215,BurrowQueenDown +216,BurrowQueenUp +217,NydusCanalTransport +218,Blink +219,BurrowInfestorDown +220,BurrowInfestorUp +221,MorphToOverseer +222,UpgradeToPlanetaryFortress +223,InfestationPitResearch +224,BanelingNestResearch +225,BurrowUltraliskDown +226,BurrowUltraliskUp +227,UpgradeToOrbital +228,UpgradeToWarpGate +229,MorphBackToGateway +230,OrbitalLiftOff +231,OrbitalCommandLand +232,ForceField +233,PhasingMode +234,TransportMode +235,FusionCoreResearch +236,CyberneticsCoreResearch +237,TwilightCouncilResearch +238,TacNukeStrike +241,EMP +243,HiveTrain +245,Transfusion +254,AttackRedirect +255,StimpackRedirect +256,StimpackMarauderRedirect +258,StopRedirect +259,GenerateCreep +260,QueenBuild +261,SpineCrawlerUproot +262,SporeCrawlerUproot +263,SpineCrawlerRoot +264,SporeCrawlerRoot +265,CreepTumorBurrowedBuild +266,BuildAutoTurret +267,ArchonWarp +268,NydusNetworkBuild +270,Charge +274,Contaminate +277,que5Passive +278,que5PassiveCancelToSelection +281,RavagerCorrosiveBile +282,ShieldBatteryRechargeChanneled +303,BurrowLurkerMPDown +304,BurrowLurkerMPUp +307,BurrowRavagerDown +308,BurrowRavagerUp +309,MorphToRavager +310,MorphToTransportOverlord +312,ThorNormalMode +317,DigesterCreepSpray +321,MorphToMothership +346,XelNagaHealingShrine +355,MothershipCoreMassRecall +357,MorphToHellion +367,MorphToHellionTank +375,MorphToSwarmHostBurrowedMP +376,MorphToSwarmHostMP +378,attackProtossBuilding +380,stopProtossBuilding +381,BlindingCloud +383,Yoink +386,ViperConsumeStructure +389,TestZerg +390,VolatileBurstBuilding +397,WidowMineBurrow +398,WidowMineUnburrow +399,WidowMineAttack +400,TornadoMissile +403,HallucinationOracle +404,MedivacSpeedBoost +405,ExtendingBridgeNEWide8Out +406,ExtendingBridgeNEWide8 +407,ExtendingBridgeNWWide8Out +408,ExtendingBridgeNWWide8 +409,ExtendingBridgeNEWide10Out +410,ExtendingBridgeNEWide10 +411,ExtendingBridgeNWWide10Out +412,ExtendingBridgeNWWide10 +413,ExtendingBridgeNEWide12Out +414,ExtendingBridgeNEWide12 +415,ExtendingBridgeNWWide12Out +416,ExtendingBridgeNWWide12 +418,CritterFlee +419,OracleRevelation +427,MothershipCorePurifyNexus +428,XelNaga_Caverns_DoorE +429,XelNaga_Caverns_DoorEOpened +430,XelNaga_Caverns_DoorN +431,XelNaga_Caverns_DoorNE +432,XelNaga_Caverns_DoorNEOpened +433,XelNaga_Caverns_DoorNOpened +434,XelNaga_Caverns_DoorNW +435,XelNaga_Caverns_DoorNWOpened +436,XelNaga_Caverns_DoorS +437,XelNaga_Caverns_DoorSE +438,XelNaga_Caverns_DoorSEOpened +439,XelNaga_Caverns_DoorSOpened +440,XelNaga_Caverns_DoorSW +441,XelNaga_Caverns_DoorSWOpened +442,XelNaga_Caverns_DoorW +443,XelNaga_Caverns_DoorWOpened +444,XelNaga_Caverns_Floating_BridgeNE8Out +445,XelNaga_Caverns_Floating_BridgeNE8 +446,XelNaga_Caverns_Floating_BridgeNW8Out +447,XelNaga_Caverns_Floating_BridgeNW8 +448,XelNaga_Caverns_Floating_BridgeNE10Out +449,XelNaga_Caverns_Floating_BridgeNE10 +450,XelNaga_Caverns_Floating_BridgeNW10Out +451,XelNaga_Caverns_Floating_BridgeNW10 +452,XelNaga_Caverns_Floating_BridgeNE12Out +453,XelNaga_Caverns_Floating_BridgeNE12 +454,XelNaga_Caverns_Floating_BridgeNW12Out +455,XelNaga_Caverns_Floating_BridgeNW12 +456,XelNaga_Caverns_Floating_BridgeH8Out +457,XelNaga_Caverns_Floating_BridgeH8 +458,XelNaga_Caverns_Floating_BridgeV8Out +459,XelNaga_Caverns_Floating_BridgeV8 +460,XelNaga_Caverns_Floating_BridgeH10Out +461,XelNaga_Caverns_Floating_BridgeH10 +462,XelNaga_Caverns_Floating_BridgeV10Out +463,XelNaga_Caverns_Floating_BridgeV10 +464,XelNaga_Caverns_Floating_BridgeH12Out +465,XelNaga_Caverns_Floating_BridgeH12 +466,XelNaga_Caverns_Floating_BridgeV12Out +467,XelNaga_Caverns_Floating_BridgeV12 +468,TemporalField +494,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +495,SnowRefinery_Terran_ExtendingBridgeNEShort8 +496,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +497,SnowRefinery_Terran_ExtendingBridgeNWShort8 +519,CausticSpray +522,MorphToLurker +526,PurificationNovaTargeted +528,LockOn +530,LockOnCancel +532,Hyperjump +534,ThorAPMode +537,NydusWormTransport +538,OracleWeapon +544,LocustMPFlyingSwoop +545,HallucinationDisruptor +546,HallucinationAdept +547,VoidRaySwarmDamageBoost +548,SeekerDummyChannel +549,AiurLightBridgeNE8Out +550,AiurLightBridgeNE8 +551,AiurLightBridgeNE10Out +552,AiurLightBridgeNE10 +553,AiurLightBridgeNE12Out +554,AiurLightBridgeNE12 +555,AiurLightBridgeNW8Out +556,AiurLightBridgeNW8 +557,AiurLightBridgeNW10Out +558,AiurLightBridgeNW10 +559,AiurLightBridgeNW12Out +560,AiurLightBridgeNW12 +573,ShakurasLightBridgeNE8Out +574,ShakurasLightBridgeNE8 +575,ShakurasLightBridgeNE10Out +576,ShakurasLightBridgeNE10 +577,ShakurasLightBridgeNE12Out +578,ShakurasLightBridgeNE12 +579,ShakurasLightBridgeNW8Out +580,ShakurasLightBridgeNW8 +581,ShakurasLightBridgeNW10Out +582,ShakurasLightBridgeNW10 +583,ShakurasLightBridgeNW12Out +584,ShakurasLightBridgeNW12 +585,VoidMPImmortalReviveRebuild +587,ArbiterMPStasisField +588,ArbiterMPRecall +589,CorsairMPDisruptionWeb +590,MorphToGuardianMP +591,MorphToDevourerMP +592,DefilerMPConsume +593,DefilerMPDarkSwarm +594,DefilerMPPlague +595,DefilerMPBurrow +596,DefilerMPUnburrow +597,QueenMPEnsnare +598,QueenMPSpawnBroodlings +599,QueenMPInfestCommandCenter +603,OracleBuild +607,ParasiticBomb +608,AdeptPhaseShift +611,LurkerHoldFire +612,LurkerRemoveHoldFire +615,LiberatorAGTarget +616,LiberatorAATarget +618,AiurLightBridgeAbandonedNE8Out +619,AiurLightBridgeAbandonedNE8 +620,AiurLightBridgeAbandonedNE10Out +621,AiurLightBridgeAbandonedNE10 +622,AiurLightBridgeAbandonedNE12Out +623,AiurLightBridgeAbandonedNE12 +624,AiurLightBridgeAbandonedNW8Out +625,AiurLightBridgeAbandonedNW8 +626,AiurLightBridgeAbandonedNW10Out +627,AiurLightBridgeAbandonedNW10 +628,AiurLightBridgeAbandonedNW12Out +629,AiurLightBridgeAbandonedNW12 +630,KD8Charge +633,AdeptPhaseShiftCancel +634,AdeptShadePhaseShiftCancel +635,SlaynElementalGrab +637,PortCity_Bridge_UnitNE8Out +638,PortCity_Bridge_UnitNE8 +639,PortCity_Bridge_UnitSE8Out +640,PortCity_Bridge_UnitSE8 +641,PortCity_Bridge_UnitNW8Out +642,PortCity_Bridge_UnitNW8 +643,PortCity_Bridge_UnitSW8Out +644,PortCity_Bridge_UnitSW8 +645,PortCity_Bridge_UnitNE10Out +646,PortCity_Bridge_UnitNE10 +647,PortCity_Bridge_UnitSE10Out +648,PortCity_Bridge_UnitSE10 +649,PortCity_Bridge_UnitNW10Out +650,PortCity_Bridge_UnitNW10 +651,PortCity_Bridge_UnitSW10Out +652,PortCity_Bridge_UnitSW10 +653,PortCity_Bridge_UnitNE12Out +654,PortCity_Bridge_UnitNE12 +655,PortCity_Bridge_UnitSE12Out +656,PortCity_Bridge_UnitSE12 +657,PortCity_Bridge_UnitNW12Out +658,PortCity_Bridge_UnitNW12 +659,PortCity_Bridge_UnitSW12Out +660,PortCity_Bridge_UnitSW12 +661,PortCity_Bridge_UnitN8Out +662,PortCity_Bridge_UnitN8 +663,PortCity_Bridge_UnitS8Out +664,PortCity_Bridge_UnitS8 +665,PortCity_Bridge_UnitE8Out +666,PortCity_Bridge_UnitE8 +667,PortCity_Bridge_UnitW8Out +668,PortCity_Bridge_UnitW8 +669,PortCity_Bridge_UnitN10Out +670,PortCity_Bridge_UnitN10 +671,PortCity_Bridge_UnitS10Out +672,PortCity_Bridge_UnitS10 +673,PortCity_Bridge_UnitE10Out +674,PortCity_Bridge_UnitE10 +675,PortCity_Bridge_UnitW10Out +676,PortCity_Bridge_UnitW10 +677,PortCity_Bridge_UnitN12Out +678,PortCity_Bridge_UnitN12 +679,PortCity_Bridge_UnitS12Out +680,PortCity_Bridge_UnitS12 +681,PortCity_Bridge_UnitE12Out +682,PortCity_Bridge_UnitE12 +683,PortCity_Bridge_UnitW12Out +684,PortCity_Bridge_UnitW12 +687,DarkTemplarBlink +690,BattlecruiserAttack +692,BattlecruiserMove +694,BattlecruiserStop +696,SpawnLocustsTargeted +697,ViperParasiticBombRelay +698,ParasiticBombRelayDodge +699,VoidRaySwarmDamageBoostCancel +703,ChannelSnipe +706,DarkShrineResearch +707,LurkerDenMPResearch +708,ObserverSiegeMorphtoObserver +709,ObserverMorphtoObserverSiege +710,OverseerMorphtoOverseerSiegeMode +711,OverseerSiegeModeMorphtoOverseer +712,RavenScramblerMissile +714,RavenRepairDroneHeal +715,RavenShredderMissile +716,ChronoBoostEnergyCost +717,NexusMassRecall diff --git a/sc2reader/data/LotV/76114_units.csv b/sc2reader/data/LotV/76114_units.csv new file mode 100644 index 00000000..6c7af845 --- /dev/null +++ b/sc2reader/data/LotV/76114_units.csv @@ -0,0 +1,1023 @@ +3,System_Snapshot_Dummy +21,Ball +22,StereoscopicOptionsUnit +23,Colossus +24,TechLab +25,Reactor +27,InfestorTerran +28,BanelingCocoon +29,Baneling +30,Mothership +31,PointDefenseDrone +32,Changeling +33,ChangelingZealot +34,ChangelingMarineShield +35,ChangelingMarine +36,ChangelingZerglingWings +37,ChangelingZergling +39,CommandCenter +40,SupplyDepot +41,Refinery +42,Barracks +43,EngineeringBay +44,MissileTurret +45,Bunker +46,SensorTower +47,GhostAcademy +48,Factory +49,Starport +51,Armory +52,FusionCore +53,AutoTurret +54,SiegeTankSieged +55,SiegeTank +56,VikingAssault +57,VikingFighter +58,CommandCenterFlying +59,BarracksTechLab +60,BarracksReactor +61,FactoryTechLab +62,FactoryReactor +63,StarportTechLab +64,StarportReactor +65,FactoryFlying +66,StarportFlying +67,SCV +68,BarracksFlying +69,SupplyDepotLowered +70,Marine +71,Reaper +72,Ghost +73,Marauder +74,Thor +75,Hellion +76,Medivac +77,Banshee +78,Raven +79,Battlecruiser +80,Nuke +81,Nexus +82,Pylon +83,Assimilator +84,Gateway +85,Forge +86,FleetBeacon +87,TwilightCouncil +88,PhotonCannon +89,Stargate +90,TemplarArchive +91,DarkShrine +92,RoboticsBay +93,RoboticsFacility +94,CyberneticsCore +95,Zealot +96,Stalker +97,HighTemplar +98,DarkTemplar +99,Sentry +100,Phoenix +101,Carrier +102,VoidRay +103,WarpPrism +104,Observer +105,Immortal +106,Probe +107,Interceptor +108,Hatchery +109,CreepTumor +110,Extractor +111,SpawningPool +112,EvolutionChamber +113,HydraliskDen +114,Spire +115,UltraliskCavern +116,InfestationPit +117,NydusNetwork +118,BanelingNest +119,RoachWarren +120,SpineCrawler +121,SporeCrawler +122,Lair +123,Hive +124,GreaterSpire +125,Egg +126,Drone +127,Zergling +128,Overlord +129,Hydralisk +130,Mutalisk +131,Ultralisk +132,Roach +133,Infestor +134,Corruptor +135,BroodLordCocoon +136,BroodLord +137,BanelingBurrowed +138,DroneBurrowed +139,HydraliskBurrowed +140,RoachBurrowed +141,ZerglingBurrowed +142,InfestorTerranBurrowed +143,RedstoneLavaCritterBurrowed +144,RedstoneLavaCritterInjuredBurrowed +145,RedstoneLavaCritter +146,RedstoneLavaCritterInjured +147,QueenBurrowed +148,Queen +149,InfestorBurrowed +150,OverlordCocoon +151,Overseer +152,PlanetaryFortress +153,UltraliskBurrowed +154,OrbitalCommand +155,WarpGate +156,OrbitalCommandFlying +157,ForceField +158,WarpPrismPhasing +159,CreepTumorBurrowed +160,CreepTumorQueen +161,SpineCrawlerUprooted +162,SporeCrawlerUprooted +163,Archon +164,NydusCanal +165,BroodlingEscort +166,GhostAlternate +167,GhostNova +168,RichMineralField +169,RichMineralField750 +170,Ursadon +172,LurkerMPBurrowed +173,LurkerMP +174,LurkerDenMP +175,LurkerMPEgg +176,NydusCanalAttacker +177,OverlordTransport +178,Ravager +179,RavagerBurrowed +180,RavagerCocoon +181,TransportOverlordCocoon +182,XelNagaTower +184,Oracle +185,Tempest +187,InfestedTerransEgg +188,Larva +189,OverseerSiegeMode +191,ReaperPlaceholder +192,MarineACGluescreenDummy +193,FirebatACGluescreenDummy +194,MedicACGluescreenDummy +195,MarauderACGluescreenDummy +196,VultureACGluescreenDummy +197,SiegeTankACGluescreenDummy +198,VikingACGluescreenDummy +199,BansheeACGluescreenDummy +200,BattlecruiserACGluescreenDummy +201,OrbitalCommandACGluescreenDummy +202,BunkerACGluescreenDummy +203,BunkerUpgradedACGluescreenDummy +204,MissileTurretACGluescreenDummy +205,HellbatACGluescreenDummy +206,GoliathACGluescreenDummy +207,CycloneACGluescreenDummy +208,WraithACGluescreenDummy +209,ScienceVesselACGluescreenDummy +210,HerculesACGluescreenDummy +211,ThorACGluescreenDummy +212,PerditionTurretACGluescreenDummy +213,FlamingBettyACGluescreenDummy +214,DevastationTurretACGluescreenDummy +215,BlasterBillyACGluescreenDummy +216,SpinningDizzyACGluescreenDummy +217,ZerglingKerriganACGluescreenDummy +218,RaptorACGluescreenDummy +219,QueenCoopACGluescreenDummy +220,HydraliskACGluescreenDummy +221,HydraliskLurkerACGluescreenDummy +222,MutaliskBroodlordACGluescreenDummy +223,BroodLordACGluescreenDummy +224,UltraliskACGluescreenDummy +225,TorrasqueACGluescreenDummy +226,OverseerACGluescreenDummy +227,LurkerACGluescreenDummy +228,SpineCrawlerACGluescreenDummy +229,SporeCrawlerACGluescreenDummy +230,NydusNetworkACGluescreenDummy +231,OmegaNetworkACGluescreenDummy +232,ZerglingZagaraACGluescreenDummy +233,SwarmlingACGluescreenDummy +234,QueenZagaraACGluescreenDummy +235,BanelingACGluescreenDummy +236,SplitterlingACGluescreenDummy +237,AberrationACGluescreenDummy +238,ScourgeACGluescreenDummy +239,CorruptorACGluescreenDummy +240,OverseerZagaraACGluescreenDummy +241,BileLauncherACGluescreenDummy +242,SwarmQueenACGluescreenDummy +243,RoachACGluescreenDummy +244,RoachVileACGluescreenDummy +245,RavagerACGluescreenDummy +246,SwarmHostACGluescreenDummy +247,MutaliskACGluescreenDummy +248,GuardianACGluescreenDummy +249,DevourerACGluescreenDummy +250,ViperACGluescreenDummy +251,BrutaliskACGluescreenDummy +252,LeviathanACGluescreenDummy +253,ZealotACGluescreenDummy +254,ZealotAiurACGluescreenDummy +255,DragoonACGluescreenDummy +256,HighTemplarACGluescreenDummy +257,ArchonACGluescreenDummy +258,ImmortalACGluescreenDummy +259,ObserverACGluescreenDummy +260,PhoenixAiurACGluescreenDummy +261,ReaverACGluescreenDummy +262,TempestACGluescreenDummy +263,PhotonCannonACGluescreenDummy +264,ZealotVorazunACGluescreenDummy +265,ZealotShakurasACGluescreenDummy +266,StalkerShakurasACGluescreenDummy +267,DarkTemplarShakurasACGluescreenDummy +268,CorsairACGluescreenDummy +269,VoidRayACGluescreenDummy +270,VoidRayShakurasACGluescreenDummy +271,OracleACGluescreenDummy +272,DarkArchonACGluescreenDummy +273,DarkPylonACGluescreenDummy +274,ZealotPurifierACGluescreenDummy +275,SentryPurifierACGluescreenDummy +276,ImmortalKaraxACGluescreenDummy +277,ColossusACGluescreenDummy +278,ColossusPurifierACGluescreenDummy +279,PhoenixPurifierACGluescreenDummy +280,CarrierACGluescreenDummy +281,CarrierAiurACGluescreenDummy +282,KhaydarinMonolithACGluescreenDummy +283,ShieldBatteryACGluescreenDummy +284,EliteMarineACGluescreenDummy +285,MarauderCommandoACGluescreenDummy +286,SpecOpsGhostACGluescreenDummy +287,HellbatRangerACGluescreenDummy +288,StrikeGoliathACGluescreenDummy +289,HeavySiegeTankACGluescreenDummy +290,RaidLiberatorACGluescreenDummy +291,RavenTypeIIACGluescreenDummy +292,CovertBansheeACGluescreenDummy +293,RailgunTurretACGluescreenDummy +294,BlackOpsMissileTurretACGluescreenDummy +295,SupplicantACGluescreenDummy +296,StalkerTaldarimACGluescreenDummy +297,SentryTaldarimACGluescreenDummy +298,HighTemplarTaldarimACGluescreenDummy +299,ImmortalTaldarimACGluescreenDummy +300,ColossusTaldarimACGluescreenDummy +301,WarpPrismTaldarimACGluescreenDummy +302,PhotonCannonTaldarimACGluescreenDummy +303,StukovInfestedCivilianACGluescreenDummy +304,StukovInfestedMarineACGluescreenDummy +305,StukovInfestedSiegeTankACGluescreenDummy +306,StukovInfestedDiamondbackACGluescreenDummy +307,StukovInfestedBansheeACGluescreenDummy +308,SILiberatorACGluescreenDummy +309,StukovInfestedBunkerACGluescreenDummy +310,StukovInfestedMissileTurretACGluescreenDummy +311,StukovBroodQueenACGluescreenDummy +312,ZealotFenixACGluescreenDummy +313,SentryFenixACGluescreenDummy +314,AdeptFenixACGluescreenDummy +315,ImmortalFenixACGluescreenDummy +316,ColossusFenixACGluescreenDummy +317,DisruptorACGluescreenDummy +318,ObserverFenixACGluescreenDummy +319,ScoutACGluescreenDummy +320,CarrierFenixACGluescreenDummy +321,PhotonCannonFenixACGluescreenDummy +322,PrimalZerglingACGluescreenDummy +323,RavasaurACGluescreenDummy +324,PrimalRoachACGluescreenDummy +325,FireRoachACGluescreenDummy +326,PrimalGuardianACGluescreenDummy +327,PrimalHydraliskACGluescreenDummy +328,PrimalMutaliskACGluescreenDummy +329,PrimalImpalerACGluescreenDummy +330,PrimalSwarmHostACGluescreenDummy +331,CreeperHostACGluescreenDummy +332,PrimalUltraliskACGluescreenDummy +333,TyrannozorACGluescreenDummy +334,PrimalWurmACGluescreenDummy +335,HHReaperACGluescreenDummy +336,HHWidowMineACGluescreenDummy +337,HHHellionTankACGluescreenDummy +338,HHWraithACGluescreenDummy +339,HHVikingACGluescreenDummy +340,HHBattlecruiserACGluescreenDummy +341,HHRavenACGluescreenDummy +342,HHBomberPlatformACGluescreenDummy +343,HHMercStarportACGluescreenDummy +344,HHMissileTurretACGluescreenDummy +345,TychusReaperACGluescreenDummy +346,TychusFirebatACGluescreenDummy +347,TychusSpectreACGluescreenDummy +348,TychusMedicACGluescreenDummy +349,TychusMarauderACGluescreenDummy +350,TychusWarhoundACGluescreenDummy +351,TychusHERCACGluescreenDummy +352,TychusGhostACGluescreenDummy +353,TychusSCVAutoTurretACGluescreenDummy +354,ZeratulStalkerACGluescreenDummy +355,ZeratulSentryACGluescreenDummy +356,ZeratulDarkTemplarACGluescreenDummy +357,ZeratulImmortalACGluescreenDummy +358,ZeratulObserverACGluescreenDummy +359,ZeratulDisruptorACGluescreenDummy +360,ZeratulWarpPrismACGluescreenDummy +361,ZeratulPhotonCannonACGluescreenDummy +362,MechaZerglingACGluescreenDummy +363,MechaBanelingACGluescreenDummy +364,MechaHydraliskACGluescreenDummy +365,MechaInfestorACGluescreenDummy +366,MechaCorruptorACGluescreenDummy +367,MechaUltraliskACGluescreenDummy +368,MechaOverseerACGluescreenDummy +369,MechaLurkerACGluescreenDummy +370,MechaBattlecarrierLordACGluescreenDummy +371,MechaSpineCrawlerACGluescreenDummy +372,MechaSporeCrawlerACGluescreenDummy +374,RenegadeLongboltMissileWeapon +375,NeedleSpinesWeapon +376,CorruptionWeapon +377,InfestedTerransWeapon +378,NeuralParasiteWeapon +379,PointDefenseDroneReleaseWeapon +380,HunterSeekerWeapon +381,MULE +383,ThorAAWeapon +384,PunisherGrenadesLMWeapon +385,VikingFighterWeapon +386,ATALaserBatteryLMWeapon +387,ATSLaserBatteryLMWeapon +388,LongboltMissileWeapon +389,D8ChargeWeapon +390,YamatoWeapon +391,IonCannonsWeapon +392,AcidSalivaWeapon +393,SpineCrawlerWeapon +394,SporeCrawlerWeapon +395,GlaiveWurmWeapon +396,GlaiveWurmM2Weapon +397,GlaiveWurmM3Weapon +398,StalkerWeapon +399,EMP2Weapon +400,BacklashRocketsLMWeapon +401,PhotonCannonWeapon +402,ParasiteSporeWeapon +404,Broodling +405,BroodLordBWeapon +408,AutoTurretReleaseWeapon +409,LarvaReleaseMissile +410,AcidSpinesWeapon +411,FrenzyWeapon +412,ContaminateWeapon +424,BeaconArmy +425,BeaconDefend +426,BeaconAttack +427,BeaconHarass +428,BeaconIdle +429,BeaconAuto +430,BeaconDetect +431,BeaconScout +432,BeaconClaim +433,BeaconExpand +434,BeaconRally +435,BeaconCustom1 +436,BeaconCustom2 +437,BeaconCustom3 +438,BeaconCustom4 +443,LiberatorAG +445,PreviewBunkerUpgraded +446,HellionTank +447,Cyclone +448,WidowMine +449,Liberator +451,Adept +452,Disruptor +453,SwarmHostMP +454,Viper +455,ShieldBattery +456,HighTemplarSkinPreview +457,MothershipCore +458,Viking +467,AssimilatorRich +468,RichVespeneGeyser +469,ExtractorRich +470,InhibitorZoneSmall +471,InhibitorZoneMedium +472,InhibitorZoneLarge +473,RavagerCorrosiveBileMissile +474,RavagerWeaponMissile +475,RefineryRich +476,RenegadeMissileTurret +477,Rocks2x2NonConjoined +478,FungalGrowthMissile +479,NeuralParasiteTentacleMissile +480,Beacon_Protoss +481,Beacon_ProtossSmall +482,Beacon_Terran +483,Beacon_TerranSmall +484,Beacon_Zerg +485,Beacon_ZergSmall +486,Lyote +487,CarrionBird +488,KarakMale +489,KarakFemale +490,UrsadakFemaleExotic +491,UrsadakMale +492,UrsadakFemale +493,UrsadakCalf +494,UrsadakMaleExotic +495,UtilityBot +496,CommentatorBot1 +497,CommentatorBot2 +498,CommentatorBot3 +499,CommentatorBot4 +500,Scantipede +501,Dog +502,Sheep +503,Cow +504,InfestedTerransEggPlacement +505,InfestorTerransWeapon +506,MineralField +507,MineralField450 +508,MineralField750 +509,MineralFieldOpaque +510,MineralFieldOpaque900 +511,VespeneGeyser +512,SpacePlatformGeyser +513,DestructibleSearchlight +514,DestructibleBullhornLights +515,DestructibleStreetlight +516,DestructibleSpacePlatformSign +517,DestructibleStoreFrontCityProps +518,DestructibleBillboardTall +519,DestructibleBillboardScrollingText +520,DestructibleSpacePlatformBarrier +521,DestructibleSignsDirectional +522,DestructibleSignsConstruction +523,DestructibleSignsFunny +524,DestructibleSignsIcons +525,DestructibleSignsWarning +526,DestructibleGarage +527,DestructibleGarageLarge +528,DestructibleTrafficSignal +529,TrafficSignal +530,BraxisAlphaDestructible1x1 +531,BraxisAlphaDestructible2x2 +532,DestructibleDebris4x4 +533,DestructibleDebris6x6 +534,DestructibleRock2x4Vertical +535,DestructibleRock2x4Horizontal +536,DestructibleRock2x6Vertical +537,DestructibleRock2x6Horizontal +538,DestructibleRock4x4 +539,DestructibleRock6x6 +540,DestructibleRampDiagonalHugeULBR +541,DestructibleRampDiagonalHugeBLUR +542,DestructibleRampVerticalHuge +543,DestructibleRampHorizontalHuge +544,DestructibleDebrisRampDiagonalHugeULBR +545,DestructibleDebrisRampDiagonalHugeBLUR +546,WarpPrismSkinPreview +547,SiegeTankSkinPreview +548,ThorAP +549,ThorAALance +550,LiberatorSkinPreview +551,OverlordGenerateCreepKeybind +552,MengskStatueAlone +553,MengskStatue +554,WolfStatue +555,GlobeStatue +556,Weapon +557,GlaiveWurmBounceWeapon +558,BroodLordWeapon +559,BroodLordAWeapon +560,CreepBlocker1x1 +561,PermanentCreepBlocker1x1 +562,PathingBlocker1x1 +563,PathingBlocker2x2 +564,AutoTestAttackTargetGround +565,AutoTestAttackTargetAir +566,AutoTestAttacker +567,HelperEmitterSelectionArrow +568,MultiKillObject +569,ShapeGolfball +570,ShapeCone +571,ShapeCube +572,ShapeCylinder +573,ShapeDodecahedron +574,ShapeIcosahedron +575,ShapeOctahedron +576,ShapePyramid +577,ShapeRoundedCube +578,ShapeSphere +579,ShapeTetrahedron +580,ShapeThickTorus +581,ShapeThinTorus +582,ShapeTorus +583,Shape4PointStar +584,Shape5PointStar +585,Shape6PointStar +586,Shape8PointStar +587,ShapeArrowPointer +588,ShapeBowl +589,ShapeBox +590,ShapeCapsule +591,ShapeCrescentMoon +592,ShapeDecahedron +593,ShapeDiamond +594,ShapeFootball +595,ShapeGemstone +596,ShapeHeart +597,ShapeJack +598,ShapePlusSign +599,ShapeShamrock +600,ShapeSpade +601,ShapeTube +602,ShapeEgg +603,ShapeYenSign +604,ShapeX +605,ShapeWatermelon +606,ShapeWonSign +607,ShapeTennisball +608,ShapeStrawberry +609,ShapeSmileyFace +610,ShapeSoccerball +611,ShapeRainbow +612,ShapeSadFace +613,ShapePoundSign +614,ShapePear +615,ShapePineapple +616,ShapeOrange +617,ShapePeanut +618,ShapeO +619,ShapeLemon +620,ShapeMoneyBag +621,ShapeHorseshoe +622,ShapeHockeyStick +623,ShapeHockeyPuck +624,ShapeHand +625,ShapeGolfClub +626,ShapeGrape +627,ShapeEuroSign +628,ShapeDollarSign +629,ShapeBasketball +630,ShapeCarrot +631,ShapeCherry +632,ShapeBaseball +633,ShapeBaseballBat +634,ShapeBanana +635,ShapeApple +636,ShapeCashLarge +637,ShapeCashMedium +638,ShapeCashSmall +639,ShapeFootballColored +640,ShapeLemonSmall +641,ShapeOrangeSmall +642,ShapeTreasureChestOpen +643,ShapeTreasureChestClosed +644,ShapeWatermelonSmall +645,UnbuildableRocksDestructible +646,UnbuildableBricksDestructible +647,UnbuildablePlatesDestructible +648,Debris2x2NonConjoined +649,EnemyPathingBlocker1x1 +650,EnemyPathingBlocker2x2 +651,EnemyPathingBlocker4x4 +652,EnemyPathingBlocker8x8 +653,EnemyPathingBlocker16x16 +654,ScopeTest +655,SentryACGluescreenDummy +656,StukovInfestedTrooperACGluescreenDummy +672,CollapsibleTerranTowerDebris +673,DebrisRampLeft +674,DebrisRampRight +678,LocustMP +679,CollapsibleRockTowerDebris +680,NydusCanalCreeper +681,SwarmHostBurrowedMP +682,WarHound +683,WidowMineBurrowed +684,ExtendingBridgeNEWide8Out +685,ExtendingBridgeNEWide8 +686,ExtendingBridgeNWWide8Out +687,ExtendingBridgeNWWide8 +688,ExtendingBridgeNEWide10Out +689,ExtendingBridgeNEWide10 +690,ExtendingBridgeNWWide10Out +691,ExtendingBridgeNWWide10 +692,ExtendingBridgeNEWide12Out +693,ExtendingBridgeNEWide12 +694,ExtendingBridgeNWWide12Out +695,ExtendingBridgeNWWide12 +697,CollapsibleRockTowerDebrisRampRight +698,CollapsibleRockTowerDebrisRampLeft +699,XelNaga_Caverns_DoorE +700,XelNaga_Caverns_DoorEOpened +701,XelNaga_Caverns_DoorN +702,XelNaga_Caverns_DoorNE +703,XelNaga_Caverns_DoorNEOpened +704,XelNaga_Caverns_DoorNOpened +705,XelNaga_Caverns_DoorNW +706,XelNaga_Caverns_DoorNWOpened +707,XelNaga_Caverns_DoorS +708,XelNaga_Caverns_DoorSE +709,XelNaga_Caverns_DoorSEOpened +710,XelNaga_Caverns_DoorSOpened +711,XelNaga_Caverns_DoorSW +712,XelNaga_Caverns_DoorSWOpened +713,XelNaga_Caverns_DoorW +714,XelNaga_Caverns_DoorWOpened +715,XelNaga_Caverns_Floating_BridgeNE8Out +716,XelNaga_Caverns_Floating_BridgeNE8 +717,XelNaga_Caverns_Floating_BridgeNW8Out +718,XelNaga_Caverns_Floating_BridgeNW8 +719,XelNaga_Caverns_Floating_BridgeNE10Out +720,XelNaga_Caverns_Floating_BridgeNE10 +721,XelNaga_Caverns_Floating_BridgeNW10Out +722,XelNaga_Caverns_Floating_BridgeNW10 +723,XelNaga_Caverns_Floating_BridgeNE12Out +724,XelNaga_Caverns_Floating_BridgeNE12 +725,XelNaga_Caverns_Floating_BridgeNW12Out +726,XelNaga_Caverns_Floating_BridgeNW12 +727,XelNaga_Caverns_Floating_BridgeH8Out +728,XelNaga_Caverns_Floating_BridgeH8 +729,XelNaga_Caverns_Floating_BridgeV8Out +730,XelNaga_Caverns_Floating_BridgeV8 +731,XelNaga_Caverns_Floating_BridgeH10Out +732,XelNaga_Caverns_Floating_BridgeH10 +733,XelNaga_Caverns_Floating_BridgeV10Out +734,XelNaga_Caverns_Floating_BridgeV10 +735,XelNaga_Caverns_Floating_BridgeH12Out +736,XelNaga_Caverns_Floating_BridgeH12 +737,XelNaga_Caverns_Floating_BridgeV12Out +738,XelNaga_Caverns_Floating_BridgeV12 +741,CollapsibleTerranTowerPushUnitRampLeft +742,CollapsibleTerranTowerPushUnitRampRight +745,CollapsibleRockTowerPushUnit +746,CollapsibleTerranTowerPushUnit +747,CollapsibleRockTowerPushUnitRampRight +748,CollapsibleRockTowerPushUnitRampLeft +749,DigesterCreepSprayTargetUnit +750,DigesterCreepSprayUnit +751,NydusCanalAttackerWeapon +752,ViperConsumeStructureWeapon +755,ResourceBlocker +756,TempestWeapon +757,YoinkMissile +761,YoinkVikingAirMissile +763,YoinkVikingGroundMissile +765,YoinkSiegeTankMissile +767,WarHoundWeapon +769,EyeStalkWeapon +772,WidowMineWeapon +773,WidowMineAirWeapon +774,MothershipCoreWeaponWeapon +775,TornadoMissileWeapon +776,TornadoMissileDummyWeapon +777,TalonsMissileWeapon +778,CreepTumorMissile +779,LocustMPEggAMissileWeapon +780,LocustMPEggBMissileWeapon +781,LocustMPWeapon +783,RepulsorCannonWeapon +787,CollapsibleRockTowerDiagonal +788,CollapsibleTerranTowerDiagonal +789,CollapsibleTerranTowerRampLeft +790,CollapsibleTerranTowerRampRight +791,Ice2x2NonConjoined +792,IceProtossCrates +793,ProtossCrates +794,TowerMine +795,PickupPalletGas +796,PickupPalletMinerals +797,PickupScrapSalvage1x1 +798,PickupScrapSalvage2x2 +799,PickupScrapSalvage3x3 +800,RoughTerrain +801,UnbuildableBricksSmallUnit +802,UnbuildablePlatesSmallUnit +803,UnbuildablePlatesUnit +804,UnbuildableRocksSmallUnit +805,XelNagaHealingShrine +806,InvisibleTargetDummy +807,ProtossVespeneGeyser +808,CollapsibleRockTower +809,CollapsibleTerranTower +810,ThornLizard +811,CleaningBot +812,DestructibleRock6x6Weak +813,ProtossSnakeSegmentDemo +814,PhysicsCapsule +815,PhysicsCube +816,PhysicsCylinder +817,PhysicsKnot +818,PhysicsL +819,PhysicsPrimitives +820,PhysicsSphere +821,PhysicsStar +822,CreepBlocker4x4 +823,DestructibleCityDebris2x4Vertical +824,DestructibleCityDebris2x4Horizontal +825,DestructibleCityDebris2x6Vertical +826,DestructibleCityDebris2x6Horizontal +827,DestructibleCityDebris4x4 +828,DestructibleCityDebris6x6 +829,DestructibleCityDebrisHugeDiagonalBLUR +830,DestructibleCityDebrisHugeDiagonalULBR +831,TestZerg +832,PathingBlockerRadius1 +833,DestructibleRockEx12x4Vertical +834,DestructibleRockEx12x4Horizontal +835,DestructibleRockEx12x6Vertical +836,DestructibleRockEx12x6Horizontal +837,DestructibleRockEx14x4 +838,DestructibleRockEx16x6 +839,DestructibleRockEx1DiagonalHugeULBR +840,DestructibleRockEx1DiagonalHugeBLUR +841,DestructibleRockEx1VerticalHuge +842,DestructibleRockEx1HorizontalHuge +843,DestructibleIce2x4Vertical +844,DestructibleIce2x4Horizontal +845,DestructibleIce2x6Vertical +846,DestructibleIce2x6Horizontal +847,DestructibleIce4x4 +848,DestructibleIce6x6 +849,DestructibleIceDiagonalHugeULBR +850,DestructibleIceDiagonalHugeBLUR +851,DestructibleIceVerticalHuge +852,DestructibleIceHorizontalHuge +853,DesertPlanetSearchlight +854,DesertPlanetStreetlight +855,UnbuildableBricksUnit +856,UnbuildableRocksUnit +857,ZerusDestructibleArch +858,Artosilope +859,Anteplott +860,LabBot +861,Crabeetle +862,CollapsibleRockTowerRampRight +863,CollapsibleRockTowerRampLeft +864,LabMineralField +865,LabMineralField750 +880,CollapsibleRockTowerDebrisRampLeftGreen +881,CollapsibleRockTowerDebrisRampRightGreen +882,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +883,SnowRefinery_Terran_ExtendingBridgeNEShort8 +884,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +885,SnowRefinery_Terran_ExtendingBridgeNWShort8 +890,Tarsonis_DoorN +891,Tarsonis_DoorNLowered +892,Tarsonis_DoorNE +893,Tarsonis_DoorNELowered +894,Tarsonis_DoorE +895,Tarsonis_DoorELowered +896,Tarsonis_DoorNW +897,Tarsonis_DoorNWLowered +898,CompoundMansion_DoorN +899,CompoundMansion_DoorNLowered +900,CompoundMansion_DoorNE +901,CompoundMansion_DoorNELowered +902,CompoundMansion_DoorE +903,CompoundMansion_DoorELowered +904,CompoundMansion_DoorNW +905,CompoundMansion_DoorNWLowered +907,LocustMPFlying +908,AiurLightBridgeNE8Out +909,AiurLightBridgeNE8 +910,AiurLightBridgeNE10Out +911,AiurLightBridgeNE10 +912,AiurLightBridgeNE12Out +913,AiurLightBridgeNE12 +914,AiurLightBridgeNW8Out +915,AiurLightBridgeNW8 +916,AiurLightBridgeNW10Out +917,AiurLightBridgeNW10 +918,AiurLightBridgeNW12Out +919,AiurLightBridgeNW12 +920,AiurTempleBridgeNE8Out +922,AiurTempleBridgeNE10Out +924,AiurTempleBridgeNE12Out +926,AiurTempleBridgeNW8Out +928,AiurTempleBridgeNW10Out +930,AiurTempleBridgeNW12Out +932,ShakurasLightBridgeNE8Out +933,ShakurasLightBridgeNE8 +934,ShakurasLightBridgeNE10Out +935,ShakurasLightBridgeNE10 +936,ShakurasLightBridgeNE12Out +937,ShakurasLightBridgeNE12 +938,ShakurasLightBridgeNW8Out +939,ShakurasLightBridgeNW8 +940,ShakurasLightBridgeNW10Out +941,ShakurasLightBridgeNW10 +942,ShakurasLightBridgeNW12Out +943,ShakurasLightBridgeNW12 +944,VoidMPImmortalReviveCorpse +945,GuardianCocoonMP +946,GuardianMP +947,DevourerCocoonMP +948,DevourerMP +949,DefilerMPBurrowed +950,DefilerMP +951,OracleStasisTrap +952,DisruptorPhased +953,AiurLightBridgeAbandonedNE8Out +954,AiurLightBridgeAbandonedNE8 +955,AiurLightBridgeAbandonedNE10Out +956,AiurLightBridgeAbandonedNE10 +957,AiurLightBridgeAbandonedNE12Out +958,AiurLightBridgeAbandonedNE12 +959,AiurLightBridgeAbandonedNW8Out +960,AiurLightBridgeAbandonedNW8 +961,AiurLightBridgeAbandonedNW10Out +962,AiurLightBridgeAbandonedNW10 +963,AiurLightBridgeAbandonedNW12Out +964,AiurLightBridgeAbandonedNW12 +965,CollapsiblePurifierTowerDebris +966,PortCity_Bridge_UnitNE8Out +967,PortCity_Bridge_UnitNE8 +968,PortCity_Bridge_UnitSE8Out +969,PortCity_Bridge_UnitSE8 +970,PortCity_Bridge_UnitNW8Out +971,PortCity_Bridge_UnitNW8 +972,PortCity_Bridge_UnitSW8Out +973,PortCity_Bridge_UnitSW8 +974,PortCity_Bridge_UnitNE10Out +975,PortCity_Bridge_UnitNE10 +976,PortCity_Bridge_UnitSE10Out +977,PortCity_Bridge_UnitSE10 +978,PortCity_Bridge_UnitNW10Out +979,PortCity_Bridge_UnitNW10 +980,PortCity_Bridge_UnitSW10Out +981,PortCity_Bridge_UnitSW10 +982,PortCity_Bridge_UnitNE12Out +983,PortCity_Bridge_UnitNE12 +984,PortCity_Bridge_UnitSE12Out +985,PortCity_Bridge_UnitSE12 +986,PortCity_Bridge_UnitNW12Out +987,PortCity_Bridge_UnitNW12 +988,PortCity_Bridge_UnitSW12Out +989,PortCity_Bridge_UnitSW12 +990,PortCity_Bridge_UnitN8Out +991,PortCity_Bridge_UnitN8 +992,PortCity_Bridge_UnitS8Out +993,PortCity_Bridge_UnitS8 +994,PortCity_Bridge_UnitE8Out +995,PortCity_Bridge_UnitE8 +996,PortCity_Bridge_UnitW8Out +997,PortCity_Bridge_UnitW8 +998,PortCity_Bridge_UnitN10Out +999,PortCity_Bridge_UnitN10 +1000,PortCity_Bridge_UnitS10Out +1001,PortCity_Bridge_UnitS10 +1002,PortCity_Bridge_UnitE10Out +1003,PortCity_Bridge_UnitE10 +1004,PortCity_Bridge_UnitW10Out +1005,PortCity_Bridge_UnitW10 +1006,PortCity_Bridge_UnitN12Out +1007,PortCity_Bridge_UnitN12 +1008,PortCity_Bridge_UnitS12Out +1009,PortCity_Bridge_UnitS12 +1010,PortCity_Bridge_UnitE12Out +1011,PortCity_Bridge_UnitE12 +1012,PortCity_Bridge_UnitW12Out +1013,PortCity_Bridge_UnitW12 +1014,PurifierRichMineralField +1015,PurifierRichMineralField750 +1016,CollapsibleRockTowerPushUnitRampLeftGreen +1017,CollapsibleRockTowerPushUnitRampRightGreen +1032,CollapsiblePurifierTowerPushUnit +1034,LocustMPPrecursor +1035,ReleaseInterceptorsBeacon +1036,AdeptPhaseShift +1037,HydraliskImpaleMissile +1038,CycloneMissileLargeAir +1039,CycloneMissile +1040,CycloneMissileLarge +1041,OracleWeapon +1042,TempestWeaponGround +1043,ScoutMPAirWeaponLeft +1044,ScoutMPAirWeaponRight +1045,ArbiterMPWeaponMissile +1046,GuardianMPWeapon +1047,DevourerMPWeaponMissile +1048,DefilerMPDarkSwarmWeapon +1049,QueenMPEnsnareMissile +1050,QueenMPSpawnBroodlingsMissile +1051,LightningBombWeapon +1052,HERCPlacement +1053,GrappleWeapon +1056,CausticSprayMissile +1057,ParasiticBombMissile +1058,ParasiticBombDummy +1059,AdeptWeapon +1060,AdeptUpgradeWeapon +1061,LiberatorMissile +1062,LiberatorDamageMissile +1063,LiberatorAGMissile +1064,KD8Charge +1065,KD8ChargeWeapon +1067,SlaynElementalGrabWeapon +1068,SlaynElementalGrabAirUnit +1069,SlaynElementalGrabGroundUnit +1070,SlaynElementalWeapon +1075,CollapsibleRockTowerRampLeftGreen +1076,CollapsibleRockTowerRampRightGreen +1077,DestructibleExpeditionGate6x6 +1078,DestructibleZergInfestation3x3 +1079,HERC +1080,Moopy +1081,Replicant +1082,SeekerMissile +1083,AiurTempleBridgeDestructibleNE8Out +1084,AiurTempleBridgeDestructibleNE10Out +1085,AiurTempleBridgeDestructibleNE12Out +1086,AiurTempleBridgeDestructibleNW8Out +1087,AiurTempleBridgeDestructibleNW10Out +1088,AiurTempleBridgeDestructibleNW12Out +1089,AiurTempleBridgeDestructibleSW8Out +1090,AiurTempleBridgeDestructibleSW10Out +1091,AiurTempleBridgeDestructibleSW12Out +1092,AiurTempleBridgeDestructibleSE8Out +1093,AiurTempleBridgeDestructibleSE10Out +1094,AiurTempleBridgeDestructibleSE12Out +1096,FlyoverUnit +1097,CorsairMP +1098,ScoutMP +1100,ArbiterMP +1101,ScourgeMP +1102,DefilerMPPlagueWeapon +1103,QueenMP +1104,XelNagaDestructibleRampBlocker6S +1105,XelNagaDestructibleRampBlocker6SE +1106,XelNagaDestructibleRampBlocker6E +1107,XelNagaDestructibleRampBlocker6NE +1108,XelNagaDestructibleRampBlocker6N +1109,XelNagaDestructibleRampBlocker6NW +1110,XelNagaDestructibleRampBlocker6W +1111,XelNagaDestructibleRampBlocker6SW +1112,XelNagaDestructibleRampBlocker8S +1113,XelNagaDestructibleRampBlocker8SE +1114,XelNagaDestructibleRampBlocker8E +1115,XelNagaDestructibleRampBlocker8NE +1116,XelNagaDestructibleRampBlocker8N +1117,XelNagaDestructibleRampBlocker8NW +1118,XelNagaDestructibleRampBlocker8W +1119,XelNagaDestructibleRampBlocker8SW +1120,XelNagaDestructibleBlocker6S +1121,XelNagaDestructibleBlocker6SE +1122,XelNagaDestructibleBlocker6E +1123,XelNagaDestructibleBlocker6NE +1124,XelNagaDestructibleBlocker6N +1125,XelNagaDestructibleBlocker6NW +1126,XelNagaDestructibleBlocker6W +1127,XelNagaDestructibleBlocker6SW +1128,XelNagaDestructibleBlocker8S +1129,XelNagaDestructibleBlocker8SE +1130,XelNagaDestructibleBlocker8E +1131,XelNagaDestructibleBlocker8NE +1132,XelNagaDestructibleBlocker8N +1133,XelNagaDestructibleBlocker8NW +1134,XelNagaDestructibleBlocker8W +1135,XelNagaDestructibleBlocker8SW +1136,ReptileCrate +1137,SlaynSwarmHostSpawnFlyer +1138,SlaynElemental +1139,PurifierVespeneGeyser +1140,ShakurasVespeneGeyser +1141,CollapsiblePurifierTowerDiagonal +1142,CreepOnlyBlocker4x4 +1143,BattleStationMineralField +1144,BattleStationMineralField750 +1145,PurifierMineralField +1146,PurifierMineralField750 +1147,Beacon_Nova +1148,Beacon_NovaSmall +1149,Ursula +1150,Elsecaro_Colonist_Hut +1151,SnowGlazeStarterMP +1152,PylonOvercharged +1153,ObserverSiegeMode +1154,RavenRepairDrone +1156,ParasiticBombRelayDummy +1157,BypassArmorDrone +1158,AdeptPiercingWeapon +1159,HighTemplarWeaponMissile +1160,CycloneMissileLargeAirAlternative +1161,RavenScramblerMissile +1162,RavenRepairDroneReleaseWeapon +1163,RavenShredderMissileWeapon +1164,InfestedAcidSpinesWeapon +1165,InfestorEnsnareAttackMissile +1166,SNARE_PLACEHOLDER +1169,CorrosiveParasiteWeapon diff --git a/sc2reader/data/LotV/77379_abilities.csv b/sc2reader/data/LotV/77379_abilities.csv new file mode 100644 index 00000000..b06e856c --- /dev/null +++ b/sc2reader/data/LotV/77379_abilities.csv @@ -0,0 +1,411 @@ +39,Taunt +40,stop +42,move +45,attack +60,SprayTerran +61,SprayZerg +62,SprayProtoss +63,SalvageShared +65,GhostHoldFire +66,GhostWeaponsFree +68,Explode +69,FleetBeaconResearch +70,FungalGrowth +71,GuardianShield +72,MULERepair +73,ZerglingTrain +74,NexusTrainMothership +75,Feedback +76,MassRecall +78,HallucinationArchon +79,HallucinationColossus +80,HallucinationHighTemplar +81,HallucinationImmortal +82,HallucinationPhoenix +83,HallucinationProbe +84,HallucinationStalker +85,HallucinationVoidRay +86,HallucinationWarpPrism +87,HallucinationZealot +88,MULEGather +90,CalldownMULE +91,GravitonBeam +95,SpawnChangeling +102,Rally +103,ProgressRally +104,RallyCommand +105,RallyNexus +106,RallyHatchery +107,RoachWarrenResearch +110,NeuralParasite +111,SpawnLarva +112,StimpackMarauder +113,SupplyDrop +117,UltraliskCavernResearch +119,SCVHarvest +120,ProbeHarvest +122,que1 +123,que5 +124,que5CancelToSelection +126,que5Addon +127,BuildInProgress +128,Repair +129,TerranBuild +131,Stimpack +132,GhostCloak +134,MedivacHeal +135,SiegeMode +136,Unsiege +137,BansheeCloak +138,MedivacTransport +139,ScannerSweep +140,Yamato +141,AssaultMode +142,FighterMode +143,BunkerTransport +144,CommandCenterTransport +145,CommandCenterLiftOff +146,CommandCenterLand +147,BarracksFlyingBuild +148,BarracksLiftOff +149,FactoryFlyingBuild +150,FactoryLiftOff +151,StarportFlyingBuild +152,StarportLiftOff +153,FactoryLand +154,StarportLand +155,CommandCenterTrain +156,BarracksLand +157,SupplyDepotLower +158,SupplyDepotRaise +159,BarracksTrain +160,FactoryTrain +161,StarportTrain +162,EngineeringBayResearch +164,GhostAcademyTrain +165,BarracksTechLabResearch +166,FactoryTechLabResearch +167,StarportTechLabResearch +168,GhostAcademyResearch +169,ArmoryResearch +170,ProtossBuild +171,WarpPrismTransport +172,GatewayTrain +173,StargateTrain +174,RoboticsFacilityTrain +175,NexusTrain +176,PsiStorm +177,HangarQueue5 +179,CarrierTrain +180,ForgeResearch +181,RoboticsBayResearch +182,TemplarArchiveResearch +183,ZergBuild +184,DroneHarvest +185,EvolutionChamberResearch +186,UpgradeToLair +187,UpgradeToHive +188,UpgradeToGreaterSpire +189,HiveResearch +190,SpawningPoolResearch +191,HydraliskDenResearch +192,GreaterSpireResearch +193,LarvaTrain +194,MorphToBroodLord +195,BurrowBanelingDown +196,BurrowBanelingUp +197,BurrowDroneDown +198,BurrowDroneUp +199,BurrowHydraliskDown +200,BurrowHydraliskUp +201,BurrowRoachDown +202,BurrowRoachUp +203,BurrowZerglingDown +204,BurrowZerglingUp +205,BurrowInfestorTerranDown +206,BurrowInfestorTerranUp +207,RedstoneLavaCritterBurrow +208,RedstoneLavaCritterInjuredBurrow +209,RedstoneLavaCritterUnburrow +210,RedstoneLavaCritterInjuredUnburrow +211,OverlordTransport +214,WarpGateTrain +215,BurrowQueenDown +216,BurrowQueenUp +217,NydusCanalTransport +218,Blink +219,BurrowInfestorDown +220,BurrowInfestorUp +221,MorphToOverseer +222,UpgradeToPlanetaryFortress +223,InfestationPitResearch +224,BanelingNestResearch +225,BurrowUltraliskDown +226,BurrowUltraliskUp +227,UpgradeToOrbital +228,UpgradeToWarpGate +229,MorphBackToGateway +230,OrbitalLiftOff +231,OrbitalCommandLand +232,ForceField +233,PhasingMode +234,TransportMode +235,FusionCoreResearch +236,CyberneticsCoreResearch +237,TwilightCouncilResearch +238,TacNukeStrike +241,EMP +243,HiveTrain +245,Transfusion +254,AttackRedirect +255,StimpackRedirect +256,StimpackMarauderRedirect +258,StopRedirect +259,GenerateCreep +260,QueenBuild +261,SpineCrawlerUproot +262,SporeCrawlerUproot +263,SpineCrawlerRoot +264,SporeCrawlerRoot +265,CreepTumorBurrowedBuild +266,BuildAutoTurret +267,ArchonWarp +268,NydusNetworkBuild +270,Charge +274,Contaminate +277,que5Passive +278,que5PassiveCancelToSelection +281,RavagerCorrosiveBile +282,ShieldBatteryRechargeChanneled +303,BurrowLurkerMPDown +304,BurrowLurkerMPUp +307,BurrowRavagerDown +308,BurrowRavagerUp +309,MorphToRavager +310,MorphToTransportOverlord +312,ThorNormalMode +317,DigesterCreepSpray +321,MorphToMothership +346,XelNagaHealingShrine +355,MothershipCoreMassRecall +357,MorphToHellion +367,MorphToHellionTank +375,MorphToSwarmHostBurrowedMP +376,MorphToSwarmHostMP +378,attackProtossBuilding +380,stopProtossBuilding +381,BlindingCloud +383,Yoink +386,ViperConsumeStructure +389,TestZerg +390,VolatileBurstBuilding +397,WidowMineBurrow +398,WidowMineUnburrow +399,WidowMineAttack +400,TornadoMissile +403,HallucinationOracle +404,MedivacSpeedBoost +405,ExtendingBridgeNEWide8Out +406,ExtendingBridgeNEWide8 +407,ExtendingBridgeNWWide8Out +408,ExtendingBridgeNWWide8 +409,ExtendingBridgeNEWide10Out +410,ExtendingBridgeNEWide10 +411,ExtendingBridgeNWWide10Out +412,ExtendingBridgeNWWide10 +413,ExtendingBridgeNEWide12Out +414,ExtendingBridgeNEWide12 +415,ExtendingBridgeNWWide12Out +416,ExtendingBridgeNWWide12 +418,CritterFlee +419,OracleRevelation +427,MothershipCorePurifyNexus +428,XelNaga_Caverns_DoorE +429,XelNaga_Caverns_DoorEOpened +430,XelNaga_Caverns_DoorN +431,XelNaga_Caverns_DoorNE +432,XelNaga_Caverns_DoorNEOpened +433,XelNaga_Caverns_DoorNOpened +434,XelNaga_Caverns_DoorNW +435,XelNaga_Caverns_DoorNWOpened +436,XelNaga_Caverns_DoorS +437,XelNaga_Caverns_DoorSE +438,XelNaga_Caverns_DoorSEOpened +439,XelNaga_Caverns_DoorSOpened +440,XelNaga_Caverns_DoorSW +441,XelNaga_Caverns_DoorSWOpened +442,XelNaga_Caverns_DoorW +443,XelNaga_Caverns_DoorWOpened +444,XelNaga_Caverns_Floating_BridgeNE8Out +445,XelNaga_Caverns_Floating_BridgeNE8 +446,XelNaga_Caverns_Floating_BridgeNW8Out +447,XelNaga_Caverns_Floating_BridgeNW8 +448,XelNaga_Caverns_Floating_BridgeNE10Out +449,XelNaga_Caverns_Floating_BridgeNE10 +450,XelNaga_Caverns_Floating_BridgeNW10Out +451,XelNaga_Caverns_Floating_BridgeNW10 +452,XelNaga_Caverns_Floating_BridgeNE12Out +453,XelNaga_Caverns_Floating_BridgeNE12 +454,XelNaga_Caverns_Floating_BridgeNW12Out +455,XelNaga_Caverns_Floating_BridgeNW12 +456,XelNaga_Caverns_Floating_BridgeH8Out +457,XelNaga_Caverns_Floating_BridgeH8 +458,XelNaga_Caverns_Floating_BridgeV8Out +459,XelNaga_Caverns_Floating_BridgeV8 +460,XelNaga_Caverns_Floating_BridgeH10Out +461,XelNaga_Caverns_Floating_BridgeH10 +462,XelNaga_Caverns_Floating_BridgeV10Out +463,XelNaga_Caverns_Floating_BridgeV10 +464,XelNaga_Caverns_Floating_BridgeH12Out +465,XelNaga_Caverns_Floating_BridgeH12 +466,XelNaga_Caverns_Floating_BridgeV12Out +467,XelNaga_Caverns_Floating_BridgeV12 +468,TemporalField +494,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +495,SnowRefinery_Terran_ExtendingBridgeNEShort8 +496,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +497,SnowRefinery_Terran_ExtendingBridgeNWShort8 +519,CausticSpray +522,MorphToLurker +526,PurificationNovaTargeted +528,LockOn +530,LockOnCancel +532,Hyperjump +534,ThorAPMode +537,NydusWormTransport +538,OracleWeapon +544,LocustMPFlyingSwoop +545,HallucinationDisruptor +546,HallucinationAdept +547,VoidRaySwarmDamageBoost +548,SeekerDummyChannel +549,AiurLightBridgeNE8Out +550,AiurLightBridgeNE8 +551,AiurLightBridgeNE10Out +552,AiurLightBridgeNE10 +553,AiurLightBridgeNE12Out +554,AiurLightBridgeNE12 +555,AiurLightBridgeNW8Out +556,AiurLightBridgeNW8 +557,AiurLightBridgeNW10Out +558,AiurLightBridgeNW10 +559,AiurLightBridgeNW12Out +560,AiurLightBridgeNW12 +573,ShakurasLightBridgeNE8Out +574,ShakurasLightBridgeNE8 +575,ShakurasLightBridgeNE10Out +576,ShakurasLightBridgeNE10 +577,ShakurasLightBridgeNE12Out +578,ShakurasLightBridgeNE12 +579,ShakurasLightBridgeNW8Out +580,ShakurasLightBridgeNW8 +581,ShakurasLightBridgeNW10Out +582,ShakurasLightBridgeNW10 +583,ShakurasLightBridgeNW12Out +584,ShakurasLightBridgeNW12 +585,VoidMPImmortalReviveRebuild +587,ArbiterMPStasisField +588,ArbiterMPRecall +589,CorsairMPDisruptionWeb +590,MorphToGuardianMP +591,MorphToDevourerMP +592,DefilerMPConsume +593,DefilerMPDarkSwarm +594,DefilerMPPlague +595,DefilerMPBurrow +596,DefilerMPUnburrow +597,QueenMPEnsnare +598,QueenMPSpawnBroodlings +599,QueenMPInfestCommandCenter +603,OracleBuild +607,ParasiticBomb +608,AdeptPhaseShift +611,LurkerHoldFire +612,LurkerRemoveHoldFire +615,LiberatorAGTarget +616,LiberatorAATarget +618,AiurLightBridgeAbandonedNE8Out +619,AiurLightBridgeAbandonedNE8 +620,AiurLightBridgeAbandonedNE10Out +621,AiurLightBridgeAbandonedNE10 +622,AiurLightBridgeAbandonedNE12Out +623,AiurLightBridgeAbandonedNE12 +624,AiurLightBridgeAbandonedNW8Out +625,AiurLightBridgeAbandonedNW8 +626,AiurLightBridgeAbandonedNW10Out +627,AiurLightBridgeAbandonedNW10 +628,AiurLightBridgeAbandonedNW12Out +629,AiurLightBridgeAbandonedNW12 +630,KD8Charge +633,AdeptPhaseShiftCancel +634,AdeptShadePhaseShiftCancel +635,SlaynElementalGrab +637,PortCity_Bridge_UnitNE8Out +638,PortCity_Bridge_UnitNE8 +639,PortCity_Bridge_UnitSE8Out +640,PortCity_Bridge_UnitSE8 +641,PortCity_Bridge_UnitNW8Out +642,PortCity_Bridge_UnitNW8 +643,PortCity_Bridge_UnitSW8Out +644,PortCity_Bridge_UnitSW8 +645,PortCity_Bridge_UnitNE10Out +646,PortCity_Bridge_UnitNE10 +647,PortCity_Bridge_UnitSE10Out +648,PortCity_Bridge_UnitSE10 +649,PortCity_Bridge_UnitNW10Out +650,PortCity_Bridge_UnitNW10 +651,PortCity_Bridge_UnitSW10Out +652,PortCity_Bridge_UnitSW10 +653,PortCity_Bridge_UnitNE12Out +654,PortCity_Bridge_UnitNE12 +655,PortCity_Bridge_UnitSE12Out +656,PortCity_Bridge_UnitSE12 +657,PortCity_Bridge_UnitNW12Out +658,PortCity_Bridge_UnitNW12 +659,PortCity_Bridge_UnitSW12Out +660,PortCity_Bridge_UnitSW12 +661,PortCity_Bridge_UnitN8Out +662,PortCity_Bridge_UnitN8 +663,PortCity_Bridge_UnitS8Out +664,PortCity_Bridge_UnitS8 +665,PortCity_Bridge_UnitE8Out +666,PortCity_Bridge_UnitE8 +667,PortCity_Bridge_UnitW8Out +668,PortCity_Bridge_UnitW8 +669,PortCity_Bridge_UnitN10Out +670,PortCity_Bridge_UnitN10 +671,PortCity_Bridge_UnitS10Out +672,PortCity_Bridge_UnitS10 +673,PortCity_Bridge_UnitE10Out +674,PortCity_Bridge_UnitE10 +675,PortCity_Bridge_UnitW10Out +676,PortCity_Bridge_UnitW10 +677,PortCity_Bridge_UnitN12Out +678,PortCity_Bridge_UnitN12 +679,PortCity_Bridge_UnitS12Out +680,PortCity_Bridge_UnitS12 +681,PortCity_Bridge_UnitE12Out +682,PortCity_Bridge_UnitE12 +683,PortCity_Bridge_UnitW12Out +684,PortCity_Bridge_UnitW12 +687,DarkTemplarBlink +690,BattlecruiserAttack +692,BattlecruiserMove +694,BattlecruiserStop +696,AmorphousArmorcloud +697,SpawnLocustsTargeted +698,ViperParasiticBombRelay +699,ParasiticBombRelayDodge +700,VoidRaySwarmDamageBoostCancel +704,ChannelSnipe +707,DarkShrineResearch +708,LurkerDenMPResearch +709,ObserverSiegeMorphtoObserver +710,ObserverMorphtoObserverSiege +711,OverseerMorphtoOverseerSiegeMode +712,OverseerSiegeModeMorphtoOverseer +713,RavenScramblerMissile +715,RavenRepairDroneHeal +716,RavenShredderMissile +717,ChronoBoostEnergyCost +718,NexusMassRecall diff --git a/sc2reader/data/LotV/77379_units.csv b/sc2reader/data/LotV/77379_units.csv new file mode 100644 index 00000000..51c81189 --- /dev/null +++ b/sc2reader/data/LotV/77379_units.csv @@ -0,0 +1,1038 @@ +3,System_Snapshot_Dummy +21,Ball +22,StereoscopicOptionsUnit +23,Colossus +24,TechLab +25,Reactor +27,InfestorTerran +28,BanelingCocoon +29,Baneling +30,Mothership +31,PointDefenseDrone +32,Changeling +33,ChangelingZealot +34,ChangelingMarineShield +35,ChangelingMarine +36,ChangelingZerglingWings +37,ChangelingZergling +39,CommandCenter +40,SupplyDepot +41,Refinery +42,Barracks +43,EngineeringBay +44,MissileTurret +45,Bunker +46,RefineryRich +47,SensorTower +48,GhostAcademy +49,Factory +50,Starport +52,Armory +53,FusionCore +54,AutoTurret +55,SiegeTankSieged +56,SiegeTank +57,VikingAssault +58,VikingFighter +59,CommandCenterFlying +60,BarracksTechLab +61,BarracksReactor +62,FactoryTechLab +63,FactoryReactor +64,StarportTechLab +65,StarportReactor +66,FactoryFlying +67,StarportFlying +68,SCV +69,BarracksFlying +70,SupplyDepotLowered +71,Marine +72,Reaper +73,Ghost +74,Marauder +75,Thor +76,Hellion +77,Medivac +78,Banshee +79,Raven +80,Battlecruiser +81,Nuke +82,Nexus +83,Pylon +84,Assimilator +85,Gateway +86,Forge +87,FleetBeacon +88,TwilightCouncil +89,PhotonCannon +90,Stargate +91,TemplarArchive +92,DarkShrine +93,RoboticsBay +94,RoboticsFacility +95,CyberneticsCore +96,Zealot +97,Stalker +98,HighTemplar +99,DarkTemplar +100,Sentry +101,Phoenix +102,Carrier +103,VoidRay +104,WarpPrism +105,Observer +106,Immortal +107,Probe +108,Interceptor +109,Hatchery +110,CreepTumor +111,Extractor +112,SpawningPool +113,EvolutionChamber +114,HydraliskDen +115,Spire +116,UltraliskCavern +117,InfestationPit +118,NydusNetwork +119,BanelingNest +120,RoachWarren +121,SpineCrawler +122,SporeCrawler +123,Lair +124,Hive +125,GreaterSpire +126,Egg +127,Drone +128,Zergling +129,Overlord +130,Hydralisk +131,Mutalisk +132,Ultralisk +133,Roach +134,Infestor +135,Corruptor +136,BroodLordCocoon +137,BroodLord +138,BanelingBurrowed +139,DroneBurrowed +140,HydraliskBurrowed +141,RoachBurrowed +142,ZerglingBurrowed +143,InfestorTerranBurrowed +144,RedstoneLavaCritterBurrowed +145,RedstoneLavaCritterInjuredBurrowed +146,RedstoneLavaCritter +147,RedstoneLavaCritterInjured +148,QueenBurrowed +149,Queen +150,InfestorBurrowed +151,OverlordCocoon +152,Overseer +153,PlanetaryFortress +154,UltraliskBurrowed +155,OrbitalCommand +156,WarpGate +157,OrbitalCommandFlying +158,ForceField +159,WarpPrismPhasing +160,CreepTumorBurrowed +161,CreepTumorQueen +162,SpineCrawlerUprooted +163,SporeCrawlerUprooted +164,Archon +165,NydusCanal +166,BroodlingEscort +167,GhostAlternate +168,GhostNova +169,RichMineralField +170,RichMineralField750 +171,Ursadon +173,LurkerMPBurrowed +174,LurkerMP +175,LurkerDenMP +176,LurkerMPEgg +177,NydusCanalAttacker +178,OverlordTransport +179,Ravager +180,RavagerBurrowed +181,RavagerCocoon +182,TransportOverlordCocoon +183,XelNagaTower +185,Oracle +186,Tempest +188,InfestedTerransEgg +189,Larva +190,OverseerSiegeMode +192,ReaperPlaceholder +193,MarineACGluescreenDummy +194,FirebatACGluescreenDummy +195,MedicACGluescreenDummy +196,MarauderACGluescreenDummy +197,VultureACGluescreenDummy +198,SiegeTankACGluescreenDummy +199,VikingACGluescreenDummy +200,BansheeACGluescreenDummy +201,BattlecruiserACGluescreenDummy +202,OrbitalCommandACGluescreenDummy +203,BunkerACGluescreenDummy +204,BunkerUpgradedACGluescreenDummy +205,MissileTurretACGluescreenDummy +206,HellbatACGluescreenDummy +207,GoliathACGluescreenDummy +208,CycloneACGluescreenDummy +209,WraithACGluescreenDummy +210,ScienceVesselACGluescreenDummy +211,HerculesACGluescreenDummy +212,ThorACGluescreenDummy +213,PerditionTurretACGluescreenDummy +214,FlamingBettyACGluescreenDummy +215,DevastationTurretACGluescreenDummy +216,BlasterBillyACGluescreenDummy +217,SpinningDizzyACGluescreenDummy +218,ZerglingKerriganACGluescreenDummy +219,RaptorACGluescreenDummy +220,QueenCoopACGluescreenDummy +221,HydraliskACGluescreenDummy +222,HydraliskLurkerACGluescreenDummy +223,MutaliskBroodlordACGluescreenDummy +224,BroodLordACGluescreenDummy +225,UltraliskACGluescreenDummy +226,TorrasqueACGluescreenDummy +227,OverseerACGluescreenDummy +228,LurkerACGluescreenDummy +229,SpineCrawlerACGluescreenDummy +230,SporeCrawlerACGluescreenDummy +231,NydusNetworkACGluescreenDummy +232,OmegaNetworkACGluescreenDummy +233,ZerglingZagaraACGluescreenDummy +234,SwarmlingACGluescreenDummy +235,QueenZagaraACGluescreenDummy +236,BanelingACGluescreenDummy +237,SplitterlingACGluescreenDummy +238,AberrationACGluescreenDummy +239,ScourgeACGluescreenDummy +240,CorruptorACGluescreenDummy +241,OverseerZagaraACGluescreenDummy +242,BileLauncherACGluescreenDummy +243,SwarmQueenACGluescreenDummy +244,RoachACGluescreenDummy +245,RoachVileACGluescreenDummy +246,RavagerACGluescreenDummy +247,SwarmHostACGluescreenDummy +248,MutaliskACGluescreenDummy +249,GuardianACGluescreenDummy +250,DevourerACGluescreenDummy +251,ViperACGluescreenDummy +252,BrutaliskACGluescreenDummy +253,LeviathanACGluescreenDummy +254,ZealotACGluescreenDummy +255,ZealotAiurACGluescreenDummy +256,DragoonACGluescreenDummy +257,HighTemplarACGluescreenDummy +258,ArchonACGluescreenDummy +259,ImmortalACGluescreenDummy +260,ObserverACGluescreenDummy +261,PhoenixAiurACGluescreenDummy +262,ReaverACGluescreenDummy +263,TempestACGluescreenDummy +264,PhotonCannonACGluescreenDummy +265,ZealotVorazunACGluescreenDummy +266,ZealotShakurasACGluescreenDummy +267,StalkerShakurasACGluescreenDummy +268,DarkTemplarShakurasACGluescreenDummy +269,CorsairACGluescreenDummy +270,VoidRayACGluescreenDummy +271,VoidRayShakurasACGluescreenDummy +272,OracleACGluescreenDummy +273,DarkArchonACGluescreenDummy +274,DarkPylonACGluescreenDummy +275,ZealotPurifierACGluescreenDummy +276,SentryPurifierACGluescreenDummy +277,ImmortalKaraxACGluescreenDummy +278,ColossusACGluescreenDummy +279,ColossusPurifierACGluescreenDummy +280,PhoenixPurifierACGluescreenDummy +281,CarrierACGluescreenDummy +282,CarrierAiurACGluescreenDummy +283,KhaydarinMonolithACGluescreenDummy +284,ShieldBatteryACGluescreenDummy +285,EliteMarineACGluescreenDummy +286,MarauderCommandoACGluescreenDummy +287,SpecOpsGhostACGluescreenDummy +288,HellbatRangerACGluescreenDummy +289,StrikeGoliathACGluescreenDummy +290,HeavySiegeTankACGluescreenDummy +291,RaidLiberatorACGluescreenDummy +292,RavenTypeIIACGluescreenDummy +293,CovertBansheeACGluescreenDummy +294,RailgunTurretACGluescreenDummy +295,BlackOpsMissileTurretACGluescreenDummy +296,SupplicantACGluescreenDummy +297,StalkerTaldarimACGluescreenDummy +298,SentryTaldarimACGluescreenDummy +299,HighTemplarTaldarimACGluescreenDummy +300,ImmortalTaldarimACGluescreenDummy +301,ColossusTaldarimACGluescreenDummy +302,WarpPrismTaldarimACGluescreenDummy +303,PhotonCannonTaldarimACGluescreenDummy +304,StukovInfestedCivilianACGluescreenDummy +305,StukovInfestedMarineACGluescreenDummy +306,StukovInfestedSiegeTankACGluescreenDummy +307,StukovInfestedDiamondbackACGluescreenDummy +308,StukovInfestedBansheeACGluescreenDummy +309,SILiberatorACGluescreenDummy +310,StukovInfestedBunkerACGluescreenDummy +311,StukovInfestedMissileTurretACGluescreenDummy +312,StukovBroodQueenACGluescreenDummy +313,ZealotFenixACGluescreenDummy +314,SentryFenixACGluescreenDummy +315,AdeptFenixACGluescreenDummy +316,ImmortalFenixACGluescreenDummy +317,ColossusFenixACGluescreenDummy +318,DisruptorACGluescreenDummy +319,ObserverFenixACGluescreenDummy +320,ScoutACGluescreenDummy +321,CarrierFenixACGluescreenDummy +322,PhotonCannonFenixACGluescreenDummy +323,PrimalZerglingACGluescreenDummy +324,RavasaurACGluescreenDummy +325,PrimalRoachACGluescreenDummy +326,FireRoachACGluescreenDummy +327,PrimalGuardianACGluescreenDummy +328,PrimalHydraliskACGluescreenDummy +329,PrimalMutaliskACGluescreenDummy +330,PrimalImpalerACGluescreenDummy +331,PrimalSwarmHostACGluescreenDummy +332,CreeperHostACGluescreenDummy +333,PrimalUltraliskACGluescreenDummy +334,TyrannozorACGluescreenDummy +335,PrimalWurmACGluescreenDummy +336,HHReaperACGluescreenDummy +337,HHWidowMineACGluescreenDummy +338,HHHellionTankACGluescreenDummy +339,HHWraithACGluescreenDummy +340,HHVikingACGluescreenDummy +341,HHBattlecruiserACGluescreenDummy +342,HHRavenACGluescreenDummy +343,HHBomberPlatformACGluescreenDummy +344,HHMercStarportACGluescreenDummy +345,HHMissileTurretACGluescreenDummy +346,TychusReaperACGluescreenDummy +347,TychusFirebatACGluescreenDummy +348,TychusSpectreACGluescreenDummy +349,TychusMedicACGluescreenDummy +350,TychusMarauderACGluescreenDummy +351,TychusWarhoundACGluescreenDummy +352,TychusHERCACGluescreenDummy +353,TychusGhostACGluescreenDummy +354,TychusSCVAutoTurretACGluescreenDummy +355,ZeratulStalkerACGluescreenDummy +356,ZeratulSentryACGluescreenDummy +357,ZeratulDarkTemplarACGluescreenDummy +358,ZeratulImmortalACGluescreenDummy +359,ZeratulObserverACGluescreenDummy +360,ZeratulDisruptorACGluescreenDummy +361,ZeratulWarpPrismACGluescreenDummy +362,ZeratulPhotonCannonACGluescreenDummy +363,MechaZerglingACGluescreenDummy +364,MechaBanelingACGluescreenDummy +365,MechaHydraliskACGluescreenDummy +366,MechaInfestorACGluescreenDummy +367,MechaCorruptorACGluescreenDummy +368,MechaUltraliskACGluescreenDummy +369,MechaOverseerACGluescreenDummy +370,MechaLurkerACGluescreenDummy +371,MechaBattlecarrierLordACGluescreenDummy +372,MechaSpineCrawlerACGluescreenDummy +373,MechaSporeCrawlerACGluescreenDummy +374,TrooperMengskACGluescreenDummy +375,MedivacMengskACGluescreenDummy +376,BlimpMengskACGluescreenDummy +377,MarauderMengskACGluescreenDummy +378,GhostMengskACGluescreenDummy +379,SiegeTankMengskACGluescreenDummy +380,ThorMengskACGluescreenDummy +381,VikingMengskACGluescreenDummy +382,BattlecruiserMengskACGluescreenDummy +383,BunkerDepotMengskACGluescreenDummy +384,MissileTurretMengskACGluescreenDummy +385,ArtilleryMengskACGluescreenDummy +387,RenegadeLongboltMissileWeapon +388,NeedleSpinesWeapon +389,CorruptionWeapon +390,InfestedTerransWeapon +391,NeuralParasiteWeapon +392,PointDefenseDroneReleaseWeapon +393,HunterSeekerWeapon +394,MULE +396,ThorAAWeapon +397,PunisherGrenadesLMWeapon +398,VikingFighterWeapon +399,ATALaserBatteryLMWeapon +400,ATSLaserBatteryLMWeapon +401,LongboltMissileWeapon +402,D8ChargeWeapon +403,YamatoWeapon +404,IonCannonsWeapon +405,AcidSalivaWeapon +406,SpineCrawlerWeapon +407,SporeCrawlerWeapon +408,GlaiveWurmWeapon +409,GlaiveWurmM2Weapon +410,GlaiveWurmM3Weapon +411,StalkerWeapon +412,EMP2Weapon +413,BacklashRocketsLMWeapon +414,PhotonCannonWeapon +415,ParasiteSporeWeapon +417,Broodling +418,BroodLordBWeapon +421,AutoTurretReleaseWeapon +422,LarvaReleaseMissile +423,AcidSpinesWeapon +424,FrenzyWeapon +425,ContaminateWeapon +437,BeaconArmy +438,BeaconDefend +439,BeaconAttack +440,BeaconHarass +441,BeaconIdle +442,BeaconAuto +443,BeaconDetect +444,BeaconScout +445,BeaconClaim +446,BeaconExpand +447,BeaconRally +448,BeaconCustom1 +449,BeaconCustom2 +450,BeaconCustom3 +451,BeaconCustom4 +456,LiberatorAG +458,PreviewBunkerUpgraded +459,HellionTank +460,Cyclone +461,WidowMine +462,Liberator +464,Adept +465,Disruptor +466,SwarmHostMP +467,Viper +468,ShieldBattery +469,HighTemplarSkinPreview +470,MothershipCore +471,Viking +481,InhibitorZoneSmall +482,InhibitorZoneMedium +483,InhibitorZoneLarge +484,AccelerationZoneSmall +485,AccelerationZoneMedium +486,AccelerationZoneLarge +487,AssimilatorRich +488,RichVespeneGeyser +489,ExtractorRich +490,RavagerCorrosiveBileMissile +491,RavagerWeaponMissile +492,RenegadeMissileTurret +493,Rocks2x2NonConjoined +494,FungalGrowthMissile +495,NeuralParasiteTentacleMissile +496,Beacon_Protoss +497,Beacon_ProtossSmall +498,Beacon_Terran +499,Beacon_TerranSmall +500,Beacon_Zerg +501,Beacon_ZergSmall +502,Lyote +503,CarrionBird +504,KarakMale +505,KarakFemale +506,UrsadakFemaleExotic +507,UrsadakMale +508,UrsadakFemale +509,UrsadakCalf +510,UrsadakMaleExotic +511,UtilityBot +512,CommentatorBot1 +513,CommentatorBot2 +514,CommentatorBot3 +515,CommentatorBot4 +516,Scantipede +517,Dog +518,Sheep +519,Cow +520,InfestedTerransEggPlacement +521,InfestorTerransWeapon +522,MineralField +523,MineralField450 +524,MineralField750 +525,MineralFieldOpaque +526,MineralFieldOpaque900 +527,VespeneGeyser +528,SpacePlatformGeyser +529,DestructibleSearchlight +530,DestructibleBullhornLights +531,DestructibleStreetlight +532,DestructibleSpacePlatformSign +533,DestructibleStoreFrontCityProps +534,DestructibleBillboardTall +535,DestructibleBillboardScrollingText +536,DestructibleSpacePlatformBarrier +537,DestructibleSignsDirectional +538,DestructibleSignsConstruction +539,DestructibleSignsFunny +540,DestructibleSignsIcons +541,DestructibleSignsWarning +542,DestructibleGarage +543,DestructibleGarageLarge +544,DestructibleTrafficSignal +545,TrafficSignal +546,BraxisAlphaDestructible1x1 +547,BraxisAlphaDestructible2x2 +548,DestructibleDebris4x4 +549,DestructibleDebris6x6 +550,DestructibleRock2x4Vertical +551,DestructibleRock2x4Horizontal +552,DestructibleRock2x6Vertical +553,DestructibleRock2x6Horizontal +554,DestructibleRock4x4 +555,DestructibleRock6x6 +556,DestructibleRampDiagonalHugeULBR +557,DestructibleRampDiagonalHugeBLUR +558,DestructibleRampVerticalHuge +559,DestructibleRampHorizontalHuge +560,DestructibleDebrisRampDiagonalHugeULBR +561,DestructibleDebrisRampDiagonalHugeBLUR +562,WarpPrismSkinPreview +563,SiegeTankSkinPreview +564,ThorAP +565,ThorAALance +566,LiberatorSkinPreview +567,OverlordGenerateCreepKeybind +568,MengskStatueAlone +569,MengskStatue +570,WolfStatue +571,GlobeStatue +572,Weapon +573,GlaiveWurmBounceWeapon +574,BroodLordWeapon +575,BroodLordAWeapon +576,CreepBlocker1x1 +577,PermanentCreepBlocker1x1 +578,PathingBlocker1x1 +579,PathingBlocker2x2 +580,AutoTestAttackTargetGround +581,AutoTestAttackTargetAir +582,AutoTestAttacker +583,HelperEmitterSelectionArrow +584,MultiKillObject +585,ShapeGolfball +586,ShapeCone +587,ShapeCube +588,ShapeCylinder +589,ShapeDodecahedron +590,ShapeIcosahedron +591,ShapeOctahedron +592,ShapePyramid +593,ShapeRoundedCube +594,ShapeSphere +595,ShapeTetrahedron +596,ShapeThickTorus +597,ShapeThinTorus +598,ShapeTorus +599,Shape4PointStar +600,Shape5PointStar +601,Shape6PointStar +602,Shape8PointStar +603,ShapeArrowPointer +604,ShapeBowl +605,ShapeBox +606,ShapeCapsule +607,ShapeCrescentMoon +608,ShapeDecahedron +609,ShapeDiamond +610,ShapeFootball +611,ShapeGemstone +612,ShapeHeart +613,ShapeJack +614,ShapePlusSign +615,ShapeShamrock +616,ShapeSpade +617,ShapeTube +618,ShapeEgg +619,ShapeYenSign +620,ShapeX +621,ShapeWatermelon +622,ShapeWonSign +623,ShapeTennisball +624,ShapeStrawberry +625,ShapeSmileyFace +626,ShapeSoccerball +627,ShapeRainbow +628,ShapeSadFace +629,ShapePoundSign +630,ShapePear +631,ShapePineapple +632,ShapeOrange +633,ShapePeanut +634,ShapeO +635,ShapeLemon +636,ShapeMoneyBag +637,ShapeHorseshoe +638,ShapeHockeyStick +639,ShapeHockeyPuck +640,ShapeHand +641,ShapeGolfClub +642,ShapeGrape +643,ShapeEuroSign +644,ShapeDollarSign +645,ShapeBasketball +646,ShapeCarrot +647,ShapeCherry +648,ShapeBaseball +649,ShapeBaseballBat +650,ShapeBanana +651,ShapeApple +652,ShapeCashLarge +653,ShapeCashMedium +654,ShapeCashSmall +655,ShapeFootballColored +656,ShapeLemonSmall +657,ShapeOrangeSmall +658,ShapeTreasureChestOpen +659,ShapeTreasureChestClosed +660,ShapeWatermelonSmall +661,UnbuildableRocksDestructible +662,UnbuildableBricksDestructible +663,UnbuildablePlatesDestructible +664,Debris2x2NonConjoined +665,EnemyPathingBlocker1x1 +666,EnemyPathingBlocker2x2 +667,EnemyPathingBlocker4x4 +668,EnemyPathingBlocker8x8 +669,EnemyPathingBlocker16x16 +670,ScopeTest +671,SentryACGluescreenDummy +672,StukovInfestedTrooperACGluescreenDummy +688,CollapsibleTerranTowerDebris +689,DebrisRampLeft +690,DebrisRampRight +694,LocustMP +695,CollapsibleRockTowerDebris +696,NydusCanalCreeper +697,SwarmHostBurrowedMP +698,WarHound +699,WidowMineBurrowed +700,ExtendingBridgeNEWide8Out +701,ExtendingBridgeNEWide8 +702,ExtendingBridgeNWWide8Out +703,ExtendingBridgeNWWide8 +704,ExtendingBridgeNEWide10Out +705,ExtendingBridgeNEWide10 +706,ExtendingBridgeNWWide10Out +707,ExtendingBridgeNWWide10 +708,ExtendingBridgeNEWide12Out +709,ExtendingBridgeNEWide12 +710,ExtendingBridgeNWWide12Out +711,ExtendingBridgeNWWide12 +713,CollapsibleRockTowerDebrisRampRight +714,CollapsibleRockTowerDebrisRampLeft +715,XelNaga_Caverns_DoorE +716,XelNaga_Caverns_DoorEOpened +717,XelNaga_Caverns_DoorN +718,XelNaga_Caverns_DoorNE +719,XelNaga_Caverns_DoorNEOpened +720,XelNaga_Caverns_DoorNOpened +721,XelNaga_Caverns_DoorNW +722,XelNaga_Caverns_DoorNWOpened +723,XelNaga_Caverns_DoorS +724,XelNaga_Caverns_DoorSE +725,XelNaga_Caverns_DoorSEOpened +726,XelNaga_Caverns_DoorSOpened +727,XelNaga_Caverns_DoorSW +728,XelNaga_Caverns_DoorSWOpened +729,XelNaga_Caverns_DoorW +730,XelNaga_Caverns_DoorWOpened +731,XelNaga_Caverns_Floating_BridgeNE8Out +732,XelNaga_Caverns_Floating_BridgeNE8 +733,XelNaga_Caverns_Floating_BridgeNW8Out +734,XelNaga_Caverns_Floating_BridgeNW8 +735,XelNaga_Caverns_Floating_BridgeNE10Out +736,XelNaga_Caverns_Floating_BridgeNE10 +737,XelNaga_Caverns_Floating_BridgeNW10Out +738,XelNaga_Caverns_Floating_BridgeNW10 +739,XelNaga_Caverns_Floating_BridgeNE12Out +740,XelNaga_Caverns_Floating_BridgeNE12 +741,XelNaga_Caverns_Floating_BridgeNW12Out +742,XelNaga_Caverns_Floating_BridgeNW12 +743,XelNaga_Caverns_Floating_BridgeH8Out +744,XelNaga_Caverns_Floating_BridgeH8 +745,XelNaga_Caverns_Floating_BridgeV8Out +746,XelNaga_Caverns_Floating_BridgeV8 +747,XelNaga_Caverns_Floating_BridgeH10Out +748,XelNaga_Caverns_Floating_BridgeH10 +749,XelNaga_Caverns_Floating_BridgeV10Out +750,XelNaga_Caverns_Floating_BridgeV10 +751,XelNaga_Caverns_Floating_BridgeH12Out +752,XelNaga_Caverns_Floating_BridgeH12 +753,XelNaga_Caverns_Floating_BridgeV12Out +754,XelNaga_Caverns_Floating_BridgeV12 +757,CollapsibleTerranTowerPushUnitRampLeft +758,CollapsibleTerranTowerPushUnitRampRight +761,CollapsibleRockTowerPushUnit +762,CollapsibleTerranTowerPushUnit +763,CollapsibleRockTowerPushUnitRampRight +764,CollapsibleRockTowerPushUnitRampLeft +765,DigesterCreepSprayTargetUnit +766,DigesterCreepSprayUnit +767,NydusCanalAttackerWeapon +768,ViperConsumeStructureWeapon +771,ResourceBlocker +772,TempestWeapon +773,YoinkMissile +777,YoinkVikingAirMissile +779,YoinkVikingGroundMissile +781,YoinkSiegeTankMissile +783,WarHoundWeapon +785,EyeStalkWeapon +788,WidowMineWeapon +789,WidowMineAirWeapon +790,MothershipCoreWeaponWeapon +791,TornadoMissileWeapon +792,TornadoMissileDummyWeapon +793,TalonsMissileWeapon +794,CreepTumorMissile +795,LocustMPEggAMissileWeapon +796,LocustMPEggBMissileWeapon +797,LocustMPWeapon +799,RepulsorCannonWeapon +803,CollapsibleRockTowerDiagonal +804,CollapsibleTerranTowerDiagonal +805,CollapsibleTerranTowerRampLeft +806,CollapsibleTerranTowerRampRight +807,Ice2x2NonConjoined +808,IceProtossCrates +809,ProtossCrates +810,TowerMine +811,PickupPalletGas +812,PickupPalletMinerals +813,PickupScrapSalvage1x1 +814,PickupScrapSalvage2x2 +815,PickupScrapSalvage3x3 +816,RoughTerrain +817,UnbuildableBricksSmallUnit +818,UnbuildablePlatesSmallUnit +819,UnbuildablePlatesUnit +820,UnbuildableRocksSmallUnit +821,XelNagaHealingShrine +822,InvisibleTargetDummy +823,ProtossVespeneGeyser +824,CollapsibleRockTower +825,CollapsibleTerranTower +826,ThornLizard +827,CleaningBot +828,DestructibleRock6x6Weak +829,ProtossSnakeSegmentDemo +830,PhysicsCapsule +831,PhysicsCube +832,PhysicsCylinder +833,PhysicsKnot +834,PhysicsL +835,PhysicsPrimitives +836,PhysicsSphere +837,PhysicsStar +838,CreepBlocker4x4 +839,DestructibleCityDebris2x4Vertical +840,DestructibleCityDebris2x4Horizontal +841,DestructibleCityDebris2x6Vertical +842,DestructibleCityDebris2x6Horizontal +843,DestructibleCityDebris4x4 +844,DestructibleCityDebris6x6 +845,DestructibleCityDebrisHugeDiagonalBLUR +846,DestructibleCityDebrisHugeDiagonalULBR +847,TestZerg +848,PathingBlockerRadius1 +849,DestructibleRockEx12x4Vertical +850,DestructibleRockEx12x4Horizontal +851,DestructibleRockEx12x6Vertical +852,DestructibleRockEx12x6Horizontal +853,DestructibleRockEx14x4 +854,DestructibleRockEx16x6 +855,DestructibleRockEx1DiagonalHugeULBR +856,DestructibleRockEx1DiagonalHugeBLUR +857,DestructibleRockEx1VerticalHuge +858,DestructibleRockEx1HorizontalHuge +859,DestructibleIce2x4Vertical +860,DestructibleIce2x4Horizontal +861,DestructibleIce2x6Vertical +862,DestructibleIce2x6Horizontal +863,DestructibleIce4x4 +864,DestructibleIce6x6 +865,DestructibleIceDiagonalHugeULBR +866,DestructibleIceDiagonalHugeBLUR +867,DestructibleIceVerticalHuge +868,DestructibleIceHorizontalHuge +869,DesertPlanetSearchlight +870,DesertPlanetStreetlight +871,UnbuildableBricksUnit +872,UnbuildableRocksUnit +873,ZerusDestructibleArch +874,Artosilope +875,Anteplott +876,LabBot +877,Crabeetle +878,CollapsibleRockTowerRampRight +879,CollapsibleRockTowerRampLeft +880,LabMineralField +881,LabMineralField750 +896,CollapsibleRockTowerDebrisRampLeftGreen +897,CollapsibleRockTowerDebrisRampRightGreen +898,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +899,SnowRefinery_Terran_ExtendingBridgeNEShort8 +900,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +901,SnowRefinery_Terran_ExtendingBridgeNWShort8 +906,Tarsonis_DoorN +907,Tarsonis_DoorNLowered +908,Tarsonis_DoorNE +909,Tarsonis_DoorNELowered +910,Tarsonis_DoorE +911,Tarsonis_DoorELowered +912,Tarsonis_DoorNW +913,Tarsonis_DoorNWLowered +914,CompoundMansion_DoorN +915,CompoundMansion_DoorNLowered +916,CompoundMansion_DoorNE +917,CompoundMansion_DoorNELowered +918,CompoundMansion_DoorE +919,CompoundMansion_DoorELowered +920,CompoundMansion_DoorNW +921,CompoundMansion_DoorNWLowered +923,LocustMPFlying +924,AiurLightBridgeNE8Out +925,AiurLightBridgeNE8 +926,AiurLightBridgeNE10Out +927,AiurLightBridgeNE10 +928,AiurLightBridgeNE12Out +929,AiurLightBridgeNE12 +930,AiurLightBridgeNW8Out +931,AiurLightBridgeNW8 +932,AiurLightBridgeNW10Out +933,AiurLightBridgeNW10 +934,AiurLightBridgeNW12Out +935,AiurLightBridgeNW12 +936,AiurTempleBridgeNE8Out +938,AiurTempleBridgeNE10Out +940,AiurTempleBridgeNE12Out +942,AiurTempleBridgeNW8Out +944,AiurTempleBridgeNW10Out +946,AiurTempleBridgeNW12Out +948,ShakurasLightBridgeNE8Out +949,ShakurasLightBridgeNE8 +950,ShakurasLightBridgeNE10Out +951,ShakurasLightBridgeNE10 +952,ShakurasLightBridgeNE12Out +953,ShakurasLightBridgeNE12 +954,ShakurasLightBridgeNW8Out +955,ShakurasLightBridgeNW8 +956,ShakurasLightBridgeNW10Out +957,ShakurasLightBridgeNW10 +958,ShakurasLightBridgeNW12Out +959,ShakurasLightBridgeNW12 +960,VoidMPImmortalReviveCorpse +961,GuardianCocoonMP +962,GuardianMP +963,DevourerCocoonMP +964,DevourerMP +965,DefilerMPBurrowed +966,DefilerMP +967,OracleStasisTrap +968,DisruptorPhased +969,AiurLightBridgeAbandonedNE8Out +970,AiurLightBridgeAbandonedNE8 +971,AiurLightBridgeAbandonedNE10Out +972,AiurLightBridgeAbandonedNE10 +973,AiurLightBridgeAbandonedNE12Out +974,AiurLightBridgeAbandonedNE12 +975,AiurLightBridgeAbandonedNW8Out +976,AiurLightBridgeAbandonedNW8 +977,AiurLightBridgeAbandonedNW10Out +978,AiurLightBridgeAbandonedNW10 +979,AiurLightBridgeAbandonedNW12Out +980,AiurLightBridgeAbandonedNW12 +981,CollapsiblePurifierTowerDebris +982,PortCity_Bridge_UnitNE8Out +983,PortCity_Bridge_UnitNE8 +984,PortCity_Bridge_UnitSE8Out +985,PortCity_Bridge_UnitSE8 +986,PortCity_Bridge_UnitNW8Out +987,PortCity_Bridge_UnitNW8 +988,PortCity_Bridge_UnitSW8Out +989,PortCity_Bridge_UnitSW8 +990,PortCity_Bridge_UnitNE10Out +991,PortCity_Bridge_UnitNE10 +992,PortCity_Bridge_UnitSE10Out +993,PortCity_Bridge_UnitSE10 +994,PortCity_Bridge_UnitNW10Out +995,PortCity_Bridge_UnitNW10 +996,PortCity_Bridge_UnitSW10Out +997,PortCity_Bridge_UnitSW10 +998,PortCity_Bridge_UnitNE12Out +999,PortCity_Bridge_UnitNE12 +1000,PortCity_Bridge_UnitSE12Out +1001,PortCity_Bridge_UnitSE12 +1002,PortCity_Bridge_UnitNW12Out +1003,PortCity_Bridge_UnitNW12 +1004,PortCity_Bridge_UnitSW12Out +1005,PortCity_Bridge_UnitSW12 +1006,PortCity_Bridge_UnitN8Out +1007,PortCity_Bridge_UnitN8 +1008,PortCity_Bridge_UnitS8Out +1009,PortCity_Bridge_UnitS8 +1010,PortCity_Bridge_UnitE8Out +1011,PortCity_Bridge_UnitE8 +1012,PortCity_Bridge_UnitW8Out +1013,PortCity_Bridge_UnitW8 +1014,PortCity_Bridge_UnitN10Out +1015,PortCity_Bridge_UnitN10 +1016,PortCity_Bridge_UnitS10Out +1017,PortCity_Bridge_UnitS10 +1018,PortCity_Bridge_UnitE10Out +1019,PortCity_Bridge_UnitE10 +1020,PortCity_Bridge_UnitW10Out +1021,PortCity_Bridge_UnitW10 +1022,PortCity_Bridge_UnitN12Out +1023,PortCity_Bridge_UnitN12 +1024,PortCity_Bridge_UnitS12Out +1025,PortCity_Bridge_UnitS12 +1026,PortCity_Bridge_UnitE12Out +1027,PortCity_Bridge_UnitE12 +1028,PortCity_Bridge_UnitW12Out +1029,PortCity_Bridge_UnitW12 +1030,PurifierRichMineralField +1031,PurifierRichMineralField750 +1032,CollapsibleRockTowerPushUnitRampLeftGreen +1033,CollapsibleRockTowerPushUnitRampRightGreen +1048,CollapsiblePurifierTowerPushUnit +1050,LocustMPPrecursor +1051,ReleaseInterceptorsBeacon +1052,AdeptPhaseShift +1053,HydraliskImpaleMissile +1054,CycloneMissileLargeAir +1055,CycloneMissile +1056,CycloneMissileLarge +1057,OracleWeapon +1058,TempestWeaponGround +1059,ScoutMPAirWeaponLeft +1060,ScoutMPAirWeaponRight +1061,ArbiterMPWeaponMissile +1062,GuardianMPWeapon +1063,DevourerMPWeaponMissile +1064,DefilerMPDarkSwarmWeapon +1065,QueenMPEnsnareMissile +1066,QueenMPSpawnBroodlingsMissile +1067,LightningBombWeapon +1068,HERCPlacement +1069,GrappleWeapon +1072,CausticSprayMissile +1073,ParasiticBombMissile +1074,ParasiticBombDummy +1075,AdeptWeapon +1076,AdeptUpgradeWeapon +1077,LiberatorMissile +1078,LiberatorDamageMissile +1079,LiberatorAGMissile +1080,KD8Charge +1081,KD8ChargeWeapon +1083,SlaynElementalGrabWeapon +1084,SlaynElementalGrabAirUnit +1085,SlaynElementalGrabGroundUnit +1086,SlaynElementalWeapon +1091,CollapsibleRockTowerRampLeftGreen +1092,CollapsibleRockTowerRampRightGreen +1093,DestructibleExpeditionGate6x6 +1094,DestructibleZergInfestation3x3 +1095,HERC +1096,Moopy +1097,Replicant +1098,SeekerMissile +1099,AiurTempleBridgeDestructibleNE8Out +1100,AiurTempleBridgeDestructibleNE10Out +1101,AiurTempleBridgeDestructibleNE12Out +1102,AiurTempleBridgeDestructibleNW8Out +1103,AiurTempleBridgeDestructibleNW10Out +1104,AiurTempleBridgeDestructibleNW12Out +1105,AiurTempleBridgeDestructibleSW8Out +1106,AiurTempleBridgeDestructibleSW10Out +1107,AiurTempleBridgeDestructibleSW12Out +1108,AiurTempleBridgeDestructibleSE8Out +1109,AiurTempleBridgeDestructibleSE10Out +1110,AiurTempleBridgeDestructibleSE12Out +1112,FlyoverUnit +1113,CorsairMP +1114,ScoutMP +1116,ArbiterMP +1117,ScourgeMP +1118,DefilerMPPlagueWeapon +1119,QueenMP +1120,XelNagaDestructibleRampBlocker6S +1121,XelNagaDestructibleRampBlocker6SE +1122,XelNagaDestructibleRampBlocker6E +1123,XelNagaDestructibleRampBlocker6NE +1124,XelNagaDestructibleRampBlocker6N +1125,XelNagaDestructibleRampBlocker6NW +1126,XelNagaDestructibleRampBlocker6W +1127,XelNagaDestructibleRampBlocker6SW +1128,XelNagaDestructibleRampBlocker8S +1129,XelNagaDestructibleRampBlocker8SE +1130,XelNagaDestructibleRampBlocker8E +1131,XelNagaDestructibleRampBlocker8NE +1132,XelNagaDestructibleRampBlocker8N +1133,XelNagaDestructibleRampBlocker8NW +1134,XelNagaDestructibleRampBlocker8W +1135,XelNagaDestructibleRampBlocker8SW +1136,XelNagaDestructibleBlocker6S +1137,XelNagaDestructibleBlocker6SE +1138,XelNagaDestructibleBlocker6E +1139,XelNagaDestructibleBlocker6NE +1140,XelNagaDestructibleBlocker6N +1141,XelNagaDestructibleBlocker6NW +1142,XelNagaDestructibleBlocker6W +1143,XelNagaDestructibleBlocker6SW +1144,XelNagaDestructibleBlocker8S +1145,XelNagaDestructibleBlocker8SE +1146,XelNagaDestructibleBlocker8E +1147,XelNagaDestructibleBlocker8NE +1148,XelNagaDestructibleBlocker8N +1149,XelNagaDestructibleBlocker8NW +1150,XelNagaDestructibleBlocker8W +1151,XelNagaDestructibleBlocker8SW +1152,ReptileCrate +1153,SlaynSwarmHostSpawnFlyer +1154,SlaynElemental +1155,PurifierVespeneGeyser +1156,ShakurasVespeneGeyser +1157,CollapsiblePurifierTowerDiagonal +1158,CreepOnlyBlocker4x4 +1159,BattleStationMineralField +1160,BattleStationMineralField750 +1161,PurifierMineralField +1162,PurifierMineralField750 +1163,Beacon_Nova +1164,Beacon_NovaSmall +1165,Ursula +1166,Elsecaro_Colonist_Hut +1167,SnowGlazeStarterMP +1168,PylonOvercharged +1169,ObserverSiegeMode +1170,RavenRepairDrone +1172,ParasiticBombRelayDummy +1173,BypassArmorDrone +1174,AdeptPiercingWeapon +1175,HighTemplarWeaponMissile +1176,CycloneMissileLargeAirAlternative +1177,RavenScramblerMissile +1178,RavenRepairDroneReleaseWeapon +1179,RavenShredderMissileWeapon +1180,InfestedAcidSpinesWeapon +1181,InfestorEnsnareAttackMissile +1182,SNARE_PLACEHOLDER +1185,CorrosiveParasiteWeapon diff --git a/sc2reader/data/LotV/80949_abilities.csv b/sc2reader/data/LotV/80949_abilities.csv new file mode 100644 index 00000000..8444506e --- /dev/null +++ b/sc2reader/data/LotV/80949_abilities.csv @@ -0,0 +1,409 @@ +40,Taunt +41,stop +43,move +46,attack +62,SprayTerran +63,SprayZerg +64,SprayProtoss +65,SalvageShared +67,GhostHoldFire +68,GhostWeaponsFree +70,Explode +71,FleetBeaconResearch +72,FungalGrowth +73,GuardianShield +74,MULERepair +75,ZerglingTrain +76,NexusTrainMothership +77,Feedback +78,MassRecall +80,HallucinationArchon +81,HallucinationColossus +82,HallucinationHighTemplar +83,HallucinationImmortal +84,HallucinationPhoenix +85,HallucinationProbe +86,HallucinationStalker +87,HallucinationVoidRay +88,HallucinationWarpPrism +89,HallucinationZealot +90,MULEGather +92,CalldownMULE +93,GravitonBeam +97,SpawnChangeling +104,Rally +105,ProgressRally +106,RallyCommand +107,RallyNexus +108,RallyHatchery +109,RoachWarrenResearch +112,NeuralParasite +113,SpawnLarva +114,StimpackMarauder +115,SupplyDrop +119,UltraliskCavernResearch +121,SCVHarvest +122,ProbeHarvest +124,que1 +125,que5 +126,que5CancelToSelection +128,que5Addon +129,BuildInProgress +130,Repair +131,TerranBuild +133,Stimpack +134,GhostCloak +136,MedivacHeal +137,SiegeMode +138,Unsiege +139,BansheeCloak +140,MedivacTransport +141,ScannerSweep +142,Yamato +143,AssaultMode +144,FighterMode +145,BunkerTransport +146,CommandCenterTransport +147,CommandCenterLiftOff +148,CommandCenterLand +149,BarracksFlyingBuild +150,BarracksLiftOff +151,FactoryFlyingBuild +152,FactoryLiftOff +153,StarportFlyingBuild +154,StarportLiftOff +155,FactoryLand +156,StarportLand +157,CommandCenterTrain +158,BarracksLand +159,SupplyDepotLower +160,SupplyDepotRaise +161,BarracksTrain +162,FactoryTrain +163,StarportTrain +164,EngineeringBayResearch +166,GhostAcademyTrain +167,BarracksTechLabResearch +168,FactoryTechLabResearch +169,StarportTechLabResearch +170,GhostAcademyResearch +171,ArmoryResearch +172,ProtossBuild +173,WarpPrismTransport +174,GatewayTrain +175,StargateTrain +176,RoboticsFacilityTrain +177,NexusTrain +178,PsiStorm +179,HangarQueue5 +181,CarrierTrain +182,ForgeResearch +183,RoboticsBayResearch +184,TemplarArchiveResearch +185,ZergBuild +186,DroneHarvest +187,EvolutionChamberResearch +188,UpgradeToLair +189,UpgradeToHive +190,UpgradeToGreaterSpire +191,HiveResearch +192,SpawningPoolResearch +193,HydraliskDenResearch +194,GreaterSpireResearch +195,LarvaTrain +196,MorphToBroodLord +197,BurrowBanelingDown +198,BurrowBanelingUp +199,BurrowDroneDown +200,BurrowDroneUp +201,BurrowHydraliskDown +202,BurrowHydraliskUp +203,BurrowRoachDown +204,BurrowRoachUp +205,BurrowZerglingDown +206,BurrowZerglingUp +207,BurrowInfestorTerranDown +208,BurrowInfestorTerranUp +209,RedstoneLavaCritterBurrow +210,RedstoneLavaCritterInjuredBurrow +211,RedstoneLavaCritterUnburrow +212,RedstoneLavaCritterInjuredUnburrow +213,OverlordTransport +216,WarpGateTrain +217,BurrowQueenDown +218,BurrowQueenUp +219,NydusCanalTransport +220,Blink +221,BurrowInfestorDown +222,BurrowInfestorUp +223,MorphToOverseer +224,UpgradeToPlanetaryFortress +225,InfestationPitResearch +226,BanelingNestResearch +227,BurrowUltraliskDown +228,BurrowUltraliskUp +229,UpgradeToOrbital +230,UpgradeToWarpGate +231,MorphBackToGateway +232,OrbitalLiftOff +233,OrbitalCommandLand +234,ForceField +235,PhasingMode +236,TransportMode +237,FusionCoreResearch +238,CyberneticsCoreResearch +239,TwilightCouncilResearch +240,TacNukeStrike +243,EMP +245,HiveTrain +247,Transfusion +256,AttackRedirect +257,StimpackRedirect +258,StimpackMarauderRedirect +260,StopRedirect +261,GenerateCreep +262,QueenBuild +263,SpineCrawlerUproot +264,SporeCrawlerUproot +265,SpineCrawlerRoot +266,SporeCrawlerRoot +267,CreepTumorBurrowedBuild +268,BuildAutoTurret +269,ArchonWarp +270,NydusNetworkBuild +272,Charge +276,Contaminate +279,que5Passive +280,que5PassiveCancelToSelection +283,RavagerCorrosiveBile +305,BurrowLurkerMPDown +306,BurrowLurkerMPUp +309,BurrowRavagerDown +310,BurrowRavagerUp +311,MorphToRavager +312,MorphToTransportOverlord +314,ThorNormalMode +319,DigesterCreepSpray +323,MorphToMothership +348,XelNagaHealingShrine +357,MothershipCoreMassRecall +359,MorphToHellion +369,MorphToHellionTank +377,MorphToSwarmHostBurrowedMP +378,MorphToSwarmHostMP +383,BlindingCloud +385,Yoink +388,ViperConsumeStructure +391,TestZerg +392,VolatileBurstBuilding +399,WidowMineBurrow +400,WidowMineUnburrow +401,WidowMineAttack +402,TornadoMissile +405,HallucinationOracle +406,MedivacSpeedBoost +407,ExtendingBridgeNEWide8Out +408,ExtendingBridgeNEWide8 +409,ExtendingBridgeNWWide8Out +410,ExtendingBridgeNWWide8 +411,ExtendingBridgeNEWide10Out +412,ExtendingBridgeNEWide10 +413,ExtendingBridgeNWWide10Out +414,ExtendingBridgeNWWide10 +415,ExtendingBridgeNEWide12Out +416,ExtendingBridgeNEWide12 +417,ExtendingBridgeNWWide12Out +418,ExtendingBridgeNWWide12 +420,CritterFlee +421,OracleRevelation +429,MothershipCorePurifyNexus +430,XelNaga_Caverns_DoorE +431,XelNaga_Caverns_DoorEOpened +432,XelNaga_Caverns_DoorN +433,XelNaga_Caverns_DoorNE +434,XelNaga_Caverns_DoorNEOpened +435,XelNaga_Caverns_DoorNOpened +436,XelNaga_Caverns_DoorNW +437,XelNaga_Caverns_DoorNWOpened +438,XelNaga_Caverns_DoorS +439,XelNaga_Caverns_DoorSE +440,XelNaga_Caverns_DoorSEOpened +441,XelNaga_Caverns_DoorSOpened +442,XelNaga_Caverns_DoorSW +443,XelNaga_Caverns_DoorSWOpened +444,XelNaga_Caverns_DoorW +445,XelNaga_Caverns_DoorWOpened +446,XelNaga_Caverns_Floating_BridgeNE8Out +447,XelNaga_Caverns_Floating_BridgeNE8 +448,XelNaga_Caverns_Floating_BridgeNW8Out +449,XelNaga_Caverns_Floating_BridgeNW8 +450,XelNaga_Caverns_Floating_BridgeNE10Out +451,XelNaga_Caverns_Floating_BridgeNE10 +452,XelNaga_Caverns_Floating_BridgeNW10Out +453,XelNaga_Caverns_Floating_BridgeNW10 +454,XelNaga_Caverns_Floating_BridgeNE12Out +455,XelNaga_Caverns_Floating_BridgeNE12 +456,XelNaga_Caverns_Floating_BridgeNW12Out +457,XelNaga_Caverns_Floating_BridgeNW12 +458,XelNaga_Caverns_Floating_BridgeH8Out +459,XelNaga_Caverns_Floating_BridgeH8 +460,XelNaga_Caverns_Floating_BridgeV8Out +461,XelNaga_Caverns_Floating_BridgeV8 +462,XelNaga_Caverns_Floating_BridgeH10Out +463,XelNaga_Caverns_Floating_BridgeH10 +464,XelNaga_Caverns_Floating_BridgeV10Out +465,XelNaga_Caverns_Floating_BridgeV10 +466,XelNaga_Caverns_Floating_BridgeH12Out +467,XelNaga_Caverns_Floating_BridgeH12 +468,XelNaga_Caverns_Floating_BridgeV12Out +469,XelNaga_Caverns_Floating_BridgeV12 +470,TemporalField +496,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +497,SnowRefinery_Terran_ExtendingBridgeNEShort8 +498,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +499,SnowRefinery_Terran_ExtendingBridgeNWShort8 +521,CausticSpray +524,MorphToLurker +528,PurificationNovaTargeted +530,LockOn +532,LockOnCancel +534,Hyperjump +536,ThorAPMode +539,NydusWormTransport +540,OracleWeapon +546,LocustMPFlyingSwoop +547,HallucinationDisruptor +548,HallucinationAdept +549,VoidRaySwarmDamageBoost +550,SeekerDummyChannel +551,AiurLightBridgeNE8Out +552,AiurLightBridgeNE8 +553,AiurLightBridgeNE10Out +554,AiurLightBridgeNE10 +555,AiurLightBridgeNE12Out +556,AiurLightBridgeNE12 +557,AiurLightBridgeNW8Out +558,AiurLightBridgeNW8 +559,AiurLightBridgeNW10Out +560,AiurLightBridgeNW10 +561,AiurLightBridgeNW12Out +562,AiurLightBridgeNW12 +575,ShakurasLightBridgeNE8Out +576,ShakurasLightBridgeNE8 +577,ShakurasLightBridgeNE10Out +578,ShakurasLightBridgeNE10 +579,ShakurasLightBridgeNE12Out +580,ShakurasLightBridgeNE12 +581,ShakurasLightBridgeNW8Out +582,ShakurasLightBridgeNW8 +583,ShakurasLightBridgeNW10Out +584,ShakurasLightBridgeNW10 +585,ShakurasLightBridgeNW12Out +586,ShakurasLightBridgeNW12 +587,VoidMPImmortalReviveRebuild +589,ArbiterMPStasisField +590,ArbiterMPRecall +591,CorsairMPDisruptionWeb +592,MorphToGuardianMP +593,MorphToDevourerMP +594,DefilerMPConsume +595,DefilerMPDarkSwarm +596,DefilerMPPlague +597,DefilerMPBurrow +598,DefilerMPUnburrow +599,QueenMPEnsnare +600,QueenMPSpawnBroodlings +601,QueenMPInfestCommandCenter +605,OracleBuild +609,ParasiticBomb +610,AdeptPhaseShift +613,LurkerHoldFire +614,LurkerRemoveHoldFire +617,LiberatorAGTarget +618,LiberatorAATarget +620,AiurLightBridgeAbandonedNE8Out +621,AiurLightBridgeAbandonedNE8 +622,AiurLightBridgeAbandonedNE10Out +623,AiurLightBridgeAbandonedNE10 +624,AiurLightBridgeAbandonedNE12Out +625,AiurLightBridgeAbandonedNE12 +626,AiurLightBridgeAbandonedNW8Out +627,AiurLightBridgeAbandonedNW8 +628,AiurLightBridgeAbandonedNW10Out +629,AiurLightBridgeAbandonedNW10 +630,AiurLightBridgeAbandonedNW12Out +631,AiurLightBridgeAbandonedNW12 +632,KD8Charge +635,AdeptPhaseShiftCancel +636,AdeptShadePhaseShiftCancel +637,SlaynElementalGrab +639,PortCity_Bridge_UnitNE8Out +640,PortCity_Bridge_UnitNE8 +641,PortCity_Bridge_UnitSE8Out +642,PortCity_Bridge_UnitSE8 +643,PortCity_Bridge_UnitNW8Out +644,PortCity_Bridge_UnitNW8 +645,PortCity_Bridge_UnitSW8Out +646,PortCity_Bridge_UnitSW8 +647,PortCity_Bridge_UnitNE10Out +648,PortCity_Bridge_UnitNE10 +649,PortCity_Bridge_UnitSE10Out +650,PortCity_Bridge_UnitSE10 +651,PortCity_Bridge_UnitNW10Out +652,PortCity_Bridge_UnitNW10 +653,PortCity_Bridge_UnitSW10Out +654,PortCity_Bridge_UnitSW10 +655,PortCity_Bridge_UnitNE12Out +656,PortCity_Bridge_UnitNE12 +657,PortCity_Bridge_UnitSE12Out +658,PortCity_Bridge_UnitSE12 +659,PortCity_Bridge_UnitNW12Out +660,PortCity_Bridge_UnitNW12 +661,PortCity_Bridge_UnitSW12Out +662,PortCity_Bridge_UnitSW12 +663,PortCity_Bridge_UnitN8Out +664,PortCity_Bridge_UnitN8 +665,PortCity_Bridge_UnitS8Out +666,PortCity_Bridge_UnitS8 +667,PortCity_Bridge_UnitE8Out +668,PortCity_Bridge_UnitE8 +669,PortCity_Bridge_UnitW8Out +670,PortCity_Bridge_UnitW8 +671,PortCity_Bridge_UnitN10Out +672,PortCity_Bridge_UnitN10 +673,PortCity_Bridge_UnitS10Out +674,PortCity_Bridge_UnitS10 +675,PortCity_Bridge_UnitE10Out +676,PortCity_Bridge_UnitE10 +677,PortCity_Bridge_UnitW10Out +678,PortCity_Bridge_UnitW10 +679,PortCity_Bridge_UnitN12Out +680,PortCity_Bridge_UnitN12 +681,PortCity_Bridge_UnitS12Out +682,PortCity_Bridge_UnitS12 +683,PortCity_Bridge_UnitE12Out +684,PortCity_Bridge_UnitE12 +685,PortCity_Bridge_UnitW12Out +686,PortCity_Bridge_UnitW12 +689,DarkTemplarBlink +693,BattlecruiserAttack +695,BattlecruiserMove +697,BattlecruiserStop +698,BatteryOvercharge +700,AmorphousArmorcloud +702,SpawnLocustsTargeted +703,ViperParasiticBombRelay +704,ParasiticBombRelayDodge +705,VoidRaySwarmDamageBoostCancel +709,ChannelSnipe +712,DarkShrineResearch +713,LurkerDenMPResearch +714,ObserverSiegeMorphtoObserver +715,ObserverMorphtoObserverSiege +716,OverseerMorphtoOverseerSiegeMode +717,OverseerSiegeModeMorphtoOverseer +718,RavenScramblerMissile +720,RavenRepairDroneHeal +721,RavenShredderMissile +722,ChronoBoostEnergyCost +723,NexusMassRecall diff --git a/sc2reader/data/LotV/80949_units.csv b/sc2reader/data/LotV/80949_units.csv new file mode 100644 index 00000000..19aa551f --- /dev/null +++ b/sc2reader/data/LotV/80949_units.csv @@ -0,0 +1,1058 @@ +3,System_Snapshot_Dummy +21,Ball +22,StereoscopicOptionsUnit +23,Colossus +24,TechLab +25,Reactor +27,InfestorTerran +28,BanelingCocoon +29,Baneling +30,Mothership +31,PointDefenseDrone +32,Changeling +33,ChangelingZealot +34,ChangelingMarineShield +35,ChangelingMarine +36,ChangelingZerglingWings +37,ChangelingZergling +39,CommandCenter +40,SupplyDepot +41,Refinery +42,Barracks +43,EngineeringBay +44,MissileTurret +45,Bunker +46,RefineryRich +47,SensorTower +48,GhostAcademy +49,Factory +50,Starport +52,Armory +53,FusionCore +54,AutoTurret +55,SiegeTankSieged +56,SiegeTank +57,VikingAssault +58,VikingFighter +59,CommandCenterFlying +60,BarracksTechLab +61,BarracksReactor +62,FactoryTechLab +63,FactoryReactor +64,StarportTechLab +65,StarportReactor +66,FactoryFlying +67,StarportFlying +68,SCV +69,BarracksFlying +70,SupplyDepotLowered +71,Marine +72,Reaper +73,Ghost +74,Marauder +75,Thor +76,Hellion +77,Medivac +78,Banshee +79,Raven +80,Battlecruiser +81,Nuke +82,Nexus +83,Pylon +84,Assimilator +85,Gateway +86,Forge +87,FleetBeacon +88,TwilightCouncil +89,PhotonCannon +90,Stargate +91,TemplarArchive +92,DarkShrine +93,RoboticsBay +94,RoboticsFacility +95,CyberneticsCore +96,Zealot +97,Stalker +98,HighTemplar +99,DarkTemplar +100,Sentry +101,Phoenix +102,Carrier +103,VoidRay +104,WarpPrism +105,Observer +106,Immortal +107,Probe +108,Interceptor +109,Hatchery +110,CreepTumor +111,Extractor +112,SpawningPool +113,EvolutionChamber +114,HydraliskDen +115,Spire +116,UltraliskCavern +117,InfestationPit +118,NydusNetwork +119,BanelingNest +120,RoachWarren +121,SpineCrawler +122,SporeCrawler +123,Lair +124,Hive +125,GreaterSpire +126,Egg +127,Drone +128,Zergling +129,Overlord +130,Hydralisk +131,Mutalisk +132,Ultralisk +133,Roach +134,Infestor +135,Corruptor +136,BroodLordCocoon +137,BroodLord +138,BanelingBurrowed +139,DroneBurrowed +140,HydraliskBurrowed +141,RoachBurrowed +142,ZerglingBurrowed +143,InfestorTerranBurrowed +144,RedstoneLavaCritterBurrowed +145,RedstoneLavaCritterInjuredBurrowed +146,RedstoneLavaCritter +147,RedstoneLavaCritterInjured +148,QueenBurrowed +149,Queen +150,InfestorBurrowed +151,OverlordCocoon +152,Overseer +153,PlanetaryFortress +154,UltraliskBurrowed +155,OrbitalCommand +156,WarpGate +157,OrbitalCommandFlying +158,ForceField +159,WarpPrismPhasing +160,CreepTumorBurrowed +161,CreepTumorQueen +162,SpineCrawlerUprooted +163,SporeCrawlerUprooted +164,Archon +165,NydusCanal +166,BroodlingEscort +167,GhostAlternate +168,GhostNova +169,RichMineralField +170,RichMineralField750 +171,Ursadon +173,LurkerMPBurrowed +174,LurkerMP +175,LurkerDenMP +176,LurkerMPEgg +177,NydusCanalAttacker +178,OverlordTransport +179,Ravager +180,RavagerBurrowed +181,RavagerCocoon +182,TransportOverlordCocoon +183,XelNagaTower +185,Oracle +186,Tempest +188,InfestedTerransEgg +189,Larva +190,OverseerSiegeMode +192,ReaperPlaceholder +193,MarineACGluescreenDummy +194,FirebatACGluescreenDummy +195,MedicACGluescreenDummy +196,MarauderACGluescreenDummy +197,VultureACGluescreenDummy +198,SiegeTankACGluescreenDummy +199,VikingACGluescreenDummy +200,BansheeACGluescreenDummy +201,BattlecruiserACGluescreenDummy +202,OrbitalCommandACGluescreenDummy +203,BunkerACGluescreenDummy +204,BunkerUpgradedACGluescreenDummy +205,MissileTurretACGluescreenDummy +206,HellbatACGluescreenDummy +207,GoliathACGluescreenDummy +208,CycloneACGluescreenDummy +209,WraithACGluescreenDummy +210,ScienceVesselACGluescreenDummy +211,HerculesACGluescreenDummy +212,ThorACGluescreenDummy +213,PerditionTurretACGluescreenDummy +214,FlamingBettyACGluescreenDummy +215,DevastationTurretACGluescreenDummy +216,BlasterBillyACGluescreenDummy +217,SpinningDizzyACGluescreenDummy +218,ZerglingKerriganACGluescreenDummy +219,RaptorACGluescreenDummy +220,QueenCoopACGluescreenDummy +221,HydraliskACGluescreenDummy +222,HydraliskLurkerACGluescreenDummy +223,MutaliskBroodlordACGluescreenDummy +224,BroodLordACGluescreenDummy +225,UltraliskACGluescreenDummy +226,TorrasqueACGluescreenDummy +227,OverseerACGluescreenDummy +228,LurkerACGluescreenDummy +229,SpineCrawlerACGluescreenDummy +230,SporeCrawlerACGluescreenDummy +231,NydusNetworkACGluescreenDummy +232,OmegaNetworkACGluescreenDummy +233,ZerglingZagaraACGluescreenDummy +234,SwarmlingACGluescreenDummy +235,QueenZagaraACGluescreenDummy +236,BanelingACGluescreenDummy +237,SplitterlingACGluescreenDummy +238,AberrationACGluescreenDummy +239,ScourgeACGluescreenDummy +240,CorruptorACGluescreenDummy +241,OverseerZagaraACGluescreenDummy +242,BileLauncherACGluescreenDummy +243,SwarmQueenACGluescreenDummy +244,RoachACGluescreenDummy +245,RoachVileACGluescreenDummy +246,RavagerACGluescreenDummy +247,SwarmHostACGluescreenDummy +248,MutaliskACGluescreenDummy +249,GuardianACGluescreenDummy +250,DevourerACGluescreenDummy +251,ViperACGluescreenDummy +252,BrutaliskACGluescreenDummy +253,LeviathanACGluescreenDummy +254,ZealotACGluescreenDummy +255,ZealotAiurACGluescreenDummy +256,DragoonACGluescreenDummy +257,HighTemplarACGluescreenDummy +258,ArchonACGluescreenDummy +259,ImmortalACGluescreenDummy +260,ObserverACGluescreenDummy +261,PhoenixAiurACGluescreenDummy +262,ReaverACGluescreenDummy +263,TempestACGluescreenDummy +264,PhotonCannonACGluescreenDummy +265,ZealotVorazunACGluescreenDummy +266,ZealotShakurasACGluescreenDummy +267,StalkerShakurasACGluescreenDummy +268,DarkTemplarShakurasACGluescreenDummy +269,CorsairACGluescreenDummy +270,VoidRayACGluescreenDummy +271,VoidRayShakurasACGluescreenDummy +272,OracleACGluescreenDummy +273,DarkArchonACGluescreenDummy +274,DarkPylonACGluescreenDummy +275,ZealotPurifierACGluescreenDummy +276,SentryPurifierACGluescreenDummy +277,ImmortalKaraxACGluescreenDummy +278,ColossusACGluescreenDummy +279,ColossusPurifierACGluescreenDummy +280,PhoenixPurifierACGluescreenDummy +281,CarrierACGluescreenDummy +282,CarrierAiurACGluescreenDummy +283,KhaydarinMonolithACGluescreenDummy +284,ShieldBatteryACGluescreenDummy +285,EliteMarineACGluescreenDummy +286,MarauderCommandoACGluescreenDummy +287,SpecOpsGhostACGluescreenDummy +288,HellbatRangerACGluescreenDummy +289,StrikeGoliathACGluescreenDummy +290,HeavySiegeTankACGluescreenDummy +291,RaidLiberatorACGluescreenDummy +292,RavenTypeIIACGluescreenDummy +293,CovertBansheeACGluescreenDummy +294,RailgunTurretACGluescreenDummy +295,BlackOpsMissileTurretACGluescreenDummy +296,SupplicantACGluescreenDummy +297,StalkerTaldarimACGluescreenDummy +298,SentryTaldarimACGluescreenDummy +299,HighTemplarTaldarimACGluescreenDummy +300,ImmortalTaldarimACGluescreenDummy +301,ColossusTaldarimACGluescreenDummy +302,WarpPrismTaldarimACGluescreenDummy +303,PhotonCannonTaldarimACGluescreenDummy +304,StukovInfestedCivilianACGluescreenDummy +305,StukovInfestedMarineACGluescreenDummy +306,StukovInfestedSiegeTankACGluescreenDummy +307,StukovInfestedDiamondbackACGluescreenDummy +308,StukovInfestedBansheeACGluescreenDummy +309,SILiberatorACGluescreenDummy +310,StukovInfestedBunkerACGluescreenDummy +311,StukovInfestedMissileTurretACGluescreenDummy +312,StukovBroodQueenACGluescreenDummy +313,ZealotFenixACGluescreenDummy +314,SentryFenixACGluescreenDummy +315,AdeptFenixACGluescreenDummy +316,ImmortalFenixACGluescreenDummy +317,ColossusFenixACGluescreenDummy +318,DisruptorACGluescreenDummy +319,ObserverFenixACGluescreenDummy +320,ScoutACGluescreenDummy +321,CarrierFenixACGluescreenDummy +322,PhotonCannonFenixACGluescreenDummy +323,PrimalZerglingACGluescreenDummy +324,RavasaurACGluescreenDummy +325,PrimalRoachACGluescreenDummy +326,FireRoachACGluescreenDummy +327,PrimalGuardianACGluescreenDummy +328,PrimalHydraliskACGluescreenDummy +329,PrimalMutaliskACGluescreenDummy +330,PrimalImpalerACGluescreenDummy +331,PrimalSwarmHostACGluescreenDummy +332,CreeperHostACGluescreenDummy +333,PrimalUltraliskACGluescreenDummy +334,TyrannozorACGluescreenDummy +335,PrimalWurmACGluescreenDummy +336,HHReaperACGluescreenDummy +337,HHWidowMineACGluescreenDummy +338,HHHellionTankACGluescreenDummy +339,HHWraithACGluescreenDummy +340,HHVikingACGluescreenDummy +341,HHBattlecruiserACGluescreenDummy +342,HHRavenACGluescreenDummy +343,HHBomberPlatformACGluescreenDummy +344,HHMercStarportACGluescreenDummy +345,HHMissileTurretACGluescreenDummy +346,TychusReaperACGluescreenDummy +347,TychusFirebatACGluescreenDummy +348,TychusSpectreACGluescreenDummy +349,TychusMedicACGluescreenDummy +350,TychusMarauderACGluescreenDummy +351,TychusWarhoundACGluescreenDummy +352,TychusHERCACGluescreenDummy +353,TychusGhostACGluescreenDummy +354,TychusSCVAutoTurretACGluescreenDummy +355,ZeratulStalkerACGluescreenDummy +356,ZeratulSentryACGluescreenDummy +357,ZeratulDarkTemplarACGluescreenDummy +358,ZeratulImmortalACGluescreenDummy +359,ZeratulObserverACGluescreenDummy +360,ZeratulDisruptorACGluescreenDummy +361,ZeratulWarpPrismACGluescreenDummy +362,ZeratulPhotonCannonACGluescreenDummy +363,MechaZerglingACGluescreenDummy +364,MechaBanelingACGluescreenDummy +365,MechaHydraliskACGluescreenDummy +366,MechaInfestorACGluescreenDummy +367,MechaCorruptorACGluescreenDummy +368,MechaUltraliskACGluescreenDummy +369,MechaOverseerACGluescreenDummy +370,MechaLurkerACGluescreenDummy +371,MechaBattlecarrierLordACGluescreenDummy +372,MechaSpineCrawlerACGluescreenDummy +373,MechaSporeCrawlerACGluescreenDummy +374,TrooperMengskACGluescreenDummy +375,MedivacMengskACGluescreenDummy +376,BlimpMengskACGluescreenDummy +377,MarauderMengskACGluescreenDummy +378,GhostMengskACGluescreenDummy +379,SiegeTankMengskACGluescreenDummy +380,ThorMengskACGluescreenDummy +381,VikingMengskACGluescreenDummy +382,BattlecruiserMengskACGluescreenDummy +383,BunkerDepotMengskACGluescreenDummy +384,MissileTurretMengskACGluescreenDummy +385,ArtilleryMengskACGluescreenDummy +387,RenegadeLongboltMissileWeapon +388,LoadOutSpray@1 +389,LoadOutSpray@2 +390,LoadOutSpray@3 +391,LoadOutSpray@4 +392,LoadOutSpray@5 +393,LoadOutSpray@6 +394,LoadOutSpray@7 +395,LoadOutSpray@8 +396,LoadOutSpray@9 +397,LoadOutSpray@10 +398,LoadOutSpray@11 +399,LoadOutSpray@12 +400,LoadOutSpray@13 +401,LoadOutSpray@14 +402,NeedleSpinesWeapon +403,CorruptionWeapon +404,InfestedTerransWeapon +405,NeuralParasiteWeapon +406,PointDefenseDroneReleaseWeapon +407,HunterSeekerWeapon +408,MULE +410,ThorAAWeapon +411,PunisherGrenadesLMWeapon +412,VikingFighterWeapon +413,ATALaserBatteryLMWeapon +414,ATSLaserBatteryLMWeapon +415,LongboltMissileWeapon +416,D8ChargeWeapon +417,YamatoWeapon +418,IonCannonsWeapon +419,AcidSalivaWeapon +420,SpineCrawlerWeapon +421,SporeCrawlerWeapon +422,GlaiveWurmWeapon +423,GlaiveWurmM2Weapon +424,GlaiveWurmM3Weapon +425,StalkerWeapon +426,EMP2Weapon +427,BacklashRocketsLMWeapon +428,PhotonCannonWeapon +429,ParasiteSporeWeapon +431,Broodling +432,BroodLordBWeapon +435,AutoTurretReleaseWeapon +436,LarvaReleaseMissile +437,AcidSpinesWeapon +438,FrenzyWeapon +439,ContaminateWeapon +451,BeaconArmy +452,BeaconDefend +453,BeaconAttack +454,BeaconHarass +455,BeaconIdle +456,BeaconAuto +457,BeaconDetect +458,BeaconScout +459,BeaconClaim +460,BeaconExpand +461,BeaconRally +462,BeaconCustom1 +463,BeaconCustom2 +464,BeaconCustom3 +465,BeaconCustom4 +470,LiberatorAG +472,PreviewBunkerUpgraded +473,HellionTank +474,Cyclone +475,WidowMine +476,Liberator +478,Adept +479,Disruptor +480,SwarmHostMP +481,Viper +482,ShieldBattery +483,HighTemplarSkinPreview +484,MothershipCore +485,Viking +498,InhibitorZoneSmall +499,InhibitorZoneMedium +500,InhibitorZoneLarge +501,AccelerationZoneSmall +502,AccelerationZoneMedium +503,AccelerationZoneLarge +504,AccelerationZoneFlyingSmall +505,AccelerationZoneFlyingMedium +506,AccelerationZoneFlyingLarge +507,InhibitorZoneFlyingSmall +508,InhibitorZoneFlyingMedium +509,InhibitorZoneFlyingLarge +510,AssimilatorRich +511,RichVespeneGeyser +512,ExtractorRich +513,RavagerCorrosiveBileMissile +514,RavagerWeaponMissile +515,RenegadeMissileTurret +516,Rocks2x2NonConjoined +517,FungalGrowthMissile +518,NeuralParasiteTentacleMissile +519,Beacon_Protoss +520,Beacon_ProtossSmall +521,Beacon_Terran +522,Beacon_TerranSmall +523,Beacon_Zerg +524,Beacon_ZergSmall +525,Lyote +526,CarrionBird +527,KarakMale +528,KarakFemale +529,UrsadakFemaleExotic +530,UrsadakMale +531,UrsadakFemale +532,UrsadakCalf +533,UrsadakMaleExotic +534,UtilityBot +535,CommentatorBot1 +536,CommentatorBot2 +537,CommentatorBot3 +538,CommentatorBot4 +539,Scantipede +540,Dog +541,Sheep +542,Cow +543,InfestedTerransEggPlacement +544,InfestorTerransWeapon +545,MineralField +546,MineralField450 +547,MineralField750 +548,MineralFieldOpaque +549,MineralFieldOpaque900 +550,VespeneGeyser +551,SpacePlatformGeyser +552,DestructibleSearchlight +553,DestructibleBullhornLights +554,DestructibleStreetlight +555,DestructibleSpacePlatformSign +556,DestructibleStoreFrontCityProps +557,DestructibleBillboardTall +558,DestructibleBillboardScrollingText +559,DestructibleSpacePlatformBarrier +560,DestructibleSignsDirectional +561,DestructibleSignsConstruction +562,DestructibleSignsFunny +563,DestructibleSignsIcons +564,DestructibleSignsWarning +565,DestructibleGarage +566,DestructibleGarageLarge +567,DestructibleTrafficSignal +568,TrafficSignal +569,BraxisAlphaDestructible1x1 +570,BraxisAlphaDestructible2x2 +571,DestructibleDebris4x4 +572,DestructibleDebris6x6 +573,DestructibleRock2x4Vertical +574,DestructibleRock2x4Horizontal +575,DestructibleRock2x6Vertical +576,DestructibleRock2x6Horizontal +577,DestructibleRock4x4 +578,DestructibleRock6x6 +579,DestructibleRampDiagonalHugeULBR +580,DestructibleRampDiagonalHugeBLUR +581,DestructibleRampVerticalHuge +582,DestructibleRampHorizontalHuge +583,DestructibleDebrisRampDiagonalHugeULBR +584,DestructibleDebrisRampDiagonalHugeBLUR +585,WarpPrismSkinPreview +586,SiegeTankSkinPreview +587,ThorAP +588,ThorAALance +589,LiberatorSkinPreview +590,OverlordGenerateCreepKeybind +591,MengskStatueAlone +592,MengskStatue +593,WolfStatue +594,GlobeStatue +595,Weapon +596,GlaiveWurmBounceWeapon +597,BroodLordWeapon +598,BroodLordAWeapon +599,CreepBlocker1x1 +600,PermanentCreepBlocker1x1 +601,PathingBlocker1x1 +602,PathingBlocker2x2 +603,AutoTestAttackTargetGround +604,AutoTestAttackTargetAir +605,AutoTestAttacker +606,HelperEmitterSelectionArrow +607,MultiKillObject +608,ShapeGolfball +609,ShapeCone +610,ShapeCube +611,ShapeCylinder +612,ShapeDodecahedron +613,ShapeIcosahedron +614,ShapeOctahedron +615,ShapePyramid +616,ShapeRoundedCube +617,ShapeSphere +618,ShapeTetrahedron +619,ShapeThickTorus +620,ShapeThinTorus +621,ShapeTorus +622,Shape4PointStar +623,Shape5PointStar +624,Shape6PointStar +625,Shape8PointStar +626,ShapeArrowPointer +627,ShapeBowl +628,ShapeBox +629,ShapeCapsule +630,ShapeCrescentMoon +631,ShapeDecahedron +632,ShapeDiamond +633,ShapeFootball +634,ShapeGemstone +635,ShapeHeart +636,ShapeJack +637,ShapePlusSign +638,ShapeShamrock +639,ShapeSpade +640,ShapeTube +641,ShapeEgg +642,ShapeYenSign +643,ShapeX +644,ShapeWatermelon +645,ShapeWonSign +646,ShapeTennisball +647,ShapeStrawberry +648,ShapeSmileyFace +649,ShapeSoccerball +650,ShapeRainbow +651,ShapeSadFace +652,ShapePoundSign +653,ShapePear +654,ShapePineapple +655,ShapeOrange +656,ShapePeanut +657,ShapeO +658,ShapeLemon +659,ShapeMoneyBag +660,ShapeHorseshoe +661,ShapeHockeyStick +662,ShapeHockeyPuck +663,ShapeHand +664,ShapeGolfClub +665,ShapeGrape +666,ShapeEuroSign +667,ShapeDollarSign +668,ShapeBasketball +669,ShapeCarrot +670,ShapeCherry +671,ShapeBaseball +672,ShapeBaseballBat +673,ShapeBanana +674,ShapeApple +675,ShapeCashLarge +676,ShapeCashMedium +677,ShapeCashSmall +678,ShapeFootballColored +679,ShapeLemonSmall +680,ShapeOrangeSmall +681,ShapeTreasureChestOpen +682,ShapeTreasureChestClosed +683,ShapeWatermelonSmall +684,UnbuildableRocksDestructible +685,UnbuildableBricksDestructible +686,UnbuildablePlatesDestructible +687,Debris2x2NonConjoined +688,EnemyPathingBlocker1x1 +689,EnemyPathingBlocker2x2 +690,EnemyPathingBlocker4x4 +691,EnemyPathingBlocker8x8 +692,EnemyPathingBlocker16x16 +693,ScopeTest +694,SentryACGluescreenDummy +695,StukovInfestedTrooperACGluescreenDummy +711,CollapsibleTerranTowerDebris +712,DebrisRampLeft +713,DebrisRampRight +717,LocustMP +718,CollapsibleRockTowerDebris +719,NydusCanalCreeper +720,SwarmHostBurrowedMP +721,WarHound +722,WidowMineBurrowed +723,ExtendingBridgeNEWide8Out +724,ExtendingBridgeNEWide8 +725,ExtendingBridgeNWWide8Out +726,ExtendingBridgeNWWide8 +727,ExtendingBridgeNEWide10Out +728,ExtendingBridgeNEWide10 +729,ExtendingBridgeNWWide10Out +730,ExtendingBridgeNWWide10 +731,ExtendingBridgeNEWide12Out +732,ExtendingBridgeNEWide12 +733,ExtendingBridgeNWWide12Out +734,ExtendingBridgeNWWide12 +736,CollapsibleRockTowerDebrisRampRight +737,CollapsibleRockTowerDebrisRampLeft +738,XelNaga_Caverns_DoorE +739,XelNaga_Caverns_DoorEOpened +740,XelNaga_Caverns_DoorN +741,XelNaga_Caverns_DoorNE +742,XelNaga_Caverns_DoorNEOpened +743,XelNaga_Caverns_DoorNOpened +744,XelNaga_Caverns_DoorNW +745,XelNaga_Caverns_DoorNWOpened +746,XelNaga_Caverns_DoorS +747,XelNaga_Caverns_DoorSE +748,XelNaga_Caverns_DoorSEOpened +749,XelNaga_Caverns_DoorSOpened +750,XelNaga_Caverns_DoorSW +751,XelNaga_Caverns_DoorSWOpened +752,XelNaga_Caverns_DoorW +753,XelNaga_Caverns_DoorWOpened +754,XelNaga_Caverns_Floating_BridgeNE8Out +755,XelNaga_Caverns_Floating_BridgeNE8 +756,XelNaga_Caverns_Floating_BridgeNW8Out +757,XelNaga_Caverns_Floating_BridgeNW8 +758,XelNaga_Caverns_Floating_BridgeNE10Out +759,XelNaga_Caverns_Floating_BridgeNE10 +760,XelNaga_Caverns_Floating_BridgeNW10Out +761,XelNaga_Caverns_Floating_BridgeNW10 +762,XelNaga_Caverns_Floating_BridgeNE12Out +763,XelNaga_Caverns_Floating_BridgeNE12 +764,XelNaga_Caverns_Floating_BridgeNW12Out +765,XelNaga_Caverns_Floating_BridgeNW12 +766,XelNaga_Caverns_Floating_BridgeH8Out +767,XelNaga_Caverns_Floating_BridgeH8 +768,XelNaga_Caverns_Floating_BridgeV8Out +769,XelNaga_Caverns_Floating_BridgeV8 +770,XelNaga_Caverns_Floating_BridgeH10Out +771,XelNaga_Caverns_Floating_BridgeH10 +772,XelNaga_Caverns_Floating_BridgeV10Out +773,XelNaga_Caverns_Floating_BridgeV10 +774,XelNaga_Caverns_Floating_BridgeH12Out +775,XelNaga_Caverns_Floating_BridgeH12 +776,XelNaga_Caverns_Floating_BridgeV12Out +777,XelNaga_Caverns_Floating_BridgeV12 +780,CollapsibleTerranTowerPushUnitRampLeft +781,CollapsibleTerranTowerPushUnitRampRight +784,CollapsibleRockTowerPushUnit +785,CollapsibleTerranTowerPushUnit +786,CollapsibleRockTowerPushUnitRampRight +787,CollapsibleRockTowerPushUnitRampLeft +788,DigesterCreepSprayTargetUnit +789,DigesterCreepSprayUnit +790,NydusCanalAttackerWeapon +791,ViperConsumeStructureWeapon +794,ResourceBlocker +795,TempestWeapon +796,YoinkMissile +800,YoinkVikingAirMissile +802,YoinkVikingGroundMissile +804,YoinkSiegeTankMissile +806,WarHoundWeapon +808,EyeStalkWeapon +811,WidowMineWeapon +812,WidowMineAirWeapon +813,MothershipCoreWeaponWeapon +814,TornadoMissileWeapon +815,TornadoMissileDummyWeapon +816,TalonsMissileWeapon +817,CreepTumorMissile +818,LocustMPEggAMissileWeapon +819,LocustMPEggBMissileWeapon +820,LocustMPWeapon +822,RepulsorCannonWeapon +826,CollapsibleRockTowerDiagonal +827,CollapsibleTerranTowerDiagonal +828,CollapsibleTerranTowerRampLeft +829,CollapsibleTerranTowerRampRight +830,Ice2x2NonConjoined +831,IceProtossCrates +832,ProtossCrates +833,TowerMine +834,PickupPalletGas +835,PickupPalletMinerals +836,PickupScrapSalvage1x1 +837,PickupScrapSalvage2x2 +838,PickupScrapSalvage3x3 +839,RoughTerrain +840,UnbuildableBricksSmallUnit +841,UnbuildablePlatesSmallUnit +842,UnbuildablePlatesUnit +843,UnbuildableRocksSmallUnit +844,XelNagaHealingShrine +845,InvisibleTargetDummy +846,ProtossVespeneGeyser +847,CollapsibleRockTower +848,CollapsibleTerranTower +849,ThornLizard +850,CleaningBot +851,DestructibleRock6x6Weak +852,ProtossSnakeSegmentDemo +853,PhysicsCapsule +854,PhysicsCube +855,PhysicsCylinder +856,PhysicsKnot +857,PhysicsL +858,PhysicsPrimitives +859,PhysicsSphere +860,PhysicsStar +861,CreepBlocker4x4 +862,DestructibleCityDebris2x4Vertical +863,DestructibleCityDebris2x4Horizontal +864,DestructibleCityDebris2x6Vertical +865,DestructibleCityDebris2x6Horizontal +866,DestructibleCityDebris4x4 +867,DestructibleCityDebris6x6 +868,DestructibleCityDebrisHugeDiagonalBLUR +869,DestructibleCityDebrisHugeDiagonalULBR +870,TestZerg +871,PathingBlockerRadius1 +872,DestructibleRockEx12x4Vertical +873,DestructibleRockEx12x4Horizontal +874,DestructibleRockEx12x6Vertical +875,DestructibleRockEx12x6Horizontal +876,DestructibleRockEx14x4 +877,DestructibleRockEx16x6 +878,DestructibleRockEx1DiagonalHugeULBR +879,DestructibleRockEx1DiagonalHugeBLUR +880,DestructibleRockEx1VerticalHuge +881,DestructibleRockEx1HorizontalHuge +882,DestructibleIce2x4Vertical +883,DestructibleIce2x4Horizontal +884,DestructibleIce2x6Vertical +885,DestructibleIce2x6Horizontal +886,DestructibleIce4x4 +887,DestructibleIce6x6 +888,DestructibleIceDiagonalHugeULBR +889,DestructibleIceDiagonalHugeBLUR +890,DestructibleIceVerticalHuge +891,DestructibleIceHorizontalHuge +892,DesertPlanetSearchlight +893,DesertPlanetStreetlight +894,UnbuildableBricksUnit +895,UnbuildableRocksUnit +896,ZerusDestructibleArch +897,Artosilope +898,Anteplott +899,LabBot +900,Crabeetle +901,CollapsibleRockTowerRampRight +902,CollapsibleRockTowerRampLeft +903,LabMineralField +904,LabMineralField750 +919,CollapsibleRockTowerDebrisRampLeftGreen +920,CollapsibleRockTowerDebrisRampRightGreen +921,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +922,SnowRefinery_Terran_ExtendingBridgeNEShort8 +923,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +924,SnowRefinery_Terran_ExtendingBridgeNWShort8 +929,Tarsonis_DoorN +930,Tarsonis_DoorNLowered +931,Tarsonis_DoorNE +932,Tarsonis_DoorNELowered +933,Tarsonis_DoorE +934,Tarsonis_DoorELowered +935,Tarsonis_DoorNW +936,Tarsonis_DoorNWLowered +937,CompoundMansion_DoorN +938,CompoundMansion_DoorNLowered +939,CompoundMansion_DoorNE +940,CompoundMansion_DoorNELowered +941,CompoundMansion_DoorE +942,CompoundMansion_DoorELowered +943,CompoundMansion_DoorNW +944,CompoundMansion_DoorNWLowered +946,LocustMPFlying +947,AiurLightBridgeNE8Out +948,AiurLightBridgeNE8 +949,AiurLightBridgeNE10Out +950,AiurLightBridgeNE10 +951,AiurLightBridgeNE12Out +952,AiurLightBridgeNE12 +953,AiurLightBridgeNW8Out +954,AiurLightBridgeNW8 +955,AiurLightBridgeNW10Out +956,AiurLightBridgeNW10 +957,AiurLightBridgeNW12Out +958,AiurLightBridgeNW12 +959,AiurTempleBridgeNE8Out +961,AiurTempleBridgeNE10Out +963,AiurTempleBridgeNE12Out +965,AiurTempleBridgeNW8Out +967,AiurTempleBridgeNW10Out +969,AiurTempleBridgeNW12Out +971,ShakurasLightBridgeNE8Out +972,ShakurasLightBridgeNE8 +973,ShakurasLightBridgeNE10Out +974,ShakurasLightBridgeNE10 +975,ShakurasLightBridgeNE12Out +976,ShakurasLightBridgeNE12 +977,ShakurasLightBridgeNW8Out +978,ShakurasLightBridgeNW8 +979,ShakurasLightBridgeNW10Out +980,ShakurasLightBridgeNW10 +981,ShakurasLightBridgeNW12Out +982,ShakurasLightBridgeNW12 +983,VoidMPImmortalReviveCorpse +984,GuardianCocoonMP +985,GuardianMP +986,DevourerCocoonMP +987,DevourerMP +988,DefilerMPBurrowed +989,DefilerMP +990,OracleStasisTrap +991,DisruptorPhased +992,AiurLightBridgeAbandonedNE8Out +993,AiurLightBridgeAbandonedNE8 +994,AiurLightBridgeAbandonedNE10Out +995,AiurLightBridgeAbandonedNE10 +996,AiurLightBridgeAbandonedNE12Out +997,AiurLightBridgeAbandonedNE12 +998,AiurLightBridgeAbandonedNW8Out +999,AiurLightBridgeAbandonedNW8 +1000,AiurLightBridgeAbandonedNW10Out +1001,AiurLightBridgeAbandonedNW10 +1002,AiurLightBridgeAbandonedNW12Out +1003,AiurLightBridgeAbandonedNW12 +1004,CollapsiblePurifierTowerDebris +1005,PortCity_Bridge_UnitNE8Out +1006,PortCity_Bridge_UnitNE8 +1007,PortCity_Bridge_UnitSE8Out +1008,PortCity_Bridge_UnitSE8 +1009,PortCity_Bridge_UnitNW8Out +1010,PortCity_Bridge_UnitNW8 +1011,PortCity_Bridge_UnitSW8Out +1012,PortCity_Bridge_UnitSW8 +1013,PortCity_Bridge_UnitNE10Out +1014,PortCity_Bridge_UnitNE10 +1015,PortCity_Bridge_UnitSE10Out +1016,PortCity_Bridge_UnitSE10 +1017,PortCity_Bridge_UnitNW10Out +1018,PortCity_Bridge_UnitNW10 +1019,PortCity_Bridge_UnitSW10Out +1020,PortCity_Bridge_UnitSW10 +1021,PortCity_Bridge_UnitNE12Out +1022,PortCity_Bridge_UnitNE12 +1023,PortCity_Bridge_UnitSE12Out +1024,PortCity_Bridge_UnitSE12 +1025,PortCity_Bridge_UnitNW12Out +1026,PortCity_Bridge_UnitNW12 +1027,PortCity_Bridge_UnitSW12Out +1028,PortCity_Bridge_UnitSW12 +1029,PortCity_Bridge_UnitN8Out +1030,PortCity_Bridge_UnitN8 +1031,PortCity_Bridge_UnitS8Out +1032,PortCity_Bridge_UnitS8 +1033,PortCity_Bridge_UnitE8Out +1034,PortCity_Bridge_UnitE8 +1035,PortCity_Bridge_UnitW8Out +1036,PortCity_Bridge_UnitW8 +1037,PortCity_Bridge_UnitN10Out +1038,PortCity_Bridge_UnitN10 +1039,PortCity_Bridge_UnitS10Out +1040,PortCity_Bridge_UnitS10 +1041,PortCity_Bridge_UnitE10Out +1042,PortCity_Bridge_UnitE10 +1043,PortCity_Bridge_UnitW10Out +1044,PortCity_Bridge_UnitW10 +1045,PortCity_Bridge_UnitN12Out +1046,PortCity_Bridge_UnitN12 +1047,PortCity_Bridge_UnitS12Out +1048,PortCity_Bridge_UnitS12 +1049,PortCity_Bridge_UnitE12Out +1050,PortCity_Bridge_UnitE12 +1051,PortCity_Bridge_UnitW12Out +1052,PortCity_Bridge_UnitW12 +1053,PurifierRichMineralField +1054,PurifierRichMineralField750 +1055,CollapsibleRockTowerPushUnitRampLeftGreen +1056,CollapsibleRockTowerPushUnitRampRightGreen +1071,CollapsiblePurifierTowerPushUnit +1073,LocustMPPrecursor +1074,ReleaseInterceptorsBeacon +1075,AdeptPhaseShift +1076,HydraliskImpaleMissile +1077,CycloneMissileLargeAir +1078,CycloneMissile +1079,CycloneMissileLarge +1080,OracleWeapon +1081,TempestWeaponGround +1082,ScoutMPAirWeaponLeft +1083,ScoutMPAirWeaponRight +1084,ArbiterMPWeaponMissile +1085,GuardianMPWeapon +1086,DevourerMPWeaponMissile +1087,DefilerMPDarkSwarmWeapon +1088,QueenMPEnsnareMissile +1089,QueenMPSpawnBroodlingsMissile +1090,LightningBombWeapon +1091,HERCPlacement +1092,GrappleWeapon +1095,CausticSprayMissile +1096,ParasiticBombMissile +1097,ParasiticBombDummy +1098,AdeptWeapon +1099,AdeptUpgradeWeapon +1100,LiberatorMissile +1101,LiberatorDamageMissile +1102,LiberatorAGMissile +1103,KD8Charge +1104,KD8ChargeWeapon +1106,SlaynElementalGrabWeapon +1107,SlaynElementalGrabAirUnit +1108,SlaynElementalGrabGroundUnit +1109,SlaynElementalWeapon +1114,CollapsibleRockTowerRampLeftGreen +1115,CollapsibleRockTowerRampRightGreen +1116,DestructibleExpeditionGate6x6 +1117,DestructibleZergInfestation3x3 +1118,HERC +1119,Moopy +1120,Replicant +1121,SeekerMissile +1122,AiurTempleBridgeDestructibleNE8Out +1123,AiurTempleBridgeDestructibleNE10Out +1124,AiurTempleBridgeDestructibleNE12Out +1125,AiurTempleBridgeDestructibleNW8Out +1126,AiurTempleBridgeDestructibleNW10Out +1127,AiurTempleBridgeDestructibleNW12Out +1128,AiurTempleBridgeDestructibleSW8Out +1129,AiurTempleBridgeDestructibleSW10Out +1130,AiurTempleBridgeDestructibleSW12Out +1131,AiurTempleBridgeDestructibleSE8Out +1132,AiurTempleBridgeDestructibleSE10Out +1133,AiurTempleBridgeDestructibleSE12Out +1135,FlyoverUnit +1136,CorsairMP +1137,ScoutMP +1139,ArbiterMP +1140,ScourgeMP +1141,DefilerMPPlagueWeapon +1142,QueenMP +1143,XelNagaDestructibleRampBlocker6S +1144,XelNagaDestructibleRampBlocker6SE +1145,XelNagaDestructibleRampBlocker6E +1146,XelNagaDestructibleRampBlocker6NE +1147,XelNagaDestructibleRampBlocker6N +1148,XelNagaDestructibleRampBlocker6NW +1149,XelNagaDestructibleRampBlocker6W +1150,XelNagaDestructibleRampBlocker6SW +1151,XelNagaDestructibleRampBlocker8S +1152,XelNagaDestructibleRampBlocker8SE +1153,XelNagaDestructibleRampBlocker8E +1154,XelNagaDestructibleRampBlocker8NE +1155,XelNagaDestructibleRampBlocker8N +1156,XelNagaDestructibleRampBlocker8NW +1157,XelNagaDestructibleRampBlocker8W +1158,XelNagaDestructibleRampBlocker8SW +1159,XelNagaDestructibleBlocker6S +1160,XelNagaDestructibleBlocker6SE +1161,XelNagaDestructibleBlocker6E +1162,XelNagaDestructibleBlocker6NE +1163,XelNagaDestructibleBlocker6N +1164,XelNagaDestructibleBlocker6NW +1165,XelNagaDestructibleBlocker6W +1166,XelNagaDestructibleBlocker6SW +1167,XelNagaDestructibleBlocker8S +1168,XelNagaDestructibleBlocker8SE +1169,XelNagaDestructibleBlocker8E +1170,XelNagaDestructibleBlocker8NE +1171,XelNagaDestructibleBlocker8N +1172,XelNagaDestructibleBlocker8NW +1173,XelNagaDestructibleBlocker8W +1174,XelNagaDestructibleBlocker8SW +1175,ReptileCrate +1176,SlaynSwarmHostSpawnFlyer +1177,SlaynElemental +1178,PurifierVespeneGeyser +1179,ShakurasVespeneGeyser +1180,CollapsiblePurifierTowerDiagonal +1181,CreepOnlyBlocker4x4 +1182,BattleStationMineralField +1183,BattleStationMineralField750 +1184,PurifierMineralField +1185,PurifierMineralField750 +1186,Beacon_Nova +1187,Beacon_NovaSmall +1188,Ursula +1189,Elsecaro_Colonist_Hut +1190,SnowGlazeStarterMP +1191,PylonOvercharged +1192,ObserverSiegeMode +1193,RavenRepairDrone +1195,ParasiticBombRelayDummy +1196,BypassArmorDrone +1197,AdeptPiercingWeapon +1198,HighTemplarWeaponMissile +1199,CycloneMissileLargeAirAlternative +1200,RavenScramblerMissile +1201,RavenRepairDroneReleaseWeapon +1202,RavenShredderMissileWeapon +1203,InfestedAcidSpinesWeapon +1204,InfestorEnsnareAttackMissile +1205,SNARE_PLACEHOLDER +1208,CorrosiveParasiteWeapon diff --git a/sc2reader/data/LotV/89720_abilities.csv b/sc2reader/data/LotV/89720_abilities.csv new file mode 100644 index 00000000..b6bf64d9 --- /dev/null +++ b/sc2reader/data/LotV/89720_abilities.csv @@ -0,0 +1,409 @@ +40,Taunt +41,stop +43,move +46,attack +62,SprayTerran +63,SprayZerg +64,SprayProtoss +65,SalvageShared +67,GhostHoldFire +68,GhostWeaponsFree +70,Explode +71,FleetBeaconResearch +72,FungalGrowth +73,GuardianShield +74,MULERepair +76,NexusTrainMothership +77,Feedback +78,MassRecall +80,HallucinationArchon +81,HallucinationColossus +82,HallucinationHighTemplar +83,HallucinationImmortal +84,HallucinationPhoenix +85,HallucinationProbe +86,HallucinationStalker +87,HallucinationVoidRay +88,HallucinationWarpPrism +89,HallucinationZealot +90,MULEGather +92,CalldownMULE +93,GravitonBeam +97,SpawnChangeling +104,Rally +105,ProgressRally +106,RallyCommand +107,RallyNexus +108,RallyHatchery +109,RoachWarrenResearch +112,NeuralParasite +113,SpawnLarva +114,StimpackMarauder +115,SupplyDrop +119,UltraliskCavernResearch +121,SCVHarvest +122,ProbeHarvest +124,que1 +125,que5 +126,que5CancelToSelection +128,que5Addon +129,BuildInProgress +130,Repair +131,TerranBuild +133,Stimpack +134,GhostCloak +136,MedivacHeal +137,SiegeMode +138,Unsiege +139,BansheeCloak +140,MedivacTransport +141,ScannerSweep +142,Yamato +143,AssaultMode +144,FighterMode +145,BunkerTransport +146,CommandCenterTransport +147,CommandCenterLiftOff +148,CommandCenterLand +149,BarracksBuild +150,BarracksLiftOff +151,FactoryFlyingBuild +152,FactoryLiftOff +153,StarportBuild +154,StarportLiftOff +155,FactoryLand +156,StarportLand +157,OrbitalCommandTrain +158,BarracksLand +159,SupplyDepotLower +160,SupplyDepotRaise +161,BarracksTrain +162,FactoryTrain +163,StarportTrain +164,EngineeringBayResearch +166,GhostAcademyTrain +167,BarracksTechLabResearch +168,FactoryTechLabResearch +169,StarportTechLabResearch +170,GhostAcademyResearch +171,ArmoryResearch +172,ProtossBuild +173,WarpPrismTransport +174,GatewayTrain +175,StargateTrain +176,RoboticsFacilityTrain +177,NexusTrain +178,PsiStorm +179,HangarQueue5 +181,CarrierTrain +182,ForgeResearch +183,RoboticsBayResearch +184,TemplarArchiveResearch +185,ZergBuild +186,DroneHarvest +187,EvolutionChamberResearch +188,UpgradeToLair +189,UpgradeToHive +190,UpgradeToGreaterSpire +191,HiveResearch +192,SpawningPoolResearch +193,HydraliskDenResearch +194,GreaterSpireResearch +195,LarvaTrain +196,MorphToBroodLord +197,BurrowBanelingDown +198,BurrowBanelingUp +199,BurrowDroneDown +200,BurrowDroneUp +201,BurrowHydraliskDown +202,BurrowHydraliskUp +203,BurrowRoachDown +204,BurrowRoachUp +205,BurrowZerglingDown +206,BurrowZerglingUp +207,BurrowInfestorTerranDown +208,BurrowInfestorTerranUp +209,RedstoneLavaCritterBurrow +210,RedstoneLavaCritterInjuredBurrow +211,RedstoneLavaCritterUnburrow +212,RedstoneLavaCritterInjuredUnburrow +213,OverlordTransport +216,WarpGateTrain +217,BurrowQueenDown +218,BurrowQueenUp +219,NydusCanalTransport +220,Blink +221,BurrowInfestorDown +222,BurrowInfestorUp +223,MorphToOverseer +224,UpgradeToPlanetaryFortress +225,InfestationPitResearch +226,BanelingNestResearch +227,BurrowUltraliskDown +228,BurrowUltraliskUp +229,UpgradeToOrbital +230,UpgradeToWarpGate +231,MorphBackToGateway +232,OrbitalLiftOff +233,OrbitalCommandLand +234,ForceField +235,PhasingMode +236,TransportMode +237,FusionCoreResearch +238,CyberneticsCoreResearch +239,TwilightCouncilResearch +240,TacNukeStrike +243,EMP +245,HiveTrain +247,Transfusion +256,AttackRedirect +257,StimpackRedirect +258,StimpackMarauderRedirect +260,StopRedirect +261,GenerateCreep +262,QueenBuild +263,SpineCrawlerUproot +264,SporeCrawlerUproot +265,SpineCrawlerRoot +266,SporeCrawlerRoot +267,CreepTumorBurrowedBuild +268,BuildAutoTurret +269,ArchonWarp +270,NydusNetworkBuild +272,Charge +276,Contaminate +279,que5Passive +280,que5PassiveCancelToSelection +283,RavagerCorrosiveBile +305,BurrowLurkerMPDown +306,BurrowLurkerMPUp +309,BurrowRavagerDown +310,BurrowRavagerUp +311,MorphToRavager +312,MorphToTransportOverlord +314,ThorNormalMode +319,DigesterCreepSpray +323,MorphToMothership +348,XelNagaHealingShrine +357,MothershipCoreMassRecall +359,MorphToHellion +369,MorphToHellionTank +377,MorphToSwarmHostBurrowedMP +378,MorphToSwarmHostMP +383,BlindingCloud +385,Yoink +388,ViperConsumeStructure +391,TestZerg +392,VolatileBurstBuilding +399,WidowMineBurrow +400,WidowMineUnburrow +401,WidowMineAttack +402,TornadoMissile +405,HallucinationOracle +406,MedivacSpeedBoost +407,ExtendingBridgeNEWide8Out +408,ExtendingBridgeNEWide8 +409,ExtendingBridgeNWWide8Out +410,ExtendingBridgeNWWide8 +411,ExtendingBridgeNEWide10Out +412,ExtendingBridgeNEWide10 +413,ExtendingBridgeNWWide10Out +414,ExtendingBridgeNWWide10 +415,ExtendingBridgeNEWide12Out +416,ExtendingBridgeNEWide12 +417,ExtendingBridgeNWWide12Out +418,ExtendingBridgeNWWide12 +420,CritterFlee +421,OracleRevelation +429,MothershipCorePurifyNexus +430,XelNaga_Caverns_DoorE +431,XelNaga_Caverns_DoorEOpened +432,XelNaga_Caverns_DoorN +433,XelNaga_Caverns_DoorNE +434,XelNaga_Caverns_DoorNEOpened +435,XelNaga_Caverns_DoorNOpened +436,XelNaga_Caverns_DoorNW +437,XelNaga_Caverns_DoorNWOpened +438,XelNaga_Caverns_DoorS +439,XelNaga_Caverns_DoorSE +440,XelNaga_Caverns_DoorSEOpened +441,XelNaga_Caverns_DoorSOpened +442,XelNaga_Caverns_DoorSW +443,XelNaga_Caverns_DoorSWOpened +444,XelNaga_Caverns_DoorW +445,XelNaga_Caverns_DoorWOpened +446,XelNaga_Caverns_Floating_BridgeNE8Out +447,XelNaga_Caverns_Floating_BridgeNE8 +448,XelNaga_Caverns_Floating_BridgeNW8Out +449,XelNaga_Caverns_Floating_BridgeNW8 +450,XelNaga_Caverns_Floating_BridgeNE10Out +451,XelNaga_Caverns_Floating_BridgeNE10 +452,XelNaga_Caverns_Floating_BridgeNW10Out +453,XelNaga_Caverns_Floating_BridgeNW10 +454,XelNaga_Caverns_Floating_BridgeNE12Out +455,XelNaga_Caverns_Floating_BridgeNE12 +456,XelNaga_Caverns_Floating_BridgeNW12Out +457,XelNaga_Caverns_Floating_BridgeNW12 +458,XelNaga_Caverns_Floating_BridgeH8Out +459,XelNaga_Caverns_Floating_BridgeH8 +460,XelNaga_Caverns_Floating_BridgeV8Out +461,XelNaga_Caverns_Floating_BridgeV8 +462,XelNaga_Caverns_Floating_BridgeH10Out +463,XelNaga_Caverns_Floating_BridgeH10 +464,XelNaga_Caverns_Floating_BridgeV10Out +465,XelNaga_Caverns_Floating_BridgeV10 +466,XelNaga_Caverns_Floating_BridgeH12Out +467,XelNaga_Caverns_Floating_BridgeH12 +468,XelNaga_Caverns_Floating_BridgeV12Out +469,XelNaga_Caverns_Floating_BridgeV12 +470,TemporalField +496,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +497,SnowRefinery_Terran_ExtendingBridgeNEShort8 +498,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +499,SnowRefinery_Terran_ExtendingBridgeNWShort8 +521,CausticSpray +524,MorphToLurker +528,PurificationNovaTargeted +530,LockOn +532,LockOnCancel +534,Hyperjump +536,ThorAPMode +539,NydusWormTransport +540,OracleWeapon +546,LocustMPFlyingSwoop +547,HallucinationDisruptor +548,HallucinationAdept +549,VoidRaySwarmDamageBoost +550,SeekerDummyChannel +551,AiurLightBridgeNE8Out +552,AiurLightBridgeNE8 +553,AiurLightBridgeNE10Out +554,AiurLightBridgeNE10 +555,AiurLightBridgeNE12Out +556,AiurLightBridgeNE12 +557,AiurLightBridgeNW8Out +558,AiurLightBridgeNW8 +559,AiurLightBridgeNW10Out +560,AiurLightBridgeNW10 +561,AiurLightBridgeNW12Out +562,AiurLightBridgeNW12 +575,ShakurasLightBridgeNE8Out +576,ShakurasLightBridgeNE8 +577,ShakurasLightBridgeNE10Out +578,ShakurasLightBridgeNE10 +579,ShakurasLightBridgeNE12Out +580,ShakurasLightBridgeNE12 +581,ShakurasLightBridgeNW8Out +582,ShakurasLightBridgeNW8 +583,ShakurasLightBridgeNW10Out +584,ShakurasLightBridgeNW10 +585,ShakurasLightBridgeNW12Out +586,ShakurasLightBridgeNW12 +587,VoidMPImmortalReviveRebuild +589,ArbiterMPStasisField +590,ArbiterMPRecall +591,CorsairMPDisruptionWeb +592,MorphToGuardianMP +593,MorphToDevourerMP +594,DefilerMPConsume +595,DefilerMPDarkSwarm +596,DefilerMPPlague +597,DefilerMPBurrow +598,DefilerMPUnburrow +599,QueenMPEnsnare +600,QueenMPSpawnBroodlings +601,QueenMPInfestCommandCenter +605,OracleBuild +609,ParasiticBomb +610,AdeptPhaseShift +613,LurkerHoldFire +614,LurkerRemoveHoldFire +617,LiberatorAGTarget +618,LiberatorAATarget +620,AiurLightBridgeAbandonedNE8Out +621,AiurLightBridgeAbandonedNE8 +622,AiurLightBridgeAbandonedNE10Out +623,AiurLightBridgeAbandonedNE10 +624,AiurLightBridgeAbandonedNE12Out +625,AiurLightBridgeAbandonedNE12 +626,AiurLightBridgeAbandonedNW8Out +627,AiurLightBridgeAbandonedNW8 +628,AiurLightBridgeAbandonedNW10Out +629,AiurLightBridgeAbandonedNW10 +630,AiurLightBridgeAbandonedNW12Out +631,AiurLightBridgeAbandonedNW12 +632,KD8Charge +635,AdeptPhaseShiftCancel +636,AdeptShadePhaseShiftCancel +637,SlaynElementalGrab +639,PortCity_Bridge_UnitNE8Out +640,PortCity_Bridge_UnitNE8 +641,PortCity_Bridge_UnitSE8Out +642,PortCity_Bridge_UnitSE8 +643,PortCity_Bridge_UnitNW8Out +644,PortCity_Bridge_UnitNW8 +645,PortCity_Bridge_UnitSW8Out +646,PortCity_Bridge_UnitSW8 +647,PortCity_Bridge_UnitNE10Out +648,PortCity_Bridge_UnitNE10 +649,PortCity_Bridge_UnitSE10Out +650,PortCity_Bridge_UnitSE10 +651,PortCity_Bridge_UnitNW10Out +652,PortCity_Bridge_UnitNW10 +653,PortCity_Bridge_UnitSW10Out +654,PortCity_Bridge_UnitSW10 +655,PortCity_Bridge_UnitNE12Out +656,PortCity_Bridge_UnitNE12 +657,PortCity_Bridge_UnitSE12Out +658,PortCity_Bridge_UnitSE12 +659,PortCity_Bridge_UnitNW12Out +660,PortCity_Bridge_UnitNW12 +661,PortCity_Bridge_UnitSW12Out +662,PortCity_Bridge_UnitSW12 +663,PortCity_Bridge_UnitN8Out +664,PortCity_Bridge_UnitN8 +665,PortCity_Bridge_UnitS8Out +666,PortCity_Bridge_UnitS8 +667,PortCity_Bridge_UnitE8Out +668,PortCity_Bridge_UnitE8 +669,PortCity_Bridge_UnitW8Out +670,PortCity_Bridge_UnitW8 +671,PortCity_Bridge_UnitN10Out +672,PortCity_Bridge_UnitN10 +673,PortCity_Bridge_UnitS10Out +674,PortCity_Bridge_UnitS10 +675,PortCity_Bridge_UnitE10Out +676,PortCity_Bridge_UnitE10 +677,PortCity_Bridge_UnitW10Out +678,PortCity_Bridge_UnitW10 +679,PortCity_Bridge_UnitN12Out +680,PortCity_Bridge_UnitN12 +681,PortCity_Bridge_UnitS12Out +682,PortCity_Bridge_UnitS12 +683,PortCity_Bridge_UnitE12Out +684,PortCity_Bridge_UnitE12 +685,PortCity_Bridge_UnitW12Out +686,PortCity_Bridge_UnitW12 +689,DarkTemplarBlink +693,BattlecruiserAttack +695,BattlecruiserMove +697,BattlecruiserStop +698,BatteryOvercharge +700,AmorphousArmorcloud +702,SpawnLocustsTargeted +703,ViperParasiticBombRelay +704,ParasiticBombRelayDodge +705,VoidRaySwarmDamageBoostCancel +709,ChannelSnipe +712,DarkShrineResearch +713,LurkerDenMPResearch +714,ObserverSiegeMorphtoObserver +715,ObserverMorphtoObserverSiege +716,OverseerMorphtoOverseerSiegeMode +717,OverseerSiegeModeMorphtoOverseer +718,RavenScramblerMissile +720,RavenRepairDroneHeal +721,RavenShredderMissile +722,ChronoBoostEnergyCost +723,NexusMassRecall +729,MorphToBaneling diff --git a/sc2reader/data/LotV/89720_units.csv b/sc2reader/data/LotV/89720_units.csv new file mode 100644 index 00000000..19aa551f --- /dev/null +++ b/sc2reader/data/LotV/89720_units.csv @@ -0,0 +1,1058 @@ +3,System_Snapshot_Dummy +21,Ball +22,StereoscopicOptionsUnit +23,Colossus +24,TechLab +25,Reactor +27,InfestorTerran +28,BanelingCocoon +29,Baneling +30,Mothership +31,PointDefenseDrone +32,Changeling +33,ChangelingZealot +34,ChangelingMarineShield +35,ChangelingMarine +36,ChangelingZerglingWings +37,ChangelingZergling +39,CommandCenter +40,SupplyDepot +41,Refinery +42,Barracks +43,EngineeringBay +44,MissileTurret +45,Bunker +46,RefineryRich +47,SensorTower +48,GhostAcademy +49,Factory +50,Starport +52,Armory +53,FusionCore +54,AutoTurret +55,SiegeTankSieged +56,SiegeTank +57,VikingAssault +58,VikingFighter +59,CommandCenterFlying +60,BarracksTechLab +61,BarracksReactor +62,FactoryTechLab +63,FactoryReactor +64,StarportTechLab +65,StarportReactor +66,FactoryFlying +67,StarportFlying +68,SCV +69,BarracksFlying +70,SupplyDepotLowered +71,Marine +72,Reaper +73,Ghost +74,Marauder +75,Thor +76,Hellion +77,Medivac +78,Banshee +79,Raven +80,Battlecruiser +81,Nuke +82,Nexus +83,Pylon +84,Assimilator +85,Gateway +86,Forge +87,FleetBeacon +88,TwilightCouncil +89,PhotonCannon +90,Stargate +91,TemplarArchive +92,DarkShrine +93,RoboticsBay +94,RoboticsFacility +95,CyberneticsCore +96,Zealot +97,Stalker +98,HighTemplar +99,DarkTemplar +100,Sentry +101,Phoenix +102,Carrier +103,VoidRay +104,WarpPrism +105,Observer +106,Immortal +107,Probe +108,Interceptor +109,Hatchery +110,CreepTumor +111,Extractor +112,SpawningPool +113,EvolutionChamber +114,HydraliskDen +115,Spire +116,UltraliskCavern +117,InfestationPit +118,NydusNetwork +119,BanelingNest +120,RoachWarren +121,SpineCrawler +122,SporeCrawler +123,Lair +124,Hive +125,GreaterSpire +126,Egg +127,Drone +128,Zergling +129,Overlord +130,Hydralisk +131,Mutalisk +132,Ultralisk +133,Roach +134,Infestor +135,Corruptor +136,BroodLordCocoon +137,BroodLord +138,BanelingBurrowed +139,DroneBurrowed +140,HydraliskBurrowed +141,RoachBurrowed +142,ZerglingBurrowed +143,InfestorTerranBurrowed +144,RedstoneLavaCritterBurrowed +145,RedstoneLavaCritterInjuredBurrowed +146,RedstoneLavaCritter +147,RedstoneLavaCritterInjured +148,QueenBurrowed +149,Queen +150,InfestorBurrowed +151,OverlordCocoon +152,Overseer +153,PlanetaryFortress +154,UltraliskBurrowed +155,OrbitalCommand +156,WarpGate +157,OrbitalCommandFlying +158,ForceField +159,WarpPrismPhasing +160,CreepTumorBurrowed +161,CreepTumorQueen +162,SpineCrawlerUprooted +163,SporeCrawlerUprooted +164,Archon +165,NydusCanal +166,BroodlingEscort +167,GhostAlternate +168,GhostNova +169,RichMineralField +170,RichMineralField750 +171,Ursadon +173,LurkerMPBurrowed +174,LurkerMP +175,LurkerDenMP +176,LurkerMPEgg +177,NydusCanalAttacker +178,OverlordTransport +179,Ravager +180,RavagerBurrowed +181,RavagerCocoon +182,TransportOverlordCocoon +183,XelNagaTower +185,Oracle +186,Tempest +188,InfestedTerransEgg +189,Larva +190,OverseerSiegeMode +192,ReaperPlaceholder +193,MarineACGluescreenDummy +194,FirebatACGluescreenDummy +195,MedicACGluescreenDummy +196,MarauderACGluescreenDummy +197,VultureACGluescreenDummy +198,SiegeTankACGluescreenDummy +199,VikingACGluescreenDummy +200,BansheeACGluescreenDummy +201,BattlecruiserACGluescreenDummy +202,OrbitalCommandACGluescreenDummy +203,BunkerACGluescreenDummy +204,BunkerUpgradedACGluescreenDummy +205,MissileTurretACGluescreenDummy +206,HellbatACGluescreenDummy +207,GoliathACGluescreenDummy +208,CycloneACGluescreenDummy +209,WraithACGluescreenDummy +210,ScienceVesselACGluescreenDummy +211,HerculesACGluescreenDummy +212,ThorACGluescreenDummy +213,PerditionTurretACGluescreenDummy +214,FlamingBettyACGluescreenDummy +215,DevastationTurretACGluescreenDummy +216,BlasterBillyACGluescreenDummy +217,SpinningDizzyACGluescreenDummy +218,ZerglingKerriganACGluescreenDummy +219,RaptorACGluescreenDummy +220,QueenCoopACGluescreenDummy +221,HydraliskACGluescreenDummy +222,HydraliskLurkerACGluescreenDummy +223,MutaliskBroodlordACGluescreenDummy +224,BroodLordACGluescreenDummy +225,UltraliskACGluescreenDummy +226,TorrasqueACGluescreenDummy +227,OverseerACGluescreenDummy +228,LurkerACGluescreenDummy +229,SpineCrawlerACGluescreenDummy +230,SporeCrawlerACGluescreenDummy +231,NydusNetworkACGluescreenDummy +232,OmegaNetworkACGluescreenDummy +233,ZerglingZagaraACGluescreenDummy +234,SwarmlingACGluescreenDummy +235,QueenZagaraACGluescreenDummy +236,BanelingACGluescreenDummy +237,SplitterlingACGluescreenDummy +238,AberrationACGluescreenDummy +239,ScourgeACGluescreenDummy +240,CorruptorACGluescreenDummy +241,OverseerZagaraACGluescreenDummy +242,BileLauncherACGluescreenDummy +243,SwarmQueenACGluescreenDummy +244,RoachACGluescreenDummy +245,RoachVileACGluescreenDummy +246,RavagerACGluescreenDummy +247,SwarmHostACGluescreenDummy +248,MutaliskACGluescreenDummy +249,GuardianACGluescreenDummy +250,DevourerACGluescreenDummy +251,ViperACGluescreenDummy +252,BrutaliskACGluescreenDummy +253,LeviathanACGluescreenDummy +254,ZealotACGluescreenDummy +255,ZealotAiurACGluescreenDummy +256,DragoonACGluescreenDummy +257,HighTemplarACGluescreenDummy +258,ArchonACGluescreenDummy +259,ImmortalACGluescreenDummy +260,ObserverACGluescreenDummy +261,PhoenixAiurACGluescreenDummy +262,ReaverACGluescreenDummy +263,TempestACGluescreenDummy +264,PhotonCannonACGluescreenDummy +265,ZealotVorazunACGluescreenDummy +266,ZealotShakurasACGluescreenDummy +267,StalkerShakurasACGluescreenDummy +268,DarkTemplarShakurasACGluescreenDummy +269,CorsairACGluescreenDummy +270,VoidRayACGluescreenDummy +271,VoidRayShakurasACGluescreenDummy +272,OracleACGluescreenDummy +273,DarkArchonACGluescreenDummy +274,DarkPylonACGluescreenDummy +275,ZealotPurifierACGluescreenDummy +276,SentryPurifierACGluescreenDummy +277,ImmortalKaraxACGluescreenDummy +278,ColossusACGluescreenDummy +279,ColossusPurifierACGluescreenDummy +280,PhoenixPurifierACGluescreenDummy +281,CarrierACGluescreenDummy +282,CarrierAiurACGluescreenDummy +283,KhaydarinMonolithACGluescreenDummy +284,ShieldBatteryACGluescreenDummy +285,EliteMarineACGluescreenDummy +286,MarauderCommandoACGluescreenDummy +287,SpecOpsGhostACGluescreenDummy +288,HellbatRangerACGluescreenDummy +289,StrikeGoliathACGluescreenDummy +290,HeavySiegeTankACGluescreenDummy +291,RaidLiberatorACGluescreenDummy +292,RavenTypeIIACGluescreenDummy +293,CovertBansheeACGluescreenDummy +294,RailgunTurretACGluescreenDummy +295,BlackOpsMissileTurretACGluescreenDummy +296,SupplicantACGluescreenDummy +297,StalkerTaldarimACGluescreenDummy +298,SentryTaldarimACGluescreenDummy +299,HighTemplarTaldarimACGluescreenDummy +300,ImmortalTaldarimACGluescreenDummy +301,ColossusTaldarimACGluescreenDummy +302,WarpPrismTaldarimACGluescreenDummy +303,PhotonCannonTaldarimACGluescreenDummy +304,StukovInfestedCivilianACGluescreenDummy +305,StukovInfestedMarineACGluescreenDummy +306,StukovInfestedSiegeTankACGluescreenDummy +307,StukovInfestedDiamondbackACGluescreenDummy +308,StukovInfestedBansheeACGluescreenDummy +309,SILiberatorACGluescreenDummy +310,StukovInfestedBunkerACGluescreenDummy +311,StukovInfestedMissileTurretACGluescreenDummy +312,StukovBroodQueenACGluescreenDummy +313,ZealotFenixACGluescreenDummy +314,SentryFenixACGluescreenDummy +315,AdeptFenixACGluescreenDummy +316,ImmortalFenixACGluescreenDummy +317,ColossusFenixACGluescreenDummy +318,DisruptorACGluescreenDummy +319,ObserverFenixACGluescreenDummy +320,ScoutACGluescreenDummy +321,CarrierFenixACGluescreenDummy +322,PhotonCannonFenixACGluescreenDummy +323,PrimalZerglingACGluescreenDummy +324,RavasaurACGluescreenDummy +325,PrimalRoachACGluescreenDummy +326,FireRoachACGluescreenDummy +327,PrimalGuardianACGluescreenDummy +328,PrimalHydraliskACGluescreenDummy +329,PrimalMutaliskACGluescreenDummy +330,PrimalImpalerACGluescreenDummy +331,PrimalSwarmHostACGluescreenDummy +332,CreeperHostACGluescreenDummy +333,PrimalUltraliskACGluescreenDummy +334,TyrannozorACGluescreenDummy +335,PrimalWurmACGluescreenDummy +336,HHReaperACGluescreenDummy +337,HHWidowMineACGluescreenDummy +338,HHHellionTankACGluescreenDummy +339,HHWraithACGluescreenDummy +340,HHVikingACGluescreenDummy +341,HHBattlecruiserACGluescreenDummy +342,HHRavenACGluescreenDummy +343,HHBomberPlatformACGluescreenDummy +344,HHMercStarportACGluescreenDummy +345,HHMissileTurretACGluescreenDummy +346,TychusReaperACGluescreenDummy +347,TychusFirebatACGluescreenDummy +348,TychusSpectreACGluescreenDummy +349,TychusMedicACGluescreenDummy +350,TychusMarauderACGluescreenDummy +351,TychusWarhoundACGluescreenDummy +352,TychusHERCACGluescreenDummy +353,TychusGhostACGluescreenDummy +354,TychusSCVAutoTurretACGluescreenDummy +355,ZeratulStalkerACGluescreenDummy +356,ZeratulSentryACGluescreenDummy +357,ZeratulDarkTemplarACGluescreenDummy +358,ZeratulImmortalACGluescreenDummy +359,ZeratulObserverACGluescreenDummy +360,ZeratulDisruptorACGluescreenDummy +361,ZeratulWarpPrismACGluescreenDummy +362,ZeratulPhotonCannonACGluescreenDummy +363,MechaZerglingACGluescreenDummy +364,MechaBanelingACGluescreenDummy +365,MechaHydraliskACGluescreenDummy +366,MechaInfestorACGluescreenDummy +367,MechaCorruptorACGluescreenDummy +368,MechaUltraliskACGluescreenDummy +369,MechaOverseerACGluescreenDummy +370,MechaLurkerACGluescreenDummy +371,MechaBattlecarrierLordACGluescreenDummy +372,MechaSpineCrawlerACGluescreenDummy +373,MechaSporeCrawlerACGluescreenDummy +374,TrooperMengskACGluescreenDummy +375,MedivacMengskACGluescreenDummy +376,BlimpMengskACGluescreenDummy +377,MarauderMengskACGluescreenDummy +378,GhostMengskACGluescreenDummy +379,SiegeTankMengskACGluescreenDummy +380,ThorMengskACGluescreenDummy +381,VikingMengskACGluescreenDummy +382,BattlecruiserMengskACGluescreenDummy +383,BunkerDepotMengskACGluescreenDummy +384,MissileTurretMengskACGluescreenDummy +385,ArtilleryMengskACGluescreenDummy +387,RenegadeLongboltMissileWeapon +388,LoadOutSpray@1 +389,LoadOutSpray@2 +390,LoadOutSpray@3 +391,LoadOutSpray@4 +392,LoadOutSpray@5 +393,LoadOutSpray@6 +394,LoadOutSpray@7 +395,LoadOutSpray@8 +396,LoadOutSpray@9 +397,LoadOutSpray@10 +398,LoadOutSpray@11 +399,LoadOutSpray@12 +400,LoadOutSpray@13 +401,LoadOutSpray@14 +402,NeedleSpinesWeapon +403,CorruptionWeapon +404,InfestedTerransWeapon +405,NeuralParasiteWeapon +406,PointDefenseDroneReleaseWeapon +407,HunterSeekerWeapon +408,MULE +410,ThorAAWeapon +411,PunisherGrenadesLMWeapon +412,VikingFighterWeapon +413,ATALaserBatteryLMWeapon +414,ATSLaserBatteryLMWeapon +415,LongboltMissileWeapon +416,D8ChargeWeapon +417,YamatoWeapon +418,IonCannonsWeapon +419,AcidSalivaWeapon +420,SpineCrawlerWeapon +421,SporeCrawlerWeapon +422,GlaiveWurmWeapon +423,GlaiveWurmM2Weapon +424,GlaiveWurmM3Weapon +425,StalkerWeapon +426,EMP2Weapon +427,BacklashRocketsLMWeapon +428,PhotonCannonWeapon +429,ParasiteSporeWeapon +431,Broodling +432,BroodLordBWeapon +435,AutoTurretReleaseWeapon +436,LarvaReleaseMissile +437,AcidSpinesWeapon +438,FrenzyWeapon +439,ContaminateWeapon +451,BeaconArmy +452,BeaconDefend +453,BeaconAttack +454,BeaconHarass +455,BeaconIdle +456,BeaconAuto +457,BeaconDetect +458,BeaconScout +459,BeaconClaim +460,BeaconExpand +461,BeaconRally +462,BeaconCustom1 +463,BeaconCustom2 +464,BeaconCustom3 +465,BeaconCustom4 +470,LiberatorAG +472,PreviewBunkerUpgraded +473,HellionTank +474,Cyclone +475,WidowMine +476,Liberator +478,Adept +479,Disruptor +480,SwarmHostMP +481,Viper +482,ShieldBattery +483,HighTemplarSkinPreview +484,MothershipCore +485,Viking +498,InhibitorZoneSmall +499,InhibitorZoneMedium +500,InhibitorZoneLarge +501,AccelerationZoneSmall +502,AccelerationZoneMedium +503,AccelerationZoneLarge +504,AccelerationZoneFlyingSmall +505,AccelerationZoneFlyingMedium +506,AccelerationZoneFlyingLarge +507,InhibitorZoneFlyingSmall +508,InhibitorZoneFlyingMedium +509,InhibitorZoneFlyingLarge +510,AssimilatorRich +511,RichVespeneGeyser +512,ExtractorRich +513,RavagerCorrosiveBileMissile +514,RavagerWeaponMissile +515,RenegadeMissileTurret +516,Rocks2x2NonConjoined +517,FungalGrowthMissile +518,NeuralParasiteTentacleMissile +519,Beacon_Protoss +520,Beacon_ProtossSmall +521,Beacon_Terran +522,Beacon_TerranSmall +523,Beacon_Zerg +524,Beacon_ZergSmall +525,Lyote +526,CarrionBird +527,KarakMale +528,KarakFemale +529,UrsadakFemaleExotic +530,UrsadakMale +531,UrsadakFemale +532,UrsadakCalf +533,UrsadakMaleExotic +534,UtilityBot +535,CommentatorBot1 +536,CommentatorBot2 +537,CommentatorBot3 +538,CommentatorBot4 +539,Scantipede +540,Dog +541,Sheep +542,Cow +543,InfestedTerransEggPlacement +544,InfestorTerransWeapon +545,MineralField +546,MineralField450 +547,MineralField750 +548,MineralFieldOpaque +549,MineralFieldOpaque900 +550,VespeneGeyser +551,SpacePlatformGeyser +552,DestructibleSearchlight +553,DestructibleBullhornLights +554,DestructibleStreetlight +555,DestructibleSpacePlatformSign +556,DestructibleStoreFrontCityProps +557,DestructibleBillboardTall +558,DestructibleBillboardScrollingText +559,DestructibleSpacePlatformBarrier +560,DestructibleSignsDirectional +561,DestructibleSignsConstruction +562,DestructibleSignsFunny +563,DestructibleSignsIcons +564,DestructibleSignsWarning +565,DestructibleGarage +566,DestructibleGarageLarge +567,DestructibleTrafficSignal +568,TrafficSignal +569,BraxisAlphaDestructible1x1 +570,BraxisAlphaDestructible2x2 +571,DestructibleDebris4x4 +572,DestructibleDebris6x6 +573,DestructibleRock2x4Vertical +574,DestructibleRock2x4Horizontal +575,DestructibleRock2x6Vertical +576,DestructibleRock2x6Horizontal +577,DestructibleRock4x4 +578,DestructibleRock6x6 +579,DestructibleRampDiagonalHugeULBR +580,DestructibleRampDiagonalHugeBLUR +581,DestructibleRampVerticalHuge +582,DestructibleRampHorizontalHuge +583,DestructibleDebrisRampDiagonalHugeULBR +584,DestructibleDebrisRampDiagonalHugeBLUR +585,WarpPrismSkinPreview +586,SiegeTankSkinPreview +587,ThorAP +588,ThorAALance +589,LiberatorSkinPreview +590,OverlordGenerateCreepKeybind +591,MengskStatueAlone +592,MengskStatue +593,WolfStatue +594,GlobeStatue +595,Weapon +596,GlaiveWurmBounceWeapon +597,BroodLordWeapon +598,BroodLordAWeapon +599,CreepBlocker1x1 +600,PermanentCreepBlocker1x1 +601,PathingBlocker1x1 +602,PathingBlocker2x2 +603,AutoTestAttackTargetGround +604,AutoTestAttackTargetAir +605,AutoTestAttacker +606,HelperEmitterSelectionArrow +607,MultiKillObject +608,ShapeGolfball +609,ShapeCone +610,ShapeCube +611,ShapeCylinder +612,ShapeDodecahedron +613,ShapeIcosahedron +614,ShapeOctahedron +615,ShapePyramid +616,ShapeRoundedCube +617,ShapeSphere +618,ShapeTetrahedron +619,ShapeThickTorus +620,ShapeThinTorus +621,ShapeTorus +622,Shape4PointStar +623,Shape5PointStar +624,Shape6PointStar +625,Shape8PointStar +626,ShapeArrowPointer +627,ShapeBowl +628,ShapeBox +629,ShapeCapsule +630,ShapeCrescentMoon +631,ShapeDecahedron +632,ShapeDiamond +633,ShapeFootball +634,ShapeGemstone +635,ShapeHeart +636,ShapeJack +637,ShapePlusSign +638,ShapeShamrock +639,ShapeSpade +640,ShapeTube +641,ShapeEgg +642,ShapeYenSign +643,ShapeX +644,ShapeWatermelon +645,ShapeWonSign +646,ShapeTennisball +647,ShapeStrawberry +648,ShapeSmileyFace +649,ShapeSoccerball +650,ShapeRainbow +651,ShapeSadFace +652,ShapePoundSign +653,ShapePear +654,ShapePineapple +655,ShapeOrange +656,ShapePeanut +657,ShapeO +658,ShapeLemon +659,ShapeMoneyBag +660,ShapeHorseshoe +661,ShapeHockeyStick +662,ShapeHockeyPuck +663,ShapeHand +664,ShapeGolfClub +665,ShapeGrape +666,ShapeEuroSign +667,ShapeDollarSign +668,ShapeBasketball +669,ShapeCarrot +670,ShapeCherry +671,ShapeBaseball +672,ShapeBaseballBat +673,ShapeBanana +674,ShapeApple +675,ShapeCashLarge +676,ShapeCashMedium +677,ShapeCashSmall +678,ShapeFootballColored +679,ShapeLemonSmall +680,ShapeOrangeSmall +681,ShapeTreasureChestOpen +682,ShapeTreasureChestClosed +683,ShapeWatermelonSmall +684,UnbuildableRocksDestructible +685,UnbuildableBricksDestructible +686,UnbuildablePlatesDestructible +687,Debris2x2NonConjoined +688,EnemyPathingBlocker1x1 +689,EnemyPathingBlocker2x2 +690,EnemyPathingBlocker4x4 +691,EnemyPathingBlocker8x8 +692,EnemyPathingBlocker16x16 +693,ScopeTest +694,SentryACGluescreenDummy +695,StukovInfestedTrooperACGluescreenDummy +711,CollapsibleTerranTowerDebris +712,DebrisRampLeft +713,DebrisRampRight +717,LocustMP +718,CollapsibleRockTowerDebris +719,NydusCanalCreeper +720,SwarmHostBurrowedMP +721,WarHound +722,WidowMineBurrowed +723,ExtendingBridgeNEWide8Out +724,ExtendingBridgeNEWide8 +725,ExtendingBridgeNWWide8Out +726,ExtendingBridgeNWWide8 +727,ExtendingBridgeNEWide10Out +728,ExtendingBridgeNEWide10 +729,ExtendingBridgeNWWide10Out +730,ExtendingBridgeNWWide10 +731,ExtendingBridgeNEWide12Out +732,ExtendingBridgeNEWide12 +733,ExtendingBridgeNWWide12Out +734,ExtendingBridgeNWWide12 +736,CollapsibleRockTowerDebrisRampRight +737,CollapsibleRockTowerDebrisRampLeft +738,XelNaga_Caverns_DoorE +739,XelNaga_Caverns_DoorEOpened +740,XelNaga_Caverns_DoorN +741,XelNaga_Caverns_DoorNE +742,XelNaga_Caverns_DoorNEOpened +743,XelNaga_Caverns_DoorNOpened +744,XelNaga_Caverns_DoorNW +745,XelNaga_Caverns_DoorNWOpened +746,XelNaga_Caverns_DoorS +747,XelNaga_Caverns_DoorSE +748,XelNaga_Caverns_DoorSEOpened +749,XelNaga_Caverns_DoorSOpened +750,XelNaga_Caverns_DoorSW +751,XelNaga_Caverns_DoorSWOpened +752,XelNaga_Caverns_DoorW +753,XelNaga_Caverns_DoorWOpened +754,XelNaga_Caverns_Floating_BridgeNE8Out +755,XelNaga_Caverns_Floating_BridgeNE8 +756,XelNaga_Caverns_Floating_BridgeNW8Out +757,XelNaga_Caverns_Floating_BridgeNW8 +758,XelNaga_Caverns_Floating_BridgeNE10Out +759,XelNaga_Caverns_Floating_BridgeNE10 +760,XelNaga_Caverns_Floating_BridgeNW10Out +761,XelNaga_Caverns_Floating_BridgeNW10 +762,XelNaga_Caverns_Floating_BridgeNE12Out +763,XelNaga_Caverns_Floating_BridgeNE12 +764,XelNaga_Caverns_Floating_BridgeNW12Out +765,XelNaga_Caverns_Floating_BridgeNW12 +766,XelNaga_Caverns_Floating_BridgeH8Out +767,XelNaga_Caverns_Floating_BridgeH8 +768,XelNaga_Caverns_Floating_BridgeV8Out +769,XelNaga_Caverns_Floating_BridgeV8 +770,XelNaga_Caverns_Floating_BridgeH10Out +771,XelNaga_Caverns_Floating_BridgeH10 +772,XelNaga_Caverns_Floating_BridgeV10Out +773,XelNaga_Caverns_Floating_BridgeV10 +774,XelNaga_Caverns_Floating_BridgeH12Out +775,XelNaga_Caverns_Floating_BridgeH12 +776,XelNaga_Caverns_Floating_BridgeV12Out +777,XelNaga_Caverns_Floating_BridgeV12 +780,CollapsibleTerranTowerPushUnitRampLeft +781,CollapsibleTerranTowerPushUnitRampRight +784,CollapsibleRockTowerPushUnit +785,CollapsibleTerranTowerPushUnit +786,CollapsibleRockTowerPushUnitRampRight +787,CollapsibleRockTowerPushUnitRampLeft +788,DigesterCreepSprayTargetUnit +789,DigesterCreepSprayUnit +790,NydusCanalAttackerWeapon +791,ViperConsumeStructureWeapon +794,ResourceBlocker +795,TempestWeapon +796,YoinkMissile +800,YoinkVikingAirMissile +802,YoinkVikingGroundMissile +804,YoinkSiegeTankMissile +806,WarHoundWeapon +808,EyeStalkWeapon +811,WidowMineWeapon +812,WidowMineAirWeapon +813,MothershipCoreWeaponWeapon +814,TornadoMissileWeapon +815,TornadoMissileDummyWeapon +816,TalonsMissileWeapon +817,CreepTumorMissile +818,LocustMPEggAMissileWeapon +819,LocustMPEggBMissileWeapon +820,LocustMPWeapon +822,RepulsorCannonWeapon +826,CollapsibleRockTowerDiagonal +827,CollapsibleTerranTowerDiagonal +828,CollapsibleTerranTowerRampLeft +829,CollapsibleTerranTowerRampRight +830,Ice2x2NonConjoined +831,IceProtossCrates +832,ProtossCrates +833,TowerMine +834,PickupPalletGas +835,PickupPalletMinerals +836,PickupScrapSalvage1x1 +837,PickupScrapSalvage2x2 +838,PickupScrapSalvage3x3 +839,RoughTerrain +840,UnbuildableBricksSmallUnit +841,UnbuildablePlatesSmallUnit +842,UnbuildablePlatesUnit +843,UnbuildableRocksSmallUnit +844,XelNagaHealingShrine +845,InvisibleTargetDummy +846,ProtossVespeneGeyser +847,CollapsibleRockTower +848,CollapsibleTerranTower +849,ThornLizard +850,CleaningBot +851,DestructibleRock6x6Weak +852,ProtossSnakeSegmentDemo +853,PhysicsCapsule +854,PhysicsCube +855,PhysicsCylinder +856,PhysicsKnot +857,PhysicsL +858,PhysicsPrimitives +859,PhysicsSphere +860,PhysicsStar +861,CreepBlocker4x4 +862,DestructibleCityDebris2x4Vertical +863,DestructibleCityDebris2x4Horizontal +864,DestructibleCityDebris2x6Vertical +865,DestructibleCityDebris2x6Horizontal +866,DestructibleCityDebris4x4 +867,DestructibleCityDebris6x6 +868,DestructibleCityDebrisHugeDiagonalBLUR +869,DestructibleCityDebrisHugeDiagonalULBR +870,TestZerg +871,PathingBlockerRadius1 +872,DestructibleRockEx12x4Vertical +873,DestructibleRockEx12x4Horizontal +874,DestructibleRockEx12x6Vertical +875,DestructibleRockEx12x6Horizontal +876,DestructibleRockEx14x4 +877,DestructibleRockEx16x6 +878,DestructibleRockEx1DiagonalHugeULBR +879,DestructibleRockEx1DiagonalHugeBLUR +880,DestructibleRockEx1VerticalHuge +881,DestructibleRockEx1HorizontalHuge +882,DestructibleIce2x4Vertical +883,DestructibleIce2x4Horizontal +884,DestructibleIce2x6Vertical +885,DestructibleIce2x6Horizontal +886,DestructibleIce4x4 +887,DestructibleIce6x6 +888,DestructibleIceDiagonalHugeULBR +889,DestructibleIceDiagonalHugeBLUR +890,DestructibleIceVerticalHuge +891,DestructibleIceHorizontalHuge +892,DesertPlanetSearchlight +893,DesertPlanetStreetlight +894,UnbuildableBricksUnit +895,UnbuildableRocksUnit +896,ZerusDestructibleArch +897,Artosilope +898,Anteplott +899,LabBot +900,Crabeetle +901,CollapsibleRockTowerRampRight +902,CollapsibleRockTowerRampLeft +903,LabMineralField +904,LabMineralField750 +919,CollapsibleRockTowerDebrisRampLeftGreen +920,CollapsibleRockTowerDebrisRampRightGreen +921,SnowRefinery_Terran_ExtendingBridgeNEShort8Out +922,SnowRefinery_Terran_ExtendingBridgeNEShort8 +923,SnowRefinery_Terran_ExtendingBridgeNWShort8Out +924,SnowRefinery_Terran_ExtendingBridgeNWShort8 +929,Tarsonis_DoorN +930,Tarsonis_DoorNLowered +931,Tarsonis_DoorNE +932,Tarsonis_DoorNELowered +933,Tarsonis_DoorE +934,Tarsonis_DoorELowered +935,Tarsonis_DoorNW +936,Tarsonis_DoorNWLowered +937,CompoundMansion_DoorN +938,CompoundMansion_DoorNLowered +939,CompoundMansion_DoorNE +940,CompoundMansion_DoorNELowered +941,CompoundMansion_DoorE +942,CompoundMansion_DoorELowered +943,CompoundMansion_DoorNW +944,CompoundMansion_DoorNWLowered +946,LocustMPFlying +947,AiurLightBridgeNE8Out +948,AiurLightBridgeNE8 +949,AiurLightBridgeNE10Out +950,AiurLightBridgeNE10 +951,AiurLightBridgeNE12Out +952,AiurLightBridgeNE12 +953,AiurLightBridgeNW8Out +954,AiurLightBridgeNW8 +955,AiurLightBridgeNW10Out +956,AiurLightBridgeNW10 +957,AiurLightBridgeNW12Out +958,AiurLightBridgeNW12 +959,AiurTempleBridgeNE8Out +961,AiurTempleBridgeNE10Out +963,AiurTempleBridgeNE12Out +965,AiurTempleBridgeNW8Out +967,AiurTempleBridgeNW10Out +969,AiurTempleBridgeNW12Out +971,ShakurasLightBridgeNE8Out +972,ShakurasLightBridgeNE8 +973,ShakurasLightBridgeNE10Out +974,ShakurasLightBridgeNE10 +975,ShakurasLightBridgeNE12Out +976,ShakurasLightBridgeNE12 +977,ShakurasLightBridgeNW8Out +978,ShakurasLightBridgeNW8 +979,ShakurasLightBridgeNW10Out +980,ShakurasLightBridgeNW10 +981,ShakurasLightBridgeNW12Out +982,ShakurasLightBridgeNW12 +983,VoidMPImmortalReviveCorpse +984,GuardianCocoonMP +985,GuardianMP +986,DevourerCocoonMP +987,DevourerMP +988,DefilerMPBurrowed +989,DefilerMP +990,OracleStasisTrap +991,DisruptorPhased +992,AiurLightBridgeAbandonedNE8Out +993,AiurLightBridgeAbandonedNE8 +994,AiurLightBridgeAbandonedNE10Out +995,AiurLightBridgeAbandonedNE10 +996,AiurLightBridgeAbandonedNE12Out +997,AiurLightBridgeAbandonedNE12 +998,AiurLightBridgeAbandonedNW8Out +999,AiurLightBridgeAbandonedNW8 +1000,AiurLightBridgeAbandonedNW10Out +1001,AiurLightBridgeAbandonedNW10 +1002,AiurLightBridgeAbandonedNW12Out +1003,AiurLightBridgeAbandonedNW12 +1004,CollapsiblePurifierTowerDebris +1005,PortCity_Bridge_UnitNE8Out +1006,PortCity_Bridge_UnitNE8 +1007,PortCity_Bridge_UnitSE8Out +1008,PortCity_Bridge_UnitSE8 +1009,PortCity_Bridge_UnitNW8Out +1010,PortCity_Bridge_UnitNW8 +1011,PortCity_Bridge_UnitSW8Out +1012,PortCity_Bridge_UnitSW8 +1013,PortCity_Bridge_UnitNE10Out +1014,PortCity_Bridge_UnitNE10 +1015,PortCity_Bridge_UnitSE10Out +1016,PortCity_Bridge_UnitSE10 +1017,PortCity_Bridge_UnitNW10Out +1018,PortCity_Bridge_UnitNW10 +1019,PortCity_Bridge_UnitSW10Out +1020,PortCity_Bridge_UnitSW10 +1021,PortCity_Bridge_UnitNE12Out +1022,PortCity_Bridge_UnitNE12 +1023,PortCity_Bridge_UnitSE12Out +1024,PortCity_Bridge_UnitSE12 +1025,PortCity_Bridge_UnitNW12Out +1026,PortCity_Bridge_UnitNW12 +1027,PortCity_Bridge_UnitSW12Out +1028,PortCity_Bridge_UnitSW12 +1029,PortCity_Bridge_UnitN8Out +1030,PortCity_Bridge_UnitN8 +1031,PortCity_Bridge_UnitS8Out +1032,PortCity_Bridge_UnitS8 +1033,PortCity_Bridge_UnitE8Out +1034,PortCity_Bridge_UnitE8 +1035,PortCity_Bridge_UnitW8Out +1036,PortCity_Bridge_UnitW8 +1037,PortCity_Bridge_UnitN10Out +1038,PortCity_Bridge_UnitN10 +1039,PortCity_Bridge_UnitS10Out +1040,PortCity_Bridge_UnitS10 +1041,PortCity_Bridge_UnitE10Out +1042,PortCity_Bridge_UnitE10 +1043,PortCity_Bridge_UnitW10Out +1044,PortCity_Bridge_UnitW10 +1045,PortCity_Bridge_UnitN12Out +1046,PortCity_Bridge_UnitN12 +1047,PortCity_Bridge_UnitS12Out +1048,PortCity_Bridge_UnitS12 +1049,PortCity_Bridge_UnitE12Out +1050,PortCity_Bridge_UnitE12 +1051,PortCity_Bridge_UnitW12Out +1052,PortCity_Bridge_UnitW12 +1053,PurifierRichMineralField +1054,PurifierRichMineralField750 +1055,CollapsibleRockTowerPushUnitRampLeftGreen +1056,CollapsibleRockTowerPushUnitRampRightGreen +1071,CollapsiblePurifierTowerPushUnit +1073,LocustMPPrecursor +1074,ReleaseInterceptorsBeacon +1075,AdeptPhaseShift +1076,HydraliskImpaleMissile +1077,CycloneMissileLargeAir +1078,CycloneMissile +1079,CycloneMissileLarge +1080,OracleWeapon +1081,TempestWeaponGround +1082,ScoutMPAirWeaponLeft +1083,ScoutMPAirWeaponRight +1084,ArbiterMPWeaponMissile +1085,GuardianMPWeapon +1086,DevourerMPWeaponMissile +1087,DefilerMPDarkSwarmWeapon +1088,QueenMPEnsnareMissile +1089,QueenMPSpawnBroodlingsMissile +1090,LightningBombWeapon +1091,HERCPlacement +1092,GrappleWeapon +1095,CausticSprayMissile +1096,ParasiticBombMissile +1097,ParasiticBombDummy +1098,AdeptWeapon +1099,AdeptUpgradeWeapon +1100,LiberatorMissile +1101,LiberatorDamageMissile +1102,LiberatorAGMissile +1103,KD8Charge +1104,KD8ChargeWeapon +1106,SlaynElementalGrabWeapon +1107,SlaynElementalGrabAirUnit +1108,SlaynElementalGrabGroundUnit +1109,SlaynElementalWeapon +1114,CollapsibleRockTowerRampLeftGreen +1115,CollapsibleRockTowerRampRightGreen +1116,DestructibleExpeditionGate6x6 +1117,DestructibleZergInfestation3x3 +1118,HERC +1119,Moopy +1120,Replicant +1121,SeekerMissile +1122,AiurTempleBridgeDestructibleNE8Out +1123,AiurTempleBridgeDestructibleNE10Out +1124,AiurTempleBridgeDestructibleNE12Out +1125,AiurTempleBridgeDestructibleNW8Out +1126,AiurTempleBridgeDestructibleNW10Out +1127,AiurTempleBridgeDestructibleNW12Out +1128,AiurTempleBridgeDestructibleSW8Out +1129,AiurTempleBridgeDestructibleSW10Out +1130,AiurTempleBridgeDestructibleSW12Out +1131,AiurTempleBridgeDestructibleSE8Out +1132,AiurTempleBridgeDestructibleSE10Out +1133,AiurTempleBridgeDestructibleSE12Out +1135,FlyoverUnit +1136,CorsairMP +1137,ScoutMP +1139,ArbiterMP +1140,ScourgeMP +1141,DefilerMPPlagueWeapon +1142,QueenMP +1143,XelNagaDestructibleRampBlocker6S +1144,XelNagaDestructibleRampBlocker6SE +1145,XelNagaDestructibleRampBlocker6E +1146,XelNagaDestructibleRampBlocker6NE +1147,XelNagaDestructibleRampBlocker6N +1148,XelNagaDestructibleRampBlocker6NW +1149,XelNagaDestructibleRampBlocker6W +1150,XelNagaDestructibleRampBlocker6SW +1151,XelNagaDestructibleRampBlocker8S +1152,XelNagaDestructibleRampBlocker8SE +1153,XelNagaDestructibleRampBlocker8E +1154,XelNagaDestructibleRampBlocker8NE +1155,XelNagaDestructibleRampBlocker8N +1156,XelNagaDestructibleRampBlocker8NW +1157,XelNagaDestructibleRampBlocker8W +1158,XelNagaDestructibleRampBlocker8SW +1159,XelNagaDestructibleBlocker6S +1160,XelNagaDestructibleBlocker6SE +1161,XelNagaDestructibleBlocker6E +1162,XelNagaDestructibleBlocker6NE +1163,XelNagaDestructibleBlocker6N +1164,XelNagaDestructibleBlocker6NW +1165,XelNagaDestructibleBlocker6W +1166,XelNagaDestructibleBlocker6SW +1167,XelNagaDestructibleBlocker8S +1168,XelNagaDestructibleBlocker8SE +1169,XelNagaDestructibleBlocker8E +1170,XelNagaDestructibleBlocker8NE +1171,XelNagaDestructibleBlocker8N +1172,XelNagaDestructibleBlocker8NW +1173,XelNagaDestructibleBlocker8W +1174,XelNagaDestructibleBlocker8SW +1175,ReptileCrate +1176,SlaynSwarmHostSpawnFlyer +1177,SlaynElemental +1178,PurifierVespeneGeyser +1179,ShakurasVespeneGeyser +1180,CollapsiblePurifierTowerDiagonal +1181,CreepOnlyBlocker4x4 +1182,BattleStationMineralField +1183,BattleStationMineralField750 +1184,PurifierMineralField +1185,PurifierMineralField750 +1186,Beacon_Nova +1187,Beacon_NovaSmall +1188,Ursula +1189,Elsecaro_Colonist_Hut +1190,SnowGlazeStarterMP +1191,PylonOvercharged +1192,ObserverSiegeMode +1193,RavenRepairDrone +1195,ParasiticBombRelayDummy +1196,BypassArmorDrone +1197,AdeptPiercingWeapon +1198,HighTemplarWeaponMissile +1199,CycloneMissileLargeAirAlternative +1200,RavenScramblerMissile +1201,RavenRepairDroneReleaseWeapon +1202,RavenShredderMissileWeapon +1203,InfestedAcidSpinesWeapon +1204,InfestorEnsnareAttackMissile +1205,SNARE_PLACEHOLDER +1208,CorrosiveParasiteWeapon diff --git a/sc2reader/data/__init__.py b/sc2reader/data/__init__.py index dd5ff207..d97e4885 100755 --- a/sc2reader/data/__init__.py +++ b/sc2reader/data/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import json import pkgutil @@ -18,7 +15,7 @@ ABIL_LOOKUP = dict() for entry in ( - pkgutil.get_data("sc2reader.data", "ability_lookup.csv").decode("utf8").split("\n") + pkgutil.get_data("sc2reader.data", "ability_lookup.csv").decode("utf8").splitlines() ): if not entry: continue @@ -27,7 +24,7 @@ UNIT_LOOKUP = dict() for entry in ( - pkgutil.get_data("sc2reader.data", "unit_lookup.csv").decode("utf8").split("\n") + pkgutil.get_data("sc2reader.data", "unit_lookup.csv").decode("utf8").splitlines() ): if not entry: continue @@ -41,7 +38,7 @@ train_commands = json.loads(command_data) -class Unit(object): +class Unit: """Represents an in-game unit.""" def __init__(self, unit_id): @@ -84,7 +81,7 @@ def __init__(self, unit_id): #: The unique in-game id for this unit. The id can sometimes be zero because #: TargetUnitCommandEvents will create a new unit with id zero when a unit - #: behind the fog of war is targetted. + #: behind the fog of war is targeted. self.id = unit_id #: A reference to the unit type this unit is current in. @@ -151,46 +148,46 @@ def title(self): @property def type(self): - """ The internal type id of the current unit type of this unit. None if no type is assigned""" + """The internal type id of the current unit type of this unit. None if no type is assigned""" return self._type_class.id if self._type_class else None @property def race(self): - """ The race of this unit. One of Terran, Protoss, Zerg, Neutral, or None""" + """The race of this unit. One of Terran, Protoss, Zerg, Neutral, or None""" return self._type_class.race if self._type_class else None @property def minerals(self): - """ The mineral cost of the unit. None if no type is assigned""" + """The mineral cost of the unit. None if no type is assigned""" return self._type_class.minerals if self._type_class else None @property def vespene(self): - """ The vespene cost of the unit. None if no type is assigned""" + """The vespene cost of the unit. None if no type is assigned""" return self._type_class.vespene if self._type_class else None @property def supply(self): - """ The supply used by this unit. Negative for supply providers. None if no type is assigned """ + """The supply used by this unit. Negative for supply providers. None if no type is assigned""" return self._type_class.supply if self._type_class else None @property def is_worker(self): - """ Boolean flagging units as worker units. SCV, MULE, Drone, Probe """ + """Boolean flagging units as worker units. SCV, MULE, Drone, Probe""" return self._type_class.is_worker if self._type_class else False @property def is_building(self): - """ Boolean flagging units as buildings. """ + """Boolean flagging units as buildings.""" return self._type_class.is_building if self._type_class else False @property def is_army(self): - """ Boolean flagging units as army units. """ + """Boolean flagging units as army units.""" return self._type_class.is_army if self._type_class else False def __str__(self): - return "{0} [{1:X}]".format(self.name, self.id) + return f"{self.name} [{self.id:X}]" def __cmp__(self, other): return cmp(self.id, other.id) @@ -220,8 +217,8 @@ def __repr__(self): return str(self) -class UnitType(object): - """ Represents an in game unit type """ +class UnitType: + """Represents an in game unit type""" def __init__( self, @@ -271,8 +268,8 @@ def __init__( self.is_army = is_army -class Ability(object): - """ Represents an in-game ability """ +class Ability: + """Represents an in-game ability""" def __init__( self, id, name=None, title=None, is_build=False, build_time=0, build_unit=None @@ -297,15 +294,15 @@ def __init__( @loggable -class Build(object): +class Build: """ :param build_id: The build number identifying this dataset. - The datapack for a particualr group of builds. Maps internal integer ids + The datapack for a particular group of builds. Maps internal integer ids to :class:`Unit` and :class:`Ability` types. Also contains builder methods for creating new units and changing their types. - All build data is valid for standard games only. For arcade maps milage + All build data is valid for standard games only. For arcade maps mileage may vary. """ @@ -342,9 +339,7 @@ def change_type(self, unit, new_type, frame): 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 - ) + f"Unable to change type of {unit} to {new_type} [frame {frame}]; unit type not found in build {self.id}" ) def add_ability( @@ -402,9 +397,9 @@ def add_unit_type( def load_build(expansion, version): build = Build(version) - unit_file = "{0}/{1}_units.csv".format(expansion, version) + unit_file = f"{expansion}/{version}_units.csv" for entry in ( - pkgutil.get_data("sc2reader.data", unit_file).decode("utf8").split("\n") + pkgutil.get_data("sc2reader.data", unit_file).decode("utf8").splitlines() ): if not entry: continue @@ -421,10 +416,10 @@ def load_build(expansion, version): build.add_unit_type(**values) - abil_file = "{0}/{1}_abilities.csv".format(expansion, version) + abil_file = f"{expansion}/{version}_abilities.csv" 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") + pkgutil.get_data("sc2reader.data", abil_file).decode("utf8").splitlines() ): if not entry: continue @@ -469,7 +464,20 @@ def load_build(expansion, version): # Load LotV Data lotv_builds = dict() -for version in ("base", "44401", "47185", "48258", "53644", "54724", "59587", "70154"): +for version in ( + "base", + "44401", + "47185", + "48258", + "53644", + "54724", + "59587", + "70154", + "76114", + "77379", + "80949", + "89720", +): lotv_builds[version] = load_build("LotV", version) datapacks = builds = {"WoL": wol_builds, "HotS": hots_builds, "LotV": lotv_builds} diff --git a/sc2reader/data/ability_lookup.csv b/sc2reader/data/ability_lookup.csv index 09b69177..4eaee18f 100755 --- a/sc2reader/data/ability_lookup.csv +++ b/sc2reader/data/ability_lookup.csv @@ -127,7 +127,7 @@ FactoryLand,LandFactory,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, FactoryLiftOff,LiftFactory,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, FactoryReactorMorph,BuildReactorFactory,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, FactoryTechLabMorph,BuildTechLabFactory,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -FactoryTechLabResearch,ResearchSiegeTech,ResearchInfernalPreIgniter,Research250mmStrikeCannons,ResearchTransformationServos,ResearchDrillingClaws,,ResearchSmartServos,,ResearchCycloneRapidFireLaunchers,,,,,,,,,,,,,,,,,,,,,,CancelFactoryTechLabResearch, +FactoryTechLabResearch,ResearchSiegeTech,ResearchInfernalPreIgniter,Research250mmStrikeCannons,ResearchTransformationServos,ResearchDrillingClaws,,ResearchSmartServos,,ResearchCycloneRapidFireLaunchers,ResearchCycloneLockOnDamageUpgrade,,,,,,,,,,,,,,,,,,,,,CancelFactoryTechLabResearch, FactoryTrain,,BuildSiegeTank,,,BuildThor,BuildHellion,BuildBattleHellion,TrainCyclone,,,,,BuildWarHound,,,,,,,,,,,,BuildWidowMine,,,,,,CancelFactoryTrain, Feedback,Feedback,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, FighterMode,FighterMode,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -137,10 +137,10 @@ ForceField,ForceField,CancelForceField,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ForgeResearch,UpgradeGroundWeapons1,UpgradeGroundWeapons2,UpgradeGroundWeapons3,UpgradeGroundArmor1,UpgradeGroundArmor2,UpgradeGroundArmor3,UpgradeShields1,UpgradeShields2,UpgradesShields3,,,,,,,,,,,,,,,,,,,,,,CancelForgeResearch, Frenzy,Frenzy,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, FungalGrowth,FungalGrowth,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -FusionCoreResearch,ResearchWeaponRefit,ResearchBehemothReactor,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelFusionCoreResearch, +FusionCoreResearch,ResearchWeaponRefit,ResearchBehemothReactor,ResearchMedivacIncreaseSpeedBoost,,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelFusionCoreResearch, GatewayTrain,TrainZealot,TrainStalker,,TrainHighTemplar,TrainDarkTemplar,TrainSentry,TrainAdept,,,,,,,,,,,,,,,,,,,,,,,,CancelGatewayTrain, GenerateCreep,GenerateCreep,StopGenerateCreep,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GhostAcademyResearch,ResearchPersonalCloaking,ResearchMoebiusReactor,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelGhostAcademyResearch, +GhostAcademyResearch,ResearchPersonalCloaking,ResearchMoebiusReactor,ResearchEnhancedShockwaves,,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelGhostAcademyResearch, GhostCloak,CloakGhost,DecloakGhost,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, GhostHoldFire,HoldFireGhost,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, GhostWeaponsFree,GWeaponsFreeGhost,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -161,7 +161,7 @@ HangarQueue5,CancelLast,CancelSlot,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, HerdInteract,Herd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, HoldFire,Stop,HoldFire,Cheer,Dance,,,,,,,,,,,,,,,,,,,,,,,,,,,, HydraliskDenResearch,ResearchEvolveGroovedSpines,ResearchEvolveMuscularAugments,EvolveGroovedSpines,EvolveMuscularAugments,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelHydraliskDenResearch, -InfestationPitResearch,,,EvolvePathogenGlands,EvolveNeuralParasite,EvolveEnduringLocusts,,,,,,,,,,,,,,,,,,,,,,,,,,CancelInfestationPitResearch, +InfestationPitResearch,,,EvolvePathogenGlands,EvolveNeuralParasite,EvolveEnduringLocusts,ResearchMicrobialShroud,,,,,,,,,,,,,,,,,,,,,,,,,CancelInfestationPitResearch, InfestedTerrans,SpawnInfestedTerran,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, InfestedTerransLayEgg,SpawnInfestedTerran,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, InvulnerabilityShield,InvulnerabilityShield,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -329,7 +329,7 @@ TrainQueen,TrainQueen,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelTrainQueen, Transfusion,QueenTransfusion,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, TransportMode,TransportMode,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, TwilightCouncilResearch,ResearchCharge,ResearchBlink,ResearchAdeptPiercingAttack,,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelTwilightCouncilResearch, -UltraliskCavernResearch,,,EvolveChitinousPlating,EvolveBurrowCharge,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelUltraliskCavernResearch, +UltraliskCavernResearch,ResearchAnabolicSynthesis,,EvolveChitinousPlating,EvolveBurrowCharge,,,,,,,,,,,,,,,,,,,,,,,,,,,CancelUltraliskCavernResearch, UltraliskWeaponCooldown,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, Unsiege,TankMode,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, UpgradeToGreaterSpire,MorphToGreaterSpire,CancelMorphToGreaterSpire,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -518,7 +518,7 @@ LocustMPFlyingSwoopAttack,LocustMPFlyingSwoopAttack MorphToTransportOverlord,MorphToTransportOverlord,Cancel BypassArmor,BypassArmor BypassArmorDroneCU,BypassArmorDroneCU -ChannelSnipe,ChannelSnipe +ChannelSnipe,ChannelSnipe,Cancel LockOnAir,LockOnAir PurificationNovaTargetted,PurificationNovaTargetted SnowRefinery_Terran_ExtendingBridgeNEShort8Out,SnowRefinery_Terran_ExtendingBridgeNEShort8Out @@ -861,3 +861,9 @@ NexusMassRecall,NexusMassRecall OverlordSingleTransport,Load,,UnloadAt ParasiticBombRelayDodge,ParasiticBombRelayDodge ViperParasiticBombRelay,ViperParasiticBombRelay +BattlecruiserStop,Stop +BattlecruiserAttack,BattlecruiserAttack +BattlecruiserMove,Move,Patrol,HoldPos +AmorphousArmorcloud,AmorphousArmorcloud +BatteryOvercharge,BatteryOvercharge +MorphToBaneling,MorphToBaneling,Cancel diff --git a/sc2reader/data/attributes.json b/sc2reader/data/attributes.json index e0d541bf..7922c1cc 100644 --- a/sc2reader/data/attributes.json +++ b/sc2reader/data/attributes.json @@ -292,6 +292,8 @@ "Team", { "T1": "Team 1", + "T10": "Team 10", + "T11": "Team 11", "T2": "Team 2", "T3": "Team 3", "T4": "Team 4", @@ -299,9 +301,7 @@ "T6": "Team 6", "T7": "Team 7", "T8": "Team 8", - "T9": "Team 9", - "T10": "Team 10", - "T11": "Team 11" + "T9": "Team 9" } ], "3000": [ @@ -317,8 +317,12 @@ "3001": [ "Race", { + "InfT": "Infested Terran", + "PZrg": "Primal Zerg", "Prot": "Protoss", "RAND": "Random", + "TerH": "Terran Horner", + "TerT": "Terran Tychus", "Terr": "Terran", "Zerg": "Zerg" } @@ -705,9 +709,10 @@ "Arta": "Artanis", "Deha": "Dehaka", "Feni": "Fenix", - "Horn": "Horner", + "Horn": "Han & Horner", "Kara": "Karax", "Kerr": "Kerrigan", + "Meng": "Mengsk", "Nova": "Nova", "Rayn": "Raynor", "Stet": "Stetmann", @@ -758,6 +763,142 @@ "90": "90 Minutes" } ], + "3016": [ + "Commander Mastery Level", + { + "0": "0", + "1": "1", + "10": "10", + "100": "100", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "31": "31", + "32": "32", + "33": "33", + "34": "34", + "35": "35", + "36": "36", + "37": "37", + "38": "38", + "39": "39", + "4": "4", + "40": "40", + "41": "41", + "42": "42", + "43": "43", + "44": "44", + "45": "45", + "46": "46", + "47": "47", + "48": "48", + "49": "49", + "5": "5", + "50": "50", + "51": "51", + "52": "52", + "53": "53", + "54": "54", + "55": "55", + "56": "56", + "57": "57", + "58": "58", + "59": "59", + "6": "6", + "60": "60", + "61": "61", + "62": "62", + "63": "63", + "64": "64", + "65": "65", + "66": "66", + "67": "67", + "68": "68", + "69": "69", + "7": "7", + "70": "70", + "71": "71", + "72": "72", + "73": "73", + "74": "74", + "75": "75", + "76": "76", + "77": "77", + "78": "78", + "79": "79", + "8": "8", + "80": "80", + "81": "81", + "82": "82", + "83": "83", + "84": "84", + "85": "85", + "86": "86", + "87": "87", + "88": "88", + "89": "89", + "9": "9", + "90": "90", + "91": "91", + "92": "92", + "93": "93", + "94": "94", + "95": "95", + "96": "96", + "97": "97", + "98": "98", + "99": "99" + } + ], + "3017": [ + "Commander Mastery Tier", + { + "0": "0", + "1": "1", + "10": "10", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "3019": [ + "Random Commander", + { + "no": "Not Random", + "yes": "Random" + } + ], + "3020": [ + "Commander Is Trial", + { + "no": "Not Trial", + "yes": "Trial" + } + ], "3102": [ "AI Build", { @@ -1569,7 +1710,251 @@ "no": "Not Ready", "yes": "Ready" } + ], + "5000": [ + "Commander Mastery Talent", + { + "0": "0", + "1": "1", + "10": "10", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "5001": [ + "Commander Mastery Talent", + { + "0": "0", + "1": "1", + "10": "10", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "5002": [ + "Commander Mastery Talent", + { + "0": "0", + "1": "1", + "10": "10", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "5003": [ + "Commander Mastery Talent", + { + "0": "0", + "1": "1", + "10": "10", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "5004": [ + "Commander Mastery Talent", + { + "0": "0", + "1": "1", + "10": "10", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "5005": [ + "Commander Mastery Talent", + { + "0": "0", + "1": "1", + "10": "10", + "11": "11", + "12": "12", + "13": "13", + "14": "14", + "15": "15", + "16": "16", + "17": "17", + "18": "18", + "19": "19", + "2": "2", + "20": "20", + "21": "21", + "22": "22", + "23": "23", + "24": "24", + "25": "25", + "26": "26", + "27": "27", + "28": "28", + "29": "29", + "3": "3", + "30": "30", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9" + } + ], + "5100": [ + "Brutal Plus Level", + { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6" + } + ], + "5200": [ + "Brutal Plus Is Retry", + { + "no": "No", + "yes": "Yes" + } + ], + "5300": [ + "Commander Prestige Level", + { + "0": "0", + "1": "1", + "2": "2", + "3": "3" + } ] }, - "decisions": "(dp0\nc__builtin__\nfrozenset\np1\n((lp2\nS'Hard'\np3\naVHarder\np4\na(I3004\nS'Hard'\np5\ntp6\natp7\nRp8\ng4\nsg1\n((lp9\n(I2001\nS'1v1'\np10\ntp11\naS'1 v 1'\np12\naV1v1\np13\natp14\nRp15\ng13\nsg1\n((lp16\n(I3104\nS'AB04'\np17\ntp18\naS'Agressive Push'\np19\naVAggressive Push\np20\natp21\nRp22\ng20\nsg1\n((lp23\nS'Agressive Push'\np24\naVAggressive Push\np25\na(I3199\nS'AB04'\np26\ntp27\natp28\nRp29\ng25\nsg1\n((lp30\nV6v6\np31\naS'6 v 6'\np32\na(I2001\nS'6v6'\np33\ntp34\natp35\nRp36\ng31\nsg1\n((lp37\nS'Agressive Push'\np38\na(I3102\nS'AB04'\np39\ntp40\naVAggressive Push\np41\natp42\nRp43\ng41\nsg1\n((lp44\nI2003\naVTeams2v2\np45\naS'Team'\np46\natp47\nRp48\ng45\nsg1\n((lp49\nVLadder\np50\naS'Automated Match Making'\np51\na(I3009\nS'Amm'\np52\ntp53\natp54\nRp55\ng50\nsg1\n((lp56\n(I2001\nS'5v5'\np57\ntp58\naS'5 v 5'\np59\naV5v5\np60\natp61\nRp62\ng60\nsg1\n((lp63\nVFree For All Teams\np64\naS'Free For All Archon'\np65\na(I2000\nVFFAT\np66\ntp67\natp68\nRp69\ng65\nsg1\n((lp70\nI3141\naVAI Build (Terran)\np71\naS'AI Build'\np72\natp73\nRp74\ng71\nsg1\n((lp75\n(I2001\nS'3v3'\np76\ntp77\naS'3 v 3'\np78\naV3v3\np79\natp80\nRp81\ng79\nsg1\n((lp82\n(I3168\nS'AB04'\np83\ntp84\naS'Agressive Push'\np85\naVAggressive Push\np86\natp87\nRp88\ng86\nsg1\n((lp89\n(I3200\nS'AB04'\np90\ntp91\naS'Agressive Push'\np92\naVAggressive Push\np93\natp94\nRp95\ng93\nsg1\n((lp96\nVAI Build (Protoss)\np97\naI3174\naS'AI Build'\np98\natp99\nRp100\ng97\nsg1\n((lp101\nS'Very Hard'\np102\naVElite\np103\na(I3004\nS'VyHd'\np104\ntp105\natp106\nRp107\ng103\nsg1\n((lp108\nS'Agressive Push'\np109\naVAggressive Push\np110\na(I3167\nS'AB04'\np111\ntp112\natp113\nRp114\ng110\nsg1\n((lp115\nI3204\naS'AI Build'\np116\naVAI Build (Zerg)\np117\natp118\nRp119\ng117\nsg1\n((lp120\nVInsane\np121\naS'Cheater 3 (Insane)'\np122\na(I3004\nS'Insa'\np123\ntp124\natp125\nRp126\ng121\nsg1\n((lp127\n(I3007\nS'Watc'\np128\ntp129\naS'Observer'\np130\naS'Watcher'\np131\natp132\nRp133\ng130\nsg1\n((lp134\nI3205\naVAI Build (Zerg)\np135\naS'AI Build'\np136\natp137\nRp138\ng135\nsg1\n((lp139\nVTeams5v5\np140\naS'Team'\np141\naI2007\natp142\nRp143\ng140\nsg1\n((lp144\nI3171\naVAI Build (Protoss)\np145\naS'AI Build'\np146\natp147\nRp148\ng145\nsg1\n((lp149\nS'Unknown'\np150\naI2012\naS'Team'\np151\natp152\nRp153\ng151\nsg1\n((lp154\nI3173\naS'AI Build'\np155\naVAI Build (Protoss)\np156\natp157\nRp158\ng156\nsg1\n((lp159\nVAI Build (Terran)\np160\naI3142\naS'AI Build'\np161\natp162\nRp163\ng160\nsg1\n((lp164\nI3172\naVAI Build (Protoss)\np165\naS'AI Build'\np166\natp167\nRp168\ng165\nsg1\n((lp169\nS'Level 1 (Very Easy)'\np170\na(I3004\nS'VyEy'\np171\ntp172\naVVery Easy\np173\natp174\nRp175\ng173\nsg1\n((lp176\nS'Agressive Push'\np177\naVAggressive Push\np178\na(I3135\nS'AB04'\np179\ntp180\natp181\nRp182\ng178\nsg1\n((lp183\nV2v2\np184\naS'2 v 2'\np185\na(I2001\nS'2v2'\np186\ntp187\natp188\nRp189\ng184\nsg1\n((lp190\nS'Agressive Push'\np191\na(I3166\nS'AB04'\np192\ntp193\naVAggressive Push\np194\natp195\nRp196\ng194\nsg1\n((lp197\nVTeamsFFA\np198\naI2006\naS'Team'\np199\natp200\nRp201\ng198\nsg1\n((lp202\nS'AI Build'\np203\naVAI Build (Terran)\np204\naI3143\natp205\nRp206\ng204\nsg1\n((lp207\nVTeams7v7\np208\naI2011\naS'Team'\np209\natp210\nRp211\ng208\nsg1\n((lp212\nVMedium\np213\naS'Level 3 (Medium)'\np214\na(I3004\nS'Medi'\np215\ntp216\natp217\nRp218\ng213\nsg1\n((lp219\nI3140\naS'AI Build'\np220\naVAI Build (Terran)\np221\natp222\nRp223\ng221\nsg1\n((lp224\nVTeams4v4\np225\naI2005\naS'Team'\np226\natp227\nRp228\ng225\nsg1\n((lp229\nS'Agressive Push'\np230\na(I3198\nS'AB04'\np231\ntp232\naVAggressive Push\np233\natp234\nRp235\ng233\nsg1\n((lp236\n(I3136\nS'AB04'\np237\ntp238\naS'Agressive Push'\np239\naVAggressive Push\np240\natp241\nRp242\ng240\nsg1\n((lp243\nI2008\naVTeams6v6\np244\naS'Team'\np245\natp246\nRp247\ng244\nsg1\n((lp248\nS'Agressive Push'\np249\naVAggressive Push\np250\na(I3103\nS'AB04'\np251\ntp252\natp253\nRp254\ng250\nsg1\n((lp255\nV4v4\np256\naS'4 v 4'\np257\na(I2001\nS'4v4'\np258\ntp259\natp260\nRp261\ng256\nsg1\n((lp262\nS'Agressive Push'\np263\na(I3134\nS'AB04'\np264\ntp265\naVAggressive Push\np266\natp267\nRp268\ng266\nsg1\n((lp269\nVTeams1v1\np270\naI2002\naS'Team'\np271\natp272\nRp273\ng270\nsg1\n((lp274\nI3139\naS'AI Build'\np275\naVAI Build (Terran)\np276\natp277\nRp278\ng276\nsg1\n((lp279\nS'AI Build'\np280\naVAI Build (Zerg)\np281\naI3207\natp282\nRp283\ng281\nsg1\n((lp284\n(I2001\nS'FFA'\np285\ntp286\naS'Free For All'\np287\naVFFA\np288\natp289\nRp290\ng288\nsg1\n((lp291\nVAI Build (Zerg)\np292\naI3206\naS'AI Build'\np293\natp294\nRp295\ng292\nsg1\n((lp296\nVTeams3v3\np297\naI2004\naS'Team'\np298\natp299\nRp300\ng297\nsg1\n((lp301\nVAI Build (Protoss)\np302\naS'AI Build'\np303\naI3175\natp304\nRp305\ng302\nsg1\n((lp306\nS'Level 2 (Easy)'\np307\na(I3004\nS'Easy'\np308\ntp309\naVEasy\np310\natp311\nRp312\ng310\nsg1\n((lp313\nI3203\naS'AI Build'\np314\naVAI Build (Zerg)\np315\natp316\nRp317\ng315\ns." + "decisions": "(dp0\nc__builtin__\nfrozenset\np1\n((lp2\nS'Hard'\np3\naVHarder\np4\na(I3004\nS'Hard'\np5\ntp6\natp7\nRp8\ng4\nsg1\n((lp9\n(I2001\nS'1v1'\np10\ntp11\naS'1 v 1'\np12\naV1v1\np13\natp14\nRp15\ng13\nsg1\n((lp16\nVUnknown\np17\naI2003\naVTeams2v2\np18\natp19\nRp20\ng18\nsg1\n((lp21\nI3009\naVGame Mode\np22\nag17\natp23\nRp24\ng22\nsg1\n((lp25\n(I3104\nS'AB04'\np26\ntp27\naS'Agressive Push'\np28\naVAggressive Push\np29\natp30\nRp31\ng29\nsg1\n((lp32\nI2001\nag17\naVTeams\np33\natp34\nRp35\ng33\nsg1\n((lp36\ng17\naVTeams4v4\np37\naI2005\natp38\nRp39\ng37\nsg1\n((lp40\nS'Agressive Push'\np41\naVAggressive Push\np42\na(I3199\nS'AB04'\np43\ntp44\natp45\nRp46\ng42\nsg1\n((lp47\nV6v6\np48\naS'6 v 6'\np49\na(I2001\nS'6v6'\np50\ntp51\natp52\nRp53\ng48\nsg1\n((lp54\nS'Agressive Push'\np55\na(I3102\nS'AB04'\np56\ntp57\naVAggressive Push\np58\natp59\nRp60\ng58\nsg1\n((lp61\nI4001\nag17\naVUsing Custom Observer UI\np62\natp63\nRp64\ng62\nsg1\n((lp65\nI2003\naVTeams2v2\np66\naS'Team'\np67\natp68\nRp69\ng66\nsg1\n((lp70\nVLadder\np71\naS'Automated Match Making'\np72\na(I3009\nS'Amm'\np73\ntp74\natp75\nRp76\ng71\nsg1\n((lp77\n(I2001\nS'5v5'\np78\ntp79\naS'5 v 5'\np80\naV5v5\np81\natp82\nRp83\ng81\nsg1\n((lp84\nVFree For All Teams\np85\naS'Free For All Archon'\np86\na(I2000\nVFFAT\np87\ntp88\natp89\nRp90\ng86\nsg1\n((lp91\ng17\naVGame Duration\np92\naI3015\natp93\nRp94\ng92\nsg1\n((lp95\nI2008\nag17\naVTeams6v6\np96\natp97\nRp98\ng96\nsg1\n((lp99\nVHorner\np100\na(I3013\nVHorn\np101\ntp102\naS'Han & Horner'\np103\natp104\nRp105\ng103\nsg1\n((lp106\nI3141\naVAI Build (Terran)\np107\naS'AI Build'\np108\natp109\nRp110\ng107\nsg1\n((lp111\nI3008\nag17\naVObserver Type\np112\natp113\nRp114\ng112\nsg1\n((lp115\n(I2001\nS'3v3'\np116\ntp117\naS'3 v 3'\np118\naV3v3\np119\natp120\nRp121\ng119\nsg1\n((lp122\n(I3168\nS'AB04'\np123\ntp124\naS'Agressive Push'\np125\naVAggressive Push\np126\natp127\nRp128\ng126\nsg1\n((lp129\n(I3200\nS'AB04'\np130\ntp131\naS'Agressive Push'\np132\naVAggressive Push\np133\natp134\nRp135\ng133\nsg1\n((lp136\nVAI Build (Protoss)\np137\naI3174\naS'AI Build'\np138\natp139\nRp140\ng137\nsg1\n((lp141\nS'Very Hard'\np142\naVElite\np143\na(I3004\nS'VyHd'\np144\ntp145\natp146\nRp147\ng143\nsg1\n((lp148\nS'Agressive Push'\np149\naVAggressive Push\np150\na(I3167\nS'AB04'\np151\ntp152\natp153\nRp154\ng150\nsg1\n((lp155\nVTeams5v5\np156\nag17\naI2007\natp157\nRp158\ng156\nsg1\n((lp159\nI3204\naS'AI Build'\np160\naVAI Build (Zerg)\np161\natp162\nRp163\ng161\nsg1\n((lp164\nVInsane\np165\naS'Cheater 3 (Insane)'\np166\na(I3004\nS'Insa'\np167\ntp168\natp169\nRp170\ng165\nsg1\n((lp171\n(I3007\nS'Watc'\np172\ntp173\naS'Observer'\np174\naS'Watcher'\np175\natp176\nRp177\ng174\nsg1\n((lp178\nI3205\naVAI Build (Zerg)\np179\naS'AI Build'\np180\natp181\nRp182\ng179\nsg1\n((lp183\nVTeams5v5\np184\naS'Team'\np185\naI2007\natp186\nRp187\ng184\nsg1\n((lp188\nI3171\naVAI Build (Protoss)\np189\naS'AI Build'\np190\natp191\nRp192\ng189\nsg1\n((lp193\nI1000\naVRules\np194\nag17\natp195\nRp196\ng194\nsg1\n((lp197\nS'Unknown'\np198\naI2012\naS'Team'\np199\natp200\nRp201\ng199\nsg1\n((lp202\ng17\naVTandem Leader Slot\np203\naI3012\natp204\nRp205\ng203\nsg1\n((lp206\ng17\naI3011\naVPlayer Logo Index\np207\natp208\nRp209\ng207\nsg1\n((lp210\nI1001\naVPremade Game\np211\nag17\natp212\nRp213\ng211\nsg1\n((lp214\nI3173\naS'AI Build'\np215\naVAI Build (Protoss)\np216\natp217\nRp218\ng216\nsg1\n((lp219\nVAI Build (Terran)\np220\naI3142\naS'AI Build'\np221\natp222\nRp223\ng220\nsg1\n((lp224\nI3172\naVAI Build (Protoss)\np225\naS'AI Build'\np226\natp227\nRp228\ng225\nsg1\n((lp229\nS'Level 1 (Very Easy)'\np230\na(I3004\nS'VyEy'\np231\ntp232\naVVery Easy\np233\natp234\nRp235\ng233\nsg1\n((lp236\nS'Agressive Push'\np237\naVAggressive Push\np238\na(I3135\nS'AB04'\np239\ntp240\natp241\nRp242\ng238\nsg1\n((lp243\nV2v2\np244\naS'2 v 2'\np245\na(I2001\nS'2v2'\np246\ntp247\natp248\nRp249\ng244\nsg1\n((lp250\nS'Agressive Push'\np251\na(I3166\nS'AB04'\np252\ntp253\naVAggressive Push\np254\natp255\nRp256\ng254\nsg1\n((lp257\nVTeamsFFA\np258\naI2006\naS'Team'\np259\natp260\nRp261\ng258\nsg1\n((lp262\nS'AI Build'\np263\naVAI Build (Terran)\np264\naI3143\natp265\nRp266\ng264\nsg1\n((lp267\nVTeams7v7\np268\naI2011\naS'Team'\np269\natp270\nRp271\ng268\nsg1\n((lp272\nVTeams3v3\np273\nag17\naI2004\natp274\nRp275\ng273\nsg1\n((lp276\nI3000\naVGame Speed\np277\nag17\natp278\nRp279\ng277\nsg1\n((lp280\nVMedium\np281\naS'Level 3 (Medium)'\np282\na(I3004\nS'Medi'\np283\ntp284\natp285\nRp286\ng281\nsg1\n((lp287\nI3140\naS'AI Build'\np288\naVAI Build (Terran)\np289\natp290\nRp291\ng289\nsg1\n((lp292\nVTeams4v4\np293\naI2005\naS'Team'\np294\natp295\nRp296\ng293\nsg1\n((lp297\nS'Agressive Push'\np298\na(I3198\nS'AB04'\np299\ntp300\naVAggressive Push\np301\natp302\nRp303\ng301\nsg1\n((lp304\nVLocked Alliances\np305\nag17\naI3010\natp306\nRp307\ng305\nsg1\n((lp308\nVReady\np309\nag17\naI4005\natp310\nRp311\ng309\nsg1\n((lp312\ng17\naVLobby Delay\np313\naI3006\natp314\nRp315\ng313\nsg1\n((lp316\n(I3136\nS'AB04'\np317\ntp318\naS'Agressive Push'\np319\naVAggressive Push\np320\natp321\nRp322\ng320\nsg1\n((lp323\nI2008\naVTeams6v6\np324\naS'Team'\np325\natp326\nRp327\ng324\nsg1\n((lp328\nS'Agressive Push'\np329\naVAggressive Push\np330\na(I3103\nS'AB04'\np331\ntp332\natp333\nRp334\ng330\nsg1\n((lp335\nV4v4\np336\naS'4 v 4'\np337\na(I2001\nS'4v4'\np338\ntp339\natp340\nRp341\ng336\nsg1\n((lp342\nS'Agressive Push'\np343\na(I3134\nS'AB04'\np344\ntp345\naVAggressive Push\np346\natp347\nRp348\ng346\nsg1\n((lp349\nVTeams1v1\np350\naI2002\naS'Team'\np351\natp352\nRp353\ng350\nsg1\n((lp354\nVParticipant Role\np355\nag17\naI3007\natp356\nRp357\ng355\nsg1\n((lp358\nI3139\naS'AI Build'\np359\naVAI Build (Terran)\np360\natp361\nRp362\ng360\nsg1\n((lp363\nI4000\nag17\naVGame Privacy\np364\natp365\nRp366\ng364\nsg1\n((lp367\nS'AI Build'\np368\naVAI Build (Zerg)\np369\naI3207\natp370\nRp371\ng369\nsg1\n((lp372\n(I2001\nS'FFA'\np373\ntp374\naS'Free For All'\np375\naVFFA\np376\natp377\nRp378\ng376\nsg1\n((lp379\nVAI Build (Zerg)\np380\naI3206\naS'AI Build'\np381\natp382\nRp383\ng380\nsg1\n((lp384\nVTeams3v3\np385\naI2004\naS'Team'\np386\natp387\nRp388\ng385\nsg1\n((lp389\nVAI Build (Protoss)\np390\naS'AI Build'\np391\naI3175\natp392\nRp393\ng390\nsg1\n((lp394\nS'Level 2 (Easy)'\np395\na(I3004\nS'Easy'\np396\ntp397\naVEasy\np398\natp399\nRp400\ng398\nsg1\n((lp401\nVTeams1v1\np402\nag17\naI2002\natp403\nRp404\ng402\nsg1\n((lp405\nI3203\naS'AI Build'\np406\naVAI Build (Zerg)\np407\natp408\nRp409\ng407\ns." } diff --git a/sc2reader/data/create_lookup.py b/sc2reader/data/create_lookup.py index 2ffa2560..427d1e27 100755 --- a/sc2reader/data/create_lookup.py +++ b/sc2reader/data/create_lookup.py @@ -1,10 +1,10 @@ abilities = dict() -with open("hots_abilities.csv", "r") as f: +with open("hots_abilities.csv") as f: for line in f: num, ability = line.strip("\r\n ").split(",") abilities[ability] = [""] * 32 -with open("command_lookup.csv", "r") as f: +with open("command_lookup.csv") as f: for line in f: ability, commands = line.strip("\r\n ").split("|", 1) abilities[ability] = commands.split("|") diff --git a/sc2reader/data/unit_info.json b/sc2reader/data/unit_info.json index 81cf8669..15d02ff6 100644 --- a/sc2reader/data/unit_info.json +++ b/sc2reader/data/unit_info.json @@ -224,9 +224,15 @@ }, "ravager": { "is_army": true, - "minerals": 75, - "vespene": 25, - "supply": 2 + "minerals": 100, + "vespene": 100, + "supply": 3 + }, + "ravagercocoon": { + "is_army": true, + "minerals": 100, + "vespene": 100, + "supply": 3 }, "roach": { "is_army": true, diff --git a/sc2reader/data/unit_lookup.csv b/sc2reader/data/unit_lookup.csv index 7327ef0d..2284dd18 100755 --- a/sc2reader/data/unit_lookup.csv +++ b/sc2reader/data/unit_lookup.csv @@ -1020,3 +1020,65 @@ ZeratulStalkerACGluescreenDummy,ZeratulStalkerACGluescreenDummy ZeratulPhotonCannonACGluescreenDummy,ZeratulPhotonCannonACGluescreenDummy Viking,Viking TychusReaperACGluescreenDummy,TychusReaperACGluescreenDummy +MechaZerglingACGluescreenDummy,MechaZerglingACGluescreenDummy +MechaBanelingACGluescreenDummy,MechaBanelingACGluescreenDummy +MechaHydraliskACGluescreenDummy,MechaHydraliskACGluescreenDummy +MechaInfestorACGluescreenDummy,MechaInfestorACGluescreenDummy +MechaCorruptorACGluescreenDummy,MechaCorruptorACGluescreenDummy +MechaUltraliskACGluescreenDummy,MechaUltraliskACGluescreenDummy +MechaOverseerACGluescreenDummy,MechaOverseerACGluescreenDummy +MechaLurkerACGluescreenDummy,MechaLurkerACGluescreenDummy +MechaBattlecarrierLordACGluescreenDummy,MechaBattlecarrierLordACGluescreenDummy +MechaSpineCrawlerACGluescreenDummy,MechaSpineCrawlerACGluescreenDummy +MechaSporeCrawlerACGluescreenDummy,MechaSporeCrawlerACGluescreenDummy +PreviewBunkerUpgraded,PreviewBunkerUpgraded +AssimilatorRich,AssimilatorRich +ExtractorRich,ExtractorRich +InhibitorZoneSmall,InhibitorZoneSmall +InhibitorZoneMedium,InhibitorZoneMedium +InhibitorZoneLarge,InhibitorZoneLarge +RefineryRich,RefineryRich +MineralField450,MineralField450 +MineralFieldOpaque,MineralFieldOpaque +MineralFieldOpaque900,MineralFieldOpaque900 +CollapsibleRockTowerDebrisRampLeftGreen,CollapsibleRockTowerDebrisRampLeftGreen +CollapsibleRockTowerDebrisRampRightGreen,CollapsibleRockTowerDebrisRampRightGreen +CollapsibleRockTowerPushUnitRampLeftGreen,CollapsibleRockTowerPushUnitRampLeftGreen +CollapsibleRockTowerPushUnitRampRightGreen,CollapsibleRockTowerPushUnitRampRightGreen +CollapsibleRockTowerRampLeftGreen,CollapsibleRockTowerRampLeftGreen +CollapsibleRockTowerRampRightGreen,CollapsibleRockTowerRampRightGreen +TrooperMengskACGluescreenDummy,TrooperMengskACGluescreenDummy +MedivacMengskACGluescreenDummy,MedivacMengskACGluescreenDummy +BlimpMengskACGluescreenDummy,BlimpMengskACGluescreenDummy +MarauderMengskACGluescreenDummy,MarauderMengskACGluescreenDummy +GhostMengskACGluescreenDummy,GhostMengskACGluescreenDummy +SiegeTankMengskACGluescreenDummy,SiegeTankMengskACGluescreenDummy +ThorMengskACGluescreenDummy,ThorMengskACGluescreenDummy +VikingMengskACGluescreenDummy,VikingMengskACGluescreenDummy +BattlecruiserMengskACGluescreenDummy,BattlecruiserMengskACGluescreenDummy +BunkerDepotMengskACGluescreenDummy,BunkerDepotMengskACGluescreenDummy +MissileTurretMengskACGluescreenDummy,MissileTurretMengskACGluescreenDummy +ArtilleryMengskACGluescreenDummy,ArtilleryMengskACGluescreenDummy +AccelerationZoneSmall,AccelerationZoneSmall +AccelerationZoneMedium,AccelerationZoneMedium +AccelerationZoneLarge,AccelerationZoneLarge +LoadOutSpray@1,LoadOutSpray@1 +LoadOutSpray@2,LoadOutSpray@2 +LoadOutSpray@3,LoadOutSpray@3 +LoadOutSpray@4,LoadOutSpray@4 +LoadOutSpray@5,LoadOutSpray@5 +LoadOutSpray@6,LoadOutSpray@6 +LoadOutSpray@7,LoadOutSpray@7 +LoadOutSpray@8,LoadOutSpray@8 +LoadOutSpray@9,LoadOutSpray@9 +LoadOutSpray@10,LoadOutSpray@10 +LoadOutSpray@11,LoadOutSpray@11 +LoadOutSpray@12,LoadOutSpray@12 +LoadOutSpray@13,LoadOutSpray@13 +LoadOutSpray@14,LoadOutSpray@14 +AccelerationZoneFlyingSmall,AccelerationZoneFlyingSmall +AccelerationZoneFlyingMedium,AccelerationZoneFlyingMedium +AccelerationZoneFlyingLarge,AccelerationZoneFlyingLarge +InhibitorZoneFlyingSmall,InhibitorZoneFlyingSmall +InhibitorZoneFlyingMedium,InhibitorZoneFlyingMedium +InhibitorZoneFlyingLarge,InhibitorZoneFlyingLarge diff --git a/sc2reader/decoders.py b/sc2reader/decoders.py index 78372e8f..ed726356 100644 --- a/sc2reader/decoders.py +++ b/sc2reader/decoders.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from io import BytesIO import struct @@ -12,7 +9,7 @@ from ordereddict import OrderedDict -class ByteDecoder(object): +class ByteDecoder: """ :param contents: The string or file-like object to decode :param endian: Either > or <. Indicates the endian the bytes are stored in. @@ -20,7 +17,7 @@ class ByteDecoder(object): Used to unpack parse byte aligned files. """ - #: The Bytes object used internaly for reading from the + #: The Bytes object used internally for reading from the #: decoder contents. cStringIO is faster than managing our #: own string access in python. For PyPy installations a #: managed string implementation might be faster. @@ -31,7 +28,8 @@ class ByteDecoder(object): _contents = "" def __init__(self, contents, endian): - """ Accepts both strings and files implementing ``read()`` and + """ + Accepts both strings and files implementing ``read()`` and decodes them in the specified endian format. """ if hasattr(contents, "read"): @@ -66,40 +64,58 @@ def __init__(self, contents, endian): self._unpack_bytes = lambda bytes: bytes if self.endian == ">" else bytes[::-1] def done(self): - """ Returns true when all bytes have been decoded """ + """ + Returns true when all bytes have been decoded + """ return self.tell() == self.length def read_range(self, start, end): - """ Returns the raw byte string from the indicated address range """ + """ + Returns the raw byte string from the indicated address range + """ return self._contents[start:end] def peek(self, count): - """ Returns the raw byte string for the next ``count`` bytes """ + """ + Returns the raw byte string for the next ``count`` bytes + """ start = self.tell() return self._contents[start : start + count] def read_uint8(self): - """ Returns the next byte as an unsigned integer """ + """ + Returns the next byte as an unsigned integer + """ return ord(self.read(1)) def read_uint16(self): - """ Returns the next two bytes as an unsigned integer """ + """ + Returns the next two bytes as an unsigned integer + """ return self._unpack_short(self.read(2))[0] def read_uint32(self): - """ Returns the next four bytes as an unsigned integer """ + """ + Returns the next four bytes as an unsigned integer + """ return self._unpack_int(self.read(4))[0] def read_uint64(self): - """ Returns the next eight bytes as an unsigned integer """ + """ + Returns the next eight bytes as an unsigned integer + """ return self._unpack_longlong(self.read(8))[0] def read_bytes(self, count): - """ Returns the next ``count`` bytes as a byte string """ + """ + Returns the next ``count`` bytes as a byte string + """ return self._unpack_bytes(self.read(count)) def read_uint(self, count): - """ Returns the next ``count`` bytes as an unsigned integer """ + """ + Returns the next ``count`` bytes as an unsigned integer + """ unpack = struct.Struct(str(self.endian + "B" * count)).unpack uint = 0 for byte in unpack(self.read(count)): @@ -107,11 +123,15 @@ def read_uint(self, count): return uint def read_string(self, count, encoding="utf8"): - """ Read a string in given encoding (default utf8) that is ``count`` bytes long """ + """ + 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"): - """ Read a NULL byte terminated character string decoded with given encoding (default utf8). Ignores endian. """ + """ + Read a NULL byte terminated character string decoded with given encoding (default utf8). Ignores endian. + """ cstring = BytesIO() while True: c = self.read(1) @@ -121,7 +141,7 @@ def read_cstring(self, encoding="utf8"): cstring.write(c) -class BitPackedDecoder(object): +class BitPackedDecoder: """ :param contents: The string of file-like object to decode @@ -170,16 +190,22 @@ def __init__(self, contents): self.read_bool = functools.partial(self.read_bits, 1) def done(self): - """ Returns true when all bytes in the buffer have been used""" + """ + Returns true when all bytes in the buffer have been used + """ return self.tell() == self.length def byte_align(self): - """ Moves cursor to the beginning of the next byte """ + """ + Moves cursor to the beginning of the next byte + """ self._next_byte = None self._bit_shift = 0 def read_uint8(self): - """ Returns the next 8 bits as an unsigned integer """ + """ + Returns the next 8 bits as an unsigned integer + """ data = ord(self._buffer.read(1)) if self._bit_shift != 0: @@ -192,7 +218,9 @@ def read_uint8(self): return data def read_uint16(self): - """ Returns the next 16 bits as an unsigned integer """ + """ + Returns the next 16 bits as an unsigned integer + """ data = self._buffer.read_uint16() if self._bit_shift != 0: @@ -206,7 +234,9 @@ def read_uint16(self): return data def read_uint32(self): - """ Returns the next 32 bits as an unsigned integer """ + """ + Returns the next 32 bits as an unsigned integer + """ data = self._buffer.read_uint32() if self._bit_shift != 0: @@ -220,7 +250,9 @@ def read_uint32(self): return data def read_uint64(self): - """ Returns the next 64 bits as an unsigned integer """ + """Returns + the next 64 bits as an unsigned integer + """ data = self._buffer.read_uint64() if self._bit_shift != 0: @@ -234,7 +266,9 @@ def read_uint64(self): return data def read_vint(self): - """ Reads a signed integer of variable length """ + """ + Reads a signed integer of variable length + """ byte = ord(self._buffer.read(1)) negative = byte & 0x01 result = (byte & 0x7F) >> 1 @@ -246,26 +280,32 @@ def read_vint(self): return -result if negative else result def read_aligned_bytes(self, count): - """ Skips to the beginning of the next byte and returns the next ``count`` bytes as a byte string """ + """ + Skips to the beginning of the next byte and returns the next ``count`` bytes as a byte string + """ self.byte_align() return self._buffer.read_bytes(count) 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) """ + """ + 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) def read_bytes(self, count): - """ Returns the next ``count*8`` bits as a byte string """ + """ + Returns the next ``count*8`` bits as a byte string + """ data = self._buffer.read_bytes(count) if self._bit_shift != 0: 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): + for next_byte in struct.unpack("B" * count, data): temp_buffer.write( - struct.pack(str("B"), prev_byte & hi_mask | next_byte & lo_mask) + struct.pack("B", prev_byte & hi_mask | next_byte & lo_mask) ) prev_byte = next_byte @@ -276,7 +316,9 @@ def read_bytes(self, count): return data def read_bits(self, count): - """ Returns the next ``count`` bits as an unsigned integer """ + """Returns + the next ``count`` bits as an unsigned integer + """ result = 0 bits = count bit_shift = self._bit_shift @@ -312,7 +354,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("B" * bytes, self._read(bytes)): bits -= 8 result |= byte << bits @@ -325,7 +367,9 @@ def read_bits(self, count): return result def read_frames(self): - """ Reads a frame count as an unsigned integer """ + """ + Reads a frame count as an unsigned integer + """ byte = self.read_uint8() time, additional_bytes = byte >> 2, byte & 0x03 if additional_bytes == 0: @@ -338,8 +382,9 @@ def read_frames(self): return time << 24 | self.read_uint16() << 8 | self.read_uint8() def read_struct(self, datatype=None): - """ Reads a nested data structure. If the type is not specified the - first byte is used as the type identifier. + """ + Reads a nested data structure. If the type is not specified + the first byte is used as the type identifier. """ self.byte_align() datatype = ord(self._buffer.read(1)) if datatype is None else datatype @@ -365,9 +410,7 @@ 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 = {self.read_vint(): self.read_struct() for i in range(entries)} elif datatype == 0x06: # u8 data = ord(self._buffer.read(1)) @@ -382,6 +425,6 @@ def read_struct(self, datatype=None): data = self.read_vint() else: - raise TypeError("Unknown Data Structure: '{0}'".format(datatype)) + raise TypeError(f"Unknown Data Structure: '{datatype}'") return data diff --git a/sc2reader/engine/__init__.py b/sc2reader/engine/__init__.py index b3b8ad87..e76bf41b 100644 --- a/sc2reader/engine/__init__.py +++ b/sc2reader/engine/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import sys from sc2reader.engine.engine import GameEngine from sc2reader.engine.events import PluginExit diff --git a/sc2reader/engine/engine.py b/sc2reader/engine/engine.py index e091fa3a..96b7f759 100644 --- a/sc2reader/engine/engine.py +++ b/sc2reader/engine/engine.py @@ -1,116 +1,121 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import collections -from sc2reader.events import * +from sc2reader.events import ( + CommandEvent, + ControlGroupEvent, + Event, + GameEvent, + MessageEvent, + TrackerEvent, +) from sc2reader.engine.events import InitGameEvent, EndGameEvent, PluginExit -class GameEngine(object): - """ GameEngine Specification - -------------------------- +class GameEngine: + """ + GameEngine Specification + -------------------------- - The game engine runs through all the events for a given replay in - chronological order. For each event, event handlers from registered - plugins are executed in order of plugin registration from most general - to most specific. + The game engine runs through all the events for a given replay in + chronological order. For each event, event handlers from registered + plugins are executed in order of plugin registration from most general + to most specific. - Example Usage:: + Example Usage:: - class Plugin1(): - def handleCommandEvent(self, event, replay): - pass + class Plugin1(): + def handleCommandEvent(self, event, replay): + pass - class Plugin2(): - def handleEvent(self, event, replay): - pass + class Plugin2(): + def handleEvent(self, event, replay): + pass - def handleTargetUnitCommandEvent(self, event, replay): - pass + def handleTargetUnitCommandEvent(self, event, replay): + pass - ... + ... - engine = GameEngine(plugins=[Plugin1(), Plugin2()], **options) - engine.register_plugins(Plugin3(), Plugin(4)) - engine.reigster_plugin(Plugin(5)) - engine.run(replay) + engine = GameEngine(plugins=[Plugin1(), Plugin2()], **options) + engine.register_plugins(Plugin3(), Plugin(4)) + engine.reigster_plugin(Plugin(5)) + engine.run(replay) - Calls functions in the following order for a ``TargetUnitCommandEvent``:: + Calls functions in the following order for a ``TargetUnitCommandEvent``:: - Plugin1.handleCommandEvent(event, replay) - Plugin2.handleEvent(event, replay) - Plugin2.handleTargetUnitCommandEvent(event, replay) + Plugin1.handleCommandEvent(event, replay) + Plugin2.handleEvent(event, replay) + Plugin2.handleTargetUnitCommandEvent(event, replay) - Plugin Specification - ------------------------- + Plugin Specification + ------------------------- - Plugins can opt in to handle events with methods in the format: + Plugins can opt in to handle events with methods in the format: - def handleEventName(self, event, replay) + def handleEventName(self, event, replay) - In addition to handling specific event types, plugins can also - handle events more generally by handling built-in parent classes - from the list below:: + In addition to handling specific event types, plugins can also + handle events more generally by handling built-in parent classes + from the list below:: - * handleEvent - called for every single event of all types - * handleMessageEvent - called for events in replay.message.events - * handleGameEvent - called for events in replay.game.events - * handleTrackerEvent - called for events in replay.tracker.events - * handleCommandEvent - called for all types of command events - * handleControlGroupEvent - called for all player control group events + * handleEvent - called for every single event of all types + * handleMessageEvent - called for events in replay.message.events + * handleGameEvent - called for events in replay.game.events + * handleTrackerEvent - called for events in replay.tracker.events + * handleCommandEvent - called for all types of command events + * handleControlGroupEvent - called for all player control group events - Plugins may also handle optional ``InitGame`` and ``EndGame`` events generated - by the GameEngine before and after processing all the events: + Plugins may also handle optional ``InitGame`` and ``EndGame`` events generated + by the GameEngine before and after processing all the events: - * handleInitGame - is called prior to processing a new replay to provide - an opportunity for the plugin to clear internal state and set up any - replay state necessary. + * handleInitGame - is called prior to processing a new replay to provide + an opportunity for the plugin to clear internal state and set up any + replay state necessary. - * handleEndGame - is called after all events have been processed and - can be used to perform post processing on aggrated data or clean up - intermediate data caches. + * handleEndGame - is called after all events have been processed and + can be used to perform post processing on aggregated data or clean up + intermediate data caches. - Event handlers can choose to ``yield`` additional events which will be injected - into the event stream directly after the event currently being processed. This - feature allows for message passing between plugins. An ExpansionTracker plugin - could notify all other plugins of a new ExpansionEvent that they could opt to - process:: + Event handlers can choose to ``yield`` additional events which will be injected + into the event stream directly after the event currently being processed. This + feature allows for message passing between plugins. An ExpansionTracker plugin + could notify all other plugins of a new ExpansionEvent that they could opt to + process:: - def handleUnitDoneEvent(self, event, replay): - if event.unit.name == 'Nexus': - yield ExpansionEvent(event.frame, event.unit) - .... + def handleUnitDoneEvent(self, event, replay): + if event.unit.name == 'Nexus': + yield ExpansionEvent(event.frame, event.unit) + .... - If a plugin wishes to stop processing a replay it can yield a PluginExit event before returning:: + If a plugin wishes to stop processing a replay it can yield a PluginExit event before returning:: - def handleEvent(self, event, replay): - if len(replay.tracker_events) == 0: - yield PluginExit(self, code=0, details=dict(msg="tracker events required")) - return - ... + def handleEvent(self, event, replay): + if len(replay.tracker_events) == 0: + yield PluginExit(self, code=0, details=dict(msg="tracker events required")) + return + ... - def handleCommandEvent(self, event, replay): - try: - possibly_throwing_error() - catch Error as e: - logger.error(e) - yield PluginExit(self, code=0, details=dict(msg="Unexpected exception")) + def handleCommandEvent(self, event, replay): + try: + possibly_throwing_error() + catch Error as e: + logger.error(e) + yield PluginExit(self, code=0, details=dict(msg="Unexpected exception")) - The GameEngine will intercept this event and remove the plugin from the list of - active plugins for this replay. The exit code and details will be available from the - replay:: + The GameEngine will intercept this event and remove the plugin from the list of + active plugins for this replay. The exit code and details will be available from the + replay:: - code, details = replay.plugins['MyPlugin'] + code, details = replay.plugins['MyPlugin'] - If your plugin depends on another plugin, it is a good idea to implement handlePluginExit - and be alerted if the plugin that you require fails. This way you can exit gracefully. You - can also check to see if the plugin name is in ``replay.plugin_failures``:: + If your plugin depends on another plugin, it is a good idea to implement handlePluginExit + and be alerted if the plugin that you require fails. This way you can exit gracefully. You + can also check to see if the plugin name is in ``replay.plugin_failures``:: - if 'RequiredPlugin' in replay.plugin_failures: - code, details = replay.plugins['RequiredPlugin'] - message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code) - yield PluginExit(self, code=1, details=dict(msg=message)) + if 'RequiredPlugin' in replay.plugin_failures: + code, details = replay.plugins['RequiredPlugin'] + message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code) + yield PluginExit(self, code=1, details=dict(msg=message)) """ def __init__(self, plugins=[]): @@ -199,7 +204,7 @@ def run(self, replay): def _get_event_handlers(self, event, plugins): return sum( - [self._get_plugin_event_handlers(plugin, event) for plugin in plugins], [] + (self._get_plugin_event_handlers(plugin, event) for plugin in plugins), [] ) def _get_plugin_event_handlers(self, plugin, event): diff --git a/sc2reader/engine/events.py b/sc2reader/engine/events.py index 8857a4b1..fc6d4728 100644 --- a/sc2reader/engine/events.py +++ b/sc2reader/engine/events.py @@ -1,16 +1,12 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - - -class InitGameEvent(object): +class InitGameEvent: name = "InitGame" -class EndGameEvent(object): +class EndGameEvent: name = "EndGame" -class PluginExit(object): +class PluginExit: name = "PluginExit" def __init__(self, plugin, code=0, details=None): diff --git a/sc2reader/engine/plugins/__init__.py b/sc2reader/engine/plugins/__init__.py index 7ddff085..ca62c70c 100644 --- a/sc2reader/engine/plugins/__init__.py +++ b/sc2reader/engine/plugins/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from sc2reader.engine.plugins.apm import APMTracker from sc2reader.engine.plugins.selection import SelectionTracker from sc2reader.engine.plugins.context import ContextLoader diff --git a/sc2reader/engine/plugins/apm.py b/sc2reader/engine/plugins/apm.py index ec6fee7c..d1b7ad38 100644 --- a/sc2reader/engine/plugins/apm.py +++ b/sc2reader/engine/plugins/apm.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from collections import defaultdict -class APMTracker(object): +class APMTracker: """ Builds ``player.aps`` and ``player.apm`` dictionaries where an action is any Selection, ControlGroup, or Command event. diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index dd846634..38f8b3d2 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -1,13 +1,11 @@ -# -*- coding: utf-8 -*- # TODO: Dry this up a bit! -from __future__ import absolute_import, print_function, unicode_literals, division from sc2reader.log_utils import loggable from sc2reader.utils import Length @loggable -class ContextLoader(object): +class ContextLoader: name = "ContextLoader" def handleInitGame(self, event, replay): @@ -45,12 +43,7 @@ def handleCommandEvent(self, event, replay): 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__, - ) + f"{event.frame}\t{event.player.name}\tMissing ability {event.ability_id:X} from {replay.datapack.__class__.__name__}" ) else: @@ -60,7 +53,7 @@ def handleCommandEvent(self, event, replay): if event.other_unit_id in replay.objects: event.other_unit = replay.objects[event.other_unit_id] elif event.other_unit_id is not None: - self.logger.error("Other unit {0} not found".format(event.other_unit_id)) + self.logger.error(f"Other unit {event.other_unit_id} not found") def handleTargetUnitCommandEvent(self, event, replay): self.last_target_ability_event[event.player.pid] = event @@ -151,7 +144,7 @@ def handleSelectionEvent(self, event, replay): def handleResourceTradeEvent(self, event, replay): event.sender = event.player - event.recipient = replay.players[event.recipient_id] + event.recipient = replay.player[event.recipient_id] def handleHijackReplayGameEvent(self, event, replay): replay.resume_from_replay = True @@ -199,15 +192,11 @@ def handleUnitDiedEvent(self, event, replay): 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 - ) + f"Unable to delete unit index {event.unit_id_index} at {Length(seconds=event.second)} [{event.frame}], index not active." ) else: self.logger.error( - "Unit {0} died at {1} [{2}] before it was born!".format( - event.unit_id, Length(seconds=event.second), event.frame - ) + f"Unit {event.unit_id} died at {Length(seconds=event.second)} [{event.frame}] before it was born!" ) if event.killing_player_id in replay.player: @@ -217,9 +206,7 @@ def handleUnitDiedEvent(self, event, replay): 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 - ) + f"Unknown killing player id {event.killing_player_id} at {Length(seconds=event.second)} [{event.frame}]" ) if event.killing_unit_id in replay.objects: @@ -229,9 +216,7 @@ def handleUnitDiedEvent(self, event, replay): 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 - ) + f"Unknown killing unit id {event.killing_unit_id} at {Length(seconds=event.second)} [{event.frame}]" ) def handleUnitOwnerChangeEvent(self, event, replay): @@ -245,12 +230,10 @@ def handleUnitOwnerChangeEvent(self, event, replay): 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 - ) + f"Unit {event.unit_id} owner changed at {Length(seconds=event.second)} [{event.frame}] before it was born!" ) - if event.unit_upkeeper: + if event.unit_upkeeper and event.unit: if event.unit.owner: event.unit.owner.units.remove(event.unit) event.unit.owner = event.unit_upkeeper @@ -265,9 +248,7 @@ def handleUnitTypeChangeEvent(self, event, replay): 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) - ) + f"Unit {event.unit_id} type changed at {Length(seconds=event.second)} before it was born!" ) def handleUpgradeCompleteEvent(self, event, replay): @@ -308,9 +289,7 @@ def handleUnitDoneEvent(self, event, replay): 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 - ) + f"Unit {event.unit_id} done at {Length(seconds=event.second)} [{event.frame}] before it was started!" ) def handleUnitPositionsEvent(self, event, replay): @@ -324,9 +303,7 @@ def handleUnitPositionsEvent(self, event, replay): 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 - ) + f"Unit at active_unit index {unit_index} moved at {Length(seconds=event.second)} [{event.frame}] but it doesn't exist!" ) def load_message_game_player(self, event, replay): @@ -338,12 +315,7 @@ def load_message_game_player(self, event, replay): 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, - ) + f"Bad pid ({event.pid}) for event {event.__class__} at {Length(seconds=event.second)} [{event.frame}]." ) else: pass # This is a global event @@ -354,12 +326,7 @@ def load_message_game_player(self, event, replay): 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, - ) + f"Bad pid ({event.pid}) for event {event.__class__} at {Length(seconds=event.second)} [{event.frame}]." ) else: pass # This is a global event @@ -369,12 +336,7 @@ def load_tracker_player(self, event, replay): 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, - ) + f"Bad pid ({event.pid}) for event {event.__class__} at {Length(seconds=event.second)} [{event.frame}]." ) def load_tracker_upkeeper(self, event, replay): @@ -382,12 +344,7 @@ def load_tracker_upkeeper(self, event, replay): 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, - ) + f"Bad upkeep_pid ({event.upkeep_pid}) for event {event.__class__} at {Length(seconds=event.second)} [{event.frame}]." ) def load_tracker_controller(self, event, replay): @@ -395,10 +352,5 @@ def load_tracker_controller(self, event, replay): 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, - ) + f"Bad control_pid ({event.control_pid}) for event {event.__class__} at {Length(seconds=event.second)} [{event.frame}]." ) diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index 98f0148f..7125924d 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from io import BytesIO try: @@ -20,8 +17,9 @@ from collections import defaultdict from itertools import tee + # The creep tracker plugin -class CreepTracker(object): +class CreepTracker: """ The Creep tracker populates player.max_creep_spread and player.creep_spread by minute @@ -41,14 +39,14 @@ def handleInitGame(self, event, replay): if player.play_race[0] == "Z": self.creepTracker.init_cgu_lists(player.pid) except Exception as e: - print("Whoa! {}".format(e)) + print(f"Whoa! {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)) + print(f"Whoa! {e}") pass def handleUnitInitEvent(self, event, replay): @@ -62,7 +60,7 @@ def handleUnitInitEvent(self, event, replay): event.second, ) except Exception as e: - print("Whoa! {}".format(e)) + print(f"Whoa! {e}") pass def handleUnitBornEvent(self, event, replay): @@ -76,7 +74,7 @@ def handleUnitBornEvent(self, event, replay): event.second, ) except Exception as e: - print("Whoa! {}".format(e)) + print(f"Whoa! {e}") pass def handleEndGame(self, event, replay): @@ -86,8 +84,8 @@ def handleEndGame(self, event, replay): 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 + 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: @@ -98,7 +96,7 @@ def handleEndGame(self, event, replay): ## 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)) + print(f"Whoa! {e}") pass @@ -107,7 +105,7 @@ 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 + ##every minute in the game and the minimap with creep highlighted ## will be printed out. self.debug = replay.opt["debug"] ##This list contains creep spread area for each player @@ -117,7 +115,7 @@ def __init__(self, replay): self.creep_spread_image_by_minute = dict() ## 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 + ## This list corresponds to creep_gen_units storing the time of each CGU 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 @@ -125,9 +123,9 @@ def __init__(self, replay): 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]) + 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 @@ -168,12 +166,12 @@ 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 + ## subtract 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): + if (x**2 + y**2) <= (radius * radius): output_coordinates.append((x, y)) return output_coordinates @@ -231,7 +229,7 @@ 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 = (x[0] for x in cgu_per_minute) cgu_units = list() for index in indexes: cgu_units.append(self.creep_gen_units[player_id][index]) @@ -249,7 +247,7 @@ def reduce_cgu_per_minute(self, 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) + minutes = (int(x[0][1] // 60) * 60 for x in cgu_per_minute2) self.creep_gen_units[player_id] = list(cgu_unit_max_per_minute) self.creep_gen_units_times[player_id] = list(minutes) @@ -257,8 +255,8 @@ 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]): # 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 = ( + (x[1], self.unit_name_to_radius[x[2]]) for x in cgu_per_player ) # convert event coords to minimap coords cgu_radius = self.convert_cgu_radius_event_to_map_coord(cgu_radius) @@ -288,9 +286,9 @@ def cgu_radius_to_map_positions(self, cgu_radius, radius_to_coordinates): 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], + cgu_map_position = ( + (x[0] + point[0], x[1] + point[1]) + for x in self.radius_to_coordinates[radius] ) total_points_on_map = total_points_on_map | Set(cgu_map_position) return total_points_on_map diff --git a/sc2reader/engine/plugins/gameheart.py b/sc2reader/engine/plugins/gameheart.py index 1e412031..f2af2e82 100644 --- a/sc2reader/engine/plugins/gameheart.py +++ b/sc2reader/engine/plugins/gameheart.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from datetime import datetime from sc2reader.utils import Length, get_real_type from sc2reader.objects import Observer, Team @@ -8,7 +5,7 @@ from sc2reader.constants import GAME_SPEED_FACTOR -class GameHeartNormalizer(object): +class GameHeartNormalizer: """ normalize a GameHeart replay to: 1) reset frames to the game start @@ -34,6 +31,11 @@ def handleInitGame(self, event, replay): yield PluginExit(self, code=0, details=dict()) return + # Exit plugin if game is LOTV as LOTV games dont use GameHeart + if replay.expansion == "LotV": + yield PluginExit(self, code=0, details=dict()) + return + start_frame = -1 actual_players = {} for event in replay.tracker_events: diff --git a/sc2reader/engine/plugins/selection.py b/sc2reader/engine/plugins/selection.py index 9f741894..4dcc80c2 100644 --- a/sc2reader/engine/plugins/selection.py +++ b/sc2reader/engine/plugins/selection.py @@ -1,27 +1,24 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - - -class SelectionTracker(object): - """ Tracks a player's active selection as an input into other plugins. +class SelectionTracker: + """ + Tracks a player's active selection as an input into other plugins. - In some situations selection tracking isn't perfect. The plugin will - detect these situations and report errors. For a player will a high - level of selection errors, it may be best to ignore the selection - results as they could have been severely compromised. + In some situations selection tracking isn't perfect. The plugin will + detect these situations and report errors. For a player will a high + level of selection errors, it may be best to ignore the selection + results as they could have been severely compromised. - Exposes the following interface, directly integrated into the player: + Exposes the following interface, directly integrated into the player: - for person in replay.entities: - total_errors = person.selection_errors + for person in replay.entities: + total_errors = person.selection_errors - selection = person.selection - control_group_0 = selection[0] - ... - control_group_9 = selection[9] - active_selection = selection[10] + selection = person.selection + control_group_0 = selection[0] + ... + control_group_9 = selection[9] + active_selection = selection[10] - # TODO: list a few error inducing sitations + # TODO: list a few error inducing situations """ name = "SelectionTracker" @@ -69,7 +66,9 @@ def _select(self, selection, units): return sorted(set(selection + units)) def _deselect(self, selection, mode, data): - """Returns false if there was a data error when deselecting""" + """ + Returns false if there was a data error when deselecting + """ if mode == "None": return selection, False @@ -77,7 +76,9 @@ def _deselect(self, selection, mode, data): if mode == "Mask": # Deselect objects according to deselect mask - sfilter = lambda bit_u: not bit_u[0] + def sfilter(bit_u): + return not bit_u[0] + mask = data + [False] * (selection_size - data_size) new_selection = [u for (bit, u) in filter(sfilter, zip(mask, selection))] error = data_size > selection_size diff --git a/sc2reader/engine/plugins/supply.py b/sc2reader/engine/plugins/supply.py index c106f757..6935b847 100644 --- a/sc2reader/engine/plugins/supply.py +++ b/sc2reader/engine/plugins/supply.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from collections import defaultdict -class SupplyTracker(object): +class SupplyTracker: def add_to_units_alive(self, event, replay): unit_name = event.unit_type_name if unit_name in self.unit_name_to_supply: @@ -14,7 +11,7 @@ def add_to_units_alive(self, event, replay): 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]]) + 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 @@ -33,7 +30,7 @@ def add_to_units_alive(self, event, replay): 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]]) + 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 @@ -45,7 +42,7 @@ def add_to_units_alive(self, event, replay): replay.players[event.control_pid - 1].current_food_made[time_complete], ) else: - print("Unit name {0} does not exist".format(event.unit_type_name)) + print(f"Unit name {event.unit_type_name} does not exist") return def remove_from_units_alive(self, event, replay): @@ -54,7 +51,7 @@ def remove_from_units_alive(self, event, replay): 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]]) + total_supply = sum(x[0] for x in self.units_alive[player]) replay.players[player - 1].current_food_used[ event.second @@ -73,7 +70,7 @@ def remove_from_units_alive(self, event, replay): ) 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]]) + 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 @@ -87,7 +84,7 @@ def remove_from_units_alive(self, event, replay): ) def handleInitGame(self, event, replay): - ## This dictionary contains te supply of every unit + ## This dictionary contains the supply of every unit self.unit_name_to_supply = { # Zerg "Drone": (1, 17), diff --git a/sc2reader/engine/utils.py b/sc2reader/engine/utils.py index c8ca4af5..4c62597e 100644 --- a/sc2reader/engine/utils.py +++ b/sc2reader/engine/utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from bisect import bisect_left @@ -13,7 +10,7 @@ def __init__(self, initial_state): def __getitem__(self, frame): if frame in self: - return super(GameState, self).__getitem__(frame) + return super().__getitem__(frame) # Get the previous frame from our sorted frame list # bisect_left returns the left most key where an item is @@ -42,4 +39,4 @@ def __setitem__(self, frame, value): self._frames.insert(bisect_left(self._frames, frame), frame) self._frameset.add(frame) - super(GameState, self).__setitem__(frame, value) + super().__setitem__(frame, value) diff --git a/sc2reader/events/__init__.py b/sc2reader/events/__init__.py index 6ceaa632..126f482a 100644 --- a/sc2reader/events/__init__.py +++ b/sc2reader/events/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - # Export all events of all types to the package interface from sc2reader.events import base, game, message, tracker from sc2reader.events.base import * diff --git a/sc2reader/events/base.py b/sc2reader/events/base.py index 89f16c5e..7245c4e2 100644 --- a/sc2reader/events/base.py +++ b/sc2reader/events/base.py @@ -1,6 +1,2 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - - -class Event(object): +class Event: name = "Event" diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 9dda98b5..0da9afa2 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from sc2reader.utils import Length from sc2reader.events.base import Event from sc2reader.log_utils import loggable @@ -41,14 +38,12 @@ def _str_prefix(self): 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 = f"Player {self.player.pid} - ({self.player.play_race})" elif self.player: player_name = self.player.name else: player_name = "no name" - return "{0}\t{1:<15} ".format(Length(seconds=int(self.frame / 16)), player_name) + return f"{Length(seconds=int(self.frame / 16))}\t{player_name:<15} " def __str__(self): return self._str_prefix() + self.name @@ -61,7 +56,7 @@ class GameStartEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(GameStartEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: ??? self.data = data @@ -73,7 +68,7 @@ class PlayerLeaveEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(PlayerLeaveEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: ??? self.data = data @@ -86,7 +81,7 @@ class UserOptionsEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(UserOptionsEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: self.game_fully_downloaded = data["game_fully_downloaded"] @@ -145,7 +140,7 @@ class CommandEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(CommandEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: Flags on the command??? self.flags = data["flags"] @@ -240,19 +235,17 @@ def __init__(self, frame, pid, data): def __str__(self): string = self._str_prefix() if self.has_ability: - string += "Ability ({0:X})".format(self.ability_id) + string += f"Ability ({self.ability_id:X})" if self.ability: - string += " - {0}".format(self.ability.name) + string += f" - {self.ability.name}" else: string += "Right Click" if self.ability_type == "TargetUnit": - string += "; Target: {0} [{1:0>8X}]".format( - self.target.name, self.target_unit_id - ) + string += f"; Target: {self.target.name} [{self.target_unit_id:0>8X}]" if self.ability_type in ("TargetPoint", "TargetUnit"): - string += "; Location: {0}".format(str(self.location)) + string += f"; Location: {str(self.location)}" return string @@ -268,7 +261,7 @@ class BasicCommandEvent(CommandEvent): """ def __init__(self, frame, pid, data): - super(BasicCommandEvent, self).__init__(frame, pid, data) + super().__init__(frame, pid, data) class TargetPointCommandEvent(CommandEvent): @@ -284,7 +277,7 @@ class TargetPointCommandEvent(CommandEvent): """ def __init__(self, frame, pid, data): - super(TargetPointCommandEvent, self).__init__(frame, pid, data) + super().__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 @@ -312,7 +305,7 @@ class TargetUnitCommandEvent(CommandEvent): """ def __init__(self, frame, pid, data): - super(TargetUnitCommandEvent, self).__init__(frame, pid, data) + super().__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) @@ -324,7 +317,7 @@ def __init__(self, frame, pid, data): #: 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) - #: A reference to the targetted unit. When the :attr:`target_unit_id` is + #: A reference to the targeted 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` #: with an id of zero. It should not be confused with a real unit. self.target_unit = None @@ -333,7 +326,7 @@ def __init__(self, frame, pid, data): 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. + #: When the targeted unit is under fog of war this id is zero. 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. @@ -374,7 +367,7 @@ class UpdateTargetUnitCommandEvent(TargetUnitCommandEvent): from TargetUnitCommandEvent, but for flexibility, it will be treated differently. - One example of this event occuring is casting inject on a hatchery while + One example of this event occurring is casting inject on a hatchery while holding shift, and then shift clicking on a second hatchery. """ @@ -393,12 +386,32 @@ class DataCommandEvent(CommandEvent): """ def __init__(self, frame, pid, data): - super(DataCommandEvent, self).__init__(frame, pid, data) + super().__init__(frame, pid, data) #: Other target data. Available for Data type events. self.target_data = self.ability_type_data.get("data", None) +@loggable +class CommandManagerStateEvent(GameEvent): + """ + These events indicated that the last :class:`CommandEvent` called has been + called again. For example, if you add three SCVs to an empty queue on a + Command Center, the first add will be generate a :class:`BasicCommandEvent` + and the two subsequent adds will each generate a + :class:`CommandManagerStateEvent`. + """ + + def __init__(self, frame, pid, data): + super().__init__(frame, pid) + + #: Always 1? + self.state = data["state"] + + #: An index identifying how many events of this type have been called + self.sequence = data["sequence"] + + @loggable class SelectionEvent(GameEvent): """ @@ -406,14 +419,14 @@ class SelectionEvent(GameEvent): player is updated. Unlike other game events, these events can also be generated by non-player actions like unit deaths or transformations. - Starting in Starcraft 2.0.0, selection events targetting control group + Starting in Starcraft 2.0.0, selection events targeting control group buffers are also generated when control group selections are modified 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) + super().__init__(frame, pid) #: The control group being modified. 10 for active selection self.control_group = data["control_group_index"] @@ -504,17 +517,21 @@ def __str__(self): def create_control_group_event(frame, pid, data): update_type = data["control_group_update"] - if update_type == 0: + if update_type in [0, 4]: + # 0 is the normal set command. + # 4 is used when you steal and set. If required, type 4 will be followed by autogenerated + # selection and control group events that remove the units from their other groups. return SetControlGroupEvent(frame, pid, data) - elif update_type == 1: + elif update_type in [1, 5]: + # 1 is the normal add command. + # 5 is used when you steal and add. If required, type 5 will be followed by autogenerated + # selection and control group events that remove the units from their other groups. return AddToControlGroupEvent(frame, pid, data) elif update_type == 2: return GetControlGroupEvent(frame, pid, data) elif update_type == 3: - # TODO: What could this be?!? - return ControlGroupEvent(frame, pid, data) + return DeleteControlGroupEvent(frame, pid, data) else: - # No idea what this is but we're seeing update_types of 4 and 5 in 3.0 return ControlGroupEvent(frame, pid, data) @@ -529,12 +546,12 @@ class ControlGroupEvent(GameEvent): * :class:`GetControlGroup` - Recorded when a user retrieves a control group (#). * :class:`AddToControlGroup` - Recorded when a user adds to a control group (shift+ctrl+#) - All three events have the same set of data (shown below) but are interpretted differently. + All three events have the same set of data (shown below) but are interpreted differently. See the class entry for details. """ def __init__(self, frame, pid, data): - super(ControlGroupEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: Index to the control group being modified self.control_group = data["control_group_index"] @@ -572,6 +589,15 @@ class AddToControlGroupEvent(SetControlGroupEvent): """ +class DeleteControlGroupEvent(ControlGroupEvent): + """ + Extends :class:`ControlGroupEvent` + + This event deletes the control group (all units are removed). This happens when all + units are stolen from the event group (alt, alt+shift modifiers by default). + """ + + class GetControlGroupEvent(ControlGroupEvent): """ Extends :class:`ControlGroupEvent` @@ -592,7 +618,7 @@ class CameraEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(CameraEvent, self).__init__(frame, pid) + super().__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 @@ -613,9 +639,7 @@ def __init__(self, frame, pid, data): 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() + f"{self.name} at ({self.x}, {self.y})" @loggable @@ -626,7 +650,7 @@ class ResourceTradeEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(ResourceTradeEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: The id of the player sending the resources self.sender_id = pid @@ -650,14 +674,15 @@ def __init__(self, frame, pid, data): self.vespene = self.resources[1] if len(self.resources) >= 2 else None #: Amount terrazine sent - self.terrazon = self.resources[2] if len(self.resources) >= 3 else None + self.terrazine = self.resources[2] if len(self.resources) >= 3 else None #: Amount custom resource sent 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() + + f" transfer {self.minerals} minerals, {self.vespene} gas, {self.terrazine} terrazine, and {self.custom_resource} custom to {self.recipient}" ) @@ -667,7 +692,7 @@ class ResourceRequestEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(ResourceRequestEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: An array of resources sent self.resources = data["resources"] @@ -685,8 +710,9 @@ 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() + + f" requests {self.minerals} minerals, {self.vespene} gas, {self.terrazine} terrazine, and {self.custom_resource} custom" ) @@ -696,7 +722,7 @@ class ResourceRequestFulfillEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(ResourceRequestFulfillEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: The id of the request being fulfilled self.request_id = data["request_id"] @@ -708,7 +734,7 @@ class ResourceRequestCancelEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(ResourceRequestCancelEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: The id of the request being cancelled self.request_id = data["request_id"] @@ -720,10 +746,29 @@ class HijackReplayGameEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(HijackReplayGameEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: The method used. Not sure what 0/1 represent self.method = data["method"] #: Information on the users hijacking the game self.user_infos = data["user_infos"] + + +@loggable +class DialogControlEvent(GameEvent): + """ + Generated when a dialog is interacted with. + """ + + def __init__(self, frame, pid, data): + super().__init__(frame, pid) + + #: Identifier for the dialog + self.control_id = data["control_id"] + + #: How dialog was interacted with + self.event_type = data["event_type"] + + #: Data specific to event type such as changes or clicks + self.event_data = data["event_data"] diff --git a/sc2reader/events/message.py b/sc2reader/events/message.py index 2fdb9cfd..c1848f64 100644 --- a/sc2reader/events/message.py +++ b/sc2reader/events/message.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from sc2reader.events.base import Event from sc2reader.utils import Length from sc2reader.log_utils import loggable @@ -9,7 +6,7 @@ @loggable class MessageEvent(Event): """ - Parent class for all message events. + Parent class for all message events. """ def __init__(self, frame, pid): @@ -27,7 +24,7 @@ def __init__(self, frame, pid): def _str_prefix(self): 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) + return f"{Length(seconds=int(self.frame / 16))}\t{player_name:<15} " def __str__(self): return self._str_prefix() + self.name @@ -36,11 +33,11 @@ def __str__(self): @loggable class ChatEvent(MessageEvent): """ - Records in-game chat events. + Records in-game chat events. """ def __init__(self, frame, pid, target, text): - super(ChatEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: The numerical target type. 0 = to all; 2 = to allies; 4 = to observers. self.target = target @@ -60,11 +57,11 @@ def __init__(self, frame, pid, target, text): @loggable class ProgressEvent(MessageEvent): """ - Sent during the load screen to update load process for other clients. + Sent during the load screen to update load process for other clients. """ def __init__(self, frame, pid, progress): - super(ProgressEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: Marks the load progress for the player. Scaled 0-100. self.progress = progress @@ -73,11 +70,11 @@ def __init__(self, frame, pid, progress): @loggable class PingEvent(MessageEvent): """ - Records pings made by players in game. + Records pings made by players in game. """ def __init__(self, frame, pid, target, x, y): - super(PingEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: The numerical target type. 0 = to all; 2 = to allies; 4 = to observers. self.target = target diff --git a/sc2reader/events/tracker.py b/sc2reader/events/tracker.py index 47ebaf09..8e0c6772 100644 --- a/sc2reader/events/tracker.py +++ b/sc2reader/events/tracker.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import functools from sc2reader.events.base import Event @@ -17,7 +14,7 @@ class TrackerEvent(Event): 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 @@ -29,17 +26,17 @@ def load_context(self, replay): pass def _str_prefix(self): - return "{0}\t ".format(Length(seconds=int(self.frame / 16))) + return f"{Length(seconds=int(self.frame / 16))}\t " def __str__(self): return self._str_prefix() + self.name class PlayerSetupEvent(TrackerEvent): - """ Sent during game setup to help us organize players better """ + """Sent during game setup to help us organize players better""" def __init__(self, frames, data, build): - super(PlayerSetupEvent, self).__init__(frames) + super().__init__(frames) #: The player id of the player we are setting up self.pid = data[0] @@ -68,7 +65,7 @@ class PlayerStatsEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(PlayerStatsEvent, self).__init__(frames) + super().__init__(frames) #: Id of the player the stats are for self.pid = data[0] @@ -275,7 +272,7 @@ def __init__(self, frames, data, build): ) def __str__(self): - return self._str_prefix() + "{0: >15} - Stats Update".format(str(self.player)) + return self._str_prefix() + f"{str(self.player): >15} - Stats Update" class UnitBornEvent(TrackerEvent): @@ -291,7 +288,7 @@ class UnitBornEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitBornEvent, self).__init__(frames) + super().__init__(frames) #: The index portion of the unit id self.unit_id_index = data[0] @@ -337,8 +334,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() + + f"{str(self.unit_upkeeper): >15} - Unit born {self.unit}" ) @@ -349,7 +347,7 @@ class UnitDiedEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitDiedEvent, self).__init__(frames) + super().__init__(frames) #: The index portion of the unit id self.unit_id_index = data[0] @@ -412,8 +410,8 @@ def __init__(self, frames, data, build): ) def __str__(self): - return self._str_prefix() + "{0: >15} - Unit died {1}.".format( - str(self.unit.owner), self.unit + return ( + self._str_prefix() + f"{str(self.unit.owner): >15} - Unit died {self.unit}." ) @@ -424,7 +422,7 @@ class UnitOwnerChangeEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitOwnerChangeEvent, self).__init__(frames) + super().__init__(frames) #: The index portion of the unit id self.unit_id_index = data[0] @@ -451,9 +449,7 @@ 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() + f"{str(self.unit_upkeeper): >15} took {self.unit}" class UnitTypeChangeEvent(TrackerEvent): @@ -464,7 +460,7 @@ class UnitTypeChangeEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitTypeChangeEvent, self).__init__(frames) + super().__init__(frames) #: The index portion of the unit id self.unit_id_index = data[0] @@ -482,8 +478,9 @@ def __init__(self, frames, data, build): self.unit_type_name = data[2].decode("utf8") def __str__(self): - return self._str_prefix() + "{0: >15} - Unit {1} type changed to {2}".format( - str(self.unit.owner), self.unit, self.unit_type_name + return ( + self._str_prefix() + + f"{str(self.unit.owner): >15} - Unit {self.unit} type changed to {self.unit_type_name}" ) @@ -493,7 +490,7 @@ class UpgradeCompleteEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UpgradeCompleteEvent, self).__init__(frames) + super().__init__(frames) #: The player that completed the upgrade self.pid = data[0] @@ -508,8 +505,9 @@ def __init__(self, frames, data, build): 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() + + f"{str(self.player): >15} - {self.upgrade_type_name} upgrade completed" ) @@ -521,7 +519,7 @@ class UnitInitEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitInitEvent, self).__init__(frames) + super().__init__(frames) #: The index portion of the unit id self.unit_id_index = data[0] @@ -567,8 +565,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() + + f"{str(self.unit_upkeeper): >15} - Unit initiated {self.unit}" ) @@ -579,7 +578,7 @@ class UnitDoneEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitDoneEvent, self).__init__(frames) + super().__init__(frames) #: The index portion of the unit id self.unit_id_index = data[0] @@ -594,8 +593,8 @@ 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() + f"{str(self.unit.owner): >15} - Unit {self.unit} done" ) @@ -607,7 +606,7 @@ class UnitPositionsEvent(TrackerEvent): """ def __init__(self, frames, data, build): - super(UnitPositionsEvent, self).__init__(frames) + super().__init__(frames) #: The starting unit index point. self.first_unit_index = data[0] diff --git a/sc2reader/exceptions.py b/sc2reader/exceptions.py index dff2d31b..5dc20e81 100644 --- a/sc2reader/exceptions.py +++ b/sc2reader/exceptions.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - - class SC2ReaderError(Exception): pass @@ -29,10 +25,10 @@ class MultipleMatchingFilesError(SC2ReaderError): class ReadError(SC2ReaderError): def __init__(self, msg, type, location, replay=None, game_events=[], buffer=None): self.__dict__.update(locals()) - super(ReadError, self).__init__(msg) + super().__init__(msg) def __str__(self): - return "{0}, Type: {1}".format(self.msg, self.type) + return f"{self.msg}, Type: {self.type}" class ParseError(SC2ReaderError): diff --git a/sc2reader/factories/__init__.py b/sc2reader/factories/__init__.py index c6c469f6..744e1aae 100644 --- a/sc2reader/factories/__init__.py +++ b/sc2reader/factories/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, unicode_literals, division - from sc2reader.factories.sc2factory import SC2Factory from sc2reader.factories.sc2factory import FileCachedSC2Factory from sc2reader.factories.sc2factory import DictCachedSC2Factory diff --git a/sc2reader/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index f7669ef7..724a51e3 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import json from collections import defaultdict @@ -88,7 +85,6 @@ def toDict(replay): "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), @@ -130,7 +126,7 @@ def APMTracker(replay): elif event.name == "PlayerLeaveEvent": player.seconds_played = event.second - if len(player.apm) > 0: + if len(player.apm) > 0 and player.seconds_played > 0: player.avg_apm = ( sum(player.aps.values()) / float(player.seconds_played) * 60 ) @@ -159,12 +155,7 @@ def SelectionTracker(replay): 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], - ) + f"[{Length(seconds=event.second)}] {person.name} selected {len(selections[0x0A].objects)} units: {selections[0x0A]}" ) elif event.name == "SetControlGroupEvent": @@ -172,9 +163,7 @@ def SelectionTracker(replay): 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 - ) + f"[{Length(seconds=event.second)}] {person.name} set hotkey {event.hotkey} to current selection" ) elif event.name == "AddToControlGroupEvent": @@ -185,9 +174,7 @@ def SelectionTracker(replay): 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 - ) + f"[{Length(seconds=event.second)}] {person.name} added current selection to hotkey {event.hotkey}" ) elif event.name == "GetControlGroupEvent": @@ -197,13 +184,7 @@ def SelectionTracker(replay): 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], - ) + f"[{Length(seconds=event.second)}] {person.name} retrieved hotkey {event.control_group}, {len(selections[0x0A].objects)} units: {selections[0x0A]}" ) else: @@ -215,10 +196,8 @@ def SelectionTracker(replay): if error: person.selection_errors += 1 if debug: - logger.warn( - "Error detected in deselection mode {0}.".format( - event.mask_type - ) + logger.warning( + f"Error detected in deselection mode {event.mask_type}." ) person.selection = player_selections diff --git a/sc2reader/factories/plugins/utils.py b/sc2reader/factories/plugins/utils.py index e472b86e..f747c36f 100644 --- a/sc2reader/factories/plugins/utils.py +++ b/sc2reader/factories/plugins/utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from bisect import bisect_left from collections import defaultdict from datetime import datetime @@ -40,7 +37,7 @@ def __init__(self, initial_state): def __getitem__(self, frame): if frame in self: - return super(GameState, self).__getitem__(frame) + return super().__getitem__(frame) # Get the previous frame from our sorted frame list # bisect_left returns the left most key where an item is @@ -69,11 +66,11 @@ def __setitem__(self, frame, value): self._frames.insert(bisect_left(self._frames, frame), frame) self._frameset.add(frame) - super(GameState, self).__setitem__(frame, value) + super().__setitem__(frame, value) @loggable -class UnitSelection(object): +class UnitSelection: def __init__(self, objects=None): self.objects = objects or list() @@ -89,13 +86,13 @@ def deselect(self, mode, data): return True elif mode == "Mask": - """ Deselect objects according to deselect 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)) - self.logger.debug("Deselection Mask: {0}".format(mask)) + self.logger.debug(f"Deselection Mask: {mask}") self.objects = [ obj for (slct, obj) in filter( @@ -105,7 +102,7 @@ def deselect(self, mode, data): return len(mask) <= size elif mode == "OneIndices": - """ Deselect objects according to indexes """ + """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 @@ -113,7 +110,7 @@ def deselect(self, mode, data): return len(clean_data) == len(data) elif mode == "ZeroIndices": - """ Deselect objects according to indexes """ + """Deselect objects according to indexes""" clean_data = list(filter(lambda i: i < size, data)) self.objects = [self.objects[i] for i in clean_data] return len(clean_data) == len(data) @@ -130,7 +127,7 @@ def copy(self): class PlayerSelection(defaultdict): def __init__(self): - super(PlayerSelection, self).__init__(UnitSelection) + super().__init__(UnitSelection) def copy(self): new = PlayerSelection() diff --git a/sc2reader/factories/sc2factory.py b/sc2reader/factories/sc2factory.py index 690797a3..40de1c7f 100644 --- a/sc2reader/factories/sc2factory.py +++ b/sc2reader/factories/sc2factory.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from collections import defaultdict from io import BytesIO import os @@ -27,8 +24,9 @@ @log_utils.loggable -class SC2Factory(object): - """The SC2Factory class acts as a generic loader interface for all +class SC2Factory: + """ + The SC2Factory class acts as a generic loader interface for all available to sc2reader resources. At current time this includes :class:`~sc2reader.resources.Replay` and :class:`~sc2reader.resources.Map` resources. These resources can be loaded in both singular and plural contexts with: @@ -81,57 +79,79 @@ def __init__(self, **options): # Primary Interface def load_replay(self, source, options=None, **new_options): - """Loads a single sc2replay file. Accepts file path, url, or file object.""" + """ + Loads a single sc2replay file. Accepts file path, url, or file object. + """ return self.load(Replay, source, options, **new_options) def load_replays(self, sources, options=None, **new_options): - """Loads a collection of sc2replay files, returns a generator.""" + """ + Loads a collection of sc2replay files, returns a generator. + """ 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.""" + """ + Loads a single s2ml file. Accepts file path, url, or file object. + """ return self.load(Localization, source, options, **new_options) def load_localizations(self, sources, options=None, **new_options): - """Loads a collection of s2ml files, returns a generator.""" + """ + Loads a collection of s2ml files, returns a generator. + """ 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.""" + """ + Loads a single s2ma file. Accepts file path, url, or file object. + """ return self.load(Map, source, options, **new_options) def load_maps(self, sources, options=None, **new_options): - """Loads a collection of s2ma files, returns a generator.""" + """ + Loads a collection of s2ma files, returns a generator. + """ 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.""" + """ + Loads a single s2gs file. Accepts file path, url, or file object. + """ return self.load(GameSummary, source, options, **new_options) def load_game_summaries(self, sources, options=None, **new_options): - """Loads a collection of s2gs files, returns a generator.""" + """ + Loads a collection of s2gs files, returns a generator. + """ 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 - the options will only be applied when loading that class""" + """ + Configures the factory to use the supplied options. If cls is specified + the options will only be applied when loading that class + """ if isinstance(cls, basestring): cls = self._resource_name_map.get[cls.lower()] cls = cls or Resource self.options[cls].update(options) def reset(self): - "Resets the options to factory defaults" + """ + Resets the options to factory defaults + """ self.options = defaultdict(dict) def register_plugin(self, cls, plugin): - "Registers the given Plugin to be run on classes of the supplied name." + """ + Registers the given Plugin to be run on classes of the supplied name. + """ if isinstance(cls, basestring): cls = self._resource_name_map.get(cls.lower(), Resource) self.plugins.append((cls, plugin)) @@ -170,7 +190,9 @@ def _get_options(self, cls, **new_options): return options def _load_resources(self, resources, options=None, **new_options): - """Collections of resources or a path to a directory""" + """ + Collections of resources or a path to a directory + """ options = options or self._get_options(Resource, **new_options) # Path to a folder, retrieve all relevant files as the collection @@ -190,7 +212,9 @@ def load_local_resource_contents(self, location, **options): return resource_file.read() def _load_resource(self, resource, options=None, **new_options): - """http links, filesystem locations, and file-like objects""" + """ + http links, filesystem locations, and file-like objects + """ options = options or self._get_options(Resource, **new_options) if isinstance(resource, utils.DepotFile): @@ -235,22 +259,20 @@ def get_remote_cache_key(self, remote_resource): 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().load_remote_resource_contents(remote_resource, **options) self.cache_set(cache_key, resource) else: resource = self.cache_get(cache_key) return resource def cache_has(self, cache_key): - raise NotImplemented() + raise NotImplementedError() def cache_get(self, cache_key): - raise NotImplemented() + raise NotImplementedError() def cache_set(self, cache_key, value): - raise NotImplemented() + raise NotImplementedError() class FileCachedSC2Factory(CachedSC2Factory): @@ -263,17 +285,15 @@ class FileCachedSC2Factory(CachedSC2Factory): """ def __init__(self, cache_dir, **options): - super(FileCachedSC2Factory, self).__init__(**options) + super().__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) + f"cache_dir ({self.cache_dir}) must be an existing directory." ) 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 - ) + f"Must have read/write access to {self.cache_dir} for local file caching." ) def cache_has(self, cache_key): @@ -306,7 +326,7 @@ class DictCachedSC2Factory(CachedSC2Factory): """ def __init__(self, cache_max_size=0, **options): - super(DictCachedSC2Factory, self).__init__(**options) + super().__init__(**options) self.cache_dict = dict() self.cache_used = dict() self.cache_max_size = cache_max_size @@ -339,9 +359,7 @@ class DoubleCachedSC2Factory(DictCachedSC2Factory, FileCachedSC2Factory): """ def __init__(self, cache_dir, cache_max_size=0, **options): - super(DoubleCachedSC2Factory, self).__init__( - cache_max_size, cache_dir=cache_dir, **options - ) + super().__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) diff --git a/sc2reader/log_utils.py b/sc2reader/log_utils.py index a53656c8..dc20fe92 100644 --- a/sc2reader/log_utils.py +++ b/sc2reader/log_utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import logging try: @@ -36,7 +33,7 @@ def createLock(self): LEVEL_MAP = dict( DEBUG=logging.DEBUG, INFO=logging.INFO, - WARN=logging.WARN, + WARN=logging.WARNING, ERROR=logging.ERROR, CRITICAL=logging.CRITICAL, ) @@ -67,18 +64,18 @@ def add_log_handler(handler, level="WARN", format=None, datefmt=None): def get_logger(entity): """ - Retrieves loggers from the enties fully scoped name. + Retrieves loggers from the enties fully scoped name. - get_logger(Replay) -> sc2reader.replay.Replay - get_logger(get_logger) -> sc2reader.utils.get_logger + get_logger(Replay) -> sc2reader.replay.Replay + get_logger(get_logger) -> sc2reader.utils.get_logger - :param entity: The entity for which we want a logger. + :param entity: The entity for which we want a logger. """ try: return logging.getLogger(entity.__module__ + "." + entity.__name__) except AttributeError: - raise TypeError("Cannot retrieve logger for {0}.".format(entity)) + raise TypeError(f"Cannot retrieve logger for {entity}.") def loggable(cls): diff --git a/sc2reader/objects.py b/sc2reader/objects.py index a49c3012..03632dee 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -1,28 +1,25 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import hashlib import math from collections import namedtuple from sc2reader import utils, log_utils from sc2reader.decoders import ByteDecoder -from sc2reader.constants import * +from sc2reader.constants import GATEWAY_LOOKUP, LOBBY_PROPERTIES, LOCALIZED_RACES Location = namedtuple("Location", ["x", "y"]) -class Team(object): +class Team: """ The team object primarily a container object for organizing :class:`Player` objects with some metadata. As such, it implements iterable and can be looped over like a list. - :param interger number: The team number as recorded in the replay + :param integer number: The team number as recorded in the replay """ #: A unique hash identifying the team of players - hash = str() + hash = "" #: The team number as recorded in the replay number = int() @@ -32,7 +29,7 @@ class Team(object): #: The result of the game for this team. #: One of "Win", "Loss", or "Unknown" - result = str() + result = "" def __init__(self, number): self.number = number @@ -51,12 +48,12 @@ def lineup(self): return "".join(sorted(p.play_race[0].upper() for p in self.players)) @property - def hash(self): + def hash(self): # noqa: F811 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( + return "Team {}: {}".format( self.number, ", ".join([str(p) for p in self.players]) ) @@ -65,28 +62,32 @@ def __repr__(self): @log_utils.loggable -class Attribute(object): +class Attribute: def __init__(self, header, attr_id, player, value): self.header = header self.id = attr_id self.player = player if self.id not in LOBBY_PROPERTIES: - self.logger.info("Unknown attribute id: {0}".format(self.id)) + self.logger.info(f"Unknown attribute id: {self.id}") self.name = "Unknown" self.value = None else: self.name, lookup = LOBBY_PROPERTIES[self.id] - self.value = lookup[value.strip("\x00 ")[::-1]] + try: + self.value = lookup[value.strip("\x00 ")[::-1]] + except KeyError: + self.logger.info(f"Missing attribute value: {value}") + self.value = None def __repr__(self): return str(self) def __str__(self): - return "[{0}] {1}: {2}".format(self.player, self.name, self.value) + return f"[{self.player}] {self.name}: {self.value}" -class Entity(object): +class Entity: """ :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity @@ -134,13 +135,16 @@ def __init__(self, sid, slot_data): toon_handle = self.toon_handle or "0-S2-0-0" parts = toon_handle.split("-") + #: The Battle.net region id the entity is registered to + self.region_id = int(parts[0]) + #: The Battle.net region the entity is registered to - self.region = GATEWAY_LOOKUP[int(parts[0])] + self.region = GATEWAY_LOOKUP[self.region_id] #: The Battle.net subregion the entity is registered to self.subregion = int(parts[2]) - #: The Battle.net acount identifier. Used to construct the + #: The Battle.net account 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 = int(parts[3]) @@ -160,7 +164,7 @@ def format(self, format_string): return format_string.format(**self.__dict__) -class Player(object): +class Player: """ :param integer pid: The player's unique player id. :param dict detail_data: The detail data associated with this player @@ -237,16 +241,19 @@ def __init__(self, pid, slot_data, detail_data, attribute_data): #: The Battle.net region the entity is registered to self.region = GATEWAY_LOOKUP[detail_data["bnet"]["region"]] + #: The Battle.net region id the entity is registered to + self.region_id = detail_data["bnet"]["region"] + #: The Battle.net subregion the entity is registered to self.subregion = detail_data["bnet"]["subregion"] - #: The Battle.net acount identifier. Used to construct the + #: The Battle.net account 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"] -class User(object): +class User: """ :param integer uid: The user's unique user id :param dict init_data: The init data associated with this user @@ -254,7 +261,7 @@ class User(object): #: The Battle.net profile url template URL_TEMPLATE = ( - "http://{region}.battle.net/sc2/en/profile/{toon_id}/{subregion}/{name}/" + "https://starcraft2.com/en-us/profile/{region_id}/{subregion}/{toon_id}" ) def __init__(self, uid, init_data): @@ -281,16 +288,28 @@ def __init__(self, uid, init_data): #: This is deprecated because it doesn't actually work. self.recorder = None + #: The user's mmr at the time of the game + #: Currently, there are three cases observed for a user that does not have a current mmr: + #: 1. The user has no 'scaled_rating' key in their init_data, + #: 2. The user has a None value for their 'scaled_rating' key, or + #: 3. The user has a negative rating, often -36400. + #: For ease of use, this property will return None in both cases. + matchmaking_rating = int(init_data.get("scaled_rating") or 0) + self.mmr = matchmaking_rating if matchmaking_rating > 0 else None + @property def url(self): - """The player's formatted Battle.net profile url""" + """ + 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')) class Observer(Entity, User): - """ Extends :class:`Entity` and :class:`User`. + """ + Extends :class:`Entity` and :class:`User`. :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity @@ -307,14 +326,15 @@ def __init__(self, sid, slot_data, uid, init_data, pid): self.pid = pid def __str__(self): - return "Observer {0} - {1}".format(self.uid, self.name) + return f"Observer {self.uid} - {self.name}" def __repr__(self): return str(self) class Computer(Entity, Player): - """ Extends :class:`Entity` and :class:`Player` + """ + Extends :class:`Entity` and :class:`Player` :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity @@ -331,14 +351,15 @@ def __init__(self, sid, slot_data, pid, detail_data, attribute_data): self.name = detail_data["name"] def __str__(self): - return "Player {0} - {1} ({2})".format(self.pid, self.name, self.play_race) + return f"Player {self.pid} - {self.name} ({self.play_race})" def __repr__(self): return str(self) class Participant(Entity, User, Player): - """ Extends :class:`Entity`, :class:`User`, and :class:`Player` + """ + Extends :class:`Entity`, :class:`User`, and :class:`Player` :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity @@ -357,7 +378,7 @@ def __init__( Player.__init__(self, pid, slot_data, detail_data, attribute_data) def __str__(self): - return "Player {0} - {1} ({2})".format(self.pid, self.name, self.play_race) + return f"Player {self.pid} - {self.name} ({self.play_race})" def __repr__(self): return str(self) @@ -376,10 +397,10 @@ class PlayerSummary: teamid = int() #: The race the player played in the game. - play_race = str() + play_race = "" #: The race the player picked in the lobby. - pick_race = str() + pick_race = "" #: If the player is a computer is_ai = False @@ -394,7 +415,7 @@ class PlayerSummary: subregion = int() #: The player's region, such as us, eu, sea - region = str() + region = "" #: unknown1 unknown1 = int() @@ -417,11 +438,9 @@ 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 f"User {self.region.upper()}-S2-{self.subregion}-{self.bnetid}" else: - return "AI ({0})".format(self.play_race) + return f"AI ({self.play_race})" def __repr__(self): return str(self) @@ -429,7 +448,7 @@ def __repr__(self): def get_stats(self): s = "" for k in self.stats: - s += "{0}: {1}\n".format(self.stats_pretty_names[k], self.stats[k]) + s += f"{self.stats_pretty_names[k]}: {self.stats[k]}\n" return s.strip() @@ -464,14 +483,14 @@ def __init__(self, x, y, xy_list=None): self.values = y def as_points(self): - """ Get the graph as a list of (x, y) tuples """ + """Get the graph as a list of (x, y) tuples""" return list(zip(self.times, self.values)) def __str__(self): - return "Graph with {0} values".format(len(self.times)) + return f"Graph with {len(self.times)} values" -class MapInfoPlayer(object): +class MapInfoPlayer: """ Describes the player data as found in the MapInfo document of SC2Map archives. """ @@ -530,7 +549,7 @@ def __init__(self, pid, control, color, race, unknown, start_point, ai, decal): @log_utils.loggable -class MapInfo(object): +class MapInfo: """ Represents the data encoded into the MapInfo file inside every SC2Map archive """ @@ -541,7 +560,7 @@ def __init__(self, contents): data = ByteDecoder(contents, endian="LITTLE") magic = data.read_string(4) if magic != "MapI": - self.logger.warn("Invalid MapInfo file: {0}".format(magic)) + self.logger.warning(f"Invalid MapInfo file: {magic}") return #: The map info file format version @@ -560,7 +579,7 @@ def __init__(self, contents): self.small_preview_type = data.read_uint32() #: (Optional) Small map preview path; relative to root of map archive - self.small_preview_path = str() + self.small_preview_path = "" if self.small_preview_type == 2: self.small_preview_path = data.read_cstring() @@ -568,7 +587,7 @@ def __init__(self, contents): self.large_preview_type = data.read_uint32() #: (Optional) Large map preview path; relative to root of map archive - self.large_preview_path = str() + self.large_preview_path = "" if self.large_preview_type == 2: self.large_preview_path = data.read_cstring() @@ -604,8 +623,8 @@ def __init__(self, contents): #: 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 - # Leave early so we dont barf. Turns out ggtracker doesnt need - # any of the map data thats loaded below. + # Leave early so we don't barf. Turns out ggtracker doesn't need + # any of the map data that is loaded below. return #: Load screen type: 0 = default, 1 = custom @@ -752,7 +771,7 @@ def __init__(self, contents): 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!") + self.logger.warning("Not all of the MapInfo file was read!") def __str__(self): return self.map_name diff --git a/sc2reader/readers.py b/sc2reader/readers.py index a61a5934..eab6e4c9 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -1,66 +1,107 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import struct from sc2reader.exceptions import ParseError, ReadError -from sc2reader.objects import * -from sc2reader.events.game import * -from sc2reader.events.message import * -from sc2reader.events.tracker import * +from sc2reader.objects import Attribute +from sc2reader.events.game import ( + CameraEvent, + CommandManagerStateEvent, + HijackReplayGameEvent, + PlayerLeaveEvent, + ResourceTradeEvent, + SelectionEvent, + UpdateTargetPointCommandEvent, + UpdateTargetUnitCommandEvent, + UserOptionsEvent, + DialogControlEvent, + create_command_event, + create_control_group_event, +) +from sc2reader.events.message import ChatEvent, PingEvent, ProgressEvent +from sc2reader.events.tracker import ( + PlayerSetupEvent, + PlayerStatsEvent, + UnitBornEvent, + UnitDiedEvent, + UnitDoneEvent, + UnitInitEvent, + UnitOwnerChangeEvent, + UnitPositionsEvent, + UnitTypeChangeEvent, + UpgradeCompleteEvent, +) from sc2reader.utils import DepotFile from sc2reader.decoders import BitPackedDecoder, ByteDecoder -class InitDataReader(object): +class InitDataReader: 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, + 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, + 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, + 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, + 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)) ], @@ -74,27 +115,29 @@ 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), @@ -102,9 +145,11 @@ def __call__(self, data, replay): 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_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), @@ -121,36 +166,40 @@ def __call__(self, data, replay): 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, + 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, + 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, + 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), @@ -164,114 +213,166 @@ def __call__(self, data, replay): 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), + ai_build=( + data.read_bits(8 if replay.base_build >= 38749 else 7) + if replay.base_build >= 23925 + else None + ), + handicap=data.read_bits( + 32 if replay.base_build >= 80669 else 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, + 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 + 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, - trophy_id=data.read_uint32() - if replay.base_build >= 75689 - else None, - reward_overrides=[ + toon_handle=( + data.read_aligned_string(data.read_bits(7)) + if replay.base_build >= 17266 + else None + ), + licenses=( [ - data.read_uint32(), - [data.read_uint32() for i in range(data.read_bits(17))], + data.read_uint32() + for i in range( + data.read_bits( + 16 + if replay.base_build >= 77379 + else 13 if replay.base_build >= 70154 else 9 + ) + ) ] - for j in range(data.read_bits(17)) - ] - if replay.base_build >= 47185 - else None, + 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 + ), + trophy_id=( + data.read_uint32() if replay.base_build >= 75689 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 + ), + brutal_plus_difficulty=( + data.read_uint32() if replay.base_build >= 77379 else None + ), + retry_mutation_indexes=( + [data.read_uint32() for i in range(data.read_bits(3))] + if replay.base_build >= 77379 + else None + ), + ac_enemy_race=( + data.read_uint32() if replay.base_build >= 77379 else None + ), + ac_enemy_wave_type=( + data.read_uint32() if replay.base_build >= 77379 else None + ), + selected_commander_prestige=( + data.read_uint32() if replay.base_build >= 80871 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(f"{data.length - data.tell()} bytes left!") return result -class AttributesEventsReader(object): +class AttributesEventsReader: def __call__(self, data, replay): data = ByteDecoder(data, endian="LITTLE") data.read_bytes(5 if replay.base_build >= 17326 else 4) @@ -289,7 +390,7 @@ def __call__(self, data, replay): return result -class DetailsReader(object): +class DetailsReader: def __call__(self, data, replay): details = BitPackedDecoder(data).read_struct() return dict( @@ -311,9 +412,9 @@ def __call__(self, data, replay): 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 + 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] ], @@ -330,15 +431,17 @@ def __call__(self, data, replay): 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, ) -class MessageEventsReader(object): +class MessageEventsReader: def __call__(self, data, replay): data = BitPackedDecoder(data) pings = list() @@ -377,7 +480,7 @@ def __call__(self, data, replay): return dict(pings=pings, messages=messages, packets=packets) -class GameEventsReader_Base(object): +class GameEventsReader_Base: def __init__(self): self.EVENT_DISPATCH = { 0: (None, self.unknown_event), @@ -396,7 +499,7 @@ def __init__(self): 28: (SelectionEvent, self.selection_delta_event), 29: (create_control_group_event, self.control_group_update_event), 30: (None, self.selection_sync_check_event), - 31: (None, self.resource_trade_event), + 31: (ResourceTradeEvent, self.resource_trade_event), 32: (None, self.trigger_chat_message_event), 33: (None, self.ai_communicate_event), 34: (None, self.set_absolute_game_speed_event), @@ -417,7 +520,7 @@ def __init__(self): 52: (None, self.trigger_purchase_exit_event), 53: (None, self.trigger_planet_mission_launched_event), 54: (None, self.trigger_planet_panel_canceled_event), - 55: (None, self.trigger_dialog_control_event), + 55: (DialogControlEvent, self.trigger_dialog_control_event), 56: (None, self.trigger_sound_length_sync_event), 57: (None, self.trigger_conversation_skipped_event), 58: (None, self.trigger_mouse_clicked_event), @@ -498,9 +601,7 @@ 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) - ), + f"Event type {hex(event_type)} unknown at position {hex(event_start)}.", event_type, event_start, replay, @@ -514,9 +615,7 @@ def __call__(self, data, replay): return game_events except ParseError as e: raise ReadError( - "Parse error '{0}' unknown at position {1}.".format( - e.msg, hex(event_start) - ), + f"Parse error '{e.msg}' unknown at position {hex(event_start)}.", event_type, event_start, replay, @@ -525,9 +624,7 @@ def __call__(self, data, replay): ) except EOFError as e: raise ReadError( - "EOFError error '{0}' unknown at position {1}.".format( - e.msg, hex(event_start) - ), + f"EOFError error '{e.msg}' unknown at position {hex(event_start)}.", event_type, event_start, replay, @@ -536,7 +633,7 @@ def __call__(self, data, replay): ) # 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 @@ -560,7 +657,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 @@ -693,9 +790,11 @@ 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): @@ -958,13 +1057,17 @@ class GameEventsReader_16561(GameEventsReader_15405): def command_event(self, data): return dict( flags=data.read_bits(17), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -985,9 +1088,9 @@ def command_event(self, data): 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, + 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), @@ -1054,7 +1157,7 @@ def control_group_update_event(self, data): 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. + # but I don't care about ability to read old replays. return dict(decrement_ms=data.read_bits(32)) @@ -1072,7 +1175,7 @@ class GameEventsReader_16939(GameEventsReader_16755): class GameEventsReader_17326(GameEventsReader_16939): def __init__(self): - super(GameEventsReader_17326, self).__init__() + super().__init__() self.EVENT_DISPATCH.update({59: (None, self.trigger_mouse_moved_event)}) @@ -1113,13 +1216,17 @@ class GameEventsReader_18574(GameEventsReader_18092): def command_event(self, data): return dict( flags=data.read_bits(18), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -1140,9 +1247,9 @@ def command_event(self, data): 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, + 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), @@ -1164,13 +1271,17 @@ class GameEventsReader_19595(GameEventsReader_19132): def command_event(self, data): return dict( flags=data.read_bits(18), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -1190,12 +1301,12 @@ def command_event(self, data): 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, + 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), @@ -1232,7 +1343,7 @@ class GameEventsReader_21029(GameEventsReader_19595): class GameEventsReader_22612(GameEventsReader_21029): def __init__(self): - super(GameEventsReader_22612, self).__init__() + super().__init__() self.EVENT_DISPATCH.update( { @@ -1261,13 +1372,17 @@ def user_options_event(self, data): def command_event(self, data): return dict( flags=data.read_bits(20), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -1287,12 +1402,12 @@ def command_event(self, data): 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, + 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), @@ -1498,9 +1613,11 @@ def selection_delta_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, @@ -1526,7 +1643,7 @@ def trigger_dialog_control_event(self, data): class GameEventsReader_24247(GameEventsReader_HotSBeta): def __init__(self): - super(GameEventsReader_24247, self).__init__() + super().__init__() self.EVENT_DISPATCH.update( { @@ -1572,12 +1689,16 @@ def hijack_replay_game_event(self, data): 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, + 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)) @@ -1587,9 +1708,11 @@ def hijack_replay_game_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, @@ -1610,12 +1733,16 @@ 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, ) @@ -1667,15 +1794,21 @@ def hijack_replay_game_event(self, data): 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, + 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)) ], @@ -1684,9 +1817,11 @@ def hijack_replay_game_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, @@ -1697,30 +1832,34 @@ 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): def __init__(self): - super(GameEventsReader_34784, self).__init__() + super().__init__() self.EVENT_DISPATCH.update( { 25: ( None, self.command_manager_reset_event, - ), # Re-using this old number + ), # Reusing this old number 61: (None, self.trigger_hotkey_pressed_event), - 103: (None, self.command_manager_state_event), + 103: (CommandManagerStateEvent, self.command_manager_state_event), 104: ( UpdateTargetPointCommandEvent, self.command_update_target_point_event, @@ -1825,13 +1964,17 @@ def command_update_target_unit_event(self, data): def command_event(self, data): return dict( flags=data.read_bits(23), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -1851,12 +1994,12 @@ def command_event(self, data): 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, + 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), @@ -1903,9 +2046,11 @@ 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, @@ -1920,15 +2065,19 @@ 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, ) @@ -1962,7 +2111,7 @@ def control_group_update_event(self, data): class GameEventsReader_38215(GameEventsReader_36442): def __init__(self): - super(GameEventsReader_38215, self).__init__() + super().__init__() self.EVENT_DISPATCH.update( { @@ -1974,13 +2123,17 @@ def __init__(self): def trigger_command_error_event(self, data): return dict( error=data.read_uint32() - 2147483648, - ability=dict( - 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, + ability=( + dict( + 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 + ), ) def trigger_mousewheel_event(self, data): @@ -1995,13 +2148,17 @@ def command_event(self, data): # with the only change being that flags now has 25 bits instead of 23. return dict( flags=data.read_bits(25), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -2021,12 +2178,12 @@ def command_event(self, data): 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, + 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), @@ -2104,19 +2261,22 @@ def trigger_ping_event(self, data): class GameEventsReader_64469(GameEventsReader_38996): - # this function is exactly the same as command_event() from GameEventsReader_38996 # with the only change being that flags now has 26 bits instead of 25. def command_event(self, data): return dict( flags=data.read_bits(26), - ability=dict( - 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, + ability=( + dict( + 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 + ), data={ # Choice 0: lambda: ("None", None), 1: lambda: ( @@ -2136,12 +2296,12 @@ def command_event(self, data): 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, + 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), @@ -2163,7 +2323,7 @@ class GameEventsReader_65895(GameEventsReader_64469): """ def __init__(self): - super(GameEventsReader_65895, self).__init__() + super().__init__() self.EVENT_DISPATCH.update( {116: (None, self.set_sync_loading), 117: (None, self.set_sync_playing)} @@ -2176,7 +2336,70 @@ def set_sync_playing(self, data): return dict(sync_load=data.read_uint32()) -class TrackerEventsReader(object): +class GameEventsReader_80669(GameEventsReader_65895): + # this is almost the same as `command_event` from previous build + # the only addition is introduction of extra command flag: + # > https://news.blizzard.com/en-us/starcraft2/23471116/starcraft-ii-4-13-0-ptr-patch-notes + # > New order command flag: Attack Once + # > When issuing an attack order, it is now allowed to issue an “attack once” order with order command flags. + # > const int c_cmdAttackOnce = 26; + # ideally this part of the code should be more generic so it doesn't have to copy-pasted as a whole + # every time there's a tiny change in one of the sub-structs + def command_event(self, data): + return dict( + flags=data.read_bits(27), + ability=( + dict( + 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 + ), + 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, + ), + ), + ), + 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, + unit_group=data.read_uint32() if data.read_bool() else None, + ) + + +class TrackerEventsReader: def __init__(self): self.EVENT_DISPATCH = { 0: PlayerStatsEvent, diff --git a/sc2reader/resources.py b/sc2reader/resources.py index 8c1d7110..388f1765 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - from collections import defaultdict, namedtuple from datetime import datetime import hashlib @@ -29,7 +26,7 @@ from sc2reader.constants import GAME_SPEED_FACTOR, LOBBY_PROPERTIES -class Resource(object): +class Resource: def __init__(self, file_object, filename=None, factory=None, **options): self.factory = factory self.opt = options @@ -43,14 +40,13 @@ def __init__(self, file_object, filename=None, factory=None, **options): class Replay(Resource): - #: A nested dictionary of player => { attr_name : attr_value } for #: known attributes. Player 16 represents the global context and #: contains attributes like game speed. attributes = defaultdict(dict) #: Fully qualified filename of the replay file represented. - filename = str() + filename = "" #: Total number of frames in this game at 16 frames per second. frames = int() @@ -62,27 +58,27 @@ class Replay(Resource): base_build = int() #: The full version release string as seen on Battle.net - release_string = str() + release_string = "" #: A tuple of the individual pieces of the release string versions = tuple() #: The game speed: Slower, Slow, Normal, Fast, Faster - speed = str() + speed = "" #: Deprecated, use :attr:`game_type` or :attr:`real_type` instead - type = str() + type = "" - #: The game type choosen at game creation: 1v1, 2v2, 3v3, 4v4, FFA - game_type = str() + #: The game type chosen at game creation: 1v1, 2v2, 3v3, 4v4, FFA + game_type = "" #: The real type of the replay as observed by counting players on teams. #: For outmatched games, the smaller team numbers come first. #: Example Values: 1v1, 2v2, 3v3, FFA, 2v4, etc. - real_type = str() + real_type = "" #: The category of the game, Ladder and Private - category = str() + category = "" #: A flag for public ladder games is_ladder = bool() @@ -91,10 +87,10 @@ class Replay(Resource): is_private = bool() #: The raw hash name of the s2ma resource as hosted on bnet depots - map_hash = str() + map_hash = "" #: The name of the map the game was played on - map_name = str() + map_name = "" #: A reference to the loaded :class:`Map` resource. map = None @@ -130,7 +126,7 @@ class Replay(Resource): real_length = None #: The region the game was played on: us, eu, sea, etc - region = str() + region = "" #: An integrated list of all the game events events = list() @@ -187,10 +183,10 @@ class Replay(Resource): #: A sha256 hash uniquely representing the combination of people in the game. #: Can be used in conjunction with date times to match different replays #: of the game game. - people_hash = str() + people_hash = "" #: SC2 Expansion. One of 'WoL', 'HotS' - expansion = str() + expansion = "" #: True of the game was resumed from a replay resume_from_replay = False @@ -208,9 +204,9 @@ def __init__( load_level=4, engine=sc2reader.engine, do_tracker_events=True, - **options + **options, ): - super(Replay, self).__init__(replay_file, filename, **options) + super().__init__(replay_file, filename, **options) self.datapack = None self.raw_data = dict() @@ -277,7 +273,7 @@ def __init__( self.frames = header_data[3] self.build = self.versions[4] self.base_build = self.versions[5] - self.release_string = "{0}.{1}.{2}.{3}".format(*self.versions[1:5]) + self.release_string = "{}.{}.{}.{}".format(*self.versions[1:5]) fps = self.game_fps if 34784 <= self.build: # lotv replay, adjust time fps = self.game_fps * 1.4 @@ -371,9 +367,9 @@ def load_attribute_events(self): 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.speed = self.attributes[16].get("Game Speed", 1.0) + self.category = self.attributes[16].get("Game Mode", "") + self.type = self.game_type = self.attributes[16].get("Teams", "") self.is_ladder = self.category == "Ladder" self.is_private = self.category == "Private" @@ -396,17 +392,17 @@ def load_details(self): dependency_hashes = [d.hash for d in details["cache_handles"]] if ( - hashlib.sha256("Standard Data: Void.SC2Mod".encode("utf8")).hexdigest() + hashlib.sha256(b"Standard Data: Void.SC2Mod").hexdigest() in dependency_hashes ): self.expansion = "LotV" elif ( - hashlib.sha256("Standard Data: Swarm.SC2Mod".encode("utf8")).hexdigest() + hashlib.sha256(b"Standard Data: Swarm.SC2Mod").hexdigest() in dependency_hashes ): self.expansion = "HotS" elif ( - hashlib.sha256("Standard Data: Liberty.SC2Mod".encode("utf8")).hexdigest() + hashlib.sha256(b"Standard Data: Liberty.SC2Mod").hexdigest() in dependency_hashes ): self.expansion = "WoL" @@ -421,18 +417,17 @@ 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 + 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] - ) + seconds=self.length.seconds + // GAME_SPEED_FACTOR[self.expansion].get(self.speed, 1.0) ) self.start_time = datetime.utcfromtimestamp( self.unix_timestamp - self.real_length.seconds @@ -551,14 +546,14 @@ def get_team(team_id): # Pull results up for teams for team in self.teams: - results = set([p.result for p in team.players]) + results = {p.result for p in team.players} if len(results) == 1: team.result = list(results)[0] if team.result == "Win": self.winner = team else: - self.logger.warn( - "Conflicting results for Team {0}: {1}".format(team.number, results) + self.logger.warning( + f"Conflicting results for Team {team.number}: {results}" ) team.result = "Unknown" @@ -583,7 +578,7 @@ def get_team(team_id): # Pretty sure this just never worked, forget about it for now self.recorder = None - entity_names = sorted(map(lambda p: p.name, self.entities)) + entity_names = sorted((p.name for p in self.entities)) hash_input = self.region + ":" + ",".join(entity_names) self.people_hash = hashlib.sha256(hash_input.encode("utf8")).hexdigest() @@ -774,7 +769,12 @@ def register_default_readers(self): self.register_reader( "replay.game.events", readers.GameEventsReader_65895(), - lambda r: 65895 <= r.base_build, + lambda r: 65895 <= r.base_build < 80669, + ) + self.register_reader( + "replay.game.events", + readers.GameEventsReader_80669(), + lambda r: 80669 <= r.base_build, ) self.register_reader( "replay.game.events", @@ -858,7 +858,23 @@ def register_default_datapacks(self): ) self.register_datapack( datapacks["LotV"]["70154"], - lambda r: r.expansion == "LotV" and 70154 <= r.build, + lambda r: r.expansion == "LotV" and 70154 <= r.build < 76114, + ) + self.register_datapack( + datapacks["LotV"]["76114"], + lambda r: r.expansion == "LotV" and 76114 <= r.build < 77379, + ) + self.register_datapack( + datapacks["LotV"]["77379"], + lambda r: r.expansion == "LotV" and 77379 <= r.build < 80949, + ) + self.register_datapack( + datapacks["LotV"]["80949"], + lambda r: r.expansion == "LotV" and 80949 <= r.build < 89634, + ) + self.register_datapack( + datapacks["LotV"]["89720"], + lambda r: r.expansion == "LotV" and 89634 <= r.build, ) # Internal Methods @@ -868,9 +884,7 @@ def _get_reader(self, data_file): return reader else: raise ValueError( - "Valid {0} reader could not found for build {1}".format( - data_file, self.build - ) + f"Valid {data_file} reader could not found for build {self.build}" ) def _get_datapack(self): @@ -888,7 +902,7 @@ def _read_data(self, data_file, reader): "replay.message.events", "replay.tracker.events", ]: - raise ValueError("{0} not found in archive".format(data_file)) + raise ValueError(f"{data_file} not found in archive") def __getstate__(self): state = self.__dict__.copy() @@ -898,22 +912,20 @@ def __getstate__(self): class Map(Resource): - 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) + super().__init__(map_file, filename, **options) #: The localized (only enUS supported right now) map name. - self.name = str() + self.name = "" #: The localized (only enUS supported right now) map author. - self.author = str() + self.author = "" #: The localized (only enUS supported right now) map description. - self.description = str() + self.description = "" #: The localized (only enUS supported right now) map website. - self.website = str() + self.website = "" #: The unique hash used to identify this map on bnet's depots. self.hash = map_hash @@ -935,7 +947,7 @@ def __init__(self, map_file, filename=None, region=None, map_hash=None, **option # 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" + r"enUS.SC2Data\LocalizedData\GameStrings.txt" ) if game_strings_file: for line in game_strings_file.decode("utf8").split("\r\n"): @@ -982,9 +994,7 @@ def __init__(self, map_file, filename=None, region=None, map_hash=None, **option 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 - return cls.url_template.format(region, map_hash) + return utils.get_resource_url(region, hash, "s2ma") else: return None @@ -998,11 +1008,15 @@ def __init__(self, s2ml_file, **options): class GameSummary(Resource): - - url_template = "http://{0}.depot.battle.net:1119/{1}.s2gs" + """ + Data extracted from the post-game Game Summary with units killed, + etc. This code does not work as reliably for Co-op games, which + have a completely different format for the report, which means + that the data is not necessarily in the places we expect. + """ #: Game speed - game_speed = str() + game_speed = "" #: Game length (real-time) real_length = int() @@ -1029,7 +1043,7 @@ class GameSummary(Resource): localization_urls = dict() def __init__(self, summary_file, filename=None, lang="enUS", **options): - super(GameSummary, self).__init__(summary_file, filename, lang=lang, **options) + super().__init__(summary_file, filename, lang=lang, **options) #: A dict of team# -> teams self.team = dict() @@ -1056,8 +1070,8 @@ def __init__(self, summary_file, filename=None, lang="enUS", **options): self.localization_urls = dict() self.lobby_properties = dict() self.lobby_player_properties = dict() - self.game_type = str() - self.real_type = str() + self.game_type = "" + self.real_type = "" # The first 16 bytes appear to be some sort of compression header buffer = BitPackedDecoder(zlib.decompress(summary_file.read()[16:])) @@ -1094,7 +1108,9 @@ def __init__(self, summary_file, filename=None, lang="enUS", **options): self.load_player_stats() self.load_players() - self.game_type = self.settings["Teams"].replace(" ", "") + # the game type is probably co-op because it uses a different + # game summary format than other games + self.game_type = self.settings.get("Teams", "Unknown").replace(" ", "") self.real_type = utils.get_real_type(self.teams) # The s2gs file also keeps reference to a series of s2mv files @@ -1133,8 +1149,8 @@ def load_translations(self): self.id_map[uid] = (sheet, entry) for value in item[1]: - sheet = value[1][0][1] - entry = value[1][0][2] + sheet = value[1][0][1] if value[1][0] else None + entry = value[1][0][2] if value[1][0] else None self.id_map[(uid, value[0])] = (sheet, entry) # Each localization is a pairing of a language id, e.g. enUS @@ -1172,7 +1188,7 @@ def load_translations(self): translation = dict() for uid, (sheet, item) in self.id_map.items(): - if sheet < len(sheets) and item in sheets[sheet]: + if sheet is not None and sheet < len(sheets) and item in sheets[sheet]: translation[uid] = sheets[sheet][item] elif self.opt["debug"]: msg = "No {0} translation for sheet {1}, item {2}" @@ -1230,7 +1246,7 @@ def use_property(prop, player=None): # Lobby properties can require on player properties. # How does this work? I assume that one player satisfying the - # property requirments is sufficient + # property requirements is sufficient if requirement.is_lobby: values = [setting] else: @@ -1238,7 +1254,7 @@ def use_property(prop, player=None): # Because of the above complication we resort to a set intersection of # the applicable values and the set of required values. - if not set(requirement.values[val][0] for val in values) & set(req[1]): + if not {requirement.values[val][0] for val in values} & set(req[1]): break else: @@ -1265,7 +1281,7 @@ def use_property(prop, player=None): def load_player_stats(self): translation = self.translations[self.opt["lang"]] - stat_items = sum([p[0] for p in self.parts[3:]], []) + stat_items = sum((p[0] for p in self.parts[3:]), []) for item in stat_items: # Each stat item is laid out as follows @@ -1318,7 +1334,7 @@ def load_player_stats(self): ) ) elif stat_id != 83886080: # We know this one is always bad. - self.logger.warn("Untranslatable key = {0}".format(stat_id)) + self.logger.warning(f"Untranslatable key = {stat_id}") # Once we've compiled all the build commands we need to make # sure they are properly sorted for presentation. @@ -1414,7 +1430,7 @@ def load_players(self): self.player[player.pid] = player def __str__(self): - return "{0} - {1} {2}".format( + return "{} - {} {}".format( self.start_time, self.game_length, "v".join( @@ -1426,24 +1442,20 @@ def __str__(self): 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" - #: The name of the map - name = str() + name = "" #: Hash of map file - map_hash = str() + map_hash = "" #: Link to the map file - map_url = str() + map_url = "" #: Hash of the map image - image_hash = str() + image_hash = "" #: Link to the image of the map (.s2mv) - image_url = str() + image_url = "" #: Localization dictionary, {language, url} localization_urls = dict() @@ -1452,7 +1464,7 @@ class MapHeader(Resource): blizzard = False def __init__(self, header_file, filename=None, **options): - super(MapHeader, self).__init__(header_file, filename, **options) + super().__init__(header_file, filename, **options) self.data = BitPackedDecoder(header_file).read_struct() # Name @@ -1464,21 +1476,21 @@ def __init__(self, header_file, filename=None, **options): # 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_url = utils.get_resource_url( + parsed_hash["server"], parsed_hash["hash"], "s2mv" ) # 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( + self.map_url = utils.get_resource_url( 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( + for h in l18n_struct: + parsed_hash = utils.parse_hash(h[1][0]) + self.localization_urls[h[0]] = utils.get_resource_url( parsed_hash["server"], parsed_hash["hash"], parsed_hash["type"] ) diff --git a/sc2reader/scripts/__init__.py b/sc2reader/scripts/__init__.py index e826f791..551b70ab 100755 --- a/sc2reader/scripts/__init__.py +++ b/sc2reader/scripts/__init__.py @@ -1,5 +1,2 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - # import submodules from sc2reader.scripts import utils diff --git a/sc2reader/scripts/sc2attributes.py b/sc2reader/scripts/sc2attributes.py index 109cf30b..6e2ca4d1 100644 --- a/sc2reader/scripts/sc2attributes.py +++ b/sc2reader/scripts/sc2attributes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Recursively searches for s2gs files in specified paths. Adds # new attributes and values and allows the user to choose when @@ -28,7 +27,6 @@ # those decisions. The decisions are pickled instead of in json # because the data structure is too complex for the json format. # -from __future__ import absolute_import, print_function, unicode_literals, division import argparse import json @@ -50,7 +48,7 @@ def main(): global decisions parser = argparse.ArgumentParser( - description="Recursively parses replay files, inteded for debugging parse issues." + description="Recursively parses replay files, intended for debugging parse issues." ) parser.add_argument( "folders", metavar="folder", type=str, nargs="+", help="Path to a folder" @@ -64,7 +62,7 @@ def main(): attributes = dict() if os.path.exists(data_path): - with open(data_path, "r") as data_file: + with open(data_path) as data_file: data = json.load(data_file) attributes = data.get("attributes", attributes) decisions = pickle.loads(data.get("decisions", "(dp0\n.")) @@ -100,7 +98,7 @@ def main(): attribute_values[str(value_key)] = value_name - attributes["{0:0>4}".format(group_key)] = ( + attributes[f"{group_key:0>4}"] = ( group_name, attribute_values, ) @@ -121,16 +119,14 @@ 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(f"Naming conflict on {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)) + print(f" (o) Old value '{old_value}'") + print(f" (n) New value '{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)) + print(f"Invalid choice `{answer}`") else: break decisions[key] = {"o": old_value, "n": new_value}[answer] diff --git a/sc2reader/scripts/sc2json.py b/sc2reader/scripts/sc2json.py index 7cc2f331..d2fdb8ef 100755 --- a/sc2reader/scripts/sc2json.py +++ b/sc2reader/scripts/sc2json.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division import sc2reader from sc2reader.factories.plugins.replay import toJSON @@ -34,9 +32,12 @@ def main(): args = parser.parse_args() factory = sc2reader.factories.SC2Factory() - factory.register_plugin( - "Replay", toJSON(encoding=args.encoding, indent=args.indent) - ) + try: + factory.register_plugin( + "Replay", toJSON(encoding=args.encoding, indent=args.indent) + ) # legacy Python + except TypeError: + factory.register_plugin("Replay", toJSON(indent=args.indent)) replay_json = factory.load_replay(args.path[0]) print(replay_json) diff --git a/sc2reader/scripts/sc2parse.py b/sc2reader/scripts/sc2parse.py index 311db343..4b517908 100755 --- a/sc2reader/scripts/sc2parse.py +++ b/sc2reader/scripts/sc2parse.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ This script is intended for use debugging parse issues on replays. @@ -16,7 +15,6 @@ If there are parse exceptions, this script should be run to generate an info for the ticket filed. """ -from __future__ import absolute_import, print_function, unicode_literals, division import argparse import sc2reader @@ -27,7 +25,7 @@ def main(): parser = argparse.ArgumentParser( - description="Recursively parses replay files, inteded for debugging parse issues." + description="Recursively parses replay files, intended for debugging parse issues." ) parser.add_argument( "--one_each", @@ -46,7 +44,7 @@ def main(): releases_parsed = set() for folder in args.folders: - print("dealing with {0}".format(folder)) + print(f"dealing with {folder}") for path in sc2reader.utils.get_files(folder, extension="SC2Replay"): try: rs = sc2reader.load_replay(path, load_level=0).release_string @@ -57,29 +55,23 @@ def main(): if not args.one_each or replay.is_ladder: 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 - ] - ) + human_pids = {human.pid for human in replay.humans} + event_pids = { + event.player.pid + for event in replay.events + if getattr(event, "player", None) + } + player_pids = { + player.pid for player in replay.players if player.is_human + } + ability_pids = { + 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 - ) + f"Event Pid problem! pids={human_pids} but event pids={event_pids}" ) print( " with {path}: {build} - {real_type} on {map_name} - Played {start_time}".format( @@ -88,9 +80,7 @@ def main(): ) elif player_pids != ability_pids: print( - "Ability Pid problem! pids={pids} but event pids={event_pids}".format( - pids=player_pids, event_pids=ability_pids - ) + f"Ability Pid problem! pids={player_pids} but event pids={ability_pids}" ) print( " with {path}: {build} - {real_type} on {map_name} - Played {start_time}".format( @@ -104,11 +94,7 @@ def main(): ) ) print( - "Units were: {units}".format( - units=set( - [obj.name for obj in replay.objects.values()] - ) - ) + f"Units were: {({obj.name for obj in replay.objects.values()})}" ) except sc2reader.exceptions.ReadError as e: @@ -124,7 +110,7 @@ def main(): ) print("[ERROR] {}", e) for event in e.game_events[-5:]: - print("{0}".format(event)) + print(f"{event}") print(e.buffer.read_range(e.location, e.location + 50).encode("hex")) print except Exception as e: @@ -137,13 +123,13 @@ def main(): **replay.__dict__ ) ) - print("[ERROR] {0}".format(e)) + print(f"[ERROR] {e}") for pid, attributes in replay.attributes.items(): - print("{0} {1}".format(pid, attributes)) + print(f"{pid} {attributes}") for pid, info in enumerate(replay.players): - print("{0} {1}".format(pid, info)) + print(f"{pid} {info}") for message in replay.messages: - print("{0} {1}".format(message.pid, message.text)) + print(f"{message.pid} {message.text}") traceback.print_exc() print("") except Exception as e2: @@ -153,8 +139,8 @@ def main(): **replay.__dict__ ) ) - print("[ERROR] {0}".format(e)) - print("[ERROR] {0}".format(e2)) + print(f"[ERROR] {e}") + print(f"[ERROR] {e2}") traceback.print_exc() print diff --git a/sc2reader/scripts/sc2printer.py b/sc2reader/scripts/sc2printer.py index 92cfe686..76241a05 100755 --- a/sc2reader/scripts/sc2printer.py +++ b/sc2reader/scripts/sc2printer.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division import os import argparse @@ -11,62 +9,52 @@ def printReplay(filepath, arguments): - """ Prints summary information about SC2 replay file """ + """Prints summary information about SC2 replay file""" try: replay = sc2reader.load_replay(filepath, debug=True) if arguments.map: - print(" Map: {0}".format(replay.map_name)) + print(f" Map: {replay.map_name}") if arguments.length: - print(" Length: {0} minutes".format(replay.game_length)) + print(f" Length: {replay.game_length} minutes") if arguments.date: - print(" Date: {0}".format(replay.start_time)) + print(f" Date: {replay.start_time}") if arguments.teams: lineups = [team.lineup for team in replay.teams] - print(" Teams: {0}".format("v".join(lineups))) + print(" Teams: {}".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] - ) + f" Team {team.number}\t{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(f" \t{player.name} ({player.pick_race[0]})") if arguments.observers: print(" Observers:") for observer in replay.observers: - print(" {0}".format(observer.name)) + print(f" {observer.name}") if arguments.messages: print(" Messages:") for message in replay.messages: - print(" {0}".format(message)) + print(f" {message}") if arguments.version: - print(" Version: {0}".format(replay.release_string)) + print(f" Version: {replay.release_string}") print except ReadError as e: raise return prev = e.game_events[-1] - 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(f"\nVersion {e.replay.release_string} replay:\n\t{e.replay.filepath}") + print(f"\t{e.msg}, Type={e.type:X}") + print(f"\tPrevious Event: {prev.name}") 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("Error with '{0}': ".format(filepath)) + print(f"Error with '{filepath}': ") print(e) except Exception as e: - print("Error with '{0}': ".format(filepath)) + print(f"Error with '{filepath}': ") print(e) raise @@ -75,21 +63,21 @@ def printGameSummary(filepath, arguments): summary = sc2reader.load_game_summary(filepath) if arguments.map: - print(" Map: {0}".format(summary.map_name)) + print(f" Map: {summary.map_name}") if arguments.length: - print(" Length: {0} minutes".format(summary.game_length)) + print(f" Length: {summary.game_length} minutes") if arguments.date: - print(" Date: {0}".format(summary.start_time)) + print(f" Date: {summary.start_time}") if arguments.teams: lineups = [team.lineup for team in summary.teams] - print(" Teams: {0}".format("v".join(lineups))) + print(" Teams: {}".format("v".join(lineups))) for team in summary.teams: - print(" Team {0}\t{1}".format(team.number, team.players[0])) + print(f" Team {team.number}\t{team.players[0]}") for player in team.players[1:]: - print(" \t{0}".format(player)) + print(f" \t{player}") if arguments.builds: for player in summary.players: - print("\n== {0} ==\n".format(player)) + print(f"\n== {player} ==\n") for order in summary.build_orders[player.pid]: msg = " {0:0>2}:{1:0>2} {2:<35} {3:0>2}/{4}" print( @@ -176,14 +164,10 @@ def main(): 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) - ) + print(f"\n--------------------------------------\n{filepath}\n") printReplay(filepath, arguments) elif ext.lower() == ".s2gs": - print( - "\n--------------------------------------\n{0}\n".format(filepath) - ) + print(f"\n--------------------------------------\n{filepath}\n") printGameSummary(filepath, arguments) diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index d8711962..c3c8ee17 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division try: # Assume that we are on *nix or Mac @@ -22,25 +20,32 @@ def getch(): try: sys.stdin.read(1) break - except IOError: + except OSError: pass finally: 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 + # Oops, we might be on windows, try this one from msvcrt import getch except ImportError as e: # We can't make getch happen, just dump events to the screen - getch = lambda: True + def getch(): + return True import argparse import sc2reader -from sc2reader.events import * +from sc2reader.events import ( + CameraEvent, + CommandEvent, + ControlGroupEvent, + GameStartEvent, + PlayerLeaveEvent, + SelectionEvent, +) def main(): @@ -79,15 +84,13 @@ def main(): 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(f"Release {replay.release_string}") + print(f"{replay.type} on {replay.map_name} at {replay.start_time}") print("") for team in replay.teams: print(team) for player in team.players: - print(" {0}".format(player)) + print(f" {player}") print("\n--------------------------\n\n") # Allow picking of the player to 'watch' @@ -99,13 +102,12 @@ def main(): # Allow specification of events to `show` # 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.hotkeys and isinstance(event, ControlGroupEvent)) or (args.cameras and isinstance(event, CameraEvent)) ): print(event) diff --git a/sc2reader/scripts/utils.py b/sc2reader/scripts/utils.py deleted file mode 100644 index cf998e5d..00000000 --- a/sc2reader/scripts/utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - -import argparse -import re -import textwrap - - -class Formatter(argparse.RawTextHelpFormatter): - """FlexiFormatter which respects new line formatting and wraps the rest - - Example: - >>> parser = argparse.ArgumentParser(formatter_class=FlexiFormatter) - >>> parser.add_argument('a',help='''\ - ... This argument's help text will have this first long line\ - ... wrapped to fit the target window size so that your text\ - ... remains flexible. - ... - ... 1. This option list - ... 2. is still persisted - ... 3. and the option strings get wrapped like this\ - ... with an indent for readability. - ... - ... You must use backslashes at the end of lines to indicate that\ - ... you want the text to wrap instead of preserving the newline. - ... ''') - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - @classmethod - def new(cls, **options): - return lambda prog: Formatter(prog, **options) - - def _split_lines(self, text, width): - lines = list() - 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: - 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() - new_lines = textwrap.wrap( - text=line, - width=width, - 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 [" "]) - - return lines diff --git a/sc2reader/utils.py b/sc2reader/utils.py index 41e4ed09..660f07bd 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -1,17 +1,14 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import binascii -import os import json +import os from datetime import timedelta, datetime -from sc2reader.log_utils import loggable -from sc2reader.exceptions import MPQError from sc2reader.constants import COLOR_CODES, COLOR_CODES_INV +from sc2reader.exceptions import MPQError +from sc2reader.log_utils import loggable -class DepotFile(object): +class DepotFile: """ :param bytes: The raw bytes representing the depot file @@ -19,16 +16,9 @@ class DepotFile(object): and assembles them into a URL so that the dependency can be fetched. """ - #: The url template for all DepotFiles - 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 ") - - # There is no SEA depot, use US instead - if self.server == "SEA": - self.server = "US" + self.server = bytes[4:8].decode("utf-8").strip("\x00 ").lower() #: The unique content based hash of the file self.hash = binascii.b2a_hex(bytes[8:]).decode("utf8") @@ -38,8 +28,8 @@ def __init__(self, bytes): @property def url(self): - """ Returns url of the depot file. """ - return self.url_template.format(self.server, self.hash, self.type) + """Returns url of the depot file.""" + return get_resource_url(self.server, self.hash, self.type) def __hash__(self): return hash(self.url) @@ -52,11 +42,11 @@ def windows_to_unix(windows_time): # This windows timestamp measures the number of 100 nanosecond periods since # January 1st, 1601. First we subtract the number of nanosecond periods from # 1601-1970, then we divide by 10^7 to bring it back to seconds. - return int((windows_time - 116444735995904000) / 10 ** 7) + return int((windows_time - 116444735995904000) / 10**7) @loggable -class Color(object): +class Color: """ Stores a color name and rgba representation of a color. Individual color components can be retrieved with the dot operator:: @@ -75,7 +65,7 @@ class Color(object): 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) + self.logger.warning(f"Invalid color name: {name}") hexstr = COLOR_CODES_INV.get(name, "000000") self.r = int(hexstr[0:2], 16) self.g = int(hexstr[2:4], 16) @@ -88,18 +78,22 @@ def __init__(self, name=None, r=0, g=0, b=0, a=255): self.b = b self.a = a if self.hex not in COLOR_CODES: - self.logger.warn("Invalid color hex value: " + self.hex) + self.logger.warning(f"Invalid color hex value: {self.hex}") self.name = COLOR_CODES.get(self.hex, self.hex) @property def rgba(self): - """ Returns a tuple containing the color's (r,g,b,a) """ + """ + Returns a tuple containing the color's (r,g,b,a) + """ return (self.r, self.g, self.b, self.a) @property def hex(self): - """The hexadecimal representation of the color""" - return "{0.r:02X}{0.g:02X}{0.b:02X}".format(self) + """ + The hexadecimal representation of the color + """ + return f"{self.r:02X}{self.g:02X}{self.b:02X}" def __str__(self): return self.name @@ -148,7 +142,7 @@ def recovery_attempt(): # Python2 and Python3 handle wrapped exceptions with old tracebacks in incompatible ways # Python3 handles it by default and Python2's method won't compile in python3 # Since the underlying traceback isn't important to most people, don't expose it anymore - raise MPQError("Unable to extract file: {0}".format(data_file), e) + raise MPQError(f"Unable to extract file: {data_file}", e) def get_files( @@ -165,15 +159,18 @@ def get_files( """ # os.walk and os.path.isfile fail silently. We want to be loud! if not os.path.exists(path): - raise ValueError("Location `{0}` does not exist".format(path)) + raise ValueError(f"Location `{path}` does not exist") # 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() - ) + + def type_check(path): + return os.path.splitext(path)[1][1:].lower() == extension.lower() + else: - type_check = lambda n: True + + def type_check(n): + return True # os.walk can't handle file paths, only directories if os.path.isfile(path): @@ -196,31 +193,51 @@ def get_files( depth -= 1 +def get_resource_url(region, hash, type): + url_template = "{}://{}-s2-depot.{}/{}.{}" + scheme = "https" + domain = "classic.blizzard.com" + + if region == "sea": + region = "us" + elif region == "cn": + scheme = "http" + domain = "battlenet.com.cn" + return url_template.format(scheme, region, domain, hash, type) + + class Length(timedelta): - """ Extends the builtin timedelta class. See python docs for more info on - what capabilities this gives you. + """ + Extends the builtin timedelta class. See python docs for more info on + what capabilities this gives you. """ @property def hours(self): - """ The number of hours in represented. """ + """ + The number of hours in represented. + """ return self.seconds // 3600 @property def mins(self): - """ The number of minutes in excess of the hours. """ + """ + The number of minutes in excess of the hours. + """ return self.seconds // 60 % 60 @property def secs(self): - """ The number of seconds in excess of the minutes. """ + """ + The number of seconds in excess of the minutes. + """ return self.seconds % 60 def __str__(self): if self.hours: - return "{0:0>2}.{1:0>2}.{2:0>2}".format(self.hours, self.mins, self.secs) + return f"{self.hours:0>2}.{self.mins:0>2}.{self.secs:0>2}" else: - return "{0:0>2}.{1:0>2}".format(self.mins, self.secs) + return f"{self.mins:0>2}.{self.secs:0>2}" class JSONDateEncoder(json.JSONEncoder): @@ -301,7 +318,6 @@ def toDict(replay): "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), diff --git a/setup.py b/setup.py deleted file mode 100644 index 90c1d629..00000000 --- a/setup.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys -import setuptools - -setuptools.setup( - license="MIT", - name="sc2reader", - version="1.4.0", - 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(), - 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", - ], - 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", - ] - }, - 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, -) diff --git a/test_replays/4.11.0.77379/Oblivion Express.SC2Replay b/test_replays/4.11.0.77379/Oblivion Express.SC2Replay new file mode 100644 index 00000000..c1646f4d Binary files /dev/null and b/test_replays/4.11.0.77379/Oblivion Express.SC2Replay differ diff --git a/test_replays/5.0.0.80949/2020-07-28 - (T)Ocrucius VS (Z)Rairden.SC2Replay b/test_replays/5.0.0.80949/2020-07-28 - (T)Ocrucius VS (Z)Rairden.SC2Replay new file mode 100644 index 00000000..a2fbc3ee Binary files /dev/null and b/test_replays/5.0.0.80949/2020-07-28 - (T)Ocrucius VS (Z)Rairden.SC2Replay differ diff --git a/test_replays/test_replays.py b/test_replays/test_replays.py index 8d779eea..4ea53c20 100644 --- a/test_replays/test_replays.py +++ b/test_replays/test_replays.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime import json from xml.dom import minidom @@ -79,12 +76,8 @@ 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, "https://starcraft2.com/en-us/profile/2/1/520049") + self.assertEqual(boom.url, "https://starcraft2.com/en-us/profile/2/1/1694745") self.assertEqual(len(replay.messages), 12) self.assertEqual(replay.messages[0].text, "hf") @@ -163,10 +156,10 @@ def test_us_realm(self): 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/", + "https://starcraft2.com/en-us/profile/1/1/2358439", ) self.assertEqual( - reddawn.url, "http://us.battle.net/sc2/en/profile/2198663/1/reddawn/" + reddawn.url, "https://starcraft2.com/en-us/profile/1/1/2198663" ) def test_kr_realm_and_tampered_messages(self): @@ -177,13 +170,11 @@ def test_kr_realm_and_tampered_messages(self): replay = sc2reader.load_replay("test_replays/1.1.3.16939/11.SC2Replay") 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/티에스엘사기수/" - ) + second = [ + player for player in replay.players if player.name == "티에스엘사기수" + ][0] + self.assertEqual(first.url, "https://starcraft2.com/en-us/profile/3/1/258945") + self.assertEqual(second.url, "https://starcraft2.com/en-us/profile/3/1/102472") self.assertEqual(replay.messages[0].text, "sc2.replays.net") self.assertEqual(replay.messages[5].text, "sc2.replays.net") @@ -219,19 +210,14 @@ def test_hots_pids(self): "test_replays/2.0.0.24247/molten.SC2Replay", "test_replays/2.0.0.23925/Akilon Wastes.SC2Replay", ]: - 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 = {player.pid for player in replay.players if player.is_human} + ability_pids = { + event.player.pid + for event in replay.events + if "CommandEvent" in event.name + } self.assertEqual(ability_pids, player_pids) def test_wol_pids(self): @@ -239,27 +225,21 @@ def test_wol_pids(self): "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 - ] - ) - player_pids = set([player.pid for player in replay.players]) + ability_pids = { + event.player.pid for event in replay.events if "CommandEvent" in event.name + } + player_pids = {player.pid for player in replay.players} self.assertEqual(ability_pids, player_pids) def test_hots_hatchfun(self): replay = sc2reader.load_replay("test_replays/2.0.0.24247/molten.SC2Replay") - player_pids = set([player.pid for player in replay.players]) - spawner_pids = set( - [ - event.player.pid - for event in replay.events - if "TargetUnitCommandEvent" in event.name - and event.ability.name == "SpawnLarva" - ] - ) + player_pids = {player.pid for player in replay.players} + spawner_pids = { + event.player.pid + for event in replay.events + if "TargetUnitCommandEvent" in event.name + and event.ability.name == "SpawnLarva" + } self.assertTrue(spawner_pids.issubset(player_pids)) def test_hots_vs_ai(self): @@ -305,6 +285,15 @@ def test_send_resources(self): replay = sc2reader.load_replay( "test_replays/2.0.4.24944/Backwater Complex (15).SC2Replay" ) + trade_events = [ + event for event in replay.events if event.name == "ResourceTradeEvent" + ] + self.assertEqual(len(trade_events), 5) + self.assertEqual(trade_events[0].sender.name, "Guardian") + self.assertEqual(trade_events[0].recipient.name, "Sturmkind") + self.assertEqual(trade_events[0].recipient_id, 2) + self.assertEqual(trade_events[0].minerals, 0) + self.assertEqual(trade_events[0].vespene, 750) def test_cn_replays(self): replay = sc2reader.load_replay("test_replays/2.0.5.25092/cn1.SC2Replay") @@ -312,7 +301,7 @@ def test_cn_replays(self): self.assertEqual(replay.expansion, "WoL") def test_unit_types(self): - """ sc2reader#136 regression test """ + """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" @@ -406,15 +395,13 @@ def test_gameheartnormalizer_plugin(self): # Not a GameHeart game! replay = sc2reader.load_replay("test_replays/2.0.0.24247/molten.SC2Replay") - player_pids = set([player.pid for player in replay.players]) - spawner_pids = set( - [ - event.player.pid - for event in replay.events - if "TargetUnitCommandEvent" in event.name - and event.ability.name == "SpawnLarva" - ] - ) + player_pids = {player.pid for player in replay.players} + spawner_pids = { + event.player.pid + for event in replay.events + if "TargetUnitCommandEvent" in event.name + and event.ability.name == "SpawnLarva" + } self.assertTrue(spawner_pids.issubset(player_pids)) replay = sc2reader.load_replay("test_replays/gameheart/gameheart.SC2Replay") @@ -540,8 +527,8 @@ def test_30(self): def test_31(self): for i in range(1, 5): - print("DOING {}".format(i)) - replay = sc2reader.load_replay("test_replays/3.1.0/{}.SC2Replay".format(i)) + print(f"DOING {i}") + replay = sc2reader.load_replay(f"test_replays/3.1.0/{i}.SC2Replay") def test_30_map(self): for replayfilename in ["test_replays/3.0.0.38215/third.SC2Replay"]: @@ -586,13 +573,11 @@ def test_funny_minerals(self): 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 - ] - ) + { + ou.attributes["UnitType"].value + for ou in itemlist + if "MineralField" in ou.attributes["UnitType"].value + } ) # print(mineralFieldNames) self.assertTrue(len(mineralPosStrs) > 0) @@ -607,9 +592,7 @@ 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(f"test_replays/3.3.0/{replaynum}.SC2Replay") self.assertTrue(replay is not None) def test_33_shift_click_calldown_mule(self): @@ -701,6 +684,18 @@ def test_75689(self): replay = factory.load_replay(replayfilename) self.assertEqual(replay.players[0].trophy_id, 13) + def test_77379(self): + replay = sc2reader.load_replay( + "test_replays/4.11.0.77379/Oblivion Express.SC2Replay" + ) + self.assertEqual(replay.players[0].commander, "Mengsk") + self.assertEqual(replay.players[1].commander, "Stetmann") + + def test_80949(self): + replay = sc2reader.load_replay( + "test_replays/5.0.0.80949/2020-07-28 - (T)Ocrucius VS (Z)Rairden.SC2Replay" + ) + def test_anonymous_replay(self): replayfilename = "test_replays/4.1.2.60604/1.SC2Replay" factory = sc2reader.factories.SC2Factory() @@ -714,7 +709,7 @@ def test_game_event_string(self): player.play_race = "TestRace" event = GameEvent(16, 16) event.player = player - self.assertEqual("{0}\t{1:<15} ".format(time, "Global"), event._str_prefix()) + self.assertEqual("{}\t{:<15} ".format(time, "Global"), event._str_prefix()) # Player with name player = MockPlayer() @@ -722,12 +717,12 @@ def test_game_event_string(self): player.play_race = "TestRace" event = GameEvent(16, 1) event.player = player - self.assertEqual("{0}\t{1:<15} ".format(time, player.name), event._str_prefix()) + self.assertEqual(f"{time}\t{player.name:<15} ", event._str_prefix()) # No Player player = MockPlayer() event = GameEvent(16, 1) - self.assertEqual("{0}\t{1:<15} ".format(time, "no name"), event._str_prefix()) + self.assertEqual("{}\t{:<15} ".format(time, "no name"), event._str_prefix()) # Player without name player = MockPlayer() @@ -736,13 +731,13 @@ def test_game_event_string(self): event = GameEvent(16, 1) event.player = player self.assertEqual( - "{0}\tPlayer {1} - ({2}) ".format(time, player.pid, player.play_race), + f"{time}\tPlayer {player.pid} - ({player.play_race}) ", event._str_prefix(), ) class TestGameEngine(unittest.TestCase): - class TestEvent(object): + class TestEvent: name = "TestEvent" def __init__(self, value): @@ -751,7 +746,7 @@ def __init__(self, value): def __str__(self): return self.value - class TestPlugin1(object): + class TestPlugin1: name = "TestPlugin1" def handleInitGame(self, event, replay): @@ -769,7 +764,7 @@ def handleTestEvent(self, event, replay): def handleEndGame(self, event, replay): yield TestGameEngine.TestEvent("g") - class TestPlugin2(object): + class TestPlugin2: name = "TestPlugin2" def handleInitGame(self, event, replay): @@ -784,7 +779,7 @@ def handlePluginExit(self, event, replay): def handleEndGame(self, event, replay): yield TestGameEngine.TestEvent("f") - class MockReplay(object): + class MockReplay: def __init__(self, events): self.events = events @@ -800,7 +795,7 @@ def test_plugin1(self): self.assertEqual(replay.plugin_result["TestPlugin2"], (0, dict())) -class MockPlayer(object): +class MockPlayer: def __init__(self): self.name = None self.play_race = None diff --git a/test_s2gs/test_all.py b/test_s2gs/test_all.py index be867add..75a1daa9 100644 --- a/test_s2gs/test_all.py +++ b/test_s2gs/test_all.py @@ -1,5 +1,3 @@ -# -*- coding: UTF-8 -*- - # Newer unittest features aren't built in for python 2.6 import sys