From 2a493562c23a6535c2bed4c22be35da876849d79 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 25 May 2022 18:26:00 +0200 Subject: [PATCH 01/36] Drop support for legacy Python --- .circleci/config.yml | 6 -- docs/source/conf.py | 1 - examples/sc2autosave.py | 41 ++++++----- examples/sc2store.py | 3 +- generate_build_data.py | 22 +++--- new_units.py | 8 +-- sc2reader/__init__.py | 2 - sc2reader/constants.py | 3 - sc2reader/data/__init__.py | 19 +++--- sc2reader/data/create_lookup.py | 4 +- sc2reader/decoders.py | 21 +++--- sc2reader/engine/__init__.py | 3 - sc2reader/engine/engine.py | 7 +- sc2reader/engine/events.py | 10 +-- sc2reader/engine/plugins/__init__.py | 3 - sc2reader/engine/plugins/apm.py | 5 +- sc2reader/engine/plugins/context.py | 34 +++++---- sc2reader/engine/plugins/creeptracker.py | 15 ++-- sc2reader/engine/plugins/gameheart.py | 5 +- sc2reader/engine/plugins/selection.py | 6 +- sc2reader/engine/plugins/supply.py | 15 ++-- sc2reader/engine/utils.py | 7 +- sc2reader/events/__init__.py | 3 - sc2reader/events/base.py | 6 +- sc2reader/events/game.py | 55 +++++++-------- sc2reader/events/message.py | 11 ++- sc2reader/events/tracker.py | 41 ++++++----- sc2reader/exceptions.py | 8 +-- sc2reader/factories/__init__.py | 2 - sc2reader/factories/plugins/replay.py | 13 ++-- sc2reader/factories/plugins/utils.py | 13 ++-- sc2reader/factories/sc2factory.py | 17 ++--- sc2reader/log_utils.py | 5 +- sc2reader/objects.py | 55 +++++++-------- sc2reader/readers.py | 37 +++++----- sc2reader/resources.py | 87 ++++++++++++------------ sc2reader/scripts/__init__.py | 3 - sc2reader/scripts/sc2attributes.py | 14 ++-- sc2reader/scripts/sc2json.py | 2 - sc2reader/scripts/sc2parse.py | 44 ++++++------ sc2reader/scripts/sc2printer.py | 48 +++++++------ sc2reader/scripts/sc2replayer.py | 10 ++- sc2reader/scripts/utils.py | 3 - sc2reader/utils.py | 15 ++-- test_replays/test_replays.py | 69 ++++++++----------- test_s2gs/test_all.py | 2 - 46 files changed, 334 insertions(+), 469 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0799ef91..9c0a5914 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,11 +25,6 @@ jobs: - run: black . --check - Python2: - docker: - - image: circleci/python:2.7.18 - steps: *build_and_test_steps - Python3: docker: - image: circleci/python:3.10 @@ -41,5 +36,4 @@ workflows: build: jobs: - StyleCheck - - Python2 - Python3 diff --git a/docs/source/conf.py b/docs/source/conf.py index 17164624..ead7fdd3 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. diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index f7091ba4..b7f1ffbd 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 @@ -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/.../Mutliplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplater/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/.../Mutliplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplater/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/.../Mutliplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplater/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/.../Mutliplayer \ + --dest ~/My\\ Documents/Starcraft\\ II/Accounts/.../Multiplater/Saved \ --rename "{:matchup}/({:length}) {:map}: {:teams}" \ --player-format "{:name}({:play_race})" \ --team-order-by number \ @@ -200,7 +199,7 @@ def run(args): 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) @@ -250,7 +249,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 +261,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 +289,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 @@ -341,7 +340,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 +350,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, @@ -374,13 +373,13 @@ 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( + "About to reset directory: {}\nAll files and subdirectories will be removed.".format( args.dest ) ) choice = raw_input("Proceed anyway? (y/n) ") if choice.lower() == "y": - args.log.write("Removing old directory: {0}\n".format(args.dest)) + args.log.write(f"Removing old directory: {args.dest}\n") if not args.dryrun: print(args.dest) shutil.rmtree(args.dest) @@ -404,13 +403,13 @@ 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) @@ -425,7 +424,7 @@ def save_state(state, args): with open(data_file, "w") as file: cPickle.dump(state, file) else: - args.log.write("Writing state to file: {0}\n".format(data_file)) + args.log.write(f"Writing state to file: {data_file}\n") def main(): diff --git a/examples/sc2store.py b/examples/sc2store.py index 0a072f43..6aff2a98 100755 --- a/examples/sc2store.py +++ b/examples/sc2store.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import cPickle import os @@ -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..a13f9807 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: @@ -137,7 +137,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 +145,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 +163,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 +175,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 +258,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 +267,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 +290,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 +302,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..86d82ee5 100644 --- a/new_units.py +++ b/new_units.py @@ -15,11 +15,11 @@ 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("") @@ -31,8 +31,8 @@ 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/sc2reader/__init__.py b/sc2reader/__init__.py index 419f47b5..415b270d 100644 --- a/sc2reader/__init__.py +++ b/sc2reader/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ sc2reader ~~~~~~~~~~~ @@ -18,7 +17,6 @@ :copyright: (c) 2011 by Graylin Kim. :license: MIT, see LICENSE for more details. """ -from __future__ import absolute_import, print_function, unicode_literals, division __version__ = "1.8.0" diff --git a/sc2reader/constants.py b/sc2reader/constants.py index 1edfa70e..db34afa7 100644 --- a/sc2reader/constants.py +++ b/sc2reader/constants.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - # These are found in Repack-MPQ/fileset.{locale}#Mods#Core.SC2Mod#{locale}.SC2Data/LocalizedData/Editor/EditorCategoryStrings.txt # EDSTR_CATEGORY_Race # EDSTR_PLAYERPROPS_RACE diff --git a/sc2reader/data/__init__.py b/sc2reader/data/__init__.py index 6cbb9425..cb333d47 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 @@ -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): @@ -190,7 +187,7 @@ def is_army(self): 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,7 +217,7 @@ def __repr__(self): return str(self) -class UnitType(object): +class UnitType: """Represents an in game unit type""" def __init__( @@ -271,7 +268,7 @@ def __init__( self.is_army = is_army -class Ability(object): +class Ability: """Represents an in-game ability""" def __init__( @@ -297,7 +294,7 @@ def __init__( @loggable -class Build(object): +class Build: """ :param build_id: The build number identifying this dataset. @@ -342,7 +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( + "Unable to change type of {} to {} [frame {}]; unit type not found in build {}".format( unit, new_type, frame, self.id ) ) @@ -402,7 +399,7 @@ 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") ): @@ -421,7 +418,7 @@ 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") 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/decoders.py b/sc2reader/decoders.py index 56674a1c..9085ab7d 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. @@ -144,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 @@ -306,9 +303,9 @@ def read_bytes(self, count): temp_buffer = BytesIO() prev_byte = self._next_byte lo_mask, hi_mask = self._bit_masks[self._bit_shift] - for next_byte in struct.unpack(str("B") * count, data): + 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 @@ -357,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 @@ -413,9 +410,9 @@ def read_struct(self, datatype=None): elif datatype == 0x05: # Struct entries = self.read_vint() - data = dict( - [(self.read_vint(), self.read_struct()) for i in range(entries)] - ) + data = { + self.read_vint(): self.read_struct() for i in range(entries) + } elif datatype == 0x06: # u8 data = ord(self._buffer.read(1)) @@ -430,6 +427,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 15c0af66..9c7ee68f 100644 --- a/sc2reader/engine/engine.py +++ b/sc2reader/engine/engine.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import collections from sc2reader.events import * from sc2reader.engine.events import InitGameEvent, EndGameEvent, PluginExit -class GameEngine(object): +class GameEngine: """ GameEngine Specification -------------------------- @@ -200,7 +197,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 5a8fb34a..1a07ca6c 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,7 +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( + "{}\t{}\tMissing ability {:X} from {}".format( event.frame, event.player.name, event.ability_id, @@ -60,7 +58,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 @@ -199,13 +197,13 @@ 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( + "Unable to delete unit index {} at {} [{}], index not active.".format( event.killer_pid, Length(seconds=event.second), event.frame ) ) else: self.logger.error( - "Unit {0} died at {1} [{2}] before it was born!".format( + "Unit {} died at {} [{}] before it was born!".format( event.unit_id, Length(seconds=event.second), event.frame ) ) @@ -217,7 +215,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( + "Unknown killing player id {} at {} [{}]".format( event.killing_player_id, Length(seconds=event.second), event.frame ) ) @@ -229,7 +227,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( + "Unknown killing unit id {} at {} [{}]".format( event.killing_unit_id, Length(seconds=event.second), event.frame ) ) @@ -245,7 +243,7 @@ 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( + "Unit {} owner changed at {} [{}] before it was born!".format( event.unit_id, Length(seconds=event.second), event.frame ) ) @@ -265,7 +263,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( + "Unit {} type changed at {} [{}] before it was born!".format( event.unit_id, Length(seconds=event.second) ) ) @@ -308,7 +306,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( + "Unit {} done at {} [{}] before it was started!".format( event.killer_pid, Length(seconds=event.second), event.frame ) ) @@ -324,7 +322,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( + "Unit at active_unit index {} moved at {} [{}] but it doesn't exist!".format( event.killer_pid, Length(seconds=event.second), event.frame ) ) @@ -338,7 +336,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( + "Bad pid ({}) for event {} at {} [{}].".format( event.pid, event.__class__, Length(seconds=event.second), @@ -354,7 +352,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( + "Bad pid ({}) for event {} at {} [{}].".format( event.pid, event.__class__, Length(seconds=event.second), @@ -369,7 +367,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( + "Bad pid ({}) for event {} at {} [{}].".format( event.pid, event.__class__, Length(seconds=event.second), @@ -382,7 +380,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( + "Bad upkeep_pid ({}) for event {} at {} [{}].".format( event.upkeep_pid, event.__class__, Length(seconds=event.second), @@ -395,7 +393,7 @@ 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( + "Bad control_pid ({}) for event {} at {} [{}].".format( event.control_pid, event.__class__, Length(seconds=event.second), diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index 5a56b4f2..90f01e0c 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: @@ -21,7 +18,7 @@ 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 +38,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 +59,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 +73,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): @@ -98,7 +95,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 diff --git a/sc2reader/engine/plugins/gameheart.py b/sc2reader/engine/plugins/gameheart.py index d3905624..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 diff --git a/sc2reader/engine/plugins/selection.py b/sc2reader/engine/plugins/selection.py index 52f50c65..006ac78e 100644 --- a/sc2reader/engine/plugins/selection.py +++ b/sc2reader/engine/plugins/selection.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - - -class SelectionTracker(object): +class SelectionTracker: """ Tracks a player's active selection as an input into other plugins. diff --git a/sc2reader/engine/plugins/supply.py b/sc2reader/engine/plugins/supply.py index 8305e50b..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 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 82232011..2d04c4c2 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,14 @@ 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( + player_name = "Player {} - ({})".format( 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 +58,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 +70,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 +83,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 +142,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 +237,19 @@ 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( + string += "; Target: {} [{:0>8X}]".format( self.target.name, self.target_unit_id ) if self.ability_type in ("TargetPoint", "TargetUnit"): - string += "; Location: {0}".format(str(self.location)) + string += f"; Location: {str(self.location)}" return string @@ -268,7 +265,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 +281,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 +309,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) @@ -393,7 +390,7 @@ 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) @@ -410,7 +407,7 @@ class CommandManagerStateEvent(GameEvent): """ def __init__(self, frame, pid, data): - super(CommandManagerStateEvent, self).__init__(frame, pid) + super().__init__(frame, pid) #: Always 1? self.state = data["state"] @@ -433,7 +430,7 @@ class SelectionEvent(GameEvent): """ 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"] @@ -554,7 +551,7 @@ class ControlGroupEvent(GameEvent): """ 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"] @@ -612,7 +609,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 @@ -633,7 +630,7 @@ def __init__(self, frame, pid, data): self.yaw = data["yaw"] def __str__(self): - return self._str_prefix() + "{0} at ({1}, {2})".format( + return self._str_prefix() + "{} at ({}, {})".format( self.name, self.x, self.y ) @@ -646,7 +643,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 @@ -676,7 +673,7 @@ def __init__(self, frame, pid, data): self.custom_resource = self.resources[3] if len(self.resources) >= 4 else None def __str__(self): - return self._str_prefix() + " transfer {0} minerals, {1} gas, {2} terrazine, and {3} custom to {4}".format( + return self._str_prefix() + " transfer {} minerals, {} gas, {} terrazine, and {} custom to {}".format( self.minerals, self.vespene, self.terrazine, @@ -691,7 +688,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"] @@ -711,7 +708,7 @@ def __init__(self, frame, pid, data): def __str__(self): return ( self._str_prefix() - + " requests {0} minerals, {1} gas, {2} terrazine, and {3} custom".format( + + " requests {} minerals, {} gas, {} terrazine, and {} custom".format( self.minerals, self.vespene, self.terrazine, self.custom_resource ) ) @@ -723,7 +720,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"] @@ -735,7 +732,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"] @@ -747,7 +744,7 @@ 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"] diff --git a/sc2reader/events/message.py b/sc2reader/events/message.py index 57da5dce..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 @@ -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 @@ -40,7 +37,7 @@ class ChatEvent(MessageEvent): """ 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 @@ -64,7 +61,7 @@ class ProgressEvent(MessageEvent): """ 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 @@ -77,7 +74,7 @@ class PingEvent(MessageEvent): """ 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 6220d5fe..7355ef35 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 @@ -29,7 +26,7 @@ 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 @@ -39,7 +36,7 @@ class PlayerSetupEvent(TrackerEvent): """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,7 +334,7 @@ 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( + return self._str_prefix() + "{: >15} - Unit born {}".format( str(self.unit_upkeeper), self.unit ) @@ -349,7 +346,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,7 +409,7 @@ def __init__(self, frames, data, build): ) def __str__(self): - return self._str_prefix() + "{0: >15} - Unit died {1}.".format( + return self._str_prefix() + "{: >15} - Unit died {}.".format( str(self.unit.owner), self.unit ) @@ -424,7 +421,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,7 +448,7 @@ def __init__(self, frames, data, build): self.unit_controller = None def __str__(self): - return self._str_prefix() + "{0: >15} took {1}".format( + return self._str_prefix() + "{: >15} took {}".format( str(self.unit_upkeeper), self.unit ) @@ -464,7 +461,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,7 +479,7 @@ 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( + return self._str_prefix() + "{: >15} - Unit {} type changed to {}".format( str(self.unit.owner), self.unit, 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,7 +505,7 @@ def __init__(self, frames, data, build): self.count = data[2] def __str__(self): - return self._str_prefix() + "{0: >15} - {1} upgrade completed".format( + return self._str_prefix() + "{: >15} - {} upgrade completed".format( str(self.player), self.upgrade_type_name ) @@ -521,7 +518,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,7 +564,7 @@ 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( + return self._str_prefix() + "{: >15} - Unit initiated {}".format( str(self.unit_upkeeper), self.unit ) @@ -579,7 +576,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,7 +591,7 @@ def __init__(self, frames, data, build): self.unit = None def __str__(self): - return self._str_prefix() + "{0: >15} - Unit {1} done".format( + return self._str_prefix() + "{: >15} - Unit {} done".format( str(self.unit.owner), self.unit ) @@ -607,7 +604,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..b3ba681b 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 @@ -159,7 +156,7 @@ def SelectionTracker(replay): selections[event.control_group] = control_group if debug: logger.info( - "[{0}] {1} selected {2} units: {3}".format( + "[{}] {} selected {} units: {}".format( Length(seconds=event.second), person.name, len(selections[0x0A].objects), @@ -172,7 +169,7 @@ def SelectionTracker(replay): selections[event.control_group] = selections[0x0A].copy() if debug: logger.info( - "[{0}] {1} set hotkey {2} to current selection".format( + "[{}] {} set hotkey {} to current selection".format( Length(seconds=event.second), person.name, event.hotkey ) ) @@ -185,7 +182,7 @@ def SelectionTracker(replay): selections[event.control_group] = control_group if debug: logger.info( - "[{0}] {1} added current selection to hotkey {2}".format( + "[{}] {} added current selection to hotkey {}".format( Length(seconds=event.second), person.name, event.hotkey ) ) @@ -197,7 +194,7 @@ def SelectionTracker(replay): selections[0xA] = control_group if debug: logger.info( - "[{0}] {1} retrieved hotkey {2}, {3} units: {4}".format( + "[{}] {} retrieved hotkey {}, {} units: {}".format( Length(seconds=event.second), person.name, event.control_group, @@ -216,7 +213,7 @@ def SelectionTracker(replay): person.selection_errors += 1 if debug: logger.warn( - "Error detected in deselection mode {0}.".format( + "Error detected in deselection mode {}.".format( event.mask_type ) ) diff --git a/sc2reader/factories/plugins/utils.py b/sc2reader/factories/plugins/utils.py index 7eef123a..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() @@ -95,7 +92,7 @@ def deselect(self, mode, data): # 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( @@ -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 1bde3ac1..e4b38c6f 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,7 +24,7 @@ @log_utils.loggable -class SC2Factory(object): +class SC2Factory: """ The SC2Factory class acts as a generic loader interface for all available to sc2reader resources. At current time this includes @@ -262,7 +259,7 @@ 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( + resource = super().load_remote_resource_contents( remote_resource, **options ) self.cache_set(cache_key, resource) @@ -290,15 +287,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( + "Must have read/write access to {} for local file caching.".format( self.cache_dir ) ) @@ -333,7 +330,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 @@ -366,7 +363,7 @@ class DoubleCachedSC2Factory(DictCachedSC2Factory, FileCachedSC2Factory): """ def __init__(self, cache_dir, cache_max_size=0, **options): - super(DoubleCachedSC2Factory, self).__init__( + super().__init__( cache_max_size, cache_dir=cache_dir, **options ) diff --git a/sc2reader/log_utils.py b/sc2reader/log_utils.py index c4288820..3337e348 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: @@ -78,7 +75,7 @@ def get_logger(entity): 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 11ef8eb7..2949b516 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import hashlib import math from collections import namedtuple @@ -12,7 +9,7 @@ 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 @@ -22,7 +19,7 @@ class Team(object): """ #: 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 @@ -56,7 +53,7 @@ def hash(self): 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,14 +62,14 @@ 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: @@ -80,17 +77,17 @@ def __init__(self, header, attr_id, player, value): try: self.value = lookup[value.strip("\x00 ")[::-1]] except KeyError: - self.logger.info("Missing attribute value: {0}".format(value)) + 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 @@ -164,7 +161,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 @@ -253,7 +250,7 @@ def __init__(self, pid, slot_data, detail_data, attribute_data): 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 @@ -317,7 +314,7 @@ 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) @@ -342,7 +339,7 @@ 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) @@ -369,7 +366,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) @@ -388,10 +385,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 @@ -406,7 +403,7 @@ class PlayerSummary: subregion = int() #: The player's region, such as us, eu, sea - region = str() + region = '' #: unknown1 unknown1 = int() @@ -429,11 +426,11 @@ def __init__(self, pid): def __str__(self): if not self.is_ai: - return "User {0}-S2-{1}-{2}".format( + return "User {}-S2-{}-{}".format( self.region.upper(), self.subregion, self.bnetid ) else: - return "AI ({0})".format(self.play_race) + return f"AI ({self.play_race})" def __repr__(self): return str(self) @@ -441,7 +438,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() @@ -480,10 +477,10 @@ def as_points(self): 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. """ @@ -542,7 +539,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 """ @@ -553,7 +550,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.warn(f"Invalid MapInfo file: {magic}") return #: The map info file format version @@ -572,7 +569,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() @@ -580,7 +577,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() diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 89ba67fc..92aeefb9 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import struct from sc2reader.exceptions import ParseError, ReadError @@ -12,7 +9,7 @@ from sc2reader.decoders import BitPackedDecoder, ByteDecoder -class InitDataReader(object): +class InitDataReader: def __call__(self, data, replay): data = BitPackedDecoder(data) result = dict( @@ -292,11 +289,11 @@ def __call__(self, data, replay): ), ) 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) @@ -314,7 +311,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( @@ -363,7 +360,7 @@ def __call__(self, data, replay): ) -class MessageEventsReader(object): +class MessageEventsReader: def __call__(self, data, replay): data = BitPackedDecoder(data) pings = list() @@ -402,7 +399,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), @@ -523,7 +520,7 @@ def __call__(self, data, replay): # Otherwise throw a read error else: raise ReadError( - "Event type {0} unknown at position {1}.".format( + "Event type {} unknown at position {}.".format( hex(event_type), hex(event_start) ), event_type, @@ -539,7 +536,7 @@ def __call__(self, data, replay): return game_events except ParseError as e: raise ReadError( - "Parse error '{0}' unknown at position {1}.".format( + "Parse error '{}' unknown at position {}.".format( e.msg, hex(event_start) ), event_type, @@ -550,7 +547,7 @@ def __call__(self, data, replay): ) except EOFError as e: raise ReadError( - "EOFError error '{0}' unknown at position {1}.".format( + "EOFError error '{}' unknown at position {}.".format( e.msg, hex(event_start) ), event_type, @@ -585,7 +582,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 @@ -1097,7 +1094,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)}) @@ -1257,7 +1254,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( { @@ -1551,7 +1548,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( { @@ -1736,7 +1733,7 @@ def game_user_join_event(self, data): class GameEventsReader_34784(GameEventsReader_27950): def __init__(self): - super(GameEventsReader_34784, self).__init__() + super().__init__() self.EVENT_DISPATCH.update( { @@ -1987,7 +1984,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( { @@ -2188,7 +2185,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)} @@ -2260,7 +2257,7 @@ def command_event(self, data): ) -class TrackerEventsReader(object): +class TrackerEventsReader: def __init__(self): self.EVENT_DISPATCH = { 0: PlayerStatsEvent, diff --git a/sc2reader/resources.py b/sc2reader/resources.py index 428e9cc9..564801d3 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 @@ -50,7 +47,7 @@ class Replay(Resource): 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 +59,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 chosen at game creation: 1v1, 2v2, 3v3, 4v4, FFA - game_type = str() + 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 +88,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 +127,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 +184,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 @@ -210,7 +207,7 @@ def __init__( do_tracker_events=True, **options ): - super(Replay, self).__init__(replay_file, filename, **options) + super().__init__(replay_file, filename, **options) self.datapack = None self.raw_data = dict() @@ -277,7 +274,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 @@ -396,17 +393,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" @@ -550,14 +547,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) + f"Conflicting results for Team {team.number}: {results}" ) team.result = "Unknown" @@ -884,7 +881,7 @@ def _get_reader(self, data_file): return reader else: raise ValueError( - "Valid {0} reader could not found for build {1}".format( + "Valid {} reader could not found for build {}".format( data_file, self.build ) ) @@ -904,7 +901,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() @@ -915,19 +912,19 @@ def __getstate__(self): class Map(Resource): 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 @@ -949,7 +946,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"): @@ -1018,7 +1015,7 @@ class GameSummary(Resource): """ #: Game speed - game_speed = str() + game_speed = '' #: Game length (real-time) real_length = int() @@ -1045,7 +1042,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() @@ -1072,8 +1069,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:])) @@ -1256,7 +1253,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: @@ -1283,7 +1280,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 @@ -1336,7 +1333,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.warn(f"Untranslatable key = {stat_id}") # Once we've compiled all the build commands we need to make # sure they are properly sorted for presentation. @@ -1432,7 +1429,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( @@ -1445,19 +1442,19 @@ class MapHeader(Resource): """**Experimental**""" #: 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() @@ -1466,7 +1463,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 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 a794e235..7eba02ca 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 @@ -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, ) @@ -122,15 +120,15 @@ def get_choice(s2gs_key, old_value, new_value): 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) + 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 c7e842e8..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 diff --git a/sc2reader/scripts/sc2parse.py b/sc2reader/scripts/sc2parse.py index 868f897a..3f0aab6f 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 @@ -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,24 +55,20 @@ 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( - [ + 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 = set( - [player.pid for player in replay.players if player.is_human] - ) - ability_pids = set( - [ + } + 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( @@ -105,9 +99,9 @@ def main(): ) print( "Units were: {units}".format( - units=set( - [obj.name for obj in replay.objects.values()] - ) + units={ + obj.name for obj in replay.objects.values() + } ) ) @@ -124,7 +118,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 +131,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 +147,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 f27fda49..25b58189 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 @@ -16,37 +14,37 @@ def printReplay(filepath, arguments): 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 {}\t{} ({})".format( team.number, team.players[0].name, team.players[0].pick_race[0] ) ) for player in team.players[1:]: print( - " \t{0} ({1})".format( + " \t{} ({})".format( 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: @@ -54,19 +52,19 @@ def printReplay(filepath, arguments): return prev = e.game_events[-1] print( - "\nVersion {0} replay:\n\t{1}".format( + "\nVersion {} replay:\n\t{}".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"\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 +73,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( @@ -177,12 +175,12 @@ def main(): name, ext = os.path.splitext(filepath) if ext.lower() == ".sc2replay": print( - "\n--------------------------------------\n{0}\n".format(filepath) + f"\n--------------------------------------\n{filepath}\n" ) printReplay(filepath, arguments) elif ext.lower() == ".s2gs": print( - "\n--------------------------------------\n{0}\n".format(filepath) + f"\n--------------------------------------\n{filepath}\n" ) printGameSummary(filepath, arguments) diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index 3d8212ca..c3e61b5e 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,7 +20,7 @@ def getch(): try: sys.stdin.read(1) break - except IOError: + except OSError: pass finally: termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) @@ -78,15 +76,15 @@ 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(f"Release {replay.release_string}") print( - "{0} on {1} at {2}".format(replay.type, replay.map_name, replay.start_time) + 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' diff --git a/sc2reader/scripts/utils.py b/sc2reader/scripts/utils.py index cf998e5d..cab32a13 100644 --- a/sc2reader/scripts/utils.py +++ b/sc2reader/scripts/utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import argparse import re import textwrap diff --git a/sc2reader/utils.py b/sc2reader/utils.py index 93787212..514ca807 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals, division - import binascii import os import json @@ -11,7 +8,7 @@ from sc2reader.constants import COLOR_CODES, COLOR_CODES_INV -class DepotFile(object): +class DepotFile: """ :param bytes: The raw bytes representing the depot file @@ -49,7 +46,7 @@ def windows_to_unix(windows_time): @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:: @@ -145,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( @@ -162,7 +159,7 @@ 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: @@ -235,9 +232,9 @@ def secs(self): 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): diff --git a/test_replays/test_replays.py b/test_replays/test_replays.py index 2b1d1492..a7f7ac37 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 @@ -214,16 +211,14 @@ def test_hots_pids(self): replay = sc2reader.load_replay(replayfilename) self.assertEqual(replay.expansion, "HotS") - player_pids = set( - [player.pid for player in replay.players if player.is_human] - ) - ability_pids = set( - [ + 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): @@ -231,27 +226,23 @@ def test_wol_pids(self): "test_replays/1.5.4.24540/ggtracker_1471849.SC2Replay" ) self.assertEqual(replay.expansion, "WoL") - ability_pids = set( - [ + ability_pids = { event.player.pid for event in replay.events if "CommandEvent" in event.name - ] - ) - player_pids = set([player.pid for player in replay.players]) + } + 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( - [ + 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): @@ -407,15 +398,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( - [ + 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") @@ -541,8 +530,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"]: @@ -587,13 +576,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 - ] - ) + } ) # print(mineralFieldNames) self.assertTrue(len(mineralPosStrs) > 0) @@ -609,7 +596,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) + f"test_replays/3.3.0/{replaynum}.SC2Replay" ) self.assertTrue(replay is not None) @@ -727,7 +714,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() @@ -735,12 +722,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() @@ -749,13 +736,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): @@ -764,7 +751,7 @@ def __init__(self, value): def __str__(self): return self.value - class TestPlugin1(object): + class TestPlugin1: name = "TestPlugin1" def handleInitGame(self, event, replay): @@ -782,7 +769,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): @@ -797,7 +784,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 @@ -813,7 +800,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 From 1f18ff757e91801190ddffb95da2f12da2c52adf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 25 May 2022 18:40:18 +0200 Subject: [PATCH 02/36] Update README.rst and setup.py --- README.rst | 16 ++++----- sc2reader/decoders.py | 4 +-- sc2reader/events/game.py | 19 +++++----- sc2reader/factories/plugins/replay.py | 4 +-- sc2reader/factories/sc2factory.py | 8 ++--- sc2reader/objects.py | 14 ++++---- sc2reader/resources.py | 50 +++++++++++++-------------- sc2reader/scripts/sc2attributes.py | 4 +-- sc2reader/scripts/sc2parse.py | 16 ++++----- sc2reader/scripts/sc2printer.py | 8 ++--- sc2reader/scripts/sc2replayer.py | 4 +-- setup.py | 10 ++---- test_replays/test_replays.py | 40 +++++++++------------ 13 files changed, 82 insertions(+), 115 deletions(-) diff --git a/README.rst b/README.rst index dcbcb938..63d74a99 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ 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. @@ -195,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) @@ -203,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. @@ -235,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 @@ -250,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/sc2reader/decoders.py b/sc2reader/decoders.py index 9085ab7d..ed726356 100644 --- a/sc2reader/decoders.py +++ b/sc2reader/decoders.py @@ -410,9 +410,7 @@ def read_struct(self, datatype=None): elif datatype == 0x05: # Struct entries = self.read_vint() - data = { - 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)) diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 2d04c4c2..50a9f8f0 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -630,9 +630,7 @@ def __init__(self, frame, pid, data): self.yaw = data["yaw"] def __str__(self): - return self._str_prefix() + "{} at ({}, {})".format( - self.name, self.x, self.y - ) + return self._str_prefix() + "{} at ({}, {})".format(self.name, self.x, self.y) @loggable @@ -673,12 +671,15 @@ def __init__(self, frame, pid, data): self.custom_resource = self.resources[3] if len(self.resources) >= 4 else None def __str__(self): - return self._str_prefix() + " transfer {} minerals, {} gas, {} terrazine, and {} custom to {}".format( - self.minerals, - self.vespene, - self.terrazine, - self.custom_resource, - self.recipient, + return ( + self._str_prefix() + + " transfer {} minerals, {} gas, {} terrazine, and {} custom to {}".format( + self.minerals, + self.vespene, + self.terrazine, + self.custom_resource, + self.recipient, + ) ) diff --git a/sc2reader/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index b3ba681b..7f645eb3 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -213,9 +213,7 @@ def SelectionTracker(replay): person.selection_errors += 1 if debug: logger.warn( - "Error detected in deselection mode {}.".format( - event.mask_type - ) + "Error detected in deselection mode {}.".format(event.mask_type) ) person.selection = player_selections diff --git a/sc2reader/factories/sc2factory.py b/sc2reader/factories/sc2factory.py index e4b38c6f..c020459f 100644 --- a/sc2reader/factories/sc2factory.py +++ b/sc2reader/factories/sc2factory.py @@ -259,9 +259,7 @@ 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().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) @@ -363,9 +361,7 @@ class DoubleCachedSC2Factory(DictCachedSC2Factory, FileCachedSC2Factory): """ def __init__(self, cache_dir, cache_max_size=0, **options): - super().__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/objects.py b/sc2reader/objects.py index 2949b516..d6c9491d 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -19,7 +19,7 @@ class Team: """ #: A unique hash identifying the team of players - hash = '' + hash = "" #: The team number as recorded in the replay number = int() @@ -29,7 +29,7 @@ class Team: #: The result of the game for this team. #: One of "Win", "Loss", or "Unknown" - result = '' + result = "" def __init__(self, number): self.number = number @@ -385,10 +385,10 @@ class PlayerSummary: teamid = int() #: The race the player played in the game. - play_race = '' + play_race = "" #: The race the player picked in the lobby. - pick_race = '' + pick_race = "" #: If the player is a computer is_ai = False @@ -403,7 +403,7 @@ class PlayerSummary: subregion = int() #: The player's region, such as us, eu, sea - region = '' + region = "" #: unknown1 unknown1 = int() @@ -569,7 +569,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 = '' + self.small_preview_path = "" if self.small_preview_type == 2: self.small_preview_path = data.read_cstring() @@ -577,7 +577,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 = '' + self.large_preview_path = "" if self.large_preview_type == 2: self.large_preview_path = data.read_cstring() diff --git a/sc2reader/resources.py b/sc2reader/resources.py index 564801d3..2f8085d6 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -47,7 +47,7 @@ class Replay(Resource): attributes = defaultdict(dict) #: Fully qualified filename of the replay file represented. - filename = '' + filename = "" #: Total number of frames in this game at 16 frames per second. frames = int() @@ -59,27 +59,27 @@ class Replay(Resource): base_build = int() #: The full version release string as seen on Battle.net - release_string = '' + release_string = "" #: A tuple of the individual pieces of the release string versions = tuple() #: The game speed: Slower, Slow, Normal, Fast, Faster - speed = '' + speed = "" #: Deprecated, use :attr:`game_type` or :attr:`real_type` instead - type = '' + type = "" #: The game type chosen at game creation: 1v1, 2v2, 3v3, 4v4, FFA - game_type = '' + 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 = '' + real_type = "" #: The category of the game, Ladder and Private - category = '' + category = "" #: A flag for public ladder games is_ladder = bool() @@ -88,10 +88,10 @@ class Replay(Resource): is_private = bool() #: The raw hash name of the s2ma resource as hosted on bnet depots - map_hash = '' + map_hash = "" #: The name of the map the game was played on - map_name = '' + map_name = "" #: A reference to the loaded :class:`Map` resource. map = None @@ -127,7 +127,7 @@ class Replay(Resource): real_length = None #: The region the game was played on: us, eu, sea, etc - region = '' + region = "" #: An integrated list of all the game events events = list() @@ -184,10 +184,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 = '' + people_hash = "" #: SC2 Expansion. One of 'WoL', 'HotS' - expansion = '' + expansion = "" #: True of the game was resumed from a replay resume_from_replay = False @@ -205,7 +205,7 @@ def __init__( load_level=4, engine=sc2reader.engine, do_tracker_events=True, - **options + **options, ): super().__init__(replay_file, filename, **options) self.datapack = None @@ -915,16 +915,16 @@ def __init__(self, map_file, filename=None, region=None, map_hash=None, **option super().__init__(map_file, filename, **options) #: The localized (only enUS supported right now) map name. - self.name = '' + self.name = "" #: The localized (only enUS supported right now) map author. - self.author = '' + self.author = "" #: The localized (only enUS supported right now) map description. - self.description = '' + self.description = "" #: The localized (only enUS supported right now) map website. - self.website = '' + self.website = "" #: The unique hash used to identify this map on bnet's depots. self.hash = map_hash @@ -1015,7 +1015,7 @@ class GameSummary(Resource): """ #: Game speed - game_speed = '' + game_speed = "" #: Game length (real-time) real_length = int() @@ -1069,8 +1069,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 = '' - self.real_type = '' + 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:])) @@ -1442,19 +1442,19 @@ class MapHeader(Resource): """**Experimental**""" #: The name of the map - name = '' + name = "" #: Hash of map file - map_hash = '' + map_hash = "" #: Link to the map file - map_url = '' + map_url = "" #: Hash of the map image - image_hash = '' + image_hash = "" #: Link to the image of the map (.s2mv) - image_url = '' + image_url = "" #: Localization dictionary, {language, url} localization_urls = dict() diff --git a/sc2reader/scripts/sc2attributes.py b/sc2reader/scripts/sc2attributes.py index 7eba02ca..6e2ca4d1 100644 --- a/sc2reader/scripts/sc2attributes.py +++ b/sc2reader/scripts/sc2attributes.py @@ -119,9 +119,7 @@ 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( - f"Naming conflict on {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(f" (o) Old value '{old_value}'") print(f" (n) New value '{new_value}'") diff --git a/sc2reader/scripts/sc2parse.py b/sc2reader/scripts/sc2parse.py index 3f0aab6f..22f792a4 100755 --- a/sc2reader/scripts/sc2parse.py +++ b/sc2reader/scripts/sc2parse.py @@ -57,17 +57,17 @@ def main(): human_pids = {human.pid for human in replay.humans} event_pids = { - event.player.pid - for event in replay.events - if getattr(event, "player", None) + 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 + event.player.pid + for event in replay.events + if "CommandEvent" in event.name } if human_pids != event_pids: print( @@ -99,9 +99,7 @@ def main(): ) print( "Units were: {units}".format( - units={ - obj.name for obj in replay.objects.values() - } + units={obj.name for obj in replay.objects.values()} ) ) diff --git a/sc2reader/scripts/sc2printer.py b/sc2reader/scripts/sc2printer.py index 25b58189..634aff53 100755 --- a/sc2reader/scripts/sc2printer.py +++ b/sc2reader/scripts/sc2printer.py @@ -174,14 +174,10 @@ def main(): for filepath in utils.get_files(path, depth=depth): name, ext = os.path.splitext(filepath) if ext.lower() == ".sc2replay": - print( - f"\n--------------------------------------\n{filepath}\n" - ) + print(f"\n--------------------------------------\n{filepath}\n") printReplay(filepath, arguments) elif ext.lower() == ".s2gs": - print( - f"\n--------------------------------------\n{filepath}\n" - ) + print(f"\n--------------------------------------\n{filepath}\n") printGameSummary(filepath, arguments) diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index c3e61b5e..c6c16c93 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -77,9 +77,7 @@ def main(): for filename in sc2reader.utils.get_files(args.FILE): replay = sc2reader.load_replay(filename, debug=True) print(f"Release {replay.release_string}") - print( - f"{replay.type} on {replay.map_name} at {replay.start_time}" - ) + print(f"{replay.type} on {replay.map_name} at {replay.start_time}") print("") for team in replay.teams: print(team) diff --git a/setup.py b/setup.py index f0f57022..95bf19fd 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -import sys import setuptools setuptools.setup( @@ -20,13 +19,10 @@ "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 :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Games/Entertainment", @@ -44,9 +40,7 @@ "sc2json = sc2reader.scripts.sc2json:main", ] }, - install_requires=["mpyq>=0.2.3", "argparse", "ordereddict", "unittest2", "pil"] - if float(sys.version[:3]) < 2.7 - else ["mpyq>=0.2.4"], + install_requires=["mpyq", "pillow"], tests_require=["pytest"], packages=setuptools.find_packages(), include_package_data=True, diff --git a/test_replays/test_replays.py b/test_replays/test_replays.py index a7f7ac37..022be926 100644 --- a/test_replays/test_replays.py +++ b/test_replays/test_replays.py @@ -211,13 +211,11 @@ def test_hots_pids(self): replay = sc2reader.load_replay(replayfilename) self.assertEqual(replay.expansion, "HotS") - player_pids = { - player.pid for player in replay.players if player.is_human - } + 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 + event.player.pid + for event in replay.events + if "CommandEvent" in event.name } self.assertEqual(ability_pids, player_pids) @@ -227,9 +225,7 @@ def test_wol_pids(self): ) self.assertEqual(replay.expansion, "WoL") ability_pids = { - event.player.pid - for event in replay.events - if "CommandEvent" in event.name + 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) @@ -238,10 +234,10 @@ def test_hots_hatchfun(self): replay = sc2reader.load_replay("test_replays/2.0.0.24247/molten.SC2Replay") 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" + 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)) @@ -400,10 +396,10 @@ def test_gameheartnormalizer_plugin(self): replay = sc2reader.load_replay("test_replays/2.0.0.24247/molten.SC2Replay") 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" + 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)) @@ -577,9 +573,9 @@ def test_funny_minerals(self): ] mineralFieldNames = list( { - 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) @@ -595,9 +591,7 @@ def test_32(self): def test_33(self): for replaynum in range(1, 4): - replay = sc2reader.load_replay( - f"test_replays/3.3.0/{replaynum}.SC2Replay" - ) + 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): From 6acc9e10e950cb1a7388c8bd8b6706df8cd8dea9 Mon Sep 17 00:00:00 2001 From: Robert Heine Date: Sun, 7 Aug 2022 14:20:56 -0400 Subject: [PATCH 03/36] adjust attribute to reflect whats being checked --- sc2reader/engine/plugins/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index 1a07ca6c..d047c25f 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -307,7 +307,7 @@ def handleUnitDoneEvent(self, event, replay): else: self.logger.error( "Unit {} done at {} [{}] before it was started!".format( - event.killer_pid, Length(seconds=event.second), event.frame + event.unit_id, Length(seconds=event.second), event.frame ) ) @@ -323,7 +323,7 @@ def handleUnitPositionsEvent(self, event, replay): else: self.logger.error( "Unit at active_unit index {} moved at {} [{}] but it doesn't exist!".format( - event.killer_pid, Length(seconds=event.second), event.frame + event.unit_index, Length(seconds=event.second), event.frame ) ) From c4dd5da9f3dfd1ac9535b35e8c90f296b1683c36 Mon Sep 17 00:00:00 2001 From: Robert Heine Date: Sun, 7 Aug 2022 17:06:15 -0400 Subject: [PATCH 04/36] unit_index isnt an attribute of an event its a var --- sc2reader/engine/plugins/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index d047c25f..42b03d3d 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -323,7 +323,7 @@ def handleUnitPositionsEvent(self, event, replay): else: self.logger.error( "Unit at active_unit index {} moved at {} [{}] but it doesn't exist!".format( - event.unit_index, Length(seconds=event.second), event.frame + unit_index, Length(seconds=event.second), event.frame ) ) From 310cb1dba370d5371b31b35273e37e106f0f91ea Mon Sep 17 00:00:00 2001 From: Robert Heine Date: Sun, 7 Aug 2022 17:20:10 -0400 Subject: [PATCH 05/36] change value to better reflect logged error --- sc2reader/engine/plugins/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index 42b03d3d..4adfc0e1 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -198,7 +198,7 @@ def handleUnitDiedEvent(self, event, replay): else: self.logger.error( "Unable to delete unit index {} at {} [{}], index not active.".format( - event.killer_pid, Length(seconds=event.second), event.frame + event.unit_id_index, Length(seconds=event.second), event.frame ) ) else: From 5400aa0ee82eb4ee93a2dd5d8cdd648c1dfc623a Mon Sep 17 00:00:00 2001 From: Bean Date: Mon, 13 Feb 2023 21:03:48 +0800 Subject: [PATCH 06/36] add banalce data for 89720 --- sc2reader/data/LotV/89720_abilities.csv | 409 +++++++++ sc2reader/data/LotV/89720_units.csv | 1058 +++++++++++++++++++++++ sc2reader/data/__init__.py | 1 + sc2reader/data/ability_lookup.csv | 3 +- sc2reader/resources.py | 6 +- 5 files changed, 1475 insertions(+), 2 deletions(-) create mode 100644 sc2reader/data/LotV/89720_abilities.csv create mode 100644 sc2reader/data/LotV/89720_units.csv 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 cb333d47..a83f4932 100755 --- a/sc2reader/data/__init__.py +++ b/sc2reader/data/__init__.py @@ -478,6 +478,7 @@ def load_build(expansion, version): "76114", "77379", "80949", + "89720" ): lotv_builds[version] = load_build("LotV", version) diff --git a/sc2reader/data/ability_lookup.csv b/sc2reader/data/ability_lookup.csv index 831c79ef..4eaee18f 100755 --- a/sc2reader/data/ability_lookup.csv +++ b/sc2reader/data/ability_lookup.csv @@ -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 @@ -866,3 +866,4 @@ BattlecruiserAttack,BattlecruiserAttack BattlecruiserMove,Move,Patrol,HoldPos AmorphousArmorcloud,AmorphousArmorcloud BatteryOvercharge,BatteryOvercharge +MorphToBaneling,MorphToBaneling,Cancel diff --git a/sc2reader/resources.py b/sc2reader/resources.py index 2f8085d6..de47b7e3 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -871,7 +871,11 @@ def register_default_datapacks(self): ) self.register_datapack( datapacks["LotV"]["80949"], - lambda r: r.expansion == "LotV" and 80949 <= r.build, + lambda r: r.expansion == "LotV" and 80949 <= r.build < 89720, + ) + self.register_datapack( + datapacks["LotV"]["89720"], + lambda r: r.expansion == "LotV" and 89720 <= r.build, ) # Internal Methods From 66522ab40818fcbb8177a5ffa7edf1c19204347a Mon Sep 17 00:00:00 2001 From: Bean Date: Mon, 13 Feb 2023 21:26:08 +0800 Subject: [PATCH 07/36] build 89634 also use datapack 89720 --- sc2reader/resources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sc2reader/resources.py b/sc2reader/resources.py index de47b7e3..e29adefd 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -871,11 +871,11 @@ def register_default_datapacks(self): ) self.register_datapack( datapacks["LotV"]["80949"], - lambda r: r.expansion == "LotV" and 80949 <= r.build < 89720, + lambda r: r.expansion == "LotV" and 80949 <= r.build < 89634, ) self.register_datapack( datapacks["LotV"]["89720"], - lambda r: r.expansion == "LotV" and 89720 <= r.build, + lambda r: r.expansion == "LotV" and 89634 <= r.build, ) # Internal Methods From 61f3a2d1f49ebd2c48163467bdcaca8e1c17b241 Mon Sep 17 00:00:00 2001 From: Bean Date: Tue, 14 Feb 2023 10:44:36 +0800 Subject: [PATCH 08/36] fix for linting issues --- examples/sc2autosave.py | 1 - sc2reader/data/__init__.py | 2 +- sc2reader/engine/plugins/creeptracker.py | 1 + sc2reader/readers.py | 1 - sc2reader/resources.py | 1 - sc2reader/scripts/sc2replayer.py | 1 - sc2reader/scripts/utils.py | 1 - test_replays/test_replays.py | 1 - 8 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index b7f1ffbd..dae7bff1 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -184,7 +184,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): diff --git a/sc2reader/data/__init__.py b/sc2reader/data/__init__.py index a83f4932..ccd6455c 100755 --- a/sc2reader/data/__init__.py +++ b/sc2reader/data/__init__.py @@ -478,7 +478,7 @@ def load_build(expansion, version): "76114", "77379", "80949", - "89720" + "89720", ): lotv_builds[version] = load_build("LotV", version) diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index 90f01e0c..39bb6b91 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -17,6 +17,7 @@ from collections import defaultdict from itertools import tee + # The creep tracker plugin class CreepTracker: """ diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 92aeefb9..77fb681d 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -2126,7 +2126,6 @@ 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): diff --git a/sc2reader/resources.py b/sc2reader/resources.py index e29adefd..924f7bde 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -40,7 +40,6 @@ 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. diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index c6c16c93..d78410b0 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -94,7 +94,6 @@ def main(): # Allow specification of events to `show` # Loop through the events for event in events: - if ( isinstance(event, CommandEvent) or isinstance(event, SelectionEvent) diff --git a/sc2reader/scripts/utils.py b/sc2reader/scripts/utils.py index cab32a13..1f87baa4 100644 --- a/sc2reader/scripts/utils.py +++ b/sc2reader/scripts/utils.py @@ -35,7 +35,6 @@ def _split_lines(self, text, width): 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)) diff --git a/test_replays/test_replays.py b/test_replays/test_replays.py index 022be926..a63c59fc 100644 --- a/test_replays/test_replays.py +++ b/test_replays/test_replays.py @@ -208,7 +208,6 @@ 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 = {player.pid for player in replay.players if player.is_human} From cba20f2a4ad55fe44011c4ee5c40b8bdac534c91 Mon Sep 17 00:00:00 2001 From: dlithio Date: Wed, 16 Aug 2023 15:44:31 -0500 Subject: [PATCH 09/36] Identify all control group event types --- sc2reader/events/game.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 50a9f8f0..26fc2813 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -521,17 +521,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) @@ -589,6 +593,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` From 97b101e1db62930d342098966ab5eb85e0a05077 Mon Sep 17 00:00:00 2001 From: NumberPigeon Date: Wed, 30 Aug 2023 18:45:15 +0800 Subject: [PATCH 10/36] feat: add region_id attr to Entity --- sc2reader/objects.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index d6c9491d..1922ccb8 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -135,6 +135,9 @@ 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])] From ce38298666c080447e5cc530469dda6028d91389 Mon Sep 17 00:00:00 2001 From: NumberPigeon Date: Wed, 30 Aug 2023 22:02:22 +0800 Subject: [PATCH 11/36] feat: add mmr for User class --- sc2reader/objects.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index d6c9491d..54fd3e8e 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -285,6 +285,21 @@ 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. + if ( + "scaled_rating" in init_data + and init_data["scaled_rating"] is not None + and init_data["scaled_rating"] > 0 + ): + self.mmr = init_data["scaled_rating"] + else: + self.mmr = None + @property def url(self): """ From 77a82cb77f4a2515f6ba0160f7c982146a993d06 Mon Sep 17 00:00:00 2001 From: NumberPigeon <108506296+NumberPigeon@users.noreply.github.com> Date: Wed, 30 Aug 2023 22:54:51 +0800 Subject: [PATCH 12/36] refactor: cleaner code Co-authored-by: Christian Clauss --- sc2reader/objects.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 54fd3e8e..b49f72ab 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -291,14 +291,8 @@ def __init__(self, uid, 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. - if ( - "scaled_rating" in init_data - and init_data["scaled_rating"] is not None - and init_data["scaled_rating"] > 0 - ): - self.mmr = init_data["scaled_rating"] - else: - self.mmr = None + mmr = int(init_data.get("scaled_rating") or 0) + self.mmr = mmr if mmr > 0 else None @property def url(self): From d0685e354c3298e389ef1e25fa0e37aab18c81e3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 30 Aug 2023 16:26:40 +0200 Subject: [PATCH 13/36] CircleCI: Test on Python 3.11 --- .circleci/config.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9c0a5914..96e88466 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,21 +2,22 @@ version: 2.0 build_and_test: &build_and_test_steps - checkout - - run: sudo pip install --upgrade pip - - run: sudo pip install pytest -r requirements.txt - - run: pip install --user . - - 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.10 + - image: cimg/python:3.11 steps: - checkout - - run: sudo pip install black codespell flake8 - run: python --version ; pip --version ; pwd ; ls -l + - run: pip install black codespell flake8 ruff - run: codespell -L queenland,uint # stop the build if there are Python syntax errors or undefined names - run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics @@ -27,7 +28,7 @@ jobs: Python3: docker: - - image: circleci/python:3.10 + - image: cimg/python:3.11 steps: *build_and_test_steps From 50a137e778786d720b0633c31f5ded9fdc4e9a58 Mon Sep 17 00:00:00 2001 From: NumberPigeon <108506296+NumberPigeon@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:03:27 +0800 Subject: [PATCH 14/36] doc: meaningful tmp var name, while keeping the attribute's name simple Co-authored-by: Christian Clauss --- sc2reader/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index b49f72ab..0c5877e1 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -291,8 +291,8 @@ def __init__(self, uid, 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. - mmr = int(init_data.get("scaled_rating") or 0) - self.mmr = mmr if mmr > 0 else None + matchmaking_rating = int(init_data.get("scaled_rating") or 0) + self.mmr = matchmaking_rating if matchmaking_rating > 0 else None @property def url(self): From f9678286db9bb5e06d8dc24cf4582650682776a9 Mon Sep 17 00:00:00 2001 From: NumberPigeon <108506296+NumberPigeon@users.noreply.github.com> Date: Thu, 31 Aug 2023 01:18:12 +0800 Subject: [PATCH 15/36] refactor: reuse region_id Co-authored-by: Christian Clauss --- sc2reader/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 1922ccb8..1488a088 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -139,7 +139,7 @@ def __init__(self, sid, slot_data): 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]) From bc73f6550721e1edd52b063a745b7e69ca0d5c3b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 7 Sep 2023 10:27:21 +0200 Subject: [PATCH 16/36] PEP 621: Migrate from setup.py to pyproject.toml --- pyproject.toml | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 48 --------------------------------- 2 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..b569cad6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,72 @@ +[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.7" +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.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "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", +] +[project.optional-dependencies] +testing = [ + "pytest", +] +[project.urls] +Homepage = "https://github.com/ggtracker/sc2reader" +[project.scripts] +sc2attributes = "sc2reader.scripts.sc2attributes:main" +sc2json = "sc2reader.scripts.sc2json:main" +sc2parse = "sc2reader.scripts.sc2parse:main" +sc2printer = "sc2reader.scripts.sc2printer:main" +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} diff --git a/setup.py b/setup.py deleted file mode 100644 index 95bf19fd..00000000 --- a/setup.py +++ /dev/null @@ -1,48 +0,0 @@ -import setuptools - -setuptools.setup( - license="MIT", - name="sc2reader", - version="1.8.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 :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "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", "pillow"], - tests_require=["pytest"], - packages=setuptools.find_packages(), - include_package_data=True, - zip_safe=True, -) From 56169d88a059fe7e2aa05eadefacf2f58e1509d4 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 9 Sep 2023 21:00:31 -0400 Subject: [PATCH 17/36] fstrings and fix deprecation of logger.warn --- sc2reader/factories/plugins/replay.py | 4 ++-- sc2reader/log_utils.py | 2 +- sc2reader/objects.py | 4 ++-- sc2reader/resources.py | 4 ++-- sc2reader/utils.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sc2reader/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index 7f645eb3..05c424b4 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -212,8 +212,8 @@ def SelectionTracker(replay): if error: person.selection_errors += 1 if debug: - logger.warn( - "Error detected in deselection mode {}.".format(event.mask_type) + logger.warning( + f"Error detected in deselection mode {event.mask_type}." ) person.selection = player_selections diff --git a/sc2reader/log_utils.py b/sc2reader/log_utils.py index 3337e348..dc20fe92 100644 --- a/sc2reader/log_utils.py +++ b/sc2reader/log_utils.py @@ -33,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, ) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 9a7a0e8b..de82cb04 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -562,7 +562,7 @@ def __init__(self, contents): data = ByteDecoder(contents, endian="LITTLE") magic = data.read_string(4) if magic != "MapI": - self.logger.warn(f"Invalid MapInfo file: {magic}") + self.logger.warning(f"Invalid MapInfo file: {magic}") return #: The map info file format version @@ -773,7 +773,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/resources.py b/sc2reader/resources.py index 924f7bde..3e495c30 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -552,7 +552,7 @@ def get_team(team_id): if team.result == "Win": self.winner = team else: - self.logger.warn( + self.logger.warning( f"Conflicting results for Team {team.number}: {results}" ) team.result = "Unknown" @@ -1336,7 +1336,7 @@ def load_player_stats(self): ) ) elif stat_id != 83886080: # We know this one is always bad. - self.logger.warn(f"Untranslatable key = {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. diff --git a/sc2reader/utils.py b/sc2reader/utils.py index 514ca807..25678955 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -65,7 +65,7 @@ class Color: 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) @@ -78,7 +78,7 @@ 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 From dd282b7e96ccfa5ca9d018738c1fe0778d836241 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 Sep 2023 07:14:51 +0200 Subject: [PATCH 18/36] Lint Python code with ruff instead of flake8 --- .circleci/config.yml | 7 ++----- STYLE_GUIDE.rst | 7 +++---- docs/source/conf.py | 4 +++- examples/sc2autosave.py | 3 ++- ruff.toml | 8 ++++++++ sc2reader/engine/plugins/context.py | 2 +- sc2reader/engine/plugins/selection.py | 4 +++- sc2reader/factories/plugins/replay.py | 1 - sc2reader/factories/sc2factory.py | 6 +++--- sc2reader/resources.py | 6 +++--- sc2reader/scripts/sc2replayer.py | 3 ++- sc2reader/utils.py | 12 +++++++----- 12 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 ruff.toml diff --git a/.circleci/config.yml b/.circleci/config.yml index 96e88466..8f73efb1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,12 +17,9 @@ jobs: steps: - checkout - run: python --version ; pip --version ; pwd ; ls -l - - run: pip install black codespell flake8 ruff + - run: pip install black codespell ruff - run: codespell -L queenland,uint - # stop the build if there are Python syntax errors or undefined names - - run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - run: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - run: ruff . - run: black . --check diff --git a/STYLE_GUIDE.rst b/STYLE_GUIDE.rst index 14f91df5..a25c04ff 100644 --- a/STYLE_GUIDE.rst +++ b/STYLE_GUIDE.rst @@ -1,12 +1,11 @@ STYLE GUIDE ============== -As a rough style guide, please lint your code with black, codespell, and flake8:: +As a rough style guide, please lint your code with black, codespell, and ruff:: - pip install black codespell flake8 + pip install black codespell ruff codespell -L queenland,uint - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + ruff . black . --check More up-to-date checks may be detailed in `.circleci/config.yml`. diff --git a/docs/source/conf.py b/docs/source/conf.py index ead7fdd3..4fc4f46e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,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" diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index dae7bff1..59c573f0 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -192,8 +192,9 @@ 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) diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..899d7e69 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,8 @@ +ignore = [ + "E402", # Module level import not at top of file + "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 .` +] +line-length=129 diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index 4adfc0e1..6b0d05dc 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -263,7 +263,7 @@ def handleUnitTypeChangeEvent(self, event, replay): replay.datapack.change_type(event.unit, event.unit_type_name, event.frame) else: self.logger.error( - "Unit {} type changed at {} [{}] before it was born!".format( + "Unit {} type changed at {} before it was born!".format( event.unit_id, Length(seconds=event.second) ) ) diff --git a/sc2reader/engine/plugins/selection.py b/sc2reader/engine/plugins/selection.py index 006ac78e..4dcc80c2 100644 --- a/sc2reader/engine/plugins/selection.py +++ b/sc2reader/engine/plugins/selection.py @@ -76,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/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index 7f645eb3..16e31ed0 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -85,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), diff --git a/sc2reader/factories/sc2factory.py b/sc2reader/factories/sc2factory.py index c020459f..c5298eb3 100644 --- a/sc2reader/factories/sc2factory.py +++ b/sc2reader/factories/sc2factory.py @@ -266,13 +266,13 @@ def load_remote_resource_contents(self, remote_resource, **options): 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): diff --git a/sc2reader/resources.py b/sc2reader/resources.py index 924f7bde..e74a9d7c 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -1491,8 +1491,8 @@ def __init__(self, header_file, filename=None, **options): # 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]] = utils.get_resource_url( + 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/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index d78410b0..c569c5ad 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -32,7 +32,8 @@ def getch(): 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 diff --git a/sc2reader/utils.py b/sc2reader/utils.py index 514ca807..9dcb5735 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -163,11 +163,14 @@ def get_files( # 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): @@ -315,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), From 01b1cac6597bdab4f8f98e3afab4a888fc6f1920 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 Sep 2023 07:37:32 +0200 Subject: [PATCH 19/36] Fix E402 Module level import not at top of file --- examples/sc2store.py | 4 ++-- ruff.toml | 1 - sc2reader/constants.py | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/sc2store.py b/examples/sc2store.py index 6aff2a98..77d0e71b 100755 --- a/examples/sc2store.py +++ b/examples/sc2store.py @@ -11,8 +11,6 @@ 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 @@ -23,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( diff --git a/ruff.toml b/ruff.toml index 899d7e69..57d86cdc 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,4 @@ ignore = [ - "E402", # Module level import not at top of file "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 diff --git a/sc2reader/constants.py b/sc2reader/constants.py index db34afa7..a1ae473f 100644 --- a/sc2reader/constants.py +++ b/sc2reader/constants.py @@ -1,3 +1,6 @@ +import json +import pkgutil + # These are found in Repack-MPQ/fileset.{locale}#Mods#Core.SC2Mod#{locale}.SC2Data/LocalizedData/Editor/EditorCategoryStrings.txt # EDSTR_CATEGORY_Race # EDSTR_PLAYERPROPS_RACE @@ -101,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() From 9b592cd85592ca4919a0817c4fcc18af35ea522d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 Sep 2023 07:46:01 +0200 Subject: [PATCH 20/36] removestar --in-place . --- sc2reader/engine/engine.py | 3 ++- sc2reader/objects.py | 2 +- sc2reader/readers.py | 15 +++++++++++---- sc2reader/scripts/sc2replayer.py | 3 ++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/sc2reader/engine/engine.py b/sc2reader/engine/engine.py index 9c7ee68f..1cd131da 100644 --- a/sc2reader/engine/engine.py +++ b/sc2reader/engine/engine.py @@ -1,5 +1,6 @@ import collections -from sc2reader.events import * +from sc2reader.events import (CommandEvent, ControlGroupEvent, Event, GameEvent, MessageEvent, + TrackerEvent) from sc2reader.engine.events import InitGameEvent, EndGameEvent, PluginExit diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 9a7a0e8b..0b90e140 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -4,7 +4,7 @@ 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"]) diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 77fb681d..360100db 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -1,10 +1,17 @@ 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, 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 diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index d78410b0..d1f3de44 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -37,7 +37,8 @@ def getch(): import argparse import sc2reader -from sc2reader.events import * +from sc2reader.events import (CameraEvent, CommandEvent, GameStartEvent, PlayerLeaveEvent, + SelectionEvent) def main(): From 34bc4c67f9f407bfa9e39f37e9f5ef02ca0dcfe1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 Sep 2023 08:07:19 +0200 Subject: [PATCH 21/36] Fix undefined name: HotkeyEvent --> ControlGroupEvent --- sc2reader/scripts/sc2replayer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index d1f3de44..3e5a3d1c 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -37,8 +37,8 @@ def getch(): import argparse import sc2reader -from sc2reader.events import (CameraEvent, CommandEvent, GameStartEvent, PlayerLeaveEvent, - SelectionEvent) +from sc2reader.events import (CameraEvent, CommandEvent, ControlGroupEvent, + GameStartEvent, PlayerLeaveEvent, SelectionEvent) def main(): @@ -100,7 +100,7 @@ def main(): 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) From 477a898e43ab4cada4f729e538950261d3739aaa Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 Sep 2023 14:53:37 +0200 Subject: [PATCH 22/36] psf/black --- sc2reader/engine/engine.py | 10 ++++++++-- sc2reader/readers.py | 34 +++++++++++++++++++++++--------- sc2reader/scripts/sc2replayer.py | 10 ++++++++-- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/sc2reader/engine/engine.py b/sc2reader/engine/engine.py index 1cd131da..96b7f759 100644 --- a/sc2reader/engine/engine.py +++ b/sc2reader/engine/engine.py @@ -1,6 +1,12 @@ import collections -from sc2reader.events import (CommandEvent, ControlGroupEvent, Event, GameEvent, MessageEvent, - TrackerEvent) +from sc2reader.events import ( + CommandEvent, + ControlGroupEvent, + Event, + GameEvent, + MessageEvent, + TrackerEvent, +) from sc2reader.engine.events import InitGameEvent, EndGameEvent, PluginExit diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 360100db..f65ee0ed 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -2,16 +2,32 @@ from sc2reader.exceptions import ParseError, ReadError from sc2reader.objects import Attribute -from sc2reader.events.game import (CameraEvent, CommandManagerStateEvent, HijackReplayGameEvent, - PlayerLeaveEvent, ResourceTradeEvent, SelectionEvent, - UpdateTargetPointCommandEvent, UpdateTargetUnitCommandEvent, - UserOptionsEvent, create_command_event, - create_control_group_event) +from sc2reader.events.game import ( + CameraEvent, + CommandManagerStateEvent, + HijackReplayGameEvent, + PlayerLeaveEvent, + ResourceTradeEvent, + SelectionEvent, + UpdateTargetPointCommandEvent, + UpdateTargetUnitCommandEvent, + UserOptionsEvent, + 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.events.tracker import ( + PlayerSetupEvent, + PlayerStatsEvent, + UnitBornEvent, + UnitDiedEvent, + UnitDoneEvent, + UnitInitEvent, + UnitOwnerChangeEvent, + UnitPositionsEvent, + UnitTypeChangeEvent, + UpgradeCompleteEvent, +) from sc2reader.utils import DepotFile from sc2reader.decoders import BitPackedDecoder, ByteDecoder diff --git a/sc2reader/scripts/sc2replayer.py b/sc2reader/scripts/sc2replayer.py index 3e5a3d1c..06f97acd 100755 --- a/sc2reader/scripts/sc2replayer.py +++ b/sc2reader/scripts/sc2replayer.py @@ -37,8 +37,14 @@ def getch(): import argparse import sc2reader -from sc2reader.events import (CameraEvent, CommandEvent, ControlGroupEvent, - GameStartEvent, PlayerLeaveEvent, SelectionEvent) +from sc2reader.events import ( + CameraEvent, + CommandEvent, + ControlGroupEvent, + GameStartEvent, + PlayerLeaveEvent, + SelectionEvent, +) def main(): From 3ceb5a20df5daa97f00b6890d86c8ae29c10cd5d Mon Sep 17 00:00:00 2001 From: Kevin Leung Date: Fri, 22 Sep 2023 22:00:30 -0700 Subject: [PATCH 23/36] Update CONTRIBUTING.md Minor updates for current tooling --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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. From e5435e6add32de8e77eeed295f68e6dd868cd677 Mon Sep 17 00:00:00 2001 From: manuelseeger Date: Thu, 28 Dec 2023 13:50:36 +0100 Subject: [PATCH 24/36] Fix division by zero on instant leave replays --- sc2reader/factories/plugins/replay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sc2reader/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index 676f0117..ff5986eb 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -126,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 ) From d832145f3958cd068e599872f9faa07505c5bf0f Mon Sep 17 00:00:00 2001 From: Andrene Date: Wed, 29 May 2024 19:18:39 -0400 Subject: [PATCH 25/36] add type for dialog control events --- sc2reader/events/game.py | 17 +++++++++++++++++ sc2reader/readers.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 26fc2813..61889117 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -765,3 +765,20 @@ def __init__(self, frame, pid, data): #: 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 = self.data["event_type"] + + #: Data specific to event type such as changes or clicks + self.event_data = self.data["event_data"] \ No newline at end of file diff --git a/sc2reader/readers.py b/sc2reader/readers.py index f65ee0ed..9403e133 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -12,6 +12,7 @@ UpdateTargetPointCommandEvent, UpdateTargetUnitCommandEvent, UserOptionsEvent, + DialogControlEvent, create_command_event, create_control_group_event, ) @@ -462,7 +463,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), From 8e799a79e2ab4c9b8dd4d91b0ef690a8a61760b4 Mon Sep 17 00:00:00 2001 From: Andrene Date: Wed, 29 May 2024 19:26:36 -0400 Subject: [PATCH 26/36] not self --- sc2reader/events/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 61889117..38555bfd 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -778,7 +778,7 @@ def __init__(self, frame, pid, data): self.control_id = data["control_id"] #: How dialog was interacted with - self.event_type = self.data["event_type"] + self.event_type = data["event_type"] #: Data specific to event type such as changes or clicks - self.event_data = self.data["event_data"] \ No newline at end of file + self.event_data = data["event_data"] \ No newline at end of file From 2333c32f69cecd5c44770588aaf6ecd59794919f Mon Sep 17 00:00:00 2001 From: Andrene Date: Wed, 29 May 2024 19:45:04 -0400 Subject: [PATCH 27/36] style fixes --- examples/sc2autosave.py | 16 ++++++++-------- sc2reader/engine/plugins/creeptracker.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index 59c573f0..3d826680 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -77,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 @@ -96,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 \ @@ -112,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 \ diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index 39bb6b91..2916bdba 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -105,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 From 38a52f1a1adc69c771110ee5a6851e341d157a97 Mon Sep 17 00:00:00 2001 From: Andrene Date: Thu, 30 May 2024 08:37:11 -0400 Subject: [PATCH 28/36] ignore assertin --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8f73efb1..e89a8abe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - checkout - run: python --version ; pip --version ; pwd ; ls -l - run: pip install black codespell ruff - - run: codespell -L queenland,uint + - run: codespell -L queenland,uint,assertin - run: ruff . - run: black . --check From 702f78118306367e25201e08b2726236ec185e7d Mon Sep 17 00:00:00 2001 From: Andrene Date: Thu, 30 May 2024 14:41:22 -0400 Subject: [PATCH 29/36] style cleaning --- sc2reader/engine/plugins/creeptracker.py | 6 +- sc2reader/events/game.py | 4 +- sc2reader/readers.py | 799 +++++++++++++---------- test_replays/test_replays.py | 4 +- 4 files changed, 471 insertions(+), 342 deletions(-) diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index 2916bdba..d947c42a 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -123,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 diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 38555bfd..008f6ef9 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -766,11 +766,13 @@ def __init__(self, frame, pid, data): #: 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) @@ -781,4 +783,4 @@ def __init__(self, frame, pid, data): self.event_type = data["event_type"] #: Data specific to event type such as changes or clicks - self.event_data = data["event_data"] \ No newline at end of file + self.event_data = data["event_data"] diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 9403e133..0dbacce4 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -40,48 +40,68 @@ def __call__(self, data, replay): 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)) ], @@ -95,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), @@ -123,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), @@ -142,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), @@ -185,131 +213,158 @@ 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, + 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( - 16 - if replay.base_build >= 77379 - else 13 - if replay.base_build >= 70154 - else 9 + 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( + 16 + if replay.base_build >= 77379 + else 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=[ + ] + 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))], + [ + data.read_uint32(), + [ + data.read_uint32() + for i in range(data.read_bits(17)) + ], + ] + for j 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, + 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(): @@ -357,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] ], @@ -376,9 +431,11 @@ 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, ) @@ -739,9 +796,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): @@ -1004,13 +1063,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: ( @@ -1031,9 +1094,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), @@ -1159,13 +1222,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: ( @@ -1186,9 +1253,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), @@ -1210,13 +1277,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: ( @@ -1236,12 +1307,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), @@ -1307,13 +1378,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: ( @@ -1333,12 +1408,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), @@ -1544,9 +1619,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, @@ -1618,12 +1695,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)) @@ -1633,9 +1714,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, @@ -1656,12 +1739,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, ) @@ -1713,15 +1800,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)) ], @@ -1730,9 +1823,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, @@ -1743,15 +1838,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 + ), ) @@ -1871,13 +1970,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: ( @@ -1897,12 +2000,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), @@ -1949,9 +2052,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, @@ -1966,15 +2071,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, ) @@ -2020,13 +2129,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): @@ -2041,13 +2154,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: ( @@ -2067,12 +2184,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), @@ -2155,13 +2272,17 @@ class GameEventsReader_64469(GameEventsReader_38996): 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: ( @@ -2181,12 +2302,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), @@ -2233,13 +2354,17 @@ class GameEventsReader_80669(GameEventsReader_65895): 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, + 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: ( @@ -2259,12 +2384,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), diff --git a/test_replays/test_replays.py b/test_replays/test_replays.py index a63c59fc..4ea53c20 100644 --- a/test_replays/test_replays.py +++ b/test_replays/test_replays.py @@ -170,7 +170,9 @@ 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] + 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") From 0377720aa29be87da52a143b7cfc2d2daae96a63 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 17 Jan 2025 09:29:53 +0100 Subject: [PATCH 30/36] Split lines with str.splitlines() --- new_units.py | 4 ++-- sc2reader/data/__init__.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/new_units.py b/new_units.py index 86d82ee5..26ffa133 100644 --- a/new_units.py +++ b/new_units.py @@ -9,7 +9,7 @@ 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(",") @@ -25,7 +25,7 @@ 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) diff --git a/sc2reader/data/__init__.py b/sc2reader/data/__init__.py index ccd6455c..10598e02 100755 --- a/sc2reader/data/__init__.py +++ b/sc2reader/data/__init__.py @@ -15,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 @@ -24,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 @@ -401,7 +401,7 @@ def load_build(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,7 +421,7 @@ def load_build(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 From 9401d984ca15ec8d05f371499102afc158895af8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 17 Jan 2025 09:33:35 +0100 Subject: [PATCH 31/36] CircleCI: ruff check --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e89a8abe..85dfc97c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,7 @@ jobs: - run: python --version ; pip --version ; pwd ; ls -l - run: pip install black codespell ruff - run: codespell -L queenland,uint,assertin - - run: ruff . + - run: ruff check - run: black . --check From 2a727cc60965a6d7572973c4ea2cc22fe04dda02 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 17 Jan 2025 09:42:04 +0100 Subject: [PATCH 32/36] sc2reader/objects.py:51:9: F811 --- sc2reader/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 9ee978e7..10dbc755 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -48,7 +48,7 @@ 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() From 6b7f782129aaaca780ff4d64109def4bfc1045a9 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 19 Jan 2025 18:06:12 +0100 Subject: [PATCH 33/36] Update ruff settings and fix rules C417 and UP032 --- examples/sc2autosave.py | 4 +- generate_build_data.py | 4 +- pyproject.toml | 46 +++++++++------ ruff.toml | 7 --- sc2reader/data/__init__.py | 4 +- sc2reader/engine/plugins/context.py | 74 +++++------------------- sc2reader/engine/plugins/creeptracker.py | 14 ++--- sc2reader/events/game.py | 22 ++----- sc2reader/events/tracker.py | 32 +++++----- sc2reader/factories/plugins/replay.py | 23 ++------ sc2reader/factories/sc2factory.py | 4 +- sc2reader/objects.py | 4 +- sc2reader/readers.py | 12 +--- sc2reader/resources.py | 6 +- sc2reader/scripts/sc2parse.py | 12 +--- sc2reader/scripts/sc2printer.py | 16 +---- sc2reader/utils.py | 2 +- 17 files changed, 91 insertions(+), 195 deletions(-) delete mode 100644 ruff.toml diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index 3d826680..3364b4a9 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -373,9 +373,7 @@ def reset(args): exit("Cannot reset, destination must be directory: {0}", args.dest) print( - "About to reset directory: {}\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": diff --git a/generate_build_data.py b/generate_build_data.py index a13f9807..7419e547 100644 --- a/generate_build_data.py +++ b/generate_build_data.py @@ -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: diff --git a/pyproject.toml b/pyproject.toml index b569cad6..e33c1d05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,9 +13,9 @@ keywords = [ "sc2", "starcraft 2", ] -license = {text = "MIT"} -authors = [{name = "Kevin Leung", email = "kkleung89@gmail.com"}] -requires-python = ">=3.7" +license = { text = "MIT" } +authors = [ { name = "Kevin Leung", email = "kkleung89@gmail.com" } ] +requires-python = ">=3.9" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -25,11 +25,11 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "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", @@ -46,27 +46,35 @@ dependencies = [ "mpyq", "pillow", ] -[project.optional-dependencies] -testing = [ +optional-dependencies.testing = [ "pytest", ] -[project.urls] -Homepage = "https://github.com/ggtracker/sc2reader" -[project.scripts] -sc2attributes = "sc2reader.scripts.sc2attributes:main" -sc2json = "sc2reader.scripts.sc2json:main" -sc2parse = "sc2reader.scripts.sc2parse:main" -sc2printer = "sc2reader.scripts.sc2printer:main" -sc2replayer = "sc2reader.scripts.sc2replayer:main" +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"] +platforms = [ "any" ] [tool.setuptools.dynamic] -readme = {file = ["README.rst", "CHANGELOG.rst"]} -version = {attr = "sc2reader.__version__"} +readme = { file = [ "README.rst", "CHANGELOG.rst" ] } +version = { attr = "sc2reader.__version__" } [tool.setuptools.packages] -find = {namespaces = false} +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/ruff.toml b/ruff.toml deleted file mode 100644 index 57d86cdc..00000000 --- a/ruff.toml +++ /dev/null @@ -1,7 +0,0 @@ -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 .` -] -line-length=129 diff --git a/sc2reader/data/__init__.py b/sc2reader/data/__init__.py index 10598e02..d97e4885 100755 --- a/sc2reader/data/__init__.py +++ b/sc2reader/data/__init__.py @@ -339,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 {} to {} [frame {}]; unit type not found in build {}".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( diff --git a/sc2reader/engine/plugins/context.py b/sc2reader/engine/plugins/context.py index 6b0d05dc..38f8b3d2 100644 --- a/sc2reader/engine/plugins/context.py +++ b/sc2reader/engine/plugins/context.py @@ -43,12 +43,7 @@ def handleCommandEvent(self, event, replay): event.logger.error("\t" + player.__str__()) self.logger.error( - "{}\t{}\tMissing ability {:X} from {}".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: @@ -197,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 {} at {} [{}], index not active.".format( - event.unit_id_index, 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 {} died at {} [{}] 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: @@ -215,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 {} at {} [{}]".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: @@ -227,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 {} at {} [{}]".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): @@ -243,9 +230,7 @@ def handleUnitOwnerChangeEvent(self, event, replay): event.unit = replay.objects[event.unit_id] else: self.logger.error( - "Unit {} owner changed at {} [{}] 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 and event.unit: @@ -263,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 {} type changed at {} 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): @@ -306,9 +289,7 @@ def handleUnitDoneEvent(self, event, replay): event.unit.finished_at = event.frame else: self.logger.error( - "Unit {} done at {} [{}] before it was started!".format( - event.unit_id, 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): @@ -322,9 +303,7 @@ def handleUnitPositionsEvent(self, event, replay): event.units[unit] = unit.location else: self.logger.error( - "Unit at active_unit index {} moved at {} [{}] but it doesn't exist!".format( - unit_index, 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): @@ -336,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 ({}) for event {} at {} [{}].".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 @@ -352,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 ({}) for event {} at {} [{}].".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 @@ -367,12 +336,7 @@ def load_tracker_player(self, event, replay): event.player = replay.entity[event.pid] else: self.logger.error( - "Bad pid ({}) for event {} at {} [{}].".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): @@ -380,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 ({}) for event {} at {} [{}].".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): @@ -393,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 ({}) for event {} at {} [{}].".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 d947c42a..7125924d 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -229,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]) @@ -247,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) @@ -255,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) @@ -286,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/events/game.py b/sc2reader/events/game.py index 008f6ef9..0da9afa2 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -38,9 +38,7 @@ def _str_prefix(self): if getattr(self, "pid", 16) == 16: player_name = "Global" elif self.player and not self.player.name: - player_name = "Player {} - ({})".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: @@ -244,9 +242,7 @@ def __str__(self): string += "Right Click" if self.ability_type == "TargetUnit": - string += "; Target: {} [{: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 += f"; Location: {str(self.location)}" @@ -643,7 +639,7 @@ def __init__(self, frame, pid, data): self.yaw = data["yaw"] def __str__(self): - return self._str_prefix() + "{} at ({}, {})".format(self.name, self.x, self.y) + return self._str_prefix() + f"{self.name} at ({self.x}, {self.y})" @loggable @@ -686,13 +682,7 @@ def __init__(self, frame, pid, data): def __str__(self): return ( self._str_prefix() - + " transfer {} minerals, {} gas, {} terrazine, and {} custom to {}".format( - self.minerals, - self.vespene, - self.terrazine, - self.custom_resource, - self.recipient, - ) + + f" transfer {self.minerals} minerals, {self.vespene} gas, {self.terrazine} terrazine, and {self.custom_resource} custom to {self.recipient}" ) @@ -722,9 +712,7 @@ def __init__(self, frame, pid, data): def __str__(self): return ( self._str_prefix() - + " requests {} minerals, {} gas, {} terrazine, and {} custom".format( - self.minerals, self.vespene, self.terrazine, self.custom_resource - ) + + f" requests {self.minerals} minerals, {self.vespene} gas, {self.terrazine} terrazine, and {self.custom_resource} custom" ) diff --git a/sc2reader/events/tracker.py b/sc2reader/events/tracker.py index 7355ef35..8e0c6772 100644 --- a/sc2reader/events/tracker.py +++ b/sc2reader/events/tracker.py @@ -334,8 +334,9 @@ def __init__(self, frames, data, build): self.location = (self.x, self.y) def __str__(self): - return self._str_prefix() + "{: >15} - Unit born {}".format( - str(self.unit_upkeeper), self.unit + return ( + self._str_prefix() + + f"{str(self.unit_upkeeper): >15} - Unit born {self.unit}" ) @@ -409,8 +410,8 @@ def __init__(self, frames, data, build): ) def __str__(self): - return self._str_prefix() + "{: >15} - Unit died {}.".format( - str(self.unit.owner), self.unit + return ( + self._str_prefix() + f"{str(self.unit.owner): >15} - Unit died {self.unit}." ) @@ -448,9 +449,7 @@ def __init__(self, frames, data, build): self.unit_controller = None def __str__(self): - return self._str_prefix() + "{: >15} took {}".format( - str(self.unit_upkeeper), self.unit - ) + return self._str_prefix() + f"{str(self.unit_upkeeper): >15} took {self.unit}" class UnitTypeChangeEvent(TrackerEvent): @@ -479,8 +478,9 @@ def __init__(self, frames, data, build): self.unit_type_name = data[2].decode("utf8") def __str__(self): - return self._str_prefix() + "{: >15} - Unit {} type changed to {}".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}" ) @@ -505,8 +505,9 @@ def __init__(self, frames, data, build): self.count = data[2] def __str__(self): - return self._str_prefix() + "{: >15} - {} 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" ) @@ -564,8 +565,9 @@ def __init__(self, frames, data, build): self.location = (self.x, self.y) def __str__(self): - return self._str_prefix() + "{: >15} - Unit initiated {}".format( - str(self.unit_upkeeper), self.unit + return ( + self._str_prefix() + + f"{str(self.unit_upkeeper): >15} - Unit initiated {self.unit}" ) @@ -591,8 +593,8 @@ def __init__(self, frames, data, build): self.unit = None def __str__(self): - return self._str_prefix() + "{: >15} - Unit {} done".format( - str(self.unit.owner), self.unit + return ( + self._str_prefix() + f"{str(self.unit.owner): >15} - Unit {self.unit} done" ) diff --git a/sc2reader/factories/plugins/replay.py b/sc2reader/factories/plugins/replay.py index ff5986eb..724a51e3 100644 --- a/sc2reader/factories/plugins/replay.py +++ b/sc2reader/factories/plugins/replay.py @@ -155,12 +155,7 @@ def SelectionTracker(replay): selections[event.control_group] = control_group if debug: logger.info( - "[{}] {} selected {} units: {}".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": @@ -168,9 +163,7 @@ def SelectionTracker(replay): selections[event.control_group] = selections[0x0A].copy() if debug: logger.info( - "[{}] {} set hotkey {} 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": @@ -181,9 +174,7 @@ def SelectionTracker(replay): selections[event.control_group] = control_group if debug: logger.info( - "[{}] {} added current selection to hotkey {}".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": @@ -193,13 +184,7 @@ def SelectionTracker(replay): selections[0xA] = control_group if debug: logger.info( - "[{}] {} retrieved hotkey {}, {} units: {}".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: diff --git a/sc2reader/factories/sc2factory.py b/sc2reader/factories/sc2factory.py index c5298eb3..40de1c7f 100644 --- a/sc2reader/factories/sc2factory.py +++ b/sc2reader/factories/sc2factory.py @@ -293,9 +293,7 @@ def __init__(self, cache_dir, **options): ) elif not os.access(self.cache_dir, os.F_OK | os.W_OK | os.R_OK): raise ValueError( - "Must have read/write access to {} 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): diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 10dbc755..03632dee 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -438,9 +438,7 @@ def __init__(self, pid): def __str__(self): if not self.is_ai: - return "User {}-S2-{}-{}".format( - self.region.upper(), self.subregion, self.bnetid - ) + return f"User {self.region.upper()}-S2-{self.subregion}-{self.bnetid}" else: return f"AI ({self.play_race})" diff --git a/sc2reader/readers.py b/sc2reader/readers.py index 0dbacce4..c74c7c89 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -601,9 +601,7 @@ def __call__(self, data, replay): # Otherwise throw a read error else: raise ReadError( - "Event type {} unknown at position {}.".format( - hex(event_type), hex(event_start) - ), + f"Event type {hex(event_type)} unknown at position {hex(event_start)}.", event_type, event_start, replay, @@ -617,9 +615,7 @@ def __call__(self, data, replay): return game_events except ParseError as e: raise ReadError( - "Parse error '{}' unknown at position {}.".format( - e.msg, hex(event_start) - ), + f"Parse error '{e.msg}' unknown at position {hex(event_start)}.", event_type, event_start, replay, @@ -628,9 +624,7 @@ def __call__(self, data, replay): ) except EOFError as e: raise ReadError( - "EOFError error '{}' unknown at position {}.".format( - e.msg, hex(event_start) - ), + f"EOFError error '{e.msg}' unknown at position {hex(event_start)}.", event_type, event_start, replay, diff --git a/sc2reader/resources.py b/sc2reader/resources.py index d42ceea2..388f1765 100644 --- a/sc2reader/resources.py +++ b/sc2reader/resources.py @@ -578,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() @@ -884,9 +884,7 @@ def _get_reader(self, data_file): return reader else: raise ValueError( - "Valid {} reader could not found for build {}".format( - data_file, self.build - ) + f"Valid {data_file} reader could not found for build {self.build}" ) def _get_datapack(self): diff --git a/sc2reader/scripts/sc2parse.py b/sc2reader/scripts/sc2parse.py index 22f792a4..4b517908 100755 --- a/sc2reader/scripts/sc2parse.py +++ b/sc2reader/scripts/sc2parse.py @@ -71,9 +71,7 @@ def main(): } 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( @@ -82,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( @@ -98,9 +94,7 @@ def main(): ) ) print( - "Units were: {units}".format( - units={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: diff --git a/sc2reader/scripts/sc2printer.py b/sc2reader/scripts/sc2printer.py index 634aff53..76241a05 100755 --- a/sc2reader/scripts/sc2printer.py +++ b/sc2reader/scripts/sc2printer.py @@ -24,16 +24,10 @@ def printReplay(filepath, arguments): print(" Teams: {}".format("v".join(lineups))) for team in replay.teams: print( - " Team {}\t{} ({})".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{} ({})".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: @@ -51,11 +45,7 @@ def printReplay(filepath, arguments): raise return prev = e.game_events[-1] - print( - "\nVersion {} replay:\n\t{}".format( - e.replay.release_string, e.replay.filepath - ) - ) + 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")) diff --git a/sc2reader/utils.py b/sc2reader/utils.py index dae65a9d..7b638d3f 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -93,7 +93,7 @@ def hex(self): """ The hexadecimal representation of the color """ - return "{0.r:02X}{0.g:02X}{0.b:02X}".format(self) + return f"{self.r:02X}{self.g:02X}{self.b:02X}" def __str__(self): return self.name From 7b8dbf7d2ff2e9160bc6855765c9e79982207f59 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 27 Apr 2025 22:29:24 +0200 Subject: [PATCH 34/36] Fix examples/sc2autosave.py my moving sc2reader.scripts.utils.Formatter --- examples/sc2autosave.py | 72 ++++++++++++++++++++++++++++++++------ examples/sc2store.py | 4 +-- sc2reader/__init__.py | 24 ++++++------- sc2reader/readers.py | 2 +- sc2reader/scripts/utils.py | 59 ------------------------------- sc2reader/utils.py | 6 ++-- 6 files changed, 80 insertions(+), 87 deletions(-) delete mode 100644 sc2reader/scripts/utils.py diff --git a/examples/sc2autosave.py b/examples/sc2autosave.py index 3364b4a9..d2ae97c0 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -159,18 +159,71 @@ POST-Parse, how to do it?!?!?!?! """ import argparse -import cPickle import os +import pickle +import re import shutil import sys +import textwrap import time 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): @@ -375,8 +428,7 @@ def reset(args): print( 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": + 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) @@ -410,7 +462,7 @@ def setup(args): 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) + return pickle.load(file) else: return sc2reader.utils.AttributeDict(last_sync=0) @@ -420,7 +472,7 @@ def save_state(state, args): data_file = os.path.join(args.dest, "sc2autosave.dat") if not args.dryrun: with open(data_file, "w") as file: - cPickle.dump(state, file) + pickle.dump(state, file) else: args.log.write(f"Writing state to file: {data_file}\n") @@ -429,7 +481,7 @@ 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", ) diff --git a/examples/sc2store.py b/examples/sc2store.py index 77d0e71b..7f54a3fb 100755 --- a/examples/sc2store.py +++ b/examples/sc2store.py @@ -1,10 +1,10 @@ #!/usr/bin/env python -import cPickle import os +import pickle import shutil -import sys import sqlite3 +import sys import time import sc2reader diff --git a/sc2reader/__init__.py b/sc2reader/__init__.py index 415b270d..49b76657 100644 --- a/sc2reader/__init__.py +++ b/sc2reader/__init__.py @@ -1,21 +1,21 @@ """ - 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. """ __version__ = "1.8.0" diff --git a/sc2reader/readers.py b/sc2reader/readers.py index c74c7c89..eab6e4c9 100644 --- a/sc2reader/readers.py +++ b/sc2reader/readers.py @@ -1857,7 +1857,7 @@ def __init__(self): 25: ( None, self.command_manager_reset_event, - ), # Re-using this old number + ), # Reusing this old number 61: (None, self.trigger_hotkey_pressed_event), 103: (CommandManagerStateEvent, self.command_manager_state_event), 104: ( diff --git a/sc2reader/scripts/utils.py b/sc2reader/scripts/utils.py deleted file mode 100644 index 1f87baa4..00000000 --- a/sc2reader/scripts/utils.py +++ /dev/null @@ -1,59 +0,0 @@ -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 7b638d3f..660f07bd 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -1,11 +1,11 @@ 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: From da0747fbe278ed46a851b40533573d8a678a595a Mon Sep 17 00:00:00 2001 From: vince1st Date: Tue, 29 Apr 2025 19:23:13 +0200 Subject: [PATCH 35/36] Fix: examples/sc2autosave.py update to Python 3 Fixed the file by modifying it to use dict() instead of a custom diction that was deleted, modify the map-name parameter to work with the current library, the example was try with python 3.11 with the command: `python .\examples\sc2autosave.py --rename "{matchup}/({length}) {map} {teams}" --length "%M:%S"` a few parameters are yet to be tested --- docs/source/utilities.rst | 13 ------------- examples/sc2autosave.py | 26 ++++++++++++++------------ 2 files changed, 14 insertions(+), 25 deletions(-) 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 d2ae97c0..af272530 100755 --- a/examples/sc2autosave.py +++ b/examples/sc2autosave.py @@ -166,6 +166,7 @@ import sys import textwrap import time +from functools import cmp_to_key import sc2reader @@ -268,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" @@ -281,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 @@ -368,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), @@ -412,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): @@ -439,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): @@ -461,17 +463,17 @@ def setup(args): 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: + 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: + with open(data_file, "wb") as file: pickle.dump(state, file) else: args.log.write(f"Writing state to file: {data_file}\n") From 2f0b29ee2d12bf550e079519ef62bf707a30142d Mon Sep 17 00:00:00 2001 From: Kevin Leung Date: Mon, 10 Nov 2025 20:58:35 -0800 Subject: [PATCH 36/36] remove dead link --- README.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 63d74a99..40222eca 100644 --- a/README.rst +++ b/README.rst @@ -14,16 +14,15 @@ Who Uses sc2reader? sc2reader is currently powering: -* Websites: `gggreplays.com`_, `gamereplays.org`_, `spawningtool.com`_ +* Websites: `spawningtool.com`_, `gamereplays.org`_ * 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`_. -.. _gggreplays.com: http://gggreplays.com -.. _gamereplays.org: http://www.gamereplays.org/starcraft2/ .. _spawningtool.com: https://lotv.spawningtool.com +.. _gamereplays.org: http://www.gamereplays.org/starcraft2/ .. _The Core: http://www.teamliquid.net/forum/viewmessage.php?topic_id=341878 .. _Midi Conversion: https://github.com/obohrer/sc2midi