diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e179401a..ffd618fb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,34 +1,103 @@
# If you change the name, change the link on the README.md for the badge too
name: Tests
-
on:
push:
paths:
- - sc2/**
- - examples/**
- - test/**
- - docs_generate/**
- - pyproject.toml
- - poetry.lock
- - README.md
- - .github/workflows/ci.yml
+ - sc2/**
+ - examples/**
+ - test/**
+ - docs_generate/**
+ - .pre-commit-config.yaml
+ - generate_dicts_from_data_json.py
+ - generate_id_constants_from_stableid.py
+ - uv.lock
+ - pyproject.toml
+ - README.md
+ - .github/workflows/ci.yml
pull_request:
branches:
- - master
- - develop
+ - master
+ - develop
env:
# Docker image version, see https://hub.docker.com/r/burnysc2/python-sc2-docker/tags
# This version should always lack behind one version behind the docker-ci.yml because it is possible that it doesn't exist
- VERSION_NUMBER: '1.0.1'
- LATEST_PYTHON_VERSION: '3.10'
- LATEST_SC2_VERSION: '4.10'
+ VERSION_NUMBER: "1.0.4"
+ # TODO Change to '3.13' when a new image has been pushed
+ LATEST_PYTHON_VERSION: "3.12"
+ LATEST_SC2_VERSION: "4.10"
jobs:
+ run_pre_commit_hook:
+ name: Run pre-commit hook
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.LATEST_PYTHON_VERSION }}
+
+ - name: Install uv
+ run: pip install uv
+
+ - name: Install dependencies
+ run: |
+ uv sync --frozen --no-cache --no-install-project
+ uv run pre-commit install
+
+ - name: Run pre-commit hooks
+ run: uv run pre-commit run --all-files --hook-stage push
+
+ generate_dicts_from_data_json:
+ name: Generate dicts from data.json
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.LATEST_PYTHON_VERSION }}
+
+ - name: Install uv
+ run: pip install uv
+
+ - name: Install dependencies
+ run: |
+ uv sync --frozen --no-cache --no-install-project
+ uv run pre-commit install
+
+ - name: Run generate dicts
+ # Check if newly generated file is the same as existing file
+ # Run pre-commit hook to format files, always return exit code 0 to not end CI run
+ run: |
+ mv sc2/dicts sc2/dicts_old
+ uv run python generate_dicts_from_data_json.py
+ uv run pre-commit run --all-files --hook-stage push || true
+ rm -rf sc2/dicts/__pycache__ sc2/dicts_old/__pycache__
+
+ - name: Upload generated dicts folder as artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: Generated_dicts
+ path: sc2/dicts
+
+ - name: Compare generated dict files
+ # Exit code will be 0 if the results of both commands are equal
+ run: |
+ [[ `ls sc2/dicts | md5sum` == `ls sc2/dicts_old | md5sum` ]]
+
run_pytest_tests:
# Run pytest tests on pickle files (pre-generated SC2 API observations)
name: Run pytest
+ needs: [run_pre_commit_hook, generate_dicts_from_data_json]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
@@ -37,79 +106,55 @@ jobs:
# Python 3.6 fails due to: https://www.python.org/dev/peps/pep-0563/
# If all type annotations were removed, this library should run in py3.6 and perhaps even 3.5
# Python 3.7 support has been dropped due to missing cached_property (new since Python 3.8) https://docs.python.org/3/library/functools.html#functools.cached_property
+ # Python 3.8 support has been dropped because numpy >=1.26.0 requires Python >=3.9 (this numpy version is required to run python 3.12)
+ # Python 3.9 support has been dropped since numpy >=2.1.0 (this numpy version is required to run python 3.13)
os: [macos-latest, windows-latest, ubuntu-latest]
- python-version: ['3.8', '3.9', '3.10', '3.11']
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- - uses: actions/checkout@v2
-
- - name: Set up Python ${{ matrix.python-version }}
- id: setup-python
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install Poetry
- run: |
- pip install poetry
-
- - name: Make poetry use local .venv folder
- run: poetry config virtualenvs.in-project true
-
- - name: Set up cache
- if: matrix.os != 'windows-latest'
- uses: actions/cache@v3
- with:
- path: .venv
- key: ${{ matrix.os }}-${{ steps.setup-python.outputs.python-version }}-poetry-${{ hashFiles('poetry.lock') }}
-
- - name: Install dependencies
- run: |
- poetry install
-
- - name: Run yapf autoformat
- if: matrix.os != 'windows-latest'
- run: |
- poetry run yapf --in-place $(git ls-files '*.py')
-
- - name: Run pylint on sc2 folder
- if: matrix.os != 'windows-latest'
- run: |
- poetry run pylint $(git ls-files '*.py' | grep -E 'sc2/.*')
-
- - name: Run pylint on examples folder
- if: matrix.os != 'windows-latest'
- run: |
- poetry run pylint $(git ls-files '*.py' | grep -E 'examples/.*')
-
- - name: Run pytest
- run: |
- poetry run pytest test
-
- # Run benchmarks
- - name: Run benchmark benchmark_array_creation
- run: |
- poetry run pytest test/benchmark_array_creation.py
-
- - name: Run benchmark benchmark_distance_two_points
- run: |
- poetry run pytest test/benchmark_distance_two_points.py
-
- - name: Run benchmark benchmark_distances_cdist
- run: |
- poetry run pytest test/benchmark_distances_cdist.py
-
- - name: Run benchmark benchmark_distances_points_to_point
- run: |
- poetry run pytest test/benchmark_distances_points_to_point.py
-
- - name: Run benchmark benchmark_distances_units
- run: |
- poetry run pytest test/benchmark_distances_units.py
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ matrix.python-version }}
+ id: setup-python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install uv
+ run: pip install uv
+
+ - name: Install dependencies
+ run: uv sync --frozen --no-cache --no-install-project
+
+ - name: Run pytest
+ run: uv run python -m pytest test
+
+ # Run benchmarks
+ - name: Run benchmark benchmark_array_creation
+ run: uv run python -m pytest test/benchmark_array_creation.py
+
+ - name: Run benchmark benchmark_distance_two_points
+ run: uv run python -m pytest test/benchmark_distance_two_points.py
+
+ - name: Run benchmark benchmark_distances_cdist
+ run: uv run python -m pytest test/benchmark_distances_cdist.py
+
+ - name: Run benchmark benchmark_distances_points_to_point
+ run: uv run python -m pytest test/benchmark_distances_points_to_point.py
+
+ - name: Run benchmark benchmark_distances_units
+ run: uv run python -m pytest test/benchmark_distances_units.py
+
+ - name: Run benchmark benchmark_bot_ai_prepare_units
+ run: uv run python -m pytest test/benchmark_prepare_units.py
+
+ - name: Run benchmark benchmark_bot_ai_init
+ run: uv run python -m pytest test/benchmark_bot_ai_init.py
run_test_bots:
# Run test bots that download the SC2 linux client and run it
name: Run testbots linux
+ needs: [run_pytest_tests]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
@@ -117,206 +162,265 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
- python-version: ['3.8', '3.9', '3.10']
- sc2-version: ['4.10']
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+ sc2-version: ["4.10"]
env:
IMAGE_NAME: burnysc2/python-sc2:local
steps:
- # Copy data from repository
- - uses: actions/checkout@v2
-
- - name: Print directories and files
- run: |
- sudo apt-get install tree
- tree
-
- - name: Load and build docker image
- # Build docker image from Dockerfile using specific python and sc2 version
- env:
- BUILD_ARGS: --build-arg PYTHON_VERSION=${{ matrix.python-version }} --build-arg SC2_VERSION=${{ matrix.sc2-version }}
- run: |
- docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS .
-
- - name: Run autotest_bot.py
- # Run bot and list resulting files (replay file, stable_id.json)
- run: |
- docker run -i -d --name my_container $IMAGE_NAME
- docker exec -i my_container bash -c "python test/travis_test_script.py test/autotest_bot.py"
- docker exec -i my_container bash -c "tree"
- docker rm -f my_container
-
- - name: Run upgradestest_bot.py
- run: |
- docker run -i -d --name my_container $IMAGE_NAME
- docker exec -i my_container bash -c "python test/travis_test_script.py test/upgradestest_bot.py"
- docker exec -i my_container bash -c "tree"
- docker rm -f my_container
-
- - name: Run damagetest_bot.py
- run: |
- docker run -i -d --name my_container $IMAGE_NAME
- docker exec -i my_container bash -c "python test/travis_test_script.py test/damagetest_bot.py"
- docker exec -i my_container bash -c "tree"
- docker rm -f my_container
-
- - name: Run queries_test_bot.py
- run: |
- docker run -i -d --name my_container $IMAGE_NAME
- docker exec -i my_container bash -c "python test/travis_test_script.py test/queries_test_bot.py"
- docker exec -i my_container bash -c "tree"
- docker rm -f my_container
+ # Copy data from repository
+ - uses: actions/checkout@v3
+
+ - name: Print directories and files
+ run: sudo apt-get install tree && tree
+
+ - name: Load and build docker image
+ # Build docker image from Dockerfile using specific python and sc2 version
+ env:
+ BUILD_ARGS: --build-arg PYTHON_VERSION=${{ matrix.python-version }} --build-arg SC2_VERSION=${{ matrix.sc2-version }}
+ run: docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS --build-arg VERSION_NUMBER=${{ env.VERSION_NUMBER }} .
+
+ - name: Run autotest_bot.py
+ # Run bot and list resulting files (replay file, stable_id.json)
+ run: |
+ docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME
+ docker exec -i my_container bash -c "python test/travis_test_script.py test/autotest_bot.py"
+ docker exec -i my_container bash -c "tree"
+ docker rm -f my_container
+
+ - name: Run upgradestest_bot.py
+ run: |
+ docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME
+ docker exec -i my_container bash -c "python test/travis_test_script.py test/upgradestest_bot.py"
+ docker exec -i my_container bash -c "tree"
+ docker rm -f my_container
+
+ - name: Run damagetest_bot.py
+ run: |
+ docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME
+ docker exec -i my_container bash -c "python test/travis_test_script.py test/damagetest_bot.py"
+ docker exec -i my_container bash -c "tree"
+ docker rm -f my_container
+
+ - name: Run queries_test_bot.py
+ run: |
+ docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME
+ docker exec -i my_container bash -c "python test/travis_test_script.py test/queries_test_bot.py"
+ docker exec -i my_container bash -c "tree"
+ docker rm -f my_container
run_example_bots:
# Run example bots against computer
name: Run example bots against computer
- runs-on: ${{ matrix.os }}
+ needs: [run_pytest_tests]
+ runs-on: ubuntu-latest
timeout-minutes: 60
- strategy:
- # Do not allow this test to cancel. Finish all jobs regardless of error
- fail-fast: false
- matrix:
- os: [ubuntu-latest]
env:
IMAGE_NAME: burnysc2/python-sc2-docker:local
steps:
- # Copy data from repository
- - uses: actions/checkout@v2
-
- - name: Print directories and files
- run: |
- sudo apt-get install tree
- tree
-
- - name: Load and build docker image
- # Build docker image from Dockerfile using specific python and sc2 version
- env:
- BUILD_ARGS: --build-arg PYTHON_VERSION=${{ env.LATEST_PYTHON_VERSION }} --build-arg SC2_VERSION=${{ env.LATEST_SC2_VERSION }}
- run: |
- docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS --build-arg VERSION_NUMBER=${{ env.VERSION_NUMBER }} .
-
- - name: Run example bots vs computer
- run: |
- docker run -i -d --name my_container $IMAGE_NAME
- docker exec -i my_container bash -c "python test/run_example_bots_vs_computer.py"
- docker exec -i my_container bash -c "tree"
- docker rm -f my_container
-
-# TODO Fix in main.py "run_multiple_games" or "a_run_multiple_games" or "a_run_multiple_games_nokill"
-# run_bot_vs_bot:
-# # Run bot vs bot
-# name: Run example bots against each other
-# runs-on: ${{ matrix.os }}
-# timeout-minutes: 30
-# strategy:
-# # Do not allow this test to cancel. Finish all jobs regardless of error
-# fail-fast: false
-# matrix:
-# os: [ubuntu-latest]
-# python-version: ['3.10']
-# sc2-version: ['4.10']
-# steps:
-# # Copy data from repository
-# - uses: actions/checkout@v2
-#
-# - name: Print directories and files
-# run: |
-# sudo apt-get install tree
-# tree
-#
-# - name: Load and build docker image
-# # Build docker image from Dockerfile using python 3.8
-# run: |
-# docker build -t test_image -f test/Dockerfile --build-arg PYTHON_VERSION=${{ matrix.python-version }} --build-arg SC2_VERSION=${{ matrix.sc2-version }} --build-arg VERSION_NUMBER=$VERSION_NUMBER .
-#
-# - name: Run example bots vs each other
-# run: |
-# docker run -it -d --name my_container test_image
-# docker exec -i my_container bash -c "python test/run_example_bots_vs_each_other.py"
-# docker exec -i my_container bash -c "tree"
-# docker rm -f my_container
+ # Copy data from repository
+ - uses: actions/checkout@v3
+
+ - name: Print directories and files
+ run: sudo apt-get install tree && tree
+
+ - name: Load and build docker image
+ # Build docker image from Dockerfile using specific python and sc2 version
+ env:
+ BUILD_ARGS: --build-arg PYTHON_VERSION=${{ env.LATEST_PYTHON_VERSION }} --build-arg SC2_VERSION=${{ env.LATEST_SC2_VERSION }}
+ run: docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS --build-arg VERSION_NUMBER=${{ env.VERSION_NUMBER }} .
+
+ - name: Run example bots vs computer
+ run: |
+ docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME
+ docker exec -i my_container bash -c "python test/run_example_bots_vs_computer.py"
+ docker exec -i my_container bash -c "tree"
+ docker rm -f my_container
+
+ # TODO Fix in main.py "run_multiple_games" or "a_run_multiple_games" or "a_run_multiple_games_nokill"
+ # run_bot_vs_bot:
+ # # Run bot vs bot
+ # name: Run example bots against each other
+ # needs: [run_pytest_tests]
+ # timeout-minutes: 60
+ # env:
+ # IMAGE_NAME: burnysc2/python-sc2-docker:local
+ #
+ # steps:
+ # # Copy data from repository
+ # - uses: actions/checkout@v3
+ #
+ # - name: Print directories and files
+ # run: |
+ # sudo apt-get install tree
+ # tree
+ #
+ # - name: Load and build docker image
+ # # Build docker image from Dockerfile using specific python and sc2 version
+ # env:
+ # BUILD_ARGS: --build-arg PYTHON_VERSION=${{ env.LATEST_PYTHON_VERSION }} --build-arg SC2_VERSION=${{ env.LATEST_SC2_VERSION }}
+ # run: |
+ # docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS --build-arg VERSION_NUMBER=${{ env.VERSION_NUMBER }} .
+ #
+ # - name: Run example bots vs each other
+ # run: |
+ # docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME
+ # docker exec -i my_container bash -c "python test/run_example_bots_vs_each_other.py"
+ # docker exec -i my_container bash -c "tree"
+ # docker rm -f my_container
+
+ run_coverage:
+ # Run and upload coverage report
+ # This coverage test does not cover the whole testing range, check /bat_files/rune_code_coverage.bat
+ name: Run coverage
+ needs: [run_test_bots, run_example_bots]
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ env:
+ IMAGE_NAME: burnysc2/python-sc2-docker:local
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Load and build docker image
+ # Build docker image from Dockerfile using specific python and sc2 version
+ env:
+ BUILD_ARGS: --build-arg PYTHON_VERSION=${{ env.LATEST_PYTHON_VERSION }} --build-arg SC2_VERSION=${{ env.LATEST_SC2_VERSION }}
+ run: docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS --build-arg VERSION_NUMBER=${{ env.VERSION_NUMBER }} .
+
+ - name: Set up container
+ run: |
+ mkdir htmlcov
+ docker run -i -d \
+ -v $(pwd)/htmlcov:/root/python-sc2/htmlcov \
+ --name my_container \
+ --env 'PYTHONPATH=/root/python-sc2/' \
+ --entrypoint /bin/bash \
+ $IMAGE_NAME
+ echo "Install dev requirements because only non dev requirements exist in the docker image at the moment"
+ docker exec -i my_container bash -c "pip install uv \
+ && uv sync --frozen --no-cache --no-install-project"
+
+ - name: Run coverage on tests
+ run: docker exec -i my_container bash -c "uv run pytest --cov=./"
+
+ - name: Run coverage on autotest_bot.py
+ run: docker exec -i my_container bash -c "uv run coverage run -a test/travis_test_script.py test/autotest_bot.py"
+
+ - name: Run coverage on upgradestest_bot.py
+ run: docker exec -i my_container bash -c "uv run coverage run -a test/travis_test_script.py test/upgradestest_bot.py"
+
+ - name: Run coverage on damagetest_bot.py
+ run: docker exec -i my_container bash -c "uv run coverage run -a test/travis_test_script.py test/damagetest_bot.py"
+
+ - name: Run coverage on queries_test_bot.py
+ run: docker exec -i my_container bash -c "uv run coverage run -a test/travis_test_script.py test/queries_test_bot.py"
+
+ # Bots might run differently long each time and create flucuations in code coverage - better to mock behavior instead
+ # - name: Run coverage on example bots
+ # run: |
+ # docker exec -i my_container bash -c "uv run coverage run -a test/run_example_bots_vs_computer.py"
+
+ - name: Generate xml coverage file
+ run: |
+ docker exec -i my_container bash -c "uv run coverage xml"
+ docker cp my_container:/root/python-sc2/coverage.xml $(pwd)/coverage.xml
+
+ - name: Generate html coverage files in htmlcov/ folder
+ run: |
+ docker exec -i my_container bash -c "uv run coverage html"
+ echo "Upload htmlcov folder because it was mounted in container, so it will be available in host machine"
+
+ - name: Upload htmlcov/ folder as artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: Coverage_report
+ path: htmlcov
+
+ run_radon:
+ name: Run radon complexity analysis
+ needs: [run_test_bots, run_example_bots]
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.LATEST_PYTHON_VERSION }}
+
+ - name: Install uv
+ run: pip install uv
+
+ - name: Install dependencies
+ run: uv sync --frozen --no-cache --no-install-project
+
+ - name: Run uv radon
+ run: uv run radon cc sc2/ -a -nb
release_to_github_pages:
name: GitHub Pages
+ needs: [run_test_bots, run_example_bots]
runs-on: ubuntu-latest
- timeout-minutes: 3
- needs: [run_pytest_tests, run_test_bots, run_example_bots]
+ timeout-minutes: 5
steps:
- - uses: actions/checkout@v2
-
- - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ env.LATEST_PYTHON_VERSION }}
-
- - name: Install Poetry
- run: |
- pip install poetry
-
- - name: Make poetry use local .venv folder
- run: poetry config virtualenvs.in-project true
-
- - name: Set up cache
- if: matrix.os != 'windows-latest'
- uses: actions/cache@v3
- with:
- path: .venv
- key: ${{ matrix.os }}-${{ steps.setup-python.outputs.python-version }}-poetry-${{ hashFiles('poetry.lock') }}
-
- - name: Install dependencies
- run: |
- poetry install
-
- - name: Build docs from scratch
- run: |
- echo "" > index.html
- mkdir -p docs
- cd docs_generate
- poetry run sphinx-build -a -E -b html . ../docs
-
- - name: Remove files unrelated to docs
- run: |
- rm -rf data dockerfiles docs_generate examples sc2 test
- rm poetry.lock
-
- - name: Publish to Github Pages
- if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
- uses: JamesIves/github-pages-deploy-action@releases/v3
- with:
- ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
- BASE_BRANCH: develop # The branch the action should deploy from.
- BRANCH: gh-pages # The branch the action should deploy to.
- FOLDER: . # The folder the action should deploy.
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.LATEST_PYTHON_VERSION }}
+
+ - name: Install uv
+ run: pip install uv
+
+ - name: Install dependencies
+ run: uv sync --frozen --no-cache --no-install-project
+
+ - name: Build docs from scratch
+ run: |
+ echo "" > index.html
+ mkdir -p docs
+ cd docs_generate
+ uv run sphinx-build -a -E -b html . ../docs
+
+ - name: Debug-list all generated files
+ run: sudo apt-get install tree && tree
+
+ - name: Publish to Github Pages
+ if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
+ uses: JamesIves/github-pages-deploy-action@releases/v4
+ with:
+ ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
+ BASE_BRANCH: develop # The branch the action should deploy from.
+ BRANCH: gh-pages # The branch the action should deploy to.
+ FOLDER: docs # The folder the action should deploy.
release_to_pypi:
- name: Pypi
+ name: Pypi package release
+ needs: [run_test_bots, run_example_bots]
runs-on: ubuntu-latest
timeout-minutes: 10
- needs: [run_pytest_tests, run_test_bots, run_example_bots]
steps:
- - uses: actions/checkout@v2
-
- - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ env.LATEST_PYTHON_VERSION }}
+ - uses: actions/checkout@v3
- - name: Install Poetry
- run: |
- pip install poetry
+ - name: Set up Python ${{ env.LATEST_PYTHON_VERSION }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.LATEST_PYTHON_VERSION }}
- - name: Build package
- # Builds package to dist/* directory
- run: |
- poetry build
+ - name: Install uv
+ run: pip install uv
- - name: Publish
- # Publish package on pypi
- if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
- continue-on-error: true
- run: |
- poetry publish --username ${{ secrets.pypi_username }} --password ${{ secrets.pypi_password }}
+ - name: Build package
+ run: uv build
+ - name: Publish to pypi
+ if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
+ continue-on-error: true
+ run: uv publish --token ${{ secrets.PYPI_PYTHON_SC2_TOKEN }}
diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml
deleted file mode 100644
index c9e292a6..00000000
--- a/.github/workflows/codecoverage.yml
+++ /dev/null
@@ -1,130 +0,0 @@
-# If you change the name, change the link on the README.md for the badge too
-name: Code coverage and radon
-
-# Always run
-on: [push, pull_request]
-
-env:
- # Docker image version, see https://hub.docker.com/r/burnysc2/python-sc2-docker/tags
- # This version should always lack behind one version behind the docker-ci.yml because it is possible that it doesn't exist
- VERSION_NUMBER: '1.0.1'
- LATEST_PYTHON_VERSION: '3.10'
- LATEST_SC2_VERSION: '4.10'
-
-jobs:
- run_coverage:
- # Run and upload coverage report using python 3.9
- # This coverage test does not cover the whole testing range, check /bat_files/rune_code_coverage.bat
- name: Run coverage
- runs-on: ubuntu-latest
- timeout-minutes: 30
- strategy:
- matrix:
- os: [ubuntu-latest]
- env:
- IMAGE_NAME: burnysc2/python-sc2-docker:local
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Load and build docker image
- # Build docker image from Dockerfile using specific python and sc2 version
- env:
- BUILD_ARGS: --build-arg PYTHON_VERSION=${{ env.LATEST_PYTHON_VERSION }} --build-arg SC2_VERSION=${{ env.LATEST_SC2_VERSION }}
- run: |
- docker build -f test/Dockerfile -t $IMAGE_NAME $BUILD_ARGS --build-arg VERSION_NUMBER=${{ env.VERSION_NUMBER }} .
-
- - name: Set up container
- run: |
- mkdir htmlcov
- docker run -i -d \
- --mount type=bind,source=$(pwd)/htmlcov,destination=/root/python-sc2/htmlcov \
- --name my_container \
- $IMAGE_NAME
- echo "Install dev requirements because only non dev requirements exist in the docker image at the moment"
- docker exec -i my_container bash -c "poetry install"
-
- - name: Run coverage on tests
- run: |
- docker exec -i my_container bash -c "poetry run pytest --cov=./"
-
- - name: Run coverage on autotest_bot.py
- run: |
- docker exec -i my_container bash -c "poetry run coverage run -a test/travis_test_script.py test/autotest_bot.py"
-
- - name: Run coverage on upgradestest_bot.py
- run: |
- docker exec -i my_container bash -c "poetry run coverage run -a test/travis_test_script.py test/upgradestest_bot.py"
-
- - name: Run coverage on damagetest_bot.py
- run: |
- docker exec -i my_container bash -c "poetry run coverage run -a test/travis_test_script.py test/damagetest_bot.py"
-
- - name: Run coverage on queries_test_bot.py
- run: |
- docker exec -i my_container bash -c "poetry run coverage run -a test/travis_test_script.py test/queries_test_bot.py"
-
- - name: Run coverage on example bots
- run: |
- docker exec -i my_container bash -c "poetry run coverage run -a test/run_example_bots_vs_computer.py"
-
- - name: Generate xml coverage file
- run: |
- docker exec -i my_container bash -c "poetry run coverage xml"
- docker cp my_container:/root/python-sc2/coverage.xml $(pwd)/coverage.xml
-
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v2
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- files: coverage.xml
-
- - name: Generate html coverage files in htmlcov/ folder
- run: |
- docker exec -i my_container bash -c "poetry run coverage html"
- echo "Upload htmlcov folder because it was mounted in container, so it will be available in host machine"
-
- - name: Upload htmlcov/ folder as artifact
- uses: actions/upload-artifact@v3
- with:
- name: Coverage_report
- path: htmlcov
-
- run_radon:
- # Output a cyclomatic complexity report of the project
- name: Run radon
- runs-on: ubuntu-latest
- timeout-minutes: 10
- strategy:
- matrix:
- python-version: ['3.11']
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install Poetry
- run: |
- pip install poetry
-
- - name: Make poetry use local .venv folder
- run: poetry config virtualenvs.in-project true
-
- - name: Set up cache
- if: matrix.os != 'windows-latest'
- uses: actions/cache@v3
- with:
- path: .venv
- key: ${{ matrix.os }}-${{ steps.setup-python.outputs.python-version }}-poetry-${{ hashFiles('poetry.lock') }}
-
- - name: Install dependencies
- run: |
- poetry install
-
- - name: Run poetry radon
- run: |
- poetry run radon cc sc2/ -a -nb
diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml
index ec156f4c..f028aaa8 100644
--- a/.github/workflows/docker-ci.yml
+++ b/.github/workflows/docker-ci.yml
@@ -1,21 +1,23 @@
-name: Build and push Dockerfile
+name: Build and push Docker image
# Only run if Dockerfile or docker-ci.yml changed
on:
push:
paths:
- - dockerfiles/**
- - .github/workflows/docker-ci.yml
+ - dockerfiles/**
+ - uv.lock
+ - pyproject.toml
+ - .github/workflows/docker-ci.yml
pull_request:
branches:
- - master
- - develop
+ - master
+ - develop
env:
- VERSION_NUMBER: '1.0.2'
- LATEST_PYTHON_VERSION: '3.11'
- LATEST_SC2_VERSION: '4.10'
- EXPERIMENTAL_PYTHON_VERSION: '3.11.0b1'
+ VERSION_NUMBER: "1.0.6"
+ LATEST_PYTHON_VERSION: "3.13"
+ LATEST_SC2_VERSION: "4.10"
+ EXPERIMENTAL_PYTHON_VERSION: "3.14"
jobs:
run_test_docker_image:
@@ -28,20 +30,19 @@ jobs:
os: [ubuntu-latest]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- - name: Enable experimental docker features
- run: |
- echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
- sudo systemctl restart docker.service
+ - name: Enable experimental docker features
+ run: |
+ echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
+ sudo systemctl restart docker.service
- - name: Run shell script
- env:
- VERSION_NUMBER: ${{ env.VERSION_NUMBER }}
- PYTHON_VERSION: ${{ env.LATEST_PYTHON_VERSION }}
- SC2_VERSION: ${{ env.LATEST_SC2_VERSION }}
- run: |
- sh dockerfiles/test_docker_image.sh
+ - name: Run shell script
+ env:
+ VERSION_NUMBER: ${{ env.VERSION_NUMBER }}
+ PYTHON_VERSION: ${{ env.LATEST_PYTHON_VERSION }}
+ SC2_VERSION: ${{ env.LATEST_SC2_VERSION }}
+ run: sh dockerfiles/test_docker_image.sh
run_test_new_python_version:
name: Run test_new_python_candidate.sh
@@ -53,21 +54,20 @@ jobs:
os: [ubuntu-latest]
steps:
- - uses: actions/checkout@v2
-
- - name: Enable experimental docker features
- run: |
- echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
- sudo systemctl restart docker.service
-
- - name: Run shell script
- continue-on-error: true
- env:
- VERSION_NUMBER: ${{ env.VERSION_NUMBER }}
- PYTHON_VERSION: ${{ env.EXPERIMENTAL_PYTHON_VERSION }}
- SC2_VERSION: ${{ env.LATEST_SC2_VERSION }}
- run: |
- sh dockerfiles/test_new_python_candidate.sh
+ - uses: actions/checkout@v3
+
+ - name: Enable experimental docker features
+ run: |
+ echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
+ sudo systemctl restart docker.service
+
+ - name: Run shell script
+ continue-on-error: true
+ env:
+ VERSION_NUMBER: ${{ env.VERSION_NUMBER }}
+ PYTHON_VERSION: ${{ env.EXPERIMENTAL_PYTHON_VERSION }}
+ SC2_VERSION: ${{ env.LATEST_SC2_VERSION }}
+ run: sh dockerfiles/test_new_python_candidate.sh
docker_build:
name: Build docker image
@@ -77,62 +77,65 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
- python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
- sc2-version: ['4.10']
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+ sc2-version: ["4.10"]
env:
IMAGE_NAME: burnysc2/python-sc2-docker:py_${{ matrix.python-version }}-sc2_${{ matrix.sc2-version }}
BUILD_ARGS: --build-arg PYTHON_VERSION=${{ matrix.python-version }} --build-arg SC2_VERSION=${{ matrix.sc2-version }}
steps:
- - uses: actions/checkout@v2
-
- - name: Build docker image
- run: |
- docker build -t $IMAGE_NAME-v$VERSION_NUMBER $BUILD_ARGS - < dockerfiles/Dockerfile
-
- - name: Enable experimental docker features
- run: |
- echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
- sudo systemctl restart docker.service
-
- - name: Build squashed image
- run: |
- docker build -t $IMAGE_NAME-v$VERSION_NUMBER-squashed --squash $BUILD_ARGS - < dockerfiles/Dockerfile
-
- - name: Run test bots on squashed image
- if: matrix.python-version != '3.7'
- run: |
- echo "Start container, override the default entrypoint"
- docker run -i -d \
- --name test_container \
- --mount type=bind,source="$(pwd)",destination=/root/python-sc2,readonly \
- --entrypoint /bin/bash \
- $IMAGE_NAME-v$VERSION_NUMBER-squashed
- echo "Install python-sc2"
- docker exec -i test_container bash -c "pip install poetry \
- && cd python-sc2 && poetry install --no-dev"
- echo "Run various test bots"
- docker exec -i test_container bash -c "cd python-sc2 && poetry run python test/travis_test_script.py test/autotest_bot.py"
- docker exec -i test_container bash -c "cd python-sc2 && poetry run python test/run_example_bots_vs_computer.py"
-
- - name: Login to DockerHub
- uses: docker/login-action@v2
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- - name: Upload docker image
- if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
- run: |
- docker push $IMAGE_NAME-v$VERSION_NUMBER
-
- - name: Upload squashed docker image
- if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
- run: |
- docker push $IMAGE_NAME-v$VERSION_NUMBER-squashed
-
- - name: Upload squashed docker image as latest tag
- if: github.ref == 'refs/heads/develop' && github.event_name == 'push' && matrix.python-version == env.LATEST_PYTHON_VERSION && matrix.sc2-version == env.LATEST_SC2_VERSION
- run: |
- docker tag $IMAGE_NAME-v$VERSION_NUMBER-squashed burnysc2/python-sc2-docker:latest
- docker push burnysc2/python-sc2-docker:latest
+ - uses: actions/checkout@v3
+
+ - name: Build docker image
+ run: docker build -t $IMAGE_NAME-v$VERSION_NUMBER $BUILD_ARGS - < dockerfiles/Dockerfile
+
+ - name: Enable experimental docker features
+ run: |
+ echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
+ sudo systemctl restart docker.service
+
+ - name: Build squashed image
+ run: docker build -t $IMAGE_NAME-v$VERSION_NUMBER-squashed --squash $BUILD_ARGS - < dockerfiles/Dockerfile
+
+ - name: Run test bots on squashed image
+ if: matrix.python-version != '3.7'
+ run: |
+ echo "Start container, override the default entrypoint"
+ docker run -i -d \
+ --name test_container \
+ --env 'PYTHONPATH=/root/python-sc2/' \
+ --entrypoint /bin/bash \
+ $IMAGE_NAME-v$VERSION_NUMBER-squashed
+ echo "Install python-sc2"
+ docker exec -i test_container mkdir -p /root/python-sc2
+ docker cp pyproject.toml test_container:/root/python-sc2/
+ docker cp uv.lock test_container:/root/python-sc2/
+ docker cp sc2 test_container:/root/python-sc2/sc2
+ docker cp test test_container:/root/python-sc2/test
+ docker cp examples test_container:/root/python-sc2/examples
+ docker exec -i test_container bash -c "pip install uv \
+ && cd python-sc2 && uv sync --frozen --no-cache --no-install-project"
+ echo "Run various test bots"
+ docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/autotest_bot.py"
+ docker exec -i test_container bash -c "cd python-sc2 && uv run python test/run_example_bots_vs_computer.py"
+
+ - name: Login to DockerHub
+ if: github.ref == 'refs/heads/develop'
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Upload docker image
+ if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
+ run: docker push $IMAGE_NAME-v$VERSION_NUMBER
+
+ - name: Upload squashed docker image
+ if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
+ run: docker push $IMAGE_NAME-v$VERSION_NUMBER-squashed
+
+ - name: Upload squashed docker image as latest tag
+ if: github.ref == 'refs/heads/develop' && github.event_name == 'push' && matrix.python-version == env.LATEST_PYTHON_VERSION && matrix.sc2-version == env.LATEST_SC2_VERSION
+ run: |
+ docker tag $IMAGE_NAME-v$VERSION_NUMBER-squashed burnysc2/python-sc2-docker:latest
+ docker push burnysc2/python-sc2-docker:latest
diff --git a/.gitignore b/.gitignore
index d25cb896..f1bac733 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,6 @@ mini_games/
.coverage
/htmlcov
-/bat_files/temp/
+docs/
-/docs
+.pyre
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9ea0b2e1..0c784213 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.2.0
+ rev: v4.4.0
hooks:
# Check yaml files like this one and github actions if they are valid
- id: check-yaml
@@ -14,33 +14,18 @@ repos:
# Check github action workflow files
- repo: https://github.com/sirosen/check-jsonschema
- rev: 0.14.3
+ rev: 0.22.0
hooks:
- id: check-github-workflows
-# Remove unused imports
-- repo: https://github.com/hadialqattan/pycln
- rev: v1.3.1
- hooks:
- - id: pycln
- args: [--config=pyproject.toml]
-
# Convert relative to absolute imports
- repo: https://github.com/MarcoGorelli/absolufy-imports
rev: v0.3.1
hooks:
- id: absolufy-imports
-# Sort imports
-- repo: https://github.com/pycqa/isort
- rev: 5.5.4
- hooks:
- - id: isort
- files: \.(py)$
- args: [--settings-path=pyproject.toml]
-
- repo: https://github.com/pre-commit/pygrep-hooks
- rev: v1.9.0
+ rev: v1.10.0
hooks:
# Check for bad code
- id: python-no-eval
@@ -51,33 +36,24 @@ repos:
- repo: local
hooks:
# Autoformat code
- - id: yapf
- name: format python code
- stages: [commit]
+ - id: ruff-format-check
+ name: Check if files are formatted
+ stages: [push]
language: system
- entry: /bin/bash -c "poetry run yapf --in-place $(git ls-files '*.py')"
+ entry: uv run ruff format . --check --diff
pass_filenames: false
- - id: pylint
- name: pylint
- stages: [commit]
+ - id: ruff-lint
+ name: Lint files
+ stages: [push]
language: system
- entry: /bin/bash -c "poetry run pylint $(git ls-files '*.py' | grep -E '(sc2)|(examples)/.*')"
+ entry: uv run ruff check .
pass_filenames: false
-# TODO Types before enabling this
-# # Run mypy type checks
-# - id: mypy
-# name: mypy
-# stages: [commit]
-# language: system
-# entry: /bin/bash -c "poetry run mypy $(git ls-files '*.py' | grep -E '(sc2)|(examples)/.*')"
-# pass_filenames: false
-
- # Run tests
- - id: pytest
- name: pytest
- stages: [commit]
- language: system
- entry: poetry run pytest test
- pass_filenames: false
+ # TODO Fix issues
+ # - id: pyrefly
+ # name: Static types checking with pyrefly
+ # stages: [push]
+ # language: system
+ # entry: uv run pyrefly check
+ # pass_filenames: false
diff --git a/.pyre_configuration b/.pyre_configuration
new file mode 100644
index 00000000..6c670b47
--- /dev/null
+++ b/.pyre_configuration
@@ -0,0 +1,17 @@
+{
+ "site_package_search_strategy": "pep561",
+ "source_directories": [
+ {
+ "import_root": ".",
+ "source": "sc2"
+ },
+ {
+ "import_root": ".",
+ "source": "examples"
+ },
+ {
+ "import_root": ".",
+ "source": "test"
+ }
+ ]
+}
diff --git a/README.md b/README.md
index dc977ade..ee9cfc05 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ An easy-to-use library for writing AI Bots for StarCraft II in Python 3. The ult
**This library (currently) covers only the raw scripted interface.** At this time I don't intend to add support for graphics-based interfaces.
-The [documentation can be found here](https://burnysc2.github.io/python-sc2/docs/index.html).
+The [documentation can be found here](https://burnysc2.github.io/python-sc2/index.html).
For bot authors, looking directly at the files in the [sc2 folder](/sc2) can also be of benefit: bot_ai.py, unit.py, units.py, client.py, game_info.py and game_state.py. Most functions in those files have docstrings, example usages and type hinting.
I am planning to change this fork more radically than the main repository, for bot performance benefits and to add functions to help new bot authors. This may break older bots in the future, however I try to add deprecation warnings to give a heads up notification. This means that the [video tutorial made by sentdex](https://pythonprogramming.net/starcraft-ii-ai-python-sc2-tutorial/) is outdated and does no longer directly work with this fork.
@@ -18,7 +18,7 @@ For a list of ongoing changes and differences to the main repository of Dentosal
By installing this library you agree to be bound by the terms of the [AI and Machine Learning License](http://blzdistsc2-a.akamaihd.net/AI_AND_MACHINE_LEARNING_LICENSE.html).
-For this fork, you'll need Python 3.8 or newer.
+For this fork, you'll need Python 3.10 or newer.
Install the pypi package:
```
@@ -26,10 +26,9 @@ pip install --upgrade burnysc2
```
or directly from develop branch:
```
-pip install poetry
pip install --upgrade --force-reinstall https://github.com/BurnySc2/python-sc2/archive/develop.zip
```
-Both commands will use the `sc2` library folder, so you will not be able to have Dentosal's and this fork installed at the same time, unless you use virtual environments or poetry.
+Both commands will use the `sc2` library folder, so you will not be able to have Dentosal's and this fork installed at the same time, unless you use virtual environments.
## StarCraft II
You'll need a StarCraft II executable. If you are running Windows or macOS, just install SC2 from [blizzard app](https://starcraft2.com/).
@@ -89,7 +88,7 @@ Extract these maps into their respective *subdirectories* in the SC2 maps direct
e.g. `install-dir/Maps/Ladder2017Season1/`
#### Bot ladder maps
-Maps that are run on the [SC2 AI Ladder](http://sc2ai.net/) and [SC2 AI Arena](https://aiarena.net/) can be downloaded [from the sc2ai wiki](http://wiki.sc2ai.net/Ladder_Maps) and [the aiarena wiki](https://aiarena.net/wiki/bot-development/getting-started/#wiki-toc-maps).
+Maps that are run on the [SC2 AI Arena Ladder](https://aiarena.net/) can be downloaded [from the SC2 AI Arena Wiki](https://aiarena.net/wiki/bot-development/getting-started/#wiki-toc-maps).
**Extract these maps into the *root* of the SC2 maps directory** (otherwise ladder replays won't work).
e.g. `install-dir/Maps/AcropolisLE.SC2Map`
@@ -173,7 +172,7 @@ class MyBot(BotAI):
## Community - Help and support
-You have questions but don't want to create an issue? Join the [Starcraft 2 AI Discord server](https://discordapp.com/invite/zXHU4wM) or [aiarena.net Discord server](https://discord.gg/yDBzbtC). Questions about this repository can be asked in text channel #python. There are discussions and questions about SC2 bot programming and this repository every day.
+You have questions but don't want to create an issue? Join the [SC2 AI Arena Discord server](https://discordapp.com/invite/zXHU4wM). Questions about this repository can be asked in text channel #python. There are discussions and questions about SC2 bot programming and this repository every day.
## Bug reports, feature requests and ideas
@@ -186,6 +185,6 @@ Git commit messages use [imperative-style messages](https://stackoverflow.com/a/
To run pre-commit hooks (which run autoformatting and autosort imports) you can run
```sh
-poetry run pre-commit install
-poetry run pre-commit run --all-files
+uv run pre-commit install
+uv run pre-commit run --all-files --hook-stage push
```
diff --git a/data/README.md b/data/README.md
index c5ce9e89..b90a931c 100644
--- a/data/README.md
+++ b/data/README.md
@@ -1,6 +1,6 @@
This data comes from dentosals tech tree and is only used to generate the dictionaries in /sc2/dicts/:
-https://github.com/BurnySc2/sc2-techtree/tree/master/data
+https://github.com/BurnySc2/sc2-techtree
If you see abilities missing, requirements wrong or anything else related to the /sc2/dicts/, please open and write an issue here:
https://github.com/BurnySc2/sc2-techtree/issues/new
\ No newline at end of file
diff --git a/data/data.json b/data/data.json
index 42a00dc7..9008e36c 100644
--- a/data/data.json
+++ b/data/data.json
@@ -1 +1 @@
-{"Ability":[{"id":1,"name":"SMART","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2,"name":"TAUNT_TAUNT","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":4,"name":"STOP_STOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":5,"name":"STOP_HOLDFIRESPECIAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":6,"name":"STOP_CHEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":7,"name":"STOP_DANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":16,"name":"MOVE_MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":17,"name":"PATROL_PATROL","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":18,"name":"HOLDPOSITION_HOLD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":19,"name":"SCAN_MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":20,"name":"MOVE_TURN","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":23,"name":"ATTACK_ATTACK","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":24,"name":"ATTACK_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":25,"name":"ATTACK_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":26,"name":"EFFECT_SPRAY_TERRAN","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":28,"name":"EFFECT_SPRAY_ZERG","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":30,"name":"EFFECT_SPRAY_PROTOSS","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":32,"name":"EFFECT_SALVAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":36,"name":"BEHAVIOR_HOLDFIREON_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":38,"name":"BEHAVIOR_HOLDFIREOFF_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":40,"name":"MORPHTOINFESTEDTERRAN_INFESTEDTERRANS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":7,"produces_name":"INFESTORTERRAN"}}},{"id":42,"name":"EXPLODE_EXPLODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":45,"name":"FLEETBEACONRESEARCH_RESEARCHINTERCEPTORLAUNCHSPEEDUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":1,"upgrade_name":"CARRIERLAUNCHSPEEDUPGRADE"}}},{"id":46,"name":"RESEARCH_PHOENIXANIONPULSECRYSTALS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":99,"upgrade_name":"PHOENIXRANGEUPGRADE"}}},{"id":47,"name":"FLEETBEACONRESEARCH_TEMPESTRANGEUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":100,"upgrade_name":"TEMPESTRANGEUPGRADE"}}},{"id":48,"name":"FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":288,"upgrade_name":"VOIDRAYSPEEDUPGRADE"}}},{"id":49,"name":"FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":296,"upgrade_name":"TEMPESTGROUNDATTACKUPGRADE"}}},{"id":74,"name":"FUNGALGROWTH_FUNGALGROWTH","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":76,"name":"GUARDIANSHIELD_GUARDIANSHIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":78,"name":"EFFECT_REPAIR_MULE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":80,"name":"MORPHZERGLINGTOBANELING_BANELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":9,"produces_name":"BANELING"}}},{"id":110,"name":"NEXUSTRAINMOTHERSHIP_MOTHERSHIP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":10}}},{"id":140,"name":"FEEDBACK_FEEDBACK","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":142,"name":"EFFECT_MASSRECALL_STRATEGICRECALL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":146,"name":"HALLUCINATION_ARCHON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":148,"name":"HALLUCINATION_COLOSSUS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":150,"name":"HALLUCINATION_HIGHTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":152,"name":"HALLUCINATION_IMMORTAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":154,"name":"HALLUCINATION_PHOENIX","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":156,"name":"HALLUCINATION_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":158,"name":"HALLUCINATION_STALKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":160,"name":"HALLUCINATION_VOIDRAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":162,"name":"HALLUCINATION_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":164,"name":"HALLUCINATION_ZEALOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":166,"name":"HARVEST_GATHER_MULE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":167,"name":"HARVEST_RETURN_MULE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":171,"name":"CALLDOWNMULE_CALLDOWNMULE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":173,"name":"GRAVITONBEAM_GRAVITONBEAM","cast_range":4.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":174,"name":"CANCEL_GRAVITONBEAM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":175,"name":"BUILDINPROGRESSNYDUSCANAL_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":181,"name":"SPAWNCHANGELING_SPAWNCHANGELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":12,"produces_name":"CHANGELING"}}},{"id":195,"name":"RALLY_BUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":199,"name":"RALLY_MORPHING_UNIT","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":203,"name":"RALLY_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":207,"name":"RALLY_NEXUS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":211,"name":"RALLY_HATCHERY_UNITS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":212,"name":"RALLY_HATCHERY_WORKERS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":216,"name":"RESEARCH_GLIALREGENERATION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":2,"upgrade_name":"GLIALRECONSTITUTION"}}},{"id":217,"name":"RESEARCH_TUNNELINGCLAWS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":3,"upgrade_name":"TUNNELINGCLAWS"}}},{"id":218,"name":"ROACHWARRENRESEARCH_ROACHSUPPLY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":120,"upgrade_name":"ROACHSUPPLY"}}},{"id":245,"name":"SAPSTRUCTURE_SAPSTRUCTURE","cast_range":0.25,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":249,"name":"NEURALPARASITE_NEURALPARASITE","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":250,"name":"CANCEL_NEURALPARASITE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":251,"name":"EFFECT_INJECTLARVA","cast_range":0.10009765625,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":253,"name":"EFFECT_STIM_MARAUDER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":255,"name":"SUPPLYDROP_SUPPLYDROP","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":263,"name":"RESEARCH_ANABOLICSYNTHESIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":88,"upgrade_name":"ANABOLICSYNTHESIS"}}},{"id":265,"name":"RESEARCH_CHITINOUSPLATING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":4,"upgrade_name":"CHITINOUSPLATING"}}},{"id":295,"name":"HARVEST_GATHER_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":296,"name":"HARVEST_RETURN_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":298,"name":"HARVEST_GATHER_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":299,"name":"HARVEST_RETURN_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":301,"name":"ATTACKWARPPRISM_ATTACKWARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":302,"name":"ATTACKWARPPRISM_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":303,"name":"ATTACKWARPPRISM_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":304,"name":"CANCEL_QUEUE1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":305,"name":"CANCELSLOT_QUEUE1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":306,"name":"CANCEL_QUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":307,"name":"CANCELSLOT_QUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":308,"name":"CANCEL_QUEUECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":309,"name":"CANCELSLOT_QUEUECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":312,"name":"CANCEL_QUEUEADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":313,"name":"CANCELSLOT_ADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":314,"name":"CANCEL_BUILDINPROGRESS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":315,"name":"HALT_BUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":316,"name":"EFFECT_REPAIR_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":318,"name":"TERRANBUILD_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":18,"produces_name":"COMMANDCENTER"}}},{"id":319,"name":"TERRANBUILD_SUPPLYDEPOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":19,"produces_name":"SUPPLYDEPOT"}}},{"id":320,"name":"TERRANBUILD_REFINERY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildOnUnit":{"produces":20,"produces_name":"REFINERY"}}},{"id":321,"name":"TERRANBUILD_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":21,"produces_name":"BARRACKS"}}},{"id":322,"name":"TERRANBUILD_ENGINEERINGBAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":22,"produces_name":"ENGINEERINGBAY"}}},{"id":323,"name":"TERRANBUILD_MISSILETURRET","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":23,"produces_name":"MISSILETURRET"}}},{"id":324,"name":"TERRANBUILD_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":24,"produces_name":"BUNKER"}}},{"id":326,"name":"TERRANBUILD_SENSORTOWER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":25,"produces_name":"SENSORTOWER"}}},{"id":327,"name":"TERRANBUILD_GHOSTACADEMY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":26,"produces_name":"GHOSTACADEMY"}}},{"id":328,"name":"TERRANBUILD_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":27,"produces_name":"FACTORY"}}},{"id":329,"name":"TERRANBUILD_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":28,"produces_name":"STARPORT"}}},{"id":331,"name":"TERRANBUILD_ARMORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":29,"produces_name":"ARMORY"}}},{"id":333,"name":"TERRANBUILD_FUSIONCORE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":30,"produces_name":"FUSIONCORE"}}},{"id":348,"name":"HALT_TERRANBUILD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":380,"name":"EFFECT_STIM_MARINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":382,"name":"BEHAVIOR_CLOAKON_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":383,"name":"BEHAVIOR_CLOAKOFF_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":386,"name":"MEDIVACHEAL_HEAL","cast_range":4.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":388,"name":"SIEGEMODE_SIEGEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":32,"produces_name":"SIEGETANKSIEGED"}}},{"id":390,"name":"UNSIEGE_UNSIEGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":33,"produces_name":"SIEGETANK"}}},{"id":392,"name":"BEHAVIOR_CLOAKON_BANSHEE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":393,"name":"BEHAVIOR_CLOAKOFF_BANSHEE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":394,"name":"LOAD_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":396,"name":"UNLOADALLAT_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":397,"name":"UNLOADUNIT_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":399,"name":"SCANNERSWEEP_SCAN","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":401,"name":"YAMATO_YAMATOGUN","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":403,"name":"MORPH_VIKINGASSAULTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":34,"produces_name":"VIKINGASSAULT"}}},{"id":405,"name":"MORPH_VIKINGFIGHTERMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":35,"produces_name":"VIKINGFIGHTER"}}},{"id":407,"name":"LOAD_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":408,"name":"UNLOADALL_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":410,"name":"UNLOADUNIT_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":413,"name":"UNLOADALL_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":415,"name":"UNLOADUNIT_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":416,"name":"LOADALL_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":417,"name":"LIFT_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":36,"produces_name":"COMMANDCENTERFLYING"}}},{"id":419,"name":"LAND_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":18,"produces_name":"COMMANDCENTER"}}},{"id":421,"name":"BUILD_TECHLAB_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":37}}},{"id":422,"name":"BUILD_REACTOR_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":38}}},{"id":451,"name":"CANCEL_BARRACKSADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":452,"name":"LIFT_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":46,"produces_name":"BARRACKSFLYING"}}},{"id":454,"name":"BUILD_TECHLAB_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":39}}},{"id":455,"name":"BUILD_REACTOR_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":40}}},{"id":484,"name":"CANCEL_FACTORYADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":485,"name":"LIFT_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":43,"produces_name":"FACTORYFLYING"}}},{"id":487,"name":"BUILD_TECHLAB_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":41}}},{"id":488,"name":"BUILD_REACTOR_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":42}}},{"id":517,"name":"CANCEL_STARPORTADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":518,"name":"LIFT_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":44,"produces_name":"STARPORTFLYING"}}},{"id":520,"name":"LAND_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":27,"produces_name":"FACTORY"}}},{"id":522,"name":"LAND_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":28,"produces_name":"STARPORT"}}},{"id":524,"name":"COMMANDCENTERTRAIN_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":45,"produces_name":"SCV"}}},{"id":554,"name":"LAND_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":21,"produces_name":"BARRACKS"}}},{"id":556,"name":"MORPH_SUPPLYDEPOT_LOWER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":47,"produces_name":"SUPPLYDEPOTLOWERED"}}},{"id":558,"name":"MORPH_SUPPLYDEPOT_RAISE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":19,"produces_name":"SUPPLYDEPOT"}}},{"id":560,"name":"BARRACKSTRAIN_MARINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":48,"produces_name":"MARINE"}}},{"id":561,"name":"BARRACKSTRAIN_REAPER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":49,"produces_name":"REAPER"}}},{"id":562,"name":"BARRACKSTRAIN_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":50,"produces_name":"GHOST"}}},{"id":563,"name":"BARRACKSTRAIN_MARAUDER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":51,"produces_name":"MARAUDER"}}},{"id":591,"name":"FACTORYTRAIN_SIEGETANK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":33,"produces_name":"SIEGETANK"}}},{"id":594,"name":"FACTORYTRAIN_THOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":52,"produces_name":"THOR"}}},{"id":595,"name":"FACTORYTRAIN_HELLION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":53,"produces_name":"HELLION"}}},{"id":596,"name":"TRAIN_HELLBAT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":484,"produces_name":"HELLIONTANK"}}},{"id":597,"name":"TRAIN_CYCLONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":692,"produces_name":"CYCLONE"}}},{"id":614,"name":"FACTORYTRAIN_WIDOWMINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":498,"produces_name":"WIDOWMINE"}}},{"id":620,"name":"STARPORTTRAIN_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":54,"produces_name":"MEDIVAC"}}},{"id":621,"name":"STARPORTTRAIN_BANSHEE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":55,"produces_name":"BANSHEE"}}},{"id":622,"name":"STARPORTTRAIN_RAVEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":56,"produces_name":"RAVEN"}}},{"id":623,"name":"STARPORTTRAIN_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":57,"produces_name":"BATTLECRUISER"}}},{"id":624,"name":"STARPORTTRAIN_VIKINGFIGHTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":35,"produces_name":"VIKINGFIGHTER"}}},{"id":626,"name":"STARPORTTRAIN_LIBERATOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":689,"produces_name":"LIBERATOR"}}},{"id":650,"name":"RESEARCH_HISECAUTOTRACKING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":5,"upgrade_name":"HISECAUTOTRACKING"}}},{"id":651,"name":"RESEARCH_TERRANSTRUCTUREARMORUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":6,"upgrade_name":"TERRANBUILDINGARMOR"}}},{"id":652,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":7,"upgrade_name":"TERRANINFANTRYWEAPONSLEVEL1"}}},{"id":653,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":8,"upgrade_name":"TERRANINFANTRYWEAPONSLEVEL2"}}},{"id":654,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":9,"upgrade_name":"TERRANINFANTRYWEAPONSLEVEL3"}}},{"id":655,"name":"RESEARCH_NEOSTEELFRAME","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":10,"upgrade_name":"NEOSTEELFRAME"}}},{"id":656,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":11,"upgrade_name":"TERRANINFANTRYARMORSLEVEL1"}}},{"id":657,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":12,"upgrade_name":"TERRANINFANTRYARMORSLEVEL2"}}},{"id":658,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":13,"upgrade_name":"TERRANINFANTRYARMORSLEVEL3"}}},{"id":710,"name":"BUILD_NUKE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":730,"name":"BARRACKSTECHLABRESEARCH_STIMPACK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":15,"upgrade_name":"STIMPACK"}}},{"id":731,"name":"RESEARCH_COMBATSHIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":16,"upgrade_name":"SHIELDWALL"}}},{"id":732,"name":"RESEARCH_CONCUSSIVESHELLS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":17,"upgrade_name":"PUNISHERGRENADES"}}},{"id":761,"name":"RESEARCH_INFERNALPREIGNITER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":19,"upgrade_name":"HIGHCAPACITYBARRELS"}}},{"id":763,"name":"FACTORYTECHLABRESEARCH_RESEARCHTRANSFORMATIONSERVOS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":98,"upgrade_name":"TRANSFORMATIONSERVOS"}}},{"id":764,"name":"RESEARCH_DRILLINGCLAWS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":122,"upgrade_name":"DRILLCLAWS"}}},{"id":765,"name":"FACTORYTECHLABRESEARCH_RESEARCHLOCKONRANGEUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":123,"upgrade_name":"CYCLONELOCKONRANGEUPGRADE"}}},{"id":766,"name":"RESEARCH_SMARTSERVOS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":289,"upgrade_name":"SMARTSERVOS"}}},{"id":767,"name":"FACTORYTECHLABRESEARCH_RESEARCHARMORPIERCINGROCKETS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":290,"upgrade_name":"ARMORPIERCINGROCKETS"}}},{"id":768,"name":"RESEARCH_CYCLONERAPIDFIRELAUNCHERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":291,"upgrade_name":"CYCLONERAPIDFIRELAUNCHERS"}}},{"id":769,"name":"RESEARCH_CYCLONELOCKONDAMAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":144,"upgrade_name":"CYCLONELOCKONDAMAGEUPGRADE"}}},{"id":790,"name":"RESEARCH_BANSHEECLOAKINGFIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":20,"upgrade_name":"BANSHEECLOAK"}}},{"id":792,"name":"STARPORTTECHLABRESEARCH_RESEARCHMEDIVACENERGYUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":21,"upgrade_name":"MEDIVACCADUCEUSREACTOR"}}},{"id":793,"name":"RESEARCH_RAVENCORVIDREACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":22,"upgrade_name":"RAVENCORVIDREACTOR"}}},{"id":796,"name":"STARPORTTECHLABRESEARCH_RESEARCHSEEKERMISSILE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":23,"upgrade_name":"HUNTERSEEKER"}}},{"id":797,"name":"STARPORTTECHLABRESEARCH_RESEARCHDURABLEMATERIALS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":24,"upgrade_name":"DURABLEMATERIALS"}}},{"id":799,"name":"RESEARCH_BANSHEEHYPERFLIGHTROTORS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":136,"upgrade_name":"BANSHEESPEED"}}},{"id":800,"name":"STARPORTTECHLABRESEARCH_RESEARCHLIBERATORAGMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":125,"upgrade_name":"LIBERATORMORPH"}}},{"id":802,"name":"STARPORTTECHLABRESEARCH_RESEARCHRAPIDDEPLOYMENT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":137,"upgrade_name":"MEDIVACRAPIDDEPLOYMENT"}}},{"id":803,"name":"RESEARCH_RAVENRECALIBRATEDEXPLOSIVES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":138,"upgrade_name":"RAVENRECALIBRATEDEXPLOSIVES"}}},{"id":806,"name":"STARPORTTECHLABRESEARCH_RAVENRESEARCHENHANCEDMUNITIONS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":292,"upgrade_name":"RAVENENHANCEDMUNITIONS"}}},{"id":820,"name":"RESEARCH_PERSONALCLOAKING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":25,"upgrade_name":"PERSONALCLOAKING"}}},{"id":821,"name":"GHOSTACADEMYRESEARCH_RESEARCHGHOSTENERGYUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":26,"upgrade_name":"GHOSTMOEBIUSREACTOR"}}},{"id":822,"name":"GHOSTACADEMYRESEARCH_RESEARCHENHANCEDSHOCKWAVES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":297,"upgrade_name":"ENHANCEDSHOCKWAVES"}}},{"id":852,"name":"ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":27,"upgrade_name":"TERRANVEHICLEARMORSLEVEL1"}}},{"id":853,"name":"ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":28,"upgrade_name":"TERRANVEHICLEARMORSLEVEL2"}}},{"id":854,"name":"ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":29,"upgrade_name":"TERRANVEHICLEARMORSLEVEL3"}}},{"id":855,"name":"ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":30,"upgrade_name":"TERRANVEHICLEWEAPONSLEVEL1"}}},{"id":856,"name":"ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":31,"upgrade_name":"TERRANVEHICLEWEAPONSLEVEL2"}}},{"id":857,"name":"ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":32,"upgrade_name":"TERRANVEHICLEWEAPONSLEVEL3"}}},{"id":858,"name":"ARMORYRESEARCH_TERRANSHIPPLATINGLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":33,"upgrade_name":"TERRANSHIPARMORSLEVEL1"}}},{"id":859,"name":"ARMORYRESEARCH_TERRANSHIPPLATINGLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":34,"upgrade_name":"TERRANSHIPARMORSLEVEL2"}}},{"id":860,"name":"ARMORYRESEARCH_TERRANSHIPPLATINGLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":35,"upgrade_name":"TERRANSHIPARMORSLEVEL3"}}},{"id":861,"name":"ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":36,"upgrade_name":"TERRANSHIPWEAPONSLEVEL1"}}},{"id":862,"name":"ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":37,"upgrade_name":"TERRANSHIPWEAPONSLEVEL2"}}},{"id":863,"name":"ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":38,"upgrade_name":"TERRANSHIPWEAPONSLEVEL3"}}},{"id":864,"name":"ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":116,"upgrade_name":"TERRANVEHICLEANDSHIPARMORSLEVEL1"}}},{"id":865,"name":"ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":117,"upgrade_name":"TERRANVEHICLEANDSHIPARMORSLEVEL2"}}},{"id":866,"name":"ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":118,"upgrade_name":"TERRANVEHICLEANDSHIPARMORSLEVEL3"}}},{"id":880,"name":"PROTOSSBUILD_NEXUS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":59,"produces_name":"NEXUS"}}},{"id":881,"name":"PROTOSSBUILD_PYLON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":60,"produces_name":"PYLON"}}},{"id":882,"name":"PROTOSSBUILD_ASSIMILATOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildOnUnit":{"produces":61,"produces_name":"ASSIMILATOR"}}},{"id":883,"name":"PROTOSSBUILD_GATEWAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":62,"produces_name":"GATEWAY"}}},{"id":884,"name":"PROTOSSBUILD_FORGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":63,"produces_name":"FORGE"}}},{"id":885,"name":"PROTOSSBUILD_FLEETBEACON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":64,"produces_name":"FLEETBEACON"}}},{"id":886,"name":"PROTOSSBUILD_TWILIGHTCOUNCIL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":65,"produces_name":"TWILIGHTCOUNCIL"}}},{"id":887,"name":"PROTOSSBUILD_PHOTONCANNON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":66,"produces_name":"PHOTONCANNON"}}},{"id":889,"name":"PROTOSSBUILD_STARGATE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":67,"produces_name":"STARGATE"}}},{"id":890,"name":"PROTOSSBUILD_TEMPLARARCHIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":68,"produces_name":"TEMPLARARCHIVE"}}},{"id":891,"name":"PROTOSSBUILD_DARKSHRINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":69,"produces_name":"DARKSHRINE"}}},{"id":892,"name":"PROTOSSBUILD_ROBOTICSBAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":70,"produces_name":"ROBOTICSBAY"}}},{"id":893,"name":"PROTOSSBUILD_ROBOTICSFACILITY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":71,"produces_name":"ROBOTICSFACILITY"}}},{"id":894,"name":"PROTOSSBUILD_CYBERNETICSCORE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":72,"produces_name":"CYBERNETICSCORE"}}},{"id":895,"name":"BUILD_SHIELDBATTERY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":1910,"produces_name":"SHIELDBATTERY"}}},{"id":910,"name":"PROTOSSBUILD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":911,"name":"LOAD_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":912,"name":"UNLOADALL_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":913,"name":"UNLOADALLAT_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":914,"name":"UNLOADUNIT_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":916,"name":"GATEWAYTRAIN_ZEALOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":73,"produces_name":"ZEALOT"}}},{"id":917,"name":"GATEWAYTRAIN_STALKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":74,"produces_name":"STALKER"}}},{"id":919,"name":"GATEWAYTRAIN_HIGHTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":75,"produces_name":"HIGHTEMPLAR"}}},{"id":920,"name":"GATEWAYTRAIN_DARKTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":76,"produces_name":"DARKTEMPLAR"}}},{"id":921,"name":"GATEWAYTRAIN_SENTRY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":77,"produces_name":"SENTRY"}}},{"id":922,"name":"TRAIN_ADEPT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":311,"produces_name":"ADEPT"}}},{"id":946,"name":"STARGATETRAIN_PHOENIX","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":78,"produces_name":"PHOENIX"}}},{"id":948,"name":"STARGATETRAIN_CARRIER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":79,"produces_name":"CARRIER"}}},{"id":950,"name":"STARGATETRAIN_VOIDRAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":80,"produces_name":"VOIDRAY"}}},{"id":954,"name":"STARGATETRAIN_ORACLE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":495,"produces_name":"ORACLE"}}},{"id":955,"name":"STARGATETRAIN_TEMPEST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":496,"produces_name":"TEMPEST"}}},{"id":976,"name":"ROBOTICSFACILITYTRAIN_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":81,"produces_name":"WARPPRISM"}}},{"id":977,"name":"ROBOTICSFACILITYTRAIN_OBSERVER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":82,"produces_name":"OBSERVER"}}},{"id":978,"name":"ROBOTICSFACILITYTRAIN_COLOSSUS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":4,"produces_name":"COLOSSUS"}}},{"id":979,"name":"ROBOTICSFACILITYTRAIN_IMMORTAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":83,"produces_name":"IMMORTAL"}}},{"id":994,"name":"TRAIN_DISRUPTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":694,"produces_name":"DISRUPTOR"}}},{"id":1006,"name":"NEXUSTRAIN_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":84,"produces_name":"PROBE"}}},{"id":1036,"name":"PSISTORM_PSISTORM","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1038,"name":"CANCEL_HANGARQUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1039,"name":"CANCELSLOT_HANGARQUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1040,"name":"BROODLORDQUEUE2_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1041,"name":"BROODLORDQUEUE2_CANCELSLOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1042,"name":"BUILD_INTERCEPTORS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1062,"name":"FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":39,"upgrade_name":"PROTOSSGROUNDWEAPONSLEVEL1"}}},{"id":1063,"name":"FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":40,"upgrade_name":"PROTOSSGROUNDWEAPONSLEVEL2"}}},{"id":1064,"name":"FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":41,"upgrade_name":"PROTOSSGROUNDWEAPONSLEVEL3"}}},{"id":1065,"name":"FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":42,"upgrade_name":"PROTOSSGROUNDARMORSLEVEL1"}}},{"id":1066,"name":"FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":43,"upgrade_name":"PROTOSSGROUNDARMORSLEVEL2"}}},{"id":1067,"name":"FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":44,"upgrade_name":"PROTOSSGROUNDARMORSLEVEL3"}}},{"id":1068,"name":"FORGERESEARCH_PROTOSSSHIELDSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":45,"upgrade_name":"PROTOSSSHIELDSLEVEL1"}}},{"id":1069,"name":"FORGERESEARCH_PROTOSSSHIELDSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":46,"upgrade_name":"PROTOSSSHIELDSLEVEL2"}}},{"id":1070,"name":"FORGERESEARCH_PROTOSSSHIELDSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":47,"upgrade_name":"PROTOSSSHIELDSLEVEL3"}}},{"id":1093,"name":"RESEARCH_GRAVITICBOOSTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":48,"upgrade_name":"OBSERVERGRAVITICBOOSTER"}}},{"id":1094,"name":"RESEARCH_GRAVITICDRIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":49,"upgrade_name":"GRAVITICDRIVE"}}},{"id":1097,"name":"RESEARCH_EXTENDEDTHERMALLANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":50,"upgrade_name":"EXTENDEDTHERMALLANCE"}}},{"id":1099,"name":"ROBOTICSBAYRESEARCH_RESEARCHIMMORTALREVIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":121,"upgrade_name":"IMMORTALREVIVE"}}},{"id":1126,"name":"RESEARCH_PSISTORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":52,"upgrade_name":"PSISTORMTECH"}}},{"id":1152,"name":"ZERGBUILD_HATCHERY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":86,"produces_name":"HATCHERY"}}},{"id":1153,"name":"ZERGBUILD_CREEPTUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":87,"produces_name":"CREEPTUMOR"}}},{"id":1154,"name":"ZERGBUILD_EXTRACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildOnUnit":{"produces":88,"produces_name":"EXTRACTOR"}}},{"id":1155,"name":"ZERGBUILD_SPAWNINGPOOL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":89,"produces_name":"SPAWNINGPOOL"}}},{"id":1156,"name":"ZERGBUILD_EVOLUTIONCHAMBER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":90,"produces_name":"EVOLUTIONCHAMBER"}}},{"id":1157,"name":"ZERGBUILD_HYDRALISKDEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":91,"produces_name":"HYDRALISKDEN"}}},{"id":1158,"name":"ZERGBUILD_SPIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":92,"produces_name":"SPIRE"}}},{"id":1159,"name":"ZERGBUILD_ULTRALISKCAVERN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":93,"produces_name":"ULTRALISKCAVERN"}}},{"id":1160,"name":"ZERGBUILD_INFESTATIONPIT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":94,"produces_name":"INFESTATIONPIT"}}},{"id":1161,"name":"ZERGBUILD_NYDUSNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":95,"produces_name":"NYDUSNETWORK"}}},{"id":1162,"name":"ZERGBUILD_BANELINGNEST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":96,"produces_name":"BANELINGNEST"}}},{"id":1163,"name":"BUILD_LURKERDEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":504,"produces_name":"LURKERDENMP"}}},{"id":1165,"name":"ZERGBUILD_ROACHWARREN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":97,"produces_name":"ROACHWARREN"}}},{"id":1166,"name":"ZERGBUILD_SPINECRAWLER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":98,"produces_name":"SPINECRAWLER"}}},{"id":1167,"name":"ZERGBUILD_SPORECRAWLER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":99,"produces_name":"SPORECRAWLER"}}},{"id":1182,"name":"ZERGBUILD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1183,"name":"HARVEST_GATHER_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1184,"name":"HARVEST_RETURN_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1186,"name":"RESEARCH_ZERGMELEEWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":53,"upgrade_name":"ZERGMELEEWEAPONSLEVEL1"}}},{"id":1187,"name":"RESEARCH_ZERGMELEEWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":54,"upgrade_name":"ZERGMELEEWEAPONSLEVEL2"}}},{"id":1188,"name":"RESEARCH_ZERGMELEEWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":55,"upgrade_name":"ZERGMELEEWEAPONSLEVEL3"}}},{"id":1189,"name":"RESEARCH_ZERGGROUNDARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":56,"upgrade_name":"ZERGGROUNDARMORSLEVEL1"}}},{"id":1190,"name":"RESEARCH_ZERGGROUNDARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":57,"upgrade_name":"ZERGGROUNDARMORSLEVEL2"}}},{"id":1191,"name":"RESEARCH_ZERGGROUNDARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":58,"upgrade_name":"ZERGGROUNDARMORSLEVEL3"}}},{"id":1192,"name":"RESEARCH_ZERGMISSILEWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":59,"upgrade_name":"ZERGMISSILEWEAPONSLEVEL1"}}},{"id":1193,"name":"RESEARCH_ZERGMISSILEWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":60,"upgrade_name":"ZERGMISSILEWEAPONSLEVEL2"}}},{"id":1194,"name":"RESEARCH_ZERGMISSILEWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":61,"upgrade_name":"ZERGMISSILEWEAPONSLEVEL3"}}},{"id":1195,"name":"EVOLUTIONCHAMBERRESEARCH_EVOLVEPROPULSIVEPERISTALSIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":302,"upgrade_name":"SECRETEDCOATING"}}},{"id":1216,"name":"UPGRADETOLAIR_LAIR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":100,"produces_name":"LAIR"}}},{"id":1217,"name":"CANCEL_MORPHLAIR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1218,"name":"UPGRADETOHIVE_HIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":101,"produces_name":"HIVE"}}},{"id":1219,"name":"CANCEL_MORPHHIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1220,"name":"UPGRADETOGREATERSPIRE_GREATERSPIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":102,"produces_name":"GREATERSPIRE"}}},{"id":1221,"name":"CANCEL_MORPHGREATERSPIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1223,"name":"RESEARCH_PNEUMATIZEDCARAPACE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":62,"upgrade_name":"OVERLORDSPEED"}}},{"id":1224,"name":"LAIRRESEARCH_EVOLVEVENTRALSACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":63,"upgrade_name":"OVERLORDTRANSPORT"}}},{"id":1225,"name":"RESEARCH_BURROW","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":64,"upgrade_name":"BURROW"}}},{"id":1252,"name":"RESEARCH_ZERGLINGADRENALGLANDS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":65,"upgrade_name":"ZERGLINGATTACKSPEED"}}},{"id":1253,"name":"RESEARCH_ZERGLINGMETABOLICBOOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":66,"upgrade_name":"ZERGLINGMOVEMENTSPEED"}}},{"id":1282,"name":"RESEARCH_GROOVEDSPINES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":134,"upgrade_name":"EVOLVEGROOVEDSPINES"}}},{"id":1283,"name":"RESEARCH_MUSCULARAUGMENTS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":135,"upgrade_name":"EVOLVEMUSCULARAUGMENTS"}}},{"id":1312,"name":"RESEARCH_ZERGFLYERATTACKLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":68,"upgrade_name":"ZERGFLYERWEAPONSLEVEL1"}}},{"id":1313,"name":"RESEARCH_ZERGFLYERATTACKLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":69,"upgrade_name":"ZERGFLYERWEAPONSLEVEL2"}}},{"id":1314,"name":"RESEARCH_ZERGFLYERATTACKLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":70,"upgrade_name":"ZERGFLYERWEAPONSLEVEL3"}}},{"id":1315,"name":"RESEARCH_ZERGFLYERARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":71,"upgrade_name":"ZERGFLYERARMORSLEVEL1"}}},{"id":1316,"name":"RESEARCH_ZERGFLYERARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":72,"upgrade_name":"ZERGFLYERARMORSLEVEL2"}}},{"id":1317,"name":"RESEARCH_ZERGFLYERARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":73,"upgrade_name":"ZERGFLYERARMORSLEVEL3"}}},{"id":1342,"name":"LARVATRAIN_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":104,"produces_name":"DRONE"}}},{"id":1343,"name":"LARVATRAIN_ZERGLING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":105,"produces_name":"ZERGLING"}}},{"id":1344,"name":"LARVATRAIN_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":106,"produces_name":"OVERLORD"}}},{"id":1345,"name":"LARVATRAIN_HYDRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":107,"produces_name":"HYDRALISK"}}},{"id":1346,"name":"LARVATRAIN_MUTALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":108,"produces_name":"MUTALISK"}}},{"id":1348,"name":"LARVATRAIN_ULTRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":109,"produces_name":"ULTRALISK"}}},{"id":1351,"name":"LARVATRAIN_ROACH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":110,"produces_name":"ROACH"}}},{"id":1352,"name":"LARVATRAIN_INFESTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":111,"produces_name":"INFESTOR"}}},{"id":1353,"name":"LARVATRAIN_CORRUPTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":112,"produces_name":"CORRUPTOR"}}},{"id":1354,"name":"LARVATRAIN_VIPER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":499,"produces_name":"VIPER"}}},{"id":1356,"name":"TRAIN_SWARMHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":494,"produces_name":"SWARMHOSTMP"}}},{"id":1372,"name":"MORPHTOBROODLORD_BROODLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":114,"produces_name":"BROODLORD"}}},{"id":1373,"name":"CANCEL_MORPHBROODLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1374,"name":"BURROWDOWN_BANELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":115,"produces_name":"BANELINGBURROWED"}}},{"id":1375,"name":"BURROWBANELINGDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1376,"name":"BURROWUP_BANELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":9,"produces_name":"BANELING"}}},{"id":1378,"name":"BURROWDOWN_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":116,"produces_name":"DRONEBURROWED"}}},{"id":1379,"name":"BURROWDRONEDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1380,"name":"BURROWUP_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":104,"produces_name":"DRONE"}}},{"id":1382,"name":"BURROWDOWN_HYDRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":117,"produces_name":"HYDRALISKBURROWED"}}},{"id":1383,"name":"BURROWHYDRALISKDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1384,"name":"BURROWUP_HYDRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":107,"produces_name":"HYDRALISK"}}},{"id":1386,"name":"BURROWDOWN_ROACH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":118,"produces_name":"ROACHBURROWED"}}},{"id":1387,"name":"BURROWROACHDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1388,"name":"BURROWUP_ROACH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":110,"produces_name":"ROACH"}}},{"id":1390,"name":"BURROWDOWN_ZERGLING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":119,"produces_name":"ZERGLINGBURROWED"}}},{"id":1391,"name":"BURROWZERGLINGDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1392,"name":"BURROWUP_ZERGLING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":105,"produces_name":"ZERGLING"}}},{"id":1394,"name":"BURROWDOWN_INFESTORTERRAN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":120,"produces_name":"INFESTORTERRANBURROWED"}}},{"id":1396,"name":"BURROWUP_INFESTORTERRAN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":7,"produces_name":"INFESTORTERRAN"}}},{"id":1406,"name":"LOAD_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1408,"name":"UNLOADALLAT_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":1409,"name":"UNLOADUNIT_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1411,"name":"MERGEABLE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1412,"name":"WARPABLE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1413,"name":"WARPGATETRAIN_ZEALOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":73,"produces_name":"ZEALOT"}}},{"id":1414,"name":"WARPGATETRAIN_STALKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":74,"produces_name":"STALKER"}}},{"id":1416,"name":"WARPGATETRAIN_HIGHTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":75,"produces_name":"HIGHTEMPLAR"}}},{"id":1417,"name":"WARPGATETRAIN_DARKTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":76,"produces_name":"DARKTEMPLAR"}}},{"id":1418,"name":"WARPGATETRAIN_SENTRY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":77,"produces_name":"SENTRY"}}},{"id":1419,"name":"TRAINWARP_ADEPT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":311,"produces_name":"ADEPT"}}},{"id":1433,"name":"BURROWDOWN_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":125,"produces_name":"QUEENBURROWED"}}},{"id":1434,"name":"BURROWQUEENDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1435,"name":"BURROWUP_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":126,"produces_name":"QUEEN"}}},{"id":1437,"name":"LOAD_NYDUSNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1438,"name":"UNLOADALL_NYDASNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1440,"name":"UNLOADUNIT_NYDASNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1442,"name":"EFFECT_BLINK_STALKER","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1444,"name":"BURROWDOWN_INFESTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":127,"produces_name":"INFESTORBURROWED"}}},{"id":1445,"name":"BURROWINFESTORDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1446,"name":"BURROWUP_INFESTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":111,"produces_name":"INFESTOR"}}},{"id":1448,"name":"MORPH_OVERSEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":129,"produces_name":"OVERSEER"}}},{"id":1449,"name":"CANCEL_MORPHOVERSEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1450,"name":"UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":130,"produces_name":"PLANETARYFORTRESS"}}},{"id":1451,"name":"CANCEL_MORPHPLANETARYFORTRESS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1454,"name":"RESEARCH_PATHOGENGLANDS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":74,"upgrade_name":"INFESTORENERGYUPGRADE"}}},{"id":1455,"name":"RESEARCH_NEURALPARASITE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":101,"upgrade_name":"NEURALPARASITE"}}},{"id":1456,"name":"INFESTATIONPITRESEARCH_RESEARCHLOCUSTLIFETIMEINCREASE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":102,"upgrade_name":"LOCUSTLIFETIMEINCREASE"}}},{"id":1457,"name":"INFESTATIONPITRESEARCH_EVOLVEAMORPHOUSARMORCLOUD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":298,"upgrade_name":"MICROBIALSHROUD"}}},{"id":1482,"name":"RESEARCH_CENTRIFUGALHOOKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":75,"upgrade_name":"CENTRIFICALHOOKS"}}},{"id":1512,"name":"BURROWDOWN_ULTRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":131,"produces_name":"ULTRALISKBURROWED"}}},{"id":1514,"name":"BURROWUP_ULTRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":109,"produces_name":"ULTRALISK"}}},{"id":1516,"name":"UPGRADETOORBITAL_ORBITALCOMMAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":132,"produces_name":"ORBITALCOMMAND"}}},{"id":1517,"name":"CANCEL_MORPHORBITAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1518,"name":"MORPH_WARPGATE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":133,"produces_name":"WARPGATE"}}},{"id":1519,"name":"UPGRADETOWARPGATE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1520,"name":"MORPH_GATEWAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":62,"produces_name":"GATEWAY"}}},{"id":1521,"name":"MORPHBACKTOGATEWAY_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1522,"name":"LIFT_ORBITALCOMMAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":134,"produces_name":"ORBITALCOMMANDFLYING"}}},{"id":1524,"name":"LAND_ORBITALCOMMAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":132,"produces_name":"ORBITALCOMMAND"}}},{"id":1526,"name":"FORCEFIELD_FORCEFIELD","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1527,"name":"FORCEFIELD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1528,"name":"MORPH_WARPPRISMPHASINGMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":136,"produces_name":"WARPPRISMPHASING"}}},{"id":1529,"name":"PHASINGMODE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1530,"name":"MORPH_WARPPRISMTRANSPORTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":81,"produces_name":"WARPPRISM"}}},{"id":1531,"name":"TRANSPORTMODE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1532,"name":"RESEARCH_BATTLECRUISERWEAPONREFIT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":76,"upgrade_name":"BATTLECRUISERENABLESPECIALIZATIONS"}}},{"id":1533,"name":"FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":140,"upgrade_name":"LIBERATORAGRANGEUPGRADE"}}},{"id":1534,"name":"FUSIONCORERESEARCH_RESEARCHRAPIDREIGNITIONSYSTEM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":139,"upgrade_name":"MEDIVACINCREASESPEEDBOOST"}}},{"id":1562,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":78,"upgrade_name":"PROTOSSAIRWEAPONSLEVEL1"}}},{"id":1563,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":79,"upgrade_name":"PROTOSSAIRWEAPONSLEVEL2"}}},{"id":1564,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":80,"upgrade_name":"PROTOSSAIRWEAPONSLEVEL3"}}},{"id":1565,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":81,"upgrade_name":"PROTOSSAIRARMORSLEVEL1"}}},{"id":1566,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":82,"upgrade_name":"PROTOSSAIRARMORSLEVEL2"}}},{"id":1567,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":83,"upgrade_name":"PROTOSSAIRARMORSLEVEL3"}}},{"id":1568,"name":"RESEARCH_WARPGATE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":84,"upgrade_name":"WARPGATERESEARCH"}}},{"id":1571,"name":"CYBERNETICSCORERESEARCH_RESEARCHHALLUCINATION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":85,"upgrade_name":"HALTECH"}}},{"id":1592,"name":"RESEARCH_CHARGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":86,"upgrade_name":"CHARGE"}}},{"id":1593,"name":"RESEARCH_BLINK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":87,"upgrade_name":"BLINKTECH"}}},{"id":1594,"name":"RESEARCH_ADEPTRESONATINGGLAIVES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":130,"upgrade_name":"ADEPTPIERCINGATTACK"}}},{"id":1595,"name":"TWILIGHTCOUNCILRESEARCH_RESEARCHPSIONICSURGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":299,"upgrade_name":"SUNDERINGIMPACT"}}},{"id":1596,"name":"TWILIGHTCOUNCILRESEARCH_RESEARCHAMPLIFIEDSHIELDING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":300,"upgrade_name":"AMPLIFIEDSHIELDING"}}},{"id":1597,"name":"TWILIGHTCOUNCILRESEARCH_RESEARCHPSIONICAMPLIFIERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":301,"upgrade_name":"PSIONICAMPLIFIERS"}}},{"id":1622,"name":"TACNUKESTRIKE_NUKECALLDOWN","cast_range":12.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1623,"name":"CANCEL_NUKE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1624,"name":"SALVAGEBUNKERREFUND_SALVAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1628,"name":"EMP_EMP","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1632,"name":"TRAINQUEEN_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":126}}},{"id":1662,"name":"BURROWCREEPTUMORDOWN_BURROWDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":137,"produces_name":"CREEPTUMORBURROWED"}}},{"id":1664,"name":"TRANSFUSION_TRANSFUSION","cast_range":7.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1668,"name":"BARRACKSTECHLABMORPH_TECHLABBARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1670,"name":"FACTORYTECHLABMORPH_TECHLABFACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1672,"name":"STARPORTTECHLABMORPH_TECHLABSTARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1676,"name":"BARRACKSREACTORMORPH_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1678,"name":"FACTORYREACTORMORPH_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1680,"name":"STARPORTREACTORMORPH_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1682,"name":"ATTACK_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":1683,"name":"EFFECT_STIM_MARINE_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1684,"name":"EFFECT_STIM_MARAUDER_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1691,"name":"STOP_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1692,"name":"BEHAVIOR_GENERATECREEPON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1693,"name":"BEHAVIOR_GENERATECREEPOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1694,"name":"BUILD_CREEPTUMOR_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":138,"produces_name":"CREEPTUMORQUEEN"}}},{"id":1724,"name":"QUEENBUILD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1725,"name":"SPINECRAWLERUPROOT_SPINECRAWLERUPROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":139,"produces_name":"SPINECRAWLERUPROOTED"}}},{"id":1726,"name":"SPINECRAWLERUPROOT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1727,"name":"SPORECRAWLERUPROOT_SPORECRAWLERUPROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":140,"produces_name":"SPORECRAWLERUPROOTED"}}},{"id":1728,"name":"SPORECRAWLERUPROOT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1729,"name":"SPINECRAWLERROOT_SPINECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":98,"produces_name":"SPINECRAWLER"}}},{"id":1730,"name":"CANCEL_SPINECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1731,"name":"SPORECRAWLERROOT_SPORECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":99,"produces_name":"SPORECRAWLER"}}},{"id":1732,"name":"CANCEL_SPORECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1733,"name":"BUILD_CREEPTUMOR_TUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":87,"produces_name":"CREEPTUMOR"}}},{"id":1763,"name":"CANCEL_CREEPTUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1764,"name":"BUILDAUTOTURRET_AUTOTURRET","cast_range":2.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":31,"produces_name":"AUTOTURRET"}}},{"id":1766,"name":"MORPH_ARCHON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":141,"produces_name":"ARCHON"}}},{"id":1767,"name":"ARCHON_WARP_TARGET","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1768,"name":"BUILD_NYDUSWORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":142,"produces_name":"NYDUSCANAL"}}},{"id":1769,"name":"BUILDNYDUSCANAL_SUMMONNYDUSCANALATTACKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":491,"produces_name":"NYDUSCANALATTACKER"}}},{"id":1798,"name":"BUILDNYDUSCANAL_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1819,"name":"EFFECT_CHARGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1821,"name":"HERDINTERACT_HERD","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1825,"name":"CONTAMINATE_CONTAMINATE","cast_range":3.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1831,"name":"CANCEL_QUEUEPASIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1832,"name":"CANCELSLOT_QUEUEPASSIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1833,"name":"CANCEL_QUEUEPASSIVECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1834,"name":"CANCELSLOT_QUEUEPASSIVECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1837,"name":"MORPHTOGHOSTNOVA_MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":145,"produces_name":"GHOSTNOVA"}}},{"id":1839,"name":"DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1842,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1844,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1846,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1847,"name":"MORPH_MOTHERSHIP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":10,"produces_name":"MOTHERSHIP"}}},{"id":1848,"name":"CANCEL_MORPHMOTHERSHIP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1928,"name":"XELNAGAHEALINGSHRINE_XELNAGAHEALINGSHRINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1930,"name":"NEXUSINVULNERABILITY_NEXUSINVULNERABILITY","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1974,"name":"EFFECT_MASSRECALL_MOTHERSHIPCORE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1978,"name":"MORPH_HELLION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":53,"produces_name":"HELLION"}}},{"id":1997,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1998,"name":"MORPH_HELLBAT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":484,"produces_name":"HELLIONTANK"}}},{"id":2014,"name":"BURROWDOWN_SWARMHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":493,"produces_name":"SWARMHOSTBURROWEDMP"}}},{"id":2015,"name":"MORPHTOSWARMHOSTBURROWEDMP_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2016,"name":"BURROWUP_SWARMHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":494,"produces_name":"SWARMHOSTMP"}}},{"id":2048,"name":"ATTACKPROTOSSBUILDING_ATTACKBUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2049,"name":"ATTACKPROTOSSBUILDING_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2050,"name":"ATTACKPROTOSSBUILDING_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2057,"name":"STOP_BUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2058,"name":"STOPPROTOSSBUILDING_HOLDFIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2059,"name":"STOPPROTOSSBUILDING_CHEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2060,"name":"STOPPROTOSSBUILDING_DANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2063,"name":"BLINDINGCLOUD_BLINDINGCLOUD","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2067,"name":"EFFECT_ABDUCT","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2073,"name":"VIPERCONSUMESTRUCTURE_VIPERCONSUME","cast_range":7.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2080,"name":"TESTZERG_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2081,"name":"BEHAVIOR_BUILDINGATTACKON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2082,"name":"BEHAVIOR_BUILDINGATTACKOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2083,"name":"PICKUPSCRAPSMALL_PICKUPSCRAPSMALL","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2085,"name":"PICKUPSCRAPMEDIUM_PICKUPSCRAPMEDIUM","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2087,"name":"PICKUPSCRAPLARGE_PICKUPSCRAPLARGE","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2089,"name":"PICKUPPALLETGAS_PICKUPPALLETGAS","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2091,"name":"PICKUPPALLETMINERALS_PICKUPPALLETMINERALS","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2093,"name":"MASSIVEKNOCKOVER_MASSIVEKNOCKOVER","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2095,"name":"BURROWDOWN_WIDOWMINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":500,"produces_name":"WIDOWMINEBURROWED"}}},{"id":2096,"name":"WIDOWMINEBURROW_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2097,"name":"BURROWUP_WIDOWMINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":498,"produces_name":"WIDOWMINE"}}},{"id":2099,"name":"WIDOWMINEATTACK_WIDOWMINEATTACK","cast_range":5.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2101,"name":"TORNADOMISSILE_TORNADOMISSILE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2108,"name":"BURROWDOWN_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":503,"produces_name":"LURKERMPBURROWED"}}},{"id":2109,"name":"BURROWLURKERMPDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2110,"name":"BURROWUP_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":502,"produces_name":"LURKERMP"}}},{"id":2114,"name":"HALLUCINATION_ORACLE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2116,"name":"EFFECT_MEDIVACIGNITEAFTERBURNERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2146,"name":"ORACLEREVELATION_ORACLEREVELATION","cast_range":12.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2153,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2155,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2158,"name":"ULTRALISKWEAPONCOOLDOWN_ULTRALISKWEAPONCOOLDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2162,"name":"EFFECT_PHOTONOVERCHARGE","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2172,"name":"XELNAGA_CAVERNS_DOORNEOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2174,"name":"XELNAGA_CAVERNS_DOORNOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2178,"name":"XELNAGA_CAVERNS_DOORNWOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2184,"name":"XELNAGA_CAVERNS_DOORSEOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2186,"name":"XELNAGA_CAVERNS_DOORSOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2190,"name":"XELNAGA_CAVERNS_DOORSWOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2194,"name":"XELNAGA_CAVERNS_DOORWOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2244,"name":"EFFECT_TIMEWARP","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2324,"name":"CAUSTICSPRAY_CAUSTICSPRAY","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2330,"name":"MORPHTORAVAGER_RAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":688,"produces_name":"RAVAGER"}}},{"id":2331,"name":"CANCEL_MORPHRAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2332,"name":"MORPH_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":502,"produces_name":"LURKERMP"}}},{"id":2333,"name":"CANCEL_MORPHLURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2338,"name":"EFFECT_CORROSIVEBILE","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2340,"name":"BURROWDOWN_RAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":690,"produces_name":"RAVAGERBURROWED"}}},{"id":2341,"name":"BURROWRAVAGERDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2342,"name":"BURROWUP_RAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":688,"produces_name":"RAVAGER"}}},{"id":2344,"name":"PURIFICATIONNOVA_PURIFICATIONNOVA","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2346,"name":"EFFECT_PURIFICATIONNOVA","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2350,"name":"LOCKON_LOCKON","cast_range":7.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2354,"name":"CANCEL_LOCKON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2358,"name":"EFFECT_TACTICALJUMP","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2362,"name":"MORPH_THORHIGHIMPACTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":691,"produces_name":"THORAP"}}},{"id":2363,"name":"THORAPMODE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2364,"name":"MORPH_THOREXPLOSIVEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":52,"produces_name":"THOR"}}},{"id":2365,"name":"CANCEL_MORPHTHOREXPLOSIVEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2370,"name":"LOAD_NYDUSWORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2371,"name":"UNLOADALL_NYDUSWORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2375,"name":"BEHAVIOR_PULSARBEAMON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2376,"name":"BEHAVIOR_PULSARBEAMOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2383,"name":"LOCUSTMPFLYINGMORPHTOGROUND_LOCUSTMPFLYINGSWOOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":489,"produces_name":"LOCUSTMP"}}},{"id":2385,"name":"LOCUSTMPMORPHTOAIR_LOCUSTMPFLYINGSWOOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":693,"produces_name":"LOCUSTMPFLYING"}}},{"id":2387,"name":"EFFECT_LOCUSTSWOOP","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2389,"name":"HALLUCINATION_DISRUPTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2391,"name":"HALLUCINATION_ADEPT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2393,"name":"EFFECT_VOIDRAYPRISMATICALIGNMENT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2469,"name":"VOIDMPIMMORTALREVIVEREBUILD_IMMORTAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2473,"name":"ARBITERMPSTASISFIELD_ARBITERMPSTASISFIELD","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2475,"name":"ARBITERMPRECALL_ARBITERMPRECALL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2477,"name":"CORSAIRMPDISRUPTIONWEB_CORSAIRMPDISRUPTIONWEB","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2479,"name":"MORPHTOGUARDIANMP_MORPHTOGUARDIANMP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":727,"produces_name":"GUARDIANMP"}}},{"id":2480,"name":"MORPHTOGUARDIANMP_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2481,"name":"MORPHTODEVOURERMP_MORPHTODEVOURERMP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":729,"produces_name":"DEVOURERMP"}}},{"id":2482,"name":"MORPHTODEVOURERMP_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2483,"name":"DEFILERMPCONSUME_DEFILERMPCONSUME","cast_range":0.5,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2485,"name":"DEFILERMPDARKSWARM_DEFILERMPDARKSWARM","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2487,"name":"DEFILERMPPLAGUE_DEFILERMPPLAGUE","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2489,"name":"DEFILERMPBURROW_BURROWDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":730,"produces_name":"DEFILERMPBURROWED"}}},{"id":2490,"name":"DEFILERMPBURROW_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2491,"name":"DEFILERMPUNBURROW_BURROWUP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":731,"produces_name":"DEFILERMP"}}},{"id":2493,"name":"QUEENMPENSNARE_QUEENMPENSNARE","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2495,"name":"QUEENMPSPAWNBROODLINGS_QUEENMPSPAWNBROODLINGS","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2497,"name":"QUEENMPINFESTCOMMANDCENTER_QUEENMPINFESTCOMMANDCENTER","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2505,"name":"BUILD_STASISTRAP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":732,"produces_name":"ORACLESTASISTRAP"}}},{"id":2535,"name":"CANCEL_STASISTRAP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2536,"name":"ORACLESTASISTRAPACTIVATE_ACTIVATESTASISWARD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2542,"name":"PARASITICBOMB_PARASITICBOMB","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2544,"name":"ADEPTPHASESHIFT_ADEPTPHASESHIFT","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2548,"name":"PURIFICATIONNOVAMORPHBACK_PURIFICATIONNOVA","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":2550,"name":"BEHAVIOR_HOLDFIREON_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2552,"name":"BEHAVIOR_HOLDFIREOFF_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2554,"name":"LIBERATORMORPHTOAG_LIBERATORAGMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":734,"produces_name":"LIBERATORAG"}}},{"id":2556,"name":"LIBERATORMORPHTOAA_LIBERATORAAMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":689,"produces_name":"LIBERATOR"}}},{"id":2558,"name":"MORPH_LIBERATORAGMODE","cast_range":5.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":734,"produces_name":"LIBERATORAG"}}},{"id":2560,"name":"MORPH_LIBERATORAAMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":689,"produces_name":"LIBERATOR"}}},{"id":2588,"name":"KD8CHARGE_KD8CHARGE","cast_range":5.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2594,"name":"CANCEL_ADEPTPHASESHIFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2596,"name":"CANCEL_ADEPTSHADEPHASESHIFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2598,"name":"SLAYNELEMENTALGRAB_SLAYNELEMENTALGRAB","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2601,"name":"MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2700,"name":"EFFECT_SHADOWSTRIDE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2704,"name":"EFFECT_SPAWNLOCUSTS","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":693}}},{"id":2706,"name":"LOCUSTMPFLYINGSWOOPATTACK_LOCUSTMPFLYINGSWOOP","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2708,"name":"MORPH_OVERLORDTRANSPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":893,"produces_name":"OVERLORDTRANSPORT"}}},{"id":2709,"name":"CANCEL_MORPHOVERLORDTRANSPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2714,"name":"EFFECT_GHOSTSNIPE","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2716,"name":"PURIFYMORPHPYLON_MOTHERSHIPCOREWEAPON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":894,"produces_name":"PYLONOVERCHARGED"}}},{"id":2718,"name":"PURIFYMORPHPYLONBACK_MOTHERSHIPCOREWEAPON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":2720,"name":"RESEARCH_SHADOWSTRIKE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":141,"upgrade_name":"DARKTEMPLARBLINKUPGRADE"}}},{"id":3659,"name":"CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3660,"name":"HALT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3661,"name":"BURROWDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3662,"name":"BURROWUP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3663,"name":"LOADALL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3664,"name":"UNLOADALL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3665,"name":"STOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3666,"name":"HARVEST_GATHER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3667,"name":"HARVEST_RETURN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3668,"name":"LOAD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3669,"name":"UNLOADALLAT","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3671,"name":"CANCEL_LAST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3672,"name":"CANCEL_SLOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3673,"name":"RALLY_UNITS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3674,"name":"ATTACK","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3675,"name":"EFFECT_STIM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3676,"name":"BEHAVIOR_CLOAKON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3677,"name":"BEHAVIOR_CLOAKOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3678,"name":"LAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":0,"produces_name":"Unknown"}}},{"id":3679,"name":"LIFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3680,"name":"MORPH_ROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":0,"produces_name":"Unknown"}}},{"id":3681,"name":"MORPH_UPROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3682,"name":"BUILD_TECHLAB","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":5,"produces_name":"TECHLAB"}}},{"id":3683,"name":"BUILD_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":6,"produces_name":"REACTOR"}}},{"id":3684,"name":"EFFECT_SPRAY","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3685,"name":"EFFECT_REPAIR","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3686,"name":"EFFECT_MASSRECALL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3687,"name":"EFFECT_BLINK","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3688,"name":"BEHAVIOR_HOLDFIREON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3689,"name":"BEHAVIOR_HOLDFIREOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3690,"name":"RALLY_WORKERS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3691,"name":"BUILD_CREEPTUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":87,"produces_name":"CREEPTUMOR"}}},{"id":3707,"name":"CANCEL_VOIDRAYPRISMATICALIGNMENT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3709,"name":"RESEARCH_ADAPTIVETALONS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":293,"upgrade_name":"DIGGINGCLAWS"}}},{"id":3710,"name":"LURKERDENRESEARCH_RESEARCHLURKERRANGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":127,"upgrade_name":"LURKERRANGE"}}},{"id":3739,"name":"MORPH_OBSERVERMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":82,"produces_name":"OBSERVER"}}},{"id":3741,"name":"MORPH_SURVEILLANCEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":1911,"produces_name":"OBSERVERSIEGEMODE"}}},{"id":3743,"name":"MORPH_OVERSIGHTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":1912,"produces_name":"OVERSEERSIEGEMODE"}}},{"id":3745,"name":"MORPH_OVERSEERMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":129,"produces_name":"OVERSEER"}}},{"id":3747,"name":"EFFECT_INTERFERENCEMATRIX","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3751,"name":"EFFECT_REPAIR_REPAIRDRONE","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3753,"name":"EFFECT_ANTIARMORMISSILE","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3755,"name":"EFFECT_CHRONOBOOSTENERGYCOST","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3757,"name":"EFFECT_MASSRECALL_NEXUS","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3763,"name":"INFESTORENSNARE_INFESTORENSNARE","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3771,"name":"ATTACK_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3772,"name":"BATTLECRUISERATTACK_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3773,"name":"BATTLECRUISERATTACK_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3776,"name":"MOVE_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3777,"name":"PATROL_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3778,"name":"HOLDPOSITION_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3779,"name":"BATTLECRUISERMOVE_ACQUIREMOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3780,"name":"BATTLECRUISERMOVE_TURN","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3783,"name":"STOP_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3784,"name":"BATTLECRUISERSTOP_HOLDFIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3785,"name":"BATTLECRUISERSTOP_CHEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3786,"name":"BATTLECRUISERSTOP_DANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3789,"name":"VIPERPARASITICBOMBRELAY_PARASITICBOMB","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3791,"name":"PARASITICBOMBRELAYDODGE_PARASITICBOMB","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3793,"name":"HOLDPOSITION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3794,"name":"MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3795,"name":"PATROL","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3796,"name":"UNLOADUNIT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3967,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3970,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":4107,"name":"BATTERYOVERCHARGE_BATTERYOVERCHARGE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":4109,"name":"AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":4111,"name":"SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":4112,"name":"SHIELDBATTERYRECHARGEEX5_STOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"}],"Unit":[{"id":4,"name":"Colossus","race":"Protoss","supply":6.0,"cargo_size":8,"max_health":200.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":1200.0,"max_shield":150.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":1.5,"bonuses":[{"against":"Light","damage":5.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":5,"name":"TechLab","race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[{"ability":730,"requirements":[{"addon_to":21}]},{"ability":731,"requirements":[{"addon_to":21}]},{"ability":732,"requirements":[{"addon_to":21}]},{"ability":761,"requirements":[{"addon_to":27}]},{"ability":764,"requirements":[{"addon_to":27}]},{"ability":793,"requirements":[{"addon_to":28}]},{"ability":790,"requirements":[{"addon_to":28}]}],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":2.0,"is_flying":false},{"id":6,"name":"Reactor","race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":2.0,"is_flying":false},{"id":7,"name":"InfestorTerran","race":"Zerg","supply":0.0,"max_health":75.0,"armor":0.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":78.0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":24.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.330078125,"bonuses":[]},{"target_type":"Ground","damage_per_hit":12.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1394}]},{"id":8,"name":"BanelingCocoon","race":"Zerg","supply":0.5,"max_health":50.0,"armor":2.0,"sight":5.0,"speed":2.5,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":0.0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":9,"name":"Baneling","race":"Zerg","supply":0.5,"cargo_size":2,"max_health":30.0,"armor":0.0,"sight":8.0,"speed":2.5,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":320.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":42},{"ability":2081},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1374}]},{"id":10,"name":"Mothership","race":"Protoss","supply":8.0,"max_health":350.0,"armor":2.0,"sight":14.0,"speed":1.875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Armored","Mechanical","Psionic","Massive","Heroic"],"size":0,"radius":1.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":400,"gas":400,"time":2560.0,"max_shield":350.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":6,"range":7.0,"cooldown":2.2099609375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":142},{"ability":2244},{"ability":1}]},{"id":11,"name":"PointDefenseDrone","race":"Terran","supply":0.0,"max_health":50.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":200,"weapons":[],"attributes":["Light","Mechanical","Structure"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"is_flying":true},{"id":12,"name":"Changeling","race":"Zerg","supply":0.0,"max_health":5.0,"armor":0.0,"sight":8.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":13,"name":"ChangelingZealot","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"max_shield":50.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":2,"range":0.10009765625,"cooldown":1.199951171875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":14,"name":"ChangelingMarineShield","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":55.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":15,"name":"ChangelingMarine","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":45.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":16,"name":"ChangelingZerglingWings","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":35.0,"armor":0.0,"sight":8.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.696044921875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":17,"name":"ChangelingZergling","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":35.0,"armor":0.0,"sight":8.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.696044921875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":18,"name":"CommandCenter","race":"Terran","supply":-15.0,"cargo_capacity":5,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":400,"gas":0,"time":1600.0,"is_flying":false,"abilities":[{"ability":203},{"ability":416},{"ability":417},{"ability":524},{"ability":1},{"requirements":[{"building":22}],"ability":1450},{"requirements":[{"building":21}],"ability":1516}]},{"id":19,"name":"SupplyDepot","race":"Terran","supply":-8.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":480.0,"is_flying":false,"abilities":[{"ability":556}]},{"id":20,"name":"Refinery","race":"Terran","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"is_flying":false},{"id":21,"name":"Barracks","race":"Terran","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":true,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":1040.0,"is_flying":false,"abilities":[{"ability":195},{"ability":421},{"ability":422},{"ability":452},{"ability":560},{"ability":561},{"ability":1},{"requirements":[{"building":26,"addon":5}],"ability":562},{"requirements":[{"addon":5}],"ability":563}]},{"id":22,"name":"EngineeringBay","race":"Terran","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":560.0,"is_flying":false,"abilities":[{"ability":650},{"ability":651},{"ability":652},{"ability":656},{"ability":653,"requirements":[{"upgrade":7},{"building":29}]},{"ability":654,"requirements":[{"upgrade":8},{"building":29}]},{"ability":657,"requirements":[{"upgrade":11},{"building":29}]},{"ability":658,"requirements":[{"upgrade":12},{"building":29}]}]},{"id":23,"name":"MissileTurret","race":"Terran","supply":0.0,"max_health":250.0,"armor":0.0,"sight":11.0,"detection_range":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":400.0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":12.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":24,"name":"Bunker","race":"Terran","supply":0.0,"cargo_capacity":4,"max_health":400.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":640.0,"is_flying":false,"abilities":[{"ability":32},{"ability":195},{"ability":407},{"ability":1}]},{"id":25,"name":"SensorTower","race":"Terran","supply":0.0,"max_health":200.0,"armor":0.0,"sight":12.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":100,"time":400.0,"is_flying":false},{"id":26,"name":"GhostAcademy","race":"Terran","supply":0.0,"max_health":1250.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":640.0,"is_flying":false,"abilities":[{"ability":820},{"ability":822},{"requirements":[{"building":27}],"ability":710}]},{"id":27,"name":"Factory","race":"Terran","supply":0.0,"max_health":1250.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":true,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":960.0,"is_flying":false,"abilities":[{"ability":195},{"ability":454},{"ability":455},{"ability":485},{"ability":595},{"ability":614},{"ability":1},{"requirements":[{"addon":5}],"ability":591},{"requirements":[{"addon":5},{"building":29}],"ability":594},{"requirements":[{"building":29}],"ability":596},{"requirements":[{"addon":5}],"ability":597}]},{"id":28,"name":"Starport","race":"Terran","supply":0.0,"max_health":1300.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":true,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":800.0,"is_flying":false,"abilities":[{"ability":195},{"ability":487},{"ability":488},{"ability":518},{"ability":620},{"ability":624},{"ability":626},{"ability":1},{"requirements":[{"addon":5}],"ability":621},{"requirements":[{"addon":5}],"ability":622},{"requirements":[{"addon":5},{"building":30}],"ability":623}]},{"id":29,"name":"Armory","race":"Terran","supply":0.0,"max_health":750.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":1040.0,"is_flying":false,"abilities":[{"ability":855},{"ability":861},{"ability":864},{"ability":856,"requirements":[{"upgrade":30}]},{"ability":857,"requirements":[{"upgrade":31}]},{"ability":862,"requirements":[{"upgrade":36}]},{"ability":863,"requirements":[{"upgrade":37}]},{"ability":865,"requirements":[{"upgrade":116}]},{"ability":866,"requirements":[{"upgrade":117}]}]},{"id":30,"name":"FusionCore","race":"Terran","supply":0.0,"max_health":750.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1040.0,"is_flying":false,"abilities":[{"ability":1532},{"ability":1533},{"ability":1534}]},{"id":31,"name":"AutoTurret","race":"Terran","supply":0.0,"max_health":150.0,"armor":1.0,"sight":7.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":16.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":18.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":0.800048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":32,"name":"SiegeTankSieged","normal_mode":33,"race":"Terran","supply":3.0,"max_health":175.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":68.66796875,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":40.0,"damage_splash":0,"attacks":1,"range":13.0,"cooldown":3.0,"bonuses":[{"against":"Armored","damage":30.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":390},{"ability":1}]},{"id":33,"name":"SiegeTank","race":"Terran","supply":3.0,"cargo_size":4,"max_health":175.0,"armor":1.0,"sight":11.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":720.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":15.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.0400390625,"bonuses":[{"against":"Armored","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":388},{"ability":1}]},{"id":34,"name":"VikingAssault","normal_mode":35,"race":"Terran","supply":2.0,"cargo_size":2,"max_health":135.0,"armor":0.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":41.44140625,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":12.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.0,"bonuses":[{"against":"Mechanical","damage":8.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":405},{"ability":1}]},{"id":35,"name":"VikingFighter","race":"Terran","supply":2.0,"max_health":135.0,"armor":0.0,"sight":10.0,"speed":2.75,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":672.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":10.0,"damage_splash":0,"attacks":2,"range":9.0,"cooldown":2.0,"bonuses":[{"against":"Armored","damage":4.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":403},{"ability":1}]},{"id":36,"name":"CommandCenterFlying","normal_mode":18,"race":"Terran","supply":-15.0,"cargo_capacity":5,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":400,"gas":0,"time":32.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":416},{"ability":419},{"ability":1}]},{"id":37,"name":"BarracksTechLab","normal_mode":5,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":400.0,"is_flying":false,"abilities":[{"ability":730},{"ability":731},{"ability":732}]},{"id":38,"name":"BarracksReactor","normal_mode":6,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":800.0,"is_flying":false},{"id":39,"name":"FactoryTechLab","normal_mode":5,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":400.0,"is_flying":false,"abilities":[{"ability":761},{"ability":769},{"ability":764,"requirements":[{"building":29}]},{"ability":766,"requirements":[{"building":29}]}]},{"id":40,"name":"FactoryReactor","normal_mode":6,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":800.0,"is_flying":false},{"id":41,"name":"StarportTechLab","normal_mode":5,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":400.0,"is_flying":false,"abilities":[{"ability":790},{"ability":793},{"ability":799}]},{"id":42,"name":"StarportReactor","normal_mode":6,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":800.0,"is_flying":false},{"id":43,"name":"FactoryFlying","normal_mode":27,"race":"Terran","supply":0.0,"max_health":1250.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":32.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":454},{"ability":455},{"ability":520},{"ability":1}]},{"id":44,"name":"StarportFlying","normal_mode":28,"race":"Terran","supply":0.0,"max_health":1300.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":32.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":487},{"ability":488},{"ability":522},{"ability":1}]},{"id":45,"name":"SCV","race":"Terran","supply":1.0,"cargo_size":1,"max_health":45.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Biological","Mechanical"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":true,"is_townhall":false,"minerals":50,"gas":0,"time":272.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":26},{"ability":295},{"ability":316},{"ability":318},{"ability":319},{"ability":320},{"ability":1},{"requirements":[{"building":19}],"ability":321},{"requirements":[{"building":18}],"ability":322},{"requirements":[{"building":22}],"ability":323},{"requirements":[{"building":21}],"ability":324},{"requirements":[{"building":22}],"ability":326},{"requirements":[{"building":21}],"ability":327},{"requirements":[{"building":21}],"ability":328},{"requirements":[{"building":27}],"ability":329},{"requirements":[{"building":27}],"ability":331},{"requirements":[{"building":28}],"ability":333}]},{"id":46,"name":"BarracksFlying","normal_mode":21,"race":"Terran","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":32.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":421},{"ability":422},{"ability":554},{"ability":1}]},{"id":47,"name":"SupplyDepotLowered","normal_mode":19,"race":"Terran","supply":-8.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":20.80078125,"is_flying":false,"abilities":[{"ability":558}]},{"id":48,"name":"Marine","race":"Terran","supply":1.0,"cargo_size":1,"max_health":45.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":400.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":15}],"ability":380}]},{"id":49,"name":"Reaper","race":"Terran","supply":1.0,"cargo_size":1,"max_health":60.0,"armor":0.0,"sight":9.0,"speed":3.75,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":720.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.10009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2588},{"ability":1}]},{"id":50,"name":"Ghost","race":"Terran","supply":2.0,"cargo_size":2,"max_health":100.0,"armor":0.0,"sight":11.0,"speed":2.8125,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":75,"attributes":["Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":640.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[{"against":"Light","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":36},{"ability":1628},{"ability":2714},{"ability":1},{"requirements":[{"upgrade":25}],"ability":382}]},{"id":51,"name":"Marauder","race":"Terran","supply":2.0,"cargo_size":2,"max_health":125.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.5625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":25,"time":480.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[{"against":"Armored","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":15}],"ability":253}]},{"id":52,"name":"Thor","race":"Terran","supply":6.0,"cargo_size":8,"max_health":400.0,"armor":1.0,"sight":11.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":960.0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":6.0,"damage_splash":0,"attacks":4,"range":10.0,"cooldown":3.0,"bonuses":[{"against":"Light","damage":6.0}]},{"target_type":"Ground","damage_per_hit":30.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":1.280029296875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2362},{"ability":1}]},{"id":53,"name":"Hellion","race":"Terran","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":10.0,"speed":4.25,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":480.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":2.5,"bonuses":[{"against":"Light","damage":6.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"building":29}],"ability":1998}]},{"id":54,"name":"Medivac","race":"Terran","supply":2.0,"cargo_capacity":8,"max_health":150.0,"armor":1.0,"sight":11.0,"speed":2.5,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":672.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":386},{"ability":394},{"ability":2116},{"ability":1}]},{"id":55,"name":"Banshee","race":"Terran","supply":3.0,"max_health":140.0,"armor":0.0,"sight":10.0,"speed":2.75,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Light","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":960.0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":12.0,"damage_splash":0,"attacks":2,"range":6.0,"cooldown":1.25,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":20}],"ability":392}]},{"id":56,"name":"Raven","race":"Terran","supply":2.0,"max_health":140.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed":2.94921875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":200,"time":960.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1764},{"ability":3747},{"ability":3753},{"ability":1}]},{"id":57,"name":"Battlecruiser","race":"Terran","supply":6.0,"max_health":550.0,"armor":3.0,"sight":12.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":400,"gas":300,"time":1440.0,"is_flying":true,"abilities":[{"ability":2358},{"ability":3771},{"ability":3776},{"ability":3777},{"ability":3778},{"ability":3783},{"ability":1},{"requirements":[{"upgrade":76}],"ability":401}]},{"id":58,"name":"Nuke","race":"Terran","supply":0.0,"max_health":100.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":0.0,"is_flying":true},{"id":59,"name":"Nexus","race":"Protoss","supply":-15.0,"max_health":1000.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":400,"gas":0,"time":1600.0,"max_shield":1000.0,"is_flying":false,"abilities":[{"ability":207},{"ability":1006},{"ability":4107},{"ability":3755},{"ability":3757},{"ability":1},{"requirements":[{"building":64}],"ability":110}]},{"id":60,"name":"Pylon","race":"Protoss","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":400.0,"max_shield":200.0,"is_flying":false},{"id":61,"name":"Assimilator","race":"Protoss","supply":0.0,"max_health":300.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"max_shield":300.0,"is_flying":false},{"id":62,"name":"Gateway","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":1040.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":195},{"ability":916},{"ability":1},{"requirements":[{"building":72}],"ability":917},{"requirements":[{"building":68}],"ability":919},{"requirements":[{"building":69}],"ability":920},{"requirements":[{"building":72}],"ability":921},{"requirements":[{"building":72}],"ability":922},{"requirements":[{"upgrade":84}],"ability":1518}]},{"id":63,"name":"Forge","race":"Protoss","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":720.0,"max_shield":400.0,"is_flying":false,"abilities":[{"ability":1062},{"ability":1065},{"ability":1068},{"ability":1063,"requirements":[{"upgrade":39},{"building":65}]},{"ability":1064,"requirements":[{"upgrade":40},{"building":65}]},{"ability":1066,"requirements":[{"upgrade":42},{"building":65}]},{"ability":1067,"requirements":[{"upgrade":43},{"building":65}]},{"ability":1069,"requirements":[{"upgrade":45},{"building":65}]},{"ability":1070,"requirements":[{"upgrade":46},{"building":65}]}]},{"id":64,"name":"FleetBeacon","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":960.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":46},{"ability":48},{"ability":49}]},{"id":65,"name":"TwilightCouncil","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":800.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1592},{"ability":1593},{"ability":1594}]},{"id":66,"name":"PhotonCannon","race":"Protoss","supply":0.0,"max_health":150.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Structure"],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":640.0,"max_shield":150.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.25,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":67,"name":"Stargate","race":"Protoss","supply":0.0,"max_health":600.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":960.0,"max_shield":600.0,"is_flying":false,"abilities":[{"ability":195},{"ability":946},{"ability":950},{"ability":954},{"ability":1},{"requirements":[{"building":64}],"ability":948},{"requirements":[{"building":64}],"ability":955}]},{"id":68,"name":"TemplarArchive","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":800.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1126}]},{"id":69,"name":"DarkShrine","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.5,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1600.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":2720}]},{"id":70,"name":"RoboticsBay","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1040.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1093},{"ability":1094},{"ability":1097}]},{"id":71,"name":"RoboticsFacility","race":"Protoss","supply":0.0,"max_health":450.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":1040.0,"max_shield":450.0,"is_flying":false,"abilities":[{"ability":195},{"ability":976},{"ability":977},{"ability":979},{"ability":1},{"requirements":[{"building":70}],"ability":978},{"requirements":[{"building":70}],"ability":994}]},{"id":72,"name":"CyberneticsCore","race":"Protoss","supply":0.0,"max_health":550.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":800.0,"max_shield":550.0,"is_flying":false,"abilities":[{"ability":1562},{"ability":1565},{"ability":1568},{"ability":1563,"requirements":[{"upgrade":78},{"building":64}]},{"ability":1564,"requirements":[{"upgrade":79},{"building":64}]},{"ability":1566,"requirements":[{"upgrade":81},{"building":64}]},{"ability":1567,"requirements":[{"upgrade":82},{"building":64}]}]},{"id":73,"name":"Zealot","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":608.0,"max_shield":50.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":2,"range":0.10009765625,"cooldown":1.199951171875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":86}],"ability":1819}]},{"id":74,"name":"Stalker","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":10.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":50,"time":672.0,"max_shield":80.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":13.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.8701171875,"bonuses":[{"against":"Armored","damage":5.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":87}],"ability":1442}]},{"id":75,"name":"HighTemplar","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":40.0,"armor":0.0,"sight":10.0,"speed":1.875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Light","Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":150,"time":880.0,"max_shield":40.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.75390625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":140},{"ability":1},{"requirements":[{"upgrade":52}],"ability":1036},{"ability":1766}]},{"id":76,"name":"DarkTemplar","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":40.0,"armor":1.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":125,"time":880.0,"max_shield":80.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":45.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":1.694091796875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":141}],"ability":2700},{"ability":1766}]},{"id":77,"name":"Sentry","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":40.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Light","Mechanical","Psionic"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":100,"time":592.0,"max_shield":40.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":76},{"ability":146},{"ability":148},{"ability":150},{"ability":152},{"ability":154},{"ability":156},{"ability":158},{"ability":160},{"ability":162},{"ability":164},{"ability":1526},{"ability":2114},{"ability":2389},{"ability":2391},{"ability":1}]},{"id":78,"name":"Phoenix","race":"Protoss","supply":2.0,"max_health":120.0,"armor":0.0,"sight":10.0,"speed":4.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Light","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":560.0,"max_shield":60.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":5.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.10009765625,"bonuses":[{"against":"Light","damage":5.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":173},{"ability":1}]},{"id":79,"name":"Carrier","race":"Protoss","supply":6.0,"max_health":300.0,"armor":2.0,"sight":12.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":350,"gas":250,"time":1440.0,"max_shield":150.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1038},{"ability":1042},{"ability":1}]},{"id":80,"name":"VoidRay","race":"Protoss","supply":4.0,"max_health":150.0,"armor":0.0,"sight":10.0,"speed":2.75,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":150,"time":963.19921875,"max_shield":100.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2393},{"ability":1}]},{"id":81,"name":"WarpPrism","race":"Protoss","supply":2.0,"cargo_capacity":8,"max_health":80.0,"armor":0.0,"sight":10.0,"speed":2.953125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":0,"time":800.0,"max_shield":100.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":911},{"ability":1528},{"ability":1}]},{"id":82,"name":"Observer","race":"Protoss","supply":1.0,"max_health":40.0,"armor":0.0,"sight":11.0,"detection_range":11.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":75,"time":480.0,"max_shield":20.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":3741},{"ability":1}]},{"id":83,"name":"Immortal","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":200.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":275,"gas":100,"time":880.0,"max_shield":100.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.449951171875,"bonuses":[{"against":"Armored","damage":30.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":84,"name":"Probe","race":"Protoss","supply":1.0,"cargo_size":1,"max_health":20.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":true,"is_townhall":false,"minerals":50,"gas":0,"time":272.0,"max_shield":20.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":30},{"ability":298},{"ability":880},{"ability":881},{"ability":882},{"ability":1},{"requirements":[{"building":60}],"ability":883},{"requirements":[{"building":60}],"ability":884},{"requirements":[{"building":67}],"ability":885},{"requirements":[{"building":72}],"ability":886},{"requirements":[{"building":63}],"ability":887},{"requirements":[{"building":72}],"ability":889},{"requirements":[{"building":65}],"ability":890},{"requirements":[{"building":65}],"ability":891},{"requirements":[{"building":71}],"ability":892},{"requirements":[{"building":72}],"ability":893},{"requirements":[{"building":62}],"ability":894},{"requirements":[{"building":72}],"ability":895}]},{"id":85,"name":"Interceptor","race":"Protoss","supply":0.0,"max_health":40.0,"armor":0.0,"sight":7.0,"speed":7.5,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":15,"gas":0,"time":0.0,"max_shield":40.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":5.0,"damage_splash":0,"attacks":2,"range":2.0,"cooldown":3.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":86,"name":"Hatchery","race":"Zerg","supply":-6.0,"max_health":1500.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":350,"gas":0,"time":1600.0,"is_flying":false,"abilities":[{"ability":211},{"ability":212},{"ability":1223},{"ability":1225},{"ability":1},{"requirements":[{"building":89}],"ability":1216},{"requirements":[{"building":89}],"ability":1632}]},{"id":87,"name":"CreepTumor","race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological","Structure"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":240.0,"is_flying":false,"abilities":[{"ability":1733},{"ability":1}]},{"id":88,"name":"Extractor","race":"Zerg","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"is_flying":false},{"id":89,"name":"SpawningPool","race":"Zerg","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":0,"time":1040.0,"is_flying":false,"abilities":[{"ability":1253},{"ability":1252,"requirements":[{"building":101}]}]},{"id":90,"name":"EvolutionChamber","race":"Zerg","supply":0.0,"max_health":750.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":560.0,"is_flying":false,"abilities":[{"ability":1186},{"ability":1189},{"ability":1192},{"ability":1187,"requirements":[{"upgrade":53},{"building":100}]},{"ability":1188,"requirements":[{"upgrade":54},{"building":101}]},{"ability":1190,"requirements":[{"upgrade":56},{"building":100}]},{"ability":1191,"requirements":[{"upgrade":57},{"building":101}]},{"ability":1193,"requirements":[{"upgrade":59},{"building":100}]},{"ability":1194,"requirements":[{"upgrade":60},{"building":101}]}]},{"id":91,"name":"HydraliskDen","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":640.0,"is_flying":false,"abilities":[{"ability":1282},{"ability":1283}]},{"id":92,"name":"Spire","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":200,"time":1600.0,"is_flying":false,"abilities":[{"ability":1312},{"ability":1315},{"requirements":[{"building":101}],"ability":1220},{"ability":1313,"requirements":[{"upgrade":68},{"building":100}]},{"ability":1314,"requirements":[{"upgrade":69},{"building":101}]},{"ability":1316,"requirements":[{"upgrade":71},{"building":100}]},{"ability":1317,"requirements":[{"upgrade":72},{"building":101}]}]},{"id":93,"name":"UltraliskCavern","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":200,"time":1040.0,"is_flying":false,"abilities":[{"ability":263},{"ability":265}]},{"id":94,"name":"InfestationPit","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":800.0,"is_flying":false,"abilities":[{"ability":1454},{"ability":1455}]},{"id":95,"name":"NydusNetwork","race":"Zerg","supply":0.0,"cargo_capacity":1020,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":150,"time":800.0,"is_flying":false,"abilities":[{"ability":4},{"ability":195},{"ability":1437},{"ability":1768},{"ability":1}]},{"id":96,"name":"BanelingNest","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[{"ability":1482,"requirements":[{"building":100}]}],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":960.0,"is_flying":false},{"id":97,"name":"RoachWarren","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[{"ability":216,"requirements":[{"building":100}]},{"ability":217,"requirements":[{"building":100}]}],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":0,"time":880.0,"is_flying":false},{"id":98,"name":"SpineCrawler","race":"Zerg","supply":0.0,"max_health":300.0,"armor":2.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":800.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.85009765625,"bonuses":[{"against":"Armored","damage":5.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":1725},{"ability":1}]},{"id":99,"name":"SporeCrawler","race":"Zerg","supply":0.0,"max_health":400.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Structure"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":480.0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":15.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":0.86083984375,"bonuses":[{"against":"Biological","damage":15.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":1727},{"ability":1}]},{"id":100,"name":"Lair","normal_mode":86,"race":"Zerg","supply":-6.0,"max_health":2000.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":500,"gas":100,"time":1280.0,"is_flying":false,"abilities":[{"ability":211},{"ability":212},{"ability":1223},{"ability":1225},{"ability":1},{"requirements":[{"building":94}],"ability":1218},{"requirements":[{"building":89}],"ability":1632}]},{"id":101,"name":"Hive","normal_mode":86,"race":"Zerg","supply":-6.0,"max_health":2500.0,"armor":1.0,"sight":12.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":700,"gas":250,"time":1600.0,"is_flying":false,"abilities":[{"ability":211},{"ability":212},{"ability":1223},{"ability":1225},{"ability":1},{"requirements":[{"building":89}],"ability":1632}]},{"id":102,"name":"GreaterSpire","normal_mode":92,"race":"Zerg","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":350,"gas":350,"time":1600.0,"is_flying":false,"abilities":[{"ability":1312},{"ability":1315},{"ability":1313,"requirements":[{"upgrade":68},{"building":100}]},{"ability":1314,"requirements":[{"upgrade":69},{"building":101}]},{"ability":1316,"requirements":[{"upgrade":71},{"building":100}]},{"ability":1317,"requirements":[{"upgrade":72},{"building":101}]}]},{"id":103,"name":"Egg","race":"Zerg","supply":0.0,"max_health":200.0,"armor":10.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":104,"name":"Drone","race":"Zerg","supply":1.0,"cargo_size":1,"max_health":40.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":true,"is_townhall":false,"minerals":50,"gas":0,"time":272.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":28},{"ability":1152},{"ability":1154},{"ability":1183},{"ability":1},{"requirements":[{"building":86}],"ability":1155},{"requirements":[{"building":86}],"ability":1156},{"requirements":[{"building":100}],"ability":1157},{"requirements":[{"building":100}],"ability":1158},{"requirements":[{"building":101}],"ability":1159},{"requirements":[{"building":100}],"ability":1160},{"requirements":[{"building":100}],"ability":1161},{"requirements":[{"building":89}],"ability":1162},{"requirements":[{"building":91}],"ability":1163},{"requirements":[{"building":89}],"ability":1165},{"requirements":[{"building":89}],"ability":1166},{"requirements":[{"building":89}],"ability":1167},{"requirements":[{"upgrade":64}],"ability":1378}]},{"id":105,"name":"Zergling","race":"Zerg","supply":0.5,"cargo_size":1,"max_health":35.0,"armor":0.0,"sight":8.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":0,"time":384.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.696044921875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"building":96}],"ability":80},{"requirements":[{"upgrade":64}],"ability":1390}]},{"id":106,"name":"Overlord","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":0.0,"sight":11.0,"speed":0.64453125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":400.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1},{"requirements":[{"building":100}],"ability":1448},{"requirements":[{"building":100}],"ability":1692},{"requirements":[{"building":100}],"ability":2708}]},{"id":107,"name":"Hydralisk","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":50,"time":528.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":12.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.824951171875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1382},{"requirements":[{"building":504}],"ability":2332}]},{"id":108,"name":"Mutalisk","race":"Zerg","supply":2.0,"max_health":120.0,"armor":0.0,"sight":11.0,"speed":4.0,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":528.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":9.0,"damage_splash":0,"attacks":1,"range":3.0,"cooldown":1.524658203125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":109,"name":"Ultralisk","race":"Zerg","supply":6.0,"cargo_size":8,"max_health":500.0,"armor":2.0,"sight":9.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":880.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":35.0,"damage_splash":0,"attacks":1,"range":1.0,"cooldown":0.860107421875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1512}]},{"id":110,"name":"Roach","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":145.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":432.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":16.0,"damage_splash":0,"attacks":1,"range":4.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1386},{"requirements":[{"building":86}],"ability":2330}]},{"id":111,"name":"Infestor","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological","Psionic"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":150,"time":800.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":74},{"ability":4109},{"ability":1},{"requirements":[{"upgrade":101}],"ability":249},{"requirements":[{"upgrade":64}],"ability":1394},{"requirements":[{"upgrade":64}],"ability":1444}]},{"id":112,"name":"Corruptor","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":10.0,"speed":3.375,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":640.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":14.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.89990234375,"bonuses":[{"against":"Massive","damage":6.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2324},{"ability":1},{"requirements":[{"building":102}],"ability":1372}]},{"id":113,"name":"BroodLordCocoon","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.40625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological","Massive"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":250,"time":0.0,"is_flying":true},{"id":114,"name":"BroodLord","race":"Zerg","supply":4.0,"max_health":225.0,"armor":1.0,"sight":12.0,"speed":1.40625,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":250,"time":541.34765625,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":10.0,"cooldown":2.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":115,"name":"BanelingBurrowed","normal_mode":9,"race":"Zerg","supply":0.5,"max_health":30.0,"armor":0.0,"sight":8.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":18.962890625,"is_flying":false,"abilities":[{"ability":42},{"ability":1376}]},{"id":116,"name":"DroneBurrowed","normal_mode":104,"race":"Zerg","supply":1.0,"max_health":40.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":23.328125,"is_flying":false,"abilities":[{"ability":1380}]},{"id":117,"name":"HydraliskBurrowed","normal_mode":107,"race":"Zerg","supply":2.0,"max_health":90.0,"armor":0.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":50,"time":24.291015625,"is_flying":false,"abilities":[{"ability":1384}]},{"id":118,"name":"RoachBurrowed","normal_mode":110,"race":"Zerg","supply":2.0,"max_health":145.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":9.69140625,"is_flying":false,"abilities":[{"ability":4},{"ability":1388},{"requirements":[{"upgrade":3}],"ability":16},{"requirements":[{"upgrade":3}],"ability":17},{"requirements":[{"upgrade":3}],"ability":18},{"requirements":[{"upgrade":3}],"ability":19},{"requirements":[{"upgrade":3}],"ability":1}]},{"id":119,"name":"ZerglingBurrowed","normal_mode":105,"race":"Zerg","supply":0.5,"max_health":35.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":0,"time":24.291015625,"is_flying":false,"abilities":[{"ability":1392}]},{"id":120,"name":"InfestorTerranBurrowed","normal_mode":7,"race":"Zerg","supply":0.0,"max_health":75.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":24.291015625,"is_flying":false,"abilities":[{"ability":1396}]},{"id":125,"name":"QueenBurrowed","normal_mode":126,"race":"Zerg","supply":2.0,"max_health":175.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":60,"weapons":[],"attributes":["Biological","Psionic"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":15.33203125,"is_flying":false,"abilities":[{"ability":1435}]},{"id":126,"name":"Queen","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":175.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":25,"attributes":["Biological","Psionic"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":800.0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":9.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.0,"bonuses":[]},{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":251},{"ability":1664},{"ability":1694},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1433},{"ability":3691}]},{"id":127,"name":"InfestorBurrowed","normal_mode":111,"race":"Zerg","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":10.0,"speed":2.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological","Psionic"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":150,"time":10.962890625,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1396},{"ability":1446},{"ability":1},{"requirements":[{"upgrade":101}],"ability":249}]},{"id":128,"name":"OverlordCocoon","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":0.0,"is_flying":true},{"id":129,"name":"Overseer","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed":1.875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":266.6796875,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":181},{"ability":1825},{"ability":3743},{"ability":1}]},{"id":130,"name":"PlanetaryFortress","normal_mode":18,"race":"Terran","supply":-15.0,"cargo_capacity":5,"max_health":1500.0,"armor":3.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":550,"gas":150,"time":800.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":40.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":203},{"ability":416},{"ability":524},{"ability":1}]},{"id":131,"name":"UltraliskBurrowed","normal_mode":109,"race":"Zerg","supply":6.0,"max_health":500.0,"armor":2.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":34.0,"is_flying":false,"abilities":[{"ability":1514}]},{"id":132,"name":"OrbitalCommand","normal_mode":18,"race":"Terran","supply":-15.0,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":550,"gas":0,"time":560.0,"is_flying":false,"abilities":[{"ability":171},{"ability":203},{"ability":255},{"ability":399},{"ability":524},{"ability":1522},{"ability":1}]},{"id":133,"name":"WarpGate","normal_mode":62,"race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":160.0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1413},{"ability":1520},{"ability":1},{"requirements":[{"building":72}],"ability":1414},{"requirements":[{"building":68}],"ability":1416},{"requirements":[{"building":69}],"ability":1417},{"requirements":[{"building":72}],"ability":1418},{"requirements":[{"building":72}],"ability":1419}]},{"id":134,"name":"OrbitalCommandFlying","normal_mode":132,"race":"Terran","supply":-15.0,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed":0.9375,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":550,"gas":0,"time":32.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":1524},{"ability":1}]},{"id":136,"name":"WarpPrismPhasing","normal_mode":81,"race":"Protoss","supply":2.0,"cargo_capacity":8,"max_health":80.0,"armor":0.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":0.875,"power_radius":3.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":0,"time":24.0,"max_shield":100.0,"is_flying":true,"abilities":[{"ability":4},{"ability":911},{"ability":1530},{"ability":1}]},{"id":137,"name":"CreepTumorBurrowed","normal_mode":87,"race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological","Structure"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":18.9609375,"is_flying":false,"abilities":[{"ability":1733},{"ability":1},{"ability":3691}]},{"id":138,"name":"CreepTumorQueen","normal_mode":87,"race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological","Structure"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":240.0,"is_flying":false,"abilities":[{"ability":1733},{"ability":1}]},{"id":139,"name":"SpineCrawlerUprooted","normal_mode":98,"race":"Zerg","supply":0.0,"max_health":300.0,"armor":2.0,"sight":11.0,"speed":1.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":16.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1},{"ability":1729}]},{"id":140,"name":"SporeCrawlerUprooted","normal_mode":99,"race":"Zerg","supply":0.0,"max_health":400.0,"armor":1.0,"sight":11.0,"speed":1.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":16.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1},{"ability":1731}]},{"id":141,"name":"Archon","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":10.0,"armor":0.0,"sight":9.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Psionic","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":175,"gas":275,"time":0.0,"max_shield":350.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":3.0,"cooldown":1.75390625,"bonuses":[{"against":"Biological","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":142,"name":"NydusCanal","race":"Zerg","supply":0.0,"cargo_capacity":1020,"max_health":300.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":75,"time":320.0,"is_flying":false,"abilities":[{"ability":4},{"ability":195},{"ability":2370},{"ability":1}]},{"id":145,"name":"GhostNova","normal_mode":50,"race":"Terran","supply":2.0,"cargo_size":2,"max_health":100.0,"armor":0.0,"sight":11.0,"speed":2.8125,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":75,"attributes":["Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":0.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[{"against":"Light","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":36},{"ability":1628},{"ability":2714},{"ability":1},{"requirements":[],"ability":382}]},{"id":150,"name":"InfestedTerransEgg","race":"Zerg","supply":0.0,"max_health":75.0,"armor":2.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":16},{"ability":17},{"ability":18},{"ability":1}]},{"id":151,"name":"Larva","race":"Zerg","supply":0.0,"max_health":25.0,"armor":10.0,"sight":5.0,"speed":0.5625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":1342},{"ability":1344},{"requirements":[{"building":89}],"ability":1343},{"requirements":[{"building":91}],"ability":1345},{"requirements":[{"building":92}],"ability":1346},{"requirements":[{"building":93}],"ability":1348},{"requirements":[{"building":97}],"ability":1351},{"requirements":[{"building":94}],"ability":1352},{"requirements":[{"building":92}],"ability":1353},{"requirements":[{"building":101}],"ability":1354},{"requirements":[{"building":94}],"ability":1356}]},{"id":268,"name":"MULE","race":"Terran","supply":0.0,"max_health":60.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":78},{"ability":166},{"ability":1}]},{"id":289,"name":"Broodling","race":"Zerg","supply":0.0,"max_health":30.0,"armor":0.0,"sight":7.0,"speed":3.8359375,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.6455078125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":311,"name":"Adept","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":70.0,"armor":1.0,"sight":9.0,"speed":2.5,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":25,"time":672.0,"max_shield":70.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":4.0,"cooldown":2.25,"bonuses":[{"against":"Light","damage":12.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2544},{"ability":1}]},{"id":339,"name":"InfestedTerransEggPlacement","race":"Zerg","supply":0.0,"max_health":1.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false},{"id":484,"name":"HellionTank","race":"Terran","supply":2.0,"cargo_size":4,"max_health":135.0,"armor":0.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":480.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":18.0,"damage_splash":0,"attacks":1,"range":2.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"building":29}],"ability":1978}]},{"id":488,"name":"MothershipCore","race":"Protoss","supply":2.0,"max_health":130.0,"armor":1.0,"sight":9.0,"speed":1.875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":480.0,"max_shield":60.0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.85009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1974},{"ability":2162},{"ability":2244},{"ability":1},{"requirements":[],"ability":1847}]},{"id":489,"name":"LocustMP","race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":6.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":3.0,"cooldown":0.60009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":491,"name":"NydusCanalAttacker","race":"Zerg","supply":2.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":0,"time":320.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":492,"name":"NydusCanalCreeper","race":"Zerg","supply":0.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":0.0,"is_flying":false,"abilities":[{"ability":4},{"ability":23},{"ability":1839},{"ability":1}]},{"id":493,"name":"SwarmHostBurrowedMP","normal_mode":494,"race":"Zerg","supply":3.0,"max_health":160.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":75,"time":42.0,"is_flying":false,"abilities":[{"ability":2704},{"ability":1}]},{"id":494,"name":"SwarmHostMP","race":"Zerg","supply":3.0,"cargo_size":4,"max_health":160.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":75,"time":640.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2704},{"ability":1},{"requirements":[{"upgrade":64}],"ability":2014}]},{"id":495,"name":"Oracle","race":"Protoss","supply":3.0,"max_health":100.0,"armor":0.0,"sight":10.0,"speed":4.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":832.0,"max_shield":60.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":2146},{"ability":2375},{"ability":2505},{"ability":1}]},{"id":496,"name":"Tempest","race":"Protoss","supply":5.0,"max_health":200.0,"armor":2.0,"sight":12.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":175,"time":960.0,"max_shield":100.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":30.0,"damage_splash":0,"attacks":1,"range":14.0,"cooldown":3.300048828125,"bonuses":[{"against":"Massive","damage":22.0}]},{"target_type":"Ground","damage_per_hit":40.0,"damage_splash":0,"attacks":1,"range":10.0,"cooldown":3.300048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":497,"name":"WarHound","race":"Terran","supply":3.0,"cargo_size":4,"max_health":220.0,"armor":1.0,"sight":11.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":0.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":23.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.300048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2101},{"ability":1}]},{"id":498,"name":"WidowMine","race":"Terran","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":7.0,"speed":2.8125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":480.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2095},{"ability":1}]},{"id":499,"name":"Viper","race":"Zerg","supply":3.0,"max_health":150.0,"armor":1.0,"sight":11.0,"speed":2.953125,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological","Psionic"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":200,"time":640.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2063},{"ability":2067},{"ability":2073},{"ability":2542},{"ability":1}]},{"id":500,"name":"WidowMineBurrowed","normal_mode":498,"race":"Terran","supply":2.0,"max_health":90.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":52.0,"is_flying":false,"abilities":[{"ability":2097},{"ability":2099},{"ability":1}]},{"id":501,"name":"LurkerMPEgg","race":"Zerg","supply":2.0,"max_health":100.0,"armor":1.0,"sight":5.0,"speed":3.375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":502,"name":"LurkerMP","race":"Zerg","supply":3.0,"cargo_size":4,"max_health":200.0,"armor":1.0,"sight":10.0,"speed":2.953125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.9375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":553.328125,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":2108},{"ability":1}]},{"id":503,"name":"LurkerMPBurrowed","normal_mode":502,"race":"Zerg","supply":3.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":42.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":8.0,"cooldown":2.0,"bonuses":[{"against":"Armored","damage":10.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":2110},{"ability":2550},{"ability":1}]},{"id":504,"name":"LurkerDenMP","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[{"ability":3709,"requirements":[{"building":101}]},{"ability":3710,"requirements":[{"building":101}]}],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1280.0,"is_flying":false},{"id":569,"name":"ResourceBlocker","race":"Protoss","supply":0.0,"max_health":130.0,"armor":0.0,"sight":2.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Structure"],"abilities":[],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false},{"id":593,"name":"IceProtossCrates","race":"Protoss","supply":0.0,"max_health":10.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false},{"id":594,"name":"ProtossCrates","race":"Protoss","supply":0.0,"max_health":10.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false},{"id":595,"name":"TowerMine","race":"Terran","supply":4.0,"max_health":100.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":0.0,"is_flying":true},{"id":687,"name":"RavagerCocoon","race":"Zerg","supply":2.0,"max_health":100.0,"armor":5.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":688,"name":"Ravager","race":"Zerg","supply":3.0,"cargo_size":4,"max_health":120.0,"armor":1.0,"sight":9.0,"speed":2.75,"speed_creep_mul":1.0,"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":196.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":16.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.60009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2338},{"ability":1},{"requirements":[{"upgrade":64}],"ability":2340}]},{"id":689,"name":"Liberator","race":"Terran","supply":3.0,"max_health":180.0,"armor":0.0,"sight":10.0,"speed":3.375,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":960.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":5.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.800048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2558},{"ability":1}]},{"id":690,"name":"RavagerBurrowed","normal_mode":688,"race":"Zerg","supply":3.0,"max_health":120.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":9.69140625,"is_flying":false,"abilities":[{"ability":2342}]},{"id":691,"name":"ThorAP","normal_mode":52,"race":"Terran","supply":6.0,"cargo_size":8,"max_health":400.0,"armor":1.0,"sight":11.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":42.0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":11.0,"cooldown":1.280029296875,"bonuses":[{"against":"Massive","damage":10.0}]},{"target_type":"Ground","damage_per_hit":30.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":1.280029296875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2364},{"ability":1}]},{"id":692,"name":"Cyclone","race":"Terran","supply":3.0,"cargo_size":4,"max_health":120.0,"armor":1.0,"sight":11.0,"speed":3.375,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":720.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":18.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":1.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2350},{"ability":1}]},{"id":693,"name":"LocustMPFlying","normal_mode":489,"race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":6.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2387},{"ability":1}]},{"id":694,"name":"Disruptor","race":"Protoss","supply":3.0,"cargo_size":4,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":800.0,"max_shield":100.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2346},{"ability":1}]},{"id":725,"name":"VoidMPImmortalReviveCorpse","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":200.0,"armor":1.0,"sight":0.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":100,"time":0.0,"is_flying":false,"abilities":[{"ability":195},{"ability":2469},{"ability":1}]},{"id":726,"name":"GuardianCocoonMP","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.40625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological","Massive"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":0.0,"is_flying":true},{"id":727,"name":"GuardianMP","race":"Zerg","supply":2.0,"max_health":150.0,"armor":2.0,"sight":10.0,"speed":1.5,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":640.015625,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":9.0,"cooldown":1.300048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":728,"name":"DevourerCocoonMP","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.40625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological","Massive"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":0.0,"is_flying":true},{"id":729,"name":"DevourerMP","race":"Zerg","supply":2.0,"max_health":250.0,"armor":2.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":150,"time":640.015625,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":3.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":730,"name":"DefilerMPBurrowed","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Biological","Psionic"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":150,"time":24.291015625,"is_flying":false,"abilities":[{"ability":2491}]},{"id":731,"name":"DefilerMP","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Biological","Psionic"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":150,"time":8.80078125,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2483},{"ability":2485},{"ability":2487},{"ability":1},{"requirements":[{"upgrade":64}],"ability":2489}]},{"id":732,"name":"OracleStasisTrap","race":"Protoss","supply":0.0,"max_health":30.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Structure"],"abilities":[],"size":0,"radius":0.4375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":80.0,"max_shield":30.0,"is_flying":false},{"id":733,"name":"DisruptorPhased","race":"Protoss","supply":3.0,"cargo_size":4,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":4.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"max_shield":100.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":734,"name":"LiberatorAG","normal_mode":689,"race":"Terran","supply":3.0,"max_health":180.0,"armor":0.0,"sight":10.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":64.66796875,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":75.0,"damage_splash":0,"attacks":1,"range":10.0,"cooldown":1.60009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":2560},{"ability":1}]},{"id":800,"name":"ReleaseInterceptorsBeacon","race":"Protoss","supply":0.0,"max_health":1.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":1.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":true},{"id":801,"name":"AdeptPhaseShift","normal_mode":311,"race":"Protoss","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":1.0,"sight":4.0,"speed":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"max_shield":50.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":2596},{"ability":1}]},{"id":807,"name":"ThorAALance","race":"Terran","supply":0.0,"max_health":10.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false},{"id":820,"name":"HERCPlacement","normal_mode":838,"race":"Terran","supply":3.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":100,"time":0.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":838,"name":"HERC","race":"Terran","supply":3.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":100,"time":0.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":840,"name":"Replicant","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":300,"time":0.0,"max_shield":150.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":855,"name":"CorsairMP","race":"Protoss","supply":2.0,"max_health":120.0,"armor":1.0,"sight":9.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":0.0,"max_shield":60.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.472412109375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2477},{"ability":1}]},{"id":856,"name":"ScoutMP","race":"Protoss","supply":3.0,"max_health":150.0,"armor":0.0,"sight":9.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":275,"gas":125,"time":0.0,"max_shield":100.0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":1,"range":4.0,"cooldown":1.694091796875,"bonuses":[]},{"target_type":"Air","damage_per_hit":7.0,"damage_splash":0,"attacks":2,"range":4.0,"cooldown":1.25,"bonuses":[{"against":"Armored","damage":7.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":857,"name":"ArbiterMP","race":"Protoss","supply":4.0,"max_health":200.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Armored","Mechanical"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":350,"time":0.0,"max_shield":150.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2473},{"ability":2475},{"ability":1}]},{"id":858,"name":"ScourgeMP","race":"Zerg","supply":0.5,"max_health":25.0,"armor":0.0,"sight":5.0,"speed":3.5,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":12,"gas":37,"time":0.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":110.0,"damage_splash":0,"attacks":1,"range":0.0,"cooldown":0.833251953125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":860,"name":"QueenMP","race":"Zerg","supply":-2.0,"max_health":150.0,"armor":0.0,"sight":11.0,"speed":3.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2493},{"ability":2495},{"ability":2497},{"ability":1}]},{"id":891,"name":"Elsecaro_Colonist_Hut","race":"Terran","supply":0.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":892,"name":"TransportOverlordCocoon","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":0.0,"is_flying":true},{"id":893,"name":"OverlordTransport","race":"Zerg","supply":-8.0,"cargo_capacity":8,"max_health":200.0,"armor":0.0,"sight":11.0,"speed":0.64453125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":266.6796875,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1406},{"ability":1},{"requirements":[{"building":100}],"ability":1448},{"requirements":[{"building":100}],"ability":1692}]},{"id":894,"name":"PylonOvercharged","normal_mode":60,"race":"Protoss","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"max_shield":200.0,"is_flying":false},{"id":895,"name":"BypassArmorDrone","race":"Terran","supply":0.0,"max_health":80.0,"armor":0.0,"sight":7.0,"speed":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical","Structure"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":23},{"ability":1}]},{"id":1910,"name":"ShieldBattery","race":"Protoss","supply":0.0,"max_health":150.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"max_energy":100.0,"start_energy":100,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":640.0,"max_shield":150.0,"is_flying":false},{"id":1911,"name":"ObserverSiegeMode","normal_mode":82,"race":"Protoss","supply":1.0,"max_health":40.0,"armor":0.0,"sight":13.75,"detection_range":13.75,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":75,"time":12.0,"max_shield":20.0,"is_flying":true,"abilities":[{"ability":4},{"ability":3739}]},{"id":1912,"name":"OverseerSiegeMode","normal_mode":129,"race":"Zerg","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":13.75,"detection_range":13.75,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":12.0,"is_flying":true,"abilities":[{"ability":4},{"ability":181},{"ability":1825},{"ability":3745},{"ability":1}]},{"id":1913,"name":"RavenRepairDrone","race":"Terran","supply":0.0,"max_health":50.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":200,"weapons":[],"attributes":["Light","Mechanical","Structure","Summoned"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"is_flying":true,"abilities":[{"ability":4},{"ability":3751},{"ability":1}]},{"id":1940,"name":"Viking","race":"Terran","supply":0.0,"max_health":1.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"is_flying":false},{"id":1943,"name":"RefineryRich","race":"Terran","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"is_flying":false},{"id":1994,"name":"AssimilatorRich","race":"Protoss","supply":0.0,"max_health":300.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":0.0,"max_shield":300.0,"is_flying":false},{"id":1995,"name":"ExtractorRich","race":"Zerg","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":0.0,"is_flying":false}],"Upgrade":[{"id":1,"name":"CarrierLaunchSpeedUpgrade","cost":{"minerals":150,"gas":150,"time":1280.0}},{"id":2,"name":"GlialReconstitution","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":3,"name":"TunnelingClaws","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":4,"name":"ChitinousPlating","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":5,"name":"HiSecAutoTracking","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":6,"name":"TerranBuildingArmor","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":7,"name":"TerranInfantryWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":8,"name":"TerranInfantryWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":9,"name":"TerranInfantryWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":10,"name":"NeosteelFrame","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":11,"name":"TerranInfantryArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":12,"name":"TerranInfantryArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":13,"name":"TerranInfantryArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":14,"name":"ReaperSpeed","cost":{"minerals":50,"gas":50,"time":1600.0}},{"id":15,"name":"Stimpack","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":16,"name":"ShieldWall","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":17,"name":"PunisherGrenades","cost":{"minerals":50,"gas":50,"time":960.0}},{"id":19,"name":"HighCapacityBarrels","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":20,"name":"BansheeCloak","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":21,"name":"MedivacCaduceusReactor","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":22,"name":"RavenCorvidReactor","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":23,"name":"HunterSeeker","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":24,"name":"DurableMaterials","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":25,"name":"PersonalCloaking","cost":{"minerals":150,"gas":150,"time":1920.0}},{"id":26,"name":"GhostMoebiusReactor","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":27,"name":"TerranVehicleArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":28,"name":"TerranVehicleArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":29,"name":"TerranVehicleArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":30,"name":"TerranVehicleWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":31,"name":"TerranVehicleWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":32,"name":"TerranVehicleWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":33,"name":"TerranShipArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":34,"name":"TerranShipArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":35,"name":"TerranShipArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":36,"name":"TerranShipWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":37,"name":"TerranShipWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":38,"name":"TerranShipWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":39,"name":"ProtossGroundWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2880.0}},{"id":40,"name":"ProtossGroundWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3440.0}},{"id":41,"name":"ProtossGroundWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":4000.0}},{"id":42,"name":"ProtossGroundArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2880.0}},{"id":43,"name":"ProtossGroundArmorsLevel2","cost":{"minerals":150,"gas":150,"time":3440.0}},{"id":44,"name":"ProtossGroundArmorsLevel3","cost":{"minerals":200,"gas":200,"time":4000.0}},{"id":45,"name":"ProtossShieldsLevel1","cost":{"minerals":150,"gas":150,"time":2880.0}},{"id":46,"name":"ProtossShieldsLevel2","cost":{"minerals":225,"gas":225,"time":3440.0}},{"id":47,"name":"ProtossShieldsLevel3","cost":{"minerals":300,"gas":300,"time":4000.0}},{"id":48,"name":"ObserverGraviticBooster","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":49,"name":"GraviticDrive","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":50,"name":"ExtendedThermalLance","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":52,"name":"PsiStormTech","cost":{"minerals":200,"gas":200,"time":1760.0}},{"id":53,"name":"ZergMeleeWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":54,"name":"ZergMeleeWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3040.0}},{"id":55,"name":"ZergMeleeWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":3520.0}},{"id":56,"name":"ZergGroundArmorsLevel1","cost":{"minerals":150,"gas":150,"time":2560.0}},{"id":57,"name":"ZergGroundArmorsLevel2","cost":{"minerals":225,"gas":225,"time":3040.0}},{"id":58,"name":"ZergGroundArmorsLevel3","cost":{"minerals":300,"gas":300,"time":3520.0}},{"id":59,"name":"ZergMissileWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":60,"name":"ZergMissileWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3040.0}},{"id":61,"name":"ZergMissileWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":3520.0}},{"id":62,"name":"overlordspeed","cost":{"minerals":100,"gas":100,"time":960.0}},{"id":63,"name":"overlordtransport","cost":{"minerals":200,"gas":200,"time":2080.0}},{"id":64,"name":"Burrow","cost":{"minerals":100,"gas":100,"time":1600.0}},{"id":65,"name":"zerglingattackspeed","cost":{"minerals":200,"gas":200,"time":2080.0}},{"id":66,"name":"zerglingmovementspeed","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":68,"name":"ZergFlyerWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":69,"name":"ZergFlyerWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":70,"name":"ZergFlyerWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":71,"name":"ZergFlyerArmorsLevel1","cost":{"minerals":150,"gas":150,"time":2560.0}},{"id":72,"name":"ZergFlyerArmorsLevel2","cost":{"minerals":225,"gas":225,"time":3040.0}},{"id":73,"name":"ZergFlyerArmorsLevel3","cost":{"minerals":300,"gas":300,"time":3520.0}},{"id":74,"name":"InfestorEnergyUpgrade","cost":{"minerals":150,"gas":150,"time":1280.0}},{"id":75,"name":"CentrificalHooks","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":76,"name":"BattlecruiserEnableSpecializations","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":78,"name":"ProtossAirWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2880.0}},{"id":79,"name":"ProtossAirWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3440.0}},{"id":80,"name":"ProtossAirWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":4000.0}},{"id":81,"name":"ProtossAirArmorsLevel1","cost":{"minerals":150,"gas":150,"time":2880.0}},{"id":82,"name":"ProtossAirArmorsLevel2","cost":{"minerals":225,"gas":225,"time":3440.0}},{"id":83,"name":"ProtossAirArmorsLevel3","cost":{"minerals":300,"gas":300,"time":4000.0}},{"id":84,"name":"WarpGateResearch","cost":{"minerals":50,"gas":50,"time":2240.0}},{"id":85,"name":"haltech","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":86,"name":"Charge","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":87,"name":"BlinkTech","cost":{"minerals":150,"gas":150,"time":2720.0}},{"id":88,"name":"AnabolicSynthesis","cost":{"minerals":150,"gas":150,"time":960.0}},{"id":98,"name":"TransformationServos","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":99,"name":"PhoenixRangeUpgrade","cost":{"minerals":150,"gas":150,"time":1440.0}},{"id":100,"name":"TempestRangeUpgrade","cost":{"minerals":200,"gas":200,"time":1760.0}},{"id":101,"name":"NeuralParasite","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":102,"name":"LocustLifetimeIncrease","cost":{"minerals":200,"gas":200,"time":1920.0}},{"id":113,"name":"TerranVehicleAndShipWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":114,"name":"TerranVehicleAndShipWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":115,"name":"TerranVehicleAndShipWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":116,"name":"TerranVehicleAndShipArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":117,"name":"TerranVehicleAndShipArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":118,"name":"TerranVehicleAndShipArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":120,"name":"RoachSupply","cost":{"minerals":200,"gas":200,"time":2080.0}},{"id":121,"name":"ImmortalRevive","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":122,"name":"DrillClaws","cost":{"minerals":75,"gas":75,"time":1760.0}},{"id":123,"name":"CycloneLockOnRangeUpgrade","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":125,"name":"LiberatorMorph","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":127,"name":"LurkerRange","cost":{"minerals":150,"gas":150,"time":1280.0}},{"id":130,"name":"AdeptPiercingAttack","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":134,"name":"EvolveGroovedSpines","cost":{"minerals":100,"gas":100,"time":1600.0}},{"id":135,"name":"EvolveMuscularAugments","cost":{"minerals":100,"gas":100,"time":1600.0}},{"id":136,"name":"BansheeSpeed","cost":{"minerals":150,"gas":150,"time":2720.0}},{"id":137,"name":"MedivacRapidDeployment","cost":{"minerals":150,"gas":150,"time":1920.0}},{"id":138,"name":"RavenRecalibratedExplosives","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":139,"name":"MedivacIncreaseSpeedBoost","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":140,"name":"LiberatorAGRangeUpgrade","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":141,"name":"DarkTemplarBlinkUpgrade","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":144,"name":"CycloneLockOnDamageUpgrade","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":288,"name":"VoidRaySpeedUpgrade","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":289,"name":"SmartServos","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":290,"name":"ArmorPiercingRockets","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":291,"name":"CycloneRapidFireLaunchers","cost":{"minerals":75,"gas":75,"time":1760.0}},{"id":292,"name":"RavenEnhancedMunitions","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":293,"name":"DiggingClaws","cost":{"minerals":150,"gas":150,"time":1280.0}},{"id":296,"name":"TempestGroundAttackUpgrade","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":297,"name":"EnhancedShockwaves","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":298,"name":"MicrobialShroud","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":299,"name":"SunderingImpact","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":300,"name":"AmplifiedShielding","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":301,"name":"PsionicAmplifiers","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":302,"name":"SecretedCoating","cost":{"minerals":100,"gas":100,"time":1280.0}}]}
\ No newline at end of file
+{"Ability":[{"id":1,"name":"SMART","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2,"name":"TAUNT_TAUNT","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":4,"name":"STOP_STOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":5,"name":"STOP_HOLDFIRESPECIAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":6,"name":"STOP_CHEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":7,"name":"STOP_DANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":16,"name":"MOVE_MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3794},{"id":17,"name":"PATROL_PATROL","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3795},{"id":18,"name":"HOLDPOSITION_HOLD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3793},{"id":19,"name":"SCAN_MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3674},{"id":20,"name":"MOVE_TURN","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":23,"name":"ATTACK_ATTACK","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3674},{"id":24,"name":"ATTACK_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":25,"name":"ATTACK_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":26,"name":"EFFECT_SPRAY_TERRAN","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3684},{"id":28,"name":"EFFECT_SPRAY_ZERG","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3684},{"id":30,"name":"EFFECT_SPRAY_PROTOSS","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3684},{"id":36,"name":"BEHAVIOR_HOLDFIREON_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3688},{"id":38,"name":"BEHAVIOR_HOLDFIREOFF_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3689},{"id":40,"name":"MORPHTOINFESTEDTERRAN_INFESTEDTERRANS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":7,"produces_name":"INFESTORTERRAN"}}},{"id":42,"name":"EXPLODE_EXPLODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":45,"name":"FLEETBEACONRESEARCH_RESEARCHINTERCEPTORLAUNCHSPEEDUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":1,"upgrade_name":"CARRIERLAUNCHSPEEDUPGRADE"}}},{"id":46,"name":"RESEARCH_PHOENIXANIONPULSECRYSTALS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":99,"upgrade_name":"PHOENIXRANGEUPGRADE"}}},{"id":47,"name":"FLEETBEACONRESEARCH_TEMPESTRANGEUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":100,"upgrade_name":"TEMPESTRANGEUPGRADE"}}},{"id":48,"name":"FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":288,"upgrade_name":"VOIDRAYSPEEDUPGRADE"}}},{"id":49,"name":"FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":297,"upgrade_name":"TEMPESTGROUNDATTACKUPGRADE"}}},{"id":74,"name":"FUNGALGROWTH_FUNGALGROWTH","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":76,"name":"GUARDIANSHIELD_GUARDIANSHIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":78,"name":"EFFECT_REPAIR_MULE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3685},{"id":110,"name":"NEXUSTRAINMOTHERSHIP_MOTHERSHIP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":10}}},{"id":140,"name":"FEEDBACK_FEEDBACK","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":142,"name":"EFFECT_MASSRECALL_STRATEGICRECALL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3686},{"id":146,"name":"HALLUCINATION_ARCHON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":148,"name":"HALLUCINATION_COLOSSUS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":150,"name":"HALLUCINATION_HIGHTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":152,"name":"HALLUCINATION_IMMORTAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":154,"name":"HALLUCINATION_PHOENIX","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":156,"name":"HALLUCINATION_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":158,"name":"HALLUCINATION_STALKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":160,"name":"HALLUCINATION_VOIDRAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":162,"name":"HALLUCINATION_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":164,"name":"HALLUCINATION_ZEALOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":166,"name":"HARVEST_GATHER_MULE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3666},{"id":167,"name":"HARVEST_RETURN_MULE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3667},{"id":171,"name":"CALLDOWNMULE_CALLDOWNMULE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":173,"name":"GRAVITONBEAM_GRAVITONBEAM","cast_range":4.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":174,"name":"CANCEL_GRAVITONBEAM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":175,"name":"BUILDINPROGRESSNYDUSCANAL_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":181,"name":"SPAWNCHANGELING_SPAWNCHANGELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":12,"produces_name":"CHANGELING"}}},{"id":195,"name":"RALLY_BUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3673},{"id":199,"name":"RALLY_MORPHING_UNIT","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3673},{"id":203,"name":"RALLY_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3690},{"id":207,"name":"RALLY_NEXUS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3690},{"id":211,"name":"RALLY_HATCHERY_UNITS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3673},{"id":212,"name":"RALLY_HATCHERY_WORKERS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3690},{"id":216,"name":"RESEARCH_GLIALREGENERATION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":2,"upgrade_name":"GLIALRECONSTITUTION"}}},{"id":217,"name":"RESEARCH_TUNNELINGCLAWS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":3,"upgrade_name":"TUNNELINGCLAWS"}}},{"id":218,"name":"ROACHWARRENRESEARCH_ROACHSUPPLY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":120,"upgrade_name":"ROACHSUPPLY"}}},{"id":245,"name":"SAPSTRUCTURE_SAPSTRUCTURE","cast_range":0.25,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":249,"name":"NEURALPARASITE_NEURALPARASITE","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":250,"name":"CANCEL_NEURALPARASITE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":251,"name":"EFFECT_INJECTLARVA","cast_range":0.10009765625,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":253,"name":"EFFECT_STIM_MARAUDER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3675},{"id":255,"name":"SUPPLYDROP_SUPPLYDROP","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":263,"name":"RESEARCH_ANABOLICSYNTHESIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":88,"upgrade_name":"ANABOLICSYNTHESIS"}}},{"id":265,"name":"RESEARCH_CHITINOUSPLATING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":4,"upgrade_name":"CHITINOUSPLATING"}}},{"id":295,"name":"HARVEST_GATHER_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3666},{"id":296,"name":"HARVEST_RETURN_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3667},{"id":298,"name":"HARVEST_GATHER_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3666},{"id":299,"name":"HARVEST_RETURN_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3667},{"id":301,"name":"ATTACKWARPPRISM_ATTACKWARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":302,"name":"ATTACKWARPPRISM_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":303,"name":"ATTACKWARPPRISM_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":304,"name":"CANCEL_QUEUE1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":305,"name":"CANCELSLOT_QUEUE1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":306,"name":"CANCEL_QUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":307,"name":"CANCELSLOT_QUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":308,"name":"CANCEL_QUEUECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":309,"name":"CANCELSLOT_QUEUECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":312,"name":"CANCEL_QUEUEADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":313,"name":"CANCELSLOT_ADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":314,"name":"CANCEL_BUILDINPROGRESS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":315,"name":"HALT_BUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3660},{"id":316,"name":"EFFECT_REPAIR_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3685},{"id":318,"name":"TERRANBUILD_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":18,"produces_name":"COMMANDCENTER"}}},{"id":319,"name":"TERRANBUILD_SUPPLYDEPOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":19,"produces_name":"SUPPLYDEPOT"}}},{"id":320,"name":"TERRANBUILD_REFINERY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildOnUnit":{"produces":20,"produces_name":"REFINERY"}}},{"id":321,"name":"TERRANBUILD_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":21,"produces_name":"BARRACKS"}}},{"id":322,"name":"TERRANBUILD_ENGINEERINGBAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":22,"produces_name":"ENGINEERINGBAY"}}},{"id":323,"name":"TERRANBUILD_MISSILETURRET","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":23,"produces_name":"MISSILETURRET"}}},{"id":324,"name":"TERRANBUILD_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":24,"produces_name":"BUNKER"}}},{"id":326,"name":"TERRANBUILD_SENSORTOWER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":25,"produces_name":"SENSORTOWER"}}},{"id":327,"name":"TERRANBUILD_GHOSTACADEMY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":26,"produces_name":"GHOSTACADEMY"}}},{"id":328,"name":"TERRANBUILD_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":27,"produces_name":"FACTORY"}}},{"id":329,"name":"TERRANBUILD_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":28,"produces_name":"STARPORT"}}},{"id":331,"name":"TERRANBUILD_ARMORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":29,"produces_name":"ARMORY"}}},{"id":333,"name":"TERRANBUILD_FUSIONCORE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":30,"produces_name":"FUSIONCORE"}}},{"id":348,"name":"HALT_TERRANBUILD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3660},{"id":380,"name":"EFFECT_STIM_MARINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3675},{"id":382,"name":"BEHAVIOR_CLOAKON_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3676},{"id":383,"name":"BEHAVIOR_CLOAKOFF_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3677},{"id":386,"name":"MEDIVACHEAL_HEAL","cast_range":4.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":388,"name":"SIEGEMODE_SIEGEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":32,"produces_name":"SIEGETANKSIEGED"}}},{"id":390,"name":"UNSIEGE_UNSIEGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":33,"produces_name":"SIEGETANK"}}},{"id":392,"name":"BEHAVIOR_CLOAKON_BANSHEE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3676},{"id":393,"name":"BEHAVIOR_CLOAKOFF_BANSHEE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3677},{"id":394,"name":"LOAD_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":396,"name":"UNLOADALLAT_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3669},{"id":397,"name":"UNLOADUNIT_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3796},{"id":399,"name":"SCANNERSWEEP_SCAN","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":401,"name":"YAMATO_YAMATOGUN","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":403,"name":"MORPH_VIKINGASSAULTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":34,"produces_name":"VIKINGASSAULT"}}},{"id":405,"name":"MORPH_VIKINGFIGHTERMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":35,"produces_name":"VIKINGFIGHTER"}}},{"id":407,"name":"LOAD_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":408,"name":"UNLOADALL_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3664},{"id":410,"name":"UNLOADUNIT_BUNKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3796},{"id":412,"name":"COMMANDCENTERTRANSPORT_COMMANDCENTERTRANSPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":413,"name":"UNLOADALL_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3664},{"id":415,"name":"UNLOADUNIT_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3796},{"id":416,"name":"LOADALL_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3663},{"id":417,"name":"LIFT_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3679,"target":{"Morph":{"produces":36,"produces_name":"COMMANDCENTERFLYING"}}},{"id":419,"name":"LAND_COMMANDCENTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3678,"target":{"MorphPlace":{"produces":18,"produces_name":"COMMANDCENTER"}}},{"id":421,"name":"BUILD_TECHLAB_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3682,"target":{"BuildInstant":{"produces":37}}},{"id":422,"name":"BUILD_REACTOR_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3683,"target":{"BuildInstant":{"produces":38}}},{"id":451,"name":"CANCEL_BARRACKSADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":452,"name":"LIFT_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3679,"target":{"Morph":{"produces":46,"produces_name":"BARRACKSFLYING"}}},{"id":454,"name":"BUILD_TECHLAB_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3682,"target":{"BuildInstant":{"produces":39}}},{"id":455,"name":"BUILD_REACTOR_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3683,"target":{"BuildInstant":{"produces":40}}},{"id":484,"name":"CANCEL_FACTORYADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":485,"name":"LIFT_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3679,"target":{"Morph":{"produces":43,"produces_name":"FACTORYFLYING"}}},{"id":487,"name":"BUILD_TECHLAB_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3682,"target":{"BuildInstant":{"produces":41}}},{"id":488,"name":"BUILD_REACTOR_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3683,"target":{"BuildInstant":{"produces":42}}},{"id":517,"name":"CANCEL_STARPORTADDON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":518,"name":"LIFT_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3679,"target":{"Morph":{"produces":44,"produces_name":"STARPORTFLYING"}}},{"id":520,"name":"LAND_FACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3678,"target":{"MorphPlace":{"produces":27,"produces_name":"FACTORY"}}},{"id":522,"name":"LAND_STARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3678,"target":{"MorphPlace":{"produces":28,"produces_name":"STARPORT"}}},{"id":524,"name":"COMMANDCENTERTRAIN_SCV","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":45,"produces_name":"SCV"}}},{"id":554,"name":"LAND_BARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3678,"target":{"MorphPlace":{"produces":21,"produces_name":"BARRACKS"}}},{"id":556,"name":"MORPH_SUPPLYDEPOT_LOWER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":47,"produces_name":"SUPPLYDEPOTLOWERED"}}},{"id":558,"name":"MORPH_SUPPLYDEPOT_RAISE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":19,"produces_name":"SUPPLYDEPOT"}}},{"id":560,"name":"BARRACKSTRAIN_MARINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":48,"produces_name":"MARINE"}}},{"id":561,"name":"BARRACKSTRAIN_REAPER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":49,"produces_name":"REAPER"}}},{"id":562,"name":"BARRACKSTRAIN_GHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":50,"produces_name":"GHOST"}}},{"id":563,"name":"BARRACKSTRAIN_MARAUDER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":51,"produces_name":"MARAUDER"}}},{"id":590,"name":"FACTORYTRAIN_FACTORYTRAIN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":0,"produces_name":"Unknown"}}},{"id":591,"name":"FACTORYTRAIN_SIEGETANK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":33,"produces_name":"SIEGETANK"}}},{"id":594,"name":"FACTORYTRAIN_THOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":52,"produces_name":"THOR"}}},{"id":595,"name":"FACTORYTRAIN_HELLION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":53,"produces_name":"HELLION"}}},{"id":596,"name":"TRAIN_HELLBAT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":484,"produces_name":"HELLIONTANK"}}},{"id":597,"name":"TRAIN_CYCLONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":692,"produces_name":"CYCLONE"}}},{"id":614,"name":"FACTORYTRAIN_WIDOWMINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":498,"produces_name":"WIDOWMINE"}}},{"id":620,"name":"STARPORTTRAIN_MEDIVAC","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":54,"produces_name":"MEDIVAC"}}},{"id":621,"name":"STARPORTTRAIN_BANSHEE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":55,"produces_name":"BANSHEE"}}},{"id":622,"name":"STARPORTTRAIN_RAVEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":56,"produces_name":"RAVEN"}}},{"id":623,"name":"STARPORTTRAIN_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":57,"produces_name":"BATTLECRUISER"}}},{"id":624,"name":"STARPORTTRAIN_VIKINGFIGHTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":35,"produces_name":"VIKINGFIGHTER"}}},{"id":626,"name":"STARPORTTRAIN_LIBERATOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":689,"produces_name":"LIBERATOR"}}},{"id":650,"name":"RESEARCH_HISECAUTOTRACKING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":5,"upgrade_name":"HISECAUTOTRACKING"}}},{"id":651,"name":"RESEARCH_TERRANSTRUCTUREARMORUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":6,"upgrade_name":"TERRANBUILDINGARMOR"}}},{"id":652,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3698,"target":{"Research":{"upgrade":7,"upgrade_name":"TERRANINFANTRYWEAPONSLEVEL1"}}},{"id":653,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3698,"target":{"Research":{"upgrade":8,"upgrade_name":"TERRANINFANTRYWEAPONSLEVEL2"}}},{"id":654,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3698,"target":{"Research":{"upgrade":9,"upgrade_name":"TERRANINFANTRYWEAPONSLEVEL3"}}},{"id":655,"name":"RESEARCH_NEOSTEELFRAME","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":10,"upgrade_name":"NEOSTEELFRAME"}}},{"id":656,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3697,"target":{"Research":{"upgrade":11,"upgrade_name":"TERRANINFANTRYARMORSLEVEL1"}}},{"id":657,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3697,"target":{"Research":{"upgrade":12,"upgrade_name":"TERRANINFANTRYARMORSLEVEL2"}}},{"id":658,"name":"ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3697,"target":{"Research":{"upgrade":13,"upgrade_name":"TERRANINFANTRYARMORSLEVEL3"}}},{"id":710,"name":"BUILD_NUKE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":730,"name":"BARRACKSTECHLABRESEARCH_STIMPACK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":15,"upgrade_name":"STIMPACK"}}},{"id":731,"name":"RESEARCH_COMBATSHIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":16,"upgrade_name":"SHIELDWALL"}}},{"id":732,"name":"RESEARCH_CONCUSSIVESHELLS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":17,"upgrade_name":"PUNISHERGRENADES"}}},{"id":761,"name":"RESEARCH_INFERNALPREIGNITER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":19,"upgrade_name":"HIGHCAPACITYBARRELS"}}},{"id":763,"name":"FACTORYTECHLABRESEARCH_RESEARCHTRANSFORMATIONSERVOS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":98,"upgrade_name":"TRANSFORMATIONSERVOS"}}},{"id":764,"name":"RESEARCH_DRILLINGCLAWS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":122,"upgrade_name":"DRILLCLAWS"}}},{"id":765,"name":"FACTORYTECHLABRESEARCH_RESEARCHLOCKONRANGEUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":123,"upgrade_name":"CYCLONELOCKONRANGEUPGRADE"}}},{"id":766,"name":"RESEARCH_SMARTSERVOS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":289,"upgrade_name":"SMARTSERVOS"}}},{"id":767,"name":"FACTORYTECHLABRESEARCH_RESEARCHARMORPIERCINGROCKETS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":290,"upgrade_name":"ARMORPIERCINGROCKETS"}}},{"id":768,"name":"RESEARCH_CYCLONERAPIDFIRELAUNCHERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":291,"upgrade_name":"CYCLONERAPIDFIRELAUNCHERS"}}},{"id":769,"name":"RESEARCH_CYCLONELOCKONDAMAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":144,"upgrade_name":"CYCLONELOCKONDAMAGEUPGRADE"}}},{"id":770,"name":"FACTORYTECHLABRESEARCH_CYCLONERESEARCHHURRICANETHRUSTERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":296,"upgrade_name":"HURRICANETHRUSTERS"}}},{"id":790,"name":"RESEARCH_BANSHEECLOAKINGFIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":20,"upgrade_name":"BANSHEECLOAK"}}},{"id":793,"name":"RESEARCH_RAVENCORVIDREACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":22,"upgrade_name":"RAVENCORVIDREACTOR"}}},{"id":796,"name":"STARPORTTECHLABRESEARCH_RESEARCHSEEKERMISSILE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":23,"upgrade_name":"HUNTERSEEKER"}}},{"id":797,"name":"STARPORTTECHLABRESEARCH_RESEARCHDURABLEMATERIALS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":24,"upgrade_name":"DURABLEMATERIALS"}}},{"id":799,"name":"RESEARCH_BANSHEEHYPERFLIGHTROTORS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":136,"upgrade_name":"BANSHEESPEED"}}},{"id":800,"name":"STARPORTTECHLABRESEARCH_RESEARCHLIBERATORAGMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":125,"upgrade_name":"LIBERATORMORPH"}}},{"id":802,"name":"STARPORTTECHLABRESEARCH_RESEARCHRAPIDDEPLOYMENT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":137,"upgrade_name":"MEDIVACRAPIDDEPLOYMENT"}}},{"id":803,"name":"RESEARCH_RAVENRECALIBRATEDEXPLOSIVES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":138,"upgrade_name":"RAVENRECALIBRATEDEXPLOSIVES"}}},{"id":806,"name":"STARPORTTECHLABRESEARCH_RAVENRESEARCHENHANCEDMUNITIONS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":292,"upgrade_name":"RAVENENHANCEDMUNITIONS"}}},{"id":807,"name":"STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":300,"upgrade_name":"INTERFERENCEMATRIX"}}},{"id":820,"name":"RESEARCH_PERSONALCLOAKING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":25,"upgrade_name":"PERSONALCLOAKING"}}},{"id":852,"name":"ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":27,"upgrade_name":"TERRANVEHICLEARMORSLEVEL1"}}},{"id":853,"name":"ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":28,"upgrade_name":"TERRANVEHICLEARMORSLEVEL2"}}},{"id":854,"name":"ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":29,"upgrade_name":"TERRANVEHICLEARMORSLEVEL3"}}},{"id":855,"name":"ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3701,"target":{"Research":{"upgrade":30,"upgrade_name":"TERRANVEHICLEWEAPONSLEVEL1"}}},{"id":856,"name":"ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3701,"target":{"Research":{"upgrade":31,"upgrade_name":"TERRANVEHICLEWEAPONSLEVEL2"}}},{"id":857,"name":"ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3701,"target":{"Research":{"upgrade":32,"upgrade_name":"TERRANVEHICLEWEAPONSLEVEL3"}}},{"id":858,"name":"ARMORYRESEARCH_TERRANSHIPPLATINGLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":33,"upgrade_name":"TERRANSHIPARMORSLEVEL1"}}},{"id":859,"name":"ARMORYRESEARCH_TERRANSHIPPLATINGLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":34,"upgrade_name":"TERRANSHIPARMORSLEVEL2"}}},{"id":860,"name":"ARMORYRESEARCH_TERRANSHIPPLATINGLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":35,"upgrade_name":"TERRANSHIPARMORSLEVEL3"}}},{"id":861,"name":"ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3699,"target":{"Research":{"upgrade":36,"upgrade_name":"TERRANSHIPWEAPONSLEVEL1"}}},{"id":862,"name":"ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3699,"target":{"Research":{"upgrade":37,"upgrade_name":"TERRANSHIPWEAPONSLEVEL2"}}},{"id":863,"name":"ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3699,"target":{"Research":{"upgrade":38,"upgrade_name":"TERRANSHIPWEAPONSLEVEL3"}}},{"id":864,"name":"ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3700,"target":{"Research":{"upgrade":116,"upgrade_name":"TERRANVEHICLEANDSHIPARMORSLEVEL1"}}},{"id":865,"name":"ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3700,"target":{"Research":{"upgrade":117,"upgrade_name":"TERRANVEHICLEANDSHIPARMORSLEVEL2"}}},{"id":866,"name":"ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3700,"target":{"Research":{"upgrade":118,"upgrade_name":"TERRANVEHICLEANDSHIPARMORSLEVEL3"}}},{"id":880,"name":"PROTOSSBUILD_NEXUS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":59,"produces_name":"NEXUS"}}},{"id":881,"name":"PROTOSSBUILD_PYLON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":60,"produces_name":"PYLON"}}},{"id":882,"name":"PROTOSSBUILD_ASSIMILATOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildOnUnit":{"produces":61,"produces_name":"ASSIMILATOR"}}},{"id":883,"name":"PROTOSSBUILD_GATEWAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":62,"produces_name":"GATEWAY"}}},{"id":884,"name":"PROTOSSBUILD_FORGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":63,"produces_name":"FORGE"}}},{"id":885,"name":"PROTOSSBUILD_FLEETBEACON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":64,"produces_name":"FLEETBEACON"}}},{"id":886,"name":"PROTOSSBUILD_TWILIGHTCOUNCIL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":65,"produces_name":"TWILIGHTCOUNCIL"}}},{"id":887,"name":"PROTOSSBUILD_PHOTONCANNON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":66,"produces_name":"PHOTONCANNON"}}},{"id":889,"name":"PROTOSSBUILD_STARGATE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":67,"produces_name":"STARGATE"}}},{"id":890,"name":"PROTOSSBUILD_TEMPLARARCHIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":68,"produces_name":"TEMPLARARCHIVE"}}},{"id":891,"name":"PROTOSSBUILD_DARKSHRINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":69,"produces_name":"DARKSHRINE"}}},{"id":892,"name":"PROTOSSBUILD_ROBOTICSBAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":70,"produces_name":"ROBOTICSBAY"}}},{"id":893,"name":"PROTOSSBUILD_ROBOTICSFACILITY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":71,"produces_name":"ROBOTICSFACILITY"}}},{"id":894,"name":"PROTOSSBUILD_CYBERNETICSCORE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":72,"produces_name":"CYBERNETICSCORE"}}},{"id":895,"name":"BUILD_SHIELDBATTERY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":1910,"produces_name":"SHIELDBATTERY"}}},{"id":910,"name":"PROTOSSBUILD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3660},{"id":911,"name":"LOAD_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":912,"name":"UNLOADALL_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3664},{"id":913,"name":"UNLOADALLAT_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3669},{"id":914,"name":"UNLOADUNIT_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3796},{"id":916,"name":"GATEWAYTRAIN_ZEALOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":73,"produces_name":"ZEALOT"}}},{"id":917,"name":"GATEWAYTRAIN_STALKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":74,"produces_name":"STALKER"}}},{"id":919,"name":"GATEWAYTRAIN_HIGHTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":75,"produces_name":"HIGHTEMPLAR"}}},{"id":920,"name":"GATEWAYTRAIN_DARKTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":76,"produces_name":"DARKTEMPLAR"}}},{"id":921,"name":"GATEWAYTRAIN_SENTRY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":77,"produces_name":"SENTRY"}}},{"id":922,"name":"TRAIN_ADEPT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":311,"produces_name":"ADEPT"}}},{"id":946,"name":"STARGATETRAIN_PHOENIX","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":78,"produces_name":"PHOENIX"}}},{"id":948,"name":"STARGATETRAIN_CARRIER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":79,"produces_name":"CARRIER"}}},{"id":950,"name":"STARGATETRAIN_VOIDRAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":80,"produces_name":"VOIDRAY"}}},{"id":954,"name":"STARGATETRAIN_ORACLE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":495,"produces_name":"ORACLE"}}},{"id":955,"name":"STARGATETRAIN_TEMPEST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":496,"produces_name":"TEMPEST"}}},{"id":976,"name":"ROBOTICSFACILITYTRAIN_WARPPRISM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":81,"produces_name":"WARPPRISM"}}},{"id":977,"name":"ROBOTICSFACILITYTRAIN_OBSERVER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":82,"produces_name":"OBSERVER"}}},{"id":978,"name":"ROBOTICSFACILITYTRAIN_COLOSSUS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":4,"produces_name":"COLOSSUS"}}},{"id":979,"name":"ROBOTICSFACILITYTRAIN_IMMORTAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":83,"produces_name":"IMMORTAL"}}},{"id":994,"name":"TRAIN_DISRUPTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":694,"produces_name":"DISRUPTOR"}}},{"id":1006,"name":"NEXUSTRAIN_PROBE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":84,"produces_name":"PROBE"}}},{"id":1036,"name":"PSISTORM_PSISTORM","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1038,"name":"CANCEL_HANGARQUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":1039,"name":"CANCELSLOT_HANGARQUEUE5","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":1040,"name":"BROODLORDQUEUE2_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":1041,"name":"BROODLORDQUEUE2_CANCELSLOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":1042,"name":"BUILD_INTERCEPTORS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1062,"name":"FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3695,"target":{"Research":{"upgrade":39,"upgrade_name":"PROTOSSGROUNDWEAPONSLEVEL1"}}},{"id":1063,"name":"FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3695,"target":{"Research":{"upgrade":40,"upgrade_name":"PROTOSSGROUNDWEAPONSLEVEL2"}}},{"id":1064,"name":"FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3695,"target":{"Research":{"upgrade":41,"upgrade_name":"PROTOSSGROUNDWEAPONSLEVEL3"}}},{"id":1065,"name":"FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3694,"target":{"Research":{"upgrade":42,"upgrade_name":"PROTOSSGROUNDARMORSLEVEL1"}}},{"id":1066,"name":"FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3694,"target":{"Research":{"upgrade":43,"upgrade_name":"PROTOSSGROUNDARMORSLEVEL2"}}},{"id":1067,"name":"FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3694,"target":{"Research":{"upgrade":44,"upgrade_name":"PROTOSSGROUNDARMORSLEVEL3"}}},{"id":1068,"name":"FORGERESEARCH_PROTOSSSHIELDSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3696,"target":{"Research":{"upgrade":45,"upgrade_name":"PROTOSSSHIELDSLEVEL1"}}},{"id":1069,"name":"FORGERESEARCH_PROTOSSSHIELDSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3696,"target":{"Research":{"upgrade":46,"upgrade_name":"PROTOSSSHIELDSLEVEL2"}}},{"id":1070,"name":"FORGERESEARCH_PROTOSSSHIELDSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3696,"target":{"Research":{"upgrade":47,"upgrade_name":"PROTOSSSHIELDSLEVEL3"}}},{"id":1093,"name":"RESEARCH_GRAVITICBOOSTER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":48,"upgrade_name":"OBSERVERGRAVITICBOOSTER"}}},{"id":1094,"name":"RESEARCH_GRAVITICDRIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":49,"upgrade_name":"GRAVITICDRIVE"}}},{"id":1097,"name":"RESEARCH_EXTENDEDTHERMALLANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":50,"upgrade_name":"EXTENDEDTHERMALLANCE"}}},{"id":1099,"name":"ROBOTICSBAYRESEARCH_RESEARCHIMMORTALREVIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":121,"upgrade_name":"IMMORTALREVIVE"}}},{"id":1126,"name":"RESEARCH_PSISTORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":52,"upgrade_name":"PSISTORMTECH"}}},{"id":1152,"name":"ZERGBUILD_HATCHERY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":86,"produces_name":"HATCHERY"}}},{"id":1153,"name":"ZERGBUILD_CREEPTUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":87,"produces_name":"CREEPTUMOR"}}},{"id":1154,"name":"ZERGBUILD_EXTRACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildOnUnit":{"produces":88,"produces_name":"EXTRACTOR"}}},{"id":1155,"name":"ZERGBUILD_SPAWNINGPOOL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":89,"produces_name":"SPAWNINGPOOL"}}},{"id":1156,"name":"ZERGBUILD_EVOLUTIONCHAMBER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":90,"produces_name":"EVOLUTIONCHAMBER"}}},{"id":1157,"name":"ZERGBUILD_HYDRALISKDEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":91,"produces_name":"HYDRALISKDEN"}}},{"id":1158,"name":"ZERGBUILD_SPIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":92,"produces_name":"SPIRE"}}},{"id":1159,"name":"ZERGBUILD_ULTRALISKCAVERN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":93,"produces_name":"ULTRALISKCAVERN"}}},{"id":1160,"name":"ZERGBUILD_INFESTATIONPIT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":94,"produces_name":"INFESTATIONPIT"}}},{"id":1161,"name":"ZERGBUILD_NYDUSNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":95,"produces_name":"NYDUSNETWORK"}}},{"id":1162,"name":"ZERGBUILD_BANELINGNEST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":96,"produces_name":"BANELINGNEST"}}},{"id":1163,"name":"BUILD_LURKERDEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":504,"produces_name":"LURKERDENMP"}}},{"id":1165,"name":"ZERGBUILD_ROACHWARREN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":97,"produces_name":"ROACHWARREN"}}},{"id":1166,"name":"ZERGBUILD_SPINECRAWLER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":98,"produces_name":"SPINECRAWLER"}}},{"id":1167,"name":"ZERGBUILD_SPORECRAWLER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":99,"produces_name":"SPORECRAWLER"}}},{"id":1182,"name":"ZERGBUILD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3660},{"id":1183,"name":"HARVEST_GATHER_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3666},{"id":1184,"name":"HARVEST_RETURN_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3667},{"id":1186,"name":"RESEARCH_ZERGMELEEWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3705,"target":{"Research":{"upgrade":53,"upgrade_name":"ZERGMELEEWEAPONSLEVEL1"}}},{"id":1187,"name":"RESEARCH_ZERGMELEEWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3705,"target":{"Research":{"upgrade":54,"upgrade_name":"ZERGMELEEWEAPONSLEVEL2"}}},{"id":1188,"name":"RESEARCH_ZERGMELEEWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3705,"target":{"Research":{"upgrade":55,"upgrade_name":"ZERGMELEEWEAPONSLEVEL3"}}},{"id":1189,"name":"RESEARCH_ZERGGROUNDARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3704,"target":{"Research":{"upgrade":56,"upgrade_name":"ZERGGROUNDARMORSLEVEL1"}}},{"id":1190,"name":"RESEARCH_ZERGGROUNDARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3704,"target":{"Research":{"upgrade":57,"upgrade_name":"ZERGGROUNDARMORSLEVEL2"}}},{"id":1191,"name":"RESEARCH_ZERGGROUNDARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3704,"target":{"Research":{"upgrade":58,"upgrade_name":"ZERGGROUNDARMORSLEVEL3"}}},{"id":1192,"name":"RESEARCH_ZERGMISSILEWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3706,"target":{"Research":{"upgrade":59,"upgrade_name":"ZERGMISSILEWEAPONSLEVEL1"}}},{"id":1193,"name":"RESEARCH_ZERGMISSILEWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3706,"target":{"Research":{"upgrade":60,"upgrade_name":"ZERGMISSILEWEAPONSLEVEL2"}}},{"id":1194,"name":"RESEARCH_ZERGMISSILEWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3706,"target":{"Research":{"upgrade":61,"upgrade_name":"ZERGMISSILEWEAPONSLEVEL3"}}},{"id":1195,"name":"EVOLUTIONCHAMBERRESEARCH_EVOLVEPROPULSIVEPERISTALSIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":304,"upgrade_name":"SECRETEDCOATING"}}},{"id":1216,"name":"UPGRADETOLAIR_LAIR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":100,"produces_name":"LAIR"}}},{"id":1217,"name":"CANCEL_MORPHLAIR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1218,"name":"UPGRADETOHIVE_HIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":101,"produces_name":"HIVE"}}},{"id":1219,"name":"CANCEL_MORPHHIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1220,"name":"UPGRADETOGREATERSPIRE_GREATERSPIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":102,"produces_name":"GREATERSPIRE"}}},{"id":1221,"name":"CANCEL_MORPHGREATERSPIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1223,"name":"RESEARCH_PNEUMATIZEDCARAPACE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":62,"upgrade_name":"OVERLORDSPEED"}}},{"id":1224,"name":"LAIRRESEARCH_EVOLVEVENTRALSACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":63,"upgrade_name":"OVERLORDTRANSPORT"}}},{"id":1225,"name":"RESEARCH_BURROW","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":64,"upgrade_name":"BURROW"}}},{"id":1252,"name":"RESEARCH_ZERGLINGADRENALGLANDS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":65,"upgrade_name":"ZERGLINGATTACKSPEED"}}},{"id":1253,"name":"RESEARCH_ZERGLINGMETABOLICBOOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":66,"upgrade_name":"ZERGLINGMOVEMENTSPEED"}}},{"id":1282,"name":"RESEARCH_GROOVEDSPINES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":134,"upgrade_name":"EVOLVEGROOVEDSPINES"}}},{"id":1283,"name":"RESEARCH_MUSCULARAUGMENTS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":135,"upgrade_name":"EVOLVEMUSCULARAUGMENTS"}}},{"id":1284,"name":"HYDRALISKDENRESEARCH_RESEARCHFRENZY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":298,"upgrade_name":"FRENZY"}}},{"id":1312,"name":"RESEARCH_ZERGFLYERATTACKLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3703,"target":{"Research":{"upgrade":68,"upgrade_name":"ZERGFLYERWEAPONSLEVEL1"}}},{"id":1313,"name":"RESEARCH_ZERGFLYERATTACKLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3703,"target":{"Research":{"upgrade":69,"upgrade_name":"ZERGFLYERWEAPONSLEVEL2"}}},{"id":1314,"name":"RESEARCH_ZERGFLYERATTACKLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3703,"target":{"Research":{"upgrade":70,"upgrade_name":"ZERGFLYERWEAPONSLEVEL3"}}},{"id":1315,"name":"RESEARCH_ZERGFLYERARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3702,"target":{"Research":{"upgrade":71,"upgrade_name":"ZERGFLYERARMORSLEVEL1"}}},{"id":1316,"name":"RESEARCH_ZERGFLYERARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3702,"target":{"Research":{"upgrade":72,"upgrade_name":"ZERGFLYERARMORSLEVEL2"}}},{"id":1317,"name":"RESEARCH_ZERGFLYERARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3702,"target":{"Research":{"upgrade":73,"upgrade_name":"ZERGFLYERARMORSLEVEL3"}}},{"id":1342,"name":"LARVATRAIN_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":104,"produces_name":"DRONE"}}},{"id":1343,"name":"LARVATRAIN_ZERGLING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":105,"produces_name":"ZERGLING"}}},{"id":1344,"name":"LARVATRAIN_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":106,"produces_name":"OVERLORD"}}},{"id":1345,"name":"LARVATRAIN_HYDRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":107,"produces_name":"HYDRALISK"}}},{"id":1346,"name":"LARVATRAIN_MUTALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":108,"produces_name":"MUTALISK"}}},{"id":1348,"name":"LARVATRAIN_ULTRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":109,"produces_name":"ULTRALISK"}}},{"id":1351,"name":"LARVATRAIN_ROACH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":110,"produces_name":"ROACH"}}},{"id":1352,"name":"LARVATRAIN_INFESTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":111,"produces_name":"INFESTOR"}}},{"id":1353,"name":"LARVATRAIN_CORRUPTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":112,"produces_name":"CORRUPTOR"}}},{"id":1354,"name":"LARVATRAIN_VIPER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":499,"produces_name":"VIPER"}}},{"id":1356,"name":"TRAIN_SWARMHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Train":{"produces":494,"produces_name":"SWARMHOSTMP"}}},{"id":1372,"name":"MORPHTOBROODLORD_BROODLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":114,"produces_name":"BROODLORD"}}},{"id":1373,"name":"CANCEL_MORPHBROODLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1374,"name":"BURROWDOWN_BANELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":115,"produces_name":"BANELINGBURROWED"}}},{"id":1375,"name":"BURROWBANELINGDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1376,"name":"BURROWUP_BANELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":9,"produces_name":"BANELING"}}},{"id":1378,"name":"BURROWDOWN_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":116,"produces_name":"DRONEBURROWED"}}},{"id":1379,"name":"BURROWDRONEDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1380,"name":"BURROWUP_DRONE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":104,"produces_name":"DRONE"}}},{"id":1382,"name":"BURROWDOWN_HYDRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":117,"produces_name":"HYDRALISKBURROWED"}}},{"id":1383,"name":"BURROWHYDRALISKDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1384,"name":"BURROWUP_HYDRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":107,"produces_name":"HYDRALISK"}}},{"id":1386,"name":"BURROWDOWN_ROACH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":118,"produces_name":"ROACHBURROWED"}}},{"id":1387,"name":"BURROWROACHDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1388,"name":"BURROWUP_ROACH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":110,"produces_name":"ROACH"}}},{"id":1390,"name":"BURROWDOWN_ZERGLING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":119,"produces_name":"ZERGLINGBURROWED"}}},{"id":1391,"name":"BURROWZERGLINGDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1392,"name":"BURROWUP_ZERGLING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":105,"produces_name":"ZERGLING"}}},{"id":1394,"name":"BURROWDOWN_INFESTORTERRAN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":120,"produces_name":"INFESTORTERRANBURROWED"}}},{"id":1396,"name":"BURROWUP_INFESTORTERRAN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":7,"produces_name":"INFESTORTERRAN"}}},{"id":1406,"name":"LOAD_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":1408,"name":"UNLOADALLAT_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3669},{"id":1409,"name":"UNLOADUNIT_OVERLORD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3796},{"id":1411,"name":"MERGEABLE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1412,"name":"WARPABLE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1413,"name":"WARPGATETRAIN_ZEALOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":73,"produces_name":"ZEALOT"}}},{"id":1414,"name":"WARPGATETRAIN_STALKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":74,"produces_name":"STALKER"}}},{"id":1416,"name":"WARPGATETRAIN_HIGHTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":75,"produces_name":"HIGHTEMPLAR"}}},{"id":1417,"name":"WARPGATETRAIN_DARKTEMPLAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":76,"produces_name":"DARKTEMPLAR"}}},{"id":1418,"name":"WARPGATETRAIN_SENTRY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":77,"produces_name":"SENTRY"}}},{"id":1419,"name":"TRAINWARP_ADEPT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"TrainPlace":{"produces":311,"produces_name":"ADEPT"}}},{"id":1433,"name":"BURROWDOWN_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":125,"produces_name":"QUEENBURROWED"}}},{"id":1434,"name":"BURROWQUEENDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1435,"name":"BURROWUP_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":126,"produces_name":"QUEEN"}}},{"id":1437,"name":"LOAD_NYDUSNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":1438,"name":"UNLOADALL_NYDASNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3664},{"id":1440,"name":"UNLOADUNIT_NYDASNETWORK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3796},{"id":1442,"name":"EFFECT_BLINK_STALKER","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3687},{"id":1444,"name":"BURROWDOWN_INFESTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":127,"produces_name":"INFESTORBURROWED"}}},{"id":1445,"name":"BURROWINFESTORDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1446,"name":"BURROWUP_INFESTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":111,"produces_name":"INFESTOR"}}},{"id":1448,"name":"MORPH_OVERSEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":129,"produces_name":"OVERSEER"}}},{"id":1449,"name":"CANCEL_MORPHOVERSEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1450,"name":"UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":130,"produces_name":"PLANETARYFORTRESS"}}},{"id":1451,"name":"CANCEL_MORPHPLANETARYFORTRESS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1455,"name":"RESEARCH_NEURALPARASITE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":101,"upgrade_name":"NEURALPARASITE"}}},{"id":1456,"name":"INFESTATIONPITRESEARCH_RESEARCHLOCUSTLIFETIMEINCREASE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":102,"upgrade_name":"LOCUSTLIFETIMEINCREASE"}}},{"id":1457,"name":"INFESTATIONPITRESEARCH_EVOLVEAMORPHOUSARMORCLOUD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":299,"upgrade_name":"MICROBIALSHROUD"}}},{"id":1482,"name":"RESEARCH_CENTRIFUGALHOOKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":75,"upgrade_name":"CENTRIFICALHOOKS"}}},{"id":1512,"name":"BURROWDOWN_ULTRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":131,"produces_name":"ULTRALISKBURROWED"}}},{"id":1514,"name":"BURROWUP_ULTRALISK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":109,"produces_name":"ULTRALISK"}}},{"id":1516,"name":"UPGRADETOORBITAL_ORBITALCOMMAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":132,"produces_name":"ORBITALCOMMAND"}}},{"id":1517,"name":"CANCEL_MORPHORBITAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1518,"name":"MORPH_WARPGATE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":133,"produces_name":"WARPGATE"}}},{"id":1519,"name":"UPGRADETOWARPGATE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1520,"name":"MORPH_GATEWAY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":62,"produces_name":"GATEWAY"}}},{"id":1521,"name":"MORPHBACKTOGATEWAY_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1522,"name":"LIFT_ORBITALCOMMAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3679,"target":{"Morph":{"produces":134,"produces_name":"ORBITALCOMMANDFLYING"}}},{"id":1524,"name":"LAND_ORBITALCOMMAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3678,"target":{"MorphPlace":{"produces":132,"produces_name":"ORBITALCOMMAND"}}},{"id":1526,"name":"FORCEFIELD_FORCEFIELD","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1527,"name":"FORCEFIELD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1528,"name":"MORPH_WARPPRISMPHASINGMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":136,"produces_name":"WARPPRISMPHASING"}}},{"id":1529,"name":"PHASINGMODE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1530,"name":"MORPH_WARPPRISMTRANSPORTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":81,"produces_name":"WARPPRISM"}}},{"id":1531,"name":"TRANSPORTMODE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1532,"name":"RESEARCH_BATTLECRUISERWEAPONREFIT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":76,"upgrade_name":"BATTLECRUISERENABLESPECIALIZATIONS"}}},{"id":1533,"name":"FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":140,"upgrade_name":"LIBERATORAGRANGEUPGRADE"}}},{"id":1534,"name":"FUSIONCORERESEARCH_RESEARCHRAPIDREIGNITIONSYSTEM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":139,"upgrade_name":"MEDIVACINCREASESPEEDBOOST"}}},{"id":1535,"name":"FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":21,"upgrade_name":"MEDIVACCADUCEUSREACTOR"}}},{"id":1562,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3693,"target":{"Research":{"upgrade":78,"upgrade_name":"PROTOSSAIRWEAPONSLEVEL1"}}},{"id":1563,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3693,"target":{"Research":{"upgrade":79,"upgrade_name":"PROTOSSAIRWEAPONSLEVEL2"}}},{"id":1564,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3693,"target":{"Research":{"upgrade":80,"upgrade_name":"PROTOSSAIRWEAPONSLEVEL3"}}},{"id":1565,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3692,"target":{"Research":{"upgrade":81,"upgrade_name":"PROTOSSAIRARMORSLEVEL1"}}},{"id":1566,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3692,"target":{"Research":{"upgrade":82,"upgrade_name":"PROTOSSAIRARMORSLEVEL2"}}},{"id":1567,"name":"CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3692,"target":{"Research":{"upgrade":83,"upgrade_name":"PROTOSSAIRARMORSLEVEL3"}}},{"id":1568,"name":"RESEARCH_WARPGATE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":84,"upgrade_name":"WARPGATERESEARCH"}}},{"id":1571,"name":"CYBERNETICSCORERESEARCH_RESEARCHHALLUCINATION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":85,"upgrade_name":"HALTECH"}}},{"id":1592,"name":"RESEARCH_CHARGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":86,"upgrade_name":"CHARGE"}}},{"id":1593,"name":"RESEARCH_BLINK","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":87,"upgrade_name":"BLINKTECH"}}},{"id":1594,"name":"RESEARCH_ADEPTRESONATINGGLAIVES","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":130,"upgrade_name":"ADEPTPIERCINGATTACK"}}},{"id":1595,"name":"TWILIGHTCOUNCILRESEARCH_RESEARCHPSIONICSURGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":301,"upgrade_name":"SUNDERINGIMPACT"}}},{"id":1596,"name":"TWILIGHTCOUNCILRESEARCH_RESEARCHAMPLIFIEDSHIELDING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":302,"upgrade_name":"AMPLIFIEDSHIELDING"}}},{"id":1597,"name":"TWILIGHTCOUNCILRESEARCH_RESEARCHPSIONICAMPLIFIERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":303,"upgrade_name":"PSIONICAMPLIFIERS"}}},{"id":1622,"name":"TACNUKESTRIKE_NUKECALLDOWN","cast_range":12.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1623,"name":"CANCEL_NUKE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1628,"name":"EMP_EMP","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1632,"name":"TRAINQUEEN_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":126}}},{"id":1662,"name":"BURROWCREEPTUMORDOWN_BURROWDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":137,"produces_name":"CREEPTUMORBURROWED"}}},{"id":1664,"name":"TRANSFUSION_TRANSFUSION","cast_range":7.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1666,"name":"TECHLABMORPH_TECHLABMORPH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1668,"name":"BARRACKSTECHLABMORPH_TECHLABBARRACKS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1670,"name":"FACTORYTECHLABMORPH_TECHLABFACTORY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1672,"name":"STARPORTTECHLABMORPH_TECHLABSTARPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":5,"produces_name":"TECHLAB"}}},{"id":1674,"name":"REACTORMORPH_REACTORMORPH","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1676,"name":"BARRACKSREACTORMORPH_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1678,"name":"FACTORYREACTORMORPH_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1680,"name":"STARPORTREACTORMORPH_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":6,"produces_name":"REACTOR"}}},{"id":1682,"name":"ATTACK_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3674},{"id":1683,"name":"EFFECT_STIM_MARINE_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3675},{"id":1684,"name":"EFFECT_STIM_MARAUDER_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3675},{"id":1691,"name":"STOP_REDIRECT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":1692,"name":"BEHAVIOR_GENERATECREEPON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1693,"name":"BEHAVIOR_GENERATECREEPOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1694,"name":"BUILD_CREEPTUMOR_QUEEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3691,"target":{"Build":{"produces":138,"produces_name":"CREEPTUMORQUEEN"}}},{"id":1724,"name":"QUEENBUILD_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3660},{"id":1725,"name":"SPINECRAWLERUPROOT_SPINECRAWLERUPROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3681,"target":{"Morph":{"produces":139,"produces_name":"SPINECRAWLERUPROOTED"}}},{"id":1726,"name":"SPINECRAWLERUPROOT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1727,"name":"SPORECRAWLERUPROOT_SPORECRAWLERUPROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3681,"target":{"Morph":{"produces":140,"produces_name":"SPORECRAWLERUPROOTED"}}},{"id":1728,"name":"SPORECRAWLERUPROOT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1729,"name":"SPINECRAWLERROOT_SPINECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3680,"target":{"MorphPlace":{"produces":98,"produces_name":"SPINECRAWLER"}}},{"id":1730,"name":"CANCEL_SPINECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1731,"name":"SPORECRAWLERROOT_SPORECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3680,"target":{"MorphPlace":{"produces":99,"produces_name":"SPORECRAWLER"}}},{"id":1732,"name":"CANCEL_SPORECRAWLERROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1733,"name":"BUILD_CREEPTUMOR_TUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3691,"target":{"Build":{"produces":87,"produces_name":"CREEPTUMOR"}}},{"id":1763,"name":"CANCEL_CREEPTUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1764,"name":"BUILDAUTOTURRET_AUTOTURRET","cast_range":2.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":31,"produces_name":"AUTOTURRET"}}},{"id":1766,"name":"MORPH_ARCHON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":141,"produces_name":"ARCHON"}}},{"id":1767,"name":"ARCHON_WARP_TARGET","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1768,"name":"BUILD_NYDUSWORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":142,"produces_name":"NYDUSCANAL"}}},{"id":1769,"name":"BUILDNYDUSCANAL_SUMMONNYDUSCANALATTACKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":491,"produces_name":"NYDUSCANALATTACKER"}}},{"id":1798,"name":"BUILDNYDUSCANAL_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3660},{"id":1799,"name":"BROODLORDHANGAR_BROODLORDHANGAR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1819,"name":"EFFECT_CHARGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1820,"name":"TOWERCAPTURE_TOWERCAPTURE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1821,"name":"HERDINTERACT_HERD","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1825,"name":"CONTAMINATE_CONTAMINATE","cast_range":3.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1827,"name":"SHATTER_SHATTER","cast_range":0.10009765625,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1831,"name":"CANCEL_QUEUEPASIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":1832,"name":"CANCELSLOT_QUEUEPASSIVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":1833,"name":"CANCEL_QUEUEPASSIVECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3671},{"id":1834,"name":"CANCELSLOT_QUEUEPASSIVECANCELTOSELECTION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3672},{"id":1837,"name":"MORPHTOGHOSTNOVA_MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":145,"produces_name":"GHOSTNOVA"}}},{"id":1839,"name":"DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":1841,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS_MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":1842,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1843,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT_MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":1844,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1845,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT_MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":1846,"name":"MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1847,"name":"MORPH_MOTHERSHIP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":10,"produces_name":"MOTHERSHIP"}}},{"id":1848,"name":"CANCEL_MORPHMOTHERSHIP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1928,"name":"XELNAGAHEALINGSHRINE_XELNAGAHEALINGSHRINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":1930,"name":"NEXUSINVULNERABILITY_NEXUSINVULNERABILITY","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":1974,"name":"EFFECT_MASSRECALL_MOTHERSHIPCORE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3686},{"id":1978,"name":"MORPH_HELLION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":53,"produces_name":"HELLION"}}},{"id":1996,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS_MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":1997,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":1998,"name":"MORPH_HELLBAT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":484,"produces_name":"HELLIONTANK"}}},{"id":2014,"name":"BURROWDOWN_SWARMHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":493,"produces_name":"SWARMHOSTBURROWEDMP"}}},{"id":2015,"name":"MORPHTOSWARMHOSTBURROWEDMP_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2016,"name":"BURROWUP_SWARMHOST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":494,"produces_name":"SWARMHOSTMP"}}},{"id":2048,"name":"ATTACKPROTOSSBUILDING_ATTACKBUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3674},{"id":2049,"name":"ATTACKPROTOSSBUILDING_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2050,"name":"ATTACKPROTOSSBUILDING_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2057,"name":"STOP_BUILDING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":2058,"name":"STOPPROTOSSBUILDING_HOLDFIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2059,"name":"STOPPROTOSSBUILDING_CHEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2060,"name":"STOPPROTOSSBUILDING_DANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2063,"name":"BLINDINGCLOUD_BLINDINGCLOUD","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2067,"name":"EFFECT_ABDUCT","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2073,"name":"VIPERCONSUMESTRUCTURE_VIPERCONSUME","cast_range":7.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2079,"name":"TESTZERG_TESTZERG","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2080,"name":"TESTZERG_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2081,"name":"BEHAVIOR_BUILDINGATTACKON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2082,"name":"BEHAVIOR_BUILDINGATTACKOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2083,"name":"PICKUPSCRAPSMALL_PICKUPSCRAPSMALL","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2085,"name":"PICKUPSCRAPMEDIUM_PICKUPSCRAPMEDIUM","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2087,"name":"PICKUPSCRAPLARGE_PICKUPSCRAPLARGE","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2089,"name":"PICKUPPALLETGAS_PICKUPPALLETGAS","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2091,"name":"PICKUPPALLETMINERALS_PICKUPPALLETMINERALS","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2093,"name":"MASSIVEKNOCKOVER_MASSIVEKNOCKOVER","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2095,"name":"BURROWDOWN_WIDOWMINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":500,"produces_name":"WIDOWMINEBURROWED"}}},{"id":2096,"name":"WIDOWMINEBURROW_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2097,"name":"BURROWUP_WIDOWMINE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":498,"produces_name":"WIDOWMINE"}}},{"id":2099,"name":"WIDOWMINEATTACK_WIDOWMINEATTACK","cast_range":5.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2101,"name":"TORNADOMISSILE_TORNADOMISSILE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2108,"name":"BURROWDOWN_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":503,"produces_name":"LURKERMPBURROWED"}}},{"id":2109,"name":"BURROWLURKERMPDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2110,"name":"BURROWUP_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":502,"produces_name":"LURKERMP"}}},{"id":2114,"name":"HALLUCINATION_ORACLE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2116,"name":"EFFECT_MEDIVACIGNITEAFTERBURNERS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2146,"name":"ORACLEREVELATION_ORACLEREVELATION","cast_range":12.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2152,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":2153,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2154,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":2155,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2158,"name":"ULTRALISKWEAPONCOOLDOWN_ULTRALISKWEAPONCOOLDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2162,"name":"EFFECT_PHOTONOVERCHARGE","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2172,"name":"XELNAGA_CAVERNS_DOORNEOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2174,"name":"XELNAGA_CAVERNS_DOORNOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2178,"name":"XELNAGA_CAVERNS_DOORNWOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2184,"name":"XELNAGA_CAVERNS_DOORSEOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2186,"name":"XELNAGA_CAVERNS_DOORSOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2190,"name":"XELNAGA_CAVERNS_DOORSWOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2194,"name":"XELNAGA_CAVERNS_DOORWOPENED_XELNAGA_CAVERNS_DOORDEFAULTOPEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2244,"name":"EFFECT_TIMEWARP","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2262,"name":"TARSONIS_DOORN_TARSONIS_DOORN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2264,"name":"TARSONIS_DOORNLOWERED_TARSONIS_DOORNLOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2266,"name":"TARSONIS_DOORNE_TARSONIS_DOORNE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2268,"name":"TARSONIS_DOORNELOWERED_TARSONIS_DOORNELOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2270,"name":"TARSONIS_DOORE_TARSONIS_DOORE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2272,"name":"TARSONIS_DOORELOWERED_TARSONIS_DOORELOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2274,"name":"TARSONIS_DOORNW_TARSONIS_DOORNW","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2276,"name":"TARSONIS_DOORNWLOWERED_TARSONIS_DOORNWLOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2278,"name":"COMPOUNDMANSION_DOORN_COMPOUNDMANSION_DOORN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2280,"name":"COMPOUNDMANSION_DOORNLOWERED_COMPOUNDMANSION_DOORNLOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2282,"name":"COMPOUNDMANSION_DOORNE_COMPOUNDMANSION_DOORNE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2284,"name":"COMPOUNDMANSION_DOORNELOWERED_COMPOUNDMANSION_DOORNELOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2286,"name":"COMPOUNDMANSION_DOORE_COMPOUNDMANSION_DOORE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2288,"name":"COMPOUNDMANSION_DOORELOWERED_COMPOUNDMANSION_DOORELOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2290,"name":"COMPOUNDMANSION_DOORNW_COMPOUNDMANSION_DOORNW","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2292,"name":"COMPOUNDMANSION_DOORNWLOWERED_COMPOUNDMANSION_DOORNWLOWERED","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2324,"name":"CAUSTICSPRAY_CAUSTICSPRAY","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2330,"name":"MORPHTORAVAGER_RAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":688,"produces_name":"RAVAGER"}}},{"id":2331,"name":"CANCEL_MORPHRAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2332,"name":"MORPH_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":502,"produces_name":"LURKERMP"}}},{"id":2333,"name":"CANCEL_MORPHLURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2338,"name":"EFFECT_CORROSIVEBILE","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2340,"name":"BURROWDOWN_RAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":690,"produces_name":"RAVAGERBURROWED"}}},{"id":2341,"name":"BURROWRAVAGERDOWN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2342,"name":"BURROWUP_RAVAGER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":688,"produces_name":"RAVAGER"}}},{"id":2344,"name":"PURIFICATIONNOVA_PURIFICATIONNOVA","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2346,"name":"EFFECT_PURIFICATIONNOVA","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2350,"name":"LOCKON_LOCKON","cast_range":7.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2354,"name":"CANCEL_LOCKON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2358,"name":"EFFECT_TACTICALJUMP","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2362,"name":"MORPH_THORHIGHIMPACTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":691,"produces_name":"THORAP"}}},{"id":2363,"name":"THORAPMODE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2364,"name":"MORPH_THOREXPLOSIVEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":52,"produces_name":"THOR"}}},{"id":2365,"name":"CANCEL_MORPHTHOREXPLOSIVEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2370,"name":"LOAD_NYDUSWORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3668},{"id":2371,"name":"UNLOADALL_NYDUSWORM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3664},{"id":2375,"name":"BEHAVIOR_PULSARBEAMON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2376,"name":"BEHAVIOR_PULSARBEAMOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2383,"name":"LOCUSTMPFLYINGMORPHTOGROUND_LOCUSTMPFLYINGSWOOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":489,"produces_name":"LOCUSTMP"}}},{"id":2385,"name":"LOCUSTMPMORPHTOAIR_LOCUSTMPFLYINGSWOOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":693,"produces_name":"LOCUSTMPFLYING"}}},{"id":2387,"name":"EFFECT_LOCUSTSWOOP","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2389,"name":"HALLUCINATION_DISRUPTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2391,"name":"HALLUCINATION_ADEPT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2393,"name":"EFFECT_VOIDRAYPRISMATICALIGNMENT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2395,"name":"SEEKERDUMMYCHANNEL_SEEKERDUMMYCHANNEL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2469,"name":"VOIDMPIMMORTALREVIVEREBUILD_IMMORTAL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2473,"name":"ARBITERMPSTASISFIELD_ARBITERMPSTASISFIELD","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2475,"name":"ARBITERMPRECALL_ARBITERMPRECALL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2477,"name":"CORSAIRMPDISRUPTIONWEB_CORSAIRMPDISRUPTIONWEB","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2479,"name":"MORPHTOGUARDIANMP_MORPHTOGUARDIANMP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":727,"produces_name":"GUARDIANMP"}}},{"id":2480,"name":"MORPHTOGUARDIANMP_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2481,"name":"MORPHTODEVOURERMP_MORPHTODEVOURERMP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":729,"produces_name":"DEVOURERMP"}}},{"id":2482,"name":"MORPHTODEVOURERMP_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2483,"name":"DEFILERMPCONSUME_DEFILERMPCONSUME","cast_range":0.5,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2485,"name":"DEFILERMPDARKSWARM_DEFILERMPDARKSWARM","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2487,"name":"DEFILERMPPLAGUE_DEFILERMPPLAGUE","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2489,"name":"DEFILERMPBURROW_BURROWDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3661,"target":{"Morph":{"produces":730,"produces_name":"DEFILERMPBURROWED"}}},{"id":2490,"name":"DEFILERMPBURROW_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2491,"name":"DEFILERMPUNBURROW_BURROWUP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"remaps_to_ability_id":3662,"target":{"Morph":{"produces":731,"produces_name":"DEFILERMP"}}},{"id":2493,"name":"QUEENMPENSNARE_QUEENMPENSNARE","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2495,"name":"QUEENMPSPAWNBROODLINGS_QUEENMPSPAWNBROODLINGS","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2497,"name":"QUEENMPINFESTCOMMANDCENTER_QUEENMPINFESTCOMMANDCENTER","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2505,"name":"BUILD_STASISTRAP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":732,"produces_name":"ORACLESTASISTRAP"}}},{"id":2535,"name":"CANCEL_STASISTRAP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2536,"name":"ORACLESTASISTRAPACTIVATE_ACTIVATESTASISWARD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":2542,"name":"PARASITICBOMB_PARASITICBOMB","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2544,"name":"ADEPTPHASESHIFT_ADEPTPHASESHIFT","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":2548,"name":"PURIFICATIONNOVAMORPHBACK_PURIFICATIONNOVA","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":2550,"name":"BEHAVIOR_HOLDFIREON_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3688},{"id":2552,"name":"BEHAVIOR_HOLDFIREOFF_LURKER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3689},{"id":2554,"name":"LIBERATORMORPHTOAG_LIBERATORAGMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":734,"produces_name":"LIBERATORAG"}}},{"id":2556,"name":"LIBERATORMORPHTOAA_LIBERATORAAMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":689,"produces_name":"LIBERATOR"}}},{"id":2558,"name":"MORPH_LIBERATORAGMODE","cast_range":5.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":734,"produces_name":"LIBERATORAG"}}},{"id":2560,"name":"MORPH_LIBERATORAAMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":689,"produces_name":"LIBERATOR"}}},{"id":2588,"name":"KD8CHARGE_KD8CHARGE","cast_range":5.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2594,"name":"CANCEL_ADEPTPHASESHIFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2596,"name":"CANCEL_ADEPTSHADEPHASESHIFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2598,"name":"SLAYNELEMENTALGRAB_SLAYNELEMENTALGRAB","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2600,"name":"MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":2601,"name":"MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2700,"name":"EFFECT_SHADOWSTRIDE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3687},{"id":2704,"name":"EFFECT_SPAWNLOCUSTS","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":693}}},{"id":2706,"name":"LOCUSTMPFLYINGSWOOPATTACK_LOCUSTMPFLYINGSWOOP","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":2708,"name":"MORPH_OVERLORDTRANSPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":893,"produces_name":"OVERLORDTRANSPORT"}}},{"id":2709,"name":"CANCEL_MORPHOVERLORDTRANSPORT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2714,"name":"EFFECT_GHOSTSNIPE","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":2715,"name":"CHANNELSNIPE_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":2716,"name":"PURIFYMORPHPYLON_MOTHERSHIPCOREWEAPON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":894,"produces_name":"PYLONOVERCHARGED"}}},{"id":2718,"name":"PURIFYMORPHPYLONBACK_MOTHERSHIPCOREWEAPON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":2720,"name":"RESEARCH_SHADOWSTRIKE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":141,"upgrade_name":"DARKTEMPLARBLINKUPGRADE"}}},{"id":3659,"name":"CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3660,"name":"HALT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3661,"name":"BURROWDOWN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3662,"name":"BURROWUP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3663,"name":"LOADALL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3664,"name":"UNLOADALL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3665,"name":"STOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3666,"name":"HARVEST_GATHER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3667,"name":"HARVEST_RETURN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3668,"name":"LOAD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3669,"name":"UNLOADALLAT","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3671,"name":"CANCEL_LAST","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3672,"name":"CANCEL_SLOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3673,"name":"RALLY_UNITS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3674,"name":"ATTACK","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3675,"name":"EFFECT_STIM","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3676,"name":"BEHAVIOR_CLOAKON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3677,"name":"BEHAVIOR_CLOAKOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3678,"name":"LAND","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":0,"produces_name":"Unknown"}}},{"id":3679,"name":"LIFT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3680,"name":"MORPH_ROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"MorphPlace":{"produces":0,"produces_name":"Unknown"}}},{"id":3681,"name":"MORPH_UPROOT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"Unknown"}}},{"id":3682,"name":"BUILD_TECHLAB","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":5,"produces_name":"TECHLAB"}}},{"id":3683,"name":"BUILD_REACTOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"BuildInstant":{"produces":6,"produces_name":"REACTOR"}}},{"id":3684,"name":"EFFECT_SPRAY","cast_range":1.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3685,"name":"EFFECT_REPAIR","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3686,"name":"EFFECT_MASSRECALL","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3687,"name":"EFFECT_BLINK","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3688,"name":"BEHAVIOR_HOLDFIREON","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3689,"name":"BEHAVIOR_HOLDFIREOFF","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3690,"name":"RALLY_WORKERS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3691,"name":"BUILD_CREEPTUMOR","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Build":{"produces":87,"produces_name":"CREEPTUMOR"}}},{"id":3707,"name":"CANCEL_VOIDRAYPRISMATICALIGNMENT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":3709,"name":"RESEARCH_ADAPTIVETALONS","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":293,"upgrade_name":"DIGGINGCLAWS"}}},{"id":3710,"name":"LURKERDENRESEARCH_RESEARCHLURKERRANGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Research":{"upgrade":127,"upgrade_name":"LURKERRANGE"}}},{"id":3739,"name":"MORPH_OBSERVERMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":82,"produces_name":"OBSERVER"}}},{"id":3741,"name":"MORPH_SURVEILLANCEMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":1911,"produces_name":"OBSERVERSIEGEMODE"}}},{"id":3743,"name":"MORPH_OVERSIGHTMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":1912,"produces_name":"OVERSEERSIEGEMODE"}}},{"id":3745,"name":"MORPH_OVERSEERMODE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":129,"produces_name":"OVERSEER"}}},{"id":3747,"name":"EFFECT_INTERFERENCEMATRIX","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3751,"name":"EFFECT_REPAIR_REPAIRDRONE","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit","remaps_to_ability_id":3685},{"id":3753,"name":"EFFECT_ANTIARMORMISSILE","cast_range":10.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3755,"name":"EFFECT_CHRONOBOOSTENERGYCOST","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3757,"name":"EFFECT_MASSRECALL_NEXUS","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point","remaps_to_ability_id":3686},{"id":3763,"name":"INFESTORENSNARE_INFESTORENSNARE","cast_range":8.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3771,"name":"ATTACK_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3674},{"id":3772,"name":"BATTLECRUISERATTACK_ATTACKTOWARDS","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3773,"name":"BATTLECRUISERATTACK_ATTACKBARRAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3776,"name":"MOVE_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3794},{"id":3777,"name":"PATROL_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit","remaps_to_ability_id":3795},{"id":3778,"name":"HOLDPOSITION_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3793},{"id":3779,"name":"BATTLECRUISERMOVE_ACQUIREMOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3780,"name":"BATTLECRUISERMOVE_TURN","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3783,"name":"STOP_BATTLECRUISER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3665},{"id":3784,"name":"BATTLECRUISERSTOP_HOLDFIRE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3785,"name":"BATTLECRUISERSTOP_CHEER","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3786,"name":"BATTLECRUISERSTOP_DANCE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3789,"name":"VIPERPARASITICBOMBRELAY_PARASITICBOMB","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":3791,"name":"PARASITICBOMBRELAYDODGE_PARASITICBOMB","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":3793,"name":"HOLDPOSITION","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3794,"name":"MOVE","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3795,"name":"PATROL","cast_range":0.0,"energy_cost":0,"allow_minimap":true,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"PointOrUnit"},{"id":3796,"name":"UNLOADUNIT","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":3966,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":3967,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":3969,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":3970,"name":"MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":4109,"name":"HYDRALISKFRENZY_HYDRALISKFRENZY","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":4111,"name":"AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD","cast_range":9.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Point"},{"id":4113,"name":"SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE","cast_range":6.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":true,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":4114,"name":"SHIELDBATTERYRECHARGEEX5_STOP","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":4121,"name":"MORPHTOBANELING_BANELING","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":{"Morph":{"produces":0,"produces_name":"NOTAUNIT"}}},{"id":4122,"name":"MORPHTOBANELING_CANCEL","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None","remaps_to_ability_id":3659},{"id":4124,"name":"MOTHERSHIPCLOAK_ORACLECLOAKFIELD","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":4126,"name":"ENERGYRECHARGE_ENERGYRECHARGE","cast_range":500.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"},{"id":4128,"name":"SALVAGEEFFECT_SALVAGE","cast_range":0.0,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"None"},{"id":4132,"name":"WORKERSTOPIDLEABILITYVESPENE_GATHER","cast_range":0.300048828125,"energy_cost":0,"allow_minimap":false,"allow_autocast":false,"effect":[],"buff":[],"cooldown":0,"target":"Unit"}],"Unit":[{"id":4,"name":"Colossus","race":"Protoss","supply":6.0,"cargo_size":8,"max_health":250.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":1200.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":1.5,"bonuses":[{"against":"Light","damage":5.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":5,"name":"TechLab","race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[{"ability":730,"requirements":[{"addon_to":21}]},{"ability":731,"requirements":[{"addon_to":21}]},{"ability":732,"requirements":[{"addon_to":21}]},{"ability":761,"requirements":[{"addon_to":27}]},{"ability":764,"requirements":[{"addon_to":27}]},{"ability":793,"requirements":[{"addon_to":28}]},{"ability":790,"requirements":[{"addon_to":28}]}],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":2.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":6,"name":"Reactor","race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":2.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":7,"name":"InfestorTerran","race":"Zerg","supply":0.0,"max_health":75.0,"armor":0.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":78.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":24.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.330078125,"bonuses":[]},{"target_type":"Ground","damage_per_hit":12.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1394}]},{"id":8,"name":"BanelingCocoon","race":"Zerg","supply":0.5,"max_health":50.0,"armor":2.0,"sight":5.0,"speed":2.5,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":9,"name":"Baneling","race":"Zerg","supply":0.5,"cargo_size":2,"max_health":30.0,"armor":0.0,"sight":8.0,"speed":2.5,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":320.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":42},{"ability":2081},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1374}]},{"id":10,"name":"Mothership","race":"Protoss","supply":8.0,"max_health":350.0,"armor":2.0,"sight":14.0,"speed":2.015625,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Psionic","Massive","Heroic"],"size":0,"radius":1.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":400,"gas":400,"time":2000.0,"tech_alias":[],"unit_alias":0,"max_shield":350.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":4,"range":7.0,"cooldown":2.2099609375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":142},{"ability":2244},{"ability":4124},{"ability":1}]},{"id":11,"name":"PointDefenseDrone","race":"Terran","supply":0.0,"max_health":50.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":200,"weapons":[],"attributes":["Light","Mechanical","Structure"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":12,"name":"Changeling","race":"Zerg","supply":0.0,"max_health":5.0,"armor":0.0,"sight":8.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":13,"name":"ChangelingZealot","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"tech_alias":[],"unit_alias":12,"max_shield":50.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":2,"range":0.10009765625,"cooldown":1.199951171875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":14,"name":"ChangelingMarineShield","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":55.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"tech_alias":[],"unit_alias":12,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":15,"name":"ChangelingMarine","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":45.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"tech_alias":[],"unit_alias":12,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":16,"name":"ChangelingZerglingWings","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":35.0,"armor":0.0,"sight":8.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"tech_alias":[],"unit_alias":12,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.696044921875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":17,"name":"ChangelingZergling","normal_mode":12,"race":"Zerg","supply":0.0,"max_health":35.0,"armor":0.0,"sight":8.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"tech_alias":[],"unit_alias":12,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.696044921875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":18,"name":"CommandCenter","race":"Terran","supply":-15.0,"cargo_capacity":5,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":400,"gas":0,"time":1600.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":203},{"ability":416},{"ability":417},{"ability":524},{"ability":1},{"requirements":[{"building":22}],"ability":1450},{"requirements":[{"building":21}],"ability":1516}]},{"id":19,"name":"SupplyDepot","race":"Terran","supply":-8.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":556}]},{"id":20,"name":"Refinery","race":"Terran","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":21,"name":"Barracks","race":"Terran","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":true,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":1040.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":421},{"ability":422},{"ability":452},{"ability":560},{"ability":561},{"ability":1},{"requirements":[{"building":26,"addon":5}],"ability":562},{"requirements":[{"addon":5}],"ability":563}]},{"id":22,"name":"EngineeringBay","race":"Terran","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":560.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":650},{"ability":651},{"ability":652},{"ability":656},{"ability":653,"requirements":[{"upgrade":7},{"building":29}]},{"ability":654,"requirements":[{"upgrade":8},{"building":29}]},{"ability":657,"requirements":[{"upgrade":11},{"building":29}]},{"ability":658,"requirements":[{"upgrade":12},{"building":29}]}]},{"id":23,"name":"MissileTurret","race":"Terran","supply":0.0,"max_health":250.0,"armor":0.0,"sight":11.0,"detection_range":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":400.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":12.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":24,"name":"Bunker","race":"Terran","supply":0.0,"cargo_capacity":4,"max_health":400.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":407},{"ability":4128},{"ability":1}]},{"id":25,"name":"SensorTower","race":"Terran","supply":0.0,"max_health":200.0,"armor":0.0,"sight":12.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":50,"time":400.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4128}]},{"id":26,"name":"GhostAcademy","race":"Terran","supply":0.0,"max_health":1250.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":820},{"requirements":[{"building":27}],"ability":710}]},{"id":27,"name":"Factory","race":"Terran","supply":0.0,"max_health":1250.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":true,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":960.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":454},{"ability":455},{"ability":485},{"ability":595},{"ability":614},{"ability":1},{"requirements":[{"addon":5}],"ability":591},{"requirements":[{"addon":5},{"building":29}],"ability":594},{"requirements":[{"building":29}],"ability":596},{"requirements":[{"addon":5}],"ability":597}]},{"id":28,"name":"Starport","race":"Terran","supply":0.0,"max_health":1300.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":true,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":800.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":487},{"ability":488},{"ability":518},{"ability":620},{"ability":624},{"ability":626},{"ability":1},{"requirements":[{"addon":5}],"ability":621},{"requirements":[{"addon":5}],"ability":622},{"requirements":[{"addon":5},{"building":30}],"ability":623}]},{"id":29,"name":"Armory","race":"Terran","supply":0.0,"max_health":750.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":1040.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":855},{"ability":861},{"ability":864},{"ability":856,"requirements":[{"upgrade":30}]},{"ability":857,"requirements":[{"upgrade":31}]},{"ability":862,"requirements":[{"upgrade":36}]},{"ability":863,"requirements":[{"upgrade":37}]},{"ability":865,"requirements":[{"upgrade":116}]},{"ability":866,"requirements":[{"upgrade":117}]}]},{"id":30,"name":"FusionCore","race":"Terran","supply":0.0,"max_health":750.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1040.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1532},{"ability":1533},{"ability":1535}]},{"id":31,"name":"AutoTurret","race":"Terran","supply":0.0,"max_health":100.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":16.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":18.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":0.800048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":32,"name":"SiegeTankSieged","normal_mode":33,"race":"Terran","supply":3.0,"max_health":175.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":68.66796875,"tech_alias":[33],"unit_alias":33,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":40.0,"damage_splash":0,"attacks":1,"range":13.0,"cooldown":3.0,"bonuses":[{"against":"Armored","damage":30.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":390},{"ability":1}]},{"id":33,"name":"SiegeTank","race":"Terran","supply":3.0,"cargo_size":4,"max_health":175.0,"armor":1.0,"sight":11.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":720.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":15.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.0400390625,"bonuses":[{"against":"Armored","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":388},{"ability":1}]},{"id":34,"name":"VikingAssault","normal_mode":35,"race":"Terran","supply":2.0,"cargo_size":2,"max_health":135.0,"armor":0.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":41.44140625,"tech_alias":[1940],"unit_alias":35,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":12.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.0,"bonuses":[{"against":"Mechanical","damage":8.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":405},{"ability":1}]},{"id":35,"name":"VikingFighter","race":"Terran","supply":2.0,"max_health":135.0,"armor":0.0,"sight":10.0,"speed":2.75,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":672.0,"tech_alias":[1940],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":10.0,"damage_splash":0,"attacks":2,"range":9.0,"cooldown":2.0,"bonuses":[{"against":"Armored","damage":4.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":403},{"ability":1}]},{"id":36,"name":"CommandCenterFlying","normal_mode":18,"race":"Terran","supply":-15.0,"cargo_capacity":5,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":400,"gas":0,"time":32.0,"tech_alias":[18],"unit_alias":18,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":416},{"ability":419},{"ability":1}]},{"id":37,"name":"BarracksTechLab","normal_mode":5,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":400.0,"tech_alias":[5],"unit_alias":0,"is_flying":false,"abilities":[{"ability":730},{"ability":731},{"ability":732}]},{"id":38,"name":"BarracksReactor","normal_mode":6,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":800.0,"tech_alias":[6],"unit_alias":0,"is_flying":false},{"id":39,"name":"FactoryTechLab","normal_mode":5,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":400.0,"tech_alias":[5],"unit_alias":0,"is_flying":false,"abilities":[{"ability":761},{"ability":769},{"ability":764,"requirements":[{"building":29}]},{"ability":766,"requirements":[{"building":29}]}]},{"id":40,"name":"FactoryReactor","normal_mode":6,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":800.0,"tech_alias":[6],"unit_alias":0,"is_flying":false},{"id":41,"name":"StarportTechLab","normal_mode":5,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":400.0,"tech_alias":[5],"unit_alias":0,"is_flying":false,"abilities":[{"ability":790},{"ability":799},{"ability":807}]},{"id":42,"name":"StarportReactor","normal_mode":6,"race":"Terran","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":true,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":800.0,"tech_alias":[6],"unit_alias":0,"is_flying":false},{"id":43,"name":"FactoryFlying","normal_mode":27,"race":"Terran","supply":0.0,"max_health":1250.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":32.0,"tech_alias":[27],"unit_alias":27,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":454},{"ability":455},{"ability":520},{"ability":1}]},{"id":44,"name":"StarportFlying","normal_mode":28,"race":"Terran","supply":0.0,"max_health":1300.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":32.0,"tech_alias":[28],"unit_alias":28,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":487},{"ability":488},{"ability":522},{"ability":1}]},{"id":45,"name":"SCV","race":"Terran","supply":1.0,"cargo_size":1,"max_health":45.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Biological","Mechanical"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":true,"is_townhall":false,"minerals":50,"gas":0,"time":272.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.199951171875,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":26},{"ability":295},{"ability":316},{"ability":318},{"ability":319},{"ability":320},{"ability":1},{"requirements":[{"building":19}],"ability":321},{"requirements":[{"building":18}],"ability":322},{"requirements":[{"building":22}],"ability":323},{"requirements":[{"building":21}],"ability":324},{"requirements":[{"building":22}],"ability":326},{"requirements":[{"building":21}],"ability":327},{"requirements":[{"building":21}],"ability":328},{"requirements":[{"building":27}],"ability":329},{"requirements":[{"building":27}],"ability":331},{"requirements":[{"building":28}],"ability":333}]},{"id":46,"name":"BarracksFlying","normal_mode":21,"race":"Terran","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":32.0,"tech_alias":[21],"unit_alias":21,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":421},{"ability":422},{"ability":554},{"ability":1}]},{"id":47,"name":"SupplyDepotLowered","normal_mode":19,"race":"Terran","supply":-8.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":20.80078125,"tech_alias":[19],"unit_alias":19,"is_flying":false,"abilities":[{"ability":558}]},{"id":48,"name":"Marine","race":"Terran","supply":1.0,"cargo_size":1,"max_health":45.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":400.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":6.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.86083984375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":15}],"ability":380}]},{"id":49,"name":"Reaper","race":"Terran","supply":1.0,"cargo_size":1,"max_health":60.0,"armor":0.0,"sight":9.0,"speed":3.75,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":50,"time":720.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.10009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2588},{"ability":1}]},{"id":50,"name":"Ghost","race":"Terran","supply":3.0,"cargo_size":2,"max_health":100.0,"armor":0.0,"sight":11.0,"speed":2.8125,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":75,"attributes":["Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[{"against":"Light","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":36},{"ability":1628},{"ability":2714},{"ability":1},{"requirements":[{"upgrade":25}],"ability":382}]},{"id":51,"name":"Marauder","race":"Terran","supply":2.0,"cargo_size":2,"max_health":125.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.5625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":25,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[{"against":"Armored","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":15}],"ability":253}]},{"id":52,"name":"Thor","race":"Terran","supply":6.0,"cargo_size":8,"max_health":400.0,"armor":1.0,"sight":11.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":960.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":6.0,"damage_splash":0,"attacks":4,"range":10.0,"cooldown":3.0,"bonuses":[{"against":"Light","damage":6.0}]},{"target_type":"Ground","damage_per_hit":30.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":1.280029296875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2362},{"ability":1}]},{"id":53,"name":"Hellion","race":"Terran","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":10.0,"speed":4.25,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":2.5,"bonuses":[{"against":"Light","damage":6.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"building":29}],"ability":1998}]},{"id":54,"name":"Medivac","race":"Terran","supply":2.0,"cargo_capacity":8,"max_health":150.0,"armor":1.0,"sight":11.0,"speed":2.5,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":672.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":386},{"ability":394},{"ability":2116},{"ability":1}]},{"id":55,"name":"Banshee","race":"Terran","supply":3.0,"max_health":140.0,"armor":0.0,"sight":10.0,"speed":2.75,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Light","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":960.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":12.0,"damage_splash":0,"attacks":2,"range":6.0,"cooldown":1.25,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":20}],"ability":392}]},{"id":56,"name":"Raven","race":"Terran","supply":2.0,"max_health":140.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed":2.94921875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":150,"time":768.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1764},{"ability":3753},{"ability":1},{"requirements":[{"upgrade":299}],"ability":3747}]},{"id":57,"name":"Battlecruiser","race":"Terran","supply":6.0,"max_health":550.0,"armor":3.0,"sight":12.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":400,"gas":300,"time":1440.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":2358},{"ability":3771},{"ability":3776},{"ability":3777},{"ability":3778},{"ability":3783},{"ability":1},{"requirements":[{"upgrade":76}],"ability":401}]},{"id":58,"name":"Nuke","race":"Terran","supply":0.0,"max_health":100.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":59,"name":"Nexus","race":"Protoss","supply":-15.0,"max_health":1000.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":400,"gas":0,"time":1600.0,"tech_alias":[],"unit_alias":0,"max_shield":1000.0,"is_flying":false,"abilities":[{"ability":207},{"ability":1006},{"ability":3755},{"ability":3757},{"ability":4126},{"ability":1},{"requirements":[{"building":64}],"ability":110}]},{"id":60,"name":"Pylon","race":"Protoss","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":400.0,"tech_alias":[],"unit_alias":0,"max_shield":200.0,"is_flying":false},{"id":61,"name":"Assimilator","race":"Protoss","supply":0.0,"max_health":300.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"max_shield":300.0,"is_flying":false},{"id":62,"name":"Gateway","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":1040.0,"tech_alias":[],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":195},{"ability":916},{"ability":1},{"requirements":[{"building":72}],"ability":917},{"requirements":[{"building":68}],"ability":919},{"requirements":[{"building":69}],"ability":920},{"requirements":[{"building":72}],"ability":921},{"requirements":[{"building":72}],"ability":922},{"requirements":[{"upgrade":84}],"ability":1518}]},{"id":63,"name":"Forge","race":"Protoss","supply":0.0,"max_health":400.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":720.0,"tech_alias":[],"unit_alias":0,"max_shield":400.0,"is_flying":false,"abilities":[{"ability":1062},{"ability":1065},{"ability":1068},{"ability":1063,"requirements":[{"upgrade":39},{"building":65}]},{"ability":1064,"requirements":[{"upgrade":40},{"building":65}]},{"ability":1066,"requirements":[{"upgrade":42},{"building":65}]},{"ability":1067,"requirements":[{"upgrade":43},{"building":65}]},{"ability":1069,"requirements":[{"upgrade":45},{"building":65}]},{"ability":1070,"requirements":[{"upgrade":46},{"building":65}]}]},{"id":64,"name":"FleetBeacon","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":960.0,"tech_alias":[],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":46},{"ability":48},{"ability":49}]},{"id":65,"name":"TwilightCouncil","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":800.0,"tech_alias":[],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1592},{"ability":1593},{"ability":1594}]},{"id":66,"name":"PhotonCannon","race":"Protoss","supply":0.0,"max_health":150.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Structure"],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":640.0,"tech_alias":[],"unit_alias":0,"max_shield":150.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.25,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":67,"name":"Stargate","race":"Protoss","supply":0.0,"max_health":600.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":960.0,"tech_alias":[],"unit_alias":0,"max_shield":600.0,"is_flying":false,"abilities":[{"ability":195},{"ability":946},{"ability":950},{"ability":954},{"ability":1},{"requirements":[{"building":64}],"ability":948},{"requirements":[{"building":64}],"ability":955}]},{"id":68,"name":"TemplarArchive","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":800.0,"tech_alias":[],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1126}]},{"id":69,"name":"DarkShrine","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.5,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1600.0,"tech_alias":[],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":2720}]},{"id":70,"name":"RoboticsBay","race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1040.0,"tech_alias":[],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1093},{"ability":1094},{"ability":1097}]},{"id":71,"name":"RoboticsFacility","race":"Protoss","supply":0.0,"max_health":450.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":1040.0,"tech_alias":[],"unit_alias":0,"max_shield":450.0,"is_flying":false,"abilities":[{"ability":195},{"ability":976},{"ability":977},{"ability":979},{"ability":1},{"requirements":[{"building":70}],"ability":978},{"requirements":[{"building":70}],"ability":994}]},{"id":72,"name":"CyberneticsCore","race":"Protoss","supply":0.0,"max_health":550.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":800.0,"tech_alias":[],"unit_alias":0,"max_shield":550.0,"is_flying":false,"abilities":[{"ability":1562},{"ability":1565},{"ability":1568},{"ability":1563,"requirements":[{"upgrade":78},{"building":64}]},{"ability":1564,"requirements":[{"upgrade":79},{"building":64}]},{"ability":1566,"requirements":[{"upgrade":81},{"building":64}]},{"ability":1567,"requirements":[{"upgrade":82},{"building":64}]}]},{"id":73,"name":"Zealot","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":608.0,"tech_alias":[],"unit_alias":0,"max_shield":50.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":2,"range":0.10009765625,"cooldown":1.199951171875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":86}],"ability":1819}]},{"id":74,"name":"Stalker","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":10.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":50,"time":608.0,"tech_alias":[],"unit_alias":0,"max_shield":80.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":13.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.8701171875,"bonuses":[{"against":"Armored","damage":5.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":87}],"ability":1442}]},{"id":75,"name":"HighTemplar","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":40.0,"armor":0.0,"sight":10.0,"speed":2.015625,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Light","Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":150,"time":880.0,"tech_alias":[],"unit_alias":0,"max_shield":40.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.75390625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":140},{"ability":1},{"requirements":[{"upgrade":52}],"ability":1036},{"ability":1766}]},{"id":76,"name":"DarkTemplar","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":40.0,"armor":1.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":125,"time":880.0,"tech_alias":[],"unit_alias":0,"max_shield":80.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":45.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":1.694091796875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":141}],"ability":2700},{"ability":1766}]},{"id":77,"name":"Sentry","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":40.0,"armor":1.0,"sight":10.0,"speed":2.5,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Mechanical","Psionic"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":100,"time":512.0,"tech_alias":[],"unit_alias":0,"max_shield":40.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":76},{"ability":146},{"ability":148},{"ability":150},{"ability":152},{"ability":154},{"ability":156},{"ability":158},{"ability":160},{"ability":162},{"ability":164},{"ability":1526},{"ability":2114},{"ability":2389},{"ability":2391},{"ability":1}]},{"id":78,"name":"Phoenix","race":"Protoss","supply":2.0,"max_health":120.0,"armor":0.0,"sight":10.0,"speed":4.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Light","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":560.0,"tech_alias":[],"unit_alias":0,"max_shield":60.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":5.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.10009765625,"bonuses":[{"against":"Light","damage":5.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":173},{"ability":1}]},{"id":79,"name":"Carrier","race":"Protoss","supply":6.0,"max_health":300.0,"armor":2.0,"sight":12.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":350,"gas":250,"time":1440.0,"tech_alias":[],"unit_alias":0,"max_shield":150.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1038},{"ability":1042},{"ability":1}]},{"id":80,"name":"VoidRay","race":"Protoss","supply":4.0,"max_health":150.0,"armor":0.0,"sight":10.0,"speed":2.75,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":150,"time":963.19921875,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2393},{"ability":1}]},{"id":81,"name":"WarpPrism","race":"Protoss","supply":2.0,"cargo_capacity":8,"max_health":80.0,"armor":0.0,"sight":10.0,"speed":2.953125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":0,"time":800.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":911},{"ability":1528},{"ability":1}]},{"id":82,"name":"Observer","race":"Protoss","supply":1.0,"max_health":40.0,"armor":0.0,"sight":11.0,"detection_range":11.0,"speed":2.015625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":75,"time":400.0,"tech_alias":[],"unit_alias":0,"max_shield":30.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":3741},{"ability":1}]},{"id":83,"name":"Immortal","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":200.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":100,"time":880.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.60009765625,"bonuses":[{"against":"Armored","damage":30.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":84,"name":"Probe","race":"Protoss","supply":1.0,"cargo_size":1,"max_health":20.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":true,"is_townhall":false,"minerals":50,"gas":0,"time":272.0,"tech_alias":[],"unit_alias":0,"max_shield":20.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.199951171875,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":30},{"ability":298},{"ability":880},{"ability":881},{"ability":882},{"ability":1},{"requirements":[{"building":60}],"ability":883},{"requirements":[{"building":60}],"ability":884},{"requirements":[{"building":67}],"ability":885},{"requirements":[{"building":72}],"ability":886},{"requirements":[{"building":63}],"ability":887},{"requirements":[{"building":72}],"ability":889},{"requirements":[{"building":65}],"ability":890},{"requirements":[{"building":65}],"ability":891},{"requirements":[{"building":71}],"ability":892},{"requirements":[{"building":72}],"ability":893},{"requirements":[{"building":62}],"ability":894},{"requirements":[{"building":72}],"ability":895}]},{"id":85,"name":"Interceptor","race":"Protoss","supply":0.0,"max_health":40.0,"armor":0.0,"sight":7.0,"speed":7.5,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.25,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":15,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":40.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":5.0,"damage_splash":0,"attacks":2,"range":2.0,"cooldown":3.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":86,"name":"Hatchery","race":"Zerg","supply":-6.0,"max_health":1500.0,"armor":1.0,"sight":12.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":325,"gas":0,"time":1600.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":211},{"ability":212},{"ability":1223},{"ability":1225},{"ability":1},{"requirements":[{"building":89}],"ability":1216},{"requirements":[{"building":89}],"ability":1632}]},{"id":87,"name":"CreepTumor","race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological","Structure"],"abilities":[],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":240.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":88,"name":"Extractor","race":"Zerg","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":89,"name":"SpawningPool","race":"Zerg","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":0,"time":1040.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1253},{"ability":1252,"requirements":[{"building":101}]}]},{"id":90,"name":"EvolutionChamber","race":"Zerg","supply":0.0,"max_health":750.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":560.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1186},{"ability":1189},{"ability":1192},{"ability":1187,"requirements":[{"upgrade":53},{"building":100}]},{"ability":1188,"requirements":[{"upgrade":54},{"building":101}]},{"ability":1190,"requirements":[{"upgrade":56},{"building":100}]},{"ability":1191,"requirements":[{"upgrade":57},{"building":101}]},{"ability":1193,"requirements":[{"upgrade":59},{"building":100}]},{"ability":1194,"requirements":[{"upgrade":60},{"building":101}]}]},{"id":91,"name":"HydraliskDen","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1282},{"ability":1283},{"requirements":[{"building":101}],"ability":1284}]},{"id":92,"name":"Spire","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":200,"time":1600.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1312},{"ability":1315},{"requirements":[{"building":101}],"ability":1220},{"ability":1313,"requirements":[{"upgrade":68},{"building":100}]},{"ability":1314,"requirements":[{"upgrade":69},{"building":101}]},{"ability":1316,"requirements":[{"upgrade":71},{"building":100}]},{"ability":1317,"requirements":[{"upgrade":72},{"building":101}]}]},{"id":93,"name":"UltraliskCavern","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":200,"time":1040.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":263},{"ability":265}]},{"id":94,"name":"InfestationPit","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":800.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1455}]},{"id":95,"name":"NydusNetwork","race":"Zerg","supply":0.0,"cargo_capacity":1020,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":150,"time":800.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":195},{"ability":1437},{"ability":1768},{"ability":1}]},{"id":96,"name":"BanelingNest","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[{"ability":1482,"requirements":[{"building":100}]}],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":960.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":97,"name":"RoachWarren","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[{"ability":216,"requirements":[{"building":100}]},{"ability":217,"requirements":[{"building":100}]}],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":0,"time":880.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":98,"name":"SpineCrawler","race":"Zerg","supply":0.0,"max_health":300.0,"armor":2.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":800.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.85009765625,"bonuses":[{"against":"Armored","damage":5.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":1725},{"ability":1}]},{"id":99,"name":"SporeCrawler","race":"Zerg","supply":0.0,"max_health":300.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Structure"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":0.86083984375,"bonuses":[{"against":"Biological","damage":10.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":1727},{"ability":1}]},{"id":100,"name":"Lair","normal_mode":86,"race":"Zerg","supply":-6.0,"max_health":2000.0,"armor":1.0,"sight":12.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":475,"gas":100,"time":1280.0,"tech_alias":[86],"unit_alias":0,"is_flying":false,"abilities":[{"ability":211},{"ability":212},{"ability":1223},{"ability":1225},{"ability":1},{"requirements":[{"building":94}],"ability":1218},{"requirements":[{"building":89}],"ability":1632}]},{"id":101,"name":"Hive","normal_mode":86,"race":"Zerg","supply":-6.0,"max_health":2500.0,"armor":1.0,"sight":12.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":675,"gas":250,"time":1600.0,"tech_alias":[86,100],"unit_alias":0,"is_flying":false,"abilities":[{"ability":211},{"ability":212},{"ability":1223},{"ability":1225},{"ability":1},{"requirements":[{"building":89}],"ability":1632}]},{"id":102,"name":"GreaterSpire","normal_mode":92,"race":"Zerg","supply":0.0,"max_health":1000.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":350,"gas":350,"time":1600.0,"tech_alias":[92],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1312},{"ability":1315},{"ability":1313,"requirements":[{"upgrade":68},{"building":100}]},{"ability":1314,"requirements":[{"upgrade":69},{"building":101}]},{"ability":1316,"requirements":[{"upgrade":71},{"building":100}]},{"ability":1317,"requirements":[{"upgrade":72},{"building":101}]}]},{"id":103,"name":"Egg","race":"Zerg","supply":0.0,"max_health":200.0,"armor":10.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":104,"name":"Drone","race":"Zerg","supply":1.0,"cargo_size":1,"max_health":40.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":true,"is_townhall":false,"minerals":50,"gas":0,"time":272.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.199951171875,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":28},{"ability":1152},{"ability":1154},{"ability":1183},{"ability":1},{"requirements":[{"building":86}],"ability":1155},{"requirements":[{"building":86}],"ability":1156},{"requirements":[{"building":100}],"ability":1157},{"requirements":[{"building":100}],"ability":1158},{"requirements":[{"building":101}],"ability":1159},{"requirements":[{"building":100}],"ability":1160},{"requirements":[{"building":100}],"ability":1161},{"requirements":[{"building":89}],"ability":1162},{"requirements":[{"building":91}],"ability":1163},{"requirements":[{"building":89}],"ability":1165},{"requirements":[{"building":89}],"ability":1166},{"requirements":[{"building":89}],"ability":1167},{"requirements":[{"upgrade":64}],"ability":1378}]},{"id":105,"name":"Zergling","race":"Zerg","supply":0.5,"cargo_size":1,"max_health":35.0,"armor":0.0,"sight":8.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":0,"time":384.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.696044921875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1390},{"requirements":[{"building":96}],"ability":4121}]},{"id":106,"name":"Overlord","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":0.0,"sight":11.0,"speed":0.64453125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":400.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1},{"requirements":[{"building":100}],"ability":1448},{"requirements":[{"building":100}],"ability":1692},{"requirements":[{"building":100}],"ability":2708}]},{"id":107,"name":"Hydralisk","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":50,"time":528.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":12.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.824951171875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1382},{"requirements":[{"building":504}],"ability":2332},{"requirements":[{"upgrade":298}],"ability":4109}]},{"id":108,"name":"Mutalisk","race":"Zerg","supply":2.0,"max_health":120.0,"armor":0.0,"sight":11.0,"speed":4.0,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":528.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":9.0,"damage_splash":0,"attacks":1,"range":3.0,"cooldown":1.524658203125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":109,"name":"Ultralisk","race":"Zerg","supply":6.0,"cargo_size":8,"max_health":500.0,"armor":2.0,"sight":9.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":275,"gas":200,"time":880.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":35.0,"damage_splash":0,"attacks":1,"range":1.0,"cooldown":0.860107421875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1512}]},{"id":110,"name":"Roach","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":145.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":432.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":16.0,"damage_splash":0,"attacks":1,"range":4.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1386},{"requirements":[{"building":86}],"ability":2330}]},{"id":111,"name":"Infestor","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":75,"weapons":[],"attributes":["Armored","Biological","Psionic"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":150,"time":800.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":74},{"ability":4111},{"ability":1},{"requirements":[{"upgrade":101}],"ability":249},{"requirements":[{"upgrade":64}],"ability":1394},{"requirements":[{"upgrade":64}],"ability":1444}]},{"id":112,"name":"Corruptor","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":10.0,"speed":3.375,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":14.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.89990234375,"bonuses":[{"against":"Massive","damage":6.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2324},{"ability":1},{"requirements":[{"building":102}],"ability":1372}]},{"id":113,"name":"BroodLordCocoon","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.40625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological","Massive"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":250,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":114,"name":"BroodLord","race":"Zerg","supply":4.0,"max_health":225.0,"armor":1.0,"sight":12.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":250,"time":541.34765625,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":10.0,"cooldown":2.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":115,"name":"BanelingBurrowed","normal_mode":9,"race":"Zerg","supply":0.5,"max_health":30.0,"armor":0.0,"sight":8.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":25,"time":18.962890625,"tech_alias":[],"unit_alias":9,"is_flying":false,"abilities":[{"ability":42},{"ability":1376}]},{"id":116,"name":"DroneBurrowed","normal_mode":104,"race":"Zerg","supply":1.0,"max_health":40.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":23.328125,"tech_alias":[],"unit_alias":104,"is_flying":false,"abilities":[{"ability":1380}]},{"id":117,"name":"HydraliskBurrowed","normal_mode":107,"race":"Zerg","supply":2.0,"max_health":90.0,"armor":0.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":50,"time":24.291015625,"tech_alias":[],"unit_alias":107,"is_flying":false,"abilities":[{"ability":1384}]},{"id":118,"name":"RoachBurrowed","normal_mode":110,"race":"Zerg","supply":2.0,"max_health":145.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":9.69140625,"tech_alias":[],"unit_alias":110,"is_flying":false,"abilities":[{"ability":4},{"ability":1388},{"requirements":[{"upgrade":3}],"ability":16},{"requirements":[{"upgrade":3}],"ability":17},{"requirements":[{"upgrade":3}],"ability":18},{"requirements":[{"upgrade":3}],"ability":19},{"requirements":[{"upgrade":3}],"ability":1}]},{"id":119,"name":"ZerglingBurrowed","normal_mode":105,"race":"Zerg","supply":0.5,"max_health":35.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":0,"time":24.291015625,"tech_alias":[],"unit_alias":105,"is_flying":false,"abilities":[{"ability":1392}]},{"id":120,"name":"InfestorTerranBurrowed","normal_mode":7,"race":"Zerg","supply":0.0,"max_health":75.0,"armor":0.0,"sight":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":24.291015625,"tech_alias":[],"unit_alias":7,"is_flying":false,"abilities":[{"ability":1396}]},{"id":125,"name":"QueenBurrowed","normal_mode":126,"race":"Zerg","supply":2.0,"max_health":175.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":60,"weapons":[],"attributes":["Biological","Psionic"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":175,"gas":0,"time":15.33203125,"tech_alias":[126],"unit_alias":126,"is_flying":false,"abilities":[{"ability":1435}]},{"id":126,"name":"Queen","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":175.0,"armor":1.0,"sight":9.0,"speed":0.9375,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":25,"attributes":["Biological","Psionic"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":175,"gas":0,"time":800.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":9.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.0,"bonuses":[]},{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":251},{"ability":1664},{"ability":1694},{"ability":1},{"requirements":[{"upgrade":64}],"ability":1433},{"ability":3691}]},{"id":127,"name":"InfestorBurrowed","normal_mode":111,"race":"Zerg","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":8.0,"speed":2.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":75,"weapons":[],"attributes":["Armored","Biological","Psionic"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":150,"time":10.962890625,"tech_alias":[],"unit_alias":111,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1396},{"ability":1446},{"ability":1},{"requirements":[{"upgrade":101}],"ability":249}]},{"id":128,"name":"OverlordCocoon","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":129,"name":"Overseer","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":11.0,"detection_range":11.0,"speed":1.875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":266.6796875,"tech_alias":[106],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":181},{"ability":1825},{"ability":3743},{"ability":1}]},{"id":130,"name":"PlanetaryFortress","normal_mode":18,"race":"Terran","supply":-15.0,"cargo_capacity":5,"max_health":1500.0,"armor":2.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":550,"gas":150,"time":800.0,"tech_alias":[18],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":40.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":203},{"ability":416},{"ability":524},{"ability":1}]},{"id":131,"name":"UltraliskBurrowed","normal_mode":109,"race":"Zerg","supply":6.0,"max_health":500.0,"armor":2.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":275,"gas":200,"time":22.0,"tech_alias":[],"unit_alias":109,"is_flying":false,"abilities":[{"ability":1514}]},{"id":132,"name":"OrbitalCommand","normal_mode":18,"race":"Terran","supply":-15.0,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":550,"gas":0,"time":560.0,"tech_alias":[18],"unit_alias":0,"is_flying":false,"abilities":[{"ability":171},{"ability":203},{"ability":255},{"ability":399},{"ability":524},{"ability":1522},{"ability":1}]},{"id":133,"name":"WarpGate","normal_mode":62,"race":"Protoss","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.8125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":160.0,"tech_alias":[62],"unit_alias":0,"max_shield":500.0,"is_flying":false,"abilities":[{"ability":1413},{"ability":1520},{"ability":1},{"requirements":[{"building":72}],"ability":1414},{"requirements":[{"building":68}],"ability":1416},{"requirements":[{"building":69}],"ability":1417},{"requirements":[{"building":72}],"ability":1418},{"requirements":[{"building":72}],"ability":1419}]},{"id":134,"name":"OrbitalCommandFlying","normal_mode":132,"race":"Terran","supply":-15.0,"max_health":1500.0,"armor":1.0,"sight":11.0,"speed":0.9375,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":true,"minerals":550,"gas":0,"time":32.0,"tech_alias":[18],"unit_alias":132,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":1524},{"ability":1}]},{"id":136,"name":"WarpPrismPhasing","normal_mode":81,"race":"Protoss","supply":2.0,"cargo_capacity":8,"max_health":80.0,"armor":0.0,"sight":11.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":0.875,"power_radius":3.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":0,"time":24.0,"tech_alias":[81],"unit_alias":81,"max_shield":100.0,"is_flying":true,"abilities":[{"ability":4},{"ability":911},{"ability":1530},{"ability":1}]},{"id":137,"name":"CreepTumorBurrowed","normal_mode":87,"race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological","Structure"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":18.9609375,"tech_alias":[87],"unit_alias":87,"is_flying":false,"abilities":[{"ability":1733},{"ability":1},{"ability":3691}]},{"id":138,"name":"CreepTumorQueen","normal_mode":87,"race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological","Structure"],"abilities":[],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":240.0,"tech_alias":[87],"unit_alias":87,"is_flying":false},{"id":139,"name":"SpineCrawlerUprooted","normal_mode":98,"race":"Zerg","supply":0.0,"max_health":300.0,"armor":2.0,"sight":11.0,"speed":1.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":0,"time":16.0,"tech_alias":[],"unit_alias":98,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1},{"ability":1729}]},{"id":140,"name":"SporeCrawlerUprooted","normal_mode":99,"race":"Zerg","supply":0.0,"max_health":300.0,"armor":1.0,"sight":11.0,"speed":1.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":125,"gas":0,"time":16.0,"tech_alias":[],"unit_alias":99,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1},{"ability":1731}]},{"id":141,"name":"Archon","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":10.0,"armor":0.0,"sight":9.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Psionic","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":175,"gas":275,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":350.0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":3.0,"cooldown":1.75390625,"bonuses":[{"against":"Biological","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":142,"name":"NydusCanal","race":"Zerg","supply":0.0,"cargo_capacity":1020,"max_health":300.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":75,"time":320.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":195},{"ability":2370},{"ability":1}]},{"id":145,"name":"GhostNova","normal_mode":50,"race":"Terran","supply":3.0,"cargo_size":2,"max_health":100.0,"armor":0.0,"sight":11.0,"speed":2.8125,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":75,"attributes":["Biological","Psionic"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":0.0,"tech_alias":[],"unit_alias":50,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[{"against":"Light","damage":10.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":36},{"ability":1628},{"ability":2714},{"ability":1},{"requirements":[],"ability":382}]},{"id":150,"name":"InfestedTerransEgg","race":"Zerg","supply":0.0,"max_health":75.0,"armor":2.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[]},{"id":151,"name":"Larva","race":"Zerg","supply":0.0,"max_health":25.0,"armor":10.0,"sight":5.0,"speed":0.5625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":1342},{"ability":1344},{"requirements":[{"building":89}],"ability":1343},{"requirements":[{"building":91}],"ability":1345},{"requirements":[{"building":92}],"ability":1346},{"requirements":[{"building":93}],"ability":1348},{"requirements":[{"building":97}],"ability":1351},{"requirements":[{"building":94}],"ability":1352},{"requirements":[{"building":92}],"ability":1353},{"requirements":[{"building":101}],"ability":1354},{"requirements":[{"building":94}],"ability":1356}]},{"id":268,"name":"MULE","race":"Terran","supply":0.0,"max_health":60.0,"armor":0.0,"sight":8.0,"speed":2.8125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":78},{"ability":166},{"ability":1}]},{"id":289,"name":"Broodling","race":"Zerg","supply":0.0,"max_health":20.0,"armor":0.0,"sight":7.0,"speed":2.953125,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":4.0,"damage_splash":0,"attacks":1,"range":0.10009765625,"cooldown":0.800048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":311,"name":"Adept","race":"Protoss","supply":2.0,"cargo_size":2,"max_health":70.0,"armor":1.0,"sight":9.0,"speed":2.5,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":25,"time":672.0,"tech_alias":[],"unit_alias":0,"max_shield":70.0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":4.0,"cooldown":2.25,"bonuses":[{"against":"Light","damage":12.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2544},{"ability":1}]},{"id":339,"name":"InfestedTerransEggPlacement","race":"Zerg","supply":0.0,"max_health":1.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":484,"name":"HellionTank","race":"Terran","supply":2.0,"cargo_size":4,"max_health":135.0,"armor":0.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Light","Biological","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":18.0,"damage_splash":0,"attacks":1,"range":2.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1},{"requirements":[{"building":29}],"ability":1978}]},{"id":488,"name":"MothershipCore","race":"Protoss","supply":2.0,"max_health":130.0,"armor":1.0,"sight":9.0,"speed":1.875,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":480.0,"tech_alias":[],"unit_alias":0,"max_shield":60.0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.85009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1974},{"ability":2162},{"ability":2244},{"ability":1},{"requirements":[],"ability":1847}]},{"id":489,"name":"LocustMP","race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":6.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":3.0,"cooldown":0.60009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":491,"name":"NydusCanalAttacker","race":"Zerg","supply":2.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":0,"time":320.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":2.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":1}]},{"id":492,"name":"NydusCanalCreeper","race":"Zerg","supply":0.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":23},{"ability":1839},{"ability":1}]},{"id":493,"name":"SwarmHostBurrowedMP","normal_mode":494,"race":"Zerg","supply":3.0,"max_health":160.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":75,"time":42.0,"tech_alias":[],"unit_alias":494,"is_flying":false,"abilities":[{"ability":2704},{"ability":1}]},{"id":494,"name":"SwarmHostMP","race":"Zerg","supply":3.0,"cargo_size":4,"max_health":160.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":75,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2704},{"ability":1},{"requirements":[{"upgrade":64}],"ability":2014}]},{"id":495,"name":"Oracle","race":"Protoss","supply":3.0,"max_health":100.0,"armor":0.0,"sight":10.0,"speed":4.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Mechanical","Psionic"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":832.0,"tech_alias":[],"unit_alias":0,"max_shield":60.0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":2146},{"ability":2375},{"ability":2505},{"ability":1}]},{"id":496,"name":"Tempest","race":"Protoss","supply":4.0,"max_health":200.0,"armor":2.0,"sight":12.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":175,"time":960.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":30.0,"damage_splash":0,"attacks":1,"range":13.0,"cooldown":3.300048828125,"bonuses":[{"against":"Massive","damage":22.0}]},{"target_type":"Ground","damage_per_hit":40.0,"damage_splash":0,"attacks":1,"range":10.0,"cooldown":3.300048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":497,"name":"WarHound","race":"Terran","supply":3.0,"cargo_size":4,"max_health":220.0,"armor":1.0,"sight":11.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.8125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":75,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":23.0,"damage_splash":0,"attacks":1,"range":7.0,"cooldown":1.300048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2101},{"ability":1}]},{"id":498,"name":"WidowMine","race":"Terran","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":0.0,"sight":7.0,"speed":2.8125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2095},{"ability":1}]},{"id":499,"name":"Viper","race":"Zerg","supply":3.0,"max_health":150.0,"armor":1.0,"sight":11.0,"speed":2.953125,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological","Psionic"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":200,"time":640.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2063},{"ability":2067},{"ability":2073},{"ability":2542},{"ability":1}]},{"id":500,"name":"WidowMineBurrowed","normal_mode":498,"race":"Terran","supply":2.0,"max_health":90.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":25,"time":52.0,"tech_alias":[498],"unit_alias":498,"is_flying":false,"abilities":[{"ability":2097},{"ability":2099},{"ability":1}]},{"id":501,"name":"LurkerMPEgg","race":"Zerg","supply":2.0,"max_health":100.0,"armor":1.0,"sight":5.0,"speed":3.375,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":502,"name":"LurkerMP","race":"Zerg","supply":3.0,"cargo_size":4,"max_health":190.0,"armor":1.0,"sight":11.0,"speed":2.953125,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":0.9375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":553.328125,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":2108},{"ability":1}]},{"id":503,"name":"LurkerMPBurrowed","normal_mode":502,"race":"Zerg","supply":3.0,"max_health":190.0,"armor":1.0,"sight":11.0,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":42.0,"tech_alias":[],"unit_alias":502,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":8.0,"cooldown":2.0,"bonuses":[{"against":"Armored","damage":10.0}]}],"abilities":[{"ability":4},{"ability":23},{"ability":2110},{"ability":2550},{"ability":1}]},{"id":504,"name":"LurkerDenMP","race":"Zerg","supply":0.0,"max_health":850.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[{"ability":3709,"requirements":[{"building":101}]},{"ability":3710,"requirements":[{"building":101}]}],"size":0,"radius":1.8125,"accepts_addon":false,"needs_power":false,"needs_creep":true,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":1280.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":569,"name":"ResourceBlocker","race":"Protoss","supply":0.0,"max_health":130.0,"armor":0.0,"sight":2.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Structure"],"abilities":[],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":593,"name":"IceProtossCrates","race":"Protoss","supply":0.0,"max_health":10.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":594,"name":"ProtossCrates","race":"Protoss","supply":0.0,"max_health":10.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":595,"name":"TowerMine","race":"Terran","supply":4.0,"max_health":100.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":687,"name":"RavagerCocoon","race":"Zerg","supply":2.0,"max_health":100.0,"armor":5.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":688,"name":"Ravager","race":"Zerg","supply":3.0,"cargo_size":4,"max_health":120.0,"armor":1.0,"sight":9.0,"speed":2.75,"speed_creep_mul":1.0,"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":272.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":16.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.60009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2338},{"ability":1},{"requirements":[{"upgrade":64}],"ability":2340}]},{"id":689,"name":"Liberator","race":"Terran","supply":3.0,"max_health":180.0,"armor":0.0,"sight":10.0,"speed":3.375,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":960.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":5.0,"damage_splash":0,"attacks":2,"range":5.0,"cooldown":1.800048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2558},{"ability":1}]},{"id":690,"name":"RavagerBurrowed","normal_mode":688,"race":"Zerg","supply":3.0,"max_health":120.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":100,"time":9.69140625,"tech_alias":[],"unit_alias":688,"is_flying":false,"abilities":[{"ability":2342}]},{"id":691,"name":"ThorAP","normal_mode":52,"race":"Terran","supply":6.0,"cargo_size":8,"max_health":400.0,"armor":1.0,"sight":11.0,"speed":1.875,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":300,"gas":200,"time":42.0,"tech_alias":[52],"unit_alias":52,"is_flying":false,"weapons":[{"target_type":"Air","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":11.0,"cooldown":1.280029296875,"bonuses":[{"against":"Massive","damage":10.0}]},{"target_type":"Ground","damage_per_hit":30.0,"damage_splash":0,"attacks":2,"range":7.0,"cooldown":1.280029296875,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2364},{"ability":1}]},{"id":692,"name":"Cyclone","race":"Terran","supply":3.0,"cargo_size":4,"max_health":120.0,"armor":1.0,"sight":11.0,"speed":3.375,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":720.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Any","damage_per_hit":18.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":1.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2350},{"ability":1}]},{"id":693,"name":"LocustMPFlying","normal_mode":489,"race":"Zerg","supply":0.0,"max_health":50.0,"armor":0.0,"sight":6.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":8.0,"tech_alias":[],"unit_alias":489,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2387},{"ability":1}]},{"id":694,"name":"Disruptor","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":150,"time":800.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2346},{"ability":1}]},{"id":725,"name":"VoidMPImmortalReviveCorpse","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":200.0,"armor":1.0,"sight":0.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":2469},{"ability":1}]},{"id":726,"name":"GuardianCocoonMP","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.40625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological","Massive"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":727,"name":"GuardianMP","race":"Zerg","supply":2.0,"max_health":150.0,"armor":2.0,"sight":10.0,"speed":1.5,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":640.015625,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":9.0,"cooldown":1.300048828125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":728,"name":"DevourerCocoonMP","race":"Zerg","supply":2.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.40625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological","Massive"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":200,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":729,"name":"DevourerMP","race":"Zerg","supply":2.0,"max_health":250.0,"armor":2.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological","Massive"],"size":0,"radius":0.875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":250,"gas":150,"time":640.015625,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":25.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":3.0,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":730,"name":"DefilerMPBurrowed","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":5.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Biological","Psionic"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":150,"time":24.291015625,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":2491}]},{"id":731,"name":"DefilerMP","race":"Zerg","supply":2.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":10.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Biological","Psionic"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":50,"gas":150,"time":8.80078125,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":2483},{"ability":2485},{"ability":2487},{"ability":1},{"requirements":[{"upgrade":64}],"ability":2489}]},{"id":732,"name":"OracleStasisTrap","race":"Protoss","supply":0.0,"max_health":30.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Structure"],"abilities":[],"size":0,"radius":0.4375,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":80.0,"tech_alias":[],"unit_alias":0,"max_shield":30.0,"is_flying":false},{"id":733,"name":"DisruptorPhased","race":"Protoss","supply":3.0,"cargo_size":4,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":4.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1}]},{"id":734,"name":"LiberatorAG","normal_mode":689,"race":"Terran","supply":3.0,"max_health":180.0,"armor":0.0,"sight":10.0,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":125,"time":64.66796875,"tech_alias":[689],"unit_alias":689,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":75.0,"damage_splash":0,"attacks":1,"range":10.0,"cooldown":1.60009765625,"bonuses":[]}],"abilities":[{"ability":4},{"ability":23},{"ability":2560},{"ability":1}]},{"id":800,"name":"ReleaseInterceptorsBeacon","race":"Protoss","supply":0.0,"max_health":1.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":1.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":801,"name":"AdeptPhaseShift","normal_mode":311,"race":"Protoss","supply":2.0,"cargo_size":2,"max_health":90.0,"armor":1.0,"sight":4.0,"speed":4.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":311,"max_shield":50.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":23},{"ability":2596},{"ability":1}]},{"id":807,"name":"ThorAALance","race":"Terran","supply":0.0,"max_health":10.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":820,"name":"HERCPlacement","normal_mode":838,"race":"Terran","supply":3.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":838,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":838,"name":"HERC","race":"Terran","supply":3.0,"cargo_size":2,"max_health":80.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"attributes":["Armored","Biological"],"size":0,"radius":0.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":200,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"weapons":[{"target_type":"Ground","damage_per_hit":20.0,"damage_splash":0,"attacks":1,"range":6.0,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":840,"name":"Replicant","race":"Protoss","supply":4.0,"cargo_size":4,"max_health":100.0,"armor":1.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":300,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":150.0,"is_flying":false,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":855,"name":"CorsairMP","race":"Protoss","supply":2.0,"max_health":120.0,"armor":1.0,"sight":9.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Light","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":60.0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":5.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":0.472412109375,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2477},{"ability":1}]},{"id":856,"name":"ScoutMP","race":"Protoss","supply":3.0,"max_health":150.0,"armor":0.0,"sight":9.0,"speed":2.8125,"speed_creep_mul":1.0,"attributes":["Armored","Mechanical"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":275,"gas":125,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":100.0,"is_flying":true,"weapons":[{"target_type":"Ground","damage_per_hit":8.0,"damage_splash":0,"attacks":1,"range":4.0,"cooldown":1.694091796875,"bonuses":[]},{"target_type":"Air","damage_per_hit":7.0,"damage_splash":0,"attacks":2,"range":4.0,"cooldown":1.25,"bonuses":[{"against":"Armored","damage":7.0}]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":857,"name":"ArbiterMP","race":"Protoss","supply":4.0,"max_health":200.0,"armor":0.0,"sight":9.0,"speed":2.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"attributes":["Armored","Mechanical"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":350,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":150.0,"is_flying":true,"weapons":[{"target_type":"Any","damage_per_hit":10.0,"damage_splash":0,"attacks":1,"range":5.0,"cooldown":1.5,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2473},{"ability":2475},{"ability":1}]},{"id":858,"name":"ScourgeMP","race":"Zerg","supply":0.5,"max_health":25.0,"armor":0.0,"sight":5.0,"speed":3.5,"speed_creep_mul":1.0,"attributes":["Light","Biological"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":12,"gas":37,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"weapons":[{"target_type":"Air","damage_per_hit":110.0,"damage_splash":0,"attacks":1,"range":0.0,"cooldown":0.833251953125,"bonuses":[]}],"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":1}]},{"id":860,"name":"QueenMP","race":"Zerg","supply":-2.0,"max_health":150.0,"armor":0.0,"sight":11.0,"speed":3.25,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Biological"],"size":0,"radius":0.75,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":23},{"ability":2493},{"ability":2495},{"ability":2497},{"ability":1}]},{"id":891,"name":"Elsecaro_Colonist_Hut","race":"Terran","supply":0.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"size":0,"radius":2.125,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false,"abilities":[{"ability":195},{"ability":1}]},{"id":892,"name":"TransportOverlordCocoon","race":"Zerg","supply":-8.0,"max_health":200.0,"armor":2.0,"sight":5.0,"speed":1.875,"speed_creep_mul":1.0,"weapons":[],"attributes":["Biological"],"abilities":[],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":100,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true},{"id":893,"name":"OverlordTransport","race":"Zerg","supply":-8.0,"cargo_capacity":8,"max_health":200.0,"armor":0.0,"sight":11.0,"speed":0.9140625,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":336.015625,"tech_alias":[106],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":17},{"ability":18},{"ability":19},{"ability":1406},{"ability":1},{"requirements":[{"building":100}],"ability":1448},{"requirements":[{"building":100}],"ability":1692}]},{"id":894,"name":"PylonOvercharged","normal_mode":60,"race":"Protoss","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":10.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"tech_alias":[60,60],"unit_alias":60,"max_shield":200.0,"is_flying":false},{"id":895,"name":"BypassArmorDrone","race":"Terran","supply":0.0,"max_health":80.0,"armor":0.0,"sight":7.0,"speed":5.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical","Structure"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":16},{"ability":23},{"ability":1}]},{"id":1910,"name":"ShieldBattery","race":"Protoss","supply":0.0,"max_health":200.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"max_energy":100.0,"start_energy":78,"weapons":[],"attributes":["Armored","Structure"],"size":0,"radius":1.125,"power_radius":6.5,"accepts_addon":false,"needs_power":true,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":640.0,"tech_alias":[],"unit_alias":0,"max_shield":200.0,"is_flying":false,"abilities":[{"ability":4113},{"ability":1}]},{"id":1911,"name":"ObserverSiegeMode","normal_mode":82,"race":"Protoss","supply":1.0,"max_health":40.0,"armor":0.0,"sight":13.75,"detection_range":13.75,"speed_creep_mul":1.0,"weapons":[],"attributes":["Light","Mechanical"],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":25,"gas":75,"time":12.0,"tech_alias":[],"unit_alias":82,"max_shield":30.0,"is_flying":true,"abilities":[{"ability":4},{"ability":3739}]},{"id":1912,"name":"OverseerSiegeMode","normal_mode":129,"race":"Zerg","supply":-8.0,"max_health":200.0,"armor":1.0,"sight":13.75,"detection_range":13.75,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":50,"weapons":[],"attributes":["Armored","Biological"],"size":0,"radius":1.0,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":150,"gas":50,"time":12.0,"tech_alias":[106],"unit_alias":129,"is_flying":true,"abilities":[{"ability":4},{"ability":181},{"ability":1825},{"ability":3745},{"ability":1}]},{"id":1913,"name":"RavenRepairDrone","race":"Terran","supply":0.0,"max_health":50.0,"armor":0.0,"sight":7.0,"speed_creep_mul":1.0,"max_energy":200.0,"start_energy":200,"weapons":[],"attributes":["Light","Mechanical","Structure","Summoned"],"size":0,"radius":0.625,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":100,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":true,"abilities":[{"ability":4},{"ability":3751},{"ability":1}]},{"id":1940,"name":"Viking","race":"Terran","supply":0.0,"max_health":1.0,"armor":0.0,"sight":0.0,"speed_creep_mul":1.0,"weapons":[],"attributes":[],"abilities":[],"size":0,"radius":0.5,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":false,"is_structure":false,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":0,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":1943,"name":"RefineryRich","race":"Terran","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Mechanical","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":480.0,"tech_alias":[],"unit_alias":0,"is_flying":false},{"id":1994,"name":"AssimilatorRich","race":"Protoss","supply":0.0,"max_health":300.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"max_shield":300.0,"is_flying":false},{"id":1995,"name":"ExtractorRich","race":"Zerg","supply":0.0,"max_health":500.0,"armor":1.0,"sight":9.0,"speed_creep_mul":1.0,"weapons":[],"attributes":["Armored","Biological","Structure"],"abilities":[],"size":0,"radius":1.6875,"accepts_addon":false,"needs_power":false,"needs_creep":false,"needs_geyser":true,"is_structure":true,"is_addon":false,"is_worker":false,"is_townhall":false,"minerals":75,"gas":0,"time":0.0,"tech_alias":[],"unit_alias":0,"is_flying":false}],"Upgrade":[{"id":1,"name":"CarrierLaunchSpeedUpgrade","cost":{"minerals":150,"gas":150,"time":1280.0}},{"id":2,"name":"GlialReconstitution","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":3,"name":"TunnelingClaws","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":4,"name":"ChitinousPlating","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":5,"name":"HiSecAutoTracking","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":6,"name":"TerranBuildingArmor","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":7,"name":"TerranInfantryWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":8,"name":"TerranInfantryWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3040.0}},{"id":9,"name":"TerranInfantryWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":3520.0}},{"id":10,"name":"NeosteelFrame","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":11,"name":"TerranInfantryArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":12,"name":"TerranInfantryArmorsLevel2","cost":{"minerals":150,"gas":150,"time":3040.0}},{"id":13,"name":"TerranInfantryArmorsLevel3","cost":{"minerals":200,"gas":200,"time":3520.0}},{"id":14,"name":"ReaperSpeed","cost":{"minerals":50,"gas":50,"time":1600.0}},{"id":15,"name":"Stimpack","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":16,"name":"ShieldWall","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":17,"name":"PunisherGrenades","cost":{"minerals":50,"gas":50,"time":960.0}},{"id":19,"name":"HighCapacityBarrels","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":20,"name":"BansheeCloak","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":21,"name":"MedivacCaduceusReactor","cost":{"minerals":100,"gas":100,"time":1120.0}},{"id":22,"name":"RavenCorvidReactor","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":23,"name":"HunterSeeker","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":24,"name":"DurableMaterials","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":25,"name":"PersonalCloaking","cost":{"minerals":150,"gas":150,"time":1920.0}},{"id":27,"name":"TerranVehicleArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":28,"name":"TerranVehicleArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":29,"name":"TerranVehicleArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":30,"name":"TerranVehicleWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":31,"name":"TerranVehicleWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":32,"name":"TerranVehicleWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":33,"name":"TerranShipArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":34,"name":"TerranShipArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":35,"name":"TerranShipArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":36,"name":"TerranShipWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":37,"name":"TerranShipWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":38,"name":"TerranShipWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":39,"name":"ProtossGroundWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2720.0}},{"id":40,"name":"ProtossGroundWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3240.0}},{"id":41,"name":"ProtossGroundWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":3760.0}},{"id":42,"name":"ProtossGroundArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2720.0}},{"id":43,"name":"ProtossGroundArmorsLevel2","cost":{"minerals":150,"gas":150,"time":3240.0}},{"id":44,"name":"ProtossGroundArmorsLevel3","cost":{"minerals":200,"gas":200,"time":3760.0}},{"id":45,"name":"ProtossShieldsLevel1","cost":{"minerals":150,"gas":150,"time":2720.0}},{"id":46,"name":"ProtossShieldsLevel2","cost":{"minerals":200,"gas":200,"time":3240.0}},{"id":47,"name":"ProtossShieldsLevel3","cost":{"minerals":250,"gas":250,"time":3760.0}},{"id":48,"name":"ObserverGraviticBooster","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":49,"name":"GraviticDrive","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":50,"name":"ExtendedThermalLance","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":52,"name":"PsiStormTech","cost":{"minerals":200,"gas":200,"time":1760.0}},{"id":53,"name":"ZergMeleeWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":54,"name":"ZergMeleeWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3040.0}},{"id":55,"name":"ZergMeleeWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":3520.0}},{"id":56,"name":"ZergGroundArmorsLevel1","cost":{"minerals":150,"gas":150,"time":2560.0}},{"id":57,"name":"ZergGroundArmorsLevel2","cost":{"minerals":200,"gas":200,"time":3040.0}},{"id":58,"name":"ZergGroundArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":59,"name":"ZergMissileWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":60,"name":"ZergMissileWeaponsLevel2","cost":{"minerals":150,"gas":150,"time":3040.0}},{"id":61,"name":"ZergMissileWeaponsLevel3","cost":{"minerals":200,"gas":200,"time":3520.0}},{"id":62,"name":"overlordspeed","cost":{"minerals":100,"gas":100,"time":960.0}},{"id":63,"name":"overlordtransport","cost":{"minerals":200,"gas":200,"time":2080.0}},{"id":64,"name":"Burrow","cost":{"minerals":100,"gas":100,"time":1600.0}},{"id":65,"name":"zerglingattackspeed","cost":{"minerals":200,"gas":200,"time":2080.0}},{"id":66,"name":"zerglingmovementspeed","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":68,"name":"ZergFlyerWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":69,"name":"ZergFlyerWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":70,"name":"ZergFlyerWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":71,"name":"ZergFlyerArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":72,"name":"ZergFlyerArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":73,"name":"ZergFlyerArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":75,"name":"CentrificalHooks","cost":{"minerals":100,"gas":100,"time":1600.0}},{"id":76,"name":"BattlecruiserEnableSpecializations","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":78,"name":"ProtossAirWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2880.0}},{"id":79,"name":"ProtossAirWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3440.0}},{"id":80,"name":"ProtossAirWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":4000.0}},{"id":81,"name":"ProtossAirArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2880.0}},{"id":82,"name":"ProtossAirArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3440.0}},{"id":83,"name":"ProtossAirArmorsLevel3","cost":{"minerals":250,"gas":250,"time":4000.0}},{"id":84,"name":"WarpGateResearch","cost":{"minerals":50,"gas":50,"time":2240.0}},{"id":85,"name":"haltech","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":86,"name":"Charge","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":87,"name":"BlinkTech","cost":{"minerals":150,"gas":150,"time":2720.0}},{"id":88,"name":"AnabolicSynthesis","cost":{"minerals":150,"gas":150,"time":960.0}},{"id":98,"name":"TransformationServos","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":99,"name":"PhoenixRangeUpgrade","cost":{"minerals":150,"gas":150,"time":1440.0}},{"id":100,"name":"TempestRangeUpgrade","cost":{"minerals":200,"gas":200,"time":1760.0}},{"id":101,"name":"NeuralParasite","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":102,"name":"LocustLifetimeIncrease","cost":{"minerals":200,"gas":200,"time":1920.0}},{"id":113,"name":"TerranVehicleAndShipWeaponsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":114,"name":"TerranVehicleAndShipWeaponsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":115,"name":"TerranVehicleAndShipWeaponsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":116,"name":"TerranVehicleAndShipArmorsLevel1","cost":{"minerals":100,"gas":100,"time":2560.0}},{"id":117,"name":"TerranVehicleAndShipArmorsLevel2","cost":{"minerals":175,"gas":175,"time":3040.0}},{"id":118,"name":"TerranVehicleAndShipArmorsLevel3","cost":{"minerals":250,"gas":250,"time":3520.0}},{"id":120,"name":"RoachSupply","cost":{"minerals":200,"gas":200,"time":2080.0}},{"id":121,"name":"ImmortalRevive","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":122,"name":"DrillClaws","cost":{"minerals":75,"gas":75,"time":1760.0}},{"id":123,"name":"CycloneLockOnRangeUpgrade","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":125,"name":"LiberatorMorph","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":127,"name":"LurkerRange","cost":{"minerals":150,"gas":150,"time":1280.0}},{"id":130,"name":"AdeptPiercingAttack","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":134,"name":"EvolveGroovedSpines","cost":{"minerals":75,"gas":75,"time":1120.0}},{"id":135,"name":"EvolveMuscularAugments","cost":{"minerals":100,"gas":100,"time":1440.0}},{"id":136,"name":"BansheeSpeed","cost":{"minerals":125,"gas":125,"time":2240.0}},{"id":137,"name":"MedivacRapidDeployment","cost":{"minerals":150,"gas":150,"time":1920.0}},{"id":138,"name":"RavenRecalibratedExplosives","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":139,"name":"MedivacIncreaseSpeedBoost","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":140,"name":"LiberatorAGRangeUpgrade","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":141,"name":"DarkTemplarBlinkUpgrade","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":144,"name":"CycloneLockOnDamageUpgrade","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":288,"name":"VoidRaySpeedUpgrade","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":289,"name":"SmartServos","cost":{"minerals":100,"gas":100,"time":1760.0}},{"id":290,"name":"ArmorPiercingRockets","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":291,"name":"CycloneRapidFireLaunchers","cost":{"minerals":75,"gas":75,"time":1760.0}},{"id":292,"name":"RavenEnhancedMunitions","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":293,"name":"DiggingClaws","cost":{"minerals":100,"gas":100,"time":1280.0}},{"id":296,"name":"HurricaneThrusters","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":297,"name":"TempestGroundAttackUpgrade","cost":{"minerals":150,"gas":150,"time":2240.0}},{"id":298,"name":"Frenzy","cost":{"minerals":100,"gas":100,"time":1440.0}},{"id":299,"name":"MicrobialShroud","cost":{"minerals":150,"gas":150,"time":1760.0}},{"id":300,"name":"InterferenceMatrix","cost":{"minerals":50,"gas":50,"time":1280.0}},{"id":301,"name":"SunderingImpact","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":302,"name":"AmplifiedShielding","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":303,"name":"PsionicAmplifiers","cost":{"minerals":100,"gas":100,"time":2240.0}},{"id":304,"name":"SecretedCoating","cost":{"minerals":100,"gas":100,"time":1280.0}}]}
\ No newline at end of file
diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile
index 4848c6ec..71610fdf 100644
--- a/dockerfiles/Dockerfile
+++ b/dockerfiles/Dockerfile
@@ -1,9 +1,8 @@
# Set up StarCraft II Test Environment for python-sc2 bots (not pysc2 bots!)
-ARG PYTHON_VERSION=3.8
+ARG PYTHON_VERSION=3.10
-# Use an official debian stretch slim release as a base image
-FROM python:$PYTHON_VERSION-slim
-MAINTAINER Burny
+# https://docs.astral.sh/uv/guides/integration/docker/#available-images
+FROM ghcr.io/astral-sh/uv:python$PYTHON_VERSION-bookworm-slim
ARG SC2_VERSION=4.10
@@ -18,13 +17,17 @@ RUN apt-get update \
&& apt-get upgrade --assume-yes --quiet=2
# Update and install packages for SC2 development environment
+# gcc to compile packages
+# libc6-dev required by gcc: /usr/local/include/python3.12/Python.h:23:12: fatal error: stdlib.h: No such file or directory
# git, unzip and wget for download and extraction
# rename to rename maps
# tree for debugging
RUN apt-get install --assume-yes --no-install-recommends --no-show-upgraded \
+ gcc \
+ libc6-dev \
git \
unzip \
- wget \
+ curl \
rename \
tree
@@ -34,7 +37,7 @@ WORKDIR /root/
# Download and uncompress StarCraftII from https://github.com/Blizzard/s2client-proto#linux-packages and remove zip file
# If file is locally available, use this instead:
#COPY SC2.4.10.zip /root/
-RUN wget --quiet --show-progress --progress=bar:force http://blzdistsc2-a.akamaihd.net/Linux/SC2.$SC2_VERSION.zip \
+RUN curl http://blzdistsc2-a.akamaihd.net/Linux/SC2.$SC2_VERSION.zip -o "SC2.$SC2_VERSION.zip" \
&& unzip -q -P iagreetotheeula SC2.$SC2_VERSION.zip \
&& rm SC2.$SC2_VERSION.zip
@@ -51,45 +54,42 @@ RUN ln -s /root/StarCraftII/Maps /root/StarCraftII/maps \
# Change to maps folder
WORKDIR /root/StarCraftII/maps/
-# Maps are available here https://github.com/Blizzard/s2client-proto#map-packs and here http://wiki.sc2ai.net/Ladder_Maps
+# Maps are available here https://github.com/Blizzard/s2client-proto#map-packs and here https://sc2ai.net/wiki/maps/
# Download and uncompress StarCraftII Maps, remove zip file - using "maps" instead of "Maps" as target folder
# Get sc2ai.net ladder maps
-RUN wget --quiet --show-progress --progress=bar:force \
- http://archive.sc2ai.net/Maps/Season1Maps.zip \
- http://archive.sc2ai.net/Maps/Season2Maps.zip \
- http://archive.sc2ai.net/Maps/Season3Maps.zip \
- http://archive.sc2ai.net/Maps/Season4Maps.zip \
- http://archive.sc2ai.net/Maps/Season5Maps.zip \
- http://archive.sc2ai.net/Maps/Season6Maps.zip \
- http://archive.sc2ai.net/Maps/Season7Maps.zip \
- http://archive.sc2ai.net/Maps/Season8Maps.zip \
- http://archive.sc2ai.net/Maps/Season9Maps.zip \
- http://archive.sc2ai.net/Maps/Season10Maps.zip \
+# -L param follows links https://stackoverflow.com/a/2663023/10882657
+RUN curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/9/ -o 1.zip \
+ && curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/14/ -o 2.zip \
+ && curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/21/ -o 3.zip \
+ && curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/35/ -o 4.zip \
+ && curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/36/ -o 5.zip \
+ && curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/38/ -o 6.zip \
+ && curl -L https://sc2ai.net/wiki/184/plugin/attachments/download/39/ -o 7.zip \
&& unzip -q -o '*.zip' \
- && rm *.zip
+ && rm *.zip
# Get official blizzard maps
-RUN wget --quiet --show-progress --progress=bar:force http://blzdistsc2-a.akamaihd.net/MapPacks/Ladder2019Season3.zip \
+RUN curl http://blzdistsc2-a.akamaihd.net/MapPacks/Ladder2019Season3.zip -o Ladder2019Season3.zip \
&& unzip -q -P iagreetotheeula -o 'Ladder2019Season3.zip' \
&& mv Ladder2019Season3/* . \
&& rm Ladder2019Season3.zip \
- && rm -r Ladder2019Season3
+ && rm -r Ladder2019Season3
# Get v5.0.6 maps
-RUN wget --quiet --show-progress --progress=bar:force https://github.com/shostyn/sc2patch/raw/master/Maps/506.zip \
+RUN curl -L https://github.com/shostyn/sc2patch/raw/4987d4915b47c801adbc05e297abaa9ca2988838/Maps/506.zip -o 506.zip \
&& unzip -q -o '506.zip' \
&& rm 506.zip
# Get flat and empty maps
-RUN wget --quiet --show-progress --progress=bar:force http://blzdistsc2-a.akamaihd.net/MapPacks/Melee.zip \
+RUN curl http://blzdistsc2-a.akamaihd.net/MapPacks/Melee.zip -o Melee.zip \
&& unzip -q -P iagreetotheeula -o 'Melee.zip' \
&& mv Melee/* . \
&& rm Melee.zip \
&& rm -r Melee
# Remove LE suffix from file names
-RUN rename -v 's/LE.SC2Map/.SC2Map/' *.SC2Map
+RUN rename -v 's/LE.SC2Map/.SC2Map/' *.SC2Map
# List all map files
RUN tree
diff --git a/dockerfiles/test_docker_image.sh b/dockerfiles/test_docker_image.sh
index dd0c7fda..4b203c2e 100644
--- a/dockerfiles/test_docker_image.sh
+++ b/dockerfiles/test_docker_image.sh
@@ -7,7 +7,7 @@ set -e
# Set which versions to use
export VERSION_NUMBER=${VERSION_NUMBER:-0.9.9}
-export PYTHON_VERSION=${PYTHON_VERSION:-3.10}
+export PYTHON_VERSION=${PYTHON_VERSION:-3.12}
export SC2_VERSION=${SC2_VERSION:-4.10}
# For better readability, set local variables
@@ -17,11 +17,11 @@ BUILD_ARGS="--build-arg PYTHON_VERSION=$PYTHON_VERSION --build-arg SC2_VERSION=$
# Allow image squashing by enabling experimental docker features
# https://stackoverflow.com/a/21164441/10882657
# https://github.com/actions/virtual-environments/issues/368#issuecomment-582387669
-file=/etc/docker/daemon.json
-if [ ! -e "$file" ]; then
- echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
- sudo systemctl restart docker.service
-fi
+# file=/etc/docker/daemon.json
+# if [ ! -e "$file" ]; then
+# echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
+# sudo systemctl restart docker.service
+# fi
# Build image without context
# https://stackoverflow.com/a/54666214/10882657
@@ -33,20 +33,28 @@ docker build -t $IMAGE_NAME $BUILD_ARGS - < dockerfiles/Dockerfile
docker rm -f test_container
# Start container, override the default entrypoint
-# https://docs.docker.com/storage/bind-mounts/#use-a-read-only-bind-mount
docker run -i -d \
--name test_container \
- --mount type=bind,source="$(pwd)",destination=/root/python-sc2,readonly \
+ --env 'PYTHONPATH=/root/python-sc2' \
--entrypoint /bin/bash \
$IMAGE_NAME
-# Install python-sc2, via mount the python-sc2 folder will be available
-docker exec -i test_container bash -c "pip install poetry \
- && cd python-sc2 && poetry install --no-dev"
+# Install requirements
+docker exec -i test_container mkdir -p /root/python-sc2
+docker cp pyproject.toml test_container:/root/python-sc2/
+docker cp uv.lock test_container:/root/python-sc2/
+docker exec -i test_container bash -c "pip install uv && cd python-sc2 && uv sync --no-cache --no-install-project"
+
+docker cp sc2 test_container:/root/python-sc2/sc2
+docker cp test test_container:/root/python-sc2/test
# Run various test bots
-docker exec -i test_container bash -c "cd python-sc2 && poetry run python test/travis_test_script.py test/autotest_bot.py"
-docker exec -i test_container bash -c "cd python-sc2 && poetry run python test/run_example_bots_vs_computer.py"
+docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/autotest_bot.py"
+docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/queries_test_bot.py"
+#docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/damagetest_bot.py"
+
+docker cp examples test_container:/root/python-sc2/examples
+docker exec -i test_container bash -c "cd python-sc2 && uv run python test/run_example_bots_vs_computer.py"
# Command for entering the container to debug if something went wrong:
# docker exec -it test_container bash
diff --git a/dockerfiles/test_new_python_candidate.sh b/dockerfiles/test_new_python_candidate.sh
index 3971ef99..5e9ba053 100644
--- a/dockerfiles/test_new_python_candidate.sh
+++ b/dockerfiles/test_new_python_candidate.sh
@@ -8,7 +8,7 @@ set -e
# Set which versions to use
export VERSION_NUMBER=${VERSION_NUMBER:-0.9.9}
-export PYTHON_VERSION=${PYTHON_VERSION:-'3.11'}
+export PYTHON_VERSION=${PYTHON_VERSION:-3.13}
export SC2_VERSION=${SC2_VERSION:-4.10}
# For better readability, set local variables
@@ -25,15 +25,14 @@ docker rm -f test_container
# https://docs.docker.com/storage/bind-mounts/#use-a-read-only-bind-mount
docker run -i -d \
--name test_container \
- --mount type=bind,source="$(pwd)",destination=/root/python-sc2,readonly \
- --entrypoint /bin/bash \
+ --volume ./:/root/python-sc2:ro \
$IMAGE_NAME
# Install python-sc2, via mount the python-sc2 folder will be available
-docker exec -i test_container bash -c "pip install poetry \
- && cd python-sc2 && poetry install --no-dev"
+docker exec -i test_container bash -c "pip install uv \
+ && cd python-sc2 && uv sync --frozen --no-cache --no-install-project"
# Run various test bots
-docker exec -i test_container bash -c "cd python-sc2 && poetry run python test/travis_test_script.py test/autotest_bot.py"
-docker exec -i test_container bash -c "cd python-sc2 && poetry run python test/run_example_bots_vs_computer.py"
+docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/autotest_bot.py"
+docker exec -i test_container bash -c "cd python-sc2 && uv run python test/run_example_bots_vs_computer.py"
diff --git a/docs_generate/conf.py b/docs_generate/conf.py
index eb79b598..85a966fd 100644
--- a/docs_generate/conf.py
+++ b/docs_generate/conf.py
@@ -13,9 +13,7 @@
import os
import sys
-sys.path.insert(0, os.path.abspath(".."))
-
-import sphinx_rtd_theme # nopycln: import
+sys.path.insert(0, os.path.abspath("..")) # noqa: PTH100
# -- Project information -----------------------------------------------------
@@ -31,9 +29,15 @@
extensions = [
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
- "sphinx_rtd_theme",
+ # https://www.sphinx-doc.org/en/master/usage/extensions/viewcode.html
+ "sphinx.ext.viewcode",
]
+# autodoc_typehints options https://github.com/agronholm/sphinx-autodoc-typehints#options
+always_document_param_types = True
+typehints_use_signature = True
+typehints_use_signature_return = True
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
@@ -52,7 +56,7 @@
# http://www.sphinx-doc.org/en/stable/theming.html
# https://www.writethedocs.org/guide/tools/sphinx-themes/
# https://sphinx-themes.org/
-html_theme = "sphinx_rtd_theme"
+html_theme = "sphinx_book_theme"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
diff --git a/docs_generate/text_files/docker.rst b/docs_generate/text_files/docker.rst
index 9bb39de0..df5d91f1 100644
--- a/docs_generate/text_files/docker.rst
+++ b/docs_generate/text_files/docker.rst
@@ -12,9 +12,9 @@ Requirements
Pulling the Docker image
------------------------
-The SC2 AI community has decided to stay on Python3.8 for a while. I'll try to update the docker image as soon as a new linux binary is released, or create a pull request at https://github.com/BurnySc2/python-sc2-docker ::
+The SC2 AI community has decided to stay on Python3.10 for a while. I'll try to update the docker image as soon as a new linux binary is released, or create a pull request at https://github.com/BurnySc2/python-sc2-docker ::
- docker pull burnysc2/python-sc2-docker:release-python_3.8-sc2_4.10_arenaclient_burny
+ docker pull burnysc2/python-sc2-docker:release-python_3.10-sc2_4.10_arenaclient_burny
Deleting previous containers
-----------------------------
@@ -26,11 +26,11 @@ Launching a new container
--------------------------
The following command launches a new container in interactive mode, which means it will not shut down once it is done running::
- docker run -it -d --name app burnysc2/python-sc2-docker:release-python_3.8-sc2_4.10_arenaclient_burny
+ docker run -it -d --name app burnysc2/python-sc2-docker:release-python_3.10-sc2_4.10_arenaclient_burny
Install bot requirements
-------------------------
-The command ``docker exec -i app poetry add "burnysc2>=0.12.12"`` installs the ``burnysc2`` dependencies in the docker container. Add more libraries as needed. You can also create your custom docker image so you do not have to re-install the dependencies every time you create a new container.
+The command ``docker exec -i app uv add "burnysc2>=0.12.12"`` installs the ``burnysc2`` dependencies in the docker container. Add more libraries as needed. You can also create your custom docker image so you do not have to re-install the dependencies every time you create a new container.
Since the linux SC2 binary is usually outdated (last update as of this writing was summer of 2019), you will likely have to replace your IDs with older IDs, which can be found here: https://github.com/BurnySc2/python-sc2/tree/linux-4.10/sc2/ids
@@ -62,7 +62,7 @@ Running the match(es)
---------------------
Now you are ready to let docker run your matches (headless)::
- docker exec -i app poetry run python /root/aiarena-client/arenaclient/run_local.py
+ docker exec -i app uv run python /root/aiarena-client/arenaclient/run_local.py
Copying the replay from container to host machine
--------------------------------------------------------------
diff --git a/docs_generate/text_files/introduction.rst b/docs_generate/text_files/introduction.rst
index ddbf4955..33193fb9 100644
--- a/docs_generate/text_files/introduction.rst
+++ b/docs_generate/text_files/introduction.rst
@@ -6,7 +6,7 @@ This is an overview to the BurnySc2/python-sc2 library which can be found here:
Requirements
-------------
-- Python 3.8 or newer
+- Python 3.10 or newer
- StarCraft 2 Client installation in the **default installation path** which should be ``C:\Program Files (x86)\StarCraft II``
Installation
@@ -15,7 +15,6 @@ Install through pip using ``pip install burnysc2`` if Python is in your environm
Alternatively (of if the command above doesn't work) you can install a specific branch directly from github, here the develop branch::
- pip install poetry
pip install --upgrade --force-reinstall https://github.com/BurnySc2/python-sc2/archive/develop.zip
Creating a bot
@@ -80,10 +79,10 @@ Information about the enemy player::
self.enemy_structures: Units
# Enemy spawn locations as a list of Point2 points
- self.enemy_start_locations: List[Point2]
+ self.enemy_start_locations: list[Point2]
# Enemy units that are inside your sensor tower range
- self.blips: Set[Blip]
+ self.blips: set[Blip]
# The enemy race. If the enemy chose random, this will stay at random forever
self.enemy_race: Race
@@ -99,7 +98,7 @@ Other information::
self.all_units: Units # All units combined: yours, enemy's and neutral
# Locations of possible expansions
- self.expansion_locations: Dict[Point2, Units]
+ self.expansion_locations: dict[Point2, Units]
# Game data about units, abilities and upgrades (see game_data.py)
self.game_data: GameData
diff --git a/examples/arcade_bot.py b/examples/arcade_bot.py
index 860e7ec8..32bbf22c 100644
--- a/examples/arcade_bot.py
+++ b/examples/arcade_bot.py
@@ -19,7 +19,8 @@
- Make marines constantly run if they have a ling/bane very close to them
- Split marines before engaging
"""
-from typing import Union
+
+from __future__ import annotations
from loguru import logger
@@ -37,7 +38,6 @@
class MarineSplitChallenge(BotAI):
-
async def on_start(self):
await self.chat_send("Edit this message for automatic chat commands.")
self.client.game_step = 2
@@ -45,9 +45,7 @@ async def on_start(self):
async def on_step(self, iteration):
# do marine micro vs zerglings
for unit in self.units(UnitTypeId.MARINE):
-
if self.enemy_units:
-
# attack (or move towards) zerglings / banelings
if unit.weapon_cooldown <= self.client.game_step / 2:
enemies_in_range = self.enemy_units.filter(unit.target_in_range)
@@ -57,7 +55,8 @@ async def on_step(self, iteration):
# Use stimpack
if (
self.already_pending_upgrade(UpgradeId.STIMPACK) == 1
- and not unit.has_buff(BuffId.STIMPACK) and unit.health > 10
+ and not unit.has_buff(BuffId.STIMPACK)
+ and unit.health > 10
):
unit(AbilityId.EFFECT_STIM)
@@ -97,15 +96,17 @@ async def on_step(self, iteration):
def position_around_unit(
self,
- pos: Union[Unit, Point2, Point3],
+ pos: Unit | Point2 | Point3,
distance: int = 1,
step_size: int = 1,
exclude_out_of_bounds: bool = True,
):
+ # pyre-ignore[16]
pos = pos.position.rounded
positions = {
pos.offset(Point2((x, y)))
- for x in range(-distance, distance + 1, step_size) for y in range(-distance, distance + 1, step_size)
+ for x in range(-distance, distance + 1, step_size)
+ for y in range(-distance, distance + 1, step_size)
if (x, y) != (0, 0)
}
# filter positions outside map size
@@ -113,6 +114,7 @@ def position_around_unit(
positions = {
p
for p in positions
+ # pyre-ignore[16]
if 0 <= p[0] < self.game_info.pathing_grid.width and 0 <= p[1] < self.game_info.pathing_grid.height
}
return positions
diff --git a/examples/bot_vs_bot.py b/examples/bot_vs_bot.py
index d6e8d6a7..73d69fa7 100644
--- a/examples/bot_vs_bot.py
+++ b/examples/bot_vs_bot.py
@@ -1,7 +1,8 @@
"""
This script shows how to let two custom bots play against each other.
"""
-from typing import List
+
+from __future__ import annotations
from loguru import logger
@@ -14,7 +15,7 @@
def main_old():
- result: List[Result] = run_game(
+ result: list[Result] = run_game(
maps.get("AcropolisLE"),
[
Bot(Race.Protoss, WarpGateBot()),
diff --git a/examples/competitive/__init__.py b/examples/competitive/__init__.py
index 34aa07e2..28a10595 100644
--- a/examples/competitive/__init__.py
+++ b/examples/competitive/__init__.py
@@ -1,4 +1,3 @@
-# pylint: disable=W0212
import argparse
import asyncio
@@ -7,7 +6,7 @@
import sc2
from sc2.client import Client
-from sc2.protocol import ConnectionAlreadyClosed
+from sc2.protocol import ConnectionAlreadyClosedError
# Run ladder game
@@ -26,10 +25,7 @@ def run_ladder_game(bot):
parser.add_argument("--RealTime", action="store_true", help="Real time flag")
args, _unknown = parser.parse_known_args()
- if args.LadderServer is None:
- host = "127.0.0.1"
- else:
- host = args.LadderServer
+ host = "127.0.0.1" if args.LadderServer is None else args.LadderServer
host_port = args.GamePort
lan_port = args.StartPort
@@ -68,7 +64,7 @@ async def join_ladder_game(host, port, players, realtime, portconfig, save_repla
await client.save_replay(save_replay_as)
# await client.leave()
# await client.quit()
- except ConnectionAlreadyClosed:
+ except ConnectionAlreadyClosedError:
logger.error("Connection was closed before the game ended")
return None
finally:
diff --git a/examples/competitive/bot.py b/examples/competitive/bot.py
index e736b44e..5170635a 100644
--- a/examples/competitive/bot.py
+++ b/examples/competitive/bot.py
@@ -3,7 +3,6 @@
class CompetitiveBot(BotAI):
-
async def on_start(self):
print("Game started")
# Do things here before the game starts
@@ -12,6 +11,7 @@ async def on_step(self, iteration):
# Populate this function with whatever your bot should do!
pass
+ # pyre-ignore[11]
async def on_end(self, game_result: Result):
print("Game ended.")
# Do things here after the game ends
diff --git a/examples/competitive/run.py b/examples/competitive/run.py
index e7a983e8..10984103 100644
--- a/examples/competitive/run.py
+++ b/examples/competitive/run.py
@@ -1,4 +1,4 @@
-# pylint: disable=E0401
+# pyre-ignore-all-errors[16, 21]
import sys
from __init__ import run_ladder_game
diff --git a/examples/distributed_workers.py b/examples/distributed_workers.py
index 724cc392..95d3d4af 100644
--- a/examples/distributed_workers.py
+++ b/examples/distributed_workers.py
@@ -1,3 +1,4 @@
+# pyre-ignore-all-errors[16]
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
@@ -7,7 +8,6 @@
class TerranBot(BotAI):
-
async def on_step(self, iteration):
await self.distribute_workers()
await self.build_supply()
diff --git a/examples/observer_easy_vs_easy.py b/examples/observer_easy_vs_easy.py
index f4a10558..aa9f944b 100644
--- a/examples/observer_easy_vs_easy.py
+++ b/examples/observer_easy_vs_easy.py
@@ -8,8 +8,7 @@
def main():
run_game(
maps.get("Abyssal Reef LE"),
- [Bot(Race.Protoss, CannonRushBot()),
- Computer(Race.Protoss, Difficulty.Medium)],
+ [Bot(Race.Protoss, CannonRushBot()), Computer(Race.Protoss, Difficulty.Medium)],
realtime=True,
)
diff --git a/examples/protoss/cannon_rush.py b/examples/protoss/cannon_rush.py
index 6a7c5aa6..2d287202 100644
--- a/examples/protoss/cannon_rush.py
+++ b/examples/protoss/cannon_rush.py
@@ -9,8 +9,6 @@
class CannonRushBot(BotAI):
-
- # pylint: disable=R0912
async def on_step(self, iteration):
if iteration == 0:
await self.chat_send("(probe)(pylon)(cannon)(cannon)(gg)")
@@ -64,8 +62,7 @@ async def on_step(self, iteration):
def main():
run_game(
maps.get("(2)CatalystLE"),
- [Bot(Race.Protoss, CannonRushBot(), name="CheeseCannon"),
- Computer(Race.Protoss, Difficulty.Medium)],
+ [Bot(Race.Protoss, CannonRushBot(), name="CheeseCannon"), Computer(Race.Protoss, Difficulty.Medium)],
realtime=False,
)
diff --git a/examples/protoss/find_adept_shades.py b/examples/protoss/find_adept_shades.py
index f1f4fde0..8b136cfc 100644
--- a/examples/protoss/find_adept_shades.py
+++ b/examples/protoss/find_adept_shades.py
@@ -10,9 +10,7 @@
from sc2.position import Point2
-# pylint: disable=W0231
class FindAdeptShadesBot(BotAI):
-
def __init__(self):
self.shaded = False
self.shades_mapping = {}
@@ -28,6 +26,7 @@ async def on_step(self, iteration: int):
if adepts and not self.shaded:
# Wait for adepts to spawn and then cast ability
for adept in adepts:
+ # pyre-ignore[16]
adept(AbilityId.ADEPTPHASESHIFT_ADEPTPHASESHIFT, self.game_info.map_center)
self.shaded = True
elif self.shades_mapping:
@@ -39,6 +38,7 @@ async def on_step(self, iteration: int):
# logger.info(f"Remaining shade time: {shade.buff_duration_remain} / {shade.buff_duration_max}")
pass
if adept and shade:
+ # pyre-ignore[16]
self.client.debug_line_out(adept, shade, (0, 255, 0))
# logger.info(self.shades_mapping)
elif self.shaded:
@@ -53,6 +53,7 @@ async def on_step(self, iteration: int):
previous_shade_location = shade.position.towards(
forward_position, -(self.client.game_step / 16) * shade.movement_speed
) # See docstring of movement_speed attribute
+ # pyre-ignore[6]
closest_adept = remaining_adepts.closest_to(previous_shade_location)
self.shades_mapping[closest_adept.tag] = shade.tag
@@ -60,8 +61,7 @@ async def on_step(self, iteration: int):
def main():
run_game(
maps.get("(2)CatalystLE"),
- [Bot(Race.Protoss, FindAdeptShadesBot()),
- Computer(Race.Protoss, Difficulty.Medium)],
+ [Bot(Race.Protoss, FindAdeptShadesBot()), Computer(Race.Protoss, Difficulty.Medium)],
realtime=False,
)
diff --git a/examples/protoss/threebase_voidray.py b/examples/protoss/threebase_voidray.py
index 972c2f28..2030a28f 100644
--- a/examples/protoss/threebase_voidray.py
+++ b/examples/protoss/threebase_voidray.py
@@ -9,8 +9,6 @@
class ThreebaseVoidrayBot(BotAI):
-
- # pylint: disable=R0912
async def on_step(self, iteration):
target_base_count = 3
target_stargate_count = 3
@@ -54,8 +52,11 @@ async def on_step(self, iteration):
# If we are low on supply, build pylon
if (
- self.supply_left < 2 and self.already_pending(UnitTypeId.PYLON) == 0
- or self.supply_used > 15 and self.supply_left < 4 and self.already_pending(UnitTypeId.PYLON) < 2
+ self.supply_left < 2
+ and self.already_pending(UnitTypeId.PYLON) == 0
+ or self.supply_used > 15
+ and self.supply_left < 4
+ and self.already_pending(UnitTypeId.PYLON) < 2
):
# Always check if you can afford something before you build it
if self.can_afford(UnitTypeId.PYLON):
@@ -109,8 +110,8 @@ async def on_step(self, iteration):
pylon = self.structures(UnitTypeId.PYLON).ready.random
if (
self.townhalls.ready.amount + self.already_pending(UnitTypeId.NEXUS) >= target_base_count
- and self.structures(UnitTypeId.STARGATE).ready.amount + self.already_pending(UnitTypeId.STARGATE) <
- target_stargate_count
+ and self.structures(UnitTypeId.STARGATE).ready.amount + self.already_pending(UnitTypeId.STARGATE)
+ < target_stargate_count
):
if self.can_afford(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
@@ -125,8 +126,7 @@ async def on_step(self, iteration):
def main():
run_game(
maps.get("(2)CatalystLE"),
- [Bot(Race.Protoss, ThreebaseVoidrayBot()),
- Computer(Race.Protoss, Difficulty.Easy)],
+ [Bot(Race.Protoss, ThreebaseVoidrayBot()), Computer(Race.Protoss, Difficulty.Easy)],
realtime=False,
)
diff --git a/examples/protoss/warpgate_push.py b/examples/protoss/warpgate_push.py
index 0ae6e350..7058ea43 100644
--- a/examples/protoss/warpgate_push.py
+++ b/examples/protoss/warpgate_push.py
@@ -11,9 +11,7 @@
from sc2.player import Bot, Computer
-# pylint: disable=W0231
class WarpGateBot(BotAI):
-
def __init__(self):
# Initialize inherited class
self.proxy_built = False
@@ -31,7 +29,6 @@ async def warp_new_units(self, proxy):
return
warpgate.warp_in(UnitTypeId.STALKER, placement)
- # pylint: disable=R0912
async def on_step(self, iteration):
await self.distribute_workers()
@@ -92,7 +89,8 @@ async def on_step(self, iteration):
# Research warp gate if cybercore is completed
if (
- self.structures(UnitTypeId.CYBERNETICSCORE).ready and self.can_afford(AbilityId.RESEARCH_WARPGATE)
+ self.structures(UnitTypeId.CYBERNETICSCORE).ready
+ and self.can_afford(AbilityId.RESEARCH_WARPGATE)
and self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH) == 0
):
ccore = self.structures(UnitTypeId.CYBERNETICSCORE).ready.first
@@ -118,7 +116,8 @@ async def on_step(self, iteration):
# Build proxy pylon
if (
- self.structures(UnitTypeId.CYBERNETICSCORE).amount >= 1 and not self.proxy_built
+ self.structures(UnitTypeId.CYBERNETICSCORE).amount >= 1
+ and not self.proxy_built
and self.can_afford(UnitTypeId.PYLON)
):
p = self.game_info.map_center.towards(self.enemy_start_locations[0], 20)
diff --git a/examples/simulate_fight_scenario.py b/examples/simulate_fight_scenario.py
index d32a4169..b3d66511 100644
--- a/examples/simulate_fight_scenario.py
+++ b/examples/simulate_fight_scenario.py
@@ -13,7 +13,6 @@
class FightBot(BotAI):
-
def __init__(self):
super().__init__()
self.enemy_location: Point2 = None
@@ -48,16 +47,14 @@ async def reset_arena(self):
await self.client.debug_create_unit(
[
[UnitTypeId.SUPPLYDEPOT, 1, self.enemy_location, OPPONENT_PLAYER_ID],
- [UnitTypeId.MARINE, 4,
- self.enemy_location.towards(self.start_location, 8), OPPONENT_PLAYER_ID]
+ [UnitTypeId.MARINE, 4, self.enemy_location.towards(self.start_location, 8), OPPONENT_PLAYER_ID],
]
)
await self.client.debug_create_unit(
[
[UnitTypeId.SUPPLYDEPOT, 1, self.start_location, MY_PLAYER_ID],
- [UnitTypeId.MARINE, 4,
- self.start_location.towards(self.enemy_location, 8), MY_PLAYER_ID]
+ [UnitTypeId.MARINE, 4, self.start_location.towards(self.enemy_location, 8), MY_PLAYER_ID],
]
)
@@ -81,7 +78,7 @@ def main():
maps.get("Flat64"),
# NOTE: you can have two bots fighting with each other here
[Bot(Race.Terran, FightBot()), Computer(Race.Terran, Difficulty.Medium)],
- realtime=True
+ realtime=True,
)
diff --git a/examples/terran/cyclone_push.py b/examples/terran/cyclone_push.py
index eaa8d9f2..cf04c91a 100644
--- a/examples/terran/cyclone_push.py
+++ b/examples/terran/cyclone_push.py
@@ -10,7 +10,6 @@
class CyclonePush(BotAI):
-
def select_target(self) -> Point2:
# Pick a random enemy structure's position
targets = self.enemy_structures
@@ -23,13 +22,12 @@ def select_target(self) -> Point2:
return targets.random.position
# Pick enemy start location if it has no friendly units nearby
- if min((unit.distance_to(self.enemy_start_locations[0]) for unit in self.units)) > 5:
+ if min(unit.distance_to(self.enemy_start_locations[0]) for unit in self.units) > 5:
return self.enemy_start_locations[0]
# Pick a random mineral field on the map
return self.mineral_field.random.position
- # pylint: disable=R0912
async def on_step(self, iteration):
CCs: Units = self.townhalls(UnitTypeId.COMMANDCENTER)
# If no command center exists, attack-move with all workers and cyclones
@@ -58,7 +56,8 @@ async def on_step(self, iteration):
# While we have less than 22 workers: build more
# Check if we can afford them (by minerals and by supply)
if (
- self.can_afford(UnitTypeId.SCV) and self.supply_workers + self.already_pending(UnitTypeId.SCV) < 22
+ self.can_afford(UnitTypeId.SCV)
+ and self.supply_workers + self.already_pending(UnitTypeId.SCV) < 22
and cc.is_idle
):
cc.train(UnitTypeId.SCV)
diff --git a/examples/terran/mass_reaper.py b/examples/terran/mass_reaper.py
index f8034199..01aba5dd 100644
--- a/examples/terran/mass_reaper.py
+++ b/examples/terran/mass_reaper.py
@@ -7,7 +7,6 @@
"""
import random
-from typing import Set
from sc2 import maps
from sc2.bot_ai import BotAI
@@ -21,14 +20,11 @@
from sc2.units import Units
-# pylint: disable=W0231
class MassReaperBot(BotAI):
-
def __init__(self):
# Select distance calculation method 0, which is the pure python distance calculation without caching or indexing, using math.hypot(), for more info see bot_ai_internal.py _distances_override_functions() function
self.distance_calculation_method = 3
- # pylint: disable=R0912,R0914
async def on_step(self, iteration):
# Benchmark and print duration time of the on_step method based on "self.distance_calculation_method" value
# logger.info(self.time_formatted, self.supply_used, self.step_time[1])
@@ -39,8 +35,11 @@ async def on_step(self, iteration):
- self.already_pending(TYPE) counts how many units are queued
"""
if (
- self.supply_left < 5 and self.townhalls and self.supply_used >= 14
- and self.can_afford(UnitTypeId.SUPPLYDEPOT) and self.already_pending(UnitTypeId.SUPPLYDEPOT) < 1
+ self.supply_left < 5
+ and self.townhalls
+ and self.supply_used >= 14
+ and self.can_afford(UnitTypeId.SUPPLYDEPOT)
+ and self.already_pending(UnitTypeId.SUPPLYDEPOT) < 1
):
workers: Units = self.workers.gathering
# If workers were found
@@ -68,7 +67,8 @@ async def on_step(self, iteration):
# Expand if we can afford (400 minerals) and have less than 2 bases
if (
- 1 <= self.townhalls.amount < 2 and self.already_pending(UnitTypeId.COMMANDCENTER) == 0
+ 1 <= self.townhalls.amount < 2
+ and self.already_pending(UnitTypeId.COMMANDCENTER) == 0
and self.can_afford(UnitTypeId.COMMANDCENTER)
):
# get_next_expansion returns the position of the next possible expansion location where you can place a command center
@@ -88,8 +88,8 @@ async def on_step(self, iteration):
# self.structures.of_type(
# [UnitTypeId.SUPPLYDEPOT, UnitTypeId.SUPPLYDEPOTLOWERED, UnitTypeId.SUPPLYDEPOTDROP]
# ).ready
- and self.structures(UnitTypeId.BARRACKS).ready.amount + self.already_pending(UnitTypeId.BARRACKS) < 4 and
- self.can_afford(UnitTypeId.BARRACKS)
+ and self.structures(UnitTypeId.BARRACKS).ready.amount + self.already_pending(UnitTypeId.BARRACKS) < 4
+ and self.can_afford(UnitTypeId.BARRACKS)
):
workers: Units = self.workers.gathering
if (
@@ -113,8 +113,9 @@ async def on_step(self, iteration):
# Find all vespene geysers that are closer than range 10 to this townhall
vgs: Units = self.vespene_geyser.closer_than(10, th)
for vg in vgs:
- if await self.can_place_single(UnitTypeId.REFINERY,
- vg.position) and self.can_afford(UnitTypeId.REFINERY):
+ if await self.can_place_single(UnitTypeId.REFINERY, vg.position) and self.can_afford(
+ UnitTypeId.REFINERY
+ ):
workers: Units = self.workers.gathering
if workers: # same condition as above
worker: Unit = workers.closest_to(vg)
@@ -126,10 +127,13 @@ async def on_step(self, iteration):
# Make scvs until 22, usually you only need 1:1 mineral:gas ratio for reapers, but if you don't lose any then you will need additional depots (mule income should take care of that)
# Stop scv production when barracks is complete but we still have a command center (priotize morphing to orbital command)
- # pylint: disable=R0916
if (
- self.can_afford(UnitTypeId.SCV) and self.supply_left > 0 and self.supply_workers < 22 and (
- self.structures(UnitTypeId.BARRACKS).ready.amount < 1 and self.townhalls(UnitTypeId.COMMANDCENTER).idle
+ self.can_afford(UnitTypeId.SCV)
+ and self.supply_left > 0
+ and self.supply_workers < 22
+ and (
+ self.structures(UnitTypeId.BARRACKS).ready.amount < 1
+ and self.townhalls(UnitTypeId.COMMANDCENTER).idle
or self.townhalls(UnitTypeId.ORBITALCOMMAND).idle
)
):
@@ -151,17 +155,17 @@ async def on_step(self, iteration):
enemies: Units = self.enemy_units | self.enemy_structures
enemies_can_attack: Units = enemies.filter(lambda unit: unit.can_attack_ground)
for r in self.units(UnitTypeId.REAPER):
-
# Move to range 15 of closest unit if reaper is below 20 hp and not regenerating
enemy_threats_close: Units = enemies_can_attack.filter(
lambda unit: unit.distance_to(r) < 15
) # Threats that can attack the reaper
if r.health_percentage < 2 / 5 and enemy_threats_close:
- retreat_points: Set[Point2] = self.neighbors8(r.position,
- distance=2) | self.neighbors8(r.position, distance=4)
+ retreat_points: set[Point2] = self.neighbors8(r.position, distance=2) | self.neighbors8(
+ r.position, distance=4
+ )
# Filter points that are pathable
- retreat_points: Set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)}
+ retreat_points: set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)}
if retreat_points:
closest_enemy: Unit = enemy_threats_close.closest_to(r)
retreat_point: Unit = closest_enemy.position.furthest(retreat_points)
@@ -179,13 +183,14 @@ async def on_step(self, iteration):
continue # Continue for loop, dont execute any of the following
# Attack is on cooldown, check if grenade is on cooldown, if not then throw it to furthest enemy in range 5
- # pylint: disable=W0212
- reaper_grenade_range: float = (
- self.game_data.abilities[AbilityId.KD8CHARGE_KD8CHARGE.value]._proto.cast_range
- )
+ reaper_grenade_range: float = self.game_data.abilities[
+ AbilityId.KD8CHARGE_KD8CHARGE.value
+ ]._proto.cast_range
enemy_ground_units_in_grenade_range: Units = enemies_can_attack.filter(
- lambda unit: not unit.is_structure and not unit.is_flying and unit.type_id not in
- {UnitTypeId.LARVA, UnitTypeId.EGG} and unit.distance_to(r) < reaper_grenade_range
+ lambda unit: not unit.is_structure
+ and not unit.is_flying
+ and unit.type_id not in {UnitTypeId.LARVA, UnitTypeId.EGG}
+ and unit.distance_to(r) < reaper_grenade_range
)
if enemy_ground_units_in_grenade_range and (r.is_attacking or r.is_moving):
# If AbilityId.KD8CHARGE_KD8CHARGE in abilities, we check that to see if the reaper grenade is off cooldown
@@ -208,10 +213,11 @@ async def on_step(self, iteration):
) # Hardcoded attackrange minus 0.5
# Threats that can attack the reaper
if r.weapon_cooldown != 0 and enemy_threats_very_close:
- retreat_points: Set[Point2] = self.neighbors8(r.position,
- distance=2) | self.neighbors8(r.position, distance=4)
+ retreat_points: set[Point2] = self.neighbors8(r.position, distance=2) | self.neighbors8(
+ r.position, distance=4
+ )
# Filter points that are pathable by a reaper
- retreat_points: Set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)}
+ retreat_points: set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)}
if retreat_points:
closest_enemy: Unit = enemy_threats_very_close.closest_to(r)
retreat_point: Point2 = max(
@@ -253,13 +259,13 @@ async def on_step(self, iteration):
# Stolen and modified from position.py
@staticmethod
- def neighbors4(position, distance=1) -> Set[Point2]:
+ def neighbors4(position, distance=1) -> set[Point2]:
p = position
d = distance
return {Point2((p.x - d, p.y)), Point2((p.x + d, p.y)), Point2((p.x, p.y - d)), Point2((p.x, p.y + d))}
# Stolen and modified from position.py
- def neighbors8(self, position, distance=1) -> Set[Point2]:
+ def neighbors8(self, position, distance=1) -> set[Point2]:
p = position
d = distance
return self.neighbors4(position, distance) | {
@@ -270,7 +276,6 @@ def neighbors8(self, position, distance=1) -> Set[Point2]:
}
# Distribute workers function rewritten, the default distribute_workers() function did not saturate gas quickly enough
- # pylint: disable=R0912
async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas=False):
mineral_tags = [x.tag for x in self.mineral_field]
gas_building_tags = [x.tag for x in self.gas_buildings]
@@ -288,8 +293,10 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas=
deficit_gas_buildings[g.tag] = {"unit": g, "deficit": deficit}
elif deficit < 0:
surplus_workers = self.workers.closer_than(10, g).filter(
- lambda w: w not in worker_pool_tags and len(w.orders) == 1 and w.orders[0].ability.id in
- [AbilityId.HARVEST_GATHER] and w.orders[0].target in gas_building_tags
+ lambda w: w not in worker_pool_tags
+ and len(w.orders) == 1
+ and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
+ and w.orders[0].target in gas_building_tags
)
for _ in range(-deficit):
if surplus_workers.amount > 0:
@@ -308,8 +315,10 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas=
deficit_townhalls[th.tag] = {"unit": th, "deficit": deficit}
elif deficit < 0:
surplus_workers = self.workers.closer_than(10, th).filter(
- lambda w: w.tag not in worker_pool_tags and len(w.orders) == 1 and w.orders[0].ability.id in
- [AbilityId.HARVEST_GATHER] and w.orders[0].target in mineral_tags
+ lambda w: w.tag not in worker_pool_tags
+ and len(w.orders) == 1
+ and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
+ and w.orders[0].target in mineral_tags
)
# worker_pool.extend(surplus_workers)
for _ in range(-deficit):
@@ -337,7 +346,8 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas=
-gasInfo["deficit"] for gasTag, gasInfo in surplusgas_buildings.items() if gasInfo["deficit"] < 0
)
surplus_count += sum(
- -townhall_info["deficit"] for townhall_tag, townhall_info in surplus_townhalls.items()
+ -townhall_info["deficit"]
+ for townhall_tag, townhall_info in surplus_townhalls.items()
if townhall_info["deficit"] < 0
)
@@ -347,8 +357,10 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas=
if worker_pool.amount >= deficit_gas_count:
break
workers_near_gas = self.workers.closer_than(10, gas_info["unit"]).filter(
- lambda w: w.tag not in worker_pool_tags and len(w.orders) == 1 and w.orders[0].ability.id in
- [AbilityId.HARVEST_GATHER] and w.orders[0].target in mineral_tags
+ lambda w: w.tag not in worker_pool_tags
+ and len(w.orders) == 1
+ and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
+ and w.orders[0].target in mineral_tags
)
while workers_near_gas.amount > 0 and worker_pool.amount < deficit_gas_count:
w = workers_near_gas.pop()
diff --git a/examples/terran/onebase_battlecruiser.py b/examples/terran/onebase_battlecruiser.py
index cd7fb221..1173af5e 100644
--- a/examples/terran/onebase_battlecruiser.py
+++ b/examples/terran/onebase_battlecruiser.py
@@ -1,4 +1,4 @@
-from typing import List, Tuple
+from __future__ import annotations
from sc2 import maps
from sc2.bot_ai import BotAI
@@ -13,9 +13,8 @@
class BCRushBot(BotAI):
-
- def select_target(self) -> Tuple[Point2, bool]:
- """ Select an enemy target the units should attack. """
+ def select_target(self) -> tuple[Point2, bool]:
+ """Select an enemy target the units should attack."""
targets: Units = self.enemy_structures
if targets:
return targets.random.position, True
@@ -24,12 +23,12 @@ def select_target(self) -> Tuple[Point2, bool]:
if targets:
return targets.random.position, True
- if self.units and min((u.position.distance_to(self.enemy_start_locations[0]) for u in self.units)) < 5:
+ if self.units and min(u.position.distance_to(self.enemy_start_locations[0]) for u in self.units) < 5:
+ # pyre-ignore[7]
return self.enemy_start_locations[0].position, False
return self.mineral_field.random.position, False
- # pylint: disable=R0912
async def on_step(self, iteration):
ccs: Units = self.townhalls
# If we no longer have townhalls, attack with all workers
@@ -102,8 +101,9 @@ async def on_step(self, iteration):
# Build starport once we can build starports, up to 2
elif (
factories.ready
- and self.structures.of_type({UnitTypeId.STARPORT, UnitTypeId.STARPORTFLYING}).ready.amount +
- self.already_pending(UnitTypeId.STARPORT) < 2
+ and self.structures.of_type({UnitTypeId.STARPORT, UnitTypeId.STARPORTFLYING}).ready.amount
+ + self.already_pending(UnitTypeId.STARPORT)
+ < 2
):
if self.can_afford(UnitTypeId.STARPORT):
await self.build(
@@ -111,8 +111,8 @@ async def on_step(self, iteration):
near=cc.position.towards(self.game_info.map_center, 15).random_on_distance(8),
)
- def starport_points_to_build_addon(sp_position: Point2) -> List[Point2]:
- """ Return all points that need to be checked when trying to build an addon. Returns 4 points. """
+ def starport_points_to_build_addon(sp_position: Point2) -> list[Point2]:
+ """Return all points that need to be checked when trying to build an addon. Returns 4 points."""
addon_offset: Point2 = Point2((2.5, -0.5))
addon_position: Point2 = sp_position + addon_offset
addon_points = [
@@ -126,15 +126,17 @@ def starport_points_to_build_addon(sp_position: Point2) -> List[Point2]:
if not sp.has_add_on and self.can_afford(UnitTypeId.STARPORTTECHLAB):
addon_points = starport_points_to_build_addon(sp.position)
if all(
- self.in_map_bounds(addon_point) and self.in_placement_grid(addon_point)
- and self.in_pathing_grid(addon_point) for addon_point in addon_points
+ self.in_map_bounds(addon_point)
+ and self.in_placement_grid(addon_point)
+ and self.in_pathing_grid(addon_point)
+ for addon_point in addon_points
):
sp.build(UnitTypeId.STARPORTTECHLAB)
else:
sp(AbilityId.LIFT)
- def starport_land_positions(sp_position: Point2) -> List[Point2]:
- """ Return all points that need to be checked when trying to land at a location where there is enough space to build an addon. Returns 13 points. """
+ def starport_land_positions(sp_position: Point2) -> list[Point2]:
+ """Return all points that need to be checked when trying to land at a location where there is enough space to build an addon. Returns 13 points."""
land_positions = [(sp_position + Point2((x, y))).rounded for x in range(-1, 2) for y in range(-1, 2)]
return land_positions + starport_points_to_build_addon(sp_position)
@@ -147,10 +149,10 @@ def starport_land_positions(sp_position: Point2) -> List[Point2]:
offset_point: Point2 = Point2((-0.5, -0.5))
possible_land_positions = (sp.position.rounded + offset_point + p for p in possible_land_positions_offset)
for target_land_position in possible_land_positions:
- land_and_addon_points: List[Point2] = starport_land_positions(target_land_position)
+ land_and_addon_points: list[Point2] = starport_land_positions(target_land_position)
if all(
- self.in_map_bounds(land_pos) and self.in_placement_grid(land_pos)
- and self.in_pathing_grid(land_pos) for land_pos in land_and_addon_points
+ self.in_map_bounds(land_pos) and self.in_placement_grid(land_pos) and self.in_pathing_grid(land_pos)
+ for land_pos in land_and_addon_points
):
sp(AbilityId.LAND, target_land_position)
break
diff --git a/examples/terran/proxy_rax.py b/examples/terran/proxy_rax.py
index 5101cc36..0ce8d789 100644
--- a/examples/terran/proxy_rax.py
+++ b/examples/terran/proxy_rax.py
@@ -10,11 +10,9 @@
class ProxyRaxBot(BotAI):
-
async def on_start(self):
self.client.game_step = 2
- # pylint: disable=R0912
async def on_step(self, iteration):
# If we don't have a townhall anymore, send all units to attack
ccs: Units = self.townhalls(UnitTypeId.COMMANDCENTER)
@@ -45,8 +43,9 @@ async def on_step(self, iteration):
await self.build(UnitTypeId.SUPPLYDEPOT, near=cc.position.towards(self.game_info.map_center, 5))
# Build proxy barracks
- elif self.structures(UnitTypeId.BARRACKS
- ).amount < 3 or (self.minerals > 400 and self.structures(UnitTypeId.BARRACKS).amount < 5):
+ elif self.structures(UnitTypeId.BARRACKS).amount < 3 or (
+ self.minerals > 400 and self.structures(UnitTypeId.BARRACKS).amount < 5
+ ):
if self.can_afford(UnitTypeId.BARRACKS):
p: Point2 = self.game_info.map_center.towards(self.enemy_start_locations[0], 25)
await self.build(UnitTypeId.BARRACKS, near=p)
diff --git a/examples/terran/ramp_wall.py b/examples/terran/ramp_wall.py
index 47c675df..244bce99 100644
--- a/examples/terran/ramp_wall.py
+++ b/examples/terran/ramp_wall.py
@@ -1,5 +1,4 @@
import random
-from typing import FrozenSet, Set
import numpy as np
from loguru import logger
@@ -17,12 +16,9 @@
class RampWallBot(BotAI):
-
- # pylint: disable=W0231
def __init__(self):
self.unit_command_uses_self_do = False
- # pylint: disable=R0912
async def on_step(self, iteration):
ccs: Units = self.townhalls(UnitTypeId.COMMANDCENTER)
if not ccs:
@@ -74,7 +70,7 @@ async def on_step(self, iteration):
# Draw if two selected units are facing each other - green if this guy is facing the other, red if he is not
self.draw_facing_units()
- depot_placement_positions: FrozenSet[Point2] = self.main_base_ramp.corner_depots
+ depot_placement_positions: frozenset[Point2] = self.main_base_ramp.corner_depots
# Uncomment the following if you want to build 3 supply depots in the wall instead of a barracks in the middle + 2 depots in the corner
# depot_placement_positions = self.main_base_ramp.corner_depots | {self.main_base_ramp.depot_in_middle}
@@ -86,9 +82,8 @@ async def on_step(self, iteration):
# Filter locations close to finished supply depots
if depots:
- depot_placement_positions: Set[Point2] = {
- d
- for d in depot_placement_positions if depots.closest_distance_to(d) > 1
+ depot_placement_positions: set[Point2] = {
+ d for d in depot_placement_positions if depots.closest_distance_to(d) > 1
}
# Build depots
@@ -248,7 +243,7 @@ def draw_example(self):
self.client.debug_text_simple(text="Hello world2!")
def draw_facing_units(self):
- """ Draws green box on top of selected_unit2, if selected_unit2 is facing selected_unit1 """
+ """Draws green box on top of selected_unit2, if selected_unit2 is facing selected_unit1"""
selected_unit1: Unit
selected_unit2: Unit
red = Point3((255, 0, 0))
diff --git a/examples/too_slow_bot.py b/examples/too_slow_bot.py
index b36abd99..28d32c0e 100644
--- a/examples/too_slow_bot.py
+++ b/examples/too_slow_bot.py
@@ -9,7 +9,6 @@
class SlowBot(ProxyRaxBot):
-
async def on_step(self, iteration):
await asyncio.sleep(random.random())
await super().on_step(iteration)
diff --git a/examples/watch_replay.py b/examples/watch_replay.py
index 30cc5d33..251eb5ad 100644
--- a/examples/watch_replay.py
+++ b/examples/watch_replay.py
@@ -1,4 +1,3 @@
-import os
import platform
from pathlib import Path
@@ -32,14 +31,13 @@ async def on_step(self, iteration: int):
if not replay_path.is_file():
logger.warning(f"You are on linux, please put the replay in directory {home_replay_folder}")
raise FileNotFoundError
- replay_path = str(replay_path)
- elif os.path.isabs(replay_name):
- replay_path = replay_name
+ elif Path(replay_name).is_absolute():
+ replay_path = Path(replay_name)
else:
# Convert relative path to absolute path, assuming this replay is in this folder
- folder_path = os.path.dirname(__file__)
- replay_path = os.path.join(folder_path, replay_name)
- assert os.path.isfile(
- replay_path
- ), "Run worker_rush.py in the same folder first to generate a replay. Then run watch_replay.py again."
- run_replay(my_observer_ai, replay_path=replay_path)
+ folder_path = Path(__file__).parent
+ replay_path = folder_path / replay_name
+ assert replay_path.is_file(), (
+ "Run worker_rush.py in the same folder first to generate a replay. Then run watch_replay.py again."
+ )
+ run_replay(my_observer_ai, replay_path=str(replay_path))
diff --git a/examples/worker_rush.py b/examples/worker_rush.py
index 537d4cf4..686c7256 100644
--- a/examples/worker_rush.py
+++ b/examples/worker_rush.py
@@ -6,7 +6,6 @@
class WorkerRushBot(BotAI):
-
async def on_step(self, iteration):
if iteration == 0:
for worker in self.workers:
diff --git a/examples/worker_stack_bot.py b/examples/worker_stack_bot.py
index eed7acab..0a0cbbb2 100644
--- a/examples/worker_stack_bot.py
+++ b/examples/worker_stack_bot.py
@@ -13,7 +13,7 @@
- Re-assign workers when gas mines out
"""
-from typing import Dict, Set
+from __future__ import annotations
from loguru import logger
@@ -27,12 +27,10 @@
from sc2.units import Units
-# pylint: disable=W0231
class WorkerStackBot(BotAI):
-
def __init__(self):
- self.worker_to_mineral_patch_dict: Dict[int, int] = {}
- self.mineral_patch_to_list_of_workers: Dict[int, Set[int]] = {}
+ self.worker_to_mineral_patch_dict: dict[int, int] = {}
+ self.mineral_patch_to_list_of_workers: dict[int, set[int]] = {}
self.minerals_sorted_by_distance: Units = Units([], self)
# Distance 0.01 to 0.1 seems fine
self.townhall_distance_threshold = 0.01
@@ -44,10 +42,9 @@ async def on_start(self):
await self.assign_workers()
async def assign_workers(self):
- self.minerals_sorted_by_distance = self.mineral_field.closer_than(10,
- self.start_location).sorted_by_distance_to(
- self.start_location
- )
+ self.minerals_sorted_by_distance = self.mineral_field.closer_than(
+ 10, self.start_location
+ ).sorted_by_distance_to(self.start_location)
# Assign workers to mineral patch, start with the mineral patch closest to base
for mineral in self.minerals_sorted_by_distance:
@@ -69,16 +66,15 @@ async def assign_workers(self):
async def on_step(self, iteration: int):
if self.worker_to_mineral_patch_dict:
# Quick-access cache mineral tag to mineral Unit
- minerals: Dict[int, Unit] = {mineral.tag: mineral for mineral in self.mineral_field}
+ minerals: dict[int, Unit] = {mineral.tag: mineral for mineral in self.mineral_field}
+ worker: Unit
for worker in self.workers:
if not self.townhalls:
logger.error("All townhalls died - can't return resources")
break
-
- worker: Unit
mineral_tag = self.worker_to_mineral_patch_dict[worker.tag]
- mineral = minerals.get(mineral_tag, None)
+ mineral = minerals.get(mineral_tag)
if mineral is None:
logger.error(f"Mined out mineral with tag {mineral_tag} for worker {worker.tag}")
continue
@@ -93,6 +89,7 @@ async def on_step(self, iteration: int):
# Move worker in front of the nexus to avoid deceleration until the last moment
if worker.distance_to(th) > th.radius + worker.radius + self.townhall_distance_threshold:
pos: Point2 = th.position
+ # pyre-ignore[6]
worker.move(pos.towards(worker, th.radius * self.townhall_distance_factor))
worker.return_resource(queue=True)
else:
@@ -100,6 +97,7 @@ async def on_step(self, iteration: int):
worker.gather(mineral, queue=True)
# Print info every 30 game-seconds
+ # pyre-ignore[16]
if self.state.game_loop % (22.4 * 30) == 0:
logger.info(f"{self.time_formatted} Mined a total of {int(self.state.score.collected_minerals)} minerals")
@@ -107,8 +105,7 @@ async def on_step(self, iteration: int):
def main():
run_game(
maps.get("AcropolisLE"),
- [Bot(Race.Protoss, WorkerStackBot()),
- Computer(Race.Terran, Difficulty.Medium)],
+ [Bot(Race.Protoss, WorkerStackBot()), Computer(Race.Terran, Difficulty.Medium)],
realtime=False,
random_seed=0,
)
diff --git a/examples/zerg/banes_banes_banes.py b/examples/zerg/banes_banes_banes.py
new file mode 100644
index 00000000..85a00c70
--- /dev/null
+++ b/examples/zerg/banes_banes_banes.py
@@ -0,0 +1,139 @@
+import random
+
+from sc2 import maps
+from sc2.bot_ai import BotAI
+from sc2.data import Difficulty, Race
+from sc2.ids.ability_id import AbilityId
+from sc2.ids.unit_typeid import UnitTypeId
+from sc2.main import run_game
+from sc2.player import Bot, Computer
+from sc2.position import Point2
+from sc2.unit import Unit
+from sc2.units import Units
+
+
+class BanesBanesBanes(BotAI):
+ """
+ A dumb bot that a-moves banes.
+ Use to check if bane morphs are working correctly
+ """
+
+ def select_target(self) -> Point2:
+ if self.enemy_structures:
+ return random.choice(self.enemy_structures).position
+ return self.enemy_start_locations[0]
+
+ async def on_step(self, iteration):
+ larvae: Units = self.larva
+ lings: Units = self.units(UnitTypeId.ZERGLING)
+ # Send all idle banes to enemy
+ if banes := [u for u in self.units if u.type_id == UnitTypeId.BANELING and u.is_idle]:
+ for unit in banes:
+ unit.attack(self.select_target())
+
+ # If supply is low, train overlords
+ if (
+ self.supply_left < 2
+ and larvae
+ and self.can_afford(UnitTypeId.OVERLORD)
+ and not self.already_pending(UnitTypeId.OVERLORD)
+ ):
+ larvae.random.train(UnitTypeId.OVERLORD)
+ return
+
+ # If bane nest is ready, train banes
+ if lings and self.can_afford(UnitTypeId.BANELING) and self.structures(UnitTypeId.BANELINGNEST).ready:
+ # TODO: Get lings.random.train(UnitTypeId.BANELING) to work
+ # Broken on recent patches
+ # lings.random.train(UnitTypeId.BANELING)
+
+ # This way is working
+ lings.random(AbilityId.MORPHTOBANELING_BANELING)
+ return
+
+ # If all our townhalls are dead, send all our units to attack
+ if not self.townhalls:
+ for unit in self.units.of_type({UnitTypeId.DRONE, UnitTypeId.QUEEN, UnitTypeId.ZERGLING}):
+ unit.attack(self.enemy_start_locations[0])
+ return
+
+ hq: Unit = self.townhalls.first
+
+ # Send idle queens with >=25 energy to inject
+ for queen in self.units(UnitTypeId.QUEEN).idle:
+ # The following checks if the inject ability is in the queen abilitys - basically it checks if we have enough energy and if the ability is off-cooldown
+ # abilities = await self.get_available_abilities(queen)
+ # if AbilityId.EFFECT_INJECTLARVA in abilities:
+ if queen.energy >= 25:
+ queen(AbilityId.EFFECT_INJECTLARVA, hq)
+
+ # Build spawning pool
+ if self.structures(UnitTypeId.SPAWNINGPOOL).amount + self.already_pending(UnitTypeId.SPAWNINGPOOL) == 0:
+ if self.can_afford(UnitTypeId.SPAWNINGPOOL):
+ await self.build(
+ UnitTypeId.SPAWNINGPOOL,
+ near=hq.position.towards(self.game_info.map_center, 5),
+ )
+
+ # Upgrade to lair if spawning pool is complete
+ # if self.structures(UnitTypeId.SPAWNINGPOOL).ready:
+ # if hq.is_idle and not self.townhalls(UnitTypeId.LAIR):
+ # if self.can_afford(UnitTypeId.LAIR):
+ # hq.build(UnitTypeId.LAIR)
+
+ # If lair is ready and we have no hydra den on the way: build hydra den
+ if self.structures(UnitTypeId.SPAWNINGPOOL).ready and self.can_afford(UnitTypeId.BANELINGNEST):
+ if self.structures(UnitTypeId.BANELINGNEST).amount + self.already_pending(UnitTypeId.BANELINGNEST) == 0:
+ await self.build(
+ UnitTypeId.BANELINGNEST,
+ near=hq.position.towards(self.game_info.map_center, 5),
+ )
+
+ # If we dont have both extractors: build them
+ if (
+ self.structures(UnitTypeId.SPAWNINGPOOL)
+ and self.gas_buildings.amount + self.already_pending(UnitTypeId.EXTRACTOR) < 2
+ and self.can_afford(UnitTypeId.EXTRACTOR)
+ ):
+ # May crash if we dont have any drones
+ for vg in self.vespene_geyser.closer_than(10, hq):
+ drone: Unit = self.workers.random
+ drone.build_gas(vg)
+ break
+
+ # If we have less than 22 drones, build drones
+ if self.supply_workers + self.already_pending(UnitTypeId.DRONE) < 22:
+ if larvae and self.can_afford(UnitTypeId.DRONE):
+ larva: Unit = larvae.random
+ larva.train(UnitTypeId.DRONE)
+ return
+
+ # Saturate gas
+ for a in self.gas_buildings:
+ if a.assigned_harvesters < a.ideal_harvesters:
+ w: Units = self.workers.closer_than(10, a)
+ if w:
+ w.random.gather(a)
+
+ # Build queen once the pool is done
+ if self.structures(UnitTypeId.SPAWNINGPOOL).ready:
+ if not self.units(UnitTypeId.QUEEN) and hq.is_idle:
+ if self.can_afford(UnitTypeId.QUEEN):
+ hq.train(UnitTypeId.QUEEN)
+
+ # Train zerglings
+ if larvae and self.can_afford(UnitTypeId.ZERGLING):
+ larvae.random.train(UnitTypeId.ZERGLING)
+
+
+def main():
+ run_game(
+ maps.get("GoldenAura513AIE"),
+ [Bot(Race.Zerg, BanesBanesBanes()), Computer(Race.Terran, Difficulty.Medium)],
+ realtime=False,
+ save_replay_as="ZvT.SC2Replay",
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/zerg/expand_everywhere.py b/examples/zerg/expand_everywhere.py
index f8f5fe64..552ae0f9 100644
--- a/examples/zerg/expand_everywhere.py
+++ b/examples/zerg/expand_everywhere.py
@@ -1,6 +1,5 @@
import random
from contextlib import suppress
-from typing import Set
from sc2 import maps
from sc2.bot_ai import BotAI
@@ -13,7 +12,6 @@
class ExpandEverywhere(BotAI):
-
async def on_start(self):
self.client.game_step = 50
await self.client.debug_show_map()
@@ -21,7 +19,9 @@ async def on_start(self):
async def on_step(self, iteration):
# Build overlords if about to be supply blocked
if (
- self.supply_left < 2 and self.supply_cap < 200 and self.already_pending(UnitTypeId.OVERLORD) < 2
+ self.supply_left < 2
+ and self.supply_cap < 200
+ and self.already_pending(UnitTypeId.OVERLORD) < 2
and self.can_afford(UnitTypeId.OVERLORD)
):
self.train(UnitTypeId.OVERLORD)
@@ -29,8 +29,8 @@ async def on_step(self, iteration):
# While we have less than 16 drones, make more drones
if (
self.can_afford(UnitTypeId.DRONE)
- and self.supply_workers - self.worker_en_route_to_build(UnitTypeId.HATCHERY) <
- (self.townhalls.amount + self.placeholders(UnitTypeId.HATCHERY).amount) * 16
+ and self.supply_workers - self.worker_en_route_to_build(UnitTypeId.HATCHERY)
+ < (self.townhalls.amount + self.placeholders(UnitTypeId.HATCHERY).amount) * 16
):
self.train(UnitTypeId.DRONE)
@@ -40,10 +40,10 @@ async def on_step(self, iteration):
# Expand if we have 300 minerals, try to expand if there is one more expansion location available
with suppress(AssertionError):
if self.can_afford(UnitTypeId.HATCHERY):
- planned_hatch_locations: Set[Point2] = {placeholder.position for placeholder in self.placeholders}
- my_structure_locations: Set[Point2] = {structure.position for structure in self.structures}
- enemy_structure_locations: Set[Point2] = {structure.position for structure in self.enemy_structures}
- blocked_locations: Set[Point2] = (
+ planned_hatch_locations: set[Point2] = {placeholder.position for placeholder in self.placeholders}
+ my_structure_locations: set[Point2] = {structure.position for structure in self.structures}
+ enemy_structure_locations: set[Point2] = {structure.position for structure in self.enemy_structures}
+ blocked_locations: set[Point2] = (
my_structure_locations | planned_hatch_locations | enemy_structure_locations
)
shuffled_expansions = self.expansion_locations_list.copy()
@@ -61,7 +61,7 @@ async def on_step(self, iteration):
await self.client.debug_kill_unit(self.enemy_units)
async def on_building_construction_complete(self, unit: Unit):
- """ Set rally point of new hatcheries. """
+ """Set rally point of new hatcheries."""
if unit.type_id == UnitTypeId.HATCHERY and self.mineral_field:
mf = self.mineral_field.closest_to(unit)
unit.smart(mf)
@@ -70,8 +70,7 @@ async def on_building_construction_complete(self, unit: Unit):
def main():
run_game(
maps.get("AcropolisLE"),
- [Bot(Race.Zerg, ExpandEverywhere()),
- Computer(Race.Terran, Difficulty.Medium)],
+ [Bot(Race.Zerg, ExpandEverywhere()), Computer(Race.Terran, Difficulty.Medium)],
realtime=False,
save_replay_as="ZvT.SC2Replay",
)
diff --git a/examples/zerg/hydralisk_push.py b/examples/zerg/hydralisk_push.py
index 9ea30d13..6e6d17e2 100644
--- a/examples/zerg/hydralisk_push.py
+++ b/examples/zerg/hydralisk_push.py
@@ -14,13 +14,11 @@
class Hydralisk(BotAI):
-
def select_target(self) -> Point2:
if self.enemy_structures:
return random.choice(self.enemy_structures).position
return self.enemy_start_locations[0]
- # pylint: disable=R0912
async def on_step(self, iteration):
larvae: Units = self.larva
forces: Units = self.units.of_type({UnitTypeId.ZERGLING, UnitTypeId.HYDRALISK})
@@ -39,11 +37,13 @@ async def on_step(self, iteration):
hydra_dens = self.structures(UnitTypeId.HYDRALISKDEN)
if hydra_dens:
for hydra_den in hydra_dens.ready.idle:
- if self.already_pending_upgrade(UpgradeId.EVOLVEGROOVEDSPINES
- ) == 0 and self.can_afford(UpgradeId.EVOLVEGROOVEDSPINES):
+ if self.already_pending_upgrade(UpgradeId.EVOLVEGROOVEDSPINES) == 0 and self.can_afford(
+ UpgradeId.EVOLVEGROOVEDSPINES
+ ):
hydra_den.research(UpgradeId.EVOLVEGROOVEDSPINES)
- elif self.already_pending_upgrade(UpgradeId.EVOLVEMUSCULARAUGMENTS
- ) == 0 and self.can_afford(UpgradeId.EVOLVEMUSCULARAUGMENTS):
+ elif self.already_pending_upgrade(UpgradeId.EVOLVEMUSCULARAUGMENTS) == 0 and self.can_afford(
+ UpgradeId.EVOLVEMUSCULARAUGMENTS
+ ):
hydra_den.research(UpgradeId.EVOLVEMUSCULARAUGMENTS)
# If hydra den is ready, train hydra
diff --git a/examples/zerg/onebase_broodlord.py b/examples/zerg/onebase_broodlord.py
index 88561a3d..72d75bca 100644
--- a/examples/zerg/onebase_broodlord.py
+++ b/examples/zerg/onebase_broodlord.py
@@ -1,3 +1,4 @@
+# noqa: SIM102
import random
from sc2 import maps
@@ -13,13 +14,11 @@
class BroodlordBot(BotAI):
-
def select_target(self) -> Point2:
if self.enemy_structures:
return random.choice(self.enemy_structures).position
return self.enemy_start_locations[0]
- # pylint: disable=R0912
async def on_step(self, iteration):
larvae: Units = self.larva
forces: Units = self.units.of_type({UnitTypeId.ZERGLING, UnitTypeId.CORRUPTOR, UnitTypeId.BROODLORD})
diff --git a/examples/zerg/worker_split.py b/examples/zerg/worker_split.py
index 3d78a5bc..3edec5bb 100644
--- a/examples/zerg/worker_split.py
+++ b/examples/zerg/worker_split.py
@@ -18,9 +18,8 @@
class WorkerSplitBot(BotAI):
-
async def on_before_start(self):
- """ This function is run before the expansion locations and ramps are calculated. These calculations can take up to a second, depending on the CPU. """
+ """This function is run before the expansion locations and ramps are calculated. These calculations can take up to a second, depending on the CPU."""
mf: Units = self.mineral_field
for w in self.workers:
w.gather(mf.closest_to(w))
@@ -29,7 +28,7 @@ async def on_before_start(self):
await asyncio.sleep(3)
async def on_start(self):
- """ This function is run after the expansion locations and ramps are calculated. """
+ """This function is run after the expansion locations and ramps are calculated."""
async def on_step(self, iteration):
if iteration % 10 == 0:
diff --git a/examples/zerg/zerg_rush.py b/examples/zerg/zerg_rush.py
index 6fa7c47b..93139434 100644
--- a/examples/zerg/zerg_rush.py
+++ b/examples/zerg/zerg_rush.py
@@ -15,16 +15,13 @@
from sc2.units import Units
-# pylint: disable=W0231
class ZergRushBot(BotAI):
-
def __init__(self):
self.on_end_called = False
async def on_start(self):
self.client.game_step = 2
- # pylint: disable=R0912
async def on_step(self, iteration):
if iteration == 0:
await self.chat_send("(glhf)")
@@ -63,8 +60,9 @@ async def on_step(self, iteration):
drone.gather(mineral, queue=True)
# If we have 100 vespene, this will try to research zergling speed once the spawning pool is at 100% completion
- if self.already_pending_upgrade(UpgradeId.ZERGLINGMOVEMENTSPEED
- ) == 0 and self.can_afford(UpgradeId.ZERGLINGMOVEMENTSPEED):
+ if self.already_pending_upgrade(UpgradeId.ZERGLINGMOVEMENTSPEED) == 0 and self.can_afford(
+ UpgradeId.ZERGLINGMOVEMENTSPEED
+ ):
spawning_pools_ready: Units = self.structures(UnitTypeId.SPAWNINGPOOL).ready
if spawning_pools_ready:
self.research(UpgradeId.ZERGLINGMOVEMENTSPEED)
@@ -75,7 +73,8 @@ async def on_step(self, iteration):
# While we have less than 88 vespene mined: send drones into extractor one frame at a time
if (
- self.gas_buildings.ready and self.vespene < 88
+ self.gas_buildings.ready
+ and self.vespene < 88
and self.already_pending_upgrade(UpgradeId.ZERGLINGMOVEMENTSPEED) == 0
):
extractor: Unit = self.gas_buildings.first
@@ -101,7 +100,8 @@ async def on_step(self, iteration):
# If we have no extractor, build extractor
if (
self.gas_buildings.amount + self.already_pending(UnitTypeId.EXTRACTOR) == 0
- and self.can_afford(UnitTypeId.EXTRACTOR) and self.workers
+ and self.can_afford(UnitTypeId.EXTRACTOR)
+ and self.workers
):
drone: Unit = self.workers.random
target: Unit = self.vespene_geyser.closest_to(drone)
@@ -136,6 +136,7 @@ def draw_creep_pixelmap(self):
color = Point3((0, 255, 0))
self.client.debug_box2_out(pos, half_vertex_length=0.25, color=color)
+ # pyre-ignore[11]
async def on_end(self, game_result: Result):
self.on_end_called = True
logger.info(f"{self.time_formatted} On end was called")
diff --git a/generate_dicts_from_data_json.py b/generate_dicts_from_data_json.py
index fdd6775c..907ca581 100644
--- a/generate_dicts_from_data_json.py
+++ b/generate_dicts_from_data_json.py
@@ -8,19 +8,15 @@
data.json origin:
https://github.com/BurnySc2/sc2-techtree/tree/develop/data
-
-json viewers to inspect the data.json manually:
-http://jsonviewer.stack.hu/
-https://jsonformatter.org/json-viewer
"""
+
+from __future__ import annotations
+
import json
import lzma
-import os
import pickle
-import subprocess
from collections import OrderedDict
from pathlib import Path
-from typing import Dict, List, Optional, Set, Union
from loguru import logger
@@ -37,19 +33,17 @@ def get_map_file_path() -> Path:
# Custom repr function so that the output is always the same and only changes when there were changes in the data.json tech tree file
# The output just needs to be ordered (sorted by enum name), but it does not matter anymore if the bot then imports an unordered dict and set
class OrderedDict2(OrderedDict):
-
def __repr__(self):
if not self:
return "{}"
return (
- "{" +
- ", ".join(f"{repr(key)}: {repr(value)}"
- for key, value in sorted(self.items(), key=lambda u: u[0].name)) + "}"
+ "{"
+ + ", ".join(f"{repr(key)}: {repr(value)}" for key, value in sorted(self.items(), key=lambda u: u[0].name))
+ + "}"
)
class OrderedSet2(set):
-
def __repr__(self):
if not self:
return "set()"
@@ -67,11 +61,8 @@ def dump_dict_to_file(
logger.info(my_dict)
f.write(repr(my_dict))
- # Apply formatting
- subprocess.run(["poetry", "run", "yapf", file_path, "-i"])
-
-def generate_init_file(dict_file_paths: List[Path], file_path: Path, file_header: str):
+def generate_init_file(dict_file_paths: list[Path], file_path: Path, file_header: str):
base_file_names = sorted(path.stem for path in dict_file_paths)
with file_path.open("w") as f:
@@ -82,9 +73,6 @@ def generate_init_file(dict_file_paths: List[Path], file_path: Path, file_header
logger.info(all_line)
f.write(all_line)
- # Apply formatting
- subprocess.run(["poetry", "run", "yapf", file_path, "-i"])
-
def get_unit_train_build_abilities(data):
ability_data = data["Ability"]
@@ -92,14 +80,14 @@ def get_unit_train_build_abilities(data):
_upgrade_data = data["Upgrade"]
# From which abilities can a unit be trained
- train_abilities: Dict[UnitTypeId, Set[AbilityId]] = OrderedDict2()
+ train_abilities: dict[UnitTypeId, set[AbilityId]] = OrderedDict2()
# If the ability requires a placement position
- ability_requires_placement: Set[AbilityId] = set()
+ ability_requires_placement: set[AbilityId] = set()
# Map ability to unittypeid
- ability_to_unittypeid_dict: Dict[AbilityId, UnitTypeId] = OrderedDict2()
+ ability_to_unittypeid_dict: dict[AbilityId, UnitTypeId] = OrderedDict2()
# From which abilities can a unit be morphed
- # unit_morph_abilities: Dict[UnitTypeId, Set[AbilityId]] = {}
+ # unit_morph_abilities: dict[UnitTypeId, set[AbilityId]] = {}
entry: dict
for entry in ability_data:
@@ -126,10 +114,13 @@ def get_unit_train_build_abilities(data):
# Collect larva morph abilities, and one way morphs (exclude burrow, hellbat morph, siege tank siege)
# Also doesnt include building addons
if not train_unit_type_id_value and (
- "LARVATRAIN_" in ability_id.name or ability_id in {
+ "LARVATRAIN_" in ability_id.name
+ or ability_id
+ in {
AbilityId.MORPHTOBROODLORD_BROODLORD,
AbilityId.MORPHZERGLINGTOBANELING_BANELING,
AbilityId.MORPHTORAVAGER_RAVAGER,
+ AbilityId.MORPHTOBANELING_BANELING,
AbilityId.MORPH_LURKER,
AbilityId.UPGRADETOLAIR_LAIR,
AbilityId.UPGRADETOHIVE_HIVE,
@@ -184,7 +175,7 @@ def get_unit_train_build_abilities(data):
}
}
"""
- unit_train_abilities: Dict[UnitTypeId, Dict[str, Union[AbilityId, bool, UnitTypeId]]] = OrderedDict2()
+ unit_train_abilities: dict[UnitTypeId, dict[str, AbilityId | bool | UnitTypeId]] = OrderedDict2()
for entry in unit_data:
unit_abilities = entry.get("abilities", [])
unit_type = UnitTypeId(entry["id"])
@@ -198,7 +189,7 @@ def get_unit_train_build_abilities(data):
continue
requires_techlab: bool = False
- required_building: Optional[UnitTypeId] = None
+ required_building: UnitTypeId | None = None
requires_placement_position: bool = False
requires_power: bool = False
"""
@@ -211,12 +202,12 @@ def get_unit_train_build_abilities(data):
}
]
"""
- requirements: List[Dict[str, int]] = ability_info.get("requirements", [])
+ requirements: list[dict[str, int]] = ability_info.get("requirements", [])
if requirements:
# Assume train abilities only have one tech building requirement; thors requiring armory and techlab is seperatedly counted
- assert (
- len([req for req in requirements if req.get("building", 0)]) <= 1
- ), f"Error: Building {unit_type} has more than one tech requirements with train ability {ability_id}"
+ assert len([req for req in requirements if req.get("building", 0)]) <= 1, (
+ f"Error: Building {unit_type} has more than one tech requirements with train ability {ability_id}"
+ )
# UnitTypeId 5 == Techlab
requires_techlab: bool = any(req for req in requirements if req.get("addon", 0) == 5)
requires_tech_builing_id_value: int = next(
@@ -255,7 +246,7 @@ def get_upgrade_abilities(data):
unit_data = data["Unit"]
_upgrade_data = data["Upgrade"]
- ability_to_upgrade_dict: Dict[AbilityId, UpgradeId] = OrderedDict2()
+ ability_to_upgrade_dict: dict[AbilityId, UpgradeId] = OrderedDict2()
"""
We want to be able to research an upgrade by doing
await self.can_research(UpgradeId, return_idle_structures=True) -> returns list of idle structures that can research it
@@ -268,7 +259,6 @@ def get_upgrade_abilities(data):
if isinstance(entry.get("target", {}), str):
continue
ability_id: AbilityId = AbilityId(entry["id"])
- researched_ability_id: UnitTypeId
upgrade_id_value: int = entry.get("target", {}).get("Research", {}).get("upgrade", 0)
if upgrade_id_value:
@@ -373,12 +363,12 @@ def get_unit_abilities(data: dict):
unit_data = data["Unit"]
_upgrade_data = data["Upgrade"]
- all_unit_abilities: Dict[UnitTypeId, Set[AbilityId]] = OrderedDict2()
+ all_unit_abilities: dict[UnitTypeId, set[AbilityId]] = OrderedDict2()
entry: dict
for entry in unit_data:
entry_unit_abilities = entry.get("abilities", [])
unit_type = UnitTypeId(entry["id"])
- current_collected_unit_abilities: Set[AbilityId] = OrderedSet2()
+ current_collected_unit_abilities: set[AbilityId] = OrderedSet2()
for ability_info in entry_unit_abilities:
ability_id_value: int = ability_info.get("ability", 0)
if ability_id_value:
@@ -404,26 +394,26 @@ def generate_unit_alias_dict(data: dict):
raw_game_data, raw_game_info, raw_observation = pickle.load(f)
game_data = GameData(raw_game_data.data)
- all_unit_aliases: Dict[UnitTypeId, UnitTypeId] = OrderedDict2()
- all_tech_aliases: Dict[UnitTypeId, Set[UnitTypeId]] = OrderedDict2()
+ all_unit_aliases: dict[UnitTypeId, UnitTypeId] = OrderedDict2()
+ all_tech_aliases: dict[UnitTypeId, set[UnitTypeId]] = OrderedDict2()
entry: dict
for entry in unit_data:
unit_type_value = entry["id"]
unit_type = UnitTypeId(entry["id"])
- current_unit_tech_aliases: Set[UnitTypeId] = OrderedSet2()
+ current_unit_tech_aliases: set[UnitTypeId] = OrderedSet2()
- assert (
- unit_type_value in game_data.units
- ), f"Unit {unit_type} not listed in game_data.units - perhaps pickled file {pickled_file_path} is outdated?"
+ assert unit_type_value in game_data.units, (
+ f"Unit {unit_type} not listed in game_data.units - perhaps pickled file {pickled_file_path} is outdated?"
+ )
unit_alias: int = game_data.units[unit_type_value]._proto.unit_alias
if unit_alias:
# Might be 0 if it has no alias
unit_alias_unit_type_id = UnitTypeId(unit_alias)
all_unit_aliases[unit_type] = unit_alias_unit_type_id
- tech_aliases: List[int] = game_data.units[unit_type_value]._proto.tech_alias
+ tech_aliases: list[int] = game_data.units[unit_type_value]._proto.tech_alias
for tech_alias in tech_aliases:
# Might be 0 if it has no alias
@@ -441,15 +431,7 @@ def generate_redirect_abilities_dict(data: dict):
_unit_data = data["Unit"]
_upgrade_data = data["Upgrade"]
- # Load pickled game data files
- pickled_file_path = get_map_file_path()
- assert pickled_file_path.is_file(), f"Could not find pickled data file {pickled_file_path}"
- logger.info(f"Loading pickled game data file {pickled_file_path}")
- with lzma.open(pickled_file_path.absolute(), "rb") as f:
- raw_game_data, raw_game_info, raw_observation = pickle.load(f)
- game_data = GameData(raw_game_data.data)
-
- all_redirect_abilities: Dict[AbilityId, AbilityId] = OrderedDict2()
+ all_redirect_abilities: dict[AbilityId, AbilityId] = OrderedDict2()
entry: dict
for entry in ability_data:
@@ -460,10 +442,11 @@ def generate_redirect_abilities_dict(data: dict):
logger.info(f"Error with ability id value {ability_id_value}")
continue
- generic_redirect_ability_value: int = game_data.abilities[ability_id_value]._proto.remaps_to_ability_id
- if generic_redirect_ability_value:
- # Might be 0 if it has no redirect ability
- all_redirect_abilities[ability_id] = AbilityId(generic_redirect_ability_value)
+ generic_redirect_ability_value = entry.get("remaps_to_ability_id", 0)
+ if generic_redirect_ability_value == 0:
+ # No generic ability available
+ continue
+ all_redirect_abilities[ability_id] = AbilityId(generic_redirect_ability_value)
return all_redirect_abilities
@@ -476,7 +459,7 @@ def main():
data = json.load(f)
dicts_path = path / "sc2" / "dicts"
- os.makedirs(dicts_path, exist_ok=True)
+ Path(dicts_path).mkdir(parents=True, exist_ok=True)
# All unit train and build abilities
unit_train_abilities = get_unit_train_build_abilities(data=data)
@@ -518,7 +501,7 @@ def main():
# from sc2.ids.buff_id import BuffId
# from sc2.ids.effect_id import EffectId
-from typing import Dict, Set, Union
+from typing import Union
"""
dict_file_paths = [
@@ -534,8 +517,7 @@ def main():
init_file_path = dicts_path / "__init__.py"
init_header = f"""# DO NOT EDIT!
# This file was automatically generated by "{file_name}"
-
- """
+"""
generate_init_file(dict_file_paths=dict_file_paths, file_path=init_file_path, file_header=init_header)
dump_dict_to_file(
@@ -543,57 +525,56 @@ def main():
unit_creation_dict_path,
dict_name="TRAIN_INFO",
file_header=file_header,
- dict_type_annotation=": Dict[UnitTypeId, Dict[UnitTypeId, Dict[str, Union[AbilityId, bool, UnitTypeId]]]]",
+ dict_type_annotation=": dict[UnitTypeId, dict[UnitTypeId, dict[str, Union[AbilityId, bool, UnitTypeId]]]]",
)
dump_dict_to_file(
unit_research_abilities,
unit_research_abilities_dict_path,
dict_name="RESEARCH_INFO",
file_header=file_header,
- dict_type_annotation=
- ": Dict[UnitTypeId, Dict[UpgradeId, Dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]]",
+ dict_type_annotation=": dict[UnitTypeId, dict[UpgradeId, dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]]",
)
dump_dict_to_file(
unit_trained_from,
unit_trained_from_dict_path,
dict_name="UNIT_TRAINED_FROM",
file_header=file_header,
- dict_type_annotation=": Dict[UnitTypeId, Set[UnitTypeId]]",
+ dict_type_annotation=": dict[UnitTypeId, set[UnitTypeId]]",
)
dump_dict_to_file(
upgrade_researched_from,
upgrade_researched_from_dict_path,
dict_name="UPGRADE_RESEARCHED_FROM",
file_header=file_header,
- dict_type_annotation=": Dict[UpgradeId, UnitTypeId]",
+ dict_type_annotation=": dict[UpgradeId, UnitTypeId]",
)
dump_dict_to_file(
unit_abilities,
unit_abilities_dict_path,
dict_name="UNIT_ABILITIES",
file_header=file_header,
- dict_type_annotation=": Dict[UnitTypeId, Set[AbilityId]]",
+ dict_type_annotation=": dict[UnitTypeId, set[AbilityId]]",
)
dump_dict_to_file(
unit_unit_alias,
unit_unit_alias_dict_path,
dict_name="UNIT_UNIT_ALIAS",
file_header=file_header,
- dict_type_annotation=": Dict[UnitTypeId, UnitTypeId]",
+ dict_type_annotation=": dict[UnitTypeId, UnitTypeId]",
)
dump_dict_to_file(
unit_tech_alias,
unit_tech_alias_dict_path,
dict_name="UNIT_TECH_ALIAS",
file_header=file_header,
- dict_type_annotation=": Dict[UnitTypeId, Set[UnitTypeId]]",
+ dict_type_annotation=": dict[UnitTypeId, set[UnitTypeId]]",
)
dump_dict_to_file(
all_redirect_abilities,
all_redirect_abilities_path,
dict_name="GENERIC_REDIRECT_ABILITIES",
file_header=file_header,
- dict_type_annotation=": Dict[AbilityId, AbilityId]",
+ dict_type_annotation=": dict[AbilityId, AbilityId]",
)
diff --git a/poetry.lock b/poetry.lock
deleted file mode 100644
index deb50094..00000000
--- a/poetry.lock
+++ /dev/null
@@ -1,2356 +0,0 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
-
-[[package]]
-name = "aiohttp"
-version = "3.8.3"
-description = "Async http client/server framework (asyncio)"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"},
- {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"},
- {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"},
- {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"},
- {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"},
- {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"},
- {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"},
- {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"},
- {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"},
- {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"},
- {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"},
- {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"},
- {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"},
- {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"},
- {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"},
- {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"},
- {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"},
- {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"},
- {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"},
- {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"},
- {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"},
- {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"},
- {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"},
- {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"},
- {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"},
- {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"},
- {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"},
- {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"},
- {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"},
- {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"},
- {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"},
- {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"},
- {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"},
- {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"},
- {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"},
- {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"},
- {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"},
- {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"},
- {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"},
- {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"},
- {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"},
- {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"},
- {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"},
- {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"},
- {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"},
- {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"},
- {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"},
- {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"},
- {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"},
- {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"},
- {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"},
- {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"},
- {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"},
- {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"},
- {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"},
- {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"},
- {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"},
- {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"},
- {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"},
- {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"},
- {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"},
- {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"},
- {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"},
- {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"},
- {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"},
- {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"},
- {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"},
- {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"},
- {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"},
- {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"},
- {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"},
- {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"},
- {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"},
- {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"},
- {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"},
- {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"},
- {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"},
- {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"},
- {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"},
- {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"},
- {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"},
- {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"},
- {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"},
- {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"},
- {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"},
- {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"},
- {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"},
-]
-
-[package.dependencies]
-aiosignal = ">=1.1.2"
-async-timeout = ">=4.0.0a3,<5.0"
-attrs = ">=17.3.0"
-charset-normalizer = ">=2.0,<3.0"
-frozenlist = ">=1.1.1"
-multidict = ">=4.5,<7.0"
-yarl = ">=1.0,<2.0"
-
-[package.extras]
-speedups = ["Brotli", "aiodns", "cchardet"]
-
-[[package]]
-name = "aiosignal"
-version = "1.3.1"
-description = "aiosignal: a list of registered asynchronous callbacks"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
- {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
-]
-
-[package.dependencies]
-frozenlist = ">=1.1.0"
-
-[[package]]
-name = "alabaster"
-version = "0.7.12"
-description = "A configurable sidebar-enabled Sphinx theme"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
- {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
-]
-
-[[package]]
-name = "astroid"
-version = "2.12.13"
-description = "An abstract syntax tree for Python with inference support."
-category = "dev"
-optional = false
-python-versions = ">=3.7.2"
-files = [
- {file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"},
- {file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"},
-]
-
-[package.dependencies]
-lazy-object-proxy = ">=1.4.0"
-typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
-wrapt = [
- {version = ">=1.11,<2", markers = "python_version < \"3.11\""},
- {version = ">=1.14,<2", markers = "python_version >= \"3.11\""},
-]
-
-[[package]]
-name = "async-timeout"
-version = "4.0.2"
-description = "Timeout context manager for asyncio programs"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
- {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
-]
-
-[[package]]
-name = "attrs"
-version = "22.2.0"
-description = "Classes Without Boilerplate"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
- {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
-]
-
-[package.extras]
-cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
-dev = ["attrs[docs,tests]"]
-docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
-tests = ["attrs[tests-no-zope]", "zope.interface"]
-tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
-
-[[package]]
-name = "babel"
-version = "2.11.0"
-description = "Internationalization utilities"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"},
- {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"},
-]
-
-[package.dependencies]
-pytz = ">=2015.7"
-
-[[package]]
-name = "certifi"
-version = "2022.12.7"
-description = "Python package for providing Mozilla's CA Bundle."
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
- {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
-]
-
-[[package]]
-name = "cfgv"
-version = "3.3.1"
-description = "Validate configuration and produce human readable error messages."
-category = "dev"
-optional = false
-python-versions = ">=3.6.1"
-files = [
- {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
- {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
-]
-
-[[package]]
-name = "charset-normalizer"
-version = "2.1.1"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
-optional = false
-python-versions = ">=3.6.0"
-files = [
- {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
- {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
-]
-
-[package.extras]
-unicode-backport = ["unicodedata2"]
-
-[[package]]
-name = "codecov"
-version = "2.1.12"
-description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab"
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-files = [
- {file = "codecov-2.1.12-py2.py3-none-any.whl", hash = "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47"},
- {file = "codecov-2.1.12.tar.gz", hash = "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1"},
-]
-
-[package.dependencies]
-coverage = "*"
-requests = ">=2.7.9"
-
-[[package]]
-name = "colorama"
-version = "0.4.6"
-description = "Cross-platform colored terminal text."
-category = "main"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-files = [
- {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
- {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
-]
-
-[[package]]
-name = "contourpy"
-version = "1.0.6"
-description = "Python library for calculating contours of 2D quadrilateral grids"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "contourpy-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6"},
- {file = "contourpy-1.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb"},
- {file = "contourpy-1.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b"},
- {file = "contourpy-1.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8834c14b8c3dd849005e06703469db9bf96ba2d66a3f88ecc539c9a8982e0ee"},
- {file = "contourpy-1.0.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4052a8a4926d4468416fc7d4b2a7b2a3e35f25b39f4061a7e2a3a2748c4fc48"},
- {file = "contourpy-1.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c0e1308307a75e07d1f1b5f0f56b5af84538a5e9027109a7bcf6cb47c434e72"},
- {file = "contourpy-1.0.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fc4e7973ed0e1fe689435842a6e6b330eb7ccc696080dda9a97b1a1b78e41db"},
- {file = "contourpy-1.0.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08e8d09d96219ace6cb596506fb9b64ea5f270b2fb9121158b976d88871fcfd1"},
- {file = "contourpy-1.0.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f33da6b5d19ad1bb5e7ad38bb8ba5c426d2178928bc2b2c44e8823ea0ecb6ff3"},
- {file = "contourpy-1.0.6-cp310-cp310-win32.whl", hash = "sha256:12a7dc8439544ed05c6553bf026d5e8fa7fad48d63958a95d61698df0e00092b"},
- {file = "contourpy-1.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:eadad75bf91897f922e0fb3dca1b322a58b1726a953f98c2e5f0606bd8408621"},
- {file = "contourpy-1.0.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:913bac9d064cff033cf3719e855d4f1db9f1c179e0ecf3ba9fdef21c21c6a16a"},
- {file = "contourpy-1.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46deb310a276cc5c1fd27958e358cce68b1e8a515fa5a574c670a504c3a3fe30"},
- {file = "contourpy-1.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b64f747e92af7da3b85631a55d68c45a2d728b4036b03cdaba4bd94bcc85bd6f"},
- {file = "contourpy-1.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50627bf76abb6ba291ad08db583161939c2c5fab38c38181b7833423ab9c7de3"},
- {file = "contourpy-1.0.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:358f6364e4873f4d73360b35da30066f40387dd3c427a3e5432c6b28dd24a8fa"},
- {file = "contourpy-1.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c78bfbc1a7bff053baf7e508449d2765964d67735c909b583204e3240a2aca45"},
- {file = "contourpy-1.0.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e43255a83835a129ef98f75d13d643844d8c646b258bebd11e4a0975203e018f"},
- {file = "contourpy-1.0.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:375d81366afd547b8558c4720337218345148bc2fcffa3a9870cab82b29667f2"},
- {file = "contourpy-1.0.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b98c820608e2dca6442e786817f646d11057c09a23b68d2b3737e6dcb6e4a49b"},
- {file = "contourpy-1.0.6-cp311-cp311-win32.whl", hash = "sha256:0e4854cc02006ad6684ce092bdadab6f0912d131f91c2450ce6dbdea78ee3c0b"},
- {file = "contourpy-1.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:d2eff2af97ea0b61381828b1ad6cd249bbd41d280e53aea5cccd7b2b31b8225c"},
- {file = "contourpy-1.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5b117d29433fc8393b18a696d794961464e37afb34a6eeb8b2c37b5f4128a83e"},
- {file = "contourpy-1.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341330ed19074f956cb20877ad8d2ae50e458884bfa6a6df3ae28487cc76c768"},
- {file = "contourpy-1.0.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:371f6570a81dfdddbb837ba432293a63b4babb942a9eb7aaa699997adfb53278"},
- {file = "contourpy-1.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9447c45df407d3ecb717d837af3b70cfef432138530712263730783b3d016512"},
- {file = "contourpy-1.0.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:730c27978a0003b47b359935478b7d63fd8386dbb2dcd36c1e8de88cbfc1e9de"},
- {file = "contourpy-1.0.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:da1ef35fd79be2926ba80fbb36327463e3656c02526e9b5b4c2b366588b74d9a"},
- {file = "contourpy-1.0.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd2bc0c8f2e8de7dd89a7f1c10b8844e291bca17d359373203ef2e6100819edd"},
- {file = "contourpy-1.0.6-cp37-cp37m-win32.whl", hash = "sha256:3a1917d3941dd58732c449c810fa7ce46cc305ce9325a11261d740118b85e6f3"},
- {file = "contourpy-1.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:06ca79e1efbbe2df795822df2fa173d1a2b38b6e0f047a0ec7903fbca1d1847e"},
- {file = "contourpy-1.0.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e626cefff8491bce356221c22af5a3ea528b0b41fbabc719c00ae233819ea0bf"},
- {file = "contourpy-1.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dbe6fe7a1166b1ddd7b6d887ea6fa8389d3f28b5ed3f73a8f40ece1fc5a3d340"},
- {file = "contourpy-1.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e13b31d1b4b68db60b3b29f8e337908f328c7f05b9add4b1b5c74e0691180109"},
- {file = "contourpy-1.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79d239fc22c3b8d9d3de492aa0c245533f4f4c7608e5749af866949c0f1b1b9"},
- {file = "contourpy-1.0.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e8e686a6db92a46111a1ee0ee6f7fbfae4048f0019de207149f43ac1812cf95"},
- {file = "contourpy-1.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2bd02f1a7adff3a1f33e431eb96ab6d7987b039d2946a9b39fe6fb16a1036"},
- {file = "contourpy-1.0.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:03d1b9c6b44a9e30d554654c72be89af94fab7510b4b9f62356c64c81cec8b7d"},
- {file = "contourpy-1.0.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b48d94386f1994db7c70c76b5808c12e23ed7a4ee13693c2fc5ab109d60243c0"},
- {file = "contourpy-1.0.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:208bc904889c910d95aafcf7be9e677726df9ef71e216780170dbb7e37d118fa"},
- {file = "contourpy-1.0.6-cp38-cp38-win32.whl", hash = "sha256:444fb776f58f4906d8d354eb6f6ce59d0a60f7b6a720da6c1ccb839db7c80eb9"},
- {file = "contourpy-1.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:9bc407a6af672da20da74823443707e38ece8b93a04009dca25856c2d9adadb1"},
- {file = "contourpy-1.0.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aa4674cf3fa2bd9c322982644967f01eed0c91bb890f624e0e0daf7a5c3383e9"},
- {file = "contourpy-1.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f56515e7c6fae4529b731f6c117752247bef9cdad2b12fc5ddf8ca6a50965a5"},
- {file = "contourpy-1.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:344cb3badf6fc7316ad51835f56ac387bdf86c8e1b670904f18f437d70da4183"},
- {file = "contourpy-1.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b1e66346acfb17694d46175a0cea7d9036f12ed0c31dfe86f0f405eedde2bdd"},
- {file = "contourpy-1.0.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8468b40528fa1e15181cccec4198623b55dcd58306f8815a793803f51f6c474a"},
- {file = "contourpy-1.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2"},
- {file = "contourpy-1.0.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:494efed2c761f0f37262815f9e3c4bb9917c5c69806abdee1d1cb6611a7174a0"},
- {file = "contourpy-1.0.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:75a2e638042118118ab39d337da4c7908c1af74a8464cad59f19fbc5bbafec9b"},
- {file = "contourpy-1.0.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a628bba09ba72e472bf7b31018b6281fd4cc903f0888049a3724afba13b6e0b8"},
- {file = "contourpy-1.0.6-cp39-cp39-win32.whl", hash = "sha256:e1739496c2f0108013629aa095cc32a8c6363444361960c07493818d0dea2da4"},
- {file = "contourpy-1.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:a457ee72d9032e86730f62c5eeddf402e732fdf5ca8b13b41772aa8ae13a4563"},
- {file = "contourpy-1.0.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d912f0154a20a80ea449daada904a7eb6941c83281a9fab95de50529bfc3a1da"},
- {file = "contourpy-1.0.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4081918147fc4c29fad328d5066cfc751da100a1098398742f9f364be63803fc"},
- {file = "contourpy-1.0.6-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0537cc1195245bbe24f2913d1f9211b8f04eb203de9044630abd3664c6cc339c"},
- {file = "contourpy-1.0.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcd556c8fc37a342dd636d7eef150b1399f823a4462f8c968e11e1ebeabee769"},
- {file = "contourpy-1.0.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f6ca38dd8d988eca8f07305125dec6f54ac1c518f1aaddcc14d08c01aebb6efc"},
- {file = "contourpy-1.0.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c1baa49ab9fedbf19d40d93163b7d3e735d9cd8d5efe4cce9907902a6dad391f"},
- {file = "contourpy-1.0.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:211dfe2bd43bf5791d23afbe23a7952e8ac8b67591d24be3638cabb648b3a6eb"},
- {file = "contourpy-1.0.6-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38c6536c2d71ca2f7e418acaf5bca30a3af7f2a2fa106083c7d738337848dbe"},
- {file = "contourpy-1.0.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b1ee48a130da4dd0eb8055bbab34abf3f6262957832fd575e0cab4979a15a41"},
- {file = "contourpy-1.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5641927cc5ae66155d0c80195dc35726eae060e7defc18b7ab27600f39dd1fe7"},
- {file = "contourpy-1.0.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ee394502026d68652c2824348a40bf50f31351a668977b51437131a90d777ea"},
- {file = "contourpy-1.0.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b97454ed5b1368b66ed414c754cba15b9750ce69938fc6153679787402e4cdf"},
- {file = "contourpy-1.0.6-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0236875c5a0784215b49d00ebbe80c5b6b5d5244b3655a36dda88105334dea17"},
- {file = "contourpy-1.0.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c593aeff7a0171f639da92cb86d24954bbb61f8a1b530f74eb750a14685832"},
- {file = "contourpy-1.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675"},
- {file = "contourpy-1.0.6.tar.gz", hash = "sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142"},
-]
-
-[package.dependencies]
-numpy = ">=1.16"
-
-[package.extras]
-bokeh = ["bokeh", "selenium"]
-docs = ["docutils (<0.18)", "sphinx (<=5.2.0)", "sphinx-rtd-theme"]
-test = ["Pillow", "flake8", "isort", "matplotlib", "pytest"]
-test-minimal = ["pytest"]
-test-no-codebase = ["Pillow", "matplotlib", "pytest"]
-
-[[package]]
-name = "coverage"
-version = "7.0.3"
-description = "Code coverage measurement for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "coverage-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f7c51b6074a8a3063c341953dffe48fd6674f8e4b1d3c8aa8a91f58d6e716a8"},
- {file = "coverage-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:628f47eaf66727fc986d3b190d6fa32f5e6b7754a243919d28bc0fd7974c449f"},
- {file = "coverage-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89d5abf86c104de808108a25d171ad646c07eda96ca76c8b237b94b9c71e518"},
- {file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75e43c6f4ea4d122dac389aabdf9d4f0e160770a75e63372f88005d90f5bcc80"},
- {file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49da0ff241827ebb52d5d6d5a36d33b455fa5e721d44689c95df99fd8db82437"},
- {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0bce4ad5bdd0b02e177a085d28d2cea5fc57bb4ba2cead395e763e34cf934eb1"},
- {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f79691335257d60951638dd43576b9bcd6f52baa5c1c2cd07a509bb003238372"},
- {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5722269ed05fbdb94eef431787c66b66260ff3125d1a9afcc00facff8c45adf9"},
- {file = "coverage-7.0.3-cp310-cp310-win32.whl", hash = "sha256:bdbda870e0fda7dd0fe7db7135ca226ec4c1ade8aa76e96614829b56ca491012"},
- {file = "coverage-7.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:e56fae4292e216b8deeee38ace84557b9fa85b52db005368a275427cdabb8192"},
- {file = "coverage-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b82343a5bc51627b9d606f0b6b6b9551db7b6311a5dd920fa52a94beae2e8959"},
- {file = "coverage-7.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd0a8aa431f9b7ad9eb8264f55ef83cbb254962af3775092fb6e93890dea9ca2"},
- {file = "coverage-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112cfead1bd22eada8a8db9ed387bd3e8be5528debc42b5d3c1f7da4ffaf9fb5"},
- {file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af87e906355fa42447be5c08c5d44e6e1c005bf142f303f726ddf5ed6e0c8a4d"},
- {file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f30090e22a301952c5abd0e493a1c8358b4f0b368b49fa3e4568ed3ed68b8d1f"},
- {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae871d09901911eedda1981ea6fd0f62a999107293cdc4c4fd612321c5b34745"},
- {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ed7c9debf7bfc63c9b9f8b595409237774ff4b061bf29fba6f53b287a2fdeab9"},
- {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13121fa22dcd2c7b19c5161e3fd725692448f05377b788da4502a383573227b3"},
- {file = "coverage-7.0.3-cp311-cp311-win32.whl", hash = "sha256:037b51ee86bc600f99b3b957c20a172431c35c2ef9c1ca34bc813ab5b51fd9f5"},
- {file = "coverage-7.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:25fde928306034e8deecd5fc91a07432dcc282c8acb76749581a28963c9f4f3f"},
- {file = "coverage-7.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e8b0642c38b3d3b3c01417643ccc645345b03c32a2e84ef93cdd6844d6fe530"},
- {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18b09811f849cc958d23f733a350a66b54a8de3fed1e6128ba55a5c97ffb6f65"},
- {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:349d0b545520e8516f7b4f12373afc705d17d901e1de6a37a20e4ec9332b61f7"},
- {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b38813eee5b4739f505d94247604c72eae626d5088a16dd77b08b8b1724ab3"},
- {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba9af1218fa01b1f11c72271bc7290b701d11ad4dbc2ae97c445ecacf6858dba"},
- {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c5648c7eec5cf1ba5db1cf2d6c10036a582d7f09e172990474a122e30c841361"},
- {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d0df04495b76a885bfef009f45eebe8fe2fbf815ad7a83dabcf5aced62f33162"},
- {file = "coverage-7.0.3-cp37-cp37m-win32.whl", hash = "sha256:af6cef3796b8068713a48dd67d258dc9a6e2ebc3bd4645bfac03a09672fa5d20"},
- {file = "coverage-7.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:62ef3800c4058844e2e3fa35faa9dd0ccde8a8aba6c763aae50342e00d4479d4"},
- {file = "coverage-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acef7f3a3825a2d218a03dd02f5f3cc7f27aa31d882dd780191d1ad101120d74"},
- {file = "coverage-7.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a530663a361eb27375cec28aea5cd282089b5e4b022ae451c4c3493b026a68a5"},
- {file = "coverage-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c58cd6bb46dcb922e0d5792850aab5964433d511b3a020867650f8d930dde4f4"},
- {file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f918e9ef4c98f477a5458238dde2a1643aed956c7213873ab6b6b82e32b8ef61"},
- {file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b865aa679bee7fbd1c55960940dbd3252621dd81468268786c67122bbd15343"},
- {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c5d9b480ebae60fc2cbc8d6865194136bc690538fa542ba58726433bed6e04cc"},
- {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:985ad2af5ec3dbb4fd75d5b0735752c527ad183455520055a08cf8d6794cabfc"},
- {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ca15308ef722f120967af7474ba6a453e0f5b6f331251e20b8145497cf1bc14a"},
- {file = "coverage-7.0.3-cp38-cp38-win32.whl", hash = "sha256:c1cee10662c25c94415bbb987f2ec0e6ba9e8fce786334b10be7e6a7ab958f69"},
- {file = "coverage-7.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:44d6a556de4418f1f3bfd57094b8c49f0408df5a433cf0d253eeb3075261c762"},
- {file = "coverage-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e6dcc70a25cb95df0ae33dfc701de9b09c37f7dd9f00394d684a5b57257f8246"},
- {file = "coverage-7.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf76d79dfaea802f0f28f50153ffbc1a74ae1ee73e480baeda410b4f3e7ab25f"},
- {file = "coverage-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88834e5d56d01c141c29deedacba5773fe0bed900b1edc957595a8a6c0da1c3c"},
- {file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef001a60e888f8741e42e5aa79ae55c91be73761e4df5e806efca1ddd62fd400"},
- {file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959dc506be74e4963bd2c42f7b87d8e4b289891201e19ec551e64c6aa5441f8"},
- {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b791beb17b32ac019a78cfbe6184f992b6273fdca31145b928ad2099435e2fcb"},
- {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b07651e3b9af8f1a092861d88b4c74d913634a7f1f2280fca0ad041ad84e9e96"},
- {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e46fa4168ccb7497c9be78627fcb147e06f474f846a10d55feeb5108a24ef0"},
- {file = "coverage-7.0.3-cp39-cp39-win32.whl", hash = "sha256:e3f1cd1cd65695b1540b3cf7828d05b3515974a9d7c7530f762ac40f58a18161"},
- {file = "coverage-7.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:d8249666c23683f74f8f93aeaa8794ac87cc61c40ff70374a825f3352a4371dc"},
- {file = "coverage-7.0.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:b1ffc8f58b81baed3f8962e28c30d99442079b82ce1ec836a1f67c0accad91c1"},
- {file = "coverage-7.0.3.tar.gz", hash = "sha256:d5be4e93acce64f516bf4fd239c0e6118fc913c93fa1a3f52d15bdcc60d97b2d"},
-]
-
-[package.dependencies]
-tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
-
-[package.extras]
-toml = ["tomli"]
-
-[[package]]
-name = "cycler"
-version = "0.11.0"
-description = "Composable style cycles"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"},
- {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
-]
-
-[[package]]
-name = "dill"
-version = "0.3.6"
-description = "serialize all of python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"},
- {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
-]
-
-[package.extras]
-graph = ["objgraph (>=1.7.2)"]
-
-[[package]]
-name = "distlib"
-version = "0.3.6"
-description = "Distribution utilities"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
- {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
-]
-
-[[package]]
-name = "docutils"
-version = "0.17.1"
-description = "Docutils -- Python Documentation Utilities"
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-files = [
- {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
- {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
-]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.1.0"
-description = "Backport of PEP 654 (exception groups)"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
- {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
-]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
-[[package]]
-name = "filelock"
-version = "3.9.0"
-description = "A platform independent file lock."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"},
- {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"},
-]
-
-[package.extras]
-docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
-testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
-
-[[package]]
-name = "fonttools"
-version = "4.38.0"
-description = "Tools to manipulate font files"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "fonttools-4.38.0-py3-none-any.whl", hash = "sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb"},
- {file = "fonttools-4.38.0.zip", hash = "sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1"},
-]
-
-[package.extras]
-all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"]
-graphite = ["lz4 (>=1.7.4.2)"]
-interpolatable = ["munkres", "scipy"]
-lxml = ["lxml (>=4.0,<5)"]
-pathops = ["skia-pathops (>=0.5.0)"]
-plot = ["matplotlib"]
-repacker = ["uharfbuzz (>=0.23.0)"]
-symfont = ["sympy"]
-type1 = ["xattr"]
-ufo = ["fs (>=2.2.0,<3)"]
-unicode = ["unicodedata2 (>=14.0.0)"]
-woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
-
-[[package]]
-name = "frozenlist"
-version = "1.3.3"
-description = "A list-like structure which implements collections.abc.MutableSequence"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"},
- {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"},
- {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"},
- {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"},
- {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"},
- {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"},
- {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"},
- {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"},
- {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"},
- {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"},
- {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"},
- {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"},
- {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"},
- {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"},
- {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"},
- {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"},
- {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"},
- {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"},
- {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"},
- {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"},
- {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"},
- {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"},
- {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"},
- {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"},
- {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"},
- {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"},
- {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"},
- {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"},
- {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"},
- {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"},
- {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"},
- {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"},
- {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"},
- {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"},
- {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"},
- {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"},
- {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"},
- {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"},
- {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"},
- {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"},
- {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"},
- {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"},
- {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"},
- {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"},
- {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"},
- {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"},
- {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"},
- {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"},
- {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"},
- {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"},
- {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"},
- {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"},
- {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"},
- {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"},
- {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"},
- {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"},
- {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"},
- {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"},
- {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"},
- {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"},
- {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"},
- {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"},
- {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"},
- {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"},
- {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"},
- {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"},
- {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"},
- {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"},
- {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"},
- {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"},
- {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"},
- {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"},
- {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"},
- {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"},
-]
-
-[[package]]
-name = "future"
-version = "0.18.2"
-description = "Clean single-source support for Python 3 and 2"
-category = "dev"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-files = [
- {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
-]
-
-[[package]]
-name = "hypothesis"
-version = "6.61.0"
-description = "A library for property-based testing"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "hypothesis-6.61.0-py3-none-any.whl", hash = "sha256:7bb22d22e35db99d5724bbf5bdc686b46add94a0f228bf1be249c47ec46b9c7f"},
- {file = "hypothesis-6.61.0.tar.gz", hash = "sha256:fbf7da30aea839d88898f74bcc027f0f997060498a8a7605880688c8a2166215"},
-]
-
-[package.dependencies]
-attrs = ">=19.2.0"
-exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-sortedcontainers = ">=2.1.0,<3.0.0"
-
-[package.extras]
-all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=1.0)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2022.7)"]
-cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"]
-codemods = ["libcst (>=0.3.16)"]
-dateutil = ["python-dateutil (>=1.4)"]
-django = ["django (>=3.2)"]
-dpcontracts = ["dpcontracts (>=0.4)"]
-ghostwriter = ["black (>=19.10b0)"]
-lark = ["lark (>=0.10.1)"]
-numpy = ["numpy (>=1.9.0)"]
-pandas = ["pandas (>=1.0)"]
-pytest = ["pytest (>=4.6)"]
-pytz = ["pytz (>=2014.1)"]
-redis = ["redis (>=3.0.0)"]
-zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.7)"]
-
-[[package]]
-name = "identify"
-version = "2.5.12"
-description = "File identification library for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "identify-2.5.12-py2.py3-none-any.whl", hash = "sha256:e8a400c3062d980243d27ce10455a52832205649bbcaf27ffddb3dfaaf477bad"},
- {file = "identify-2.5.12.tar.gz", hash = "sha256:0bc96b09c838310b6fcfcc61f78a981ea07f94836ef6ef553da5bb5d4745d662"},
-]
-
-[package.extras]
-license = ["ukkonen"]
-
-[[package]]
-name = "idna"
-version = "3.4"
-description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
- {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
-]
-
-[[package]]
-name = "imagesize"
-version = "1.4.1"
-description = "Getting image size from png/jpeg/jpeg2000/gif file"
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-files = [
- {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
- {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
-]
-
-[[package]]
-name = "importlib-metadata"
-version = "6.0.0"
-description = "Read metadata from Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"},
- {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"},
-]
-
-[package.dependencies]
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-perf = ["ipython"]
-testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
-
-[[package]]
-name = "iniconfig"
-version = "1.1.1"
-description = "iniconfig: brain-dead simple config-ini parsing"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
- {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
-]
-
-[[package]]
-name = "isort"
-version = "5.11.4"
-description = "A Python utility / library to sort Python imports."
-category = "dev"
-optional = false
-python-versions = ">=3.7.0"
-files = [
- {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"},
- {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"},
-]
-
-[package.extras]
-colors = ["colorama (>=0.4.3,<0.5.0)"]
-pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
-plugins = ["setuptools"]
-requirements-deprecated-finder = ["pip-api", "pipreqs"]
-
-[[package]]
-name = "jinja2"
-version = "3.1.2"
-description = "A very fast and expressive template engine."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
- {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
-]
-
-[package.dependencies]
-MarkupSafe = ">=2.0"
-
-[package.extras]
-i18n = ["Babel (>=2.7)"]
-
-[[package]]
-name = "kiwisolver"
-version = "1.4.4"
-description = "A fast implementation of the Cassowary constraint solver"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"},
- {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"},
- {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"},
- {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"},
- {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"},
- {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"},
- {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"},
- {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"},
- {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"},
- {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"},
- {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"},
- {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"},
- {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"},
- {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"},
- {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"},
- {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"},
- {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"},
- {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"},
- {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"},
- {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"},
- {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"},
- {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"},
- {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"},
- {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"},
- {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"},
- {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"},
- {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"},
- {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"},
- {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"},
- {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"},
- {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"},
- {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"},
- {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"},
- {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"},
- {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"},
- {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"},
- {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"},
- {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"},
- {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"},
- {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"},
- {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"},
- {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"},
- {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"},
- {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"},
- {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"},
- {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"},
- {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"},
- {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"},
- {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"},
- {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"},
- {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"},
- {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"},
- {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"},
- {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"},
- {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"},
- {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"},
- {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"},
- {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"},
- {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"},
- {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"},
- {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"},
-]
-
-[[package]]
-name = "lazy-object-proxy"
-version = "1.9.0"
-description = "A fast and thorough lazy object proxy."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"},
-]
-
-[[package]]
-name = "loguru"
-version = "0.6.0"
-description = "Python logging made (stupidly) simple"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
- {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
-]
-
-[package.dependencies]
-colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
-win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
-
-[package.extras]
-dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"]
-
-[[package]]
-name = "mando"
-version = "0.6.4"
-description = "Create Python CLI apps with little to no effort at all!"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "mando-0.6.4-py2.py3-none-any.whl", hash = "sha256:4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c"},
- {file = "mando-0.6.4.tar.gz", hash = "sha256:79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"},
-]
-
-[package.dependencies]
-six = "*"
-
-[package.extras]
-restructuredtext = ["rst2ansi"]
-
-[[package]]
-name = "markupsafe"
-version = "2.1.1"
-description = "Safely add untrusted strings to HTML/XML markup."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
- {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
-]
-
-[[package]]
-name = "matplotlib"
-version = "3.6.2"
-description = "Python plotting package"
-category = "dev"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "matplotlib-3.6.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5"},
- {file = "matplotlib-3.6.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267"},
- {file = "matplotlib-3.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c"},
- {file = "matplotlib-3.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d840adcad7354be6f2ec28d0706528b0026e4c3934cc6566b84eac18633eab1b"},
- {file = "matplotlib-3.6.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78ec3c3412cf277e6252764ee4acbdbec6920cc87ad65862272aaa0e24381eee"},
- {file = "matplotlib-3.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9347cc6822f38db2b1d1ce992f375289670e595a2d1c15961aacbe0977407dfc"},
- {file = "matplotlib-3.6.2-cp310-cp310-win32.whl", hash = "sha256:e0bbee6c2a5bf2a0017a9b5e397babb88f230e6f07c3cdff4a4c4bc75ed7c617"},
- {file = "matplotlib-3.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8a0ae37576ed444fe853709bdceb2be4c7df6f7acae17b8378765bd28e61b3ae"},
- {file = "matplotlib-3.6.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:5ecfc6559132116dedfc482d0ad9df8a89dc5909eebffd22f3deb684132d002f"},
- {file = "matplotlib-3.6.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9f335e5625feb90e323d7e3868ec337f7b9ad88b5d633f876e3b778813021dab"},
- {file = "matplotlib-3.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2604c6450f9dd2c42e223b1f5dca9643a23cfecc9fde4a94bb38e0d2693b136"},
- {file = "matplotlib-3.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5afe0a7ea0e3a7a257907060bee6724a6002b7eec55d0db16fd32409795f3e1"},
- {file = "matplotlib-3.6.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0e7a658fbafcddcaefaa07ba8dae9384be2343468a8e011061791588d839fa"},
- {file = "matplotlib-3.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32d29c8c26362169c80c5718ce367e8c64f4dd068a424e7110df1dd2ed7bd428"},
- {file = "matplotlib-3.6.2-cp311-cp311-win32.whl", hash = "sha256:5024b8ed83d7f8809982d095d8ab0b179bebc07616a9713f86d30cf4944acb73"},
- {file = "matplotlib-3.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:52c2bdd7cd0bf9d5ccdf9c1816568fd4ccd51a4d82419cc5480f548981b47dd0"},
- {file = "matplotlib-3.6.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:8a8dbe2cb7f33ff54b16bb5c500673502a35f18ac1ed48625e997d40c922f9cc"},
- {file = "matplotlib-3.6.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:380d48c15ec41102a2b70858ab1dedfa33eb77b2c0982cb65a200ae67a48e9cb"},
- {file = "matplotlib-3.6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0844523dfaaff566e39dbfa74e6f6dc42e92f7a365ce80929c5030b84caa563a"},
- {file = "matplotlib-3.6.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7f716b6af94dc1b6b97c46401774472f0867e44595990fe80a8ba390f7a0a028"},
- {file = "matplotlib-3.6.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74153008bd24366cf099d1f1e83808d179d618c4e32edb0d489d526523a94d9f"},
- {file = "matplotlib-3.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f41e57ad63d336fe50d3a67bb8eaa26c09f6dda6a59f76777a99b8ccd8e26aec"},
- {file = "matplotlib-3.6.2-cp38-cp38-win32.whl", hash = "sha256:d0e9ac04065a814d4cf2c6791a2ad563f739ae3ae830d716d54245c2b96fead6"},
- {file = "matplotlib-3.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:8a9d899953c722b9afd7e88dbefd8fb276c686c3116a43c577cfabf636180558"},
- {file = "matplotlib-3.6.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:f04f97797df35e442ed09f529ad1235d1f1c0f30878e2fe09a2676b71a8801e0"},
- {file = "matplotlib-3.6.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3964934731fd7a289a91d315919cf757f293969a4244941ab10513d2351b4e83"},
- {file = "matplotlib-3.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:168093410b99f647ba61361b208f7b0d64dde1172b5b1796d765cd243cadb501"},
- {file = "matplotlib-3.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e16dcaecffd55b955aa5e2b8a804379789c15987e8ebd2f32f01398a81e975b"},
- {file = "matplotlib-3.6.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83dc89c5fd728fdb03b76f122f43b4dcee8c61f1489e232d9ad0f58020523e1c"},
- {file = "matplotlib-3.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33"},
- {file = "matplotlib-3.6.2-cp39-cp39-win32.whl", hash = "sha256:19d61ee6414c44a04addbe33005ab1f87539d9f395e25afcbe9a3c50ce77c65c"},
- {file = "matplotlib-3.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:5ba73aa3aca35d2981e0b31230d58abb7b5d7ca104e543ae49709208d8ce706a"},
- {file = "matplotlib-3.6.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1836f366272b1557a613f8265db220eb8dd883202bbbabe01bad5a4eadfd0c95"},
- {file = "matplotlib-3.6.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eda9d1b43f265da91fb9ae10d6922b5a986e2234470a524e6b18f14095b20d2"},
- {file = "matplotlib-3.6.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec9be0f4826cdb3a3a517509dcc5f87f370251b76362051ab59e42b6b765f8c4"},
- {file = "matplotlib-3.6.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3cef89888a466228fc4e4b2954e740ce8e9afde7c4315fdd18caa1b8de58ca17"},
- {file = "matplotlib-3.6.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:54fa9fe27f5466b86126ff38123261188bed568c1019e4716af01f97a12fe812"},
- {file = "matplotlib-3.6.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e68be81cd8c22b029924b6d0ee814c337c0e706b8d88495a617319e5dd5441c3"},
- {file = "matplotlib-3.6.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0ca2c60d3966dfd6608f5f8c49b8a0fcf76de6654f2eda55fc6ef038d5a6f27"},
- {file = "matplotlib-3.6.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1"},
- {file = "matplotlib-3.6.2.tar.gz", hash = "sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990"},
-]
-
-[package.dependencies]
-contourpy = ">=1.0.1"
-cycler = ">=0.10"
-fonttools = ">=4.22.0"
-kiwisolver = ">=1.0.1"
-numpy = ">=1.19"
-packaging = ">=20.0"
-pillow = ">=6.2.0"
-pyparsing = ">=2.2.1"
-python-dateutil = ">=2.7"
-setuptools_scm = ">=7"
-
-[[package]]
-name = "mccabe"
-version = "0.7.0"
-description = "McCabe checker, plugin for flake8"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
- {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
-]
-
-[[package]]
-name = "mpyq"
-version = "0.2.5"
-description = "A Python library for extracting MPQ (MoPaQ) files."
-category = "main"
-optional = false
-python-versions = "*"
-files = [
- {file = "mpyq-0.2.5.tar.gz", hash = "sha256:30aaf5962be569f3f2b53978060cd047434ee4f5a215925dd6ff0fef04ec0007"},
-]
-
-[[package]]
-name = "multidict"
-version = "6.0.4"
-description = "multidict implementation"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
- {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
- {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
- {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
- {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
- {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
- {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
- {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
- {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
- {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
- {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
- {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
- {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
- {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
- {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
- {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
- {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
- {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
- {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
- {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
- {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
- {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
- {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
- {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
- {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
- {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
- {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
- {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
- {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
- {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
- {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
- {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
- {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
- {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
- {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
- {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
- {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
- {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
- {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
- {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
- {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
- {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
- {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
- {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
- {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
- {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
- {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
- {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
- {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
- {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
- {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
- {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
- {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
- {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
- {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
- {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
- {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
- {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
- {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
- {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
- {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
- {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
- {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
- {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
- {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
- {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
- {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
- {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
- {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
- {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
- {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
- {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
- {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
- {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
-]
-
-[[package]]
-name = "mypy"
-version = "0.960"
-description = "Optional static typing for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "mypy-0.960-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3a3e525cd76c2c4f90f1449fd034ba21fcca68050ff7c8397bb7dd25dd8b8248"},
- {file = "mypy-0.960-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a76dc4f91e92db119b1be293892df8379b08fd31795bb44e0ff84256d34c251"},
- {file = "mypy-0.960-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffdad80a92c100d1b0fe3d3cf1a4724136029a29afe8566404c0146747114382"},
- {file = "mypy-0.960-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d390248ec07fa344b9f365e6ed9d205bd0205e485c555bed37c4235c868e9d5"},
- {file = "mypy-0.960-cp310-cp310-win_amd64.whl", hash = "sha256:925aa84369a07846b7f3b8556ccade1f371aa554f2bd4fb31cb97a24b73b036e"},
- {file = "mypy-0.960-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:239d6b2242d6c7f5822163ee082ef7a28ee02e7ac86c35593ef923796826a385"},
- {file = "mypy-0.960-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f1ba54d440d4feee49d8768ea952137316d454b15301c44403db3f2cb51af024"},
- {file = "mypy-0.960-cp36-cp36m-win_amd64.whl", hash = "sha256:cb7752b24528c118a7403ee955b6a578bfcf5879d5ee91790667c8ea511d2085"},
- {file = "mypy-0.960-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:826a2917c275e2ee05b7c7b736c1e6549a35b7ea5a198ca457f8c2ebea2cbecf"},
- {file = "mypy-0.960-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3eabcbd2525f295da322dff8175258f3fc4c3eb53f6d1929644ef4d99b92e72d"},
- {file = "mypy-0.960-cp37-cp37m-win_amd64.whl", hash = "sha256:f47322796c412271f5aea48381a528a613f33e0a115452d03ae35d673e6064f8"},
- {file = "mypy-0.960-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2c7f8bb9619290836a4e167e2ef1f2cf14d70e0bc36c04441e41487456561409"},
- {file = "mypy-0.960-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbfb873cf2b8d8c3c513367febde932e061a5f73f762896826ba06391d932b2a"},
- {file = "mypy-0.960-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc537885891382e08129d9862553b3d00d4be3eb15b8cae9e2466452f52b0117"},
- {file = "mypy-0.960-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:481f98c6b24383188c928f33dd2f0776690807e12e9989dd0419edd5c74aa53b"},
- {file = "mypy-0.960-cp38-cp38-win_amd64.whl", hash = "sha256:29dc94d9215c3eb80ac3c2ad29d0c22628accfb060348fd23d73abe3ace6c10d"},
- {file = "mypy-0.960-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:33d53a232bb79057f33332dbbb6393e68acbcb776d2f571ba4b1d50a2c8ba873"},
- {file = "mypy-0.960-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d645e9e7f7a5da3ec3bbcc314ebb9bb22c7ce39e70367830eb3c08d0140b9ce"},
- {file = "mypy-0.960-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85cf2b14d32b61db24ade8ac9ae7691bdfc572a403e3cb8537da936e74713275"},
- {file = "mypy-0.960-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85a20b43fa69efc0b955eba1db435e2ffecb1ca695fe359768e0503b91ea89f"},
- {file = "mypy-0.960-cp39-cp39-win_amd64.whl", hash = "sha256:0ebfb3f414204b98c06791af37a3a96772203da60636e2897408517fcfeee7a8"},
- {file = "mypy-0.960-py3-none-any.whl", hash = "sha256:bfd4f6536bd384c27c392a8b8f790fd0ed5c0cf2f63fc2fed7bce56751d53026"},
- {file = "mypy-0.960.tar.gz", hash = "sha256:d4fccf04c1acf750babd74252e0f2db6bd2ac3aa8fe960797d9f3ef41cf2bfd4"},
-]
-
-[package.dependencies]
-mypy-extensions = ">=0.4.3"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = ">=3.10"
-
-[package.extras]
-dmypy = ["psutil (>=4.0)"]
-python2 = ["typed-ast (>=1.4.0,<2)"]
-reports = ["lxml"]
-
-[[package]]
-name = "mypy-extensions"
-version = "0.4.3"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
- {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
-]
-
-[[package]]
-name = "nodeenv"
-version = "1.7.0"
-description = "Node.js virtual environment builder"
-category = "dev"
-optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
-files = [
- {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
- {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
-]
-
-[package.dependencies]
-setuptools = "*"
-
-[[package]]
-name = "numpy"
-version = "1.24.1"
-description = "Fundamental package for array computing in Python"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "numpy-1.24.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:179a7ef0889ab769cc03573b6217f54c8bd8e16cef80aad369e1e8185f994cd7"},
- {file = "numpy-1.24.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b09804ff570b907da323b3d762e74432fb07955701b17b08ff1b5ebaa8cfe6a9"},
- {file = "numpy-1.24.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1b739841821968798947d3afcefd386fa56da0caf97722a5de53e07c4ccedc7"},
- {file = "numpy-1.24.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e3463e6ac25313462e04aea3fb8a0a30fb906d5d300f58b3bc2c23da6a15398"},
- {file = "numpy-1.24.1-cp310-cp310-win32.whl", hash = "sha256:b31da69ed0c18be8b77bfce48d234e55d040793cebb25398e2a7d84199fbc7e2"},
- {file = "numpy-1.24.1-cp310-cp310-win_amd64.whl", hash = "sha256:b07b40f5fb4fa034120a5796288f24c1fe0e0580bbfff99897ba6267af42def2"},
- {file = "numpy-1.24.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7094891dcf79ccc6bc2a1f30428fa5edb1e6fb955411ffff3401fb4ea93780a8"},
- {file = "numpy-1.24.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e418681372520c992805bb723e29d69d6b7aa411065f48216d8329d02ba032"},
- {file = "numpy-1.24.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e274f0f6c7efd0d577744f52032fdd24344f11c5ae668fe8d01aac0422611df1"},
- {file = "numpy-1.24.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0044f7d944ee882400890f9ae955220d29b33d809a038923d88e4e01d652acd9"},
- {file = "numpy-1.24.1-cp311-cp311-win32.whl", hash = "sha256:442feb5e5bada8408e8fcd43f3360b78683ff12a4444670a7d9e9824c1817d36"},
- {file = "numpy-1.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:de92efa737875329b052982e37bd4371d52cabf469f83e7b8be9bb7752d67e51"},
- {file = "numpy-1.24.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b162ac10ca38850510caf8ea33f89edcb7b0bb0dfa5592d59909419986b72407"},
- {file = "numpy-1.24.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26089487086f2648944f17adaa1a97ca6aee57f513ba5f1c0b7ebdabbe2b9954"},
- {file = "numpy-1.24.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caf65a396c0d1f9809596be2e444e3bd4190d86d5c1ce21f5fc4be60a3bc5b36"},
- {file = "numpy-1.24.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0677a52f5d896e84414761531947c7a330d1adc07c3a4372262f25d84af7bf7"},
- {file = "numpy-1.24.1-cp38-cp38-win32.whl", hash = "sha256:dae46bed2cb79a58d6496ff6d8da1e3b95ba09afeca2e277628171ca99b99db1"},
- {file = "numpy-1.24.1-cp38-cp38-win_amd64.whl", hash = "sha256:6ec0c021cd9fe732e5bab6401adea5a409214ca5592cd92a114f7067febcba0c"},
- {file = "numpy-1.24.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:28bc9750ae1f75264ee0f10561709b1462d450a4808cd97c013046073ae64ab6"},
- {file = "numpy-1.24.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84e789a085aabef2f36c0515f45e459f02f570c4b4c4c108ac1179c34d475ed7"},
- {file = "numpy-1.24.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e669fbdcdd1e945691079c2cae335f3e3a56554e06bbd45d7609a6cf568c700"},
- {file = "numpy-1.24.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef85cf1f693c88c1fd229ccd1055570cb41cdf4875873b7728b6301f12cd05bf"},
- {file = "numpy-1.24.1-cp39-cp39-win32.whl", hash = "sha256:87a118968fba001b248aac90e502c0b13606721b1343cdaddbc6e552e8dfb56f"},
- {file = "numpy-1.24.1-cp39-cp39-win_amd64.whl", hash = "sha256:ddc7ab52b322eb1e40521eb422c4e0a20716c271a306860979d450decbb51b8e"},
- {file = "numpy-1.24.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed5fb71d79e771ec930566fae9c02626b939e37271ec285e9efaf1b5d4370e7d"},
- {file = "numpy-1.24.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2925567f43643f51255220424c23d204024ed428afc5aad0f86f3ffc080086"},
- {file = "numpy-1.24.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cfa1161c6ac8f92dea03d625c2d0c05e084668f4a06568b77a25a89111621566"},
- {file = "numpy-1.24.1.tar.gz", hash = "sha256:2386da9a471cc00a1f47845e27d916d5ec5346ae9696e01a8a34760858fe9dd2"},
-]
-
-[[package]]
-name = "packaging"
-version = "22.0"
-description = "Core utilities for Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"},
- {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"},
-]
-
-[[package]]
-name = "pillow"
-version = "9.4.0"
-description = "Python Imaging Library (Fork)"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"},
- {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"},
- {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"},
- {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"},
- {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"},
- {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"},
- {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"},
- {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"},
- {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
- {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},
- {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"},
- {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"},
- {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"},
- {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"},
- {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"},
- {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"},
- {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"},
- {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"},
- {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"},
- {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"},
- {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"},
- {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"},
- {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"},
- {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"},
- {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"},
- {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"},
- {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"},
- {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"},
- {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"},
- {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"},
- {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"},
- {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"},
- {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"},
- {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"},
- {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"},
- {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"},
- {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"},
- {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"},
- {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"},
- {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"},
- {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"},
- {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"},
- {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"},
- {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"},
- {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"},
- {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"},
- {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"},
- {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"},
- {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"},
- {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"},
- {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"},
- {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"},
- {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"},
- {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"},
- {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"},
- {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"},
- {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"},
- {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"},
- {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"},
- {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"},
- {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"},
- {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"},
- {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"},
- {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"},
- {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"},
- {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"},
- {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"},
- {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"},
- {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"},
- {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"},
-]
-
-[package.extras]
-docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
-tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
-
-[[package]]
-name = "platformdirs"
-version = "2.6.2"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
- {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
-]
-
-[package.extras]
-docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
-
-[[package]]
-name = "pluggy"
-version = "1.0.0"
-description = "plugin and hook calling mechanisms for python"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
- {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
-]
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
-
-[[package]]
-name = "portpicker"
-version = "1.5.2"
-description = "A library to choose unique available network ports."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "portpicker-1.5.2-py3-none-any.whl", hash = "sha256:01113f51c3cc63290a44dd7ae6e3eb9f8fe1b8a1f9d7988a897944230c39cd52"},
- {file = "portpicker-1.5.2.tar.gz", hash = "sha256:c55683ad725f5c00a41bc7db0225223e8be024b1fa564d039ed3390e4fd48fb3"},
-]
-
-[package.dependencies]
-psutil = "*"
-
-[[package]]
-name = "pre-commit"
-version = "2.21.0"
-description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"},
- {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"},
-]
-
-[package.dependencies]
-cfgv = ">=2.0.0"
-identify = ">=1.0.0"
-nodeenv = ">=0.11.1"
-pyyaml = ">=5.1"
-virtualenv = ">=20.10.0"
-
-[[package]]
-name = "protobuf"
-version = "3.20.3"
-description = "Protocol Buffers"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"},
- {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"},
- {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"},
- {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"},
- {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"},
- {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"},
- {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"},
- {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"},
- {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"},
- {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"},
- {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"},
- {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"},
- {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"},
- {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"},
- {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"},
- {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"},
- {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"},
- {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"},
- {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"},
- {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"},
- {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"},
- {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"},
-]
-
-[[package]]
-name = "psutil"
-version = "5.9.4"
-description = "Cross-platform lib for process and system monitoring in Python."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-files = [
- {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},
- {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},
- {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
- {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},
- {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},
- {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
- {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},
- {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
- {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},
- {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},
- {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},
- {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
- {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},
- {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},
-]
-
-[package.extras]
-test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
-
-[[package]]
-name = "py-cpuinfo"
-version = "9.0.0"
-description = "Get CPU info with pure Python"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"},
- {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"},
-]
-
-[[package]]
-name = "pyglet"
-version = "2.0.3"
-description = "Cross-platform windowing and multimedia library"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "pyglet-2.0.3-py3-none-any.whl", hash = "sha256:04fc73db0deff693dd70a71e166643c5f1251deed62262eaa1dbb7b0ac26e233"},
- {file = "pyglet-2.0.3.zip", hash = "sha256:b15073474f93c7ce30f71e4af824d3c847db099f2111ef16be0b631d2a222bd3"},
-]
-
-[[package]]
-name = "pygments"
-version = "2.14.0"
-description = "Pygments is a syntax highlighting package written in Python."
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
- {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
-]
-
-[package.extras]
-plugins = ["importlib-metadata"]
-
-[[package]]
-name = "pylint"
-version = "2.15.9"
-description = "python code static checker"
-category = "dev"
-optional = false
-python-versions = ">=3.7.2"
-files = [
- {file = "pylint-2.15.9-py3-none-any.whl", hash = "sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb"},
- {file = "pylint-2.15.9.tar.gz", hash = "sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4"},
-]
-
-[package.dependencies]
-astroid = ">=2.12.13,<=2.14.0-dev0"
-colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
-dill = [
- {version = ">=0.2", markers = "python_version < \"3.11\""},
- {version = ">=0.3.6", markers = "python_version >= \"3.11\""},
-]
-isort = ">=4.2.5,<6"
-mccabe = ">=0.6,<0.8"
-platformdirs = ">=2.2.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-tomlkit = ">=0.10.1"
-typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-spelling = ["pyenchant (>=3.2,<4.0)"]
-testutils = ["gitpython (>3)"]
-
-[[package]]
-name = "pyparsing"
-version = "3.0.9"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-category = "dev"
-optional = false
-python-versions = ">=3.6.8"
-files = [
- {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
- {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
-]
-
-[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
-
-[[package]]
-name = "pytest"
-version = "7.2.0"
-description = "pytest: simple powerful testing with Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
- {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
-]
-
-[package.dependencies]
-attrs = ">=19.2.0"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=0.12,<2.0"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-
-[package.extras]
-testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
-
-[[package]]
-name = "pytest-asyncio"
-version = "0.18.3"
-description = "Pytest support for asyncio"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"},
- {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"},
- {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"},
-]
-
-[package.dependencies]
-pytest = ">=6.1.0"
-
-[package.extras]
-testing = ["coverage (==6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"]
-
-[[package]]
-name = "pytest-benchmark"
-version = "4.0.0"
-description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"},
- {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"},
-]
-
-[package.dependencies]
-py-cpuinfo = "*"
-pytest = ">=3.8"
-
-[package.extras]
-aspect = ["aspectlib"]
-elasticsearch = ["elasticsearch"]
-histogram = ["pygal", "pygaljs"]
-
-[[package]]
-name = "pytest-cov"
-version = "4.0.0"
-description = "Pytest plugin for measuring coverage."
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
- {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
-]
-
-[package.dependencies]
-coverage = {version = ">=5.2.1", extras = ["toml"]}
-pytest = ">=4.6"
-
-[package.extras]
-testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
-
-[[package]]
-name = "python-dateutil"
-version = "2.8.2"
-description = "Extensions to the standard Python datetime module"
-category = "dev"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-files = [
- {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
- {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
-]
-
-[package.dependencies]
-six = ">=1.5"
-
-[[package]]
-name = "pytz"
-version = "2022.7"
-description = "World timezone definitions, modern and historical"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"},
- {file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"},
-]
-
-[[package]]
-name = "pyyaml"
-version = "6.0"
-description = "YAML parser and emitter for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
- {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
- {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
- {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
- {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
- {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
- {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
- {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
- {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
- {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
- {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
- {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
- {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
- {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
- {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
- {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
- {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
- {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
- {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
- {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
- {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
- {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
- {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
- {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
- {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
- {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
- {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
- {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
- {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
- {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
- {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
- {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
- {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
- {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
- {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
- {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
- {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
-]
-
-[[package]]
-name = "radon"
-version = "5.1.0"
-description = "Code Metrics in Python"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "radon-5.1.0-py2.py3-none-any.whl", hash = "sha256:fa74e018197f1fcb54578af0f675d8b8e2342bd8e0b72bef8197bc4c9e645f36"},
- {file = "radon-5.1.0.tar.gz", hash = "sha256:cb1d8752e5f862fb9e20d82b5f758cbc4fb1237c92c9a66450ea0ea7bf29aeee"},
-]
-
-[package.dependencies]
-colorama = {version = ">=0.4.1", markers = "python_version > \"3.4\""}
-future = "*"
-mando = ">=0.6,<0.7"
-
-[[package]]
-name = "requests"
-version = "2.28.1"
-description = "Python HTTP for Humans."
-category = "dev"
-optional = false
-python-versions = ">=3.7, <4"
-files = [
- {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
- {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
-]
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-charset-normalizer = ">=2,<3"
-idna = ">=2.5,<4"
-urllib3 = ">=1.21.1,<1.27"
-
-[package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)"]
-use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
-
-[[package]]
-name = "s2clientprotocol"
-version = "5.0.10.89165.0"
-description = "StarCraft II - client protocol."
-category = "main"
-optional = false
-python-versions = "*"
-files = [
- {file = "s2clientprotocol-5.0.10.89165.0-py2.py3-none-any.whl", hash = "sha256:b0bd229297fb13b2e2ce1f3c625ee0aaba68861620b9c823c7809a1cc364834d"},
-]
-
-[package.dependencies]
-protobuf = "*"
-
-[[package]]
-name = "scipy"
-version = "1.10.0"
-description = "Fundamental algorithms for scientific computing in Python"
-category = "main"
-optional = false
-python-versions = "<3.12,>=3.8"
-files = [
- {file = "scipy-1.10.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b901b423c91281a974f6cd1c36f5c6c523e665b5a6d5e80fcb2334e14670eefd"},
- {file = "scipy-1.10.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:16ba05d3d1b9f2141004f3f36888e05894a525960b07f4c2bfc0456b955a00be"},
- {file = "scipy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:151f066fe7d6653c3ffefd489497b8fa66d7316e3e0d0c0f7ff6acca1b802809"},
- {file = "scipy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f9ea0a37aca111a407cb98aa4e8dfde6e5d9333bae06dfa5d938d14c80bb5c3"},
- {file = "scipy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:27e548276b5a88b51212b61f6dda49a24acf5d770dff940bd372b3f7ced8c6c2"},
- {file = "scipy-1.10.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:42ab8b9e7dc1ebe248e55f54eea5307b6ab15011a7883367af48dd781d1312e4"},
- {file = "scipy-1.10.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e096b062d2efdea57f972d232358cb068413dc54eec4f24158bcbb5cb8bddfd8"},
- {file = "scipy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df25a28bd22c990b22129d3c637fd5c3be4b7c94f975dca909d8bab3309b694"},
- {file = "scipy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad449db4e0820e4b42baccefc98ec772ad7818dcbc9e28b85aa05a536b0f1a2"},
- {file = "scipy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:6faf86ef7717891195ae0537e48da7524d30bc3b828b30c9b115d04ea42f076f"},
- {file = "scipy-1.10.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:4bd0e3278126bc882d10414436e58fa3f1eca0aa88b534fcbf80ed47e854f46c"},
- {file = "scipy-1.10.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:38bfbd18dcc69eeb589811e77fae552fa923067fdfbb2e171c9eac749885f210"},
- {file = "scipy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ab2a58064836632e2cec31ca197d3695c86b066bc4818052b3f5381bfd2a728"},
- {file = "scipy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd7a30970c29d9768a7164f564d1fbf2842bfc77b7d114a99bc32703ce0bf48"},
- {file = "scipy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:9b878c671655864af59c108c20e4da1e796154bd78c0ed6bb02bc41c84625686"},
- {file = "scipy-1.10.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:3afcbddb4488ac950ce1147e7580178b333a29cd43524c689b2e3543a080a2c8"},
- {file = "scipy-1.10.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:6e4497e5142f325a5423ff5fda2fff5b5d953da028637ff7c704378c8c284ea7"},
- {file = "scipy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:441cab2166607c82e6d7a8683779cb89ba0f475b983c7e4ab88f3668e268c143"},
- {file = "scipy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0490dc499fe23e4be35b8b6dd1e60a4a34f0c4adb30ac671e6332446b3cbbb5a"},
- {file = "scipy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:954ff69d2d1bf666b794c1d7216e0a746c9d9289096a64ab3355a17c7c59db54"},
- {file = "scipy-1.10.0.tar.gz", hash = "sha256:c8b3cbc636a87a89b770c6afc999baa6bcbb01691b5ccbbc1b1791c7c0a07540"},
-]
-
-[package.dependencies]
-numpy = ">=1.19.5,<1.27.0"
-
-[package.extras]
-dev = ["click", "doit (>=0.36.0)", "flake8", "mypy", "pycodestyle", "pydevtool", "rich-click", "typing_extensions"]
-doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"]
-test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
-
-[[package]]
-name = "setuptools"
-version = "65.6.3"
-description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},
- {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
-
-[[package]]
-name = "setuptools-scm"
-version = "7.1.0"
-description = "the blessed package to manage your versions by scm tags"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "setuptools_scm-7.1.0-py3-none-any.whl", hash = "sha256:73988b6d848709e2af142aa48c986ea29592bbcfca5375678064708205253d8e"},
- {file = "setuptools_scm-7.1.0.tar.gz", hash = "sha256:6c508345a771aad7d56ebff0e70628bf2b0ec7573762be9960214730de278f27"},
-]
-
-[package.dependencies]
-packaging = ">=20.0"
-setuptools = "*"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-typing-extensions = "*"
-
-[package.extras]
-test = ["pytest (>=6.2)", "virtualenv (>20)"]
-toml = ["setuptools (>=42)"]
-
-[[package]]
-name = "six"
-version = "1.16.0"
-description = "Python 2 and 3 compatibility utilities"
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-files = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-
-[[package]]
-name = "snowballstemmer"
-version = "2.2.0"
-description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
- {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
-]
-
-[[package]]
-name = "sortedcontainers"
-version = "2.4.0"
-description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
- {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
-]
-
-[[package]]
-name = "sphinx"
-version = "5.3.0"
-description = "Python documentation generator"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"},
- {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"},
-]
-
-[package.dependencies]
-alabaster = ">=0.7,<0.8"
-babel = ">=2.9"
-colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
-docutils = ">=0.14,<0.20"
-imagesize = ">=1.3"
-importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""}
-Jinja2 = ">=3.0"
-packaging = ">=21.0"
-Pygments = ">=2.12"
-requests = ">=2.5.0"
-snowballstemmer = ">=2.0"
-sphinxcontrib-applehelp = "*"
-sphinxcontrib-devhelp = "*"
-sphinxcontrib-htmlhelp = ">=2.0.0"
-sphinxcontrib-jsmath = "*"
-sphinxcontrib-qthelp = "*"
-sphinxcontrib-serializinghtml = ">=1.1.5"
-
-[package.extras]
-docs = ["sphinxcontrib-websupport"]
-lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"]
-test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"]
-
-[[package]]
-name = "sphinx-autodoc-typehints"
-version = "1.20.0"
-description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "sphinx_autodoc_typehints-1.20.0-py3-none-any.whl", hash = "sha256:2fb4f460b0dcd9c7e83f48e72d5f741441ddb858b73eb9159ef3bc1e7a22ec78"},
- {file = "sphinx_autodoc_typehints-1.20.0.tar.gz", hash = "sha256:776a6439f1f602d4685f5046d00a90b38d8c075884ee81fe075260e92d152d9b"},
-]
-
-[package.dependencies]
-sphinx = ">=5.3"
-
-[package.extras]
-docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
-testing = ["covdefaults (>=2.2)", "coverage (>=6.5)", "diff-cover (>=7.0.1)", "nptyping (>=2.3.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "sphobjinv (>=2.2.2)", "typing-extensions (>=4.4)"]
-type-comment = ["typed-ast (>=1.5.4)"]
-
-[[package]]
-name = "sphinx-rtd-theme"
-version = "1.1.1"
-description = "Read the Docs theme for Sphinx"
-category = "dev"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-files = [
- {file = "sphinx_rtd_theme-1.1.1-py2.py3-none-any.whl", hash = "sha256:31faa07d3e97c8955637fc3f1423a5ab2c44b74b8cc558a51498c202ce5cbda7"},
- {file = "sphinx_rtd_theme-1.1.1.tar.gz", hash = "sha256:6146c845f1e1947b3c3dd4432c28998a1693ccc742b4f9ad7c63129f0757c103"},
-]
-
-[package.dependencies]
-docutils = "<0.18"
-sphinx = ">=1.6,<6"
-
-[package.extras]
-dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"]
-
-[[package]]
-name = "sphinxcontrib-applehelp"
-version = "1.0.2"
-description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
- {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
-]
-
-[package.extras]
-lint = ["docutils-stubs", "flake8", "mypy"]
-test = ["pytest"]
-
-[[package]]
-name = "sphinxcontrib-devhelp"
-version = "1.0.2"
-description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
- {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
-]
-
-[package.extras]
-lint = ["docutils-stubs", "flake8", "mypy"]
-test = ["pytest"]
-
-[[package]]
-name = "sphinxcontrib-htmlhelp"
-version = "2.0.0"
-description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
- {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
-]
-
-[package.extras]
-lint = ["docutils-stubs", "flake8", "mypy"]
-test = ["html5lib", "pytest"]
-
-[[package]]
-name = "sphinxcontrib-jsmath"
-version = "1.0.1"
-description = "A sphinx extension which renders display math in HTML via JavaScript"
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
- {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
-]
-
-[package.extras]
-test = ["flake8", "mypy", "pytest"]
-
-[[package]]
-name = "sphinxcontrib-qthelp"
-version = "1.0.3"
-description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
- {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
-]
-
-[package.extras]
-lint = ["docutils-stubs", "flake8", "mypy"]
-test = ["pytest"]
-
-[[package]]
-name = "sphinxcontrib-serializinghtml"
-version = "1.1.5"
-description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
- {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
-]
-
-[package.extras]
-lint = ["docutils-stubs", "flake8", "mypy"]
-test = ["pytest"]
-
-[[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
-category = "dev"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-files = [
- {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
- {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
-]
-
-[[package]]
-name = "tomli"
-version = "2.0.1"
-description = "A lil' TOML parser"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
- {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
-]
-
-[[package]]
-name = "tomlkit"
-version = "0.11.6"
-description = "Style preserving TOML library"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"},
- {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"},
-]
-
-[[package]]
-name = "typing-extensions"
-version = "4.4.0"
-description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
- {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
-]
-
-[[package]]
-name = "urllib3"
-version = "1.26.13"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-files = [
- {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"},
- {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"},
-]
-
-[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-
-[[package]]
-name = "virtualenv"
-version = "20.17.1"
-description = "Virtual Python Environment builder"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"},
- {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"},
-]
-
-[package.dependencies]
-distlib = ">=0.3.6,<1"
-filelock = ">=3.4.1,<4"
-platformdirs = ">=2.4,<3"
-
-[package.extras]
-docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
-testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
-
-[[package]]
-name = "win32-setctime"
-version = "1.1.0"
-description = "A small Python utility to set file creation time on Windows"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
- {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
-]
-
-[package.extras]
-dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
-
-[[package]]
-name = "wrapt"
-version = "1.14.1"
-description = "Module for decorators, wrappers and monkey patching."
-category = "dev"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-files = [
- {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
- {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
- {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
- {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
- {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
- {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
- {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
- {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
- {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
- {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
- {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
- {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
- {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
- {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
- {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
- {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
- {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
- {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
- {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
- {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
- {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
- {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
- {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
- {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
- {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
- {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
- {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
- {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
- {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
- {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
- {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
- {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
- {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
- {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
- {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
- {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
- {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
- {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
- {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
- {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
- {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
- {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
- {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
- {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
- {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
- {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
- {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
- {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
- {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
- {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
- {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
- {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
- {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
- {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
- {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
- {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
- {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
- {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
- {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
- {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
- {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
- {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
- {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
- {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
-]
-
-[[package]]
-name = "yapf"
-version = "0.32.0"
-description = "A formatter for Python code."
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
- {file = "yapf-0.32.0-py2.py3-none-any.whl", hash = "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32"},
- {file = "yapf-0.32.0.tar.gz", hash = "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b"},
-]
-
-[[package]]
-name = "yarl"
-version = "1.8.2"
-description = "Yet another URL library"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"},
- {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"},
- {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"},
- {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e"},
- {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996"},
- {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef"},
- {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8"},
- {file = "yarl-1.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6"},
- {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146"},
- {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f"},
- {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826"},
- {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3"},
- {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80"},
- {file = "yarl-1.8.2-cp310-cp310-win32.whl", hash = "sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42"},
- {file = "yarl-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574"},
- {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634"},
- {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd"},
- {file = "yarl-1.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581"},
- {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a"},
- {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf"},
- {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee"},
- {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76"},
- {file = "yarl-1.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b"},
- {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a"},
- {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6"},
- {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4"},
- {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c"},
- {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2"},
- {file = "yarl-1.8.2-cp311-cp311-win32.whl", hash = "sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b"},
- {file = "yarl-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c"},
- {file = "yarl-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef"},
- {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1"},
- {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077"},
- {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2"},
- {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89"},
- {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c"},
- {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0"},
- {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739"},
- {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b"},
- {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7"},
- {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37"},
- {file = "yarl-1.8.2-cp37-cp37m-win32.whl", hash = "sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89"},
- {file = "yarl-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5"},
- {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1"},
- {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918"},
- {file = "yarl-1.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229"},
- {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e"},
- {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e"},
- {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1"},
- {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3"},
- {file = "yarl-1.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901"},
- {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0"},
- {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05"},
- {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d"},
- {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c"},
- {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946"},
- {file = "yarl-1.8.2-cp38-cp38-win32.whl", hash = "sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165"},
- {file = "yarl-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f"},
- {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8"},
- {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf"},
- {file = "yarl-1.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77"},
- {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87"},
- {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0"},
- {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2"},
- {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08"},
- {file = "yarl-1.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d"},
- {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951"},
- {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220"},
- {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06"},
- {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516"},
- {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588"},
- {file = "yarl-1.8.2-cp39-cp39-win32.whl", hash = "sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83"},
- {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"},
- {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"},
-]
-
-[package.dependencies]
-idna = ">=2.0"
-multidict = ">=4.0"
-
-[[package]]
-name = "zipp"
-version = "3.11.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"},
- {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
-testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
-
-[metadata]
-lock-version = "2.0"
-python-versions = ">=3.8, <3.12"
-content-hash = "26f402924ff96b1e751aa1346a276db30926af9e1e72ee930402a180750d3cc2"
diff --git a/pyproject.toml b/pyproject.toml
index 87000e56..200b3a66 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,13 +1,11 @@
-[tool.poetry]
+[project]
name = "burnysc2"
-version = "6.0.7"
+version = "7.1.0"
description = "A StarCraft II API Client for Python 3"
-authors = ["BurnySc2 "]
-license = "MIT"
-homepage = "https://github.com/Burnysc2/python-sc2"
-documentation = "https://burnysc2.github.io/python-sc2/docs/index.html"
+authors = [{ name = "BurnySc2", email = "gamingburny@gmail.com" }]
+requires-python = ">=3.9, <3.14"
keywords = ["StarCraft", "StarCraft 2", "StarCraft II", "AI", "Bot"]
-classifiers=[
+classifiers = [
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
@@ -20,91 +18,66 @@ classifiers=[
"Operating System :: MacOS :: MacOS X",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
- "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",
]
-packages = [
- { include = "sc2/**/*.py" },
+readme = "README.md"
+
+dependencies = [
+ "aiohttp>=3.11.10",
+ "loguru>=0.7.3",
+ "mpyq>=0.2.5",
+ "numpy>=2.1.0; python_full_version >= '3.13'",
+ "numpy>=2.0.0; python_full_version < '3.13'",
+ "portpicker>=1.6.0",
+ "protobuf<4.0.0",
+ "s2clientprotocol>=5.0.14.93333.0",
+ "scipy>=1.14.1; python_full_version >= '3.13'",
+ "scipy>=1.7.1; python_full_version < '3.13'",
]
-[tool.poetry.dependencies]
-python = ">=3.8, <3.12"
-aiohttp = "^3.7.4"
-loguru = "^0.6.0"
-mpyq = "^0.2.5"
-numpy = "^1.19.3"
-portpicker = "^1.4.0"
-s2clientprotocol = "^5.0.7"
-scipy = "^1.7.1"
-protobuf = "<4.0.0"
+[dependency-groups]
+dev = [
+ "coverage>=7.6.9",
+ "hypothesis>=6.122.3",
+ "matplotlib>=3.9.4",
+ "mypy>=1.13.0",
+ "pillow>=11.0.0",
+ "pre-commit>=4.0.1",
+ "pyglet>=2.0.20",
+ "pylint>=3.3.2",
+ # Type checker
+ "pyrefly>=0.21.0",
+ "pytest>=8.3.4",
+ "pytest-asyncio>=0.25.0",
+ "pytest-benchmark>=5.1.0",
+ "pytest-cov>=6.0.0",
+ "radon>=6.0.1",
+ # Linter
+ "ruff>=0.8.3",
+ "sphinx-book-theme>=1.1.3",
+ "sphinx>=7.4.7",
+ "sphinx-autodoc-typehints>=2.3.0",
+ "toml>=0.10.2",
+ "yapf>=0.43.0",
+]
-[tool.poetry.dev-dependencies]
-codecov = "^2.1.12"
-coverage = "^7.0"
-hypothesis = "^6.23.1"
-matplotlib = "^3.4.3"
-mypy = "^0.960"
-pillow = "^9.0"
-pre-commit = "^2.15.0"
-pyglet = "^2.0"
-pylint = "^2.11.1"
-pytest = "^7.1.1"
-pytest-asyncio = "^0.18.3"
-pytest-benchmark = "^4.0.0"
-pytest-cov = "^4.0.0"
-radon = "^5.1.0"
-sphinx = "^5.0"
-sphinx-autodoc-typehints = "^1.18"
-sphinx-rtd-theme = "^1.0"
-toml = "^0.10.2"
-yapf = "^0.32.0"
+[tool.setuptools]
+license-files = []
+package-dir = { sc2 = "sc2" }
[build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
-
-[tool.mypy]
-python_version = "3.8"
-ignore_missing_imports = true
+# https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-a-build-backend
+# https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#custom-discovery
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
-[tool.pycln]
-all = true
-
-[tool.isort]
-line_length = 120
-multi_line_output = 3
-include_trailing_comma = true
-force_grid_wrap = 0
-use_parentheses = true
-ensure_newline_before_comments = true
-
-[tool.pylint.design]
-# Maximum number of locals for function / method body
-max-locals = 25
-[tool.pylint.messages_control]
-max-line-length = 120
-# C0301 Line too long
-# C0114 module Docstring
-# C0115 missing class docstring
-# C0116 missing function docstring
-# R0913 function with too many arguments
-# C0413 import order
-# C0411 import order of external libs
-# W0511 TODO
-# W0105 string statement with no effect
-# R0801 duplicate code
-# W0621 redefining name from outer score
-# C0103 variable name does not conform snake case naming style
-# R0903: Too few public methods of a class
-# E1101: Class 'SqlMetaclass' has no '__annotations__' member (no-member)
-# C0302: Too many lines in module (2027/1000) (too-many-lines)
-# R0902: Too many instance attributes (62/7) (too-many-instance-attributes)
-# R0915: Too many statements (61/50) (too-many-statements)
-# W0640: Cell variable mining_place defined in loop (cell-var-from-loop)
-# W1514: Using open without explicitly specifying an encoding (unspecified-encoding)
-disable = ["C0301", "C0114", "C0115", "C0116", "R0913", "C0413", "C0411", "W0511", "W0105", "R0801", "W0621", "C0103", "R0903", "E1101", "C0302", "R0902", "R0915", "W0640", "W1514"]
+[project.urls]
+Repository = "https://github.com/Burnysc2/python-sc2"
+Documentation = "https://burnysc2.github.io/python-sc2"
[tool.yapf]
based_on_style = "pep8"
@@ -112,3 +85,52 @@ column_limit = 120
split_arguments_when_comma_terminated = true
dedent_closing_brackets = true
allow_split_before_dict_value = false
+
+[tool.pyrefly]
+project_includes = ["sc2", "examples", "test"]
+
+[tool.ruff]
+target-version = 'py310'
+line-length = 120
+
+[tool.ruff.lint]
+# Allow unused variables when underscore-prefixed.
+dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
+select = [
+ "C4", # flake8-comprehensions
+ "E", # Error
+ "F", # pyflakes
+ "BLE", # flake8-blind-except
+ # "I", # isort
+ "N", # pep8-naming
+ "PGH", # pygrep-hooks
+ "PTH", # flake8-use-pathlib
+ "SIM", # flake8-simplify
+ "W", # Warning
+ "Q", # flake8-quotes
+ "YTT", # flake8-2020
+ "UP", # pyupgrade
+ # "A", # flake8-builtins
+]
+# Allow Pydantic's `@validator` decorator to trigger class method treatment.
+pep8-naming.classmethod-decorators = ["pydantic.validator", "classmethod"]
+ignore = [
+ "E501", # Line too long
+ "E402", # Module level import not at top of file
+ "F841", # Local variable `...` is assigned to but never used
+ "BLE001", # Do not catch blind exception: `Exception`
+ "N802", # Function name `...` should be lowercase
+ "N806", # Variable `...` in function should be lowercase.
+ "SIM102", # Use a single `if` statement instead of nested `if` statements
+ "UP007", # Use `X | Y` for type annotations
+ "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
+]
+
+[tool.ruff.pyupgrade]
+# Preserve types, even if a file imports `from __future__ import annotations`.
+# Remove once support for py3.8 and 3.9 is dropped
+keep-runtime-typing = true
+
+[tool.ruff.pep8-naming]
+# Allow Pydantic's `@validator` decorator to trigger class method treatment.
+classmethod-decorators = ["pydantic.validator", "classmethod"]
diff --git a/sc2/action.py b/sc2/action.py
index 9e84ac7d..0500309e 100644
--- a/sc2/action.py
+++ b/sc2/action.py
@@ -1,8 +1,9 @@
from __future__ import annotations
from itertools import groupby
-from typing import TYPE_CHECKING, Union
+from typing import TYPE_CHECKING
+# pyre-ignore[21]
from s2clientprotocol import raw_pb2 as raw_pb
from sc2.position import Point2
@@ -13,7 +14,6 @@
from sc2.unit_command import UnitCommand
-# pylint: disable=R0912
def combine_actions(action_iter):
"""
Example input:
@@ -26,7 +26,7 @@ def combine_actions(action_iter):
"""
for key, items in groupby(action_iter, key=lambda a: a.combining_tuple):
ability: AbilityId
- target: Union[None, Point2, Unit]
+ target: None | Point2 | Unit
queue: bool
# See constants.py for combineable abilities
combineable: bool
@@ -35,8 +35,7 @@ def combine_actions(action_iter):
if combineable:
# Combine actions with no target, e.g. lift, burrowup, burrowdown, siege, unsiege, uproot spines
cmd = raw_pb.ActionRawUnitCommand(
- ability_id=ability.value, unit_tags={u.unit.tag
- for u in items}, queue_command=queue
+ ability_id=ability.value, unit_tags={u.unit.tag for u in items}, queue_command=queue
)
# Combine actions with target point, e.g. attack_move or move commands on a position
if isinstance(target, Point2):
@@ -46,7 +45,7 @@ def combine_actions(action_iter):
elif isinstance(target, Unit):
cmd.target_unit_tag = target.tag
elif target is not None:
- raise RuntimeError(f"Must target a unit, point or None, found '{target !r}'")
+ raise RuntimeError(f"Must target a unit, point or None, found '{target!r}'")
yield raw_pb.ActionRaw(unit_command=cmd)
@@ -85,4 +84,4 @@ def combine_actions(action_iter):
)
yield raw_pb.ActionRaw(unit_command=cmd)
else:
- raise RuntimeError(f"Must target a unit, point or None, found '{target !r}'")
+ raise RuntimeError(f"Must target a unit, point or None, found '{target!r}'")
diff --git a/sc2/bot_ai.py b/sc2/bot_ai.py
index 2252f04a..55ef056e 100644
--- a/sc2/bot_ai.py
+++ b/sc2/bot_ai.py
@@ -1,4 +1,4 @@
-# pylint: disable=W0212,R0916,R0904
+# pyre-ignore-all-errors[6, 16]
from __future__ import annotations
import math
@@ -6,13 +6,14 @@
import warnings
from collections import Counter
from functools import cached_property
-from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
+from typing import TYPE_CHECKING
from loguru import logger
from sc2.bot_ai_internal import BotAIInternal
from sc2.cache import property_cache_once_per_frame
from sc2.constants import (
+ CREATION_ABILITY_FIX,
EQUIVALENTS_FOR_TECH_PROGRESS,
PROTOSS_TECH_REQUIREMENT,
TERRAN_STRUCTURES_REQUIRE_SCV,
@@ -43,17 +44,17 @@ class BotAI(BotAIInternal):
@property
def time(self) -> float:
- """ Returns time in seconds, assumes the game is played on 'faster' """
+ """Returns time in seconds, assumes the game is played on 'faster'"""
return self.state.game_loop / 22.4 # / (1/1.4) * (1/16)
@property
def time_formatted(self) -> str:
- """ Returns time as string in min:sec format """
+ """Returns time as string in min:sec format"""
t = self.time
return f"{int(t // 60):02}:{int(t % 60):02}"
@property
- def step_time(self) -> Tuple[float, float, float, float]:
+ def step_time(self) -> tuple[float, float, float, float]:
"""Returns a tuple of step duration in milliseconds.
First value is the minimum step duration - the shortest the bot ever took
Second value is the average step duration
@@ -70,6 +71,7 @@ def step_time(self) -> Tuple[float, float, float, float]:
self._last_step_step_time * 1000,
)
+ # pyre-ignore[11]
def alert(self, alert_code: Alert) -> bool:
"""
Check if alert is triggered in the current step.
@@ -120,7 +122,7 @@ def start_location(self) -> Point2:
return self.game_info.player_start_location
@property
- def enemy_start_locations(self) -> List[Point2]:
+ def enemy_start_locations(self) -> list[Point2]:
"""Possible start locations for enemies."""
return self.game_info.start_locations
@@ -148,35 +150,37 @@ def main_base_ramp(self) -> Ramp:
return found_main_base_ramp
@property_cache_once_per_frame
- def expansion_locations_list(self) -> List[Point2]:
- """ Returns a list of expansion positions, not sorted in any way. """
- assert (
- self._expansion_positions_list
- ), "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless."
+ def expansion_locations_list(self) -> list[Point2]:
+ """Returns a list of expansion positions, not sorted in any way."""
+ assert self._expansion_positions_list, (
+ "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless."
+ )
return self._expansion_positions_list
@property_cache_once_per_frame
- def expansion_locations_dict(self) -> Dict[Point2, Units]:
+ def expansion_locations_dict(self) -> dict[Point2, Units]:
"""
Returns dict with the correct expansion position Point2 object as key,
resources as Units (mineral fields and vespene geysers) as value.
Caution: This function is slow. If you only need the expansion locations, use the property above.
"""
- assert (
- self._expansion_positions_list
- ), "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless."
- expansion_locations: Dict[Point2, Units] = {pos: Units([], self) for pos in self._expansion_positions_list}
+ assert self._expansion_positions_list, (
+ "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless."
+ )
+ expansion_locations: dict[Point2, Units] = {pos: Units([], self) for pos in self._expansion_positions_list}
for resource in self.resources:
# It may be that some resources are not mapped to an expansion location
- exp_position: Point2 = self._resource_location_to_expansion_position_dict.get(resource.position, None)
+ exp_position: Point2 | None = self._resource_location_to_expansion_position_dict.get(
+ resource.position, None
+ )
if exp_position:
assert exp_position in expansion_locations
expansion_locations[exp_position].append(resource)
return expansion_locations
@property
- def units_created(self) -> Counter[UnitTypeId, int]:
+ def units_created(self) -> Counter[UnitTypeId]:
"""Returns a Counter for all your units and buildings you have created so far.
This may be used for statistics (at the end of the game) or for strategic decision making.
@@ -196,8 +200,8 @@ async def on_unit_created(self, unit: Unit):
return self._units_created
async def get_available_abilities(
- self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False
- ) -> List[List[AbilityId]]:
+ self, units: list[Unit] | Units, ignore_resource_requirements: bool = False
+ ) -> list[list[AbilityId]]:
"""Returns available abilities of one or more units. Right now only checks cooldown, energy cost, and whether the ability has been researched.
Examples::
@@ -213,15 +217,18 @@ async def get_available_abilities(
return await self.client.query_available_abilities(units, ignore_resource_requirements)
async def expand_now(
- self, building: UnitTypeId = None, max_distance: float = 10, location: Optional[Point2] = None
- ):
+ self,
+ building: UnitTypeId | None = None,
+ max_distance: int = 10,
+ location: Point2 | None = None,
+ ) -> None:
"""Finds the next possible expansion via 'self.get_next_expansion()'. If the target expansion is blocked (e.g. an enemy unit), it will misplace the expansion.
:param building:
:param max_distance:
:param location:"""
- if not building:
+ if building is None:
# self.race is never Race.Random
start_townhall_type = {
Race.Protoss: UnitTypeId.NEXUS,
@@ -240,7 +247,7 @@ async def expand_now(
return
await self.build(building, near=location, max_distance=max_distance, random_alternative=False, placement_step=1)
- async def get_next_expansion(self) -> Optional[Point2]:
+ async def get_next_expansion(self) -> Point2 | None:
"""Find next expansion location."""
closest = None
@@ -265,8 +272,7 @@ def is_near_to_expansion(t):
return closest
- # pylint: disable=R0912
- async def distribute_workers(self, resource_ratio: float = 2):
+ async def distribute_workers(self, resource_ratio: float = 2) -> None:
"""
Distributes workers across all the bases taken.
Keyword `resource_ratio` takes a float. If the current minerals to gas
@@ -299,21 +305,20 @@ async def distribute_workers(self, resource_ratio: float = 2):
# get all workers that target the gas extraction site
# or are on their way back from it
local_workers = self.workers.filter(
- lambda unit: unit.order_target == mining_place.tag or
- (unit.is_carrying_vespene and unit.order_target == bases.closest_to(mining_place).tag)
+ lambda unit: unit.order_target == mining_place.tag
+ or (unit.is_carrying_vespene and unit.order_target == bases.closest_to(mining_place).tag)
)
else:
# get tags of minerals around expansion
local_minerals_tags = {
- mineral.tag
- for mineral in self.mineral_field if mineral.distance_to(mining_place) <= 8
+ mineral.tag for mineral in self.mineral_field if mineral.distance_to(mining_place) <= 8
}
# get all target tags a worker can have
# tags of the minerals he could mine at that base
# get workers that work at that gather site
local_workers = self.workers.filter(
- lambda unit: unit.order_target in local_minerals_tags or
- (unit.is_carrying_minerals and unit.order_target == mining_place.tag)
+ lambda unit: unit.order_target in local_minerals_tags
+ or (unit.is_carrying_minerals and unit.order_target == mining_place.tag)
)
# too many workers
if difference > 0:
@@ -326,9 +331,11 @@ async def distribute_workers(self, resource_ratio: float = 2):
# prepare all minerals near a base if we have too many workers
# and need to send them to the closest patch
+ all_minerals_near_base = []
if len(worker_pool) > len(deficit_mining_places):
all_minerals_near_base = [
- mineral for mineral in self.mineral_field
+ mineral
+ for mineral in self.mineral_field
if any(mineral.distance_to(base) <= 8 for base in self.townhalls.ready)
]
# distribute every worker in the pool
@@ -372,7 +379,7 @@ async def distribute_workers(self, resource_ratio: float = 2):
pass
@property_cache_once_per_frame
- def owned_expansions(self) -> Dict[Point2, Unit]:
+ def owned_expansions(self) -> dict[Point2, Unit]:
"""Dict of expansions owned by the player with mapping {expansion_location: townhall_structure}."""
owned = {}
for el in self.expansion_locations_list:
@@ -401,6 +408,8 @@ def calculate_supply_cost(self, unit_type: UnitTypeId) -> float:
:param unit_type:"""
if unit_type in {UnitTypeId.ZERGLING}:
return 1
+ if unit_type in {UnitTypeId.BANELING}:
+ return 0
unit_supply_cost = self.game_data.units[unit_type.value]._proto.food_required
if unit_supply_cost > 0 and unit_type in UNIT_TRAINED_FROM and len(UNIT_TRAINED_FROM[unit_type]) == 1:
producer: UnitTypeId
@@ -441,7 +450,7 @@ def calculate_unit_value(self, unit_type: UnitTypeId) -> Cost:
unit_data = self.game_data.units[unit_type.value]
return Cost(unit_data._proto.mineral_cost, unit_data._proto.vespene_cost)
- def calculate_cost(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId]) -> Cost:
+ def calculate_cost(self, item_id: UnitTypeId | UpgradeId | AbilityId) -> Cost:
"""
Calculate the required build, train or morph cost of a unit. It is recommended to use the UnitTypeId instead of the ability to create the unit.
The total cost to create a ravager is 100/100, but the actual morph cost from roach to ravager is only 25/75, so this function returns 25/75.
@@ -473,16 +482,18 @@ def calculate_cost(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId]) -> Co
"""
if isinstance(item_id, UnitTypeId):
# Fix cost for reactor and techlab where the API returns 0 for both
- if item_id in {UnitTypeId.REACTOR, UnitTypeId.TECHLAB, UnitTypeId.ARCHON}:
+ if item_id in {UnitTypeId.REACTOR, UnitTypeId.TECHLAB, UnitTypeId.ARCHON, UnitTypeId.BANELING}:
if item_id == UnitTypeId.REACTOR:
return Cost(50, 50)
if item_id == UnitTypeId.TECHLAB:
return Cost(50, 25)
+ if item_id == UnitTypeId.BANELING:
+ return Cost(25, 25)
if item_id == UnitTypeId.ARCHON:
return self.calculate_unit_value(UnitTypeId.ARCHON)
unit_data = self.game_data.units[item_id.value]
# Cost of morphs is automatically correctly calculated by 'calculate_ability_cost'
- return self.game_data.calculate_ability_cost(unit_data.creation_ability)
+ return self.game_data.calculate_ability_cost(unit_data.creation_ability.exact_id)
if isinstance(item_id, UpgradeId):
cost = self.game_data.upgrades[item_id.value].cost
@@ -491,7 +502,7 @@ def calculate_cost(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId]) -> Co
cost = self.game_data.calculate_ability_cost(item_id)
return cost
- def can_afford(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId], check_supply_cost: bool = True) -> bool:
+ def can_afford(self, item_id: UnitTypeId | UpgradeId | AbilityId, check_supply_cost: bool = True) -> bool:
"""Tests if the player has enough resources to build a unit or structure.
Example::
@@ -522,9 +533,9 @@ async def can_cast(
self,
unit: Unit,
ability_id: AbilityId,
- target: Optional[Union[Unit, Point2]] = None,
+ target: Unit | Point2 | None = None,
only_check_energy_and_cooldown: bool = False,
- cached_abilities_of_unit: List[AbilityId] = None,
+ cached_abilities_of_unit: list[AbilityId] | None = None,
) -> bool:
"""Tests if a unit has an ability available and enough energy to cast it.
@@ -553,28 +564,32 @@ async def can_cast(
if only_check_energy_and_cooldown:
return True
cast_range = self.game_data.abilities[ability_id.value]._proto.cast_range
- ability_target = self.game_data.abilities[ability_id.value]._proto.target
+ ability_target: int = self.game_data.abilities[ability_id.value]._proto.target
# Check if target is in range (or is a self cast like stimpack)
if (
- ability_target == 1 or ability_target == Target.PointOrNone.value and isinstance(target, Point2)
+ ability_target == 1
+ or ability_target == Target.PointOrNone.value
+ and isinstance(target, Point2)
and unit.distance_to(target) <= unit.radius + target.radius + cast_range
): # cant replace 1 with "Target.None.value" because ".None" doesnt seem to be a valid enum name
return True
# Check if able to use ability on a unit
if (
- ability_target in {Target.Unit.value, Target.PointOrUnit.value} and isinstance(target, Unit)
+ ability_target in {Target.Unit.value, Target.PointOrUnit.value}
+ and isinstance(target, Unit)
and unit.distance_to(target) <= unit.radius + target.radius + cast_range
):
return True
# Check if able to use ability on a position
if (
- ability_target in {Target.Point.value, Target.PointOrUnit.value} and isinstance(target, Point2)
+ ability_target in {Target.Point.value, Target.PointOrUnit.value}
+ and isinstance(target, Point2)
and unit.distance_to(target) <= unit.radius + cast_range
):
return True
return False
- def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) -> Optional[Unit]:
+ def select_build_worker(self, pos: Unit | Point2, force: bool = False) -> Unit | None:
"""Select a worker to build a building with.
Example::
@@ -593,7 +608,9 @@ def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) ->
if workers:
for worker in workers.sorted_by_distance_to(pos).prefer_idle:
if (
- worker not in self.unit_tags_received_action and not worker.orders or len(worker.orders) == 1
+ worker not in self.unit_tags_received_action
+ and not worker.orders
+ or len(worker.orders) == 1
and worker.orders[0].ability.id in {AbilityId.MOVE, AbilityId.HARVEST_GATHER}
):
return worker
@@ -601,15 +618,14 @@ def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) ->
return workers.random if force else None
return None
- async def can_place_single(self, building: Union[AbilityId, UnitTypeId], position: Point2) -> bool:
- """ Checks the placement for only one position. """
+ async def can_place_single(self, building: AbilityId | UnitTypeId, position: Point2) -> bool:
+ """Checks the placement for only one position."""
if isinstance(building, UnitTypeId):
creation_ability = self.game_data.units[building.value].creation_ability.id
return (await self.client._query_building_placement_fast(creation_ability, [position]))[0]
return (await self.client._query_building_placement_fast(building, [position]))[0]
- async def can_place(self, building: Union[AbilityData, AbilityId, UnitTypeId],
- positions: List[Point2]) -> List[bool]:
+ async def can_place(self, building: AbilityData | AbilityId | UnitTypeId, positions: list[Point2]) -> list[bool]:
"""Tests if a building can be placed in the given locations.
Example::
@@ -642,20 +658,20 @@ async def can_place(self, building: Union[AbilityData, AbilityId, UnitTypeId],
)
return await self.can_place_single(building, positions)
assert isinstance(positions, list), f"Expected an iterable (list, tuple), but was: {positions}"
- assert isinstance(
- positions[0], Point2
- ), f"List is expected to have Point2, but instead had: {positions[0]} {type(positions[0])}"
+ assert isinstance(positions[0], Point2), (
+ f"List is expected to have Point2, but instead had: {positions[0]} {type(positions[0])}"
+ )
return await self.client._query_building_placement_fast(building, positions)
async def find_placement(
self,
- building: Union[UnitTypeId, AbilityId],
+ building: UnitTypeId | AbilityId,
near: Point2,
max_distance: int = 20,
random_alternative: bool = True,
placement_step: int = 2,
addon_place: bool = False,
- ) -> Optional[Point2]:
+ ) -> Point2 | None:
"""Finds a placement location for building.
Example::
@@ -677,9 +693,9 @@ async def find_placement(
if isinstance(building, UnitTypeId):
building = self.game_data.units[building.value].creation_ability.id
- if await self.can_place_single(
- building, near
- ) and (not addon_place or await self.can_place_single(UnitTypeId.SUPPLYDEPOT, near.offset((2.5, -0.5)))):
+ if await self.can_place_single(building, near) and (
+ not addon_place or await self.can_place_single(UnitTypeId.SUPPLYDEPOT, near.offset((2.5, -0.5)))
+ ):
return near
if max_distance == 0:
@@ -687,11 +703,12 @@ async def find_placement(
for distance in range(placement_step, max_distance, placement_step):
possible_positions = [
- Point2(p).offset(near).to2 for p in (
- [(dx, -distance) for dx in range(-distance, distance + 1, placement_step)] +
- [(dx, distance) for dx in range(-distance, distance + 1, placement_step)] +
- [(-distance, dy) for dy in range(-distance, distance + 1, placement_step)] +
- [(distance, dy) for dy in range(-distance, distance + 1, placement_step)]
+ Point2(p).offset(near).to2
+ for p in (
+ [(dx, -distance) for dx in range(-distance, distance + 1, placement_step)]
+ + [(dx, distance) for dx in range(-distance, distance + 1, placement_step)]
+ + [(-distance, dy) for dy in range(-distance, distance + 1, placement_step)]
+ + [(distance, dy) for dy in range(-distance, distance + 1, placement_step)]
)
]
res = await self.client._query_building_placement_fast(building, possible_positions)
@@ -740,7 +757,7 @@ def already_pending_upgrade(self, upgrade_type: UpgradeId) -> float:
return order.progress
return 0
- def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int]) -> float:
+ def structure_type_build_progress(self, structure_type: UnitTypeId | int) -> float:
"""
Returns the build progress of a structure type.
@@ -767,26 +784,28 @@ def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int])
:param structure_type:
"""
- assert isinstance(
- structure_type, (int, UnitTypeId)
- ), f"Needs to be int or UnitTypeId, but was: {type(structure_type)}"
+ assert isinstance(structure_type, (int, UnitTypeId)), (
+ f"Needs to be int or UnitTypeId, but was: {type(structure_type)}"
+ )
if isinstance(structure_type, int):
structure_type_value: int = structure_type
structure_type = UnitTypeId(structure_type_value)
else:
structure_type_value = structure_type.value
assert structure_type_value, f"structure_type can not be 0 or NOTAUNIT, but was: {structure_type_value}"
- equiv_values: Set[int] = {structure_type_value} | {
- s_type.value
- for s_type in EQUIVALENTS_FOR_TECH_PROGRESS.get(structure_type, set())
+ equiv_values: set[int] = {structure_type_value} | {
+ s_type.value for s_type in EQUIVALENTS_FOR_TECH_PROGRESS.get(structure_type, set())
}
# SUPPLYDEPOTDROP is not in self.game_data.units, so bot_ai should not check the build progress via creation ability (worker abilities)
if structure_type_value not in self.game_data.units:
return max((s.build_progress for s in self.structures if s._proto.unit_type in equiv_values), default=0)
- creation_ability: AbilityData = self.game_data.units[structure_type_value].creation_ability
+ creation_ability_data: AbilityData = self.game_data.units[structure_type_value].creation_ability
+ if creation_ability_data is None:
+ return 0
+ creation_ability: AbilityId = creation_ability_data.exact_id
max_value = max(
- [s.build_progress for s in self.structures if s._proto.unit_type in equiv_values] +
- [self._abilities_all_units[1].get(creation_ability, 0)],
+ [s.build_progress for s in self.structures if s._proto.unit_type in equiv_values]
+ + [self._abilities_count_and_build_progress[1].get(creation_ability, 0)],
default=0,
)
return max_value
@@ -824,12 +843,12 @@ def tech_requirement_progress(self, structure_type: UnitTypeId) -> float:
# unit_info_id_value = self.game_data.units[structure_type.value]._proto.tech_requirement
if not unit_info_id_value: # Equivalent to "if unit_info_id_value == 0:"
return 1
- progresses: List[float] = [self.structure_type_build_progress(unit_info_id_value)]
+ progresses: list[float] = [self.structure_type_build_progress(unit_info_id_value)]
for equiv_structure in EQUIVALENTS_FOR_TECH_PROGRESS.get(unit_info_id, []):
progresses.append(self.structure_type_build_progress(equiv_structure.value))
return max(progresses)
- def already_pending(self, unit_type: Union[UpgradeId, UnitTypeId]) -> float:
+ def already_pending(self, unit_type: UpgradeId | UnitTypeId) -> float:
"""
Returns a number of buildings or units already in progress, or if a
worker is en route to build it. This also includes queued orders for
@@ -841,57 +860,64 @@ def already_pending(self, unit_type: Union[UpgradeId, UnitTypeId]) -> float:
amount_of_CCs_in_queue_and_production: int = self.already_pending(UnitTypeId.COMMANDCENTER)
amount_of_lairs_morphing: int = self.already_pending(UnitTypeId.LAIR)
-
:param unit_type:
"""
if isinstance(unit_type, UpgradeId):
return self.already_pending_upgrade(unit_type)
- ability = self.game_data.units[unit_type.value].creation_ability
- return self._abilities_all_units[0][ability]
+ try:
+ ability = self.game_data.units[unit_type.value].creation_ability.exact_id
+ except AttributeError:
+ if unit_type in CREATION_ABILITY_FIX:
+ # Hotfix for checking pending archons
+ if unit_type == UnitTypeId.ARCHON:
+ return self._abilities_count_and_build_progress[0][AbilityId.ARCHON_WARP_TARGET] / 2
+ # Hotfix for rich geysirs
+ return self._abilities_count_and_build_progress[0][CREATION_ABILITY_FIX[unit_type]]
+ logger.error(f"Uncaught UnitTypeId: {unit_type}")
+ return 0
+ return self._abilities_count_and_build_progress[0][ability]
def worker_en_route_to_build(self, unit_type: UnitTypeId) -> float:
"""This function counts how many workers are on the way to start the construction a building.
:param unit_type:"""
- ability = self.game_data.units[unit_type.value].creation_ability
+ ability = self.game_data.units[unit_type.value].creation_ability.exact_id
return self._worker_orders[ability]
@property_cache_once_per_frame
def structures_without_construction_SCVs(self) -> Units:
"""Returns all structures that do not have an SCV constructing it.
Warning: this function may move to become a Units filter."""
- worker_targets: Set[Union[int, Point2]] = set()
+ worker_targets: set[int | Point2] = set()
for worker in self.workers:
# Ignore repairing workers
if not worker.is_constructing_scv:
continue
for order in worker.orders:
# When a construction is resumed, the worker.orders[0].target is the tag of the structure, else it is a Point2
- target = order.target
- if isinstance(target, int):
- worker_targets.add(target)
- else:
- worker_targets.add(Point2.from_proto(target))
+ worker_targets.add(order.target)
return self.structures.filter(
lambda structure: structure.build_progress < 1
# Redundant check?
- and structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV and structure.position not in worker_targets and
- structure.tag not in worker_targets and structure.tag in self._structures_previous_map and self.
- _structures_previous_map[structure.tag].build_progress == structure.build_progress
+ and structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV
+ and structure.position not in worker_targets
+ and structure.tag not in worker_targets
+ and structure.tag in self._structures_previous_map
+ and self._structures_previous_map[structure.tag].build_progress == structure.build_progress
)
async def build(
self,
building: UnitTypeId,
- near: Union[Unit, Point2],
+ near: Unit | Point2,
max_distance: int = 20,
- build_worker: Optional[Unit] = None,
+ build_worker: Unit | None = None,
random_alternative: bool = True,
placement_step: int = 2,
) -> bool:
"""Not recommended as this function checks many positions if it "can place" on them until it found a valid
position. Also if the given position is not placeable, this function tries to find a nearby position to place
- the structure. Then uses 'self.do' to give the worker the order to start the construction.
+ the structure. Then orders the worker to start the construction.
:param building:
:param near:
@@ -927,13 +953,13 @@ def train(
self,
unit_type: UnitTypeId,
amount: int = 1,
- closest_to: Point2 = None,
- train_only_idle_buildings: bool = True
+ closest_to: Point2 | None = None,
+ train_only_idle_buildings: bool = True,
) -> int:
"""Trains a specified number of units. Trains only one if amount is not specified.
Warning: currently has issues with warp gate warp ins
- New function. Please report any bugs!
+ Very generic function. Please use with caution and report any bugs!
Example Zerg::
@@ -975,7 +1001,7 @@ def train(
trained_amount = 0
# All train structure types: queen can made from hatchery, lair, hive
- train_structure_type: Set[UnitTypeId] = UNIT_TRAINED_FROM[unit_type]
+ train_structure_type: set[UnitTypeId] = UNIT_TRAINED_FROM[unit_type]
train_structures = self.structures if self.race != Race.Zerg else self.structures | self.larva
requires_techlab = any(
TRAIN_INFO[structure_type][unit_type].get("requires_techlab", False)
@@ -984,7 +1010,6 @@ def train(
is_protoss = self.race == Race.Protoss
is_terran = self.race == Race.Terran
can_have_addons = any(
- # pylint: disable=C0208
u in train_structure_type for u in {UnitTypeId.BARRACKS, UnitTypeId.FACTORY, UnitTypeId.STARPORT}
)
# Sort structures closest to a point
@@ -993,8 +1018,8 @@ def train(
elif can_have_addons:
# This should sort the structures in ascending order: first structures with reactor, then naked, then with techlab
train_structures = train_structures.sorted(
- key=lambda structure: -1 * (structure.add_on_tag in self.reactor_tags) + 1 *
- (structure.add_on_tag in self.techlab_tags)
+ key=lambda structure: -1 * (structure.add_on_tag in self.reactor_tags)
+ + 1 * (structure.add_on_tag in self.techlab_tags)
)
structure: Unit
@@ -1030,7 +1055,10 @@ def train(
else:
# Normal train a unit from larva or inside a structure
successfully_trained = self.do(
- structure.train(unit_type), subtract_cost=True, subtract_supply=True, ignore_warning=True
+ structure.train(unit_type),
+ subtract_cost=True,
+ subtract_supply=True,
+ ignore_warning=True,
)
# Check if structure has reactor: queue same unit again
if (
@@ -1087,16 +1115,17 @@ def research(self, upgrade_type: UpgradeId) -> bool:
:param upgrade_type:
"""
- assert (
- upgrade_type in UPGRADE_RESEARCHED_FROM
- ), f"Could not find upgrade {upgrade_type} in 'research from'-dictionary"
+ assert upgrade_type in UPGRADE_RESEARCHED_FROM, (
+ f"Could not find upgrade {upgrade_type} in 'research from'-dictionary"
+ )
# Not affordable
if not self.can_afford(upgrade_type):
return False
- research_structure_types: UnitTypeId = UPGRADE_RESEARCHED_FROM[upgrade_type]
- required_tech_building: Optional[UnitTypeId] = RESEARCH_INFO[research_structure_types][upgrade_type].get(
+ research_structure_type: UnitTypeId = UPGRADE_RESEARCHED_FROM[upgrade_type]
+ # pyre-ignore[9]
+ required_tech_building: UnitTypeId | None = RESEARCH_INFO[research_structure_type][upgrade_type].get(
"required_building", None
)
@@ -1118,8 +1147,8 @@ def research(self, upgrade_type: UpgradeId) -> bool:
}
# Convert to a set, or equivalent structures are chosen
# Overlord speed upgrade can be researched from hatchery, lair or hive
- research_structure_types: Set[UnitTypeId] = equiv_structures.get(
- research_structure_types, {research_structure_types}
+ research_structure_types: set[UnitTypeId] = equiv_structures.get(
+ research_structure_type, {research_structure_type}
)
structure: Unit
@@ -1129,6 +1158,8 @@ def research(self, upgrade_type: UpgradeId) -> bool:
structure.type_id in research_structure_types
# If structure hasn't received an action/order this frame
and structure.tag not in self.unit_tags_received_action
+ # Structure is ready / completed
+ and structure.is_ready
# Structure is idle
and structure.is_idle
# Structure belongs to protoss and is powered (near pylon)
@@ -1136,12 +1167,14 @@ def research(self, upgrade_type: UpgradeId) -> bool:
):
# Can_afford check was already done earlier in this function
successful_action: bool = self.do(
- structure.research(upgrade_type), subtract_cost=True, ignore_warning=True
+ structure.research(upgrade_type),
+ subtract_cost=True,
+ ignore_warning=True,
)
return successful_action
return False
- async def chat_send(self, message: str, team_only: bool = False):
+ async def chat_send(self, message: str, team_only: bool = False) -> None:
"""Send a chat message to the SC2 Client.
Example::
@@ -1153,17 +1186,21 @@ async def chat_send(self, message: str, team_only: bool = False):
assert isinstance(message, str), f"{message} is not a string"
await self.client.chat_send(message, team_only)
- def in_map_bounds(self, pos: Union[Point2, tuple, list]) -> bool:
+ def in_map_bounds(self, pos: Point2 | tuple | list) -> bool:
"""Tests if a 2 dimensional point is within the map boundaries of the pixelmaps.
+
:param pos:"""
return (
- self.game_info.playable_area.x <= pos[0] <
- self.game_info.playable_area.x + self.game_info.playable_area.width and self.game_info.playable_area.y <=
- pos[1] < self.game_info.playable_area.y + self.game_info.playable_area.height
+ self.game_info.playable_area.x
+ <= pos[0]
+ < self.game_info.playable_area.x + self.game_info.playable_area.width
+ and self.game_info.playable_area.y
+ <= pos[1]
+ < self.game_info.playable_area.y + self.game_info.playable_area.height
)
# For the functions below, make sure you are inside the boundaries of the map size.
- def get_terrain_height(self, pos: Union[Point2, Unit]) -> int:
+ def get_terrain_height(self, pos: Point2 | Unit) -> int:
"""Returns terrain height at a position.
Caution: terrain height is different from a unit's z-coordinate.
@@ -1172,7 +1209,7 @@ def get_terrain_height(self, pos: Union[Point2, Unit]) -> int:
pos = pos.position.rounded
return self.game_info.terrain_height[pos]
- def get_terrain_z_height(self, pos: Union[Point2, Unit]) -> float:
+ def get_terrain_z_height(self, pos: Point2 | Unit) -> float:
"""Returns terrain z-height at a position.
:param pos:"""
@@ -1180,7 +1217,7 @@ def get_terrain_z_height(self, pos: Union[Point2, Unit]) -> float:
pos = pos.position.rounded
return -16 + 32 * self.game_info.terrain_height[pos] / 255
- def in_placement_grid(self, pos: Union[Point2, Unit]) -> bool:
+ def in_placement_grid(self, pos: Point2 | Unit) -> bool:
"""Returns True if you can place something at a position.
Remember, buildings usually use 2x2, 3x3 or 5x5 of these grid points.
Caution: some x and y offset might be required, see ramp code in game_info.py
@@ -1190,7 +1227,7 @@ def in_placement_grid(self, pos: Union[Point2, Unit]) -> bool:
pos = pos.position.rounded
return self.game_info.placement_grid[pos] == 1
- def in_pathing_grid(self, pos: Union[Point2, Unit]) -> bool:
+ def in_pathing_grid(self, pos: Point2 | Unit) -> bool:
"""Returns True if a ground unit can pass through a grid point.
:param pos:"""
@@ -1198,7 +1235,7 @@ def in_pathing_grid(self, pos: Union[Point2, Unit]) -> bool:
pos = pos.position.rounded
return self.game_info.pathing_grid[pos] == 1
- def is_visible(self, pos: Union[Point2, Unit]) -> bool:
+ def is_visible(self, pos: Point2 | Unit) -> bool:
"""Returns True if you have vision on a grid point.
:param pos:"""
@@ -1207,7 +1244,7 @@ def is_visible(self, pos: Union[Point2, Unit]) -> bool:
pos = pos.position.rounded
return self.state.visibility[pos] == 2
- def has_creep(self, pos: Union[Point2, Unit]) -> bool:
+ def has_creep(self, pos: Point2 | Unit) -> bool:
"""Returns True if there is creep on the grid point.
:param pos:"""
@@ -1215,7 +1252,7 @@ def has_creep(self, pos: Union[Point2, Unit]) -> bool:
pos = pos.position.rounded
return self.state.creep[pos] == 1
- async def on_unit_destroyed(self, unit_tag: int):
+ async def on_unit_destroyed(self, unit_tag: int) -> None:
"""
Override this in your bot class.
Note that this function uses unit tags and not the unit objects
@@ -1226,12 +1263,12 @@ async def on_unit_destroyed(self, unit_tag: int):
:param unit_tag:
"""
- async def on_unit_created(self, unit: Unit):
+ async def on_unit_created(self, unit: Unit) -> None:
"""Override this in your bot class. This function is called when a unit is created.
:param unit:"""
- async def on_unit_type_changed(self, unit: Unit, previous_type: UnitTypeId):
+ async def on_unit_type_changed(self, unit: Unit, previous_type: UnitTypeId) -> None:
"""Override this in your bot class. This function is called when a unit type has changed. To get the current UnitTypeId of the unit, use 'unit.type_id'
This may happen when a larva morphed to an egg, siege tank sieged, a zerg unit burrowed, a hatchery morphed to lair,
@@ -1245,7 +1282,7 @@ async def on_unit_type_changed(self, unit: Unit, previous_type: UnitTypeId):
:param previous_type:
"""
- async def on_building_construction_started(self, unit: Unit):
+ async def on_building_construction_started(self, unit: Unit) -> None:
"""
Override this in your bot class.
This function is called when a building construction has started.
@@ -1253,7 +1290,7 @@ async def on_building_construction_started(self, unit: Unit):
:param unit:
"""
- async def on_building_construction_complete(self, unit: Unit):
+ async def on_building_construction_complete(self, unit: Unit) -> None:
"""
Override this in your bot class. This function is called when a building
construction is completed.
@@ -1261,14 +1298,14 @@ async def on_building_construction_complete(self, unit: Unit):
:param unit:
"""
- async def on_upgrade_complete(self, upgrade: UpgradeId):
+ async def on_upgrade_complete(self, upgrade: UpgradeId) -> None:
"""
Override this in your bot class. This function is called with the upgrade id of an upgrade that was not finished last step and is now.
:param upgrade:
"""
- async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float):
+ async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float) -> None:
"""
Override this in your bot class. This function is called when your own unit (unit or structure) took damage.
It will not be called if the unit died this frame.
@@ -1282,16 +1319,17 @@ async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float):
print(f"My unit took damage: {unit} took {amount_damage_taken} damage")
:param unit:
+ :param amount_damage_taken:
"""
- async def on_enemy_unit_entered_vision(self, unit: Unit):
+ async def on_enemy_unit_entered_vision(self, unit: Unit) -> None:
"""
Override this in your bot class. This function is called when an enemy unit (unit or structure) entered vision (which was not visible last frame).
:param unit:
"""
- async def on_enemy_unit_left_vision(self, unit_tag: int):
+ async def on_enemy_unit_left_vision(self, unit_tag: int) -> None:
"""
Override this in your bot class. This function is called when an enemy unit (unit or structure) left vision (which was visible last frame).
Same as the self.on_unit_destroyed event, this function is called with the unit's tag because the unit is no longer visible anymore.
@@ -1305,7 +1343,7 @@ async def on_enemy_unit_left_vision(self, unit_tag: int):
:param unit_tag:
"""
- async def on_before_start(self):
+ async def on_before_start(self) -> None:
"""
Override this in your bot class. This function is called before "on_start"
and before "prepare_first_step" that calculates expansion locations.
@@ -1313,7 +1351,7 @@ async def on_before_start(self):
This function is useful in realtime=True mode to split your workers or start producing the first worker.
"""
- async def on_start(self):
+ async def on_start(self) -> None:
"""
Override this in your bot class.
At this point, game_data, game_info and the first iteration of game_state (self.state) are available.
@@ -1329,7 +1367,8 @@ async def on_step(self, iteration: int):
"""
raise NotImplementedError
- async def on_end(self, game_result: Result):
+ # pyre-ignore[11]
+ async def on_end(self, game_result: Result) -> None:
"""Override this in your bot class. This function is called at the end of a game.
Unsure if this function will be called on the laddermanager client as the bot process may forcefully be terminated.
diff --git a/sc2/bot_ai_internal.py b/sc2/bot_ai_internal.py
index 9cd78986..f8950fe0 100644
--- a/sc2/bot_ai_internal.py
+++ b/sc2/bot_ai_internal.py
@@ -1,4 +1,4 @@
-# pylint: disable=W0201,W0212,R0912
+# pyre-ignore-all-errors[6, 16, 29]
from __future__ import annotations
import itertools
@@ -7,16 +7,20 @@
import warnings
from abc import ABC
from collections import Counter
+from collections.abc import Generator, Iterable
from contextlib import suppress
-from typing import TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Set, Tuple, Union, final
+from typing import TYPE_CHECKING, Any, final
import numpy as np
from loguru import logger
+
+# pyre-ignore[21]
from s2clientprotocol import sc2api_pb2 as sc_pb
from sc2.cache import property_cache_once_per_frame
from sc2.constants import (
ALL_GAS,
+ CREATION_ABILITY_FIX,
IS_PLACEHOLDER,
TERRAN_STRUCTURES_REQUIRE_SCV,
FakeEffectID,
@@ -25,8 +29,9 @@
mineral_ids,
)
from sc2.data import ActionResult, Race, race_townhalls
-from sc2.game_data import AbilityData, Cost, GameData
+from sc2.game_data import Cost, GameData
from sc2.game_state import Blip, EffectData, GameState
+from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
from sc2.pixel_map import PixelMap
@@ -37,6 +42,7 @@
with warnings.catch_warnings():
warnings.simplefilter("ignore")
+ # pyre-ignore[21]
from scipy.spatial.distance import cdist, pdist
if TYPE_CHECKING:
@@ -47,20 +53,23 @@
class BotAIInternal(ABC):
"""Base class for bots."""
+ def __init__(self) -> None:
+ self._initialize_variables()
+
@final
- def _initialize_variables(self):
- """ Called from main.py internally """
- self.cache: Dict[str, Any] = {}
+ def _initialize_variables(self) -> None:
+ """Called from main.py internally"""
+ self.cache: dict[str, Any] = {}
# Specific opponent bot ID used in sc2ai ladder games http://sc2ai.net/ and on ai arena https://aiarena.net
# The bot ID will stay the same each game so your bot can "adapt" to the opponent
if not hasattr(self, "opponent_id"):
# Prevent overwriting the opponent_id which is set here https://github.com/Hannessa/python-sc2-ladderbot/blob/master/__init__.py#L40
# otherwise set it to None
- self.opponent_id: str = None
+ self.opponent_id: str | None = None
# Select distance calculation method, see _distances_override_functions function
if not hasattr(self, "distance_calculation_method"):
self.distance_calculation_method: int = 2
- # Select if the Unit.command should return UnitCommand objects. Set this to True if your bot uses 'self.do(unit(ability, target))'
+ # Select if the Unit.command should return UnitCommand objects. Set this to True if your bot uses 'unit(ability, target)'
if not hasattr(self, "unit_command_uses_self_do"):
self.unit_command_uses_self_do: bool = False
# This value will be set to True by main.py in self._prepare_start if game is played in realtime (if true, the bot will have limited time per step)
@@ -83,8 +92,8 @@ def _initialize_variables(self):
self.mineral_field: Units = Units([], self)
self.vespene_geyser: Units = Units([], self)
self.placeholders: Units = Units([], self)
- self.techlab_tags: Set[int] = set()
- self.reactor_tags: Set[int] = set()
+ self.techlab_tags: set[int] = set()
+ self.reactor_tags: set[int] = set()
self.minerals: int = 50
self.vespene: int = 0
self.supply_army: float = 0
@@ -95,35 +104,36 @@ def _initialize_variables(self):
self.idle_worker_count: int = 0
self.army_count: int = 0
self.warp_gate_count: int = 0
- self.actions: List[UnitCommand] = []
- self.blips: Set[Blip] = set()
- self.race: Race = None
- self.enemy_race: Race = None
+ self.actions: list[UnitCommand] = []
+ self.blips: set[Blip] = set()
+ # pyre-ignore[11]
+ self.race: Race | None = None
+ self.enemy_race: Race | None = None
self._generated_frame = -100
self._units_created: Counter = Counter()
- self._unit_tags_seen_this_game: Set[int] = set()
- self._units_previous_map: Dict[int, Unit] = {}
- self._structures_previous_map: Dict[int, Unit] = {}
- self._enemy_units_previous_map: Dict[int, Unit] = {}
- self._enemy_structures_previous_map: Dict[int, Unit] = {}
- self._all_units_previous_map: Dict[int, Unit] = {}
- self._previous_upgrades: Set[UpgradeId] = set()
- self._expansion_positions_list: List[Point2] = []
- self._resource_location_to_expansion_position_dict: Dict[Point2, Point2] = {}
- self._time_before_step: float = None
- self._time_after_step: float = None
+ self._unit_tags_seen_this_game: set[int] = set()
+ self._units_previous_map: dict[int, Unit] = {}
+ self._structures_previous_map: dict[int, Unit] = {}
+ self._enemy_units_previous_map: dict[int, Unit] = {}
+ self._enemy_structures_previous_map: dict[int, Unit] = {}
+ self._all_units_previous_map: dict[int, Unit] = {}
+ self._previous_upgrades: set[UpgradeId] = set()
+ self._expansion_positions_list: list[Point2] = []
+ self._resource_location_to_expansion_position_dict: dict[Point2, Point2] = {}
+ self._time_before_step: float = 0
+ self._time_after_step: float = 0
self._min_step_time: float = math.inf
self._max_step_time: float = 0
self._last_step_step_time: float = 0
self._total_time_in_on_step: float = 0
self._total_steps_iterations: int = 0
# Internally used to keep track which units received an action in this frame, so that self.train() function does not give the same larva two orders - cleared every frame
- self.unit_tags_received_action: Set[int] = set()
+ self.unit_tags_received_action: set[int] = set()
@final
@property
def _game_info(self) -> GameInfo:
- """ See game_info.py """
+ """See game_info.py"""
warnings.warn(
"Using self._game_info is deprecated and may be removed soon. Please use self.game_info directly.",
DeprecationWarning,
@@ -134,7 +144,7 @@ def _game_info(self) -> GameInfo:
@final
@property
def _game_data(self) -> GameData:
- """ See game_data.py """
+ """See game_data.py"""
warnings.warn(
"Using self._game_data is deprecated and may be removed soon. Please use self.game_data directly.",
DeprecationWarning,
@@ -145,7 +155,7 @@ def _game_data(self) -> GameData:
@final
@property
def _client(self) -> Client:
- """ See client.py """
+ """See client.py"""
warnings.warn(
"Using self._client is deprecated and may be removed soon. Please use self.client directly.",
DeprecationWarning,
@@ -155,11 +165,11 @@ def _client(self) -> Client:
@final
@property_cache_once_per_frame
- def expansion_locations(self) -> Dict[Point2, Units]:
- """ Same as the function above. """
- assert (
- self._expansion_positions_list
- ), "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless."
+ def expansion_locations(self) -> dict[Point2, Units]:
+ """Same as the function above."""
+ assert self._expansion_positions_list, (
+ "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless."
+ )
warnings.warn(
"You are using 'self.expansion_locations', please use 'self.expansion_locations_list' (fast) or 'self.expansion_locations_dict' (slow) instead.",
DeprecationWarning,
@@ -168,27 +178,34 @@ def expansion_locations(self) -> Dict[Point2, Units]:
return self.expansion_locations_dict
@final
- def _find_expansion_locations(self):
- """ Ran once at the start of the game to calculate expansion locations. """
+ def _find_expansion_locations(self) -> None:
+ """Ran once at the start of the game to calculate expansion locations."""
# Idea: create a group for every resource, then merge these groups if
# any resource in a group is closer than a threshold to any resource of another group
# Distance we group resources by
resource_spread_threshold: float = 8.5
# Create a group for every resource
- resource_groups: List[List[Unit]] = [
- [resource] for resource in self.resources
+ resource_groups: list[list[Unit]] = [
+ [resource]
+ for resource in self.resources
if resource.name != "MineralField450" # dont use low mineral count patches
]
# Loop the merging process as long as we change something
merged_group = True
+ height_grid: PixelMap = self.game_info.terrain_height
while merged_group:
merged_group = False
# Check every combination of two groups
for group_a, group_b in itertools.combinations(resource_groups, 2):
# Check if any pair of resource of these groups is closer than threshold together
+ # And that they are on the same terrain level
if any(
resource_a.distance_to(resource_b) <= resource_spread_threshold
+ # check if terrain height measurement at resources is within 10 units
+ # this is since some older maps have inconsistent terrain height
+ # tiles at certain expansion locations
+ and abs(height_grid[resource_a.position.rounded] - height_grid[resource_b.position.rounded]) <= 10
for resource_a, resource_b in itertools.product(group_a, group_b)
):
# Remove the single groups and add the merged group
@@ -200,7 +217,8 @@ def _find_expansion_locations(self):
# Distance offsets we apply to center of each resource group to find expansion position
offset_range = 7
offsets = [
- (x, y) for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2)
+ (x, y)
+ for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2)
if 4 < math.hypot(x, y) <= 8
]
# Dict we want to return
@@ -216,7 +234,8 @@ def _find_expansion_locations(self):
possible_points = (Point2((offset[0] + center_x, offset[1] + center_y)) for offset in offsets)
# Filter out points that are too near
possible_points = (
- point for point in possible_points
+ point
+ for point in possible_points
# Check if point can be built on
if self.game_info.placement_grid[point.rounded] == 1
# Check if all resources have enough space to point
@@ -237,7 +256,7 @@ def _find_expansion_locations(self):
self._resource_location_to_expansion_position_dict[resource.position] = result
@final
- def _correct_zerg_supply(self):
+ def _correct_zerg_supply(self) -> None:
"""The client incorrectly rounds zerg supply down instead of up (see
https://github.com/Blizzard/s2client-proto/issues/123), so self.supply_used
and friends return the wrong value when there are an odd number of zerglings
@@ -257,33 +276,42 @@ def _correct_zerg_supply(self):
@final
@property_cache_once_per_frame
- def _abilities_all_units(self) -> Tuple[Counter, Dict[AbilityData, float]]:
+ def _abilities_count_and_build_progress(self) -> tuple[Counter[AbilityId], dict[AbilityId, float]]:
"""Cache for the already_pending function, includes protoss units warping in,
all units in production and all structures, and all morphs"""
- abilities_amount = Counter()
- max_build_progress: Dict[AbilityData, float] = {}
+ abilities_amount: Counter[AbilityId] = Counter()
+ max_build_progress: dict[AbilityId, float] = {}
unit: Unit
for unit in self.units + self.structures:
for order in unit.orders:
- abilities_amount[order.ability] += 1
- if not unit.is_ready:
- if self.race != Race.Terran or not unit.is_structure:
- # If an SCV is constructing a building, already_pending would count this structure twice
- # (once from the SCV order, and once from "not structure.is_ready")
- creation_ability: AbilityData = self.game_data.units[unit.type_id.value].creation_ability
+ abilities_amount[order.ability.exact_id] += 1
+ if not unit.is_ready and (self.race != Race.Terran or not unit.is_structure):
+ # If an SCV is constructing a building, already_pending would count this structure twice
+ # (once from the SCV order, and once from "not structure.is_ready")
+ if unit.type_id in CREATION_ABILITY_FIX:
+ if unit.type_id == UnitTypeId.ARCHON:
+ # Hotfix for archons in morph state
+ creation_ability = AbilityId.ARCHON_WARP_TARGET
+ abilities_amount[creation_ability] += 2
+ else:
+ # Hotfix for rich geysirs
+ creation_ability = CREATION_ABILITY_FIX[unit.type_id]
+ abilities_amount[creation_ability] += 1
+ else:
+ creation_ability: AbilityId = self.game_data.units[unit.type_id.value].creation_ability.exact_id
abilities_amount[creation_ability] += 1
- max_build_progress[creation_ability] = max(
- max_build_progress.get(creation_ability, 0), unit.build_progress
- )
+ max_build_progress[creation_ability] = max(
+ max_build_progress.get(creation_ability, 0), unit.build_progress
+ )
return abilities_amount, max_build_progress
@final
@property_cache_once_per_frame
- def _worker_orders(self) -> Counter:
- """ This function is used internally, do not use! It is to store all worker abilities. """
- abilities_amount = Counter()
- structures_in_production: Set[Union[Point2, int]] = set()
+ def _worker_orders(self) -> Counter[AbilityId]:
+ """This function is used internally, do not use! It is to store all worker abilities."""
+ abilities_amount: Counter[AbilityId] = Counter()
+ structures_in_production: set[Point2 | int] = set()
for structure in self.structures:
if structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV:
structures_in_production.add(structure.position)
@@ -292,13 +320,9 @@ def _worker_orders(self) -> Counter:
for order in worker.orders:
# Skip if the SCV is constructing (not isinstance(order.target, int))
# or resuming construction (isinstance(order.target, int))
- is_int = isinstance(order.target, int)
- if (
- is_int and order.target in structures_in_production
- or not is_int and Point2.from_proto(order.target) in structures_in_production
- ):
+ if order.target in structures_in_production:
continue
- abilities_amount[order.ability] += 1
+ abilities_amount[order.ability.exact_id] += 1
return abilities_amount
@final
@@ -350,9 +374,9 @@ def do(
)
return action
- assert isinstance(
- action, UnitCommand
- ), f"Given unit command is not a command, but instead of type {type(action)}"
+ assert isinstance(action, UnitCommand), (
+ f"Given unit command is not a command, but instead of type {type(action)}"
+ )
if subtract_cost:
cost: Cost = self.game_data.calculate_ability_cost(action.ability)
if can_afford_check and not (self.minerals >= cost.minerals and self.vespene >= cost.vespene):
@@ -378,9 +402,9 @@ async def synchronous_do(self, action: UnitCommand):
This function is only useful for realtime=True in the first frame of the game to instantly produce a worker
and split workers on the mineral patches.
"""
- assert isinstance(
- action, UnitCommand
- ), f"Given unit command is not a command, but instead of type {type(action)}"
+ assert isinstance(action, UnitCommand), (
+ f"Given unit command is not a command, but instead of type {type(action)}"
+ )
if not self.can_afford(action.ability):
logger.warning(f"Cannot afford action {action}")
return ActionResult.Error
@@ -395,8 +419,8 @@ async def synchronous_do(self, action: UnitCommand):
return r
@final
- async def _do_actions(self, actions: List[UnitCommand], prevent_double: bool = True):
- """Used internally by main.py automatically, use self.do() instead!
+ async def _do_actions(self, actions: list[UnitCommand], prevent_double: bool = True):
+ """Used internally by main.py after each step
:param actions:
:param prevent_double:"""
@@ -435,7 +459,9 @@ def prevent_double_actions(action) -> bool:
return True
@final
- def _prepare_start(self, client, player_id, game_info, game_data, realtime: bool = False, base_build: int = -1):
+ def _prepare_start(
+ self, client, player_id: int, game_info, game_data, realtime: bool = False, base_build: int = -1
+ ) -> None:
"""
Ran until game start to set game and player data.
@@ -452,15 +478,16 @@ def _prepare_start(self, client, player_id, game_info, game_data, realtime: bool
self.realtime: bool = realtime
self.base_build: int = base_build
- self.race: Race = Race(self.game_info.player_races[self.player_id])
-
- if len(self.game_info.player_races) == 2:
+ # Get the player's race. As observer, get Race.NoRace=0
+ self.race: Race = Race(self.game_info.player_races.get(self.player_id, 0))
+ # Get the enemy's race only if we are not observer (replay) and the game has 2 players
+ if self.player_id > 0 and len(self.game_info.player_races) == 2:
self.enemy_race: Race = Race(self.game_info.player_races[3 - self.player_id])
self._distances_override_functions(self.distance_calculation_method)
@final
- def _prepare_first_step(self):
+ def _prepare_first_step(self) -> None:
"""First step extra preparations. Must not be called before _prepare_step."""
if self.townhalls:
self.game_info.player_start_location = self.townhalls.first.position
@@ -470,7 +497,7 @@ def _prepare_first_step(self):
self._time_before_step: float = time.perf_counter()
@final
- def _prepare_step(self, state, proto_game_info):
+ def _prepare_step(self, state, proto_game_info) -> None:
"""
:param state:
:param proto_game_info:
@@ -478,16 +505,15 @@ def _prepare_step(self, state, proto_game_info):
# Set attributes from new state before on_step."""
self.state: GameState = state # See game_state.py
# update pathing grid, which unfortunately is in GameInfo instead of GameState
- self.game_info.pathing_grid: PixelMap = PixelMap(proto_game_info.game_info.start_raw.pathing_grid, in_bits=True)
+ self.game_info.pathing_grid = PixelMap(proto_game_info.game_info.start_raw.pathing_grid, in_bits=True)
# Required for events, needs to be before self.units are initialized so the old units are stored
- self._units_previous_map: Dict[int, Unit] = {unit.tag: unit for unit in self.units}
- self._structures_previous_map: Dict[int, Unit] = {structure.tag: structure for structure in self.structures}
- self._enemy_units_previous_map: Dict[int, Unit] = {unit.tag: unit for unit in self.enemy_units}
- self._enemy_structures_previous_map: Dict[int, Unit] = {
- structure.tag: structure
- for structure in self.enemy_structures
+ self._units_previous_map: dict[int, Unit] = {unit.tag: unit for unit in self.units}
+ self._structures_previous_map: dict[int, Unit] = {structure.tag: structure for structure in self.structures}
+ self._enemy_units_previous_map: dict[int, Unit] = {unit.tag: unit for unit in self.enemy_units}
+ self._enemy_structures_previous_map: dict[int, Unit] = {
+ structure.tag: structure for structure in self.enemy_structures
}
- self._all_units_previous_map: Dict[int, Unit] = {unit.tag: unit for unit in self.all_units}
+ self._all_units_previous_map: dict[int, Unit] = {unit.tag: unit for unit in self.all_units}
self._prepare_units()
self.minerals: int = state.common.minerals
@@ -512,9 +538,9 @@ def _prepare_step(self, state, proto_game_info):
self.enemy_race = Race(self.all_enemy_units.first.race)
@final
- def _prepare_units(self):
+ def _prepare_units(self) -> None:
# Set of enemy units detected by own sensor tower, as blips have less unit information than normal visible units
- self.blips: Set[Blip] = set()
+ self.blips: set[Blip] = set()
self.all_units: Units = Units([], self)
self.units: Units = Units([], self)
self.workers: Units = Units([], self)
@@ -532,10 +558,10 @@ def _prepare_units(self):
self.mineral_field: Units = Units([], self)
self.vespene_geyser: Units = Units([], self)
self.placeholders: Units = Units([], self)
- self.techlab_tags: Set[int] = set()
- self.reactor_tags: Set[int] = set()
+ self.techlab_tags: set[int] = set()
+ self.reactor_tags: set[int] = set()
- worker_types: Set[UnitTypeId] = {UnitTypeId.DRONE, UnitTypeId.DRONEBURROWED, UnitTypeId.SCV, UnitTypeId.PROBE}
+ worker_types: set[UnitTypeId] = {UnitTypeId.DRONE, UnitTypeId.DRONEBURROWED, UnitTypeId.SCV, UnitTypeId.PROBE}
index: int = 0
for unit in self.state.observation_raw.units:
@@ -617,7 +643,7 @@ def _prepare_units(self):
@final
async def _after_step(self) -> int:
- """ Executed by main.py after each on_step function. """
+ """Executed by main.py after each on_step function."""
# Keep track of the bot on_step duration
self._time_after_step: float = time.perf_counter()
step_duration = self._time_after_step - self._time_before_step
@@ -630,7 +656,7 @@ async def _after_step(self) -> int:
if self.actions:
await self._do_actions(self.actions)
self.actions.clear()
- # Clear set of unit tags that were given an order this frame by self.do()
+ # Clear set of unit tags that were given an order this frame
self.unit_tags_received_action.clear()
# Commit debug queries
await self.client._send_debug()
@@ -638,7 +664,7 @@ async def _after_step(self) -> int:
return self.state.game_loop
@final
- async def _advance_steps(self, steps: int):
+ async def _advance_steps(self, steps: int) -> None:
"""Advances the game loop by amount of 'steps'. This function is meant to be used as a debugging and testing tool only.
If you are using this, please be aware of the consequences, e.g. 'self.units' will be filled with completely new data."""
await self._after_step()
@@ -649,10 +675,9 @@ async def _advance_steps(self, steps: int):
proto_game_info = await self.client._execute(game_info=sc_pb.RequestGameInfo())
self._prepare_step(gs, proto_game_info)
await self.issue_events()
- # await self.on_step(-1)
@final
- async def issue_events(self):
+ async def issue_events(self) -> None:
"""This function will be automatically run from main.py and triggers the following functions:
- on_unit_created
- on_unit_destroyed
@@ -667,7 +692,7 @@ async def issue_events(self):
await self._issue_vision_events()
@final
- async def _issue_unit_added_events(self):
+ async def _issue_unit_added_events(self) -> None:
for unit in self.units:
if unit.tag not in self._units_previous_map and unit.tag not in self._unit_tags_seen_this_game:
self._unit_tags_seen_this_game.add(unit.tag)
@@ -684,14 +709,14 @@ async def _issue_unit_added_events(self):
await self.on_unit_type_changed(unit, previous_frame_unit.type_id)
@final
- async def _issue_upgrade_events(self):
+ async def _issue_upgrade_events(self) -> None:
difference = self.state.upgrades - self._previous_upgrades
for upgrade_completed in difference:
await self.on_upgrade_complete(upgrade_completed)
self._previous_upgrades = self.state.upgrades
@final
- async def _issue_building_events(self):
+ async def _issue_building_events(self) -> None:
for structure in self.structures:
if structure.tag not in self._structures_previous_map:
if structure.build_progress < 1:
@@ -708,8 +733,10 @@ async def _issue_building_events(self):
or structure.shield < previous_frame_structure.shield
):
damage_amount = (
- previous_frame_structure.health - structure.health + previous_frame_structure.shield -
- structure.shield
+ previous_frame_structure.health
+ - structure.health
+ + previous_frame_structure.shield
+ - structure.shield
)
await self.on_unit_took_damage(structure, damage_amount)
# Check if a structure changed its type
@@ -721,7 +748,7 @@ async def _issue_building_events(self):
await self.on_building_construction_complete(structure)
@final
- async def _issue_vision_events(self):
+ async def _issue_vision_events(self) -> None:
# Call events for enemy unit entered vision
for enemy_unit in self.enemy_units:
if enemy_unit.tag not in self._enemy_units_previous_map:
@@ -731,18 +758,16 @@ async def _issue_vision_events(self):
await self.on_enemy_unit_entered_vision(enemy_structure)
# Call events for enemy unit left vision
- enemy_units_left_vision: Set[int] = set(self._enemy_units_previous_map.keys()) - self.enemy_units.tags
+ enemy_units_left_vision: set[int] = set(self._enemy_units_previous_map) - self.enemy_units.tags
for enemy_unit_tag in enemy_units_left_vision:
await self.on_enemy_unit_left_vision(enemy_unit_tag)
- enemy_structures_left_vision: Set[int] = (
- set(self._enemy_structures_previous_map.keys()) - self.enemy_structures.tags
- )
+ enemy_structures_left_vision: set[int] = set(self._enemy_structures_previous_map) - self.enemy_structures.tags
for enemy_structure_tag in enemy_structures_left_vision:
await self.on_enemy_unit_left_vision(enemy_structure_tag)
@final
- async def _issue_unit_dead_events(self):
- for unit_tag in self.state.dead_units & set(self._all_units_previous_map.keys()):
+ async def _issue_unit_dead_events(self) -> None:
+ for unit_tag in self.state.dead_units & set(self._all_units_previous_map):
await self.on_unit_destroyed(unit_tag)
# DISTANCE CALCULATION
@@ -755,7 +780,7 @@ def _units_count(self) -> int:
@final
@property
def _pdist(self) -> np.ndarray:
- """ As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop. """
+ """As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop."""
if self._generated_frame != self.state.game_loop:
return self.calculate_distances()
return self._cached_pdist
@@ -763,7 +788,7 @@ def _pdist(self) -> np.ndarray:
@final
@property
def _cdist(self) -> np.ndarray:
- """ As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop. """
+ """As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop."""
if self._generated_frame != self.state.game_loop:
return self.calculate_distances()
return self._cached_cdist
@@ -804,7 +829,7 @@ def _calculate_distances_method2(self) -> np.ndarray:
@final
def _calculate_distances_method3(self) -> np.ndarray:
- """ Nearly same as above, but without asserts"""
+ """Nearly same as above, but without asserts"""
self._generated_frame = self.state.game_loop
flat_positions = (coord for unit in self.all_units for coord in unit.position_tuple)
positions_array: np.ndarray = np.fromiter(
@@ -830,8 +855,8 @@ def square_to_condensed(self, i, j) -> int:
@final
@staticmethod
- def convert_tuple_to_numpy_array(pos: Tuple[float, float]) -> np.ndarray:
- """ Converts a single position to a 2d numpy array with 1 row and 2 columns. """
+ def convert_tuple_to_numpy_array(pos: tuple[float, float]) -> np.ndarray:
+ """Converts a single position to a 2d numpy array with 1 row and 2 columns."""
return np.fromiter(pos, dtype=float, count=2).reshape((1, 2))
# Fast and simple calculation functions
@@ -839,16 +864,16 @@ def convert_tuple_to_numpy_array(pos: Tuple[float, float]) -> np.ndarray:
@final
@staticmethod
def distance_math_hypot(
- p1: Union[Tuple[float, float], Point2],
- p2: Union[Tuple[float, float], Point2],
+ p1: tuple[float, float] | Point2,
+ p2: tuple[float, float] | Point2,
) -> float:
return math.hypot(p1[0] - p2[0], p1[1] - p2[1])
@final
@staticmethod
def distance_math_hypot_squared(
- p1: Union[Tuple[float, float], Point2],
- p2: Union[Tuple[float, float], Point2],
+ p1: tuple[float, float] | Point2,
+ p2: tuple[float, float] | Point2,
) -> float:
return pow(p1[0] - p2[0], 2) + pow(p1[1] - p2[1], 2)
@@ -865,9 +890,9 @@ def _distance_squared_unit_to_unit_method1(self, unit1: Unit, unit2: Unit) -> fl
return 0
# Calculate index, needs to be after pdist has been calculated and cached
condensed_index = self.square_to_condensed(unit1.distance_calculation_index, unit2.distance_calculation_index)
- assert condensed_index < len(
- self._cached_pdist
- ), f"Condensed index is larger than amount of calculated distances: {condensed_index} < {len(self._cached_pdist)}, units that caused the assert error: {unit1} and {unit2}"
+ assert condensed_index < len(self._cached_pdist), (
+ f"Condensed index is larger than amount of calculated distances: {condensed_index} < {len(self._cached_pdist)}, units that caused the assert error: {unit1} and {unit2}"
+ )
distance = self._pdist[condensed_index]
return distance
@@ -881,8 +906,8 @@ def _distance_squared_unit_to_unit_method2(self, unit1: Unit, unit2: Unit) -> fl
@final
def _distance_pos_to_pos(
self,
- pos1: Union[Tuple[float, float], Point2],
- pos2: Union[Tuple[float, float], Point2],
+ pos1: tuple[float, float] | Point2,
+ pos2: tuple[float, float] | Point2,
) -> float:
return self.distance_math_hypot(pos1, pos2)
@@ -890,23 +915,23 @@ def _distance_pos_to_pos(
def _distance_units_to_pos(
self,
units: Units,
- pos: Union[Tuple[float, float], Point2],
+ pos: tuple[float, float] | Point2,
) -> Generator[float, None, None]:
- """ This function does not scale well, if len(units) > 100 it gets fairly slow """
+ """This function does not scale well, if len(units) > 100 it gets fairly slow"""
return (self.distance_math_hypot(u.position_tuple, pos) for u in units)
@final
def _distance_unit_to_points(
self,
unit: Unit,
- points: Iterable[Tuple[float, float]],
+ points: Iterable[tuple[float, float]],
) -> Generator[float, None, None]:
- """ This function does not scale well, if len(points) > 100 it gets fairly slow """
+ """This function does not scale well, if len(points) > 100 it gets fairly slow"""
pos = unit.position_tuple
return (self.distance_math_hypot(p, pos) for p in points)
@final
- def _distances_override_functions(self, method: int = 0):
+ def _distances_override_functions(self, method: int = 0) -> None:
"""Overrides the internal distance calculation functions at game start in bot_ai.py self._prepare_start() function
method 0: Use python's math.hypot
The following methods calculate the distances between all units once:
diff --git a/sc2/cache.py b/sc2/cache.py
index 3377223d..d3e9090d 100644
--- a/sc2/cache.py
+++ b/sc2/cache.py
@@ -1,6 +1,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Callable, TypeVar
+from collections.abc import Callable, Hashable
+from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from sc2.bot_ai import BotAI
@@ -8,7 +9,16 @@
T = TypeVar("T")
-class property_cache_once_per_frame(property):
+class CacheDict(dict):
+ def retrieve_and_set(self, key: Hashable, func: Callable[[], T]) -> T:
+ """Either return the value at a certain key,
+ or set the return value of a function to that key, then return that value."""
+ if key not in self:
+ self[key] = func()
+ return self[key]
+
+
+class property_cache_once_per_frame(property): # noqa: N801
"""This decorator caches the return value for one game loop,
then clears it if it is accessed in a different game loop.
Only works on properties of the bot object, because it requires
@@ -17,20 +27,22 @@ class property_cache_once_per_frame(property):
This decorator compared to the above runs a little faster, however you should only use this decorator if you are sure that you do not modify the mutable once it is calculated and cached.
Copied and modified from https://tedboy.github.io/flask/_modules/werkzeug/utils.html#cached_property
- # """
+ #"""
- def __init__(self, func: Callable[[BotAI], T], name=None):
- # pylint: disable=W0231
+ def __init__(self, func: Callable[[BotAI], T], name=None) -> None:
self.__name__ = name or func.__name__
self.__frame__ = f"__frame__{self.__name__}"
self.func = func
- def __set__(self, obj: BotAI, value: T):
+ def __set__(self, obj: BotAI, value: T) -> None:
obj.cache[self.__name__] = value
+ # pyre-ignore[16]
obj.cache[self.__frame__] = obj.state.game_loop
+ # pyre-fixme[34]
def __get__(self, obj: BotAI, _type=None) -> T:
value = obj.cache.get(self.__name__, None)
+ # pyre-ignore[16]
bot_frame = obj.state.game_loop
if value is None or obj.cache[self.__frame__] < bot_frame:
value = self.func(obj)
diff --git a/sc2/client.py b/sc2/client.py
index e2dff958..5888b190 100644
--- a/sc2/client.py
+++ b/sc2/client.py
@@ -1,8 +1,12 @@
+# pyre-ignore-all-errors[6, 9, 16, 29, 58]
from __future__ import annotations
-from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
+from collections.abc import Iterable
+from pathlib import Path
from loguru import logger
+
+# pyre-ignore[21]
from s2clientprotocol import debug_pb2 as debug_pb
from s2clientprotocol import query_pb2 as query_pb
from s2clientprotocol import raw_pb2 as raw_pb
@@ -16,27 +20,25 @@
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.position import Point2, Point3
-from sc2.protocol import ConnectionAlreadyClosed, Protocol, ProtocolError
+from sc2.protocol import ConnectionAlreadyClosedError, Protocol, ProtocolError
from sc2.renderer import Renderer
from sc2.unit import Unit
from sc2.units import Units
-# pylint: disable=R0904
class Client(Protocol):
-
- def __init__(self, ws, save_replay_path: str = None):
+ def __init__(self, ws, save_replay_path: str = None) -> None:
"""
:param ws:
"""
super().__init__(ws)
# How many frames will be waited between iterations before the next one is called
self.game_step: int = 4
- self.save_replay_path: Optional[str] = save_replay_path
+ self.save_replay_path: str | None = save_replay_path
self._player_id = None
self._game_result = None
# Store a hash value of all the debug requests to prevent sending the same ones again if they haven't changed last frame
- self._debug_hash_tuple_last_iteration: Tuple[int, int, int, int] = (0, 0, 0, 0)
+ self._debug_hash_tuple_last_iteration: tuple[int, int, int, int] = (0, 0, 0, 0)
self._debug_draw_last_frame = False
self._debug_texts = []
self._debug_lines = []
@@ -101,8 +103,8 @@ async def join_game(self, name=None, race=None, observed_player_id=None, portcon
self._player_id = result.join_game.player_id
return result.join_game.player_id
- async def leave(self):
- """ You can use 'await self.client.leave()' to surrender midst game. """
+ async def leave(self) -> None:
+ """You can use 'await self.client.leave()' to surrender midst game."""
is_resign = self._game_result is None
if is_resign:
@@ -115,14 +117,14 @@ async def leave(self):
await self.save_replay(self.save_replay_path)
self.save_replay_path = None
await self._execute(leave_game=sc_pb.RequestLeaveGame())
- except (ProtocolError, ConnectionAlreadyClosed):
+ except (ProtocolError, ConnectionAlreadyClosedError):
if is_resign:
raise
- async def save_replay(self, path):
+ async def save_replay(self, path) -> None:
logger.debug("Requesting replay from server")
result = await self._execute(save_replay=sc_pb.RequestSaveReplay())
- with open(path, "wb") as f:
+ with Path(path).open("wb") as f:
f.write(result.save_replay.data)
logger.info(f"Saved replay to {path}")
@@ -151,7 +153,7 @@ async def observation(self, game_loop: int = None):
return result
async def step(self, step_size: int = None):
- """ EXPERIMENTAL: Change self._client.game_step during the step function to increase or decrease steps per second """
+ """EXPERIMENTAL: Change self._client.game_step during the step function to increase or decrease steps per second"""
step_size = step_size or self.game_step
return await self._execute(step=sc_pb.RequestStep(count=step_size))
@@ -161,7 +163,14 @@ async def get_game_data(self) -> GameData:
)
return GameData(result.data)
- async def dump_data(self, ability_id=True, unit_type_id=True, upgrade_id=True, buff_id=True, effect_id=True):
+ async def dump_data(
+ self,
+ ability_id: bool = True,
+ unit_type_id: bool = True,
+ upgrade_id: bool = True,
+ buff_id: bool = True,
+ effect_id: bool = True,
+ ) -> None:
"""
Dump the game data files
choose what data to dump in the keywords
@@ -178,14 +187,14 @@ async def dump_data(self, ability_id=True, unit_type_id=True, upgrade_id=True, b
effect_id=effect_id,
)
)
- with open("data_dump.txt", "a") as file:
+ with Path("data_dump.txt").open("a") as file:
file.write(str(result.data))
async def get_game_info(self) -> GameInfo:
result = await self._execute(game_info=sc_pb.RequestGameInfo())
return GameInfo(result.game_info)
- async def actions(self, actions, return_successes=False):
+ async def actions(self, actions, return_successes: bool = False):
if not actions:
return None
if not isinstance(actions, list):
@@ -202,8 +211,7 @@ async def actions(self, actions, return_successes=False):
return [ActionResult(r) for r in res.action.result]
return [ActionResult(r) for r in res.action.result if ActionResult(r) != ActionResult.Success]
- async def query_pathing(self, start: Union[Unit, Point2, Point3],
- end: Union[Point2, Point3]) -> Optional[Union[int, float]]:
+ async def query_pathing(self, start: Unit | Point2 | Point3, end: Point2 | Point3) -> int | float | None:
"""Caution: returns "None" when path not found
Try to combine queries with the function below because the pathing query is generally slow.
@@ -221,7 +229,7 @@ async def query_pathing(self, start: Union[Unit, Point2, Point3],
return None
return distance
- async def query_pathings(self, zipped_list: List[List[Union[Unit, Point2, Point3]]]) -> List[float]:
+ async def query_pathings(self, zipped_list: list[list[Unit | Point2 | Point3]]) -> list[float]:
"""Usage: await self.query_pathings([[unit1, target2], [unit2, target2]])
-> returns [distance1, distance2]
Caution: returns 0 when path not found
@@ -244,8 +252,8 @@ async def query_pathings(self, zipped_list: List[List[Union[Unit, Point2, Point3
return [float(d.distance) for d in results.query.pathing]
async def _query_building_placement_fast(
- self, ability: AbilityId, positions: List[Union[Point2, Point3]], ignore_resources: bool = True
- ) -> List[bool]:
+ self, ability: AbilityId, positions: list[Point2 | Point3], ignore_resources: bool = True
+ ) -> list[bool]:
"""
Returns a list of booleans. Return True for positions that are valid, False otherwise.
@@ -268,9 +276,10 @@ async def _query_building_placement_fast(
async def query_building_placement(
self,
ability: AbilityData,
- positions: List[Union[Point2, Point3]],
- ignore_resources: bool = True
- ) -> List[ActionResult]:
+ positions: list[Point2 | Point3],
+ ignore_resources: bool = True,
+ # pyre-fixme[11]
+ ) -> list[ActionResult]:
"""This function might be deleted in favor of the function above (_query_building_placement_fast).
:param ability:
@@ -290,9 +299,9 @@ async def query_building_placement(
return [ActionResult(p.result) for p in result.query.placements]
async def query_available_abilities(
- self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False
- ) -> List[List[AbilityId]]:
- """ Query abilities of multiple units """
+ self, units: list[Unit] | Units, ignore_resource_requirements: bool = False
+ ) -> list[list[AbilityId]]:
+ """Query abilities of multiple units"""
input_was_a_list = True
if not isinstance(units, list):
""" Deprecated, accepting a single unit may be removed in the future, query a list of units instead """
@@ -308,13 +317,14 @@ async def query_available_abilities(
)
""" Fix for bots that only query a single unit, may be removed soon """
if not input_was_a_list:
+ # pyre-fixme[7]
return [[AbilityId(a.ability_id) for a in b.abilities] for b in result.query.abilities][0]
return [[AbilityId(a.ability_id) for a in b.abilities] for b in result.query.abilities]
async def query_available_abilities_with_tag(
- self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False
- ) -> Dict[int, Set[AbilityId]]:
- """ Query abilities of multiple units """
+ self, units: list[Unit] | Units, ignore_resource_requirements: bool = False
+ ) -> dict[int, set[AbilityId]]:
+ """Query abilities of multiple units"""
result = await self._execute(
query=query_pb.RequestQuery(
@@ -324,8 +334,8 @@ async def query_available_abilities_with_tag(
)
return {b.unit_tag: {AbilityId(a.ability_id) for a in b.abilities} for b in result.query.abilities}
- async def chat_send(self, message: str, team_only: bool):
- """ Writes a message to the chat """
+ async def chat_send(self, message: str, team_only: bool) -> None:
+ """Writes a message to the chat"""
ch = ChatChannel.Team if team_only else ChatChannel.Broadcast
await self._execute(
action=sc_pb.RequestAction(
@@ -333,7 +343,7 @@ async def chat_send(self, message: str, team_only: bool):
)
)
- async def toggle_autocast(self, units: Union[List[Unit], Units], ability: AbilityId):
+ async def toggle_autocast(self, units: list[Unit] | Units, ability: AbilityId) -> None:
"""Toggle autocast of all specified units
:param units:
@@ -348,15 +358,16 @@ async def toggle_autocast(self, units: Union[List[Unit], Units], ability: Abilit
actions=[
sc_pb.Action(
action_raw=raw_pb.ActionRaw(
- toggle_autocast=raw_pb.
- ActionRawToggleAutocast(ability_id=ability.value, unit_tags=(u.tag for u in units))
+ toggle_autocast=raw_pb.ActionRawToggleAutocast(
+ ability_id=ability.value, unit_tags=(u.tag for u in units)
+ )
)
)
]
)
)
- async def debug_create_unit(self, unit_spawn_commands: List[List[Union[UnitTypeId, int, Point2, Point3]]]):
+ async def debug_create_unit(self, unit_spawn_commands: list[list[UnitTypeId | int | Point2 | Point3]]) -> None:
"""Usage example (will spawn 5 marines in the center of the map for player ID 1):
await self._client.debug_create_unit([[UnitTypeId.MARINE, 5, self._game_info.map_center, 1]])
@@ -380,12 +391,13 @@ async def debug_create_unit(self, unit_spawn_commands: List[List[Union[UnitTypeI
pos=position.as_Point2D,
quantity=amount_of_units,
)
- ) for unit_type, amount_of_units, position, owner_id in unit_spawn_commands
+ )
+ for unit_type, amount_of_units, position, owner_id in unit_spawn_commands
)
)
)
- async def debug_kill_unit(self, unit_tags: Union[Unit, Units, List[int], Set[int]]):
+ async def debug_kill_unit(self, unit_tags: Unit | Units | list[int] | set[int]) -> None:
"""
:param unit_tags:
"""
@@ -399,7 +411,7 @@ async def debug_kill_unit(self, unit_tags: Union[Unit, Units, List[int], Set[int
debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(kill_unit=debug_pb.DebugKillUnit(tag=unit_tags))])
)
- async def move_camera(self, position: Union[Unit, Units, Point2, Point3]):
+ async def move_camera(self, position: Unit | Units | Point2 | Point3) -> None:
"""Moves camera to the target position
:param position:"""
@@ -420,7 +432,7 @@ async def move_camera(self, position: Union[Unit, Units, Point2, Point3]):
)
)
- async def obs_move_camera(self, position: Union[Unit, Units, Point2, Point3]):
+ async def obs_move_camera(self, position: Unit | Units | Point2 | Point3, distance: float = 0) -> None:
"""Moves observer camera to the target position. Only works when observing (e.g. watching the replay).
:param position:"""
@@ -432,12 +444,14 @@ async def obs_move_camera(self, position: Union[Unit, Units, Point2, Point3]):
await self._execute(
obs_action=sc_pb.RequestObserverAction(
actions=[
- sc_pb.ObserverAction(camera_move=sc_pb.ActionObserverCameraMove(world_pos=position.as_Point2D))
+ sc_pb.ObserverAction(
+ camera_move=sc_pb.ActionObserverCameraMove(world_pos=position.as_Point2D, distance=distance)
+ )
]
)
)
- async def move_camera_spatial(self, position: Union[Point2, Point3]):
+ async def move_camera_spatial(self, position: Point2 | Point3) -> None:
"""Moves camera to the target position using the spatial aciton interface
:param position:"""
@@ -449,17 +463,17 @@ async def move_camera_spatial(self, position: Union[Point2, Point3]):
)
await self._execute(action=sc_pb.RequestAction(actions=[action]))
- def debug_text_simple(self, text: str):
- """ Draws a text in the top left corner of the screen (up to a max of 6 messages fit there). """
+ def debug_text_simple(self, text: str) -> None:
+ """Draws a text in the top left corner of the screen (up to a max of 6 messages fit there)."""
self._debug_texts.append(DrawItemScreenText(text=text, color=None, start_point=Point2((0, 0)), font_size=8))
def debug_text_screen(
self,
text: str,
- pos: Union[Point2, Point3, tuple, list],
- color: Union[tuple, list, Point3] = None,
+ pos: Point2 | Point3 | tuple | list,
+ color: tuple | list | Point3 = None,
size: int = 8,
- ):
+ ) -> None:
"""
Draws a text on the screen (monitor / game window) with coordinates 0 <= x, y <= 1.
@@ -477,15 +491,15 @@ def debug_text_screen(
def debug_text_2d(
self,
text: str,
- pos: Union[Point2, Point3, tuple, list],
- color: Union[tuple, list, Point3] = None,
+ pos: Point2 | Point3 | tuple | list,
+ color: tuple | list | Point3 = None,
size: int = 8,
):
return self.debug_text_screen(text, pos, color, size)
def debug_text_world(
- self, text: str, pos: Union[Unit, Point3], color: Union[tuple, list, Point3] = None, size: int = 8
- ):
+ self, text: str, pos: Unit | Point3, color: tuple | list | Point3 = None, size: int = 8
+ ) -> None:
"""
Draws a text at Point3 position in the game world.
To grab a unit's 3d position, use unit.position3d
@@ -500,14 +514,10 @@ def debug_text_world(
assert isinstance(pos, Point3)
self._debug_texts.append(DrawItemWorldText(text=text, color=color, start_point=pos, font_size=size))
- def debug_text_3d(
- self, text: str, pos: Union[Unit, Point3], color: Union[tuple, list, Point3] = None, size: int = 8
- ):
+ def debug_text_3d(self, text: str, pos: Unit | Point3, color: tuple | list | Point3 = None, size: int = 8):
return self.debug_text_world(text, pos, color, size)
- def debug_line_out(
- self, p0: Union[Unit, Point3], p1: Union[Unit, Point3], color: Union[tuple, list, Point3] = None
- ):
+ def debug_line_out(self, p0: Unit | Point3, p1: Unit | Point3, color: tuple | list | Point3 = None) -> None:
"""
Draws a line from p0 to p1.
@@ -525,10 +535,10 @@ def debug_line_out(
def debug_box_out(
self,
- p_min: Union[Unit, Point3],
- p_max: Union[Unit, Point3],
- color: Union[tuple, list, Point3] = None,
- ):
+ p_min: Unit | Point3,
+ p_max: Unit | Point3,
+ color: tuple | list | Point3 = None,
+ ) -> None:
"""
Draws a box with p_min and p_max as corners of the box.
@@ -546,10 +556,10 @@ def debug_box_out(
def debug_box2_out(
self,
- pos: Union[Unit, Point3],
+ pos: Unit | Point3,
half_vertex_length: float = 0.25,
- color: Union[tuple, list, Point3] = None,
- ):
+ color: tuple | list | Point3 = None,
+ ) -> None:
"""
Draws a box center at a position 'pos', with box side lengths (vertices) of two times 'half_vertex_length'.
@@ -564,7 +574,7 @@ def debug_box2_out(
p1 = pos + Point3((half_vertex_length, half_vertex_length, half_vertex_length))
self._debug_boxes.append(DrawItemBox(start_point=p0, end_point=p1, color=color))
- def debug_sphere_out(self, p: Union[Unit, Point3], r: float, color: Union[tuple, list, Point3] = None):
+ def debug_sphere_out(self, p: Unit | Point3, r: float, color: tuple | list | Point3 = None) -> None:
"""
Draws a sphere at point p with radius r.
@@ -577,7 +587,7 @@ def debug_sphere_out(self, p: Union[Unit, Point3], r: float, color: Union[tuple,
assert isinstance(p, Point3)
self._debug_spheres.append(DrawItemSphere(start_point=p, radius=r, color=color))
- async def _send_debug(self):
+ async def _send_debug(self) -> None:
"""Sends the debug draw execution. This is run by main.py now automatically, if there is any items in the list. You do not need to run this manually any longer.
Check examples/terran/ramp_wall.py for example drawing. Each draw request needs to be sent again in every single on_step iteration.
"""
@@ -597,14 +607,18 @@ async def _send_debug(self):
debug=[
debug_pb.DebugCommand(
draw=debug_pb.DebugDraw(
- text=[text.to_proto()
- for text in self._debug_texts] if self._debug_texts else None,
- lines=[line.to_proto()
- for line in self._debug_lines] if self._debug_lines else None,
- boxes=[box.to_proto()
- for box in self._debug_boxes] if self._debug_boxes else None,
- spheres=[sphere.to_proto()
- for sphere in self._debug_spheres] if self._debug_spheres else None,
+ text=[text.to_proto() for text in self._debug_texts]
+ if self._debug_texts
+ else None,
+ lines=[line.to_proto() for line in self._debug_lines]
+ if self._debug_lines
+ else None,
+ boxes=[box.to_proto() for box in self._debug_boxes]
+ if self._debug_boxes
+ else None,
+ spheres=[sphere.to_proto() for sphere in self._debug_spheres]
+ if self._debug_spheres
+ else None,
)
)
]
@@ -629,10 +643,12 @@ async def _send_debug(self):
)
self._debug_draw_last_frame = False
- async def debug_leave(self):
+ async def debug_leave(self) -> None:
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(end_game=debug_pb.DebugEndGame())]))
- async def debug_set_unit_value(self, unit_tags: Union[Iterable[int], Units, Unit], unit_value: int, value: float):
+ async def debug_set_unit_value(
+ self, unit_tags: Iterable[int] | Units | Unit, unit_value: int, value: float
+ ) -> None:
"""Sets a "unit value" (Energy, Life or Shields) of the given units to the given value.
Can't set the life of a unit to 0, use "debug_kill_unit" for that. Also can't set the life above the unit's maximum.
The following example sets the health of all your workers to 1:
@@ -641,12 +657,12 @@ async def debug_set_unit_value(self, unit_tags: Union[Iterable[int], Units, Unit
unit_tags = unit_tags.tags
if isinstance(unit_tags, Unit):
unit_tags = [unit_tags.tag]
- assert hasattr(
- unit_tags, "__iter__"
- ), f"unit_tags argument needs to be an iterable (list, dict, set, Units), given argument is {type(unit_tags).__name__}"
- assert (
- 1 <= unit_value <= 3
- ), f"unit_value needs to be between 1 and 3 (1 for energy, 2 for life, 3 for shields), given argument is {unit_value}"
+ assert hasattr(unit_tags, "__iter__"), (
+ f"unit_tags argument needs to be an iterable (list, dict, set, Units), given argument is {type(unit_tags).__name__}"
+ )
+ assert 1 <= unit_value <= 3, (
+ f"unit_value needs to be between 1 and 3 (1 for energy, 2 for life, 3 for shields), given argument is {unit_value}"
+ )
assert all(tag > 0 for tag in unit_tags), f"Unit tags have invalid value: {unit_tags}"
assert isinstance(value, (int, float)), "Value needs to be of type int or float"
assert value >= 0, "Value can't be negative"
@@ -654,15 +670,17 @@ async def debug_set_unit_value(self, unit_tags: Union[Iterable[int], Units, Unit
debug=sc_pb.RequestDebug(
debug=(
debug_pb.DebugCommand(
- unit_value=debug_pb.
- DebugSetUnitValue(unit_value=unit_value, value=float(value), unit_tag=unit_tag)
- ) for unit_tag in unit_tags
+ unit_value=debug_pb.DebugSetUnitValue(
+ unit_value=unit_value, value=float(value), unit_tag=unit_tag
+ )
+ )
+ for unit_tag in unit_tags
)
)
)
- async def debug_hang(self, delay_in_seconds: float):
- """ Freezes the SC2 client. Not recommended to be used. """
+ async def debug_hang(self, delay_in_seconds: float) -> None:
+ """Freezes the SC2 client. Not recommended to be used."""
delay_in_ms = int(round(delay_in_seconds * 1000))
await self._execute(
debug=sc_pb.RequestDebug(
@@ -670,60 +688,60 @@ async def debug_hang(self, delay_in_seconds: float):
)
)
- async def debug_show_map(self):
- """ Reveals the whole map for the bot. Using it a second time disables it again. """
+ async def debug_show_map(self) -> None:
+ """Reveals the whole map for the bot. Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=1)]))
- async def debug_control_enemy(self):
- """ Allows control over enemy units and structures similar to team games control - does not allow the bot to spend the opponent's ressources. Using it a second time disables it again. """
+ async def debug_control_enemy(self) -> None:
+ """Allows control over enemy units and structures similar to team games control - does not allow the bot to spend the opponent's ressources. Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=2)]))
- async def debug_food(self):
- """ Should disable food usage (does not seem to work?). Using it a second time disables it again. """
+ async def debug_food(self) -> None:
+ """Should disable food usage (does not seem to work?). Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=3)]))
- async def debug_free(self):
- """ Units, structures and upgrades are free of mineral and gas cost. Using it a second time disables it again. """
+ async def debug_free(self) -> None:
+ """Units, structures and upgrades are free of mineral and gas cost. Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=4)]))
- async def debug_all_resources(self):
- """ Gives 5000 minerals and 5000 vespene to the bot. """
+ async def debug_all_resources(self) -> None:
+ """Gives 5000 minerals and 5000 vespene to the bot."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=5)]))
- async def debug_god(self):
- """ Your units and structures no longer take any damage. Using it a second time disables it again. """
+ async def debug_god(self) -> None:
+ """Your units and structures no longer take any damage. Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=6)]))
- async def debug_minerals(self):
- """ Gives 5000 minerals to the bot. """
+ async def debug_minerals(self) -> None:
+ """Gives 5000 minerals to the bot."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=7)]))
- async def debug_gas(self):
- """ Gives 5000 vespene to the bot. This does not seem to be working. """
+ async def debug_gas(self) -> None:
+ """Gives 5000 vespene to the bot. This does not seem to be working."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=8)]))
- async def debug_cooldown(self):
- """ Disables cooldowns of unit abilities for the bot. Using it a second time disables it again. """
+ async def debug_cooldown(self) -> None:
+ """Disables cooldowns of unit abilities for the bot. Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=9)]))
- async def debug_tech_tree(self):
- """ Removes all tech requirements (e.g. can build a factory without having a barracks). Using it a second time disables it again. """
+ async def debug_tech_tree(self) -> None:
+ """Removes all tech requirements (e.g. can build a factory without having a barracks). Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=10)]))
- async def debug_upgrade(self):
- """ Researches all currently available upgrades. E.g. using it once unlocks combat shield, stimpack and 1-1. Using it a second time unlocks 2-2 and all other upgrades stay researched. """
+ async def debug_upgrade(self) -> None:
+ """Researches all currently available upgrades. E.g. using it once unlocks combat shield, stimpack and 1-1. Using it a second time unlocks 2-2 and all other upgrades stay researched."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=11)]))
- async def debug_fast_build(self):
- """ Sets the build time of units and structures and upgrades to zero. Using it a second time disables it again. """
+ async def debug_fast_build(self) -> None:
+ """Sets the build time of units and structures and upgrades to zero. Using it a second time disables it again."""
await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=12)]))
- async def quick_save(self):
+ async def quick_save(self) -> None:
"""Saves the current game state to an in-memory bookmark.
See: https://github.com/Blizzard/s2client-proto/blob/eeaf5efaea2259d7b70247211dff98da0a2685a2/s2clientprotocol/sc2api.proto#L93"""
await self._execute(quick_save=sc_pb.RequestQuickSave())
- async def quick_load(self):
+ async def quick_load(self) -> None:
"""Loads the game state from the previously stored in-memory bookmark.
Caution:
- The SC2 Client will crash if the game wasn't quicksaved
@@ -733,10 +751,9 @@ async def quick_load(self):
class DrawItem:
-
@staticmethod
- def to_debug_color(color: Union[tuple, Point3]):
- """ Helper function for color conversion """
+ def to_debug_color(color: tuple | Point3):
+ """Helper function for color conversion"""
if color is None:
return debug_pb.Color(r=255, g=255, b=255)
# Need to check if not of type Point3 because Point3 inherits from tuple
@@ -746,6 +763,7 @@ def to_debug_color(color: Union[tuple, Point3]):
r = getattr(color, "r", getattr(color, "x", 255))
g = getattr(color, "g", getattr(color, "y", 255))
b = getattr(color, "b", getattr(color, "z", 255))
+ # pyre-ignore[20]
if max(r, g, b) <= 1:
r *= 255
g *= 255
@@ -755,8 +773,7 @@ def to_debug_color(color: Union[tuple, Point3]):
class DrawItemScreenText(DrawItem):
-
- def __init__(self, start_point: Point2 = None, color: Point3 = None, text: str = "", font_size: int = 8):
+ def __init__(self, start_point: Point2 = None, color: Point3 = None, text: str = "", font_size: int = 8) -> None:
self._start_point: Point2 = start_point
self._color: Point3 = color
self._text: str = text
@@ -771,13 +788,12 @@ def to_proto(self):
size=self._font_size,
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self._start_point, self._color, self._text, self._font_size))
class DrawItemWorldText(DrawItem):
-
- def __init__(self, start_point: Point3 = None, color: Point3 = None, text: str = "", font_size: int = 8):
+ def __init__(self, start_point: Point3 = None, color: Point3 = None, text: str = "", font_size: int = 8) -> None:
self._start_point: Point3 = start_point
self._color: Point3 = color
self._text: str = text
@@ -792,13 +808,12 @@ def to_proto(self):
size=self._font_size,
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self._start_point, self._text, self._font_size, self._color))
class DrawItemLine(DrawItem):
-
- def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None):
+ def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None) -> None:
self._start_point: Point3 = start_point
self._end_point: Point3 = end_point
self._color: Point3 = color
@@ -809,13 +824,12 @@ def to_proto(self):
color=self.to_debug_color(self._color),
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self._start_point, self._end_point, self._color))
class DrawItemBox(DrawItem):
-
- def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None):
+ def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None) -> None:
self._start_point: Point3 = start_point
self._end_point: Point3 = end_point
self._color: Point3 = color
@@ -827,13 +841,12 @@ def to_proto(self):
color=self.to_debug_color(self._color),
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self._start_point, self._end_point, self._color))
class DrawItemSphere(DrawItem):
-
- def __init__(self, start_point: Point3 = None, radius: float = None, color: Point3 = None):
+ def __init__(self, start_point: Point3 = None, radius: float = None, color: Point3 = None) -> None:
self._start_point: Point3 = start_point
self._radius: float = radius
self._color: Point3 = color
@@ -843,5 +856,5 @@ def to_proto(self):
p=self._start_point.as_Point, r=self._radius, color=self.to_debug_color(self._color)
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self._start_point, self._radius, self._color))
diff --git a/sc2/constants.py b/sc2/constants.py
index e3d8bb9e..cc89d36c 100644
--- a/sc2/constants.py
+++ b/sc2/constants.py
@@ -1,5 +1,8 @@
+# pyre-ignore-all-errors[16]
+from __future__ import annotations
+
from collections import defaultdict
-from typing import Any, Dict, Set
+from typing import Any
from sc2.data import Alliance, Attribute, CloakState, DisplayType, TargetType
from sc2.ids.ability_id import AbilityId
@@ -7,7 +10,7 @@
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
-mineral_ids: Set[int] = {
+mineral_ids: set[int] = {
UnitTypeId.RICHMINERALFIELD.value,
UnitTypeId.RICHMINERALFIELD750.value,
UnitTypeId.MINERALFIELD.value,
@@ -24,7 +27,7 @@
UnitTypeId.MINERALFIELDOPAQUE.value,
UnitTypeId.MINERALFIELDOPAQUE900.value,
}
-geyser_ids: Set[int] = {
+geyser_ids: set[int] = {
UnitTypeId.VESPENEGEYSER.value,
UnitTypeId.SPACEPLATFORMGEYSER.value,
UnitTypeId.RICHVESPENEGEYSER.value,
@@ -32,7 +35,7 @@
UnitTypeId.PURIFIERVESPENEGEYSER.value,
UnitTypeId.SHAKURASVESPENEGEYSER.value,
}
-transforming: Dict[UnitTypeId, AbilityId] = {
+transforming: dict[UnitTypeId, AbilityId] = {
# Terran structures
UnitTypeId.BARRACKS: AbilityId.LAND_BARRACKS,
UnitTypeId.BARRACKSFLYING: AbilityId.LAND_BARRACKS,
@@ -98,8 +101,8 @@
UnitTypeId.ZERGLING: AbilityId.BURROWUP_ZERGLING,
UnitTypeId.ZERGLINGBURROWED: AbilityId.BURROWDOWN_ZERGLING,
}
-# For now only contains units that cost supply, used in bot_ai.do()
-abilityid_to_unittypeid: Dict[AbilityId, UnitTypeId] = {
+# For now only contains units that cost supply
+abilityid_to_unittypeid: dict[AbilityId, UnitTypeId] = {
# Protoss
AbilityId.NEXUSTRAIN_PROBE: UnitTypeId.PROBE,
AbilityId.GATEWAYTRAIN_ZEALOT: UnitTypeId.ZEALOT,
@@ -166,25 +169,25 @@
IS_PSIONIC: int = Attribute.Psionic.value
UNIT_BATTLECRUISER: UnitTypeId = UnitTypeId.BATTLECRUISER
UNIT_ORACLE: UnitTypeId = UnitTypeId.ORACLE
-TARGET_GROUND: Set[int] = {TargetType.Ground.value, TargetType.Any.value}
-TARGET_AIR: Set[int] = {TargetType.Air.value, TargetType.Any.value}
-TARGET_BOTH = TARGET_GROUND | TARGET_AIR
+TARGET_GROUND: set[int] = {TargetType.Ground.value, TargetType.Any.value}
+TARGET_AIR: set[int] = {TargetType.Air.value, TargetType.Any.value}
+TARGET_BOTH: set[int] = TARGET_GROUND | TARGET_AIR
IS_SNAPSHOT = DisplayType.Snapshot.value
IS_VISIBLE = DisplayType.Visible.value
IS_PLACEHOLDER = DisplayType.Placeholder.value
IS_MINE = Alliance.Self.value
IS_ENEMY = Alliance.Enemy.value
-IS_CLOAKED: Set[int] = {CloakState.Cloaked.value, CloakState.CloakedDetected.value, CloakState.CloakedAllied.value}
+IS_CLOAKED: set[int] = {CloakState.Cloaked.value, CloakState.CloakedDetected.value, CloakState.CloakedAllied.value}
IS_REVEALED: int = CloakState.CloakedDetected.value
-CAN_BE_ATTACKED: Set[int] = {CloakState.NotCloaked.value, CloakState.CloakedDetected.value}
-IS_CARRYING_MINERALS: Set[BuffId] = {BuffId.CARRYMINERALFIELDMINERALS, BuffId.CARRYHIGHYIELDMINERALFIELDMINERALS}
-IS_CARRYING_VESPENE: Set[BuffId] = {
+CAN_BE_ATTACKED: set[int] = {CloakState.NotCloaked.value, CloakState.CloakedDetected.value}
+IS_CARRYING_MINERALS: set[BuffId] = {BuffId.CARRYMINERALFIELDMINERALS, BuffId.CARRYHIGHYIELDMINERALFIELDMINERALS}
+IS_CARRYING_VESPENE: set[BuffId] = {
BuffId.CARRYHARVESTABLEVESPENEGEYSERGAS,
BuffId.CARRYHARVESTABLEVESPENEGEYSERGASPROTOSS,
BuffId.CARRYHARVESTABLEVESPENEGEYSERGASZERG,
}
-IS_CARRYING_RESOURCES: Set[BuffId] = IS_CARRYING_MINERALS | IS_CARRYING_VESPENE
-IS_ATTACKING: Set[AbilityId] = {
+IS_CARRYING_RESOURCES: set[BuffId] = IS_CARRYING_MINERALS | IS_CARRYING_VESPENE
+IS_ATTACKING: set[AbilityId] = {
AbilityId.ATTACK,
AbilityId.ATTACK_ATTACK,
AbilityId.ATTACK_ATTACKTOWARDS,
@@ -194,8 +197,8 @@
IS_PATROLLING: AbilityId = AbilityId.PATROL_PATROL
IS_GATHERING: AbilityId = AbilityId.HARVEST_GATHER
IS_RETURNING: AbilityId = AbilityId.HARVEST_RETURN
-IS_COLLECTING: Set[AbilityId] = {IS_GATHERING, IS_RETURNING}
-IS_CONSTRUCTING_SCV: Set[AbilityId] = {
+IS_COLLECTING: set[AbilityId] = {IS_GATHERING, IS_RETURNING}
+IS_CONSTRUCTING_SCV: set[AbilityId] = {
AbilityId.TERRANBUILD_ARMORY,
AbilityId.TERRANBUILD_BARRACKS,
AbilityId.TERRANBUILD_BUNKER,
@@ -210,8 +213,8 @@
AbilityId.TERRANBUILD_STARPORT,
AbilityId.TERRANBUILD_SUPPLYDEPOT,
}
-IS_REPAIRING: Set[AbilityId] = {AbilityId.EFFECT_REPAIR, AbilityId.EFFECT_REPAIR_MULE, AbilityId.EFFECT_REPAIR_SCV}
-IS_DETECTOR: Set[UnitTypeId] = {
+IS_REPAIRING: set[AbilityId] = {AbilityId.EFFECT_REPAIR, AbilityId.EFFECT_REPAIR_MULE, AbilityId.EFFECT_REPAIR_SCV}
+IS_DETECTOR: set[UnitTypeId] = {
UnitTypeId.OBSERVER,
UnitTypeId.OBSERVERSIEGEMODE,
UnitTypeId.RAVEN,
@@ -220,7 +223,7 @@
UnitTypeId.OVERSEERSIEGEMODE,
UnitTypeId.SPORECRAWLER,
}
-SPEED_UPGRADE_DICT: Dict[UnitTypeId, UpgradeId] = {
+SPEED_UPGRADE_DICT: dict[UnitTypeId, UpgradeId] = {
# Terran
UnitTypeId.MEDIVAC: UpgradeId.MEDIVACRAPIDDEPLOYMENT,
UnitTypeId.BANSHEE: UpgradeId.BANSHEESPEED,
@@ -237,7 +240,7 @@
UnitTypeId.ROACH: UpgradeId.GLIALRECONSTITUTION,
UnitTypeId.LURKERMP: UpgradeId.DIGGINGCLAWS,
}
-SPEED_INCREASE_DICT: Dict[UnitTypeId, float] = {
+SPEED_INCREASE_DICT: dict[UnitTypeId, float] = {
# Terran
UnitTypeId.MEDIVAC: 1.18,
UnitTypeId.BANSHEE: 1.3636,
@@ -254,12 +257,12 @@
UnitTypeId.ROACH: 1.3333333333,
UnitTypeId.LURKERMP: 1.1,
}
-temp1 = set(SPEED_UPGRADE_DICT.keys())
-temp2 = set(SPEED_INCREASE_DICT.keys())
+temp1 = set(SPEED_UPGRADE_DICT)
+temp2 = set(SPEED_INCREASE_DICT)
assert temp1 == temp2, f"{temp1.symmetric_difference(temp2)}"
del temp1
del temp2
-SPEED_INCREASE_ON_CREEP_DICT: Dict[UnitTypeId, float] = {
+SPEED_INCREASE_ON_CREEP_DICT: dict[UnitTypeId, float] = {
UnitTypeId.QUEEN: 2.67,
UnitTypeId.ZERGLING: 1.3,
UnitTypeId.BANELING: 1.3,
@@ -275,21 +278,21 @@
UnitTypeId.SPINECRAWLER: 2.5,
UnitTypeId.SPORECRAWLER: 2.5,
}
-OFF_CREEP_SPEED_UPGRADE_DICT: Dict[UnitTypeId, UpgradeId] = {
+OFF_CREEP_SPEED_UPGRADE_DICT: dict[UnitTypeId, UpgradeId] = {
UnitTypeId.HYDRALISK: UpgradeId.EVOLVEMUSCULARAUGMENTS,
UnitTypeId.ULTRALISK: UpgradeId.ANABOLICSYNTHESIS,
}
-OFF_CREEP_SPEED_INCREASE_DICT: Dict[UnitTypeId, float] = {
+OFF_CREEP_SPEED_INCREASE_DICT: dict[UnitTypeId, float] = {
UnitTypeId.HYDRALISK: 1.25,
UnitTypeId.ULTRALISK: 1.2,
}
-temp1 = set(OFF_CREEP_SPEED_UPGRADE_DICT.keys())
-temp2 = set(OFF_CREEP_SPEED_INCREASE_DICT.keys())
+temp1 = set(OFF_CREEP_SPEED_UPGRADE_DICT)
+temp2 = set(OFF_CREEP_SPEED_INCREASE_DICT)
assert temp1 == temp2, f"{temp1.symmetric_difference(temp2)}"
del temp1
del temp2
# Movement speed gets altered by this factor if it is affected by this buff
-SPEED_ALTERING_BUFFS: Dict[BuffId, float] = {
+SPEED_ALTERING_BUFFS: dict[BuffId, float] = {
# Stimpack increases speed by 1.5
BuffId.STIMPACK: 1.5,
BuffId.STIMPACKMARAUDER: 1.5,
@@ -307,11 +310,10 @@
UNIT_PHOTONCANNON: UnitTypeId = UnitTypeId.PHOTONCANNON
UNIT_COLOSSUS: UnitTypeId = UnitTypeId.COLOSSUS
# Used in unit_command.py and action.py to combine only certain abilities
-COMBINEABLE_ABILITIES: Set[AbilityId] = {
+COMBINEABLE_ABILITIES: set[AbilityId] = {
AbilityId.MOVE,
AbilityId.ATTACK,
AbilityId.SCAN_MOVE,
- AbilityId.SMART,
AbilityId.STOP,
AbilityId.HOLDPOSITION,
AbilityId.PATROL,
@@ -329,18 +331,18 @@
AbilityId.EFFECT_BLINK,
AbilityId.MORPH_ARCHON,
}
-FakeEffectRadii: Dict[int, float] = {
+FakeEffectRadii: dict[int, float] = {
UnitTypeId.KD8CHARGE.value: 2,
UnitTypeId.PARASITICBOMBDUMMY.value: 3,
UnitTypeId.FORCEFIELD.value: 1.5,
}
-FakeEffectID: Dict[int, str] = {
+FakeEffectID: dict[int, str] = {
UnitTypeId.KD8CHARGE.value: "KD8CHARGE",
UnitTypeId.PARASITICBOMBDUMMY.value: "PARASITICBOMB",
UnitTypeId.FORCEFIELD.value: "FORCEFIELD",
}
-TERRAN_STRUCTURES_REQUIRE_SCV: Set[UnitTypeId] = {
+TERRAN_STRUCTURES_REQUIRE_SCV: set[UnitTypeId] = {
UnitTypeId.ARMORY,
UnitTypeId.BARRACKS,
UnitTypeId.BUNKER,
@@ -364,7 +366,7 @@ def return_NOTAUNIT() -> UnitTypeId:
# Hotfix for structures and units as the API does not seem to return the correct values, e.g. ghost and thor have None in the requirements
-TERRAN_TECH_REQUIREMENT: Dict[UnitTypeId, UnitTypeId] = defaultdict(
+TERRAN_TECH_REQUIREMENT: dict[UnitTypeId, UnitTypeId] = defaultdict(
return_NOTAUNIT,
{
UnitTypeId.MISSILETURRET: UnitTypeId.ENGINEERINGBAY,
@@ -384,7 +386,7 @@ def return_NOTAUNIT() -> UnitTypeId:
UnitTypeId.BATTLECRUISER: UnitTypeId.FUSIONCORE,
},
)
-PROTOSS_TECH_REQUIREMENT: Dict[UnitTypeId, UnitTypeId] = defaultdict(
+PROTOSS_TECH_REQUIREMENT: dict[UnitTypeId, UnitTypeId] = defaultdict(
return_NOTAUNIT,
{
UnitTypeId.PHOTONCANNON: UnitTypeId.FORGE,
@@ -408,7 +410,7 @@ def return_NOTAUNIT() -> UnitTypeId:
UnitTypeId.DISRUPTOR: UnitTypeId.ROBOTICSBAY,
},
)
-ZERG_TECH_REQUIREMENT: Dict[UnitTypeId, UnitTypeId] = defaultdict(
+ZERG_TECH_REQUIREMENT: dict[UnitTypeId, UnitTypeId] = defaultdict(
return_NOTAUNIT,
{
UnitTypeId.ZERGLING: UnitTypeId.SPAWNINGPOOL,
@@ -441,7 +443,7 @@ def return_NOTAUNIT() -> UnitTypeId:
},
)
# Required in 'tech_requirement_progress' bot_ai.py function
-EQUIVALENTS_FOR_TECH_PROGRESS: Dict[UnitTypeId, Set[UnitTypeId]] = {
+EQUIVALENTS_FOR_TECH_PROGRESS: dict[UnitTypeId, set[UnitTypeId]] = {
# Protoss
UnitTypeId.GATEWAY: {UnitTypeId.WARPGATE},
UnitTypeId.WARPPRISM: {UnitTypeId.WARPPRISMPHASING},
@@ -483,7 +485,7 @@ def return_NOTAUNIT() -> UnitTypeId:
UnitTypeId.ULTRALISK: {UnitTypeId.ULTRALISKBURROWED},
# TODO What about morphing untis? E.g. roach to ravager, overlord to drop-overlord or overseer
}
-ALL_GAS: Set[UnitTypeId] = {
+ALL_GAS: set[UnitTypeId] = {
UnitTypeId.ASSIMILATOR,
UnitTypeId.ASSIMILATORRICH,
UnitTypeId.REFINERY,
@@ -491,189 +493,55 @@ def return_NOTAUNIT() -> UnitTypeId:
UnitTypeId.EXTRACTOR,
UnitTypeId.EXTRACTORRICH,
}
-DAMAGE_BONUS_PER_UPGRADE: Dict[UnitTypeId, Dict[TargetType, Any]] = {
+# pyre-ignore[11]
+DAMAGE_BONUS_PER_UPGRADE: dict[UnitTypeId, dict[TargetType, Any]] = {
#
# Protoss
#
- UnitTypeId.PROBE: {
- TargetType.Ground.value: {
- None: 0
- }
- },
+ UnitTypeId.PROBE: {TargetType.Ground.value: {None: 0}},
# Gateway Units
- UnitTypeId.ADEPT: {
- TargetType.Ground.value: {
- IS_LIGHT: 1
- }
- },
- UnitTypeId.STALKER: {
- TargetType.Any.value: {
- IS_ARMORED: 1
- }
- },
- UnitTypeId.DARKTEMPLAR: {
- TargetType.Ground.value: {
- None: 5
- }
- },
- UnitTypeId.ARCHON: {
- TargetType.Any.value: {
- None: 3,
- IS_BIOLOGICAL: 1
- }
- },
+ UnitTypeId.ADEPT: {TargetType.Ground.value: {IS_LIGHT: 1}},
+ UnitTypeId.STALKER: {TargetType.Any.value: {IS_ARMORED: 1}},
+ UnitTypeId.DARKTEMPLAR: {TargetType.Ground.value: {None: 5}},
+ UnitTypeId.ARCHON: {TargetType.Any.value: {None: 3, IS_BIOLOGICAL: 1}},
# Robo Units
- UnitTypeId.IMMORTAL: {
- TargetType.Ground.value: {
- None: 2,
- IS_ARMORED: 3
- }
- },
- UnitTypeId.COLOSSUS: {
- TargetType.Ground.value: {
- IS_LIGHT: 1
- }
- },
+ UnitTypeId.IMMORTAL: {TargetType.Ground.value: {None: 2, IS_ARMORED: 3}},
+ UnitTypeId.COLOSSUS: {TargetType.Ground.value: {IS_LIGHT: 1}},
# Stargate Units
- UnitTypeId.ORACLE: {
- TargetType.Ground.value: {
- None: 0
- }
- },
- UnitTypeId.TEMPEST: {
- TargetType.Ground.value: {
- None: 4
- },
- TargetType.Air.value: {
- None: 3,
- IS_MASSIVE: 2
- }
- },
+ UnitTypeId.ORACLE: {TargetType.Ground.value: {None: 0}},
+ UnitTypeId.TEMPEST: {TargetType.Ground.value: {None: 4}, TargetType.Air.value: {None: 3, IS_MASSIVE: 2}},
#
# Terran
#
- UnitTypeId.SCV: {
- TargetType.Ground.value: {
- None: 0
- }
- },
+ UnitTypeId.SCV: {TargetType.Ground.value: {None: 0}},
# Barracks Units
- UnitTypeId.MARAUDER: {
- TargetType.Ground.value: {
- IS_ARMORED: 1
- }
- },
- UnitTypeId.GHOST: {
- TargetType.Any.value: {
- IS_LIGHT: 1
- }
- },
+ UnitTypeId.MARAUDER: {TargetType.Ground.value: {IS_ARMORED: 1}},
+ UnitTypeId.GHOST: {TargetType.Any.value: {IS_LIGHT: 1}},
# Factory Units
- UnitTypeId.HELLION: {
- TargetType.Ground.value: {
- IS_LIGHT: 1
- }
- },
- UnitTypeId.HELLIONTANK: {
- TargetType.Ground.value: {
- None: 2,
- IS_LIGHT: 1
- }
- },
- UnitTypeId.CYCLONE: {
- TargetType.Any.value: {
- None: 2
- }
- },
- UnitTypeId.SIEGETANK: {
- TargetType.Ground.value: {
- None: 2,
- IS_ARMORED: 1
- }
- },
- UnitTypeId.SIEGETANKSIEGED: {
- TargetType.Ground.value: {
- None: 4,
- IS_ARMORED: 1
- }
- },
- UnitTypeId.THOR: {
- TargetType.Ground.value: {
- None: 3
- },
- TargetType.Air.value: {
- IS_LIGHT: 1
- }
- },
- UnitTypeId.THORAP: {
- TargetType.Ground.value: {
- None: 3
- },
- TargetType.Air.value: {
- None: 3,
- IS_MASSIVE: 1
- }
- },
+ UnitTypeId.HELLION: {TargetType.Ground.value: {IS_LIGHT: 1}},
+ UnitTypeId.HELLIONTANK: {TargetType.Ground.value: {None: 2, IS_LIGHT: 1}},
+ UnitTypeId.CYCLONE: {TargetType.Any.value: {None: 2}},
+ UnitTypeId.SIEGETANK: {TargetType.Ground.value: {None: 2, IS_ARMORED: 1}},
+ UnitTypeId.SIEGETANKSIEGED: {TargetType.Ground.value: {None: 4, IS_ARMORED: 1}},
+ UnitTypeId.THOR: {TargetType.Ground.value: {None: 3}, TargetType.Air.value: {IS_LIGHT: 1}},
+ UnitTypeId.THORAP: {TargetType.Ground.value: {None: 3}, TargetType.Air.value: {None: 3, IS_MASSIVE: 1}},
# Starport Units
- UnitTypeId.VIKINGASSAULT: {
- TargetType.Ground.value: {
- IS_MECHANICAL: 1
- }
- },
- UnitTypeId.LIBERATORAG: {
- TargetType.Ground.value: {
- None: 5
- }
- },
+ UnitTypeId.VIKINGASSAULT: {TargetType.Ground.value: {IS_MECHANICAL: 1}},
+ UnitTypeId.LIBERATORAG: {TargetType.Ground.value: {None: 5}},
#
# Zerg
#
- UnitTypeId.DRONE: {
- TargetType.Ground.value: {
- None: 0
- }
- },
+ UnitTypeId.DRONE: {TargetType.Ground.value: {None: 0}},
# Hatch Tech Units (Queen, Ling, Bane, Roach, Ravager)
- UnitTypeId.BANELING: {
- TargetType.Ground.value: {
- None: 2,
- IS_LIGHT: 2,
- IS_STRUCTURE: 3
- }
- },
- UnitTypeId.ROACH: {
- TargetType.Ground.value: {
- None: 2
- }
- },
- UnitTypeId.RAVAGER: {
- TargetType.Ground.value: {
- None: 2
- }
- },
+ UnitTypeId.BANELING: {TargetType.Ground.value: {None: 2, IS_LIGHT: 2, IS_STRUCTURE: 3}},
+ UnitTypeId.ROACH: {TargetType.Ground.value: {None: 2}},
+ UnitTypeId.RAVAGER: {TargetType.Ground.value: {None: 2}},
# Lair Tech Units (Hydra, Lurker, Ultra)
- UnitTypeId.LURKERMPBURROWED: {
- TargetType.Ground.value: {
- None: 2,
- IS_ARMORED: 1
- }
- },
- UnitTypeId.ULTRALISK: {
- TargetType.Ground.value: {
- None: 3
- }
- },
+ UnitTypeId.LURKERMPBURROWED: {TargetType.Ground.value: {None: 2, IS_ARMORED: 1}},
+ UnitTypeId.ULTRALISK: {TargetType.Ground.value: {None: 3}},
# Spire Units (Muta, Corruptor, BL)
- UnitTypeId.CORRUPTOR: {
- TargetType.Air.value: {
- IS_MASSIVE: 1
- }
- },
- UnitTypeId.BROODLORD: {
- TargetType.Ground.value: {
- None: 2
- }
- },
+ UnitTypeId.CORRUPTOR: {TargetType.Air.value: {IS_MASSIVE: 1}},
+ UnitTypeId.BROODLORD: {TargetType.Ground.value: {None: 2}},
}
TARGET_HELPER = {
1: "no target",
@@ -682,3 +550,16 @@ def return_NOTAUNIT() -> UnitTypeId:
4: "Point2 or Unit",
5: "Point2 or no target",
}
+CREATION_ABILITY_FIX: dict[UnitTypeId, AbilityId] = {
+ UnitTypeId.ARCHON: AbilityId.ARCHON_WARP_TARGET,
+ UnitTypeId.ASSIMILATORRICH: AbilityId.PROTOSSBUILD_ASSIMILATOR,
+ UnitTypeId.BANELINGCOCOON: AbilityId.MORPHZERGLINGTOBANELING_BANELING,
+ UnitTypeId.CHANGELING: AbilityId.SPAWNCHANGELING_SPAWNCHANGELING,
+ UnitTypeId.EXTRACTORRICH: AbilityId.ZERGBUILD_EXTRACTOR,
+ UnitTypeId.INTERCEPTOR: AbilityId.BUILD_INTERCEPTORS,
+ UnitTypeId.LURKERMPEGG: AbilityId.MORPH_LURKER,
+ UnitTypeId.MULE: AbilityId.CALLDOWNMULE_CALLDOWNMULE,
+ UnitTypeId.RAVAGERCOCOON: AbilityId.MORPHTORAVAGER_RAVAGER,
+ UnitTypeId.REFINERYRICH: AbilityId.TERRANBUILD_REFINERY,
+ UnitTypeId.TECHLAB: AbilityId.BUILD_TECHLAB,
+}
diff --git a/sc2/controller.py b/sc2/controller.py
index a3d53aef..2e480330 100644
--- a/sc2/controller.py
+++ b/sc2/controller.py
@@ -2,6 +2,8 @@
from pathlib import Path
from loguru import logger
+
+# pyre-ignore[21]
from s2clientprotocol import sc2api_pb2 as sc_pb
from sc2.player import Computer
@@ -9,14 +11,12 @@
class Controller(Protocol):
-
- def __init__(self, ws, process):
+ def __init__(self, ws, process) -> None:
super().__init__(ws)
self._process = process
@property
- def running(self):
- # pylint: disable=W0212
+ def running(self) -> bool:
return self._process._process is not None
async def create_game(self, game_map, players, realtime: bool, random_seed=None, disable_fog=None):
@@ -46,13 +46,13 @@ async def request_available_maps(self):
return result
async def request_save_map(self, download_path: str):
- """ Not working on linux. """
+ """Not working on linux."""
req = sc_pb.RequestSaveMap(map_path=download_path)
result = await self._execute(save_map=req)
return result
async def request_replay_info(self, replay_path: str):
- """ Not working on linux. """
+ """Not working on linux."""
req = sc_pb.RequestReplayInfo(replay_path=replay_path, download_data=False)
result = await self._execute(replay_info=req)
return result
diff --git a/sc2/data.py b/sc2/data.py
index 7c386052..b0c9425f 100644
--- a/sc2/data.py
+++ b/sc2/data.py
@@ -1,13 +1,17 @@
-""" For the list of enums, see here
+# pyre-ignore-all-errors[16, 19]
+"""For the list of enums, see here
https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_gametypes.h
https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_action.h
https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_unit.h
https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_data.h
"""
+
+from __future__ import annotations
+
import enum
-from typing import Dict, Set
+# pyre-ignore[21]
from s2clientprotocol import common_pb2 as common_pb
from s2clientprotocol import data_pb2 as data_pb
from s2clientprotocol import error_pb2 as error_pb
@@ -39,13 +43,14 @@
ActionResult = enum.Enum("ActionResult", error_pb.ActionResult.items())
-race_worker: Dict[Race, UnitTypeId] = {
+# pyre-ignore[11]
+race_worker: dict[Race, UnitTypeId] = {
Race.Protoss: UnitTypeId.PROBE,
Race.Terran: UnitTypeId.SCV,
Race.Zerg: UnitTypeId.DRONE,
}
-race_townhalls: Dict[Race, Set[UnitTypeId]] = {
+race_townhalls: dict[Race, set[UnitTypeId]] = {
Race.Protoss: {UnitTypeId.NEXUS},
Race.Terran: {
UnitTypeId.COMMANDCENTER,
@@ -71,7 +76,7 @@
},
}
-warpgate_abilities: Dict[AbilityId, AbilityId] = {
+warpgate_abilities: dict[AbilityId, AbilityId] = {
AbilityId.GATEWAYTRAIN_ZEALOT: AbilityId.WARPGATETRAIN_ZEALOT,
AbilityId.GATEWAYTRAIN_STALKER: AbilityId.WARPGATETRAIN_STALKER,
AbilityId.GATEWAYTRAIN_HIGHTEMPLAR: AbilityId.WARPGATETRAIN_HIGHTEMPLAR,
@@ -80,7 +85,7 @@
AbilityId.TRAIN_ADEPT: AbilityId.TRAINWARP_ADEPT,
}
-race_gas: Dict[Race, UnitTypeId] = {
+race_gas: dict[Race, UnitTypeId] = {
Race.Protoss: UnitTypeId.ASSIMILATOR,
Race.Terran: UnitTypeId.REFINERY,
Race.Zerg: UnitTypeId.EXTRACTOR,
diff --git a/sc2/dicts/__init__.py b/sc2/dicts/__init__.py
index 8e39b102..b4c46780 100644
--- a/sc2/dicts/__init__.py
+++ b/sc2/dicts/__init__.py
@@ -2,6 +2,12 @@
# This file was automatically generated by "generate_dicts_from_data_json.py"
__all__ = [
- 'generic_redirect_abilities', 'unit_abilities', 'unit_research_abilities', 'unit_tech_alias',
- 'unit_train_build_abilities', 'unit_trained_from', 'unit_unit_alias', 'upgrade_researched_from'
+ "generic_redirect_abilities",
+ "unit_abilities",
+ "unit_research_abilities",
+ "unit_tech_alias",
+ "unit_train_build_abilities",
+ "unit_trained_from",
+ "unit_unit_alias",
+ "upgrade_researched_from",
]
diff --git a/sc2/dicts/generic_redirect_abilities.py b/sc2/dicts/generic_redirect_abilities.py
index 6abe070e..f303fd03 100644
--- a/sc2/dicts/generic_redirect_abilities.py
+++ b/sc2/dicts/generic_redirect_abilities.py
@@ -1,14 +1,13 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict
-
from sc2.ids.ability_id import AbilityId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+
-GENERIC_REDIRECT_ABILITIES: Dict[AbilityId, AbilityId] = {
+GENERIC_REDIRECT_ABILITIES: dict[AbilityId, AbilityId] = {
AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1: AbilityId.RESEARCH_TERRANSHIPWEAPONS,
AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2: AbilityId.RESEARCH_TERRANSHIPWEAPONS,
AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3: AbilityId.RESEARCH_TERRANSHIPWEAPONS,
@@ -119,6 +118,8 @@
AbilityId.CANCEL_STARPORTADDON: AbilityId.CANCEL,
AbilityId.CANCEL_STASISTRAP: AbilityId.CANCEL,
AbilityId.CANCEL_VOIDRAYPRISMATICALIGNMENT: AbilityId.CANCEL,
+ AbilityId.CHANNELSNIPE_CANCEL: AbilityId.CANCEL,
+ AbilityId.COMMANDCENTERTRANSPORT_COMMANDCENTERTRANSPORT: AbilityId.LOAD,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1: AbilityId.RESEARCH_PROTOSSAIRARMOR,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2: AbilityId.RESEARCH_PROTOSSAIRARMOR,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3: AbilityId.RESEARCH_PROTOSSAIRARMOR,
@@ -190,6 +191,7 @@
AbilityId.LOAD_WARPPRISM: AbilityId.LOAD,
AbilityId.MERGEABLE_CANCEL: AbilityId.CANCEL,
AbilityId.MORPHBACKTOGATEWAY_CANCEL: AbilityId.CANCEL,
+ AbilityId.MORPHTOBANELING_CANCEL: AbilityId.CANCEL,
AbilityId.MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_CANCEL: AbilityId.CANCEL,
AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL: AbilityId.CANCEL,
AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_CANCEL: AbilityId.CANCEL,
@@ -265,5 +267,5 @@
AbilityId.UPGRADETOWARPGATE_CANCEL: AbilityId.CANCEL,
AbilityId.WARPABLE_CANCEL: AbilityId.CANCEL,
AbilityId.WIDOWMINEBURROW_CANCEL: AbilityId.CANCEL,
- AbilityId.ZERGBUILD_CANCEL: AbilityId.HALT
+ AbilityId.ZERGBUILD_CANCEL: AbilityId.HALT,
}
diff --git a/sc2/dicts/unit_abilities.py b/sc2/dicts/unit_abilities.py
index 345c716b..085d2bdd 100644
--- a/sc2/dicts/unit_abilities.py
+++ b/sc2/dicts/unit_abilities.py
@@ -1,176 +1,340 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict, Set
-
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+
-UNIT_ABILITIES: Dict[UnitTypeId, Set[AbilityId]] = {
+UNIT_ABILITIES: dict[UnitTypeId, set[AbilityId]] = {
UnitTypeId.ADEPT: {
- AbilityId.ADEPTPHASESHIFT_ADEPTPHASESHIFT, AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ADEPTPHASESHIFT_ADEPTPHASESHIFT,
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ADEPTPHASESHIFT: {
- AbilityId.ATTACK_ATTACK, AbilityId.CANCEL_ADEPTSHADEPHASESHIFT, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.CANCEL_ADEPTSHADEPHASESHIFT,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ARBITERMP: {
- AbilityId.ARBITERMPRECALL_ARBITERMPRECALL, AbilityId.ARBITERMPSTASISFIELD_ARBITERMPSTASISFIELD,
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ARBITERMPRECALL_ARBITERMPRECALL,
+ AbilityId.ARBITERMPSTASISFIELD_ARBITERMPSTASISFIELD,
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ARCHON: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ARMORY: {
- AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1, AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2,
- AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3, AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1,
+ AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1,
+ AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2,
+ AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3,
+ AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1,
AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2,
- AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3, AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1,
- AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2, AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3
+ AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3,
+ AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1,
+ AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2,
+ AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3,
},
UnitTypeId.AUTOTURRET: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.BANELING: {
- AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_BUILDINGATTACKON, AbilityId.BURROWDOWN_BANELING,
- AbilityId.EXPLODE_EXPLODE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BEHAVIOR_BUILDINGATTACKON,
+ AbilityId.BURROWDOWN_BANELING,
+ AbilityId.EXPLODE_EXPLODE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.BANELINGBURROWED: {AbilityId.BURROWUP_BANELING, AbilityId.EXPLODE_EXPLODE},
UnitTypeId.BANELINGCOCOON: {AbilityId.RALLY_BUILDING, AbilityId.SMART},
UnitTypeId.BANELINGNEST: {AbilityId.RESEARCH_CENTRIFUGALHOOKS},
UnitTypeId.BANSHEE: {
- AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_CLOAKON_BANSHEE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BEHAVIOR_CLOAKON_BANSHEE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.BARRACKS: {
- AbilityId.BARRACKSTRAIN_GHOST, AbilityId.BARRACKSTRAIN_MARAUDER, AbilityId.BARRACKSTRAIN_MARINE,
- AbilityId.BARRACKSTRAIN_REAPER, AbilityId.BUILD_REACTOR_BARRACKS, AbilityId.BUILD_TECHLAB_BARRACKS,
- AbilityId.LIFT_BARRACKS, AbilityId.RALLY_BUILDING, AbilityId.SMART
+ AbilityId.BARRACKSTRAIN_GHOST,
+ AbilityId.BARRACKSTRAIN_MARAUDER,
+ AbilityId.BARRACKSTRAIN_MARINE,
+ AbilityId.BARRACKSTRAIN_REAPER,
+ AbilityId.BUILD_REACTOR_BARRACKS,
+ AbilityId.BUILD_TECHLAB_BARRACKS,
+ AbilityId.LIFT_BARRACKS,
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
},
UnitTypeId.BARRACKSFLYING: {
- AbilityId.BUILD_REACTOR_BARRACKS, AbilityId.BUILD_TECHLAB_BARRACKS, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.LAND_BARRACKS, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BUILD_REACTOR_BARRACKS,
+ AbilityId.BUILD_TECHLAB_BARRACKS,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LAND_BARRACKS,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.BARRACKSTECHLAB: {
- AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK, AbilityId.RESEARCH_COMBATSHIELD, AbilityId.RESEARCH_CONCUSSIVESHELLS
+ AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK,
+ AbilityId.RESEARCH_COMBATSHIELD,
+ AbilityId.RESEARCH_CONCUSSIVESHELLS,
},
UnitTypeId.BATTLECRUISER: {
- AbilityId.ATTACK_BATTLECRUISER, AbilityId.EFFECT_TACTICALJUMP, AbilityId.HOLDPOSITION_BATTLECRUISER,
- AbilityId.MOVE_BATTLECRUISER, AbilityId.PATROL_BATTLECRUISER, AbilityId.SMART, AbilityId.STOP_BATTLECRUISER,
- AbilityId.YAMATO_YAMATOGUN
+ AbilityId.ATTACK_BATTLECRUISER,
+ AbilityId.EFFECT_TACTICALJUMP,
+ AbilityId.HOLDPOSITION_BATTLECRUISER,
+ AbilityId.MOVE_BATTLECRUISER,
+ AbilityId.PATROL_BATTLECRUISER,
+ AbilityId.SMART,
+ AbilityId.STOP_BATTLECRUISER,
+ AbilityId.YAMATO_YAMATOGUN,
},
UnitTypeId.BROODLING: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.BROODLORD: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ },
+ UnitTypeId.BUNKER: {
+ AbilityId.LOAD_BUNKER,
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SALVAGEEFFECT_SALVAGE,
+ AbilityId.SMART,
},
- UnitTypeId.BUNKER: {AbilityId.EFFECT_SALVAGE, AbilityId.LOAD_BUNKER, AbilityId.RALLY_BUILDING, AbilityId.SMART},
UnitTypeId.BYPASSARMORDRONE: {AbilityId.ATTACK_ATTACK, AbilityId.MOVE_MOVE, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.CARRIER: {
- AbilityId.ATTACK_ATTACK, AbilityId.BUILD_INTERCEPTORS, AbilityId.CANCEL_HANGARQUEUE5,
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BUILD_INTERCEPTORS,
+ AbilityId.CANCEL_HANGARQUEUE5,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CHANGELING: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CHANGELINGMARINE: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CHANGELINGMARINESHIELD: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CHANGELINGZEALOT: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CHANGELINGZERGLING: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CHANGELINGZERGLINGWINGS: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.COLOSSUS: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.COMMANDCENTER: {
- AbilityId.COMMANDCENTERTRAIN_SCV, AbilityId.LIFT_COMMANDCENTER, AbilityId.LOADALL_COMMANDCENTER,
- AbilityId.RALLY_COMMANDCENTER, AbilityId.SMART, AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND,
- AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS
+ AbilityId.COMMANDCENTERTRAIN_SCV,
+ AbilityId.LIFT_COMMANDCENTER,
+ AbilityId.LOADALL_COMMANDCENTER,
+ AbilityId.RALLY_COMMANDCENTER,
+ AbilityId.SMART,
+ AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND,
+ AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS,
},
UnitTypeId.COMMANDCENTERFLYING: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.LAND_COMMANDCENTER, AbilityId.LOADALL_COMMANDCENTER, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LAND_COMMANDCENTER,
+ AbilityId.LOADALL_COMMANDCENTER,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CORRUPTOR: {
- AbilityId.ATTACK_ATTACK, AbilityId.CAUSTICSPRAY_CAUSTICSPRAY, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MORPHTOBROODLORD_BROODLORD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.CAUSTICSPRAY_CAUSTICSPRAY,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPHTOBROODLORD_BROODLORD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.CORSAIRMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.CORSAIRMPDISRUPTIONWEB_CORSAIRMPDISRUPTIONWEB, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.CORSAIRMPDISRUPTIONWEB_CORSAIRMPDISRUPTIONWEB,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
- UnitTypeId.CREEPTUMOR: {AbilityId.BUILD_CREEPTUMOR_TUMOR, AbilityId.SMART},
UnitTypeId.CREEPTUMORBURROWED: {AbilityId.BUILD_CREEPTUMOR, AbilityId.BUILD_CREEPTUMOR_TUMOR, AbilityId.SMART},
- UnitTypeId.CREEPTUMORQUEEN: {AbilityId.BUILD_CREEPTUMOR_TUMOR, AbilityId.SMART},
UnitTypeId.CYBERNETICSCORE: {
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2,
- AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3, AbilityId.RESEARCH_WARPGATE
+ AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3,
+ AbilityId.RESEARCH_WARPGATE,
},
UnitTypeId.CYCLONE: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.LOCKON_LOCKON, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LOCKON_LOCKON,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.DARKSHRINE: {AbilityId.RESEARCH_SHADOWSTRIKE},
UnitTypeId.DARKTEMPLAR: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_SHADOWSTRIDE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_ARCHON,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_SHADOWSTRIDE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_ARCHON,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.DEFILERMP: {
- AbilityId.DEFILERMPBURROW_BURROWDOWN, AbilityId.DEFILERMPCONSUME_DEFILERMPCONSUME,
- AbilityId.DEFILERMPDARKSWARM_DEFILERMPDARKSWARM, AbilityId.DEFILERMPPLAGUE_DEFILERMPPLAGUE,
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.DEFILERMPBURROW_BURROWDOWN,
+ AbilityId.DEFILERMPCONSUME_DEFILERMPCONSUME,
+ AbilityId.DEFILERMPDARKSWARM_DEFILERMPDARKSWARM,
+ AbilityId.DEFILERMPPLAGUE_DEFILERMPPLAGUE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.DEFILERMPBURROWED: {AbilityId.DEFILERMPUNBURROW_BURROWUP},
UnitTypeId.DEVOURERMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.DISRUPTOR: {
- AbilityId.EFFECT_PURIFICATIONNOVA, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.EFFECT_PURIFICATIONNOVA,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.DISRUPTORPHASED: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.DRONE: {
- AbilityId.ATTACK_ATTACK, AbilityId.BUILD_LURKERDEN, AbilityId.BURROWDOWN_DRONE, AbilityId.EFFECT_SPRAY_ZERG,
- AbilityId.HARVEST_GATHER_DRONE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.ZERGBUILD_BANELINGNEST, AbilityId.ZERGBUILD_EVOLUTIONCHAMBER,
- AbilityId.ZERGBUILD_EXTRACTOR, AbilityId.ZERGBUILD_HATCHERY, AbilityId.ZERGBUILD_HYDRALISKDEN,
- AbilityId.ZERGBUILD_INFESTATIONPIT, AbilityId.ZERGBUILD_NYDUSNETWORK, AbilityId.ZERGBUILD_ROACHWARREN,
- AbilityId.ZERGBUILD_SPAWNINGPOOL, AbilityId.ZERGBUILD_SPINECRAWLER, AbilityId.ZERGBUILD_SPIRE,
- AbilityId.ZERGBUILD_SPORECRAWLER, AbilityId.ZERGBUILD_ULTRALISKCAVERN
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BUILD_LURKERDEN,
+ AbilityId.BURROWDOWN_DRONE,
+ AbilityId.EFFECT_SPRAY_ZERG,
+ AbilityId.HARVEST_GATHER_DRONE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ AbilityId.ZERGBUILD_BANELINGNEST,
+ AbilityId.ZERGBUILD_EVOLUTIONCHAMBER,
+ AbilityId.ZERGBUILD_EXTRACTOR,
+ AbilityId.ZERGBUILD_HATCHERY,
+ AbilityId.ZERGBUILD_HYDRALISKDEN,
+ AbilityId.ZERGBUILD_INFESTATIONPIT,
+ AbilityId.ZERGBUILD_NYDUSNETWORK,
+ AbilityId.ZERGBUILD_ROACHWARREN,
+ AbilityId.ZERGBUILD_SPAWNINGPOOL,
+ AbilityId.ZERGBUILD_SPINECRAWLER,
+ AbilityId.ZERGBUILD_SPIRE,
+ AbilityId.ZERGBUILD_SPORECRAWLER,
+ AbilityId.ZERGBUILD_ULTRALISKCAVERN,
},
UnitTypeId.DRONEBURROWED: {AbilityId.BURROWUP_DRONE},
UnitTypeId.EGG: {AbilityId.RALLY_BUILDING, AbilityId.SMART},
@@ -181,484 +345,986 @@
AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3,
AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1,
AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3, AbilityId.RESEARCH_HISECAUTOTRACKING,
- AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE
+ AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
+ AbilityId.RESEARCH_HISECAUTOTRACKING,
+ AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE,
},
UnitTypeId.EVOLUTIONCHAMBER: {
- AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1, AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
- AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3, AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1,
- AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2, AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3,
- AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1, AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2,
- AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3
+ AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1,
+ AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
+ AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3,
+ AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1,
+ AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2,
+ AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3,
+ AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1,
+ AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2,
+ AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3,
},
UnitTypeId.FACTORY: {
- AbilityId.BUILD_REACTOR_FACTORY, AbilityId.BUILD_TECHLAB_FACTORY, AbilityId.FACTORYTRAIN_HELLION,
- AbilityId.FACTORYTRAIN_SIEGETANK, AbilityId.FACTORYTRAIN_THOR, AbilityId.FACTORYTRAIN_WIDOWMINE,
- AbilityId.LIFT_FACTORY, AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.TRAIN_CYCLONE,
- AbilityId.TRAIN_HELLBAT
+ AbilityId.BUILD_REACTOR_FACTORY,
+ AbilityId.BUILD_TECHLAB_FACTORY,
+ AbilityId.FACTORYTRAIN_HELLION,
+ AbilityId.FACTORYTRAIN_SIEGETANK,
+ AbilityId.FACTORYTRAIN_THOR,
+ AbilityId.FACTORYTRAIN_WIDOWMINE,
+ AbilityId.LIFT_FACTORY,
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
+ AbilityId.TRAIN_CYCLONE,
+ AbilityId.TRAIN_HELLBAT,
},
UnitTypeId.FACTORYFLYING: {
- AbilityId.BUILD_REACTOR_FACTORY, AbilityId.BUILD_TECHLAB_FACTORY, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.LAND_FACTORY, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BUILD_REACTOR_FACTORY,
+ AbilityId.BUILD_TECHLAB_FACTORY,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LAND_FACTORY,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.FACTORYTECHLAB: {
- AbilityId.RESEARCH_CYCLONELOCKONDAMAGE, AbilityId.RESEARCH_DRILLINGCLAWS, AbilityId.RESEARCH_INFERNALPREIGNITER,
- AbilityId.RESEARCH_SMARTSERVOS
+ AbilityId.RESEARCH_CYCLONELOCKONDAMAGE,
+ AbilityId.RESEARCH_DRILLINGCLAWS,
+ AbilityId.RESEARCH_INFERNALPREIGNITER,
+ AbilityId.RESEARCH_SMARTSERVOS,
},
UnitTypeId.FLEETBEACON: {
AbilityId.FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE,
- AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE, AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS
+ AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE,
+ AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS,
},
UnitTypeId.FORGE: {
- AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1, AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2,
- AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3, AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1,
- AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2, AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3,
- AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1, AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
- AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3
+ AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1,
+ AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2,
+ AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3,
+ AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1,
+ AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2,
+ AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3,
+ AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
+ AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
+ AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
},
UnitTypeId.FUSIONCORE: {
- AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE, AbilityId.FUSIONCORERESEARCH_RESEARCHRAPIDREIGNITIONSYSTEM,
- AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT
+ AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE,
+ AbilityId.FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE,
+ AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT,
},
UnitTypeId.GATEWAY: {
- AbilityId.GATEWAYTRAIN_DARKTEMPLAR, AbilityId.GATEWAYTRAIN_HIGHTEMPLAR, AbilityId.GATEWAYTRAIN_SENTRY,
- AbilityId.GATEWAYTRAIN_STALKER, AbilityId.GATEWAYTRAIN_ZEALOT, AbilityId.MORPH_WARPGATE,
- AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.TRAIN_ADEPT
+ AbilityId.GATEWAYTRAIN_DARKTEMPLAR,
+ AbilityId.GATEWAYTRAIN_HIGHTEMPLAR,
+ AbilityId.GATEWAYTRAIN_SENTRY,
+ AbilityId.GATEWAYTRAIN_STALKER,
+ AbilityId.GATEWAYTRAIN_ZEALOT,
+ AbilityId.MORPH_WARPGATE,
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
+ AbilityId.TRAIN_ADEPT,
},
UnitTypeId.GHOST: {
- AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_CLOAKON_GHOST, AbilityId.BEHAVIOR_HOLDFIREON_GHOST,
- AbilityId.EFFECT_GHOSTSNIPE, AbilityId.EMP_EMP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
- },
- UnitTypeId.GHOSTACADEMY: {
- AbilityId.BUILD_NUKE, AbilityId.GHOSTACADEMYRESEARCH_RESEARCHENHANCEDSHOCKWAVES,
- AbilityId.RESEARCH_PERSONALCLOAKING
- },
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BEHAVIOR_CLOAKON_GHOST,
+ AbilityId.BEHAVIOR_HOLDFIREON_GHOST,
+ AbilityId.EFFECT_GHOSTSNIPE,
+ AbilityId.EMP_EMP,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ },
+ UnitTypeId.GHOSTACADEMY: {AbilityId.BUILD_NUKE, AbilityId.RESEARCH_PERSONALCLOAKING},
UnitTypeId.GHOSTNOVA: {
- AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_CLOAKON_GHOST, AbilityId.BEHAVIOR_HOLDFIREON_GHOST,
- AbilityId.EFFECT_GHOSTSNIPE, AbilityId.EMP_EMP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BEHAVIOR_CLOAKON_GHOST,
+ AbilityId.BEHAVIOR_HOLDFIREON_GHOST,
+ AbilityId.EFFECT_GHOSTSNIPE,
+ AbilityId.EMP_EMP,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.GREATERSPIRE: {
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1, AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1,
- AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
+ AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1,
+ AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
+ AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
},
UnitTypeId.GUARDIANMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HATCHERY: {
- AbilityId.RALLY_HATCHERY_UNITS, AbilityId.RALLY_HATCHERY_WORKERS, AbilityId.RESEARCH_BURROW,
- AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, AbilityId.SMART, AbilityId.TRAINQUEEN_QUEEN,
- AbilityId.UPGRADETOLAIR_LAIR
+ AbilityId.RALLY_HATCHERY_UNITS,
+ AbilityId.RALLY_HATCHERY_WORKERS,
+ AbilityId.RESEARCH_BURROW,
+ AbilityId.RESEARCH_PNEUMATIZEDCARAPACE,
+ AbilityId.SMART,
+ AbilityId.TRAINQUEEN_QUEEN,
+ AbilityId.UPGRADETOLAIR_LAIR,
},
UnitTypeId.HELLION: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_HELLBAT, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_HELLBAT,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HELLIONTANK: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_HELLION, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_HELLION,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HERC: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HERCPLACEMENT: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HIGHTEMPLAR: {
- AbilityId.ATTACK_ATTACK, AbilityId.FEEDBACK_FEEDBACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_ARCHON,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.PSISTORM_PSISTORM, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.FEEDBACK_FEEDBACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_ARCHON,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.PSISTORM_PSISTORM,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HIVE: {
- AbilityId.RALLY_HATCHERY_UNITS, AbilityId.RALLY_HATCHERY_WORKERS, AbilityId.RESEARCH_BURROW,
- AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, AbilityId.SMART, AbilityId.TRAINQUEEN_QUEEN
+ AbilityId.RALLY_HATCHERY_UNITS,
+ AbilityId.RALLY_HATCHERY_WORKERS,
+ AbilityId.RESEARCH_BURROW,
+ AbilityId.RESEARCH_PNEUMATIZEDCARAPACE,
+ AbilityId.SMART,
+ AbilityId.TRAINQUEEN_QUEEN,
},
UnitTypeId.HYDRALISK: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_HYDRALISK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_LURKER,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_HYDRALISK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.HYDRALISKFRENZY_HYDRALISKFRENZY,
+ AbilityId.MORPH_LURKER,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.HYDRALISKBURROWED: {AbilityId.BURROWUP_HYDRALISK},
- UnitTypeId.HYDRALISKDEN: {AbilityId.RESEARCH_GROOVEDSPINES, AbilityId.RESEARCH_MUSCULARAUGMENTS},
- UnitTypeId.IMMORTAL: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
- },
- UnitTypeId.INFESTATIONPIT: {AbilityId.RESEARCH_NEURALPARASITE, AbilityId.RESEARCH_PATHOGENGLANDS},
- UnitTypeId.INFESTEDTERRANSEGG: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART
+ UnitTypeId.HYDRALISKDEN: {
+ AbilityId.HYDRALISKDENRESEARCH_RESEARCHFRENZY,
+ AbilityId.RESEARCH_GROOVEDSPINES,
+ AbilityId.RESEARCH_MUSCULARAUGMENTS,
},
+ UnitTypeId.IMMORTAL: {
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ },
+ UnitTypeId.INFESTATIONPIT: {AbilityId.RESEARCH_NEURALPARASITE},
UnitTypeId.INFESTOR: {
- AbilityId.AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD, AbilityId.BURROWDOWN_INFESTOR,
- AbilityId.BURROWDOWN_INFESTORTERRAN, AbilityId.FUNGALGROWTH_FUNGALGROWTH, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.NEURALPARASITE_NEURALPARASITE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD,
+ AbilityId.BURROWDOWN_INFESTOR,
+ AbilityId.BURROWDOWN_INFESTORTERRAN,
+ AbilityId.FUNGALGROWTH_FUNGALGROWTH,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.NEURALPARASITE_NEURALPARASITE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.INFESTORBURROWED: {
- AbilityId.BURROWUP_INFESTOR, AbilityId.BURROWUP_INFESTORTERRAN, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.NEURALPARASITE_NEURALPARASITE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BURROWUP_INFESTOR,
+ AbilityId.BURROWUP_INFESTORTERRAN,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.NEURALPARASITE_NEURALPARASITE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.INFESTORTERRAN: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_INFESTORTERRAN, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_INFESTORTERRAN,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.INFESTORTERRANBURROWED: {AbilityId.BURROWUP_INFESTORTERRAN},
UnitTypeId.INTERCEPTOR: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LAIR: {
- AbilityId.RALLY_HATCHERY_UNITS, AbilityId.RALLY_HATCHERY_WORKERS, AbilityId.RESEARCH_BURROW,
- AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, AbilityId.SMART, AbilityId.TRAINQUEEN_QUEEN,
- AbilityId.UPGRADETOHIVE_HIVE
+ AbilityId.RALLY_HATCHERY_UNITS,
+ AbilityId.RALLY_HATCHERY_WORKERS,
+ AbilityId.RESEARCH_BURROW,
+ AbilityId.RESEARCH_PNEUMATIZEDCARAPACE,
+ AbilityId.SMART,
+ AbilityId.TRAINQUEEN_QUEEN,
+ AbilityId.UPGRADETOHIVE_HIVE,
},
UnitTypeId.LARVA: {
- AbilityId.LARVATRAIN_CORRUPTOR, AbilityId.LARVATRAIN_DRONE, AbilityId.LARVATRAIN_HYDRALISK,
- AbilityId.LARVATRAIN_INFESTOR, AbilityId.LARVATRAIN_MUTALISK, AbilityId.LARVATRAIN_OVERLORD,
- AbilityId.LARVATRAIN_ROACH, AbilityId.LARVATRAIN_ULTRALISK, AbilityId.LARVATRAIN_VIPER,
- AbilityId.LARVATRAIN_ZERGLING, AbilityId.TRAIN_SWARMHOST
+ AbilityId.LARVATRAIN_CORRUPTOR,
+ AbilityId.LARVATRAIN_DRONE,
+ AbilityId.LARVATRAIN_HYDRALISK,
+ AbilityId.LARVATRAIN_INFESTOR,
+ AbilityId.LARVATRAIN_MUTALISK,
+ AbilityId.LARVATRAIN_OVERLORD,
+ AbilityId.LARVATRAIN_ROACH,
+ AbilityId.LARVATRAIN_ULTRALISK,
+ AbilityId.LARVATRAIN_VIPER,
+ AbilityId.LARVATRAIN_ZERGLING,
+ AbilityId.TRAIN_SWARMHOST,
},
UnitTypeId.LIBERATOR: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_LIBERATORAGMODE, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_LIBERATORAGMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LIBERATORAG: {
- AbilityId.ATTACK_ATTACK, AbilityId.MORPH_LIBERATORAAMODE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.MORPH_LIBERATORAAMODE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LOCUSTMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LOCUSTMPFLYING: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_LOCUSTSWOOP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_LOCUSTSWOOP,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LURKERDENMP: {AbilityId.LURKERDENRESEARCH_RESEARCHLURKERRANGE, AbilityId.RESEARCH_ADAPTIVETALONS},
UnitTypeId.LURKERMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_LURKER, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_LURKER,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LURKERMPBURROWED: {
- AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_HOLDFIREON_LURKER, AbilityId.BURROWUP_LURKER, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BEHAVIOR_HOLDFIREON_LURKER,
+ AbilityId.BURROWUP_LURKER,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.LURKERMPEGG: {AbilityId.RALLY_BUILDING, AbilityId.SMART},
UnitTypeId.MARAUDER: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_STIM_MARAUDER, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_STIM_MARAUDER,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.MARINE: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_STIM_MARINE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_STIM_MARINE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.MEDIVAC: {
- AbilityId.EFFECT_MEDIVACIGNITEAFTERBURNERS, AbilityId.HOLDPOSITION_HOLD, AbilityId.LOAD_MEDIVAC,
- AbilityId.MEDIVACHEAL_HEAL, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.EFFECT_MEDIVACIGNITEAFTERBURNERS,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LOAD_MEDIVAC,
+ AbilityId.MEDIVACHEAL_HEAL,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.MISSILETURRET: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.MOTHERSHIP: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_MASSRECALL_STRATEGICRECALL, AbilityId.EFFECT_TIMEWARP,
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_MASSRECALL_STRATEGICRECALL,
+ AbilityId.EFFECT_TIMEWARP,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOTHERSHIPCLOAK_ORACLECLOAKFIELD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.MOTHERSHIPCORE: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_MASSRECALL_MOTHERSHIPCORE, AbilityId.EFFECT_PHOTONOVERCHARGE,
- AbilityId.EFFECT_TIMEWARP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_MOTHERSHIP, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_MASSRECALL_MOTHERSHIPCORE,
+ AbilityId.EFFECT_PHOTONOVERCHARGE,
+ AbilityId.EFFECT_TIMEWARP,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_MOTHERSHIP,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.MULE: {
- AbilityId.EFFECT_REPAIR_MULE, AbilityId.HARVEST_GATHER_MULE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.EFFECT_REPAIR_MULE,
+ AbilityId.HARVEST_GATHER_MULE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.MUTALISK: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.NEXUS: {
- AbilityId.BATTERYOVERCHARGE_BATTERYOVERCHARGE, AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
- AbilityId.EFFECT_MASSRECALL_NEXUS, AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP, AbilityId.NEXUSTRAIN_PROBE,
- AbilityId.RALLY_NEXUS, AbilityId.SMART
+ AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
+ AbilityId.EFFECT_MASSRECALL_NEXUS,
+ AbilityId.ENERGYRECHARGE_ENERGYRECHARGE,
+ AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP,
+ AbilityId.NEXUSTRAIN_PROBE,
+ AbilityId.RALLY_NEXUS,
+ AbilityId.SMART,
},
UnitTypeId.NYDUSCANAL: {AbilityId.LOAD_NYDUSWORM, AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.NYDUSCANALATTACKER: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.NYDUSCANALCREEPER: {
- AbilityId.ATTACK_ATTACK, AbilityId.DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.NYDUSNETWORK: {
- AbilityId.BUILD_NYDUSWORM, AbilityId.LOAD_NYDUSNETWORK, AbilityId.RALLY_BUILDING, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.BUILD_NYDUSWORM,
+ AbilityId.LOAD_NYDUSNETWORK,
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.OBSERVER: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_SURVEILLANCEMODE, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_SURVEILLANCEMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.OBSERVERSIEGEMODE: {AbilityId.MORPH_OBSERVERMODE, AbilityId.STOP_STOP},
UnitTypeId.ORACLE: {
- AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_PULSARBEAMON, AbilityId.BUILD_STASISTRAP,
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.ORACLEREVELATION_ORACLEREVELATION,
- AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BEHAVIOR_PULSARBEAMON,
+ AbilityId.BUILD_STASISTRAP,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.ORACLEREVELATION_ORACLEREVELATION,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ORBITALCOMMAND: {
- AbilityId.CALLDOWNMULE_CALLDOWNMULE, AbilityId.COMMANDCENTERTRAIN_SCV, AbilityId.LIFT_ORBITALCOMMAND,
- AbilityId.RALLY_COMMANDCENTER, AbilityId.SCANNERSWEEP_SCAN, AbilityId.SMART, AbilityId.SUPPLYDROP_SUPPLYDROP
+ AbilityId.CALLDOWNMULE_CALLDOWNMULE,
+ AbilityId.COMMANDCENTERTRAIN_SCV,
+ AbilityId.LIFT_ORBITALCOMMAND,
+ AbilityId.RALLY_COMMANDCENTER,
+ AbilityId.SCANNERSWEEP_SCAN,
+ AbilityId.SMART,
+ AbilityId.SUPPLYDROP_SUPPLYDROP,
},
UnitTypeId.ORBITALCOMMANDFLYING: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.LAND_ORBITALCOMMAND, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LAND_ORBITALCOMMAND,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.OVERLORD: {
- AbilityId.BEHAVIOR_GENERATECREEPON, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_OVERLORDTRANSPORT,
- AbilityId.MORPH_OVERSEER, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.BEHAVIOR_GENERATECREEPON,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_OVERLORDTRANSPORT,
+ AbilityId.MORPH_OVERSEER,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.OVERLORDTRANSPORT: {
- AbilityId.BEHAVIOR_GENERATECREEPON, AbilityId.HOLDPOSITION_HOLD, AbilityId.LOAD_OVERLORD,
- AbilityId.MORPH_OVERSEER, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.BEHAVIOR_GENERATECREEPON,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LOAD_OVERLORD,
+ AbilityId.MORPH_OVERSEER,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.OVERSEER: {
- AbilityId.CONTAMINATE_CONTAMINATE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_OVERSIGHTMODE,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.SPAWNCHANGELING_SPAWNCHANGELING, AbilityId.STOP_STOP
+ AbilityId.CONTAMINATE_CONTAMINATE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_OVERSIGHTMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.SPAWNCHANGELING_SPAWNCHANGELING,
+ AbilityId.STOP_STOP,
},
UnitTypeId.OVERSEERSIEGEMODE: {
- AbilityId.CONTAMINATE_CONTAMINATE, AbilityId.MORPH_OVERSEERMODE, AbilityId.SMART,
- AbilityId.SPAWNCHANGELING_SPAWNCHANGELING, AbilityId.STOP_STOP
+ AbilityId.CONTAMINATE_CONTAMINATE,
+ AbilityId.MORPH_OVERSEERMODE,
+ AbilityId.SMART,
+ AbilityId.SPAWNCHANGELING_SPAWNCHANGELING,
+ AbilityId.STOP_STOP,
},
UnitTypeId.PHOENIX: {
- AbilityId.ATTACK_ATTACK, AbilityId.GRAVITONBEAM_GRAVITONBEAM, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.GRAVITONBEAM_GRAVITONBEAM,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.PHOTONCANNON: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.PLANETARYFORTRESS: {
- AbilityId.ATTACK_ATTACK, AbilityId.COMMANDCENTERTRAIN_SCV, AbilityId.LOADALL_COMMANDCENTER,
- AbilityId.RALLY_COMMANDCENTER, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.COMMANDCENTERTRAIN_SCV,
+ AbilityId.LOADALL_COMMANDCENTER,
+ AbilityId.RALLY_COMMANDCENTER,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.PROBE: {
- AbilityId.ATTACK_ATTACK, AbilityId.BUILD_SHIELDBATTERY, AbilityId.EFFECT_SPRAY_PROTOSS,
- AbilityId.HARVEST_GATHER_PROBE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.PROTOSSBUILD_ASSIMILATOR, AbilityId.PROTOSSBUILD_CYBERNETICSCORE, AbilityId.PROTOSSBUILD_DARKSHRINE,
- AbilityId.PROTOSSBUILD_FLEETBEACON, AbilityId.PROTOSSBUILD_FORGE, AbilityId.PROTOSSBUILD_GATEWAY,
- AbilityId.PROTOSSBUILD_NEXUS, AbilityId.PROTOSSBUILD_PHOTONCANNON, AbilityId.PROTOSSBUILD_PYLON,
- AbilityId.PROTOSSBUILD_ROBOTICSBAY, AbilityId.PROTOSSBUILD_ROBOTICSFACILITY, AbilityId.PROTOSSBUILD_STARGATE,
- AbilityId.PROTOSSBUILD_TEMPLARARCHIVE, AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BUILD_SHIELDBATTERY,
+ AbilityId.EFFECT_SPRAY_PROTOSS,
+ AbilityId.HARVEST_GATHER_PROBE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.PROTOSSBUILD_ASSIMILATOR,
+ AbilityId.PROTOSSBUILD_CYBERNETICSCORE,
+ AbilityId.PROTOSSBUILD_DARKSHRINE,
+ AbilityId.PROTOSSBUILD_FLEETBEACON,
+ AbilityId.PROTOSSBUILD_FORGE,
+ AbilityId.PROTOSSBUILD_GATEWAY,
+ AbilityId.PROTOSSBUILD_NEXUS,
+ AbilityId.PROTOSSBUILD_PHOTONCANNON,
+ AbilityId.PROTOSSBUILD_PYLON,
+ AbilityId.PROTOSSBUILD_ROBOTICSBAY,
+ AbilityId.PROTOSSBUILD_ROBOTICSFACILITY,
+ AbilityId.PROTOSSBUILD_STARGATE,
+ AbilityId.PROTOSSBUILD_TEMPLARARCHIVE,
+ AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.QUEEN: {
- AbilityId.ATTACK_ATTACK, AbilityId.BUILD_CREEPTUMOR, AbilityId.BUILD_CREEPTUMOR_QUEEN,
- AbilityId.BURROWDOWN_QUEEN, AbilityId.EFFECT_INJECTLARVA, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.TRANSFUSION_TRANSFUSION
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BUILD_CREEPTUMOR,
+ AbilityId.BUILD_CREEPTUMOR_QUEEN,
+ AbilityId.BURROWDOWN_QUEEN,
+ AbilityId.EFFECT_INJECTLARVA,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ AbilityId.TRANSFUSION_TRANSFUSION,
},
UnitTypeId.QUEENBURROWED: {AbilityId.BURROWUP_QUEEN},
UnitTypeId.QUEENMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.QUEENMPENSNARE_QUEENMPENSNARE, AbilityId.QUEENMPINFESTCOMMANDCENTER_QUEENMPINFESTCOMMANDCENTER,
- AbilityId.QUEENMPSPAWNBROODLINGS_QUEENMPSPAWNBROODLINGS, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.QUEENMPENSNARE_QUEENMPENSNARE,
+ AbilityId.QUEENMPINFESTCOMMANDCENTER_QUEENMPINFESTCOMMANDCENTER,
+ AbilityId.QUEENMPSPAWNBROODLINGS_QUEENMPSPAWNBROODLINGS,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.RAVAGER: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_RAVAGER, AbilityId.EFFECT_CORROSIVEBILE,
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_RAVAGER,
+ AbilityId.EFFECT_CORROSIVEBILE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.RAVAGERBURROWED: {AbilityId.BURROWUP_RAVAGER},
UnitTypeId.RAVAGERCOCOON: {AbilityId.RALLY_BUILDING, AbilityId.SMART},
UnitTypeId.RAVEN: {
- AbilityId.BUILDAUTOTURRET_AUTOTURRET, AbilityId.EFFECT_ANTIARMORMISSILE, AbilityId.EFFECT_INTERFERENCEMATRIX,
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.BUILDAUTOTURRET_AUTOTURRET,
+ AbilityId.EFFECT_ANTIARMORMISSILE,
+ AbilityId.EFFECT_INTERFERENCEMATRIX,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.RAVENREPAIRDRONE: {AbilityId.EFFECT_REPAIR_REPAIRDRONE, AbilityId.SMART, AbilityId.STOP_STOP},
UnitTypeId.REAPER: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.KD8CHARGE_KD8CHARGE, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.KD8CHARGE_KD8CHARGE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.REPLICANT: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ROACH: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_ROACH, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MORPHTORAVAGER_RAVAGER, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART,
- AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_ROACH,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPHTORAVAGER_RAVAGER,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ROACHBURROWED: {
- AbilityId.BURROWUP_ROACH, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BURROWUP_ROACH,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ROACHWARREN: {AbilityId.RESEARCH_GLIALREGENERATION, AbilityId.RESEARCH_TUNNELINGCLAWS},
UnitTypeId.ROBOTICSBAY: {
- AbilityId.RESEARCH_EXTENDEDTHERMALLANCE, AbilityId.RESEARCH_GRAVITICBOOSTER, AbilityId.RESEARCH_GRAVITICDRIVE
+ AbilityId.RESEARCH_EXTENDEDTHERMALLANCE,
+ AbilityId.RESEARCH_GRAVITICBOOSTER,
+ AbilityId.RESEARCH_GRAVITICDRIVE,
},
UnitTypeId.ROBOTICSFACILITY: {
- AbilityId.RALLY_BUILDING, AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS, AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL,
- AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM, AbilityId.SMART,
- AbilityId.TRAIN_DISRUPTOR
+ AbilityId.RALLY_BUILDING,
+ AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS,
+ AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL,
+ AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER,
+ AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM,
+ AbilityId.SMART,
+ AbilityId.TRAIN_DISRUPTOR,
},
UnitTypeId.SCOURGEMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.SCOUTMP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.SCV: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_REPAIR_SCV, AbilityId.EFFECT_SPRAY_TERRAN,
- AbilityId.HARVEST_GATHER_SCV, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.TERRANBUILD_ARMORY, AbilityId.TERRANBUILD_BARRACKS,
- AbilityId.TERRANBUILD_BUNKER, AbilityId.TERRANBUILD_COMMANDCENTER, AbilityId.TERRANBUILD_ENGINEERINGBAY,
- AbilityId.TERRANBUILD_FACTORY, AbilityId.TERRANBUILD_FUSIONCORE, AbilityId.TERRANBUILD_GHOSTACADEMY,
- AbilityId.TERRANBUILD_MISSILETURRET, AbilityId.TERRANBUILD_REFINERY, AbilityId.TERRANBUILD_SENSORTOWER,
- AbilityId.TERRANBUILD_STARPORT, AbilityId.TERRANBUILD_SUPPLYDEPOT
- },
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_REPAIR_SCV,
+ AbilityId.EFFECT_SPRAY_TERRAN,
+ AbilityId.HARVEST_GATHER_SCV,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ AbilityId.TERRANBUILD_ARMORY,
+ AbilityId.TERRANBUILD_BARRACKS,
+ AbilityId.TERRANBUILD_BUNKER,
+ AbilityId.TERRANBUILD_COMMANDCENTER,
+ AbilityId.TERRANBUILD_ENGINEERINGBAY,
+ AbilityId.TERRANBUILD_FACTORY,
+ AbilityId.TERRANBUILD_FUSIONCORE,
+ AbilityId.TERRANBUILD_GHOSTACADEMY,
+ AbilityId.TERRANBUILD_MISSILETURRET,
+ AbilityId.TERRANBUILD_REFINERY,
+ AbilityId.TERRANBUILD_SENSORTOWER,
+ AbilityId.TERRANBUILD_STARPORT,
+ AbilityId.TERRANBUILD_SUPPLYDEPOT,
+ },
+ UnitTypeId.SENSORTOWER: {AbilityId.SALVAGEEFFECT_SALVAGE},
UnitTypeId.SENTRY: {
- AbilityId.ATTACK_ATTACK, AbilityId.FORCEFIELD_FORCEFIELD, AbilityId.GUARDIANSHIELD_GUARDIANSHIELD,
- AbilityId.HALLUCINATION_ADEPT, AbilityId.HALLUCINATION_ARCHON, AbilityId.HALLUCINATION_COLOSSUS,
- AbilityId.HALLUCINATION_DISRUPTOR, AbilityId.HALLUCINATION_HIGHTEMPLAR, AbilityId.HALLUCINATION_IMMORTAL,
- AbilityId.HALLUCINATION_ORACLE, AbilityId.HALLUCINATION_PHOENIX, AbilityId.HALLUCINATION_PROBE,
- AbilityId.HALLUCINATION_STALKER, AbilityId.HALLUCINATION_VOIDRAY, AbilityId.HALLUCINATION_WARPPRISM,
- AbilityId.HALLUCINATION_ZEALOT, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
- },
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.FORCEFIELD_FORCEFIELD,
+ AbilityId.GUARDIANSHIELD_GUARDIANSHIELD,
+ AbilityId.HALLUCINATION_ADEPT,
+ AbilityId.HALLUCINATION_ARCHON,
+ AbilityId.HALLUCINATION_COLOSSUS,
+ AbilityId.HALLUCINATION_DISRUPTOR,
+ AbilityId.HALLUCINATION_HIGHTEMPLAR,
+ AbilityId.HALLUCINATION_IMMORTAL,
+ AbilityId.HALLUCINATION_ORACLE,
+ AbilityId.HALLUCINATION_PHOENIX,
+ AbilityId.HALLUCINATION_PROBE,
+ AbilityId.HALLUCINATION_STALKER,
+ AbilityId.HALLUCINATION_VOIDRAY,
+ AbilityId.HALLUCINATION_WARPPRISM,
+ AbilityId.HALLUCINATION_ZEALOT,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ },
+ UnitTypeId.SHIELDBATTERY: {AbilityId.SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE, AbilityId.SMART},
UnitTypeId.SIEGETANK: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SIEGEMODE_SIEGEMODE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SIEGEMODE_SIEGEMODE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.SIEGETANKSIEGED: {
- AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.UNSIEGE_UNSIEGE
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ AbilityId.UNSIEGE_UNSIEGE,
},
UnitTypeId.SPAWNINGPOOL: {AbilityId.RESEARCH_ZERGLINGADRENALGLANDS, AbilityId.RESEARCH_ZERGLINGMETABOLICBOOST},
UnitTypeId.SPINECRAWLER: {
- AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.SPINECRAWLERUPROOT_SPINECRAWLERUPROOT, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.SMART,
+ AbilityId.SPINECRAWLERUPROOT_SPINECRAWLERUPROOT,
+ AbilityId.STOP_STOP,
},
UnitTypeId.SPINECRAWLERUPROOTED: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.SPINECRAWLERROOT_SPINECRAWLERROOT, AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.SPINECRAWLERROOT_SPINECRAWLERROOT,
+ AbilityId.STOP_STOP,
},
UnitTypeId.SPIRE: {
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1, AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1,
- AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
- AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
+ AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1,
+ AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
+ AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
+ AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE,
},
UnitTypeId.SPORECRAWLER: {
- AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.SPORECRAWLERUPROOT_SPORECRAWLERUPROOT, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.SMART,
+ AbilityId.SPORECRAWLERUPROOT_SPORECRAWLERUPROOT,
+ AbilityId.STOP_STOP,
},
UnitTypeId.SPORECRAWLERUPROOTED: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART,
- AbilityId.SPORECRAWLERROOT_SPORECRAWLERROOT, AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.SPORECRAWLERROOT_SPORECRAWLERROOT,
+ AbilityId.STOP_STOP,
},
UnitTypeId.STALKER: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_BLINK_STALKER, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_BLINK_STALKER,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.STARGATE: {
- AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.STARGATETRAIN_CARRIER, AbilityId.STARGATETRAIN_ORACLE,
- AbilityId.STARGATETRAIN_PHOENIX, AbilityId.STARGATETRAIN_TEMPEST, AbilityId.STARGATETRAIN_VOIDRAY
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
+ AbilityId.STARGATETRAIN_CARRIER,
+ AbilityId.STARGATETRAIN_ORACLE,
+ AbilityId.STARGATETRAIN_PHOENIX,
+ AbilityId.STARGATETRAIN_TEMPEST,
+ AbilityId.STARGATETRAIN_VOIDRAY,
},
UnitTypeId.STARPORT: {
- AbilityId.BUILD_REACTOR_STARPORT, AbilityId.BUILD_TECHLAB_STARPORT, AbilityId.LIFT_STARPORT,
- AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.STARPORTTRAIN_BANSHEE,
- AbilityId.STARPORTTRAIN_BATTLECRUISER, AbilityId.STARPORTTRAIN_LIBERATOR, AbilityId.STARPORTTRAIN_MEDIVAC,
- AbilityId.STARPORTTRAIN_RAVEN, AbilityId.STARPORTTRAIN_VIKINGFIGHTER
+ AbilityId.BUILD_REACTOR_STARPORT,
+ AbilityId.BUILD_TECHLAB_STARPORT,
+ AbilityId.LIFT_STARPORT,
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
+ AbilityId.STARPORTTRAIN_BANSHEE,
+ AbilityId.STARPORTTRAIN_BATTLECRUISER,
+ AbilityId.STARPORTTRAIN_LIBERATOR,
+ AbilityId.STARPORTTRAIN_MEDIVAC,
+ AbilityId.STARPORTTRAIN_RAVEN,
+ AbilityId.STARPORTTRAIN_VIKINGFIGHTER,
},
UnitTypeId.STARPORTFLYING: {
- AbilityId.BUILD_REACTOR_STARPORT, AbilityId.BUILD_TECHLAB_STARPORT, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.LAND_STARPORT, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BUILD_REACTOR_STARPORT,
+ AbilityId.BUILD_TECHLAB_STARPORT,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LAND_STARPORT,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.STARPORTTECHLAB: {
- AbilityId.RESEARCH_BANSHEECLOAKINGFIELD, AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS,
- AbilityId.RESEARCH_RAVENCORVIDREACTOR
+ AbilityId.RESEARCH_BANSHEECLOAKINGFIELD,
+ AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS,
+ AbilityId.STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX,
},
UnitTypeId.SUPPLYDEPOT: {AbilityId.MORPH_SUPPLYDEPOT_LOWER},
UnitTypeId.SUPPLYDEPOTLOWERED: {AbilityId.MORPH_SUPPLYDEPOT_RAISE},
UnitTypeId.SWARMHOSTBURROWEDMP: {AbilityId.EFFECT_SPAWNLOCUSTS, AbilityId.SMART},
UnitTypeId.SWARMHOSTMP: {
- AbilityId.BURROWDOWN_SWARMHOST, AbilityId.EFFECT_SPAWNLOCUSTS, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BURROWDOWN_SWARMHOST,
+ AbilityId.EFFECT_SPAWNLOCUSTS,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.TECHLAB: {
- AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK, AbilityId.RESEARCH_BANSHEECLOAKINGFIELD,
- AbilityId.RESEARCH_COMBATSHIELD, AbilityId.RESEARCH_CONCUSSIVESHELLS, AbilityId.RESEARCH_DRILLINGCLAWS,
- AbilityId.RESEARCH_INFERNALPREIGNITER, AbilityId.RESEARCH_RAVENCORVIDREACTOR
+ AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK,
+ AbilityId.RESEARCH_BANSHEECLOAKINGFIELD,
+ AbilityId.RESEARCH_COMBATSHIELD,
+ AbilityId.RESEARCH_CONCUSSIVESHELLS,
+ AbilityId.RESEARCH_DRILLINGCLAWS,
+ AbilityId.RESEARCH_INFERNALPREIGNITER,
+ AbilityId.RESEARCH_RAVENCORVIDREACTOR,
},
UnitTypeId.TEMPEST: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.TEMPLARARCHIVE: {AbilityId.RESEARCH_PSISTORM},
UnitTypeId.THOR: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_THORHIGHIMPACTMODE, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_THORHIGHIMPACTMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.THORAP: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_THOREXPLOSIVEMODE, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_THOREXPLOSIVEMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.TWILIGHTCOUNCIL: {
- AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES, AbilityId.RESEARCH_BLINK, AbilityId.RESEARCH_CHARGE
+ AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES,
+ AbilityId.RESEARCH_BLINK,
+ AbilityId.RESEARCH_CHARGE,
},
UnitTypeId.ULTRALISK: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_ULTRALISK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_ULTRALISK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ULTRALISKBURROWED: {AbilityId.BURROWUP_ULTRALISK},
UnitTypeId.ULTRALISKCAVERN: {AbilityId.RESEARCH_ANABOLICSYNTHESIS, AbilityId.RESEARCH_CHITINOUSPLATING},
UnitTypeId.VIKINGASSAULT: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_VIKINGFIGHTERMODE, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_VIKINGFIGHTERMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.VIKINGFIGHTER: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_VIKINGASSAULTMODE, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPH_VIKINGASSAULTMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.VIPER: {
- AbilityId.BLINDINGCLOUD_BLINDINGCLOUD, AbilityId.EFFECT_ABDUCT, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.PARASITICBOMB_PARASITICBOMB, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE,
- AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.VIPERCONSUMESTRUCTURE_VIPERCONSUME
+ AbilityId.BLINDINGCLOUD_BLINDINGCLOUD,
+ AbilityId.EFFECT_ABDUCT,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PARASITICBOMB_PARASITICBOMB,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ AbilityId.VIPERCONSUMESTRUCTURE_VIPERCONSUME,
},
UnitTypeId.VOIDMPIMMORTALREVIVECORPSE: {
- AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.VOIDMPIMMORTALREVIVEREBUILD_IMMORTAL
+ AbilityId.RALLY_BUILDING,
+ AbilityId.SMART,
+ AbilityId.VOIDMPIMMORTALREVIVEREBUILD_IMMORTAL,
},
UnitTypeId.VOIDRAY: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.WARHOUND: {
- AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.TORNADOMISSILE_TORNADOMISSILE
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ AbilityId.TORNADOMISSILE_TORNADOMISSILE,
},
UnitTypeId.WARPGATE: {
- AbilityId.MORPH_GATEWAY, AbilityId.SMART, AbilityId.TRAINWARP_ADEPT, AbilityId.WARPGATETRAIN_DARKTEMPLAR,
- AbilityId.WARPGATETRAIN_HIGHTEMPLAR, AbilityId.WARPGATETRAIN_SENTRY, AbilityId.WARPGATETRAIN_STALKER,
- AbilityId.WARPGATETRAIN_ZEALOT
+ AbilityId.MORPH_GATEWAY,
+ AbilityId.SMART,
+ AbilityId.TRAINWARP_ADEPT,
+ AbilityId.WARPGATETRAIN_DARKTEMPLAR,
+ AbilityId.WARPGATETRAIN_HIGHTEMPLAR,
+ AbilityId.WARPGATETRAIN_SENTRY,
+ AbilityId.WARPGATETRAIN_STALKER,
+ AbilityId.WARPGATETRAIN_ZEALOT,
},
UnitTypeId.WARPPRISM: {
- AbilityId.HOLDPOSITION_HOLD, AbilityId.LOAD_WARPPRISM, AbilityId.MORPH_WARPPRISMPHASINGMODE,
- AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.LOAD_WARPPRISM,
+ AbilityId.MORPH_WARPPRISMPHASINGMODE,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.WARPPRISMPHASING: {
- AbilityId.LOAD_WARPPRISM, AbilityId.MORPH_WARPPRISMTRANSPORTMODE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.LOAD_WARPPRISM,
+ AbilityId.MORPH_WARPPRISMTRANSPORTMODE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.WIDOWMINE: {
- AbilityId.BURROWDOWN_WIDOWMINE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL,
- AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.BURROWDOWN_WIDOWMINE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SCAN_MOVE,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.WIDOWMINEBURROWED: {
- AbilityId.BURROWUP_WIDOWMINE, AbilityId.SMART, AbilityId.WIDOWMINEATTACK_WIDOWMINEATTACK
+ AbilityId.BURROWUP_WIDOWMINE,
+ AbilityId.SMART,
+ AbilityId.WIDOWMINEATTACK_WIDOWMINEATTACK,
},
UnitTypeId.ZEALOT: {
- AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_CHARGE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE,
- AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.EFFECT_CHARGE,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
},
UnitTypeId.ZERGLING: {
- AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_ZERGLING, AbilityId.HOLDPOSITION_HOLD,
- AbilityId.MORPHZERGLINGTOBANELING_BANELING, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART,
- AbilityId.STOP_STOP
- },
- UnitTypeId.ZERGLINGBURROWED: {AbilityId.BURROWUP_ZERGLING}
+ AbilityId.ATTACK_ATTACK,
+ AbilityId.BURROWDOWN_ZERGLING,
+ AbilityId.HOLDPOSITION_HOLD,
+ AbilityId.MORPHTOBANELING_BANELING,
+ AbilityId.MOVE_MOVE,
+ AbilityId.PATROL_PATROL,
+ AbilityId.SMART,
+ AbilityId.STOP_STOP,
+ },
+ UnitTypeId.ZERGLINGBURROWED: {AbilityId.BURROWUP_ZERGLING},
}
diff --git a/sc2/dicts/unit_research_abilities.py b/sc2/dicts/unit_research_abilities.py
index 69524614..9faaa2e8 100644
--- a/sc2/dicts/unit_research_abilities.py
+++ b/sc2/dicts/unit_research_abilities.py
@@ -1,462 +1,344 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict, Union
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+from typing import Union
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
-
-RESEARCH_INFO: Dict[UnitTypeId, Dict[UpgradeId, Dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]] = {
+RESEARCH_INFO: dict[UnitTypeId, dict[UpgradeId, dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]] = {
UnitTypeId.ARMORY: {
- UpgradeId.TERRANSHIPWEAPONSLEVEL1: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1
- },
+ UpgradeId.TERRANSHIPWEAPONSLEVEL1: {"ability": AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1},
UpgradeId.TERRANSHIPWEAPONSLEVEL2: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2,
- 'required_upgrade': UpgradeId.TERRANSHIPWEAPONSLEVEL1
+ "ability": AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2,
+ "required_upgrade": UpgradeId.TERRANSHIPWEAPONSLEVEL1,
},
UpgradeId.TERRANSHIPWEAPONSLEVEL3: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3,
- 'required_upgrade': UpgradeId.TERRANSHIPWEAPONSLEVEL2
+ "ability": AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3,
+ "required_upgrade": UpgradeId.TERRANSHIPWEAPONSLEVEL2,
},
UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL1: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1
+ "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1
},
UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL2: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2,
- 'required_upgrade': UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL1
+ "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2,
+ "required_upgrade": UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL1,
},
UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL3: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3,
- 'required_upgrade': UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL2
- },
- UpgradeId.TERRANVEHICLEWEAPONSLEVEL1: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1
+ "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3,
+ "required_upgrade": UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL2,
},
+ UpgradeId.TERRANVEHICLEWEAPONSLEVEL1: {"ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1},
UpgradeId.TERRANVEHICLEWEAPONSLEVEL2: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2,
- 'required_upgrade': UpgradeId.TERRANVEHICLEWEAPONSLEVEL1
+ "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2,
+ "required_upgrade": UpgradeId.TERRANVEHICLEWEAPONSLEVEL1,
},
UpgradeId.TERRANVEHICLEWEAPONSLEVEL3: {
- 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3,
- 'required_upgrade': UpgradeId.TERRANVEHICLEWEAPONSLEVEL2
- }
+ "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3,
+ "required_upgrade": UpgradeId.TERRANVEHICLEWEAPONSLEVEL2,
+ },
},
UnitTypeId.BANELINGNEST: {
UpgradeId.CENTRIFICALHOOKS: {
- 'ability': AbilityId.RESEARCH_CENTRIFUGALHOOKS,
- 'required_building': UnitTypeId.LAIR
+ "ability": AbilityId.RESEARCH_CENTRIFUGALHOOKS,
+ "required_building": UnitTypeId.LAIR,
}
},
UnitTypeId.BARRACKSTECHLAB: {
- UpgradeId.PUNISHERGRENADES: {
- 'ability': AbilityId.RESEARCH_CONCUSSIVESHELLS
- },
- UpgradeId.SHIELDWALL: {
- 'ability': AbilityId.RESEARCH_COMBATSHIELD
- },
- UpgradeId.STIMPACK: {
- 'ability': AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK
- }
+ UpgradeId.PUNISHERGRENADES: {"ability": AbilityId.RESEARCH_CONCUSSIVESHELLS},
+ UpgradeId.SHIELDWALL: {"ability": AbilityId.RESEARCH_COMBATSHIELD},
+ UpgradeId.STIMPACK: {"ability": AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK},
},
UnitTypeId.CYBERNETICSCORE: {
UpgradeId.PROTOSSAIRARMORSLEVEL1: {
- 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSAIRARMORSLEVEL2: {
- 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2,
- 'required_building': UnitTypeId.FLEETBEACON,
- 'required_upgrade': UpgradeId.PROTOSSAIRARMORSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2,
+ "required_building": UnitTypeId.FLEETBEACON,
+ "required_upgrade": UpgradeId.PROTOSSAIRARMORSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSAIRARMORSLEVEL3: {
- 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3,
- 'required_building': UnitTypeId.FLEETBEACON,
- 'required_upgrade': UpgradeId.PROTOSSAIRARMORSLEVEL2,
- 'requires_power': True
+ "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3,
+ "required_building": UnitTypeId.FLEETBEACON,
+ "required_upgrade": UpgradeId.PROTOSSAIRARMORSLEVEL2,
+ "requires_power": True,
},
UpgradeId.PROTOSSAIRWEAPONSLEVEL1: {
- 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSAIRWEAPONSLEVEL2: {
- 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2,
- 'required_building': UnitTypeId.FLEETBEACON,
- 'required_upgrade': UpgradeId.PROTOSSAIRWEAPONSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2,
+ "required_building": UnitTypeId.FLEETBEACON,
+ "required_upgrade": UpgradeId.PROTOSSAIRWEAPONSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSAIRWEAPONSLEVEL3: {
- 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3,
- 'required_building': UnitTypeId.FLEETBEACON,
- 'required_upgrade': UpgradeId.PROTOSSAIRWEAPONSLEVEL2,
- 'requires_power': True
- },
- UpgradeId.WARPGATERESEARCH: {
- 'ability': AbilityId.RESEARCH_WARPGATE,
- 'requires_power': True
- }
+ "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3,
+ "required_building": UnitTypeId.FLEETBEACON,
+ "required_upgrade": UpgradeId.PROTOSSAIRWEAPONSLEVEL2,
+ "requires_power": True,
+ },
+ UpgradeId.WARPGATERESEARCH: {"ability": AbilityId.RESEARCH_WARPGATE, "requires_power": True},
},
UnitTypeId.DARKSHRINE: {
- UpgradeId.DARKTEMPLARBLINKUPGRADE: {
- 'ability': AbilityId.RESEARCH_SHADOWSTRIKE,
- 'requires_power': True
- }
+ UpgradeId.DARKTEMPLARBLINKUPGRADE: {"ability": AbilityId.RESEARCH_SHADOWSTRIKE, "requires_power": True}
},
UnitTypeId.ENGINEERINGBAY: {
- UpgradeId.HISECAUTOTRACKING: {
- 'ability': AbilityId.RESEARCH_HISECAUTOTRACKING
- },
- UpgradeId.TERRANBUILDINGARMOR: {
- 'ability': AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE
- },
- UpgradeId.TERRANINFANTRYARMORSLEVEL1: {
- 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1
- },
+ UpgradeId.HISECAUTOTRACKING: {"ability": AbilityId.RESEARCH_HISECAUTOTRACKING},
+ UpgradeId.TERRANBUILDINGARMOR: {"ability": AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE},
+ UpgradeId.TERRANINFANTRYARMORSLEVEL1: {"ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1},
UpgradeId.TERRANINFANTRYARMORSLEVEL2: {
- 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2,
- 'required_building': UnitTypeId.ARMORY,
- 'required_upgrade': UpgradeId.TERRANINFANTRYARMORSLEVEL1
+ "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2,
+ "required_building": UnitTypeId.ARMORY,
+ "required_upgrade": UpgradeId.TERRANINFANTRYARMORSLEVEL1,
},
UpgradeId.TERRANINFANTRYARMORSLEVEL3: {
- 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3,
- 'required_building': UnitTypeId.ARMORY,
- 'required_upgrade': UpgradeId.TERRANINFANTRYARMORSLEVEL2
+ "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3,
+ "required_building": UnitTypeId.ARMORY,
+ "required_upgrade": UpgradeId.TERRANINFANTRYARMORSLEVEL2,
},
UpgradeId.TERRANINFANTRYWEAPONSLEVEL1: {
- 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1
+ "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1
},
UpgradeId.TERRANINFANTRYWEAPONSLEVEL2: {
- 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
- 'required_building': UnitTypeId.ARMORY,
- 'required_upgrade': UpgradeId.TERRANINFANTRYWEAPONSLEVEL1
+ "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
+ "required_building": UnitTypeId.ARMORY,
+ "required_upgrade": UpgradeId.TERRANINFANTRYWEAPONSLEVEL1,
},
UpgradeId.TERRANINFANTRYWEAPONSLEVEL3: {
- 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
- 'required_building': UnitTypeId.ARMORY,
- 'required_upgrade': UpgradeId.TERRANINFANTRYWEAPONSLEVEL2
- }
+ "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
+ "required_building": UnitTypeId.ARMORY,
+ "required_upgrade": UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
+ },
},
UnitTypeId.EVOLUTIONCHAMBER: {
- UpgradeId.ZERGGROUNDARMORSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1
- },
+ UpgradeId.ZERGGROUNDARMORSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1},
UpgradeId.ZERGGROUNDARMORSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGGROUNDARMORSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGGROUNDARMORSLEVEL1,
},
UpgradeId.ZERGGROUNDARMORSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGGROUNDARMORSLEVEL2
- },
- UpgradeId.ZERGMELEEWEAPONSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGGROUNDARMORSLEVEL2,
},
+ UpgradeId.ZERGMELEEWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1},
UpgradeId.ZERGMELEEWEAPONSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGMELEEWEAPONSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGMELEEWEAPONSLEVEL1,
},
UpgradeId.ZERGMELEEWEAPONSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGMELEEWEAPONSLEVEL2
- },
- UpgradeId.ZERGMISSILEWEAPONSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGMELEEWEAPONSLEVEL2,
},
+ UpgradeId.ZERGMISSILEWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1},
UpgradeId.ZERGMISSILEWEAPONSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGMISSILEWEAPONSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGMISSILEWEAPONSLEVEL1,
},
UpgradeId.ZERGMISSILEWEAPONSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGMISSILEWEAPONSLEVEL2
- }
+ "ability": AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGMISSILEWEAPONSLEVEL2,
+ },
},
UnitTypeId.FACTORYTECHLAB: {
- UpgradeId.CYCLONELOCKONDAMAGEUPGRADE: {
- 'ability': AbilityId.RESEARCH_CYCLONELOCKONDAMAGE
- },
- UpgradeId.DRILLCLAWS: {
- 'ability': AbilityId.RESEARCH_DRILLINGCLAWS,
- 'required_building': UnitTypeId.ARMORY
- },
- UpgradeId.HIGHCAPACITYBARRELS: {
- 'ability': AbilityId.RESEARCH_INFERNALPREIGNITER
- },
- UpgradeId.SMARTSERVOS: {
- 'ability': AbilityId.RESEARCH_SMARTSERVOS,
- 'required_building': UnitTypeId.ARMORY
- }
+ UpgradeId.CYCLONELOCKONDAMAGEUPGRADE: {"ability": AbilityId.RESEARCH_CYCLONELOCKONDAMAGE},
+ UpgradeId.DRILLCLAWS: {"ability": AbilityId.RESEARCH_DRILLINGCLAWS, "required_building": UnitTypeId.ARMORY},
+ UpgradeId.HIGHCAPACITYBARRELS: {"ability": AbilityId.RESEARCH_INFERNALPREIGNITER},
+ UpgradeId.SMARTSERVOS: {"ability": AbilityId.RESEARCH_SMARTSERVOS, "required_building": UnitTypeId.ARMORY},
},
UnitTypeId.FLEETBEACON: {
UpgradeId.PHOENIXRANGEUPGRADE: {
- 'ability': AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS,
- 'requires_power': True
+ "ability": AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS,
+ "requires_power": True,
},
UpgradeId.TEMPESTGROUNDATTACKUPGRADE: {
- 'ability': AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE,
- 'requires_power': True
+ "ability": AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE,
+ "requires_power": True,
},
UpgradeId.VOIDRAYSPEEDUPGRADE: {
- 'ability': AbilityId.FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE,
- 'requires_power': True
- }
+ "ability": AbilityId.FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE,
+ "requires_power": True,
+ },
},
UnitTypeId.FORGE: {
UpgradeId.PROTOSSGROUNDARMORSLEVEL1: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSGROUNDARMORSLEVEL2: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'required_upgrade': UpgradeId.PROTOSSGROUNDARMORSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "required_upgrade": UpgradeId.PROTOSSGROUNDARMORSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSGROUNDARMORSLEVEL3: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'required_upgrade': UpgradeId.PROTOSSGROUNDARMORSLEVEL2,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "required_upgrade": UpgradeId.PROTOSSGROUNDARMORSLEVEL2,
+ "requires_power": True,
},
UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'required_upgrade': UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "required_upgrade": UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSGROUNDWEAPONSLEVEL3: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'required_upgrade': UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "required_upgrade": UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2,
+ "requires_power": True,
},
UpgradeId.PROTOSSSHIELDSLEVEL1: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSSHIELDSLEVEL2: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'required_upgrade': UpgradeId.PROTOSSSHIELDSLEVEL1,
- 'requires_power': True
+ "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "required_upgrade": UpgradeId.PROTOSSSHIELDSLEVEL1,
+ "requires_power": True,
},
UpgradeId.PROTOSSSHIELDSLEVEL3: {
- 'ability': AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'required_upgrade': UpgradeId.PROTOSSSHIELDSLEVEL2,
- 'requires_power': True
- }
- },
- UnitTypeId.FUSIONCORE: {
- UpgradeId.BATTLECRUISERENABLESPECIALIZATIONS: {
- 'ability': AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT
+ "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "required_upgrade": UpgradeId.PROTOSSSHIELDSLEVEL2,
+ "requires_power": True,
},
- UpgradeId.LIBERATORAGRANGEUPGRADE: {
- 'ability': AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE
- },
- UpgradeId.MEDIVACINCREASESPEEDBOOST: {
- 'ability': AbilityId.FUSIONCORERESEARCH_RESEARCHRAPIDREIGNITIONSYSTEM
- }
},
- UnitTypeId.GHOSTACADEMY: {
- UpgradeId.ENHANCEDSHOCKWAVES: {
- 'ability': AbilityId.GHOSTACADEMYRESEARCH_RESEARCHENHANCEDSHOCKWAVES
- },
- UpgradeId.PERSONALCLOAKING: {
- 'ability': AbilityId.RESEARCH_PERSONALCLOAKING
- }
+ UnitTypeId.FUSIONCORE: {
+ UpgradeId.BATTLECRUISERENABLESPECIALIZATIONS: {"ability": AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT},
+ UpgradeId.LIBERATORAGRANGEUPGRADE: {"ability": AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE},
+ UpgradeId.MEDIVACCADUCEUSREACTOR: {"ability": AbilityId.FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE},
},
+ UnitTypeId.GHOSTACADEMY: {UpgradeId.PERSONALCLOAKING: {"ability": AbilityId.RESEARCH_PERSONALCLOAKING}},
UnitTypeId.GREATERSPIRE: {
- UpgradeId.ZERGFLYERARMORSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1
- },
+ UpgradeId.ZERGFLYERARMORSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1},
UpgradeId.ZERGFLYERARMORSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL1,
},
UpgradeId.ZERGFLYERARMORSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL2
- },
- UpgradeId.ZERGFLYERWEAPONSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL2,
},
+ UpgradeId.ZERGFLYERWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1},
UpgradeId.ZERGFLYERWEAPONSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL1,
},
UpgradeId.ZERGFLYERWEAPONSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL2
- }
+ "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL2,
+ },
},
UnitTypeId.HATCHERY: {
- UpgradeId.BURROW: {
- 'ability': AbilityId.RESEARCH_BURROW
- },
- UpgradeId.OVERLORDSPEED: {
- 'ability': AbilityId.RESEARCH_PNEUMATIZEDCARAPACE
- }
+ UpgradeId.BURROW: {"ability": AbilityId.RESEARCH_BURROW},
+ UpgradeId.OVERLORDSPEED: {"ability": AbilityId.RESEARCH_PNEUMATIZEDCARAPACE},
},
UnitTypeId.HIVE: {
- UpgradeId.BURROW: {
- 'ability': AbilityId.RESEARCH_BURROW
- },
- UpgradeId.OVERLORDSPEED: {
- 'ability': AbilityId.RESEARCH_PNEUMATIZEDCARAPACE
- }
+ UpgradeId.BURROW: {"ability": AbilityId.RESEARCH_BURROW},
+ UpgradeId.OVERLORDSPEED: {"ability": AbilityId.RESEARCH_PNEUMATIZEDCARAPACE},
},
UnitTypeId.HYDRALISKDEN: {
- UpgradeId.EVOLVEGROOVEDSPINES: {
- 'ability': AbilityId.RESEARCH_GROOVEDSPINES
- },
- UpgradeId.EVOLVEMUSCULARAUGMENTS: {
- 'ability': AbilityId.RESEARCH_MUSCULARAUGMENTS
- }
- },
- UnitTypeId.INFESTATIONPIT: {
- UpgradeId.INFESTORENERGYUPGRADE: {
- 'ability': AbilityId.RESEARCH_PATHOGENGLANDS
+ UpgradeId.EVOLVEGROOVEDSPINES: {"ability": AbilityId.RESEARCH_GROOVEDSPINES},
+ UpgradeId.EVOLVEMUSCULARAUGMENTS: {"ability": AbilityId.RESEARCH_MUSCULARAUGMENTS},
+ UpgradeId.FRENZY: {
+ "ability": AbilityId.HYDRALISKDENRESEARCH_RESEARCHFRENZY,
+ "required_building": UnitTypeId.HIVE,
},
- UpgradeId.NEURALPARASITE: {
- 'ability': AbilityId.RESEARCH_NEURALPARASITE
- }
},
+ UnitTypeId.INFESTATIONPIT: {UpgradeId.NEURALPARASITE: {"ability": AbilityId.RESEARCH_NEURALPARASITE}},
UnitTypeId.LAIR: {
- UpgradeId.BURROW: {
- 'ability': AbilityId.RESEARCH_BURROW
- },
- UpgradeId.OVERLORDSPEED: {
- 'ability': AbilityId.RESEARCH_PNEUMATIZEDCARAPACE
- }
+ UpgradeId.BURROW: {"ability": AbilityId.RESEARCH_BURROW},
+ UpgradeId.OVERLORDSPEED: {"ability": AbilityId.RESEARCH_PNEUMATIZEDCARAPACE},
},
UnitTypeId.LURKERDENMP: {
- UpgradeId.DIGGINGCLAWS: {
- 'ability': AbilityId.RESEARCH_ADAPTIVETALONS,
- 'required_building': UnitTypeId.HIVE
- },
+ UpgradeId.DIGGINGCLAWS: {"ability": AbilityId.RESEARCH_ADAPTIVETALONS, "required_building": UnitTypeId.HIVE},
UpgradeId.LURKERRANGE: {
- 'ability': AbilityId.LURKERDENRESEARCH_RESEARCHLURKERRANGE,
- 'required_building': UnitTypeId.HIVE
- }
+ "ability": AbilityId.LURKERDENRESEARCH_RESEARCHLURKERRANGE,
+ "required_building": UnitTypeId.HIVE,
+ },
},
UnitTypeId.ROACHWARREN: {
UpgradeId.GLIALRECONSTITUTION: {
- 'ability': AbilityId.RESEARCH_GLIALREGENERATION,
- 'required_building': UnitTypeId.LAIR
+ "ability": AbilityId.RESEARCH_GLIALREGENERATION,
+ "required_building": UnitTypeId.LAIR,
},
- UpgradeId.TUNNELINGCLAWS: {
- 'ability': AbilityId.RESEARCH_TUNNELINGCLAWS,
- 'required_building': UnitTypeId.LAIR
- }
+ UpgradeId.TUNNELINGCLAWS: {"ability": AbilityId.RESEARCH_TUNNELINGCLAWS, "required_building": UnitTypeId.LAIR},
},
UnitTypeId.ROBOTICSBAY: {
- UpgradeId.EXTENDEDTHERMALLANCE: {
- 'ability': AbilityId.RESEARCH_EXTENDEDTHERMALLANCE,
- 'requires_power': True
- },
- UpgradeId.GRAVITICDRIVE: {
- 'ability': AbilityId.RESEARCH_GRAVITICDRIVE,
- 'requires_power': True
- },
- UpgradeId.OBSERVERGRAVITICBOOSTER: {
- 'ability': AbilityId.RESEARCH_GRAVITICBOOSTER,
- 'requires_power': True
- }
+ UpgradeId.EXTENDEDTHERMALLANCE: {"ability": AbilityId.RESEARCH_EXTENDEDTHERMALLANCE, "requires_power": True},
+ UpgradeId.GRAVITICDRIVE: {"ability": AbilityId.RESEARCH_GRAVITICDRIVE, "requires_power": True},
+ UpgradeId.OBSERVERGRAVITICBOOSTER: {"ability": AbilityId.RESEARCH_GRAVITICBOOSTER, "requires_power": True},
},
UnitTypeId.SPAWNINGPOOL: {
UpgradeId.ZERGLINGATTACKSPEED: {
- 'ability': AbilityId.RESEARCH_ZERGLINGADRENALGLANDS,
- 'required_building': UnitTypeId.HIVE
+ "ability": AbilityId.RESEARCH_ZERGLINGADRENALGLANDS,
+ "required_building": UnitTypeId.HIVE,
},
- UpgradeId.ZERGLINGMOVEMENTSPEED: {
- 'ability': AbilityId.RESEARCH_ZERGLINGMETABOLICBOOST
- }
+ UpgradeId.ZERGLINGMOVEMENTSPEED: {"ability": AbilityId.RESEARCH_ZERGLINGMETABOLICBOOST},
},
UnitTypeId.SPIRE: {
- UpgradeId.ZERGFLYERARMORSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1
- },
+ UpgradeId.ZERGFLYERARMORSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1},
UpgradeId.ZERGFLYERARMORSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL1,
},
UpgradeId.ZERGFLYERARMORSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL2
- },
- UpgradeId.ZERGFLYERWEAPONSLEVEL1: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL2,
},
+ UpgradeId.ZERGFLYERWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1},
UpgradeId.ZERGFLYERWEAPONSLEVEL2: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
- 'required_building': UnitTypeId.LAIR,
- 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL1
+ "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
+ "required_building": UnitTypeId.LAIR,
+ "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL1,
},
UpgradeId.ZERGFLYERWEAPONSLEVEL3: {
- 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
- 'required_building': UnitTypeId.HIVE,
- 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL2
- }
+ "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
+ "required_building": UnitTypeId.HIVE,
+ "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL2,
+ },
},
UnitTypeId.STARPORTTECHLAB: {
- UpgradeId.BANSHEECLOAK: {
- 'ability': AbilityId.RESEARCH_BANSHEECLOAKINGFIELD
- },
- UpgradeId.BANSHEESPEED: {
- 'ability': AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS
- },
- UpgradeId.RAVENCORVIDREACTOR: {
- 'ability': AbilityId.RESEARCH_RAVENCORVIDREACTOR
- }
+ UpgradeId.BANSHEECLOAK: {"ability": AbilityId.RESEARCH_BANSHEECLOAKINGFIELD},
+ UpgradeId.BANSHEESPEED: {"ability": AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS},
+ UpgradeId.INTERFERENCEMATRIX: {"ability": AbilityId.STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX},
},
UnitTypeId.TEMPLARARCHIVE: {
- UpgradeId.PSISTORMTECH: {
- 'ability': AbilityId.RESEARCH_PSISTORM,
- 'requires_power': True
- }
+ UpgradeId.PSISTORMTECH: {"ability": AbilityId.RESEARCH_PSISTORM, "requires_power": True}
},
UnitTypeId.TWILIGHTCOUNCIL: {
- UpgradeId.ADEPTPIERCINGATTACK: {
- 'ability': AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES,
- 'requires_power': True
- },
- UpgradeId.BLINKTECH: {
- 'ability': AbilityId.RESEARCH_BLINK,
- 'requires_power': True
- },
- UpgradeId.CHARGE: {
- 'ability': AbilityId.RESEARCH_CHARGE,
- 'requires_power': True
- }
+ UpgradeId.ADEPTPIERCINGATTACK: {"ability": AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES, "requires_power": True},
+ UpgradeId.BLINKTECH: {"ability": AbilityId.RESEARCH_BLINK, "requires_power": True},
+ UpgradeId.CHARGE: {"ability": AbilityId.RESEARCH_CHARGE, "requires_power": True},
},
UnitTypeId.ULTRALISKCAVERN: {
- UpgradeId.ANABOLICSYNTHESIS: {
- 'ability': AbilityId.RESEARCH_ANABOLICSYNTHESIS
- },
- UpgradeId.CHITINOUSPLATING: {
- 'ability': AbilityId.RESEARCH_CHITINOUSPLATING
- }
- }
+ UpgradeId.ANABOLICSYNTHESIS: {"ability": AbilityId.RESEARCH_ANABOLICSYNTHESIS},
+ UpgradeId.CHITINOUSPLATING: {"ability": AbilityId.RESEARCH_CHITINOUSPLATING},
+ },
}
diff --git a/sc2/dicts/unit_tech_alias.py b/sc2/dicts/unit_tech_alias.py
index dd6a753f..0a0daa65 100644
--- a/sc2/dicts/unit_tech_alias.py
+++ b/sc2/dicts/unit_tech_alias.py
@@ -1,14 +1,13 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict, Set
-
from sc2.ids.unit_typeid import UnitTypeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+
-UNIT_TECH_ALIAS: Dict[UnitTypeId, Set[UnitTypeId]] = {
+UNIT_TECH_ALIAS: dict[UnitTypeId, set[UnitTypeId]] = {
UnitTypeId.BARRACKSFLYING: {UnitTypeId.BARRACKS},
UnitTypeId.BARRACKSREACTOR: {UnitTypeId.REACTOR},
UnitTypeId.BARRACKSTECHLAB: {UnitTypeId.TECHLAB},
@@ -40,5 +39,5 @@
UnitTypeId.VIKINGFIGHTER: {UnitTypeId.VIKING},
UnitTypeId.WARPGATE: {UnitTypeId.GATEWAY},
UnitTypeId.WARPPRISMPHASING: {UnitTypeId.WARPPRISM},
- UnitTypeId.WIDOWMINEBURROWED: {UnitTypeId.WIDOWMINE}
+ UnitTypeId.WIDOWMINEBURROWED: {UnitTypeId.WIDOWMINE},
}
diff --git a/sc2/dicts/unit_train_build_abilities.py b/sc2/dicts/unit_train_build_abilities.py
index 64d11fd2..187dbc91 100644
--- a/sc2/dicts/unit_train_build_abilities.py
+++ b/sc2/dicts/unit_train_build_abilities.py
@@ -1,613 +1,427 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict, Union
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+from typing import Union
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
-
-TRAIN_INFO: Dict[UnitTypeId, Dict[UnitTypeId, Dict[str, Union[AbilityId, bool, UnitTypeId]]]] = {
+TRAIN_INFO: dict[UnitTypeId, dict[UnitTypeId, dict[str, Union[AbilityId, bool, UnitTypeId]]]] = {
UnitTypeId.BARRACKS: {
UnitTypeId.GHOST: {
- 'ability': AbilityId.BARRACKSTRAIN_GHOST,
- 'requires_techlab': True,
- 'required_building': UnitTypeId.GHOSTACADEMY
- },
- UnitTypeId.MARAUDER: {
- 'ability': AbilityId.BARRACKSTRAIN_MARAUDER,
- 'requires_techlab': True
- },
- UnitTypeId.MARINE: {
- 'ability': AbilityId.BARRACKSTRAIN_MARINE
+ "ability": AbilityId.BARRACKSTRAIN_GHOST,
+ "requires_techlab": True,
+ "required_building": UnitTypeId.GHOSTACADEMY,
},
- UnitTypeId.REAPER: {
- 'ability': AbilityId.BARRACKSTRAIN_REAPER
- }
+ UnitTypeId.MARAUDER: {"ability": AbilityId.BARRACKSTRAIN_MARAUDER, "requires_techlab": True},
+ UnitTypeId.MARINE: {"ability": AbilityId.BARRACKSTRAIN_MARINE},
+ UnitTypeId.REAPER: {"ability": AbilityId.BARRACKSTRAIN_REAPER},
},
UnitTypeId.COMMANDCENTER: {
UnitTypeId.ORBITALCOMMAND: {
- 'ability': AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND,
- 'required_building': UnitTypeId.BARRACKS
+ "ability": AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND,
+ "required_building": UnitTypeId.BARRACKS,
},
UnitTypeId.PLANETARYFORTRESS: {
- 'ability': AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS,
- 'required_building': UnitTypeId.ENGINEERINGBAY
+ "ability": AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS,
+ "required_building": UnitTypeId.ENGINEERINGBAY,
},
- UnitTypeId.SCV: {
- 'ability': AbilityId.COMMANDCENTERTRAIN_SCV
- }
+ UnitTypeId.SCV: {"ability": AbilityId.COMMANDCENTERTRAIN_SCV},
},
UnitTypeId.CORRUPTOR: {
UnitTypeId.BROODLORD: {
- 'ability': AbilityId.MORPHTOBROODLORD_BROODLORD,
- 'required_building': UnitTypeId.GREATERSPIRE
+ "ability": AbilityId.MORPHTOBROODLORD_BROODLORD,
+ "required_building": UnitTypeId.GREATERSPIRE,
}
},
UnitTypeId.CREEPTUMOR: {
- UnitTypeId.CREEPTUMOR: {
- 'ability': AbilityId.BUILD_CREEPTUMOR_TUMOR,
- 'requires_placement_position': True
- }
+ UnitTypeId.CREEPTUMOR: {"ability": AbilityId.BUILD_CREEPTUMOR_TUMOR, "requires_placement_position": True}
},
UnitTypeId.CREEPTUMORBURROWED: {
- UnitTypeId.CREEPTUMOR: {
- 'ability': AbilityId.BUILD_CREEPTUMOR,
- 'requires_placement_position': True
- }
- },
- UnitTypeId.CREEPTUMORQUEEN: {
- UnitTypeId.CREEPTUMOR: {
- 'ability': AbilityId.BUILD_CREEPTUMOR_TUMOR,
- 'requires_placement_position': True
- }
+ UnitTypeId.CREEPTUMOR: {"ability": AbilityId.BUILD_CREEPTUMOR, "requires_placement_position": True}
},
UnitTypeId.DRONE: {
UnitTypeId.BANELINGNEST: {
- 'ability': AbilityId.ZERGBUILD_BANELINGNEST,
- 'required_building': UnitTypeId.SPAWNINGPOOL,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_BANELINGNEST,
+ "required_building": UnitTypeId.SPAWNINGPOOL,
+ "requires_placement_position": True,
},
UnitTypeId.EVOLUTIONCHAMBER: {
- 'ability': AbilityId.ZERGBUILD_EVOLUTIONCHAMBER,
- 'required_building': UnitTypeId.HATCHERY,
- 'requires_placement_position': True
- },
- UnitTypeId.EXTRACTOR: {
- 'ability': AbilityId.ZERGBUILD_EXTRACTOR
- },
- UnitTypeId.HATCHERY: {
- 'ability': AbilityId.ZERGBUILD_HATCHERY,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_EVOLUTIONCHAMBER,
+ "required_building": UnitTypeId.HATCHERY,
+ "requires_placement_position": True,
},
+ UnitTypeId.EXTRACTOR: {"ability": AbilityId.ZERGBUILD_EXTRACTOR},
+ UnitTypeId.HATCHERY: {"ability": AbilityId.ZERGBUILD_HATCHERY, "requires_placement_position": True},
UnitTypeId.HYDRALISKDEN: {
- 'ability': AbilityId.ZERGBUILD_HYDRALISKDEN,
- 'required_building': UnitTypeId.LAIR,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_HYDRALISKDEN,
+ "required_building": UnitTypeId.LAIR,
+ "requires_placement_position": True,
},
UnitTypeId.INFESTATIONPIT: {
- 'ability': AbilityId.ZERGBUILD_INFESTATIONPIT,
- 'required_building': UnitTypeId.LAIR,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_INFESTATIONPIT,
+ "required_building": UnitTypeId.LAIR,
+ "requires_placement_position": True,
},
UnitTypeId.LURKERDENMP: {
- 'ability': AbilityId.BUILD_LURKERDEN,
- 'required_building': UnitTypeId.HYDRALISKDEN,
- 'requires_placement_position': True
+ "ability": AbilityId.BUILD_LURKERDEN,
+ "required_building": UnitTypeId.HYDRALISKDEN,
+ "requires_placement_position": True,
},
UnitTypeId.NYDUSNETWORK: {
- 'ability': AbilityId.ZERGBUILD_NYDUSNETWORK,
- 'required_building': UnitTypeId.LAIR,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_NYDUSNETWORK,
+ "required_building": UnitTypeId.LAIR,
+ "requires_placement_position": True,
},
UnitTypeId.ROACHWARREN: {
- 'ability': AbilityId.ZERGBUILD_ROACHWARREN,
- 'required_building': UnitTypeId.SPAWNINGPOOL,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_ROACHWARREN,
+ "required_building": UnitTypeId.SPAWNINGPOOL,
+ "requires_placement_position": True,
},
UnitTypeId.SPAWNINGPOOL: {
- 'ability': AbilityId.ZERGBUILD_SPAWNINGPOOL,
- 'required_building': UnitTypeId.HATCHERY,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_SPAWNINGPOOL,
+ "required_building": UnitTypeId.HATCHERY,
+ "requires_placement_position": True,
},
UnitTypeId.SPINECRAWLER: {
- 'ability': AbilityId.ZERGBUILD_SPINECRAWLER,
- 'required_building': UnitTypeId.SPAWNINGPOOL,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_SPINECRAWLER,
+ "required_building": UnitTypeId.SPAWNINGPOOL,
+ "requires_placement_position": True,
},
UnitTypeId.SPIRE: {
- 'ability': AbilityId.ZERGBUILD_SPIRE,
- 'required_building': UnitTypeId.LAIR,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_SPIRE,
+ "required_building": UnitTypeId.LAIR,
+ "requires_placement_position": True,
},
UnitTypeId.SPORECRAWLER: {
- 'ability': AbilityId.ZERGBUILD_SPORECRAWLER,
- 'required_building': UnitTypeId.SPAWNINGPOOL,
- 'requires_placement_position': True
+ "ability": AbilityId.ZERGBUILD_SPORECRAWLER,
+ "required_building": UnitTypeId.SPAWNINGPOOL,
+ "requires_placement_position": True,
},
UnitTypeId.ULTRALISKCAVERN: {
- 'ability': AbilityId.ZERGBUILD_ULTRALISKCAVERN,
- 'required_building': UnitTypeId.HIVE,
- 'requires_placement_position': True
- }
+ "ability": AbilityId.ZERGBUILD_ULTRALISKCAVERN,
+ "required_building": UnitTypeId.HIVE,
+ "requires_placement_position": True,
+ },
},
UnitTypeId.FACTORY: {
- UnitTypeId.CYCLONE: {
- 'ability': AbilityId.TRAIN_CYCLONE,
- 'requires_techlab': True
- },
- UnitTypeId.HELLION: {
- 'ability': AbilityId.FACTORYTRAIN_HELLION
- },
- UnitTypeId.HELLIONTANK: {
- 'ability': AbilityId.TRAIN_HELLBAT,
- 'required_building': UnitTypeId.ARMORY
- },
- UnitTypeId.SIEGETANK: {
- 'ability': AbilityId.FACTORYTRAIN_SIEGETANK,
- 'requires_techlab': True
- },
+ UnitTypeId.CYCLONE: {"ability": AbilityId.TRAIN_CYCLONE, "requires_techlab": True},
+ UnitTypeId.HELLION: {"ability": AbilityId.FACTORYTRAIN_HELLION},
+ UnitTypeId.HELLIONTANK: {"ability": AbilityId.TRAIN_HELLBAT, "required_building": UnitTypeId.ARMORY},
+ UnitTypeId.SIEGETANK: {"ability": AbilityId.FACTORYTRAIN_SIEGETANK, "requires_techlab": True},
UnitTypeId.THOR: {
- 'ability': AbilityId.FACTORYTRAIN_THOR,
- 'requires_techlab': True,
- 'required_building': UnitTypeId.ARMORY
+ "ability": AbilityId.FACTORYTRAIN_THOR,
+ "requires_techlab": True,
+ "required_building": UnitTypeId.ARMORY,
},
- UnitTypeId.WIDOWMINE: {
- 'ability': AbilityId.FACTORYTRAIN_WIDOWMINE
- }
+ UnitTypeId.WIDOWMINE: {"ability": AbilityId.FACTORYTRAIN_WIDOWMINE},
},
UnitTypeId.GATEWAY: {
UnitTypeId.ADEPT: {
- 'ability': AbilityId.TRAIN_ADEPT,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_power': True
+ "ability": AbilityId.TRAIN_ADEPT,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_power": True,
},
UnitTypeId.DARKTEMPLAR: {
- 'ability': AbilityId.GATEWAYTRAIN_DARKTEMPLAR,
- 'required_building': UnitTypeId.DARKSHRINE,
- 'requires_power': True
+ "ability": AbilityId.GATEWAYTRAIN_DARKTEMPLAR,
+ "required_building": UnitTypeId.DARKSHRINE,
+ "requires_power": True,
},
UnitTypeId.HIGHTEMPLAR: {
- 'ability': AbilityId.GATEWAYTRAIN_HIGHTEMPLAR,
- 'required_building': UnitTypeId.TEMPLARARCHIVE,
- 'requires_power': True
+ "ability": AbilityId.GATEWAYTRAIN_HIGHTEMPLAR,
+ "required_building": UnitTypeId.TEMPLARARCHIVE,
+ "requires_power": True,
},
UnitTypeId.SENTRY: {
- 'ability': AbilityId.GATEWAYTRAIN_SENTRY,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_power': True
+ "ability": AbilityId.GATEWAYTRAIN_SENTRY,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_power": True,
},
UnitTypeId.STALKER: {
- 'ability': AbilityId.GATEWAYTRAIN_STALKER,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_power': True
+ "ability": AbilityId.GATEWAYTRAIN_STALKER,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_power": True,
},
- UnitTypeId.ZEALOT: {
- 'ability': AbilityId.GATEWAYTRAIN_ZEALOT,
- 'requires_power': True
- }
+ UnitTypeId.ZEALOT: {"ability": AbilityId.GATEWAYTRAIN_ZEALOT, "requires_power": True},
},
UnitTypeId.HATCHERY: {
- UnitTypeId.LAIR: {
- 'ability': AbilityId.UPGRADETOLAIR_LAIR,
- 'required_building': UnitTypeId.SPAWNINGPOOL
- },
- UnitTypeId.QUEEN: {
- 'ability': AbilityId.TRAINQUEEN_QUEEN,
- 'required_building': UnitTypeId.SPAWNINGPOOL
- }
+ UnitTypeId.LAIR: {"ability": AbilityId.UPGRADETOLAIR_LAIR, "required_building": UnitTypeId.SPAWNINGPOOL},
+ UnitTypeId.QUEEN: {"ability": AbilityId.TRAINQUEEN_QUEEN, "required_building": UnitTypeId.SPAWNINGPOOL},
},
UnitTypeId.HIVE: {
- UnitTypeId.QUEEN: {
- 'ability': AbilityId.TRAINQUEEN_QUEEN,
- 'required_building': UnitTypeId.SPAWNINGPOOL
- }
+ UnitTypeId.QUEEN: {"ability": AbilityId.TRAINQUEEN_QUEEN, "required_building": UnitTypeId.SPAWNINGPOOL}
},
UnitTypeId.HYDRALISK: {
- UnitTypeId.LURKERMP: {
- 'ability': AbilityId.MORPH_LURKER,
- 'required_building': UnitTypeId.LURKERDENMP
- }
+ UnitTypeId.LURKERMP: {"ability": AbilityId.MORPH_LURKER, "required_building": UnitTypeId.LURKERDENMP}
},
UnitTypeId.LAIR: {
- UnitTypeId.HIVE: {
- 'ability': AbilityId.UPGRADETOHIVE_HIVE,
- 'required_building': UnitTypeId.INFESTATIONPIT
- },
- UnitTypeId.QUEEN: {
- 'ability': AbilityId.TRAINQUEEN_QUEEN,
- 'required_building': UnitTypeId.SPAWNINGPOOL
- }
+ UnitTypeId.HIVE: {"ability": AbilityId.UPGRADETOHIVE_HIVE, "required_building": UnitTypeId.INFESTATIONPIT},
+ UnitTypeId.QUEEN: {"ability": AbilityId.TRAINQUEEN_QUEEN, "required_building": UnitTypeId.SPAWNINGPOOL},
},
UnitTypeId.LARVA: {
- UnitTypeId.CORRUPTOR: {
- 'ability': AbilityId.LARVATRAIN_CORRUPTOR,
- 'required_building': UnitTypeId.SPIRE
- },
- UnitTypeId.DRONE: {
- 'ability': AbilityId.LARVATRAIN_DRONE
- },
- UnitTypeId.HYDRALISK: {
- 'ability': AbilityId.LARVATRAIN_HYDRALISK,
- 'required_building': UnitTypeId.HYDRALISKDEN
- },
- UnitTypeId.INFESTOR: {
- 'ability': AbilityId.LARVATRAIN_INFESTOR,
- 'required_building': UnitTypeId.INFESTATIONPIT
- },
- UnitTypeId.MUTALISK: {
- 'ability': AbilityId.LARVATRAIN_MUTALISK,
- 'required_building': UnitTypeId.SPIRE
- },
- UnitTypeId.OVERLORD: {
- 'ability': AbilityId.LARVATRAIN_OVERLORD
- },
- UnitTypeId.ROACH: {
- 'ability': AbilityId.LARVATRAIN_ROACH,
- 'required_building': UnitTypeId.ROACHWARREN
- },
- UnitTypeId.SWARMHOSTMP: {
- 'ability': AbilityId.TRAIN_SWARMHOST,
- 'required_building': UnitTypeId.INFESTATIONPIT
- },
+ UnitTypeId.CORRUPTOR: {"ability": AbilityId.LARVATRAIN_CORRUPTOR, "required_building": UnitTypeId.SPIRE},
+ UnitTypeId.DRONE: {"ability": AbilityId.LARVATRAIN_DRONE},
+ UnitTypeId.HYDRALISK: {"ability": AbilityId.LARVATRAIN_HYDRALISK, "required_building": UnitTypeId.HYDRALISKDEN},
+ UnitTypeId.INFESTOR: {"ability": AbilityId.LARVATRAIN_INFESTOR, "required_building": UnitTypeId.INFESTATIONPIT},
+ UnitTypeId.MUTALISK: {"ability": AbilityId.LARVATRAIN_MUTALISK, "required_building": UnitTypeId.SPIRE},
+ UnitTypeId.OVERLORD: {"ability": AbilityId.LARVATRAIN_OVERLORD},
+ UnitTypeId.ROACH: {"ability": AbilityId.LARVATRAIN_ROACH, "required_building": UnitTypeId.ROACHWARREN},
+ UnitTypeId.SWARMHOSTMP: {"ability": AbilityId.TRAIN_SWARMHOST, "required_building": UnitTypeId.INFESTATIONPIT},
UnitTypeId.ULTRALISK: {
- 'ability': AbilityId.LARVATRAIN_ULTRALISK,
- 'required_building': UnitTypeId.ULTRALISKCAVERN
- },
- UnitTypeId.VIPER: {
- 'ability': AbilityId.LARVATRAIN_VIPER,
- 'required_building': UnitTypeId.HIVE
+ "ability": AbilityId.LARVATRAIN_ULTRALISK,
+ "required_building": UnitTypeId.ULTRALISKCAVERN,
},
- UnitTypeId.ZERGLING: {
- 'ability': AbilityId.LARVATRAIN_ZERGLING,
- 'required_building': UnitTypeId.SPAWNINGPOOL
- }
+ UnitTypeId.VIPER: {"ability": AbilityId.LARVATRAIN_VIPER, "required_building": UnitTypeId.HIVE},
+ UnitTypeId.ZERGLING: {"ability": AbilityId.LARVATRAIN_ZERGLING, "required_building": UnitTypeId.SPAWNINGPOOL},
},
UnitTypeId.NEXUS: {
UnitTypeId.MOTHERSHIP: {
- 'ability': AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP,
- 'required_building': UnitTypeId.FLEETBEACON
+ "ability": AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP,
+ "required_building": UnitTypeId.FLEETBEACON,
},
- UnitTypeId.PROBE: {
- 'ability': AbilityId.NEXUSTRAIN_PROBE
- }
+ UnitTypeId.PROBE: {"ability": AbilityId.NEXUSTRAIN_PROBE},
},
UnitTypeId.NYDUSNETWORK: {
- UnitTypeId.NYDUSCANAL: {
- 'ability': AbilityId.BUILD_NYDUSWORM,
- 'requires_placement_position': True
- }
+ UnitTypeId.NYDUSCANAL: {"ability": AbilityId.BUILD_NYDUSWORM, "requires_placement_position": True}
},
UnitTypeId.ORACLE: {
- UnitTypeId.ORACLESTASISTRAP: {
- 'ability': AbilityId.BUILD_STASISTRAP,
- 'requires_placement_position': True
- }
- },
- UnitTypeId.ORBITALCOMMAND: {
- UnitTypeId.SCV: {
- 'ability': AbilityId.COMMANDCENTERTRAIN_SCV
- }
+ UnitTypeId.ORACLESTASISTRAP: {"ability": AbilityId.BUILD_STASISTRAP, "requires_placement_position": True}
},
+ UnitTypeId.ORBITALCOMMAND: {UnitTypeId.SCV: {"ability": AbilityId.COMMANDCENTERTRAIN_SCV}},
UnitTypeId.OVERLORD: {
UnitTypeId.OVERLORDTRANSPORT: {
- 'ability': AbilityId.MORPH_OVERLORDTRANSPORT,
- 'required_building': UnitTypeId.LAIR
+ "ability": AbilityId.MORPH_OVERLORDTRANSPORT,
+ "required_building": UnitTypeId.LAIR,
},
- UnitTypeId.OVERSEER: {
- 'ability': AbilityId.MORPH_OVERSEER,
- 'required_building': UnitTypeId.LAIR
- }
+ UnitTypeId.OVERSEER: {"ability": AbilityId.MORPH_OVERSEER, "required_building": UnitTypeId.LAIR},
},
UnitTypeId.OVERLORDTRANSPORT: {
- UnitTypeId.OVERSEER: {
- 'ability': AbilityId.MORPH_OVERSEER,
- 'required_building': UnitTypeId.LAIR
- }
- },
- UnitTypeId.OVERSEER: {
- UnitTypeId.CHANGELING: {
- 'ability': AbilityId.SPAWNCHANGELING_SPAWNCHANGELING
- }
- },
- UnitTypeId.OVERSEERSIEGEMODE: {
- UnitTypeId.CHANGELING: {
- 'ability': AbilityId.SPAWNCHANGELING_SPAWNCHANGELING
- }
- },
- UnitTypeId.PLANETARYFORTRESS: {
- UnitTypeId.SCV: {
- 'ability': AbilityId.COMMANDCENTERTRAIN_SCV
- }
+ UnitTypeId.OVERSEER: {"ability": AbilityId.MORPH_OVERSEER, "required_building": UnitTypeId.LAIR}
},
+ UnitTypeId.OVERSEER: {UnitTypeId.CHANGELING: {"ability": AbilityId.SPAWNCHANGELING_SPAWNCHANGELING}},
+ UnitTypeId.OVERSEERSIEGEMODE: {UnitTypeId.CHANGELING: {"ability": AbilityId.SPAWNCHANGELING_SPAWNCHANGELING}},
+ UnitTypeId.PLANETARYFORTRESS: {UnitTypeId.SCV: {"ability": AbilityId.COMMANDCENTERTRAIN_SCV}},
UnitTypeId.PROBE: {
- UnitTypeId.ASSIMILATOR: {
- 'ability': AbilityId.PROTOSSBUILD_ASSIMILATOR
- },
+ UnitTypeId.ASSIMILATOR: {"ability": AbilityId.PROTOSSBUILD_ASSIMILATOR},
UnitTypeId.CYBERNETICSCORE: {
- 'ability': AbilityId.PROTOSSBUILD_CYBERNETICSCORE,
- 'required_building': UnitTypeId.GATEWAY,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_CYBERNETICSCORE,
+ "required_building": UnitTypeId.GATEWAY,
+ "requires_placement_position": True,
},
UnitTypeId.DARKSHRINE: {
- 'ability': AbilityId.PROTOSSBUILD_DARKSHRINE,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_DARKSHRINE,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "requires_placement_position": True,
},
UnitTypeId.FLEETBEACON: {
- 'ability': AbilityId.PROTOSSBUILD_FLEETBEACON,
- 'required_building': UnitTypeId.STARGATE,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_FLEETBEACON,
+ "required_building": UnitTypeId.STARGATE,
+ "requires_placement_position": True,
},
UnitTypeId.FORGE: {
- 'ability': AbilityId.PROTOSSBUILD_FORGE,
- 'required_building': UnitTypeId.PYLON,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_FORGE,
+ "required_building": UnitTypeId.PYLON,
+ "requires_placement_position": True,
},
UnitTypeId.GATEWAY: {
- 'ability': AbilityId.PROTOSSBUILD_GATEWAY,
- 'required_building': UnitTypeId.PYLON,
- 'requires_placement_position': True
- },
- UnitTypeId.NEXUS: {
- 'ability': AbilityId.PROTOSSBUILD_NEXUS,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_GATEWAY,
+ "required_building": UnitTypeId.PYLON,
+ "requires_placement_position": True,
},
+ UnitTypeId.NEXUS: {"ability": AbilityId.PROTOSSBUILD_NEXUS, "requires_placement_position": True},
UnitTypeId.PHOTONCANNON: {
- 'ability': AbilityId.PROTOSSBUILD_PHOTONCANNON,
- 'required_building': UnitTypeId.FORGE,
- 'requires_placement_position': True
- },
- UnitTypeId.PYLON: {
- 'ability': AbilityId.PROTOSSBUILD_PYLON,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_PHOTONCANNON,
+ "required_building": UnitTypeId.FORGE,
+ "requires_placement_position": True,
},
+ UnitTypeId.PYLON: {"ability": AbilityId.PROTOSSBUILD_PYLON, "requires_placement_position": True},
UnitTypeId.ROBOTICSBAY: {
- 'ability': AbilityId.PROTOSSBUILD_ROBOTICSBAY,
- 'required_building': UnitTypeId.ROBOTICSFACILITY,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_ROBOTICSBAY,
+ "required_building": UnitTypeId.ROBOTICSFACILITY,
+ "requires_placement_position": True,
},
UnitTypeId.ROBOTICSFACILITY: {
- 'ability': AbilityId.PROTOSSBUILD_ROBOTICSFACILITY,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_ROBOTICSFACILITY,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
},
UnitTypeId.SHIELDBATTERY: {
- 'ability': AbilityId.BUILD_SHIELDBATTERY,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True
+ "ability": AbilityId.BUILD_SHIELDBATTERY,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
},
UnitTypeId.STARGATE: {
- 'ability': AbilityId.PROTOSSBUILD_STARGATE,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_STARGATE,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
},
UnitTypeId.TEMPLARARCHIVE: {
- 'ability': AbilityId.PROTOSSBUILD_TEMPLARARCHIVE,
- 'required_building': UnitTypeId.TWILIGHTCOUNCIL,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_TEMPLARARCHIVE,
+ "required_building": UnitTypeId.TWILIGHTCOUNCIL,
+ "requires_placement_position": True,
},
UnitTypeId.TWILIGHTCOUNCIL: {
- 'ability': AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True
- }
- },
- UnitTypeId.QUEEN: {
- UnitTypeId.CREEPTUMOR: {
- 'ability': AbilityId.BUILD_CREEPTUMOR,
- 'requires_placement_position': True
+ "ability": AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
},
- UnitTypeId.CREEPTUMORQUEEN: {
- 'ability': AbilityId.BUILD_CREEPTUMOR_QUEEN,
- 'requires_placement_position': True
- }
},
- UnitTypeId.RAVEN: {
- UnitTypeId.AUTOTURRET: {
- 'ability': AbilityId.BUILDAUTOTURRET_AUTOTURRET
- }
+ UnitTypeId.QUEEN: {
+ UnitTypeId.CREEPTUMOR: {"ability": AbilityId.BUILD_CREEPTUMOR, "requires_placement_position": True},
+ UnitTypeId.CREEPTUMORQUEEN: {"ability": AbilityId.BUILD_CREEPTUMOR_QUEEN, "requires_placement_position": True},
},
+ UnitTypeId.RAVEN: {UnitTypeId.AUTOTURRET: {"ability": AbilityId.BUILDAUTOTURRET_AUTOTURRET}},
UnitTypeId.ROACH: {
- UnitTypeId.RAVAGER: {
- 'ability': AbilityId.MORPHTORAVAGER_RAVAGER,
- 'required_building': UnitTypeId.HATCHERY
- }
+ UnitTypeId.RAVAGER: {"ability": AbilityId.MORPHTORAVAGER_RAVAGER, "required_building": UnitTypeId.HATCHERY}
},
UnitTypeId.ROBOTICSFACILITY: {
UnitTypeId.COLOSSUS: {
- 'ability': AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS,
- 'required_building': UnitTypeId.ROBOTICSBAY,
- 'requires_power': True
+ "ability": AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS,
+ "required_building": UnitTypeId.ROBOTICSBAY,
+ "requires_power": True,
},
UnitTypeId.DISRUPTOR: {
- 'ability': AbilityId.TRAIN_DISRUPTOR,
- 'required_building': UnitTypeId.ROBOTICSBAY,
- 'requires_power': True
- },
- UnitTypeId.IMMORTAL: {
- 'ability': AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL,
- 'requires_power': True
+ "ability": AbilityId.TRAIN_DISRUPTOR,
+ "required_building": UnitTypeId.ROBOTICSBAY,
+ "requires_power": True,
},
- UnitTypeId.OBSERVER: {
- 'ability': AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER,
- 'requires_power': True
- },
- UnitTypeId.WARPPRISM: {
- 'ability': AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM,
- 'requires_power': True
- }
+ UnitTypeId.IMMORTAL: {"ability": AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL, "requires_power": True},
+ UnitTypeId.OBSERVER: {"ability": AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, "requires_power": True},
+ UnitTypeId.WARPPRISM: {"ability": AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM, "requires_power": True},
},
UnitTypeId.SCV: {
UnitTypeId.ARMORY: {
- 'ability': AbilityId.TERRANBUILD_ARMORY,
- 'required_building': UnitTypeId.FACTORY,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_ARMORY,
+ "required_building": UnitTypeId.FACTORY,
+ "requires_placement_position": True,
},
UnitTypeId.BARRACKS: {
- 'ability': AbilityId.TERRANBUILD_BARRACKS,
- 'required_building': UnitTypeId.SUPPLYDEPOT,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_BARRACKS,
+ "required_building": UnitTypeId.SUPPLYDEPOT,
+ "requires_placement_position": True,
},
UnitTypeId.BUNKER: {
- 'ability': AbilityId.TERRANBUILD_BUNKER,
- 'required_building': UnitTypeId.BARRACKS,
- 'requires_placement_position': True
- },
- UnitTypeId.COMMANDCENTER: {
- 'ability': AbilityId.TERRANBUILD_COMMANDCENTER,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_BUNKER,
+ "required_building": UnitTypeId.BARRACKS,
+ "requires_placement_position": True,
},
+ UnitTypeId.COMMANDCENTER: {"ability": AbilityId.TERRANBUILD_COMMANDCENTER, "requires_placement_position": True},
UnitTypeId.ENGINEERINGBAY: {
- 'ability': AbilityId.TERRANBUILD_ENGINEERINGBAY,
- 'required_building': UnitTypeId.COMMANDCENTER,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_ENGINEERINGBAY,
+ "required_building": UnitTypeId.COMMANDCENTER,
+ "requires_placement_position": True,
},
UnitTypeId.FACTORY: {
- 'ability': AbilityId.TERRANBUILD_FACTORY,
- 'required_building': UnitTypeId.BARRACKS,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_FACTORY,
+ "required_building": UnitTypeId.BARRACKS,
+ "requires_placement_position": True,
},
UnitTypeId.FUSIONCORE: {
- 'ability': AbilityId.TERRANBUILD_FUSIONCORE,
- 'required_building': UnitTypeId.STARPORT,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_FUSIONCORE,
+ "required_building": UnitTypeId.STARPORT,
+ "requires_placement_position": True,
},
UnitTypeId.GHOSTACADEMY: {
- 'ability': AbilityId.TERRANBUILD_GHOSTACADEMY,
- 'required_building': UnitTypeId.BARRACKS,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_GHOSTACADEMY,
+ "required_building": UnitTypeId.BARRACKS,
+ "requires_placement_position": True,
},
UnitTypeId.MISSILETURRET: {
- 'ability': AbilityId.TERRANBUILD_MISSILETURRET,
- 'required_building': UnitTypeId.ENGINEERINGBAY,
- 'requires_placement_position': True
- },
- UnitTypeId.REFINERY: {
- 'ability': AbilityId.TERRANBUILD_REFINERY
+ "ability": AbilityId.TERRANBUILD_MISSILETURRET,
+ "required_building": UnitTypeId.ENGINEERINGBAY,
+ "requires_placement_position": True,
},
+ UnitTypeId.REFINERY: {"ability": AbilityId.TERRANBUILD_REFINERY},
UnitTypeId.SENSORTOWER: {
- 'ability': AbilityId.TERRANBUILD_SENSORTOWER,
- 'required_building': UnitTypeId.ENGINEERINGBAY,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_SENSORTOWER,
+ "required_building": UnitTypeId.ENGINEERINGBAY,
+ "requires_placement_position": True,
},
UnitTypeId.STARPORT: {
- 'ability': AbilityId.TERRANBUILD_STARPORT,
- 'required_building': UnitTypeId.FACTORY,
- 'requires_placement_position': True
+ "ability": AbilityId.TERRANBUILD_STARPORT,
+ "required_building": UnitTypeId.FACTORY,
+ "requires_placement_position": True,
},
- UnitTypeId.SUPPLYDEPOT: {
- 'ability': AbilityId.TERRANBUILD_SUPPLYDEPOT,
- 'requires_placement_position': True
- }
+ UnitTypeId.SUPPLYDEPOT: {"ability": AbilityId.TERRANBUILD_SUPPLYDEPOT, "requires_placement_position": True},
},
UnitTypeId.SPIRE: {
UnitTypeId.GREATERSPIRE: {
- 'ability': AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE,
- 'required_building': UnitTypeId.HIVE
+ "ability": AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE,
+ "required_building": UnitTypeId.HIVE,
}
},
UnitTypeId.STARGATE: {
UnitTypeId.CARRIER: {
- 'ability': AbilityId.STARGATETRAIN_CARRIER,
- 'required_building': UnitTypeId.FLEETBEACON,
- 'requires_power': True
- },
- UnitTypeId.ORACLE: {
- 'ability': AbilityId.STARGATETRAIN_ORACLE,
- 'requires_power': True
- },
- UnitTypeId.PHOENIX: {
- 'ability': AbilityId.STARGATETRAIN_PHOENIX,
- 'requires_power': True
+ "ability": AbilityId.STARGATETRAIN_CARRIER,
+ "required_building": UnitTypeId.FLEETBEACON,
+ "requires_power": True,
},
+ UnitTypeId.ORACLE: {"ability": AbilityId.STARGATETRAIN_ORACLE, "requires_power": True},
+ UnitTypeId.PHOENIX: {"ability": AbilityId.STARGATETRAIN_PHOENIX, "requires_power": True},
UnitTypeId.TEMPEST: {
- 'ability': AbilityId.STARGATETRAIN_TEMPEST,
- 'required_building': UnitTypeId.FLEETBEACON,
- 'requires_power': True
+ "ability": AbilityId.STARGATETRAIN_TEMPEST,
+ "required_building": UnitTypeId.FLEETBEACON,
+ "requires_power": True,
},
- UnitTypeId.VOIDRAY: {
- 'ability': AbilityId.STARGATETRAIN_VOIDRAY,
- 'requires_power': True
- }
+ UnitTypeId.VOIDRAY: {"ability": AbilityId.STARGATETRAIN_VOIDRAY, "requires_power": True},
},
UnitTypeId.STARPORT: {
- UnitTypeId.BANSHEE: {
- 'ability': AbilityId.STARPORTTRAIN_BANSHEE,
- 'requires_techlab': True
- },
+ UnitTypeId.BANSHEE: {"ability": AbilityId.STARPORTTRAIN_BANSHEE, "requires_techlab": True},
UnitTypeId.BATTLECRUISER: {
- 'ability': AbilityId.STARPORTTRAIN_BATTLECRUISER,
- 'requires_techlab': True,
- 'required_building': UnitTypeId.FUSIONCORE
- },
- UnitTypeId.LIBERATOR: {
- 'ability': AbilityId.STARPORTTRAIN_LIBERATOR
+ "ability": AbilityId.STARPORTTRAIN_BATTLECRUISER,
+ "requires_techlab": True,
+ "required_building": UnitTypeId.FUSIONCORE,
},
- UnitTypeId.MEDIVAC: {
- 'ability': AbilityId.STARPORTTRAIN_MEDIVAC
- },
- UnitTypeId.RAVEN: {
- 'ability': AbilityId.STARPORTTRAIN_RAVEN,
- 'requires_techlab': True
- },
- UnitTypeId.VIKINGFIGHTER: {
- 'ability': AbilityId.STARPORTTRAIN_VIKINGFIGHTER
- }
- },
- UnitTypeId.SWARMHOSTBURROWEDMP: {
- UnitTypeId.LOCUSTMPFLYING: {
- 'ability': AbilityId.EFFECT_SPAWNLOCUSTS
- }
- },
- UnitTypeId.SWARMHOSTMP: {
- UnitTypeId.LOCUSTMPFLYING: {
- 'ability': AbilityId.EFFECT_SPAWNLOCUSTS
- }
+ UnitTypeId.LIBERATOR: {"ability": AbilityId.STARPORTTRAIN_LIBERATOR},
+ UnitTypeId.MEDIVAC: {"ability": AbilityId.STARPORTTRAIN_MEDIVAC},
+ UnitTypeId.RAVEN: {"ability": AbilityId.STARPORTTRAIN_RAVEN, "requires_techlab": True},
+ UnitTypeId.VIKINGFIGHTER: {"ability": AbilityId.STARPORTTRAIN_VIKINGFIGHTER},
},
+ UnitTypeId.SWARMHOSTBURROWEDMP: {UnitTypeId.LOCUSTMPFLYING: {"ability": AbilityId.EFFECT_SPAWNLOCUSTS}},
+ UnitTypeId.SWARMHOSTMP: {UnitTypeId.LOCUSTMPFLYING: {"ability": AbilityId.EFFECT_SPAWNLOCUSTS}},
UnitTypeId.WARPGATE: {
UnitTypeId.ADEPT: {
- 'ability': AbilityId.TRAINWARP_ADEPT,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True,
- 'requires_power': True
+ "ability": AbilityId.TRAINWARP_ADEPT,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
+ "requires_power": True,
},
UnitTypeId.DARKTEMPLAR: {
- 'ability': AbilityId.WARPGATETRAIN_DARKTEMPLAR,
- 'required_building': UnitTypeId.DARKSHRINE,
- 'requires_placement_position': True,
- 'requires_power': True
+ "ability": AbilityId.WARPGATETRAIN_DARKTEMPLAR,
+ "required_building": UnitTypeId.DARKSHRINE,
+ "requires_placement_position": True,
+ "requires_power": True,
},
UnitTypeId.HIGHTEMPLAR: {
- 'ability': AbilityId.WARPGATETRAIN_HIGHTEMPLAR,
- 'required_building': UnitTypeId.TEMPLARARCHIVE,
- 'requires_placement_position': True,
- 'requires_power': True
+ "ability": AbilityId.WARPGATETRAIN_HIGHTEMPLAR,
+ "required_building": UnitTypeId.TEMPLARARCHIVE,
+ "requires_placement_position": True,
+ "requires_power": True,
},
UnitTypeId.SENTRY: {
- 'ability': AbilityId.WARPGATETRAIN_SENTRY,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True,
- 'requires_power': True
+ "ability": AbilityId.WARPGATETRAIN_SENTRY,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
+ "requires_power": True,
},
UnitTypeId.STALKER: {
- 'ability': AbilityId.WARPGATETRAIN_STALKER,
- 'required_building': UnitTypeId.CYBERNETICSCORE,
- 'requires_placement_position': True,
- 'requires_power': True
+ "ability": AbilityId.WARPGATETRAIN_STALKER,
+ "required_building": UnitTypeId.CYBERNETICSCORE,
+ "requires_placement_position": True,
+ "requires_power": True,
},
UnitTypeId.ZEALOT: {
- 'ability': AbilityId.WARPGATETRAIN_ZEALOT,
- 'requires_placement_position': True,
- 'requires_power': True
- }
+ "ability": AbilityId.WARPGATETRAIN_ZEALOT,
+ "requires_placement_position": True,
+ "requires_power": True,
+ },
},
UnitTypeId.ZERGLING: {
UnitTypeId.BANELING: {
- 'ability': AbilityId.MORPHZERGLINGTOBANELING_BANELING,
- 'required_building': UnitTypeId.BANELINGNEST
+ "ability": AbilityId.MORPHTOBANELING_BANELING,
+ "required_building": UnitTypeId.BANELINGNEST,
}
- }
+ },
}
diff --git a/sc2/dicts/unit_trained_from.py b/sc2/dicts/unit_trained_from.py
index c01ad363..14610889 100644
--- a/sc2/dicts/unit_trained_from.py
+++ b/sc2/dicts/unit_trained_from.py
@@ -1,14 +1,13 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict, Set
-
from sc2.ids.unit_typeid import UnitTypeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+
-UNIT_TRAINED_FROM: Dict[UnitTypeId, Set[UnitTypeId]] = {
+UNIT_TRAINED_FROM: dict[UnitTypeId, set[UnitTypeId]] = {
UnitTypeId.ADEPT: {UnitTypeId.GATEWAY, UnitTypeId.WARPGATE},
UnitTypeId.ARMORY: {UnitTypeId.SCV},
UnitTypeId.ASSIMILATOR: {UnitTypeId.PROBE},
@@ -25,9 +24,7 @@
UnitTypeId.COLOSSUS: {UnitTypeId.ROBOTICSFACILITY},
UnitTypeId.COMMANDCENTER: {UnitTypeId.SCV},
UnitTypeId.CORRUPTOR: {UnitTypeId.LARVA},
- UnitTypeId.CREEPTUMOR: {
- UnitTypeId.CREEPTUMOR, UnitTypeId.CREEPTUMORBURROWED, UnitTypeId.CREEPTUMORQUEEN, UnitTypeId.QUEEN
- },
+ UnitTypeId.CREEPTUMOR: {UnitTypeId.CREEPTUMORBURROWED, UnitTypeId.QUEEN},
UnitTypeId.CREEPTUMORQUEEN: {UnitTypeId.QUEEN},
UnitTypeId.CYBERNETICSCORE: {UnitTypeId.PROBE},
UnitTypeId.CYCLONE: {UnitTypeId.FACTORY},
@@ -117,5 +114,5 @@
UnitTypeId.WARPPRISM: {UnitTypeId.ROBOTICSFACILITY},
UnitTypeId.WIDOWMINE: {UnitTypeId.FACTORY},
UnitTypeId.ZEALOT: {UnitTypeId.GATEWAY, UnitTypeId.WARPGATE},
- UnitTypeId.ZERGLING: {UnitTypeId.LARVA}
+ UnitTypeId.ZERGLING: {UnitTypeId.LARVA},
}
diff --git a/sc2/dicts/unit_unit_alias.py b/sc2/dicts/unit_unit_alias.py
index dcf6a519..538555c9 100644
--- a/sc2/dicts/unit_unit_alias.py
+++ b/sc2/dicts/unit_unit_alias.py
@@ -1,14 +1,13 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict
-
from sc2.ids.unit_typeid import UnitTypeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+
-UNIT_UNIT_ALIAS: Dict[UnitTypeId, UnitTypeId] = {
+UNIT_UNIT_ALIAS: dict[UnitTypeId, UnitTypeId] = {
UnitTypeId.ADEPTPHASESHIFT: UnitTypeId.ADEPT,
UnitTypeId.BANELINGBURROWED: UnitTypeId.BANELING,
UnitTypeId.BARRACKSFLYING: UnitTypeId.BARRACKS,
@@ -48,5 +47,5 @@
UnitTypeId.VIKINGASSAULT: UnitTypeId.VIKINGFIGHTER,
UnitTypeId.WARPPRISMPHASING: UnitTypeId.WARPPRISM,
UnitTypeId.WIDOWMINEBURROWED: UnitTypeId.WIDOWMINE,
- UnitTypeId.ZERGLINGBURROWED: UnitTypeId.ZERGLING
+ UnitTypeId.ZERGLINGBURROWED: UnitTypeId.ZERGLING,
}
diff --git a/sc2/dicts/upgrade_researched_from.py b/sc2/dicts/upgrade_researched_from.py
index ab618fef..a4992068 100644
--- a/sc2/dicts/upgrade_researched_from.py
+++ b/sc2/dicts/upgrade_researched_from.py
@@ -1,15 +1,14 @@
# THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY!
# ANY CHANGE WILL BE OVERWRITTEN
-from typing import Dict
-
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
-# from ..ids.buff_id import BuffId
-# from ..ids.effect_id import EffectId
+# from sc2.ids.buff_id import BuffId
+# from sc2.ids.effect_id import EffectId
+
-UPGRADE_RESEARCHED_FROM: Dict[UpgradeId, UnitTypeId] = {
+UPGRADE_RESEARCHED_FROM: dict[UpgradeId, UnitTypeId] = {
UpgradeId.ADEPTPIERCINGATTACK: UnitTypeId.TWILIGHTCOUNCIL,
UpgradeId.ANABOLICSYNTHESIS: UnitTypeId.ULTRALISKCAVERN,
UpgradeId.BANSHEECLOAK: UnitTypeId.STARPORTTECHLAB,
@@ -24,18 +23,18 @@
UpgradeId.DARKTEMPLARBLINKUPGRADE: UnitTypeId.DARKSHRINE,
UpgradeId.DIGGINGCLAWS: UnitTypeId.LURKERDENMP,
UpgradeId.DRILLCLAWS: UnitTypeId.FACTORYTECHLAB,
- UpgradeId.ENHANCEDSHOCKWAVES: UnitTypeId.GHOSTACADEMY,
UpgradeId.EVOLVEGROOVEDSPINES: UnitTypeId.HYDRALISKDEN,
UpgradeId.EVOLVEMUSCULARAUGMENTS: UnitTypeId.HYDRALISKDEN,
UpgradeId.EXTENDEDTHERMALLANCE: UnitTypeId.ROBOTICSBAY,
+ UpgradeId.FRENZY: UnitTypeId.HYDRALISKDEN,
UpgradeId.GLIALRECONSTITUTION: UnitTypeId.ROACHWARREN,
UpgradeId.GRAVITICDRIVE: UnitTypeId.ROBOTICSBAY,
UpgradeId.HIGHCAPACITYBARRELS: UnitTypeId.FACTORYTECHLAB,
UpgradeId.HISECAUTOTRACKING: UnitTypeId.ENGINEERINGBAY,
- UpgradeId.INFESTORENERGYUPGRADE: UnitTypeId.INFESTATIONPIT,
+ UpgradeId.INTERFERENCEMATRIX: UnitTypeId.STARPORTTECHLAB,
UpgradeId.LIBERATORAGRANGEUPGRADE: UnitTypeId.FUSIONCORE,
UpgradeId.LURKERRANGE: UnitTypeId.LURKERDENMP,
- UpgradeId.MEDIVACINCREASESPEEDBOOST: UnitTypeId.FUSIONCORE,
+ UpgradeId.MEDIVACCADUCEUSREACTOR: UnitTypeId.FUSIONCORE,
UpgradeId.NEURALPARASITE: UnitTypeId.INFESTATIONPIT,
UpgradeId.OBSERVERGRAVITICBOOSTER: UnitTypeId.ROBOTICSBAY,
UpgradeId.OVERLORDSPEED: UnitTypeId.HATCHERY,
@@ -58,7 +57,6 @@
UpgradeId.PROTOSSSHIELDSLEVEL3: UnitTypeId.FORGE,
UpgradeId.PSISTORMTECH: UnitTypeId.TEMPLARARCHIVE,
UpgradeId.PUNISHERGRENADES: UnitTypeId.BARRACKSTECHLAB,
- UpgradeId.RAVENCORVIDREACTOR: UnitTypeId.STARPORTTECHLAB,
UpgradeId.SHIELDWALL: UnitTypeId.BARRACKSTECHLAB,
UpgradeId.SMARTSERVOS: UnitTypeId.FACTORYTECHLAB,
UpgradeId.STIMPACK: UnitTypeId.BARRACKSTECHLAB,
@@ -98,5 +96,5 @@
UpgradeId.ZERGMELEEWEAPONSLEVEL3: UnitTypeId.EVOLUTIONCHAMBER,
UpgradeId.ZERGMISSILEWEAPONSLEVEL1: UnitTypeId.EVOLUTIONCHAMBER,
UpgradeId.ZERGMISSILEWEAPONSLEVEL2: UnitTypeId.EVOLUTIONCHAMBER,
- UpgradeId.ZERGMISSILEWEAPONSLEVEL3: UnitTypeId.EVOLUTIONCHAMBER
+ UpgradeId.ZERGMISSILEWEAPONSLEVEL3: UnitTypeId.EVOLUTIONCHAMBER,
}
diff --git a/sc2/expiring_dict.py b/sc2/expiring_dict.py
index 92d3656f..ebbcb23c 100644
--- a/sc2/expiring_dict.py
+++ b/sc2/expiring_dict.py
@@ -1,8 +1,10 @@
+# pyre-ignore-all-errors[14, 15, 58]
from __future__ import annotations
from collections import OrderedDict
+from collections.abc import Iterable
from threading import RLock
-from typing import TYPE_CHECKING, Any, Iterable, Union
+from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from sc2.bot_ai import BotAI
@@ -29,21 +31,22 @@ async def on_step(iteration: int):
print("test is not anymore in dict")
"""
- def __init__(self, bot: BotAI, max_age_frames: int = 1):
+ def __init__(self, bot: BotAI, max_age_frames: int = 1) -> None:
assert max_age_frames >= -1
assert bot
OrderedDict.__init__(self)
self.bot: BotAI = bot
- self.max_age: Union[int, float] = max_age_frames
+ self.max_age: int | float = max_age_frames
self.lock: RLock = RLock()
@property
def frame(self) -> int:
+ # pyre-ignore[16]
return self.bot.state.game_loop
def __contains__(self, key) -> bool:
- """ Return True if dict has key, else False, e.g. 'key in dict' """
+ """Return True if dict has key, else False, e.g. 'key in dict'"""
with self.lock:
if OrderedDict.__contains__(self, key):
# Each item is a list of [value, frame time]
@@ -53,8 +56,8 @@ def __contains__(self, key) -> bool:
del self[key]
return False
- def __getitem__(self, key, with_age=False) -> Any:
- """ Return the item of the dict using d[key] """
+ def __getitem__(self, key, with_age: bool = False) -> Any:
+ """Return the item of the dict using d[key]"""
with self.lock:
# Each item is a list of [value, frame time]
item = OrderedDict.__getitem__(self, key)
@@ -65,13 +68,13 @@ def __getitem__(self, key, with_age=False) -> Any:
OrderedDict.__delitem__(self, key)
raise KeyError(key)
- def __setitem__(self, key, value):
- """ Set d[key] = value """
+ def __setitem__(self, key, value) -> None:
+ """Set d[key] = value"""
with self.lock:
OrderedDict.__setitem__(self, key, (value, self.frame))
- def __repr__(self):
- """ Printable version of the dict instead of getting memory adress """
+ def __repr__(self) -> str:
+ """Printable version of the dict instead of getting memory adress"""
print_list = []
with self.lock:
for key, value in OrderedDict.items(self):
@@ -84,12 +87,12 @@ def __str__(self):
return self.__repr__()
def __iter__(self):
- """ Override 'for key in dict:' """
+ """Override 'for key in dict:'"""
with self.lock:
return self.keys()
# TODO find a way to improve len
- def __len__(self):
+ def __len__(self) -> int:
"""Override len method as key value pairs aren't instantly being deleted, but only on __get__(item).
This function is slow because it has to check if each element is not expired yet."""
with self.lock:
@@ -98,8 +101,8 @@ def __len__(self):
count += 1
return count
- def pop(self, key, default=None, with_age=False):
- """ Return the item and remove it """
+ def pop(self, key, default=None, with_age: bool = False):
+ """Return the item and remove it"""
with self.lock:
if OrderedDict.__contains__(self, key):
item = OrderedDict.__getitem__(self, key)
@@ -115,8 +118,8 @@ def pop(self, key, default=None, with_age=False):
return default, self.frame
return default
- def get(self, key, default=None, with_age=False):
- """ Return the value for key if key is in dict, else default """
+ def get(self, key, default=None, with_age: bool = False):
+ """Return the value for key if key is in dict, else default"""
with self.lock:
if OrderedDict.__contains__(self, key):
item = OrderedDict.__getitem__(self, key)
@@ -131,27 +134,27 @@ def get(self, key, default=None, with_age=False):
return None
return None
- def update(self, other_dict: dict):
+ def update(self, other_dict: dict) -> None:
with self.lock:
for key, value in other_dict.items():
self[key] = value
def items(self) -> Iterable:
- """ Return iterator of zipped list [keys, values] """
+ """Return iterator of zipped list [keys, values]"""
with self.lock:
for key, value in OrderedDict.items(self):
if self.frame - value[1] < self.max_age:
yield key, value[0]
def keys(self) -> Iterable:
- """ Return iterator of keys """
+ """Return iterator of keys"""
with self.lock:
for key, value in OrderedDict.items(self):
if self.frame - value[1] < self.max_age:
yield key
def values(self) -> Iterable:
- """ Return iterator of values """
+ """Return iterator of values"""
with self.lock:
for value in OrderedDict.values(self):
if self.frame - value[1] < self.max_age:
diff --git a/sc2/game_data.py b/sc2/game_data.py
index a4f2de36..3bc4fc78 100644
--- a/sc2/game_data.py
+++ b/sc2/game_data.py
@@ -1,38 +1,39 @@
-# pylint: disable=W0212
+# pyre-ignore-all-errors[29]
from __future__ import annotations
from bisect import bisect_left
+from contextlib import suppress
+from dataclasses import dataclass
from functools import lru_cache
-from typing import Dict, List, Optional, Union
from sc2.data import Attribute, Race
-from sc2.dicts.unit_trained_from import UNIT_TRAINED_FROM
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.unit_command import UnitCommand
+with suppress(ImportError):
+ from sc2.dicts.unit_trained_from import UNIT_TRAINED_FROM
+
# Set of parts of names of abilities that have no cost
# E.g every ability that has 'Hold' in its name is free
FREE_ABILITIES = {"Lower", "Raise", "Land", "Lift", "Hold", "Harvest"}
class GameData:
-
- def __init__(self, data):
+ def __init__(self, data) -> None:
"""
:param data:
"""
- ids = set(a.value for a in AbilityId if a.value != 0)
- self.abilities: Dict[int, AbilityData] = {
- a.ability_id: AbilityData(self, a)
- for a in data.abilities if a.ability_id in ids
+ ids = {a.value for a in AbilityId if a.value != 0}
+ self.abilities: dict[int, AbilityData] = {
+ a.ability_id: AbilityData(self, a) for a in data.abilities if a.ability_id in ids
}
- self.units: Dict[int, UnitTypeData] = {u.unit_id: UnitTypeData(self, u) for u in data.units if u.available}
- self.upgrades: Dict[int, UpgradeData] = {u.upgrade_id: UpgradeData(self, u) for u in data.upgrades}
+ self.units: dict[int, UnitTypeData] = {u.unit_id: UnitTypeData(self, u) for u in data.units if u.available}
+ self.upgrades: dict[int, UpgradeData] = {u.upgrade_id: UpgradeData(self, u) for u in data.upgrades}
# Cached UnitTypeIds so that conversion does not take long. This needs to be moved elsewhere if a new GameData object is created multiple times per game
@lru_cache(maxsize=256)
- def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitCommand]) -> Cost:
+ def calculate_ability_cost(self, ability: AbilityData | AbilityId | UnitCommand) -> Cost:
if isinstance(ability, AbilityId):
ability = self.abilities[ability.value]
elif isinstance(ability, UnitCommand):
@@ -47,6 +48,7 @@ def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitComm
if not AbilityData.id_exists(unit.creation_ability.id.value):
continue
+ # pyre-ignore[16]
if unit.creation_ability.is_free_morph:
continue
@@ -54,6 +56,9 @@ def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitComm
if unit.id == UnitTypeId.ZERGLING:
# HARD CODED: zerglings are generated in pairs
return Cost(unit.cost.minerals * 2, unit.cost.vespene * 2, unit.cost.time)
+ if unit.id == UnitTypeId.BANELING:
+ # HARD CODED: banelings don't cost 50/25 as described in the API, but 25/25
+ return Cost(25, 25, unit.cost.time)
# Correction for morphing units, e.g. orbital would return 550/0 instead of actual 150/0
morph_cost = unit.morph_cost
if morph_cost: # can be None
@@ -69,8 +74,7 @@ def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitComm
class AbilityData:
-
- ability_ids: List[int] = [ability_id.value for ability_id in AbilityId][1:] # sorted list
+ ability_ids: list[int] = [ability_id.value for ability_id in AbilityId][1:] # sorted list
@classmethod
def id_exists(cls, ability_id):
@@ -80,7 +84,7 @@ def id_exists(cls, ability_id):
i = bisect_left(cls.ability_ids, ability_id) # quick binary search
return i != len(cls.ability_ids) and cls.ability_ids[i] == ability_id
- def __init__(self, game_data, proto):
+ def __init__(self, game_data, proto) -> None:
self._game_data = game_data
self._proto = proto
@@ -92,29 +96,29 @@ def __repr__(self) -> str:
@property
def id(self) -> AbilityId:
- """ Returns the generic remap ID. See sc2/dicts/generic_redirect_abilities.py """
+ """Returns the generic remap ID. See sc2/dicts/generic_redirect_abilities.py"""
if self._proto.remaps_to_ability_id:
return AbilityId(self._proto.remaps_to_ability_id)
return AbilityId(self._proto.ability_id)
@property
def exact_id(self) -> AbilityId:
- """ Returns the exact ID of the ability """
+ """Returns the exact ID of the ability"""
return AbilityId(self._proto.ability_id)
@property
def link_name(self) -> str:
- """ For Stimpack this returns 'BarracksTechLabResearch' """
+ """For Stimpack this returns 'BarracksTechLabResearch'"""
return self._proto.link_name
@property
def button_name(self) -> str:
- """ For Stimpack this returns 'Stimpack' """
+ """For Stimpack this returns 'Stimpack'"""
return self._proto.button_name
@property
def friendly_name(self) -> str:
- """ For Stimpack this returns 'Research Stimpack' """
+ """For Stimpack this returns 'Research Stimpack'"""
return self._proto.friendly_name
@property
@@ -127,8 +131,7 @@ def cost(self) -> Cost:
class UnitTypeData:
-
- def __init__(self, game_data: GameData, proto):
+ def __init__(self, game_data: GameData, proto) -> None:
"""
:param game_data:
:param proto:
@@ -154,7 +157,7 @@ def name(self) -> str:
return self._proto.name
@property
- def creation_ability(self) -> Optional[AbilityData]:
+ def creation_ability(self) -> AbilityData | None:
if self._proto.ability_id == 0:
return None
if self._proto.ability_id not in self._game_data.abilities:
@@ -162,17 +165,19 @@ def creation_ability(self) -> Optional[AbilityData]:
return self._game_data.abilities[self._proto.ability_id]
@property
- def footprint_radius(self) -> Optional[float]:
- """ See unit.py footprint_radius """
+ def footprint_radius(self) -> float | None:
+ """See unit.py footprint_radius"""
if self.creation_ability is None:
return None
return self.creation_ability._proto.footprint_radius
@property
- def attributes(self) -> List[Attribute]:
+ # pyre-ignore[11]
+ def attributes(self) -> list[Attribute]:
return self._proto.attributes
def has_attribute(self, attr) -> bool:
+ # pyre-ignore[6]
assert isinstance(attr, Attribute)
return attr in self.attributes
@@ -186,12 +191,12 @@ def has_vespene(self) -> bool:
@property
def cargo_size(self) -> int:
- """ How much cargo this unit uses up in cargo_space """
+ """How much cargo this unit uses up in cargo_space"""
return self._proto.cargo_size
@property
- def tech_requirement(self) -> Optional[UnitTypeId]:
- """ Tech-building requirement of buildings - may work for units but unreliably """
+ def tech_requirement(self) -> UnitTypeId | None:
+ """Tech-building requirement of buildings - may work for units but unreliably"""
if self._proto.tech_requirement == 0:
return None
if self._proto.tech_requirement not in self._game_data.units:
@@ -199,7 +204,7 @@ def tech_requirement(self) -> Optional[UnitTypeId]:
return UnitTypeId(self._proto.tech_requirement)
@property
- def tech_alias(self) -> Optional[List[UnitTypeId]]:
+ def tech_alias(self) -> list[UnitTypeId] | None:
"""Building tech equality, e.g. OrbitalCommand is the same as CommandCenter
Building tech equality, e.g. Hive is the same as Lair and Hatchery
For Hive, this returns [UnitTypeId.Hatchery, UnitTypeId.Lair]
@@ -210,8 +215,8 @@ def tech_alias(self) -> Optional[List[UnitTypeId]]:
return return_list if return_list else None
@property
- def unit_alias(self) -> Optional[UnitTypeId]:
- """ Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand """
+ def unit_alias(self) -> UnitTypeId | None:
+ """Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand"""
if self._proto.unit_alias == 0:
return None
if self._proto.unit_alias not in self._game_data.units:
@@ -220,6 +225,7 @@ def unit_alias(self) -> Optional[UnitTypeId]:
return UnitTypeId(self._proto.unit_alias)
@property
+ # pyre-ignore[11]
def race(self) -> Race:
return Race(self._proto.race)
@@ -229,14 +235,15 @@ def cost(self) -> Cost:
@property
def cost_zerg_corrected(self) -> Cost:
- """ This returns 25 for extractor and 200 for spawning pool instead of 75 and 250 respectively """
+ """This returns 25 for extractor and 200 for spawning pool instead of 75 and 250 respectively"""
+ # pyre-ignore[16]
if self.race == Race.Zerg and Attribute.Structure.value in self.attributes:
return Cost(self._proto.mineral_cost - 50, self._proto.vespene_cost, self._proto.build_time)
return self.cost
@property
- def morph_cost(self) -> Optional[Cost]:
- """ This returns 150 minerals for OrbitalCommand instead of 550 """
+ def morph_cost(self) -> Cost | None:
+ """This returns 150 minerals for OrbitalCommand instead of 550"""
# Morphing units
supply_cost = self._proto.food_required
if supply_cost > 0 and self.id in UNIT_TRAINED_FROM and len(UNIT_TRAINED_FROM[self.id]) == 1:
@@ -261,7 +268,9 @@ def morph_cost(self) -> Optional[Cost]:
self._game_data.units[tech_alias.value].cost.minerals for tech_alias in self.tech_alias
)
tech_alias_cost_vespene = max(
- self._game_data.units[tech_alias.value].cost.vespene for tech_alias in self.tech_alias
+ self._game_data.units[tech_alias.value].cost.vespene
+ # pyre-ignore[16]
+ for tech_alias in self.tech_alias
)
return Cost(
self._proto.mineral_cost - tech_alias_cost_minerals,
@@ -271,8 +280,7 @@ def morph_cost(self) -> Optional[Cost]:
class UpgradeData:
-
- def __init__(self, game_data: GameData, proto):
+ def __init__(self, game_data: GameData, proto) -> None:
"""
:param game_data:
:param proto:
@@ -280,7 +288,7 @@ def __init__(self, game_data: GameData, proto):
self._game_data = game_data
self._proto = proto
- def __repr__(self):
+ def __repr__(self) -> str:
return f"UpgradeData({self.name} - research ability: {self.research_ability}, {self.cost})"
@property
@@ -288,7 +296,7 @@ def name(self) -> str:
return self._proto.name
@property
- def research_ability(self) -> Optional[AbilityData]:
+ def research_ability(self) -> AbilityData | None:
if self._proto.ability_id == 0:
return None
if self._proto.ability_id not in self._game_data.abilities:
@@ -300,21 +308,16 @@ def cost(self) -> Cost:
return Cost(self._proto.mineral_cost, self._proto.vespene_cost, self._proto.research_time)
+@dataclass
class Cost:
"""
The cost of an action, a structure, a unit or a research upgrade.
The time is given in frames (22.4 frames per game second).
"""
- def __init__(self, minerals: int, vespene: int, time: float = None):
- """
- :param minerals:
- :param vespene:
- :param time:
- """
- self.minerals = minerals
- self.vespene = vespene
- self.time = time
+ minerals: int
+ vespene: int
+ time: float | None = None
def __repr__(self) -> str:
return f"Cost({self.minerals}, {self.vespene})"
@@ -328,31 +331,20 @@ def __ne__(self, other: Cost) -> bool:
def __bool__(self) -> bool:
return self.minerals != 0 or self.vespene != 0
- def __add__(self, other) -> Cost:
+ def __add__(self, other: Cost) -> Cost:
if not other:
return self
if not self:
return other
- if self.time is None:
- time = other.time
- elif other.time is None:
- time = self.time
- else:
- time = self.time + other.time
- return self.__class__(self.minerals + other.minerals, self.vespene + other.vespene, time=time)
-
- def __sub__(self, other) -> Cost:
- assert isinstance(other, Cost)
- if self.time is None:
- time = other.time
- elif other.time is None:
- time = self.time
- else:
- time = self.time - other.time
- return self.__class__(self.minerals - other.minerals, self.vespene - other.vespene, time=time)
+ time = (self.time or 0) + (other.time or 0)
+ return Cost(self.minerals + other.minerals, self.vespene + other.vespene, time=time)
+
+ def __sub__(self, other: Cost) -> Cost:
+ time = (self.time or 0) + (other.time or 0)
+ return Cost(self.minerals - other.minerals, self.vespene - other.vespene, time=time)
def __mul__(self, other: int) -> Cost:
- return self.__class__(self.minerals * other, self.vespene * other, time=self.time)
+ return Cost(self.minerals * other, self.vespene * other, time=self.time)
def __rmul__(self, other: int) -> Cost:
- return self.__class__(self.minerals * other, self.vespene * other, time=self.time)
+ return Cost(self.minerals * other, self.vespene * other, time=self.time)
diff --git a/sc2/game_info.py b/sc2/game_info.py
index 2b4c4cc2..c00a0428 100644
--- a/sc2/game_info.py
+++ b/sc2/game_info.py
@@ -1,53 +1,51 @@
+# pyre-ignore-all-errors[6, 11, 16, 58]
from __future__ import annotations
import heapq
from collections import deque
+from collections.abc import Iterable
+from dataclasses import dataclass
from functools import cached_property
-from typing import Any, Deque, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple
import numpy as np
from sc2.pixel_map import PixelMap
-from sc2.player import Player, Race
+from sc2.player import Player
from sc2.position import Point2, Rect, Size
+@dataclass
class Ramp:
+ points: frozenset[Point2]
+ game_info: GameInfo
- def __init__(self, points: FrozenSet[Point2], game_info: GameInfo):
- """
- :param points:
- :param game_info:
- """
- self.cache: Dict[str, Any] = {}
- self._points: FrozenSet[Point2] = points
- self.__game_info = game_info
+ @property
+ def x_offset(self) -> float:
+ # Tested by printing actual building locations vs calculated depot positions
+ return 0.5
+
+ @property
+ def y_offset(self) -> float:
# Tested by printing actual building locations vs calculated depot positions
- self.x_offset = 0.5
- self.y_offset = 0.5
- self.cache = {}
+ return 0.5
@cached_property
def _height_map(self):
- return self.__game_info.terrain_height
+ return self.game_info.terrain_height
@cached_property
def size(self) -> int:
- return len(self._points)
+ return len(self.points)
def height_at(self, p: Point2) -> int:
return self._height_map[p]
@cached_property
- def points(self) -> FrozenSet[Point2]:
- return self._points.copy()
-
- @cached_property
- def upper(self) -> FrozenSet[Point2]:
- """ Returns the upper points of a ramp. """
+ def upper(self) -> frozenset[Point2]:
+ """Returns the upper points of a ramp."""
current_max = -10000
result = set()
- for p in self._points:
+ for p in self.points:
height = self.height_at(p)
if height > current_max:
current_max = height
@@ -57,8 +55,8 @@ def upper(self) -> FrozenSet[Point2]:
return frozenset(result)
@cached_property
- def upper2_for_ramp_wall(self) -> FrozenSet[Point2]:
- """ Returns the 2 upper ramp points of the main base ramp required for the supply depot and barracks placement properties used in this file. """
+ def upper2_for_ramp_wall(self) -> frozenset[Point2]:
+ """Returns the 2 upper ramp points of the main base ramp required for the supply depot and barracks placement properties used in this file."""
# From bottom center, find 2 points that are furthest away (within the same ramp)
return frozenset(heapq.nlargest(2, self.upper, key=lambda x: x.distance_to_point2(self.bottom_center)))
@@ -69,10 +67,10 @@ def top_center(self) -> Point2:
return pos
@cached_property
- def lower(self) -> FrozenSet[Point2]:
+ def lower(self) -> frozenset[Point2]:
current_min = 10000
result = set()
- for p in self._points:
+ for p in self.points:
height = self.height_at(p)
if height < current_min:
current_min = height
@@ -88,8 +86,8 @@ def bottom_center(self) -> Point2:
return pos
@cached_property
- def barracks_in_middle(self) -> Optional[Point2]:
- """ Barracks position in the middle of the 2 depots """
+ def barracks_in_middle(self) -> Point2 | None:
+ """Barracks position in the middle of the 2 depots"""
if len(self.upper) not in {2, 5}:
return None
if len(self.upper2_for_ramp_wall) == 2:
@@ -100,11 +98,12 @@ def barracks_in_middle(self) -> Optional[Point2]:
intersects = p1.circle_intersection(p2, 5**0.5)
any_lower_point = next(iter(self.lower))
return max(intersects, key=lambda p: p.distance_to_point2(any_lower_point))
+
raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.")
@cached_property
- def depot_in_middle(self) -> Optional[Point2]:
- """ Depot in the middle of the 3 depots """
+ def depot_in_middle(self) -> Point2 | None:
+ """Depot in the middle of the 3 depots"""
if len(self.upper) not in {2, 5}:
return None
if len(self.upper2_for_ramp_wall) == 2:
@@ -117,13 +116,14 @@ def depot_in_middle(self) -> Optional[Point2]:
except AssertionError:
# Returns None when no placement was found, this is the case on the map Honorgrounds LE with an exceptionally large main base ramp
return None
- anyLowerPoint = next(iter(self.lower))
- return max(intersects, key=lambda p: p.distance_to_point2(anyLowerPoint))
+ any_lower_point = next(iter(self.lower))
+ return max(intersects, key=lambda p: p.distance_to_point2(any_lower_point))
+
raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.")
@cached_property
- def corner_depots(self) -> FrozenSet[Point2]:
- """ Finds the 2 depot positions on the outside """
+ def corner_depots(self) -> frozenset[Point2]:
+ """Finds the 2 depot positions on the outside"""
if not self.upper2_for_ramp_wall:
return frozenset()
if len(self.upper2_for_ramp_wall) == 2:
@@ -137,29 +137,32 @@ def corner_depots(self) -> FrozenSet[Point2]:
# Offset from middle depot to corner depots is (2, 1)
intersects = center.circle_intersection(depot_position, 5**0.5)
return intersects
+
raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.")
@cached_property
def barracks_can_fit_addon(self) -> bool:
- """ Test if a barracks can fit an addon at natural ramp """
+ """Test if a barracks can fit an addon at natural ramp"""
# https://i.imgur.com/4b2cXHZ.png
if len(self.upper2_for_ramp_wall) == 2:
return self.barracks_in_middle.x + 1 > max(self.corner_depots, key=lambda depot: depot.x).x
+
raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.")
@cached_property
- def barracks_correct_placement(self) -> Optional[Point2]:
- """ Corrected placement so that an addon can fit """
+ def barracks_correct_placement(self) -> Point2 | None:
+ """Corrected placement so that an addon can fit"""
if self.barracks_in_middle is None:
return None
if len(self.upper2_for_ramp_wall) == 2:
if self.barracks_can_fit_addon:
return self.barracks_in_middle
return self.barracks_in_middle.offset((-2, 0))
+
raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.")
@cached_property
- def protoss_wall_pylon(self) -> Optional[Point2]:
+ def protoss_wall_pylon(self) -> Point2 | None:
"""
Pylon position that powers the two wall buildings and the warpin position.
"""
@@ -170,10 +173,11 @@ def protoss_wall_pylon(self) -> Optional[Point2]:
middle = self.depot_in_middle
# direction up the ramp
direction = self.barracks_in_middle.negative_offset(middle)
+ # pyre-ignore[7]
return middle + 6 * direction
@cached_property
- def protoss_wall_buildings(self) -> FrozenSet[Point2]:
+ def protoss_wall_buildings(self) -> frozenset[Point2]:
"""
List of two positions for 3x3 buildings that form a wall with a spot for a one unit block.
These buildings can be powered by a pylon on the protoss_wall_pylon position.
@@ -186,15 +190,16 @@ def protoss_wall_buildings(self) -> FrozenSet[Point2]:
direction = self.barracks_in_middle.negative_offset(middle)
# sort depots based on distance to start to get wallin orientation
sorted_depots = sorted(
- self.corner_depots, key=lambda depot: depot.distance_to(self.__game_info.player_start_location)
+ self.corner_depots, key=lambda depot: depot.distance_to(self.game_info.player_start_location)
)
- wall1 = sorted_depots[1].offset(direction)
+ wall1: Point2 = sorted_depots[1].offset(direction)
wall2 = middle + direction + (middle - wall1) / 1.5
return frozenset([wall1, wall2])
+
raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.")
@cached_property
- def protoss_wall_warpin(self) -> Optional[Point2]:
+ def protoss_wall_warpin(self) -> Point2 | None:
"""
Position for a unit to block the wall created by protoss_wall_buildings.
Powered by protoss_wall_pylon.
@@ -207,17 +212,17 @@ def protoss_wall_warpin(self) -> Optional[Point2]:
# direction up the ramp
direction = self.barracks_in_middle.negative_offset(middle)
# sort depots based on distance to start to get wallin orientation
- sorted_depots = sorted(self.corner_depots, key=lambda x: x.distance_to(self.__game_info.player_start_location))
+ sorted_depots = sorted(self.corner_depots, key=lambda x: x.distance_to(self.game_info.player_start_location))
return sorted_depots[0].negative_offset(direction)
class GameInfo:
-
- def __init__(self, proto):
+ def __init__(self, proto) -> None:
self._proto = proto
- self.players: List[Player] = [Player.from_proto(p) for p in self._proto.player_info]
+ self.players: list[Player] = [Player.from_proto(p) for p in self._proto.player_info]
self.map_name: str = self._proto.map_name
self.local_map_path: str = self._proto.local_map_path
+ # pyre-ignore[8]
self.map_size: Size = Size.from_proto(self._proto.start_raw.map_size)
# self.pathing_grid[point]: if 0, point is not pathable, if 1, point is pathable
@@ -228,31 +233,38 @@ def __init__(self, proto):
self.placement_grid: PixelMap = PixelMap(self._proto.start_raw.placement_grid, in_bits=True)
self.playable_area = Rect.from_proto(self._proto.start_raw.playable_area)
self.map_center = self.playable_area.center
- self.map_ramps: List[Ramp] = None # Filled later by BotAI._prepare_first_step
- self.vision_blockers: FrozenSet[Point2] = None # Filled later by BotAI._prepare_first_step
- self.player_races: Dict[int, Race] = {
- p.player_id: p.race_actual or p.race_requested
- for p in self._proto.player_info
+ # pyre-ignore[8]
+ self.map_ramps: list[Ramp] = None # Filled later by BotAI._prepare_first_step
+ # pyre-ignore[8]
+ self.vision_blockers: frozenset[Point2] = None # Filled later by BotAI._prepare_first_step
+ self.player_races: dict[int, int] = {
+ p.player_id: p.race_actual or p.race_requested for p in self._proto.player_info
}
- self.start_locations: List[Point2] = [Point2.from_proto(sl) for sl in self._proto.start_raw.start_locations]
+ self.start_locations: list[Point2] = [
+ Point2.from_proto(sl).round(decimals=1) for sl in self._proto.start_raw.start_locations
+ ]
+ # pyre-ignore[8]
self.player_start_location: Point2 = None # Filled later by BotAI._prepare_first_step
- def _find_ramps_and_vision_blockers(self) -> Tuple[List[Ramp], FrozenSet[Point2]]:
+ def _find_ramps_and_vision_blockers(self) -> tuple[list[Ramp], frozenset[Point2]]:
"""Calculate points that are pathable but not placeable.
Then divide them into ramp points if not all points around the points are equal height
and into vision blockers if they are."""
def equal_height_around(tile):
# mask to slice array 1 around tile
- sliced = self.terrain_height.data_numpy[tile[1] - 1:tile[1] + 2, tile[0] - 1:tile[0] + 2]
+ sliced = self.terrain_height.data_numpy[tile[1] - 1 : tile[1] + 2, tile[0] - 1 : tile[0] + 2]
return len(np.unique(sliced)) == 1
map_area = self.playable_area
# all points in the playable area that are pathable but not placable
points = [
- Point2((a, b)) for (b, a), value in np.ndenumerate(self.pathing_grid.data_numpy)
- if value == 1 and map_area.x <= a < map_area.x + map_area.width and map_area.y <= b < map_area.y +
- map_area.height and self.placement_grid[(a, b)] == 0
+ Point2((a, b))
+ for (b, a), value in np.ndenumerate(self.pathing_grid.data_numpy)
+ if value == 1
+ and map_area.x <= a < map_area.x + map_area.width
+ and map_area.y <= b < map_area.y + map_area.height
+ and self.placement_grid[(a, b)] == 0
]
# divide points into ramp points and vision blockers
ramp_points = [point for point in points if not equal_height_around(point)]
@@ -260,7 +272,7 @@ def equal_height_around(tile):
ramps = [Ramp(group, self) for group in self._find_groups(ramp_points)]
return ramps, vision_blockers
- def _find_groups(self, points: FrozenSet[Point2], minimum_points_per_group: int = 8) -> Iterable[FrozenSet[Point2]]:
+ def _find_groups(self, points: frozenset[Point2], minimum_points_per_group: int = 8) -> Iterable[frozenset[Point2]]:
"""
From a set of points, this function will try to group points together by
painting clusters of points in a rectangular map using flood fill algorithm.
@@ -271,20 +283,20 @@ def _find_groups(self, points: FrozenSet[Point2], minimum_points_per_group: int
map_width = self.pathing_grid.width
map_height = self.pathing_grid.height
current_color: int = NOT_COLORED_YET
- picture: List[List[int]] = [[-2 for _ in range(map_width)] for _ in range(map_height)]
+ picture: list[list[int]] = [[-2 for _ in range(map_width)] for _ in range(map_height)]
def paint(pt: Point2) -> None:
picture[pt.y][pt.x] = current_color
- nearby: List[Tuple[int, int]] = [(a, b) for a in [-1, 0, 1] for b in [-1, 0, 1] if a != 0 or b != 0]
+ nearby: list[tuple[int, int]] = [(a, b) for a in [-1, 0, 1] for b in [-1, 0, 1] if a != 0 or b != 0]
- remaining: Set[Point2] = set(points)
+ remaining: set[Point2] = set(points)
for point in remaining:
paint(point)
current_color = 1
- queue: Deque[Point2] = deque()
+ queue: deque[Point2] = deque()
while remaining:
- current_group: Set[Point2] = set()
+ current_group: set[Point2] = set()
if not queue:
start = remaining.pop()
paint(start)
diff --git a/sc2/game_state.py b/sc2/game_state.py
index 60c93347..b17fd12e 100644
--- a/sc2/game_state.py
+++ b/sc2/game_state.py
@@ -1,9 +1,9 @@
+# pyre-ignore-all-errors[11, 16]
from __future__ import annotations
from dataclasses import dataclass
from functools import cached_property
from itertools import chain
-from typing import List, Optional, Set, Union
from loguru import logger
@@ -25,8 +25,7 @@
class Blip:
-
- def __init__(self, proto):
+ def __init__(self, proto) -> None:
"""
:param proto:
"""
@@ -83,17 +82,16 @@ class Common:
"larva_count",
]
- def __init__(self, proto):
+ def __init__(self, proto) -> None:
self._proto = proto
- def __getattr__(self, attr):
+ def __getattr__(self, attr) -> int:
assert attr in self.ATTRIBUTES, f"'{attr}' is not a valid attribute"
return int(getattr(self._proto, attr))
class EffectData:
-
- def __init__(self, proto, fake=False):
+ def __init__(self, proto, fake: bool = False) -> None:
"""
:param proto:
:param fake:
@@ -102,14 +100,14 @@ def __init__(self, proto, fake=False):
self.fake = fake
@property
- def id(self) -> Union[EffectId, str]:
+ def id(self) -> EffectId | str:
if self.fake:
# Returns the string from constants.py, e.g. "KD8CHARGE"
return FakeEffectID[self._proto.unit_type]
return EffectId(self._proto.effect_id)
@property
- def positions(self) -> Set[Point2]:
+ def positions(self) -> set[Point2]:
if self.fake:
return {Point2.from_proto(self._proto.pos)}
return {Point2.from_proto(p) for p in self._proto.pos}
@@ -120,12 +118,12 @@ def alliance(self) -> Alliance:
@property
def is_mine(self) -> bool:
- """ Checks if the effect is caused by me. """
+ """Checks if the effect is caused by me."""
return self._proto.alliance == IS_MINE
@property
def is_enemy(self) -> bool:
- """ Checks if the effect is hostile. """
+ """Checks if the effect is hostile."""
return self._proto.alliance == IS_ENEMY
@property
@@ -150,7 +148,6 @@ class ChatMessage:
@dataclass
class AbilityLookupTemplateClass:
-
@property
def exact_id(self) -> AbilityId:
return AbilityId(self.ability_id)
@@ -167,17 +164,17 @@ def generic_id(self) -> AbilityId:
class ActionRawUnitCommand(AbilityLookupTemplateClass):
game_loop: int
ability_id: int
- unit_tags: List[int]
+ unit_tags: list[int]
queue_command: bool
- target_world_space_pos: Optional[Point2]
- target_unit_tag: Optional[int] = None
+ target_world_space_pos: Point2 | None
+ target_unit_tag: int | None = None
@dataclass
class ActionRawToggleAutocast(AbilityLookupTemplateClass):
game_loop: int
ability_id: int
- unit_tags: List[int]
+ unit_tags: list[int]
@dataclass
@@ -194,10 +191,10 @@ class ActionError(AbilityLookupTemplateClass):
class GameState:
-
- def __init__(self, response_observation, previous_observation=None):
+ def __init__(self, response_observation, previous_observation=None) -> None:
"""
:param response_observation:
+ :param previous_observation:
"""
# Only filled in realtime=True in case the bot skips frames
self.previous_observation = previous_observation
@@ -217,7 +214,7 @@ def __init__(self, response_observation, previous_observation=None):
# https://github.com/Blizzard/s2client-proto/blob/33f0ecf615aa06ca845ffe4739ef3133f37265a9/s2clientprotocol/score.proto#L31
self.score: ScoreDetails = ScoreDetails(self.observation.score)
self.abilities = self.observation.abilities # abilities of selected units
- self.upgrades: Set[UpgradeId] = {UpgradeId(upgrade) for upgrade in self.observation_raw.player.upgrade_ids}
+ self.upgrades: set[UpgradeId] = {UpgradeId(upgrade) for upgrade in self.observation_raw.player.upgrade_ids}
# self.visibility[point]: 0=Hidden, 1=Fogged, 2=Visible
self.visibility: PixelMap = PixelMap(self.observation_raw.map_state.visibility)
@@ -225,7 +222,7 @@ def __init__(self, response_observation, previous_observation=None):
self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep, in_bits=True)
# Effects like ravager bile shot, lurker attack, everything in effect_id.py
- self.effects: Set[EffectData] = {EffectData(effect) for effect in self.observation_raw.effects}
+ self.effects: set[EffectData] = {EffectData(effect) for effect in self.observation_raw.effects}
""" Usage:
for effect in self.state.effects:
if effect.id == EffectId.RAVAGERCORROSIVEBILECP:
@@ -234,15 +231,15 @@ def __init__(self, response_observation, previous_observation=None):
"""
@cached_property
- def dead_units(self) -> Set[int]:
- """ A set of unit tags that died this frame """
+ def dead_units(self) -> set[int]:
+ """A set of unit tags that died this frame"""
_dead_units = set(self.observation_raw.event.dead_units)
if self.previous_observation:
return _dead_units | set(self.previous_observation.observation.raw_data.event.dead_units)
return _dead_units
@cached_property
- def chat(self) -> List[ChatMessage]:
+ def chat(self) -> list[ChatMessage]:
"""List of chat messages sent this frame (by either player)."""
previous_frame_chat = self.previous_observation.chat if self.previous_observation else []
return [
@@ -251,7 +248,7 @@ def chat(self) -> List[ChatMessage]:
]
@cached_property
- def alerts(self) -> List[int]:
+ def alerts(self) -> list[int]:
"""
Game alerts, see https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L683-L706
"""
@@ -260,7 +257,7 @@ def alerts(self) -> List[int]:
return self.observation.alerts
@cached_property
- def actions(self) -> List[Union[ActionRawUnitCommand, ActionRawToggleAutocast, ActionRawCameraMove]]:
+ def actions(self) -> list[ActionRawUnitCommand | ActionRawToggleAutocast | ActionRawCameraMove]:
"""
List of successful actions since last frame.
See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L630-L637
@@ -314,23 +311,25 @@ def actions(self) -> List[Union[ActionRawUnitCommand, ActionRawToggleAutocast, A
return actions
@cached_property
- def actions_unit_commands(self) -> List[ActionRawUnitCommand]:
+ def actions_unit_commands(self) -> list[ActionRawUnitCommand]:
"""
List of successful unit actions since last frame.
See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/raw.proto#L185-L193
"""
+ # pyre-ignore[7]
return list(filter(lambda action: isinstance(action, ActionRawUnitCommand), self.actions))
@cached_property
- def actions_toggle_autocast(self) -> List[ActionRawToggleAutocast]:
+ def actions_toggle_autocast(self) -> list[ActionRawToggleAutocast]:
"""
List of successful autocast toggle actions since last frame.
See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/raw.proto#L199-L202
"""
+ # pyre-ignore[7]
return list(filter(lambda action: isinstance(action, ActionRawToggleAutocast), self.actions))
@cached_property
- def action_errors(self) -> List[ActionError]:
+ def action_errors(self) -> list[ActionError]:
"""
List of erroneous actions since last frame.
See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L648-L652
diff --git a/sc2/generate_ids.py b/sc2/generate_ids.py
index 22836a97..1fd4e7d6 100644
--- a/sc2/generate_ids.py
+++ b/sc2/generate_ids.py
@@ -1,15 +1,15 @@
-# pylint: disable=W0212
+from __future__ import annotations
+
import importlib
import json
import platform
-import subprocess
import sys
from pathlib import Path
+from typing import Any
from loguru import logger
-from sc2.game_data import AbilityData, GameData, UnitTypeData, UpgradeData
-from sc2.ids.ability_id import AbilityId
+from sc2.game_data import GameData
try:
from sc2.ids.id_version import ID_VERSION_STRING
@@ -18,13 +18,18 @@
class IdGenerator:
-
- def __init__(self, game_data: GameData = None, game_version: str = None, verbose: bool = False):
- self.game_data: GameData = game_data
+ def __init__(
+ self, game_data: GameData | None = None, game_version: str | None = None, verbose: bool = False
+ ) -> None:
+ self.game_data = game_data
self.game_version = game_version
self.verbose = verbose
- self.HEADER = f'# DO NOT EDIT!\n# This file was automatically generated by "{Path(__file__).name}"\n'
+ self.HEADER = f"""# pyre-ignore-all-errors[14]
+from __future__ import annotations
+# DO NOT EDIT!
+# This file was automatically generated by "{Path(__file__).name}"
+"""
self.PF = platform.system()
@@ -52,13 +57,13 @@ def __init__(self, game_data: GameData = None, game_version: str = None, verbose
}
@staticmethod
- def make_key(key):
+ def make_key(key: str) -> str:
if key[0].isdigit():
key = "_" + key
# In patch 5.0, the key has "@" character in it which is not possible with python enums
return key.upper().replace(" ", "_").replace("@", "")
- def parse_data(self, data):
+ def parse_data(self, data) -> dict[str, Any]:
# for d in data: # Units, Abilities, Upgrades, Buffs, Effects
units = self.parse_simple("Units", data)
@@ -71,6 +76,9 @@ def parse_data(self, data):
key = v["buttonname"]
remapid = v.get("remapid")
+ if key == "" and v["index"] == 0:
+ key = v["name"]
+
if (not key) and (remapid is None):
assert v["buttonname"] == ""
continue
@@ -79,12 +87,12 @@ def parse_data(self, data):
if v["friendlyname"] != "":
key = v["friendlyname"]
else:
- sys.exit(f"Not mapped: {v !r}")
+ sys.exit(f"Not mapped: {v!r}")
key = key.upper().replace(" ", "_").replace("@", "")
if "name" in v:
- key = f'{v["name"].upper().replace(" ", "_")}_{key}'
+ key = f"{v['name'].upper().replace(' ', '_')}_{key}"
if "friendlyname" in v:
key = v["friendlyname"].upper().replace(" ", "_")
@@ -131,7 +139,7 @@ def parse_simple(self, d, data):
return units
- def generate_python_code(self, enums):
+ def generate_python_code(self, enums) -> None:
assert {"Units", "Abilities", "Upgrades", "Buffs", "Effects"} <= enums.keys()
sc2dir = Path(__file__).parent
@@ -139,7 +147,7 @@ def generate_python_code(self, enums):
idsdir.mkdir(exist_ok=True)
with (idsdir / "__init__.py").open("w") as f:
- initstring = f"__all__ = {[n.lower() for n in self.FILE_TRANSLATE.values()] !r}\n".replace("'", '"')
+ initstring = f"__all__ = {[n.lower() for n in self.FILE_TRANSLATE.values()]!r}\n".replace("'", '"')
f.write("\n".join([self.HEADER, initstring]))
for name, body in enums.items():
@@ -160,10 +168,17 @@ def __repr__(self) -> str:
if class_name == "BuffId":
code += f"""
@classmethod
- def _missing_(cls, value: int) -> "{class_name}":
+ def _missing_(cls, value: int) -> {class_name}:
return cls.NULL
""".split("\n")
+ if class_name == "AbilityId":
+ code += f"""
+ @classmethod
+ def _missing_(cls, value: int) -> {class_name}:
+ return cls.NULL_NULL
+""".split("\n")
+
code += f"""
for item in {class_name}:
globals()[item.name] = item
@@ -173,39 +188,25 @@ def _missing_(cls, value: int) -> "{class_name}":
with ids_file_path.open("w") as f:
f.write("\n".join(code))
- # Apply formatting
- try:
- subprocess.run(["poetry", "run", "yapf", ids_file_path, "-i"], check=True)
- except FileNotFoundError:
- logger.info(
- f"Yapf is not installed. Please use 'pip install yapf' to install yapf formatter.\nCould not autoformat file {ids_file_path}"
- )
-
if self.game_version is not None:
version_path = Path(__file__).parent / "ids" / "id_version.py"
- with open(version_path, "w") as f:
+ with Path(version_path).open("w") as f:
f.write(f'ID_VERSION_STRING = "{self.game_version}"\n')
- def update_ids_from_stableid_json(self):
- if self.game_version is None or ID_VERSION_STRING is None or ID_VERSION_STRING != self.game_version:
+ def update_ids_from_stableid_json(self) -> None:
+ if self.game_version is None or ID_VERSION_STRING is None or self.game_version != ID_VERSION_STRING:
if self.verbose and self.game_version is not None and ID_VERSION_STRING is not None:
logger.info(
f"Game version is different (Old: {self.game_version}, new: {ID_VERSION_STRING}. Updating ids to match game version"
)
stable_id_path = Path(self.DATA_JSON[self.PF])
- assert stable_id_path.is_file(), f"stable_id.json was not found at path \"{stable_id_path}\""
+ assert stable_id_path.is_file(), f'stable_id.json was not found at path "{stable_id_path}"'
with stable_id_path.open(encoding="utf-8") as data_file:
data = json.loads(data_file.read())
self.generate_python_code(self.parse_data(data))
- # Update game_data if this is a live game
- if self.game_data is not None:
- self.reimport_ids()
- self.update_game_data()
-
@staticmethod
- def reimport_ids():
-
+ def reimport_ids() -> None:
# Reload the newly written "id" files
# TODO This only re-imports modules, but if they haven't been imported, it will yield an error
importlib.reload(sys.modules["sc2.ids.ability_id"])
@@ -222,23 +223,6 @@ def reimport_ids():
importlib.reload(sys.modules["sc2.constants"])
- def update_game_data(self):
- """Re-generate the dicts from self.game_data.
- This should be done after the ids have been reimported."""
- ids = set(a.value for a in AbilityId if a.value != 0)
- self.game_data.abilities = {
- a.ability_id: AbilityData(self.game_data, a)
- for a in self.game_data._proto.abilities if a.ability_id in ids
- }
- # self.game_data.abilities = {
- # a.ability_id: AbilityData(self.game_data, a) for a in self.game_data._proto.abilities
- # }
- self.game_data.units = {
- u.unit_id: UnitTypeData(self.game_data, u)
- for u in self.game_data._proto.units if u.available
- }
- self.game_data.upgrades = {u.upgrade_id: UpgradeData(self.game_data, u) for u in self.game_data._proto.upgrades}
-
if __name__ == "__main__":
updater = IdGenerator()
diff --git a/sc2/ids/__init__.py b/sc2/ids/__init__.py
index af83a23e..24b6bc2a 100644
--- a/sc2/ids/__init__.py
+++ b/sc2/ids/__init__.py
@@ -1,3 +1,6 @@
+# pyre-ignore-all-errors[14]
+from __future__ import annotations
+
# DO NOT EDIT!
# This file was automatically generated by "generate_ids.py"
diff --git a/sc2/ids/ability_id.py b/sc2/ids/ability_id.py
index ca5ee3cb..955be493 100644
--- a/sc2/ids/ability_id.py
+++ b/sc2/ids/ability_id.py
@@ -1,6 +1,8 @@
+# pyre-ignore-all-errors[14]
+from __future__ import annotations
+
# DO NOT EDIT!
# This file was automatically generated by "generate_ids.py"
-
import enum
@@ -81,6 +83,7 @@ class AbilityId(enum.Enum):
RALLY_NEXUS = 207
RALLY_HATCHERY_UNITS = 211
RALLY_HATCHERY_WORKERS = 212
+ ROACHWARRENRESEARCH_ROACHWARRENRESEARCH = 215
RESEARCH_GLIALREGENERATION = 216
RESEARCH_TUNNELINGCLAWS = 217
ROACHWARRENRESEARCH_ROACHSUPPLY = 218
@@ -153,6 +156,7 @@ class AbilityId(enum.Enum):
LOAD_BUNKER = 407
UNLOADALL_BUNKER = 408
UNLOADUNIT_BUNKER = 410
+ COMMANDCENTERTRANSPORT_COMMANDCENTERTRANSPORT = 412
UNLOADALL_COMMANDCENTER = 413
UNLOADUNIT_COMMANDCENTER = 415
LOADALL_COMMANDCENTER = 416
@@ -180,6 +184,7 @@ class AbilityId(enum.Enum):
BARRACKSTRAIN_REAPER = 561
BARRACKSTRAIN_GHOST = 562
BARRACKSTRAIN_MARAUDER = 563
+ FACTORYTRAIN_FACTORYTRAIN = 590
FACTORYTRAIN_SIEGETANK = 591
FACTORYTRAIN_THOR = 594
FACTORYTRAIN_HELLION = 595
@@ -201,11 +206,13 @@ class AbilityId(enum.Enum):
ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1 = 656
ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2 = 657
ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3 = 658
+ MERCCOMPOUNDRESEARCH_MERCCOMPOUNDRESEARCH = 680
MERCCOMPOUNDRESEARCH_REAPERSPEED = 683
BUILD_NUKE = 710
BARRACKSTECHLABRESEARCH_STIMPACK = 730
RESEARCH_COMBATSHIELD = 731
RESEARCH_CONCUSSIVESHELLS = 732
+ FACTORYTECHLABRESEARCH_FACTORYTECHLABRESEARCH = 760
RESEARCH_INFERNALPREIGNITER = 761
FACTORYTECHLABRESEARCH_RESEARCHTRANSFORMATIONSERVOS = 763
RESEARCH_DRILLINGCLAWS = 764
@@ -214,6 +221,7 @@ class AbilityId(enum.Enum):
FACTORYTECHLABRESEARCH_RESEARCHARMORPIERCINGROCKETS = 767
RESEARCH_CYCLONERAPIDFIRELAUNCHERS = 768
RESEARCH_CYCLONELOCKONDAMAGE = 769
+ FACTORYTECHLABRESEARCH_CYCLONERESEARCHHURRICANETHRUSTERS = 770
RESEARCH_BANSHEECLOAKINGFIELD = 790
STARPORTTECHLABRESEARCH_RESEARCHMEDIVACENERGYUPGRADE = 792
RESEARCH_RAVENCORVIDREACTOR = 793
@@ -226,9 +234,9 @@ class AbilityId(enum.Enum):
RESEARCH_HIGHCAPACITYFUELTANKS = 804
RESEARCH_ADVANCEDBALLISTICS = 805
STARPORTTECHLABRESEARCH_RAVENRESEARCHENHANCEDMUNITIONS = 806
+ STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX = 807
RESEARCH_PERSONALCLOAKING = 820
- GHOSTACADEMYRESEARCH_RESEARCHGHOSTENERGYUPGRADE = 821
- GHOSTACADEMYRESEARCH_RESEARCHENHANCEDSHOCKWAVES = 822
+ ARMORYRESEARCH_ARMORYRESEARCH = 850
ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL1 = 852
ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL2 = 853
ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL3 = 854
@@ -296,10 +304,12 @@ class AbilityId(enum.Enum):
FORGERESEARCH_PROTOSSSHIELDSLEVEL1 = 1068
FORGERESEARCH_PROTOSSSHIELDSLEVEL2 = 1069
FORGERESEARCH_PROTOSSSHIELDSLEVEL3 = 1070
+ ROBOTICSBAYRESEARCH_ROBOTICSBAYRESEARCH = 1092
RESEARCH_GRAVITICBOOSTER = 1093
RESEARCH_GRAVITICDRIVE = 1094
RESEARCH_EXTENDEDTHERMALLANCE = 1097
ROBOTICSBAYRESEARCH_RESEARCHIMMORTALREVIVE = 1099
+ TEMPLARARCHIVESRESEARCH_TEMPLARARCHIVESRESEARCH = 1122
RESEARCH_PSISTORM = 1126
ZERGBUILD_HATCHERY = 1152
ZERGBUILD_CREEPTUMOR = 1153
@@ -335,6 +345,7 @@ class AbilityId(enum.Enum):
CANCEL_MORPHHIVE = 1219
UPGRADETOGREATERSPIRE_GREATERSPIRE = 1220
CANCEL_MORPHGREATERSPIRE = 1221
+ LAIRRESEARCH_LAIRRESEARCH = 1222
RESEARCH_PNEUMATIZEDCARAPACE = 1223
LAIRRESEARCH_EVOLVEVENTRALSACKS = 1224
RESEARCH_BURROW = 1225
@@ -342,6 +353,7 @@ class AbilityId(enum.Enum):
RESEARCH_ZERGLINGMETABOLICBOOST = 1253
RESEARCH_GROOVEDSPINES = 1282
RESEARCH_MUSCULARAUGMENTS = 1283
+ HYDRALISKDENRESEARCH_RESEARCHFRENZY = 1284
HYDRALISKDENRESEARCH_RESEARCHLURKERRANGE = 1286
RESEARCH_ZERGFLYERATTACKLEVEL1 = 1312
RESEARCH_ZERGFLYERATTACKLEVEL2 = 1313
@@ -408,7 +420,7 @@ class AbilityId(enum.Enum):
CANCEL_MORPHOVERSEER = 1449
UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS = 1450
CANCEL_MORPHPLANETARYFORTRESS = 1451
- RESEARCH_PATHOGENGLANDS = 1454
+ INFESTATIONPITRESEARCH_INFESTATIONPITRESEARCH = 1452
RESEARCH_NEURALPARASITE = 1455
INFESTATIONPITRESEARCH_RESEARCHLOCUSTLIFETIMEINCREASE = 1456
INFESTATIONPITRESEARCH_EVOLVEAMORPHOUSARMORCLOUD = 1457
@@ -432,6 +444,7 @@ class AbilityId(enum.Enum):
RESEARCH_BATTLECRUISERWEAPONREFIT = 1532
FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE = 1533
FUSIONCORERESEARCH_RESEARCHRAPIDREIGNITIONSYSTEM = 1534
+ FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE = 1535
CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1 = 1562
CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2 = 1563
CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3 = 1564
@@ -455,9 +468,11 @@ class AbilityId(enum.Enum):
TRAINQUEEN_QUEEN = 1632
BURROWCREEPTUMORDOWN_BURROWDOWN = 1662
TRANSFUSION_TRANSFUSION = 1664
+ TECHLABMORPH_TECHLABMORPH = 1666
BARRACKSTECHLABMORPH_TECHLABBARRACKS = 1668
FACTORYTECHLABMORPH_TECHLABFACTORY = 1670
STARPORTTECHLABMORPH_TECHLABSTARPORT = 1672
+ REACTORMORPH_REACTORMORPH = 1674
BARRACKSREACTORMORPH_REACTOR = 1676
FACTORYREACTORMORPH_REACTOR = 1678
STARPORTREACTORMORPH_REACTOR = 1680
@@ -487,10 +502,13 @@ class AbilityId(enum.Enum):
BUILD_NYDUSWORM = 1768
BUILDNYDUSCANAL_SUMMONNYDUSCANALATTACKER = 1769
BUILDNYDUSCANAL_CANCEL = 1798
+ BROODLORDHANGAR_BROODLORDHANGAR = 1799
EFFECT_CHARGE = 1819
+ TOWERCAPTURE_TOWERCAPTURE = 1820
HERDINTERACT_HERD = 1821
FRENZY_FRENZY = 1823
CONTAMINATE_CONTAMINATE = 1825
+ SHATTER_SHATTER = 1827
INFESTEDTERRANSLAYEGG_INFESTEDTERRANS = 1829
CANCEL_QUEUEPASIVE = 1831
CANCELSLOT_QUEUEPASSIVE = 1832
@@ -499,8 +517,11 @@ class AbilityId(enum.Enum):
MORPHTOGHOSTALTERNATE_MOVE = 1835
MORPHTOGHOSTNOVA_MOVE = 1837
DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY = 1839
+ MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS_MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS = 1841
MORPHTOCOLLAPSIBLETERRANTOWERDEBRIS_CANCEL = 1842
+ MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT_MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT = 1843
MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT_CANCEL = 1844
+ MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT_MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT = 1845
MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPRIGHT_CANCEL = 1846
MORPH_MOTHERSHIP = 1847
CANCEL_MORPHMOTHERSHIP = 1848
@@ -543,15 +564,23 @@ class AbilityId(enum.Enum):
MORPH_HELLION = 1978
RESTORESHIELDS_RESTORESHIELDS = 1980
SCRYER_SCRYER = 1982
+ BURROWCHARGETRIAL_BURROWCHARGETRIAL = 1984
+ LEECHRESOURCES_LEECHRESOURCES = 1986
LEECHRESOURCES_CANCEL = 1987
SNIPEDOT_SNIPEDOT = 1988
SWARMHOSTSPAWNLOCUSTS_LOCUSTMP = 1990
CLONE_CLONE = 1992
BUILDINGSHIELD_BUILDINGSHIELD = 1994
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS_MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS = 1996
MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS_CANCEL = 1997
MORPH_HELLBAT = 1998
BUILDINGSTASIS_BUILDINGSTASIS = 2000
+ RESOURCEBLOCKER_RESOURCEBLOCKER = 2002
+ RESOURCESTUN_RESOURCESTUN = 2004
MAXIUMTHRUST_MAXIMUMTHRUST = 2006
+ SACRIFICE_SACRIFICE = 2008
+ BURROWCHARGEMP_BURROWCHARGEMP = 2010
+ BURROWCHARGEREVD_BURROWCHARGEREVD = 2012
BURROWDOWN_SWARMHOST = 2014
MORPHTOSWARMHOSTBURROWEDMP_CANCEL = 2015
BURROWUP_SWARMHOST = 2016
@@ -576,6 +605,7 @@ class AbilityId(enum.Enum):
PROTOSSBUILDINGQUEUE_CANCELSLOT = 2076
QUE8_CANCEL = 2077
QUE8_CANCELSLOT = 2078
+ TESTZERG_TESTZERG = 2079
TESTZERG_CANCEL = 2080
BEHAVIOR_BUILDINGATTACKON = 2081
BEHAVIOR_BUILDINGATTACKOFF = 2082
@@ -622,7 +652,9 @@ class AbilityId(enum.Enum):
ORACLEREVELATIONMODE_CANCEL = 2149
ORACLENORMALMODE_ORACLENORMALMODE = 2150
ORACLENORMALMODE_CANCEL = 2151
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT = 2152
MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT_CANCEL = 2153
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT = 2154
MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_CANCEL = 2155
VOIDSIPHON_VOIDSIPHON = 2156
ULTRALISKWEAPONCOOLDOWN_ULTRALISKWEAPONCOOLDOWN = 2158
@@ -677,6 +709,22 @@ class AbilityId(enum.Enum):
SNOWREFINERY_TERRAN_EXTENDINGBRIDGENESHORT10_BRIDGERETRACT = 2256
SNOWREFINERY_TERRAN_EXTENDINGBRIDGENWSHORT10OUT_BRIDGEEXTEND = 2258
SNOWREFINERY_TERRAN_EXTENDINGBRIDGENWSHORT10_BRIDGERETRACT = 2260
+ TARSONIS_DOORN_TARSONIS_DOORN = 2262
+ TARSONIS_DOORNLOWERED_TARSONIS_DOORNLOWERED = 2264
+ TARSONIS_DOORNE_TARSONIS_DOORNE = 2266
+ TARSONIS_DOORNELOWERED_TARSONIS_DOORNELOWERED = 2268
+ TARSONIS_DOORE_TARSONIS_DOORE = 2270
+ TARSONIS_DOORELOWERED_TARSONIS_DOORELOWERED = 2272
+ TARSONIS_DOORNW_TARSONIS_DOORNW = 2274
+ TARSONIS_DOORNWLOWERED_TARSONIS_DOORNWLOWERED = 2276
+ COMPOUNDMANSION_DOORN_COMPOUNDMANSION_DOORN = 2278
+ COMPOUNDMANSION_DOORNLOWERED_COMPOUNDMANSION_DOORNLOWERED = 2280
+ COMPOUNDMANSION_DOORNE_COMPOUNDMANSION_DOORNE = 2282
+ COMPOUNDMANSION_DOORNELOWERED_COMPOUNDMANSION_DOORNELOWERED = 2284
+ COMPOUNDMANSION_DOORE_COMPOUNDMANSION_DOORE = 2286
+ COMPOUNDMANSION_DOORELOWERED_COMPOUNDMANSION_DOORELOWERED = 2288
+ COMPOUNDMANSION_DOORNW_COMPOUNDMANSION_DOORNW = 2290
+ COMPOUNDMANSION_DOORNWLOWERED_COMPOUNDMANSION_DOORNWLOWERED = 2292
ARMORYRESEARCHSWARM_TERRANVEHICLEANDSHIPWEAPONSLEVEL1 = 2294
ARMORYRESEARCHSWARM_TERRANVEHICLEANDSHIPWEAPONSLEVEL2 = 2295
ARMORYRESEARCHSWARM_TERRANVEHICLEANDSHIPWEAPONSLEVEL3 = 2296
@@ -725,6 +773,7 @@ class AbilityId(enum.Enum):
HALLUCINATION_DISRUPTOR = 2389
HALLUCINATION_ADEPT = 2391
EFFECT_VOIDRAYPRISMATICALIGNMENT = 2393
+ SEEKERDUMMYCHANNEL_SEEKERDUMMYCHANNEL = 2395
AIURLIGHTBRIDGENE8OUT_BRIDGEEXTEND = 2397
AIURLIGHTBRIDGENE8_BRIDGERETRACT = 2399
AIURLIGHTBRIDGENE10OUT_BRIDGEEXTEND = 2401
@@ -818,6 +867,7 @@ class AbilityId(enum.Enum):
CANCEL_ADEPTPHASESHIFT = 2594
CANCEL_ADEPTSHADEPHASESHIFT = 2596
SLAYNELEMENTALGRAB_SLAYNELEMENTALGRAB = 2598
+ MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS = 2600
MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_CANCEL = 2601
PORTCITY_BRIDGE_UNITNE8OUT_BRIDGEEXTEND = 2602
PORTCITY_BRIDGE_UNITNE8_BRIDGERETRACT = 2604
@@ -870,11 +920,15 @@ class AbilityId(enum.Enum):
TEMPESTDISRUPTIONBLAST_TEMPESTDISRUPTIONBLAST = 2698
CANCEL_TEMPESTDISRUPTIONBLAST = 2699
EFFECT_SHADOWSTRIDE = 2700
+ LAUNCHINTERCEPTORS_LAUNCHINTERCEPTORS = 2702
EFFECT_SPAWNLOCUSTS = 2704
LOCUSTMPFLYINGSWOOPATTACK_LOCUSTMPFLYINGSWOOP = 2706
MORPH_OVERLORDTRANSPORT = 2708
CANCEL_MORPHOVERLORDTRANSPORT = 2709
+ BYPASSARMOR_BYPASSARMOR = 2710
+ BYPASSARMORDRONECU_BYPASSARMORDRONECU = 2712
EFFECT_GHOSTSNIPE = 2714
+ CHANNELSNIPE_CANCEL = 2715
PURIFYMORPHPYLON_MOTHERSHIPCOREWEAPON = 2716
PURIFYMORPHPYLONBACK_MOTHERSHIPCOREWEAPON = 2718
RESEARCH_SHADOWSTRIKE = 2720
@@ -890,15 +944,19 @@ class AbilityId(enum.Enum):
LOKIMISSILEPODS_MISSILEPODS = 2764
HUTTRANSPORT_HUTLOAD = 2766
HUTTRANSPORT_HUTUNLOADALL = 2767
+ MORPHTOTECHREACTOR_MORPHTOTECHREACTOR = 2771
LEVIATHANSPAWNBROODLORD_SPAWNBROODLORD = 2773
SS_CARRIERBOSSATTACKLAUNCH_SS_SHOOTING = 2775
SS_CARRIERSPAWNINTERCEPTOR_SS_CARRIERSPAWNINTERCEPTOR = 2777
SS_CARRIERBOSSATTACKTARGET_SS_SHOOTING = 2779
SS_FIGHTERBOMB_SS_FIGHTERBOMB = 2781
+ SS_LIGHTNINGPROJECTORTOGGLE_SS_LIGHTNINGPROJECTORTOGGLE = 2783
SS_PHOENIXSHOOTING_SS_SHOOTING = 2785
+ SS_POWERUPMORPHTOBOMB_SS_POWERUPMORPHTOBOMB = 2787
SS_BATTLECRUISERMISSILEATTACK_SS_SHOOTING = 2789
SS_LEVIATHANSPAWNBOMBS_SS_LEVIATHANSPAWNBOMBS = 2791
SS_BATTLECRUISERHUNTERSEEKERATTACK_SS_SHOOTING = 2793
+ SS_POWERUPMORPHTOHEALTH_SS_POWERUPMORPHTOHEALTH = 2795
SS_LEVIATHANTENTACLEATTACKL1NODELAY_SS_LEVIATHANTENTACLEATTACKL1NODELAY = 2797
SS_LEVIATHANTENTACLEATTACKL2NODELAY_SS_LEVIATHANTENTACLEATTACKL2NODELAY = 2799
SS_LEVIATHANTENTACLEATTACKR1NODELAY_SS_LEVIATHANTENTACLEATTACKR1NODELAY = 2801
@@ -908,6 +966,8 @@ class AbilityId(enum.Enum):
SS_TERRATRONSAWATTACK_SS_TERRATRONSAWATTACK = 2809
SS_WRAITHATTACK_SS_SHOOTING = 2811
SS_SWARMGUARDIANATTACK_SS_SHOOTING = 2813
+ SS_POWERUPMORPHTOSIDEMISSILES_SS_POWERUPMORPHTOSIDEMISSILES = 2815
+ SS_POWERUPMORPHTOSTRONGERMISSILES_SS_POWERUPMORPHTOSTRONGERMISSILES = 2817
SS_SCOUTATTACK_SS_SHOOTING = 2819
SS_INTERCEPTORATTACK_SS_SHOOTING = 2821
SS_CORRUPTORATTACK_SS_SHOOTING = 2823
@@ -916,6 +976,7 @@ class AbilityId(enum.Enum):
SS_LEVIATHANTENTACLEATTACKL1_SS_LEVIATHANTENTACLEATTACKL1 = 2829
SS_LEVIATHANTENTACLEATTACKR2_SS_LEVIATHANTENTACLEATTACKR2 = 2831
SS_SCIENCEVESSELATTACK_SS_SHOOTING = 2833
+ HEALREDIRECT_HEALREDIRECT = 2835
LURKERASPECTFROMHYDRALISKBURROWED_LURKERFROMHYDRALISKBURROWED = 2836
LURKERASPECTFROMHYDRALISKBURROWED_CANCEL = 2837
UPGRADETOLURKERDEN_LURKERDEN = 2838
@@ -951,6 +1012,7 @@ class AbilityId(enum.Enum):
EGGPOP_EGGPOP = 2911
EXPERIMENTALPLASMAGUN_EXPERIMENTALPLASMAGUN = 2913
GATHERSPECIALOBJECT_GATHERSPECIALOBJECT = 2915
+ KERRIGANSEARCH_KERRIGANSEARCH = 2917
LOKIUNDOCK_LIFT = 2919
MINDBLAST_MINDBLAST = 2921
MORPHTOINFESTEDCIVILIAN_MORPHTOINFESTEDCIVILIAN = 2923
@@ -975,11 +1037,16 @@ class AbilityId(enum.Enum):
BURROWINFESTEDCIVILIANDOWN_BURROWDOWN = 2963
BURROWINFESTEDCIVILIANUP_BURROWUP = 2965
SELENDISHANGAR_INTERCEPTOR = 2967
+ FORCEFIELDBEAM_FORCEFIELDBEAM = 2987
SIEGEBREAKERSIEGE_SIEGEMODE = 2989
SIEGEBREAKERUNSIEGE_UNSIEGE = 2991
+ SOULCHANNEL_SOULCHANNEL = 2993
SOULCHANNEL_CANCEL = 2994
+ PERDITIONTURRETBURROW_PERDITIONTURRETBURROW = 2995
+ PERDITIONTURRETUNBURROW_PERDITIONTURRETUNBURROW = 2997
SENTRYGUNBURROW_BURROWTURRET = 2999
SENTRYGUNUNBURROW_UNBURROWTURRET = 3001
+ SPIDERMINEUNBURROWRANGEDUMMY_SPIDERMINEUNBURROWRANGEDUMMY = 3003
GRAVITONPRISON_GRAVITONPRISON = 3005
IMPLOSION_IMPLOSION = 3007
OMEGASTORM_OMEGASTORM = 3009
@@ -1029,6 +1096,8 @@ class AbilityId(enum.Enum):
VAL03QUEENOFBLADESBURROW_BURROWDOWN = 3178
VAL03QUEENOFBLADESDEEPTUNNEL_DEEPTUNNEL = 3180
VAL03QUEENOFBLADESUNBURROW_BURROWUP = 3182
+ VULTURESPIDERMINEBURROW_VULTURESPIDERMINEBURROW = 3184
+ VULTURESPIDERMINEUNBURROW_VULTURESPIDERMINEUNBURROW = 3186
LOKIYAMATO_LOKIYAMATOGUN = 3188
DUKESREVENGEYAMATO_YAMATOGUN = 3190
ZERATULBLINK_ZERATULBLINK = 3192
@@ -1072,11 +1141,21 @@ class AbilityId(enum.Enum):
INFESTEDMONSTERTRAIN_INFESTEDABOMINATION = 3246
BIODOMETRANSPORT_BIODOMELOAD = 3274
BIODOMETRANSPORT_BIODOMEUNLOADALL = 3275
+ CHECKSTATION_CHECKSTATION = 3279
+ CHECKSTATIONDIAGONALBLUR_CHECKSTATIONDIAGONALBLUR = 3281
+ CHECKSTATIONDIAGONALULBR_CHECKSTATIONDIAGONALULBR = 3283
+ CHECKSTATIONVERTICAL_CHECKSTATIONVERTICAL = 3285
+ CHECKSTATIONOPENED_CHECKSTATIONOPENED = 3287
+ CHECKSTATIONDIAGONALBLUROPENED_CHECKSTATIONDIAGONALBLUROPENED = 3289
+ CHECKSTATIONDIAGONALULBROPENED_CHECKSTATIONDIAGONALULBROPENED = 3291
+ CHECKSTATIONVERTICALOPENED_CHECKSTATIONVERTICALOPENED = 3293
ATTACKALLOWSINVULNERABLE_ATTACKALLOWSINVULNERABLE = 3295
ATTACKALLOWSINVULNERABLE_ATTACKTOWARDS = 3296
ATTACKALLOWSINVULNERABLE_ATTACKBARRAGE = 3297
ZERATULSTUN_ZERATULSTUN = 3298
+ WRAITHCLOAK_WRAITHCLOAK = 3300
WRAITHCLOAK_CLOAKOFF = 3301
+ TECHREACTORMORPH_TECHREACTORMORPH = 3302
BARRACKSTECHREACTORMORPH_TECHLABBARRACKS = 3304
FACTORYTECHREACTORMORPH_TECHLABFACTORY = 3306
STARPORTTECHREACTORMORPH_TECHLABSTARPORT = 3308
@@ -1091,6 +1170,7 @@ class AbilityId(enum.Enum):
THORREBORN_CANCEL = 3323
SPECTRENUKE_SPECTRENUKECALLDOWN = 3324
SPECTRENUKE_CANCEL = 3325
+ SPECTRENUKESILOARMMAGAZINE_SPECTRENUKESILOARMMAGAZINE = 3326
SPECTRENUKESILOARMMAGAZINE_SPECTRENUKEARM = 3327
COLONISTSHIPLIFTOFF_LIFT = 3346
COLONISTSHIPLAND_LAND = 3348
@@ -1108,6 +1188,7 @@ class AbilityId(enum.Enum):
TEMPLEDOORUPURDL_TEMPLEDOORUPURDL = 3372
PSYTROUSOXIDE_PSYTROUSOXIDEON = 3374
PSYTROUSOXIDE_PSYTROUSOXIDEOFF = 3375
+ VOIDSEEKERDOCK_VOIDSEEKERDOCK = 3376
BIOPLASMIDDISCHARGE_BIOPLASMIDDISCHARGE = 3378
WRECKINGCREWASSAULTMODE_ASSAULTMODE = 3380
WRECKINGCREWFIGHTERMODE_FIGHTERMODE = 3382
@@ -1122,8 +1203,19 @@ class AbilityId(enum.Enum):
PICKUPARCADE_PICKUP = 3401
PICKUPGAS100_PICKUPGAS100 = 3403
PICKUPMINERALS100_PICKUPMINERALS100 = 3405
+ PICKUPHEALTH25_PICKUPHEALTH25 = 3407
+ PICKUPHEALTH50_PICKUPHEALTH50 = 3409
+ PICKUPHEALTH100_PICKUPHEALTH100 = 3411
+ PICKUPHEALTHFULL_PICKUPHEALTHFULL = 3413
+ PICKUPENERGY25_PICKUPENERGY25 = 3415
+ PICKUPENERGY50_PICKUPENERGY50 = 3417
+ PICKUPENERGY100_PICKUPENERGY100 = 3419
+ PICKUPENERGYFULL_PICKUPENERGYFULL = 3421
TAURENSTIMPACK_STIM = 3423
+ TESTINVENTORY_TESTINVENTORY = 3425
+ TESTPAWN_TESTPAWN = 3434
TESTREVIVE_SCV = 3454
+ TESTSELL_TESTSELL = 3484
TESTINTERACT_DESIGNATE = 3514
CLIFFDOOROPEN0_SPACEPLATFORMDOOROPEN = 3515
CLIFFDOORCLOSE0_SPACEPLATFORMDOORCLOSE = 3517
@@ -1172,6 +1264,7 @@ class AbilityId(enum.Enum):
SPECTREHOLDFIRE_SPECTREHOLDFIRE = 3621
SPECTREWEAPONSFREE_WEAPONSFREE = 3623
GWALEARN_TESTLEARN = 3625
+ REAPERPLACEMENTMORPH_REAPERPLACEMENTMORPH = 3645
LIGHTBRIDGEOFFTOPRIGHT_LIGHTBRIDGEOFF = 3647
LIGHTBRIDGEONTOPRIGHT_LIGHTBRIDGEON = 3649
TESTHEROGRAB_GRABZERGLING = 3651
@@ -1243,7 +1336,6 @@ class AbilityId(enum.Enum):
NEXUSSHIELDRECHARGEONPYLON_NEXUSSHIELDRECHARGEONPYLON = 3761
INFESTORENSNARE_INFESTORENSNARE = 3763
EFFECT_RESTORE = 3765
- SHIELDBATTERYRECHARGECHANNELED_STOP = 3766
NEXUSSHIELDOVERCHARGE_NEXUSSHIELDOVERCHARGE = 3767
NEXUSSHIELDOVERCHARGEOFF_NEXUSSHIELDOVERCHARGEOFF = 3769
ATTACK_BATTLECRUISER = 3771
@@ -1266,6 +1358,7 @@ class AbilityId(enum.Enum):
MOVE = 3794
PATROL = 3795
UNLOADUNIT = 3796
+ LOADOUTSPRAY_LOADOUTSPRAY1 = 3797
LOADOUTSPRAY_LOADOUTSPRAY2 = 3798
LOADOUTSPRAY_LOADOUTSPRAY3 = 3799
LOADOUTSPRAY_LOADOUTSPRAY4 = 3800
@@ -1279,314 +1372,30 @@ class AbilityId(enum.Enum):
LOADOUTSPRAY_LOADOUTSPRAY12 = 3808
LOADOUTSPRAY_LOADOUTSPRAY13 = 3809
LOADOUTSPRAY_LOADOUTSPRAY14 = 3810
- DUMMYABIL0_MEDIVACSPEEDBOOST = 3811
- DUMMYABIL1_MEDIVACSPEEDBOOST = 3812
- DUMMYABIL2_MEDIVACSPEEDBOOST = 3813
- DUMMYABIL3_MEDIVACSPEEDBOOST = 3814
- DUMMYABIL4_MEDIVACSPEEDBOOST = 3815
- DUMMYABIL5_MEDIVACSPEEDBOOST = 3816
- DUMMYABIL6_MEDIVACSPEEDBOOST = 3817
- DUMMYABIL7_MEDIVACSPEEDBOOST = 3818
- DUMMYABIL8_MEDIVACSPEEDBOOST = 3819
- DUMMYABIL9_MEDIVACSPEEDBOOST = 3820
- DUMMYABIL10_MEDIVACSPEEDBOOST = 3821
- DUMMYABIL11_MEDIVACSPEEDBOOST = 3822
- DUMMYABIL12_MEDIVACSPEEDBOOST = 3823
- DUMMYABIL13_MEDIVACSPEEDBOOST = 3824
- DUMMYABIL14_MEDIVACSPEEDBOOST = 3825
- DUMMYABIL15_MEDIVACSPEEDBOOST = 3826
- DUMMYABIL16_MEDIVACSPEEDBOOST = 3827
- DUMMYABIL17_MEDIVACSPEEDBOOST = 3828
- DUMMYABIL18_MEDIVACSPEEDBOOST = 3829
- DUMMYABIL19_MEDIVACSPEEDBOOST = 3830
- DUMMYABIL20_MEDIVACSPEEDBOOST = 3831
- DUMMYABIL21_MEDIVACSPEEDBOOST = 3832
- DUMMYABIL22_MEDIVACSPEEDBOOST = 3833
- DUMMYABIL23_MEDIVACSPEEDBOOST = 3834
- DUMMYABIL24_MEDIVACSPEEDBOOST = 3835
- DUMMYABIL25_MEDIVACSPEEDBOOST = 3836
- DUMMYABIL26_MEDIVACSPEEDBOOST = 3837
- DUMMYABIL27_MEDIVACSPEEDBOOST = 3838
- DUMMYABIL28_MEDIVACSPEEDBOOST = 3839
- DUMMYABIL29_MEDIVACSPEEDBOOST = 3840
- DUMMYABIL30_MEDIVACSPEEDBOOST = 3841
- DUMMYABIL31_MEDIVACSPEEDBOOST = 3842
- DUMMYABIL32_MEDIVACSPEEDBOOST = 3843
- DUMMYABIL33_MEDIVACSPEEDBOOST = 3844
- DUMMYABIL34_MEDIVACSPEEDBOOST = 3845
- DUMMYABIL35_MEDIVACSPEEDBOOST = 3846
- DUMMYABIL36_MEDIVACSPEEDBOOST = 3847
- DUMMYABIL37_MEDIVACSPEEDBOOST = 3848
- DUMMYABIL38_MEDIVACSPEEDBOOST = 3849
- DUMMYABIL39_MEDIVACSPEEDBOOST = 3850
- DUMMYABIL40_MEDIVACSPEEDBOOST = 3851
- DUMMYABIL41_MEDIVACSPEEDBOOST = 3852
- DUMMYABIL42_MEDIVACSPEEDBOOST = 3853
- DUMMYABIL43_MEDIVACSPEEDBOOST = 3854
- DUMMYABIL44_MEDIVACSPEEDBOOST = 3855
- DUMMYABIL45_MEDIVACSPEEDBOOST = 3856
- DUMMYABIL46_MEDIVACSPEEDBOOST = 3857
- DUMMYABIL47_MEDIVACSPEEDBOOST = 3858
- DUMMYABIL48_MEDIVACSPEEDBOOST = 3859
- DUMMYABIL49_MEDIVACSPEEDBOOST = 3860
- DUMMYABIL50_MEDIVACSPEEDBOOST = 3861
- DUMMYABIL51_MEDIVACSPEEDBOOST = 3862
- DUMMYABIL52_MEDIVACSPEEDBOOST = 3863
- DUMMYABIL53_MEDIVACSPEEDBOOST = 3864
- DUMMYABIL54_MEDIVACSPEEDBOOST = 3865
- DUMMYABIL55_MEDIVACSPEEDBOOST = 3866
- DUMMYABIL56_MEDIVACSPEEDBOOST = 3867
- DUMMYABIL57_MEDIVACSPEEDBOOST = 3868
- DUMMYABIL58_MEDIVACSPEEDBOOST = 3869
- DUMMYABIL59_MEDIVACSPEEDBOOST = 3870
- DUMMYABIL60_MEDIVACSPEEDBOOST = 3871
- DUMMYABIL61_MEDIVACSPEEDBOOST = 3872
- DUMMYABIL62_MEDIVACSPEEDBOOST = 3873
- DUMMYABIL63_MEDIVACSPEEDBOOST = 3874
- DUMMYABIL64_MEDIVACSPEEDBOOST = 3875
- DUMMYABIL65_MEDIVACSPEEDBOOST = 3876
- DUMMYABIL66_MEDIVACSPEEDBOOST = 3877
- DUMMYABIL67_MEDIVACSPEEDBOOST = 3878
- DUMMYABIL68_MEDIVACSPEEDBOOST = 3879
- DUMMYABIL69_MEDIVACSPEEDBOOST = 3880
- DUMMYABIL70_MEDIVACSPEEDBOOST = 3881
- DUMMYABIL71_MEDIVACSPEEDBOOST = 3882
- DUMMYABIL72_MEDIVACSPEEDBOOST = 3883
- DUMMYABIL73_MEDIVACSPEEDBOOST = 3884
- DUMMYABIL74_MEDIVACSPEEDBOOST = 3885
- DUMMYABIL75_MEDIVACSPEEDBOOST = 3886
- DUMMYABIL76_MEDIVACSPEEDBOOST = 3887
- DUMMYABIL77_MEDIVACSPEEDBOOST = 3888
- DUMMYABIL78_MEDIVACSPEEDBOOST = 3889
- DUMMYABIL79_MEDIVACSPEEDBOOST = 3890
- DUMMYABIL80_MEDIVACSPEEDBOOST = 3891
- DUMMYABIL81_MEDIVACSPEEDBOOST = 3892
- DUMMYABIL82_MEDIVACSPEEDBOOST = 3893
- DUMMYABIL83_MEDIVACSPEEDBOOST = 3894
- DUMMYABIL84_MEDIVACSPEEDBOOST = 3895
- DUMMYABIL85_MEDIVACSPEEDBOOST = 3896
- DUMMYABIL86_MEDIVACSPEEDBOOST = 3897
- DUMMYABIL87_MEDIVACSPEEDBOOST = 3898
- DUMMYABIL88_MEDIVACSPEEDBOOST = 3899
- DUMMYABIL89_MEDIVACSPEEDBOOST = 3900
- DUMMYABIL90_MEDIVACSPEEDBOOST = 3901
- DUMMYABIL91_MEDIVACSPEEDBOOST = 3902
- DUMMYABIL92_MEDIVACSPEEDBOOST = 3903
- DUMMYABIL93_MEDIVACSPEEDBOOST = 3904
- DUMMYABIL94_MEDIVACSPEEDBOOST = 3905
- DUMMYABIL95_MEDIVACSPEEDBOOST = 3906
- DUMMYABIL96_MEDIVACSPEEDBOOST = 3907
- DUMMYABIL97_MEDIVACSPEEDBOOST = 3908
- DUMMYABIL98_MEDIVACSPEEDBOOST = 3909
- DUMMYABIL99_MEDIVACSPEEDBOOST = 3910
- DUMMYABIL100_MEDIVACSPEEDBOOST = 3911
- DUMMYABIL101_MEDIVACSPEEDBOOST = 3912
- DUMMYABIL102_MEDIVACSPEEDBOOST = 3913
- DUMMYABIL103_MEDIVACSPEEDBOOST = 3914
- DUMMYABIL104_MEDIVACSPEEDBOOST = 3915
- DUMMYABIL105_MEDIVACSPEEDBOOST = 3916
- DUMMYABIL106_MEDIVACSPEEDBOOST = 3917
- DUMMYABIL107_MEDIVACSPEEDBOOST = 3918
- DUMMYABIL108_MEDIVACSPEEDBOOST = 3919
- DUMMYABIL109_MEDIVACSPEEDBOOST = 3920
- DUMMYABIL110_MEDIVACSPEEDBOOST = 3921
- DUMMYABIL111_MEDIVACSPEEDBOOST = 3922
- DUMMYABIL112_MEDIVACSPEEDBOOST = 3923
- DUMMYABIL113_MEDIVACSPEEDBOOST = 3924
- DUMMYABIL114_MEDIVACSPEEDBOOST = 3925
- DUMMYABIL115_MEDIVACSPEEDBOOST = 3926
- DUMMYABIL116_MEDIVACSPEEDBOOST = 3927
- DUMMYABIL117_MEDIVACSPEEDBOOST = 3928
- DUMMYABIL118_MEDIVACSPEEDBOOST = 3929
- DUMMYABIL119_MEDIVACSPEEDBOOST = 3930
- DUMMYABIL120_MEDIVACSPEEDBOOST = 3931
- DUMMYABIL121_MEDIVACSPEEDBOOST = 3932
- DUMMYABIL122_MEDIVACSPEEDBOOST = 3933
- DUMMYABIL123_MEDIVACSPEEDBOOST = 3934
- DUMMYABIL124_MEDIVACSPEEDBOOST = 3935
- DUMMYABIL125_MEDIVACSPEEDBOOST = 3936
- DUMMYABIL126_MEDIVACSPEEDBOOST = 3937
- DUMMYABIL127_MEDIVACSPEEDBOOST = 3938
- DUMMYABIL128_MEDIVACSPEEDBOOST = 3939
- DUMMYABIL129_MEDIVACSPEEDBOOST = 3940
- DUMMYABIL130_MEDIVACSPEEDBOOST = 3941
- DUMMYABIL131_MEDIVACSPEEDBOOST = 3942
- DUMMYABIL132_MEDIVACSPEEDBOOST = 3943
- DUMMYABIL133_MEDIVACSPEEDBOOST = 3944
- DUMMYABIL134_MEDIVACSPEEDBOOST = 3945
- DUMMYABIL135_MEDIVACSPEEDBOOST = 3946
- DUMMYABIL136_MEDIVACSPEEDBOOST = 3947
- DUMMYABIL137_MEDIVACSPEEDBOOST = 3948
- DUMMYABIL138_MEDIVACSPEEDBOOST = 3949
- DUMMYABIL139_MEDIVACSPEEDBOOST = 3950
- DUMMYABIL140_MEDIVACSPEEDBOOST = 3951
- DUMMYABIL141_MEDIVACSPEEDBOOST = 3952
- DUMMYABIL142_MEDIVACSPEEDBOOST = 3953
- DUMMYABIL143_MEDIVACSPEEDBOOST = 3954
- DUMMYABIL144_MEDIVACSPEEDBOOST = 3955
- DUMMYABIL145_MEDIVACSPEEDBOOST = 3956
- DUMMYABIL146_MEDIVACSPEEDBOOST = 3957
- DUMMYABIL147_MEDIVACSPEEDBOOST = 3958
- DUMMYABIL148_MEDIVACSPEEDBOOST = 3959
- DUMMYABIL149_MEDIVACSPEEDBOOST = 3960
- DUMMYABIL150_MEDIVACSPEEDBOOST = 3961
- DUMMYABIL151_MEDIVACSPEEDBOOST = 3962
- DUMMYABIL152_MEDIVACSPEEDBOOST = 3963
- DUMMYABIL153_MEDIVACSPEEDBOOST = 3964
- DUMMYABIL154_MEDIVACSPEEDBOOST = 3965
- DUMMYABIL155_MEDIVACSPEEDBOOST = 3966
- DUMMYABIL156_MEDIVACSPEEDBOOST = 3967
- DUMMYABIL157_MEDIVACSPEEDBOOST = 3968
- DUMMYABIL158_MEDIVACSPEEDBOOST = 3969
- DUMMYABIL159_MEDIVACSPEEDBOOST = 3970
- DUMMYABIL160_MEDIVACSPEEDBOOST = 3971
- DUMMYABIL161_MEDIVACSPEEDBOOST = 3972
- DUMMYABIL162_MEDIVACSPEEDBOOST = 3973
- DUMMYABIL163_MEDIVACSPEEDBOOST = 3974
- DUMMYABIL164_MEDIVACSPEEDBOOST = 3975
- DUMMYABIL165_MEDIVACSPEEDBOOST = 3976
- DUMMYABIL166_MEDIVACSPEEDBOOST = 3977
- DUMMYABIL167_MEDIVACSPEEDBOOST = 3978
- DUMMYABIL168_MEDIVACSPEEDBOOST = 3979
- DUMMYABIL169_MEDIVACSPEEDBOOST = 3980
- DUMMYABIL170_MEDIVACSPEEDBOOST = 3981
- DUMMYABIL171_MEDIVACSPEEDBOOST = 3982
- DUMMYABIL172_MEDIVACSPEEDBOOST = 3983
- DUMMYABIL173_MEDIVACSPEEDBOOST = 3984
- DUMMYABIL174_MEDIVACSPEEDBOOST = 3985
- DUMMYABIL175_MEDIVACSPEEDBOOST = 3986
- DUMMYABIL176_MEDIVACSPEEDBOOST = 3987
- DUMMYABIL177_MEDIVACSPEEDBOOST = 3988
- DUMMYABIL178_MEDIVACSPEEDBOOST = 3989
- DUMMYABIL179_MEDIVACSPEEDBOOST = 3990
- DUMMYABIL180_MEDIVACSPEEDBOOST = 3991
- DUMMYABIL181_MEDIVACSPEEDBOOST = 3992
- DUMMYABIL182_MEDIVACSPEEDBOOST = 3993
- DUMMYABIL183_MEDIVACSPEEDBOOST = 3994
- DUMMYABIL184_MEDIVACSPEEDBOOST = 3995
- DUMMYABIL185_MEDIVACSPEEDBOOST = 3996
- DUMMYABIL186_MEDIVACSPEEDBOOST = 3997
- DUMMYABIL187_MEDIVACSPEEDBOOST = 3998
- DUMMYABIL188_MEDIVACSPEEDBOOST = 3999
- DUMMYABIL189_MEDIVACSPEEDBOOST = 4000
- DUMMYABIL190_MEDIVACSPEEDBOOST = 4001
- DUMMYABIL191_MEDIVACSPEEDBOOST = 4002
- DUMMYABIL192_MEDIVACSPEEDBOOST = 4003
- DUMMYABIL193_MEDIVACSPEEDBOOST = 4004
- DUMMYABIL194_MEDIVACSPEEDBOOST = 4005
- DUMMYABIL195_MEDIVACSPEEDBOOST = 4006
- DUMMYABIL196_MEDIVACSPEEDBOOST = 4007
- DUMMYABIL197_MEDIVACSPEEDBOOST = 4008
- DUMMYABIL198_MEDIVACSPEEDBOOST = 4009
- DUMMYABIL199_MEDIVACSPEEDBOOST = 4010
- DUMMYABIL200_MEDIVACSPEEDBOOST = 4011
- DUMMYABIL201_MEDIVACSPEEDBOOST = 4012
- DUMMYABIL202_MEDIVACSPEEDBOOST = 4013
- DUMMYABIL203_MEDIVACSPEEDBOOST = 4014
- DUMMYABIL204_MEDIVACSPEEDBOOST = 4015
- DUMMYABIL205_MEDIVACSPEEDBOOST = 4016
- DUMMYABIL206_DUMMYABIL206 = 4017
- DUMMYABIL207_DUMMYABIL207 = 4018
- DUMMYABIL208_DUMMYABIL208 = 4019
- DUMMYABIL209_DUMMYABIL209 = 4020
- DUMMYABIL210_DUMMYABIL210 = 4021
- DUMMYABIL211_DUMMYABIL211 = 4022
- DUMMYABIL212_DUMMYABIL212 = 4023
- DUMMYABIL213_DUMMYABIL213 = 4024
- DUMMYABIL214_DUMMYABIL214 = 4025
- DUMMYABIL215_DUMMYABIL215 = 4026
- DUMMYABIL216_DUMMYABIL216 = 4027
- DUMMYABIL217_DUMMYABIL217 = 4028
- DUMMYABIL218_DUMMYABIL218 = 4029
- DUMMYABIL219_DUMMYABIL219 = 4030
- DUMMYABIL220_DUMMYABIL220 = 4031
- DUMMYABIL221_DUMMYABIL221 = 4032
- DUMMYABIL222_DUMMYABIL222 = 4033
- DUMMYABIL223_DUMMYABIL223 = 4034
- DUMMYABIL224_DUMMYABIL224 = 4035
- DUMMYABIL225_DUMMYABIL225 = 4036
- DUMMYABIL226_DUMMYABIL226 = 4037
- DUMMYABIL227_DUMMYABIL227 = 4038
- DUMMYABIL228_DUMMYABIL228 = 4039
- DUMMYABIL229_DUMMYABIL229 = 4040
- DUMMYABIL230_DUMMYABIL230 = 4041
- DUMMYABIL231_DUMMYABIL231 = 4042
- DUMMYABIL232_DUMMYABIL232 = 4043
- DUMMYABIL233_DUMMYABIL233 = 4044
- DUMMYABIL234_DUMMYABIL234 = 4045
- DUMMYABIL235_DUMMYABIL235 = 4046
- DUMMYABIL236_DUMMYABIL236 = 4047
- DUMMYABIL237_DUMMYABIL237 = 4048
- DUMMYABIL238_DUMMYABIL238 = 4049
- DUMMYABIL239_DUMMYABIL239 = 4050
- DUMMYABIL240_DUMMYABIL240 = 4051
- DUMMYABIL241_DUMMYABIL241 = 4052
- DUMMYABIL242_DUMMYABIL242 = 4053
- DUMMYABIL243_DUMMYABIL243 = 4054
- DUMMYABIL244_DUMMYABIL244 = 4055
- DUMMYABIL245_DUMMYABIL245 = 4056
- DUMMYABIL246_DUMMYABIL246 = 4057
- DUMMYABIL247_DUMMYABIL247 = 4058
- DUMMYABIL248_DUMMYABIL248 = 4059
- DUMMYABIL249_DUMMYABIL249 = 4060
- DUMMYABIL250_DUMMYABIL250 = 4061
- DUMMYABIL251_DUMMYABIL251 = 4062
- DUMMYABIL252_DUMMYABIL252 = 4063
- DUMMYABIL253_DUMMYABIL253 = 4064
- DUMMYABIL254_DUMMYABIL254 = 4065
- DUMMYABIL255_DUMMYABIL255 = 4066
- DUMMYABIL256_DUMMYABIL256 = 4067
- DUMMYABIL257_DUMMYABIL257 = 4068
- DUMMYABIL258_DUMMYABIL258 = 4069
- DUMMYABIL259_DUMMYABIL259 = 4070
- DUMMYABIL260_DUMMYABIL260 = 4071
- DUMMYABIL261_DUMMYABIL261 = 4072
- DUMMYABIL262_DUMMYABIL262 = 4073
- DUMMYABIL263_DUMMYABIL263 = 4074
- DUMMYABIL264_DUMMYABIL264 = 4075
- DUMMYABIL265_DUMMYABIL265 = 4076
- DUMMYABIL266_DUMMYABIL266 = 4077
- DUMMYABIL267_DUMMYABIL267 = 4078
- DUMMYABIL268_DUMMYABIL268 = 4079
- DUMMYABIL269_DUMMYABIL269 = 4080
- DUMMYABIL270_DUMMYABIL270 = 4081
- DUMMYABIL271_DUMMYABIL271 = 4082
- DUMMYABIL272_DUMMYABIL272 = 4083
- DUMMYABIL273_DUMMYABIL273 = 4084
- DUMMYABIL274_DUMMYABIL274 = 4085
- DUMMYABIL275_DUMMYABIL275 = 4086
- DUMMYABIL276_DUMMYABIL276 = 4087
- DUMMYABIL277_DUMMYABIL277 = 4088
- DUMMYABIL278_DUMMYABIL278 = 4089
- DUMMYABIL279_DUMMYABIL279 = 4090
- DUMMYABIL280_DUMMYABIL280 = 4091
- DUMMYABIL281_DUMMYABIL281 = 4092
- DUMMYABIL282_DUMMYABIL282 = 4093
- DUMMYABIL283_DUMMYABIL283 = 4094
- DUMMYABIL284_DUMMYABIL284 = 4095
- DUMMYABIL285_DUMMYABIL285 = 4096
- DUMMYABIL286_DUMMYABIL286 = 4097
- DUMMYABIL287_DUMMYABIL287 = 4098
- DUMMYABIL288_DUMMYABIL288 = 4099
- DUMMYABIL289_DUMMYABIL289 = 4100
- DUMMYABIL290_DUMMYABIL290 = 4101
- DUMMYABIL291_DUMMYABIL291 = 4102
- DUMMYABIL292_DUMMYABIL292 = 4103
- DUMMYABIL293_DUMMYABIL293 = 4104
- DUMMYABIL294_DUMMYABIL294 = 4105
- DUMMYABIL295_DUMMYABIL295 = 4106
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 3966
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL = 3967
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 3969
+ MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL = 3970
BATTERYOVERCHARGE_BATTERYOVERCHARGE = 4107
- DUMMYABIL296_DUMMYABIL296 = 4108
- AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD = 4109
- SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE = 4111
- SHIELDBATTERYRECHARGEEX5_STOP = 4112
- LOADOUTSPRAY_LOADOUTSPRAY1 = 4113
- MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL = 4283
- MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL = 4286
+ HYDRALISKFRENZY_HYDRALISKFRENZY = 4109
+ AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD = 4111
+ SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE = 4113
+ SHIELDBATTERYRECHARGEEX5_STOP = 4114
+ MORPHTOBANELING_BANELING = 4121
+ MORPHTOBANELING_CANCEL = 4122
+ MOTHERSHIPCLOAK_ORACLECLOAKFIELD = 4124
+ ENERGYRECHARGE_ENERGYRECHARGE = 4126
+ SALVAGEEFFECT_SALVAGE = 4128
+ SALVAGESENSORTOWERREFUND_SALVAGE = 4130
+ WORKERSTOPIDLEABILITYVESPENE_GATHER = 4132
def __repr__(self) -> str:
return f"AbilityId.{self.name}"
+ @classmethod
+ def _missing_(cls, value: int) -> AbilityId:
+ return cls.NULL_NULL
+
for item in AbilityId:
globals()[item.name] = item
diff --git a/sc2/ids/buff_id.py b/sc2/ids/buff_id.py
index 6a6f17a5..5a7345a8 100644
--- a/sc2/ids/buff_id.py
+++ b/sc2/ids/buff_id.py
@@ -1,6 +1,8 @@
+# pyre-ignore-all-errors[14]
+from __future__ import annotations
+
# DO NOT EDIT!
# This file was automatically generated by "generate_ids.py"
-
import enum
@@ -297,26 +299,23 @@ class BuffId(enum.Enum):
ACCELERATIONZONETEMPORALFIELD = 289
ACCELERATIONZONEFLYINGTEMPORALFIELD = 290
INHIBITORZONEFLYINGTEMPORALFIELD = 291
- DUMMYBUFF000 = 292
+ LOADOUTSPRAYTRACKER = 292
INHIBITORZONETEMPORALFIELD = 293
- RESONATINGGLAIVESPHASESHIFT = 294
- NEURALPARASITECHILDREN = 295
- AMORPHOUSARMORCLOUD = 296
- RAVENSHREDDERMISSILEARMORREDUCTIONUISUBTRUCT = 297
- BATTERYOVERCHARGE = 298
- DUMMYBUFF001 = 299
- DUMMYBUFF002 = 300
- DUMMYBUFF003 = 301
- DUMMYBUFF004 = 302
- ONCREEPVISIBLE = 303
- LOADOUTSPRAYTRACKER = 305
- NYDUSCREEPGROWTH = 306
+ CLOAKFIELD = 294
+ RESONATINGGLAIVESPHASESHIFT = 295
+ NEURALPARASITECHILDREN = 296
+ AMORPHOUSARMORCLOUD = 297
+ RAVENSHREDDERMISSILEARMORREDUCTIONUISUBTRUCT = 298
+ TAKENDAMAGE = 299
+ RAVENSCRAMBLERMISSILECARRIER = 300
+ BATTERYOVERCHARGE = 301
+ HYDRALISKFRENZY = 302
def __repr__(self) -> str:
return f"BuffId.{self.name}"
@classmethod
- def _missing_(cls, value: int) -> "BuffId":
+ def _missing_(cls, value: int) -> BuffId:
return cls.NULL
diff --git a/sc2/ids/effect_id.py b/sc2/ids/effect_id.py
index e0580b31..77aea24b 100644
--- a/sc2/ids/effect_id.py
+++ b/sc2/ids/effect_id.py
@@ -1,6 +1,8 @@
+# pyre-ignore-all-errors[14]
+from __future__ import annotations
+
# DO NOT EDIT!
# This file was automatically generated by "generate_ids.py"
-
import enum
diff --git a/sc2/ids/unit_typeid.py b/sc2/ids/unit_typeid.py
index abd95714..ec74ebe8 100644
--- a/sc2/ids/unit_typeid.py
+++ b/sc2/ids/unit_typeid.py
@@ -1,6 +1,8 @@
+# pyre-ignore-all-errors[14]
+from __future__ import annotations
+
# DO NOT EDIT!
# This file was automatically generated by "generate_ids.py"
-
import enum
@@ -2004,64 +2006,12 @@ class UnitTypeId(enum.Enum):
MINERALFIELD450 = 1996
MINERALFIELDOPAQUE = 1997
MINERALFIELDOPAQUE900 = 1998
- MECHAZERGLINGACGLUESCREENDUMMY_2 = 1999
- MECHABANELINGACGLUESCREENDUMMY_2 = 2000
- MECHAHYDRALISKACGLUESCREENDUMMY_2 = 2001
- MECHAINFESTORACGLUESCREENDUMMY_2 = 2002
- MECHACORRUPTORACGLUESCREENDUMMY_2 = 2003
- MECHAULTRALISKACGLUESCREENDUMMY_2 = 2004
- MECHAOVERSEERACGLUESCREENDUMMY_2 = 2005
- MECHALURKERACGLUESCREENDUMMY_2 = 2006
- MECHABATTLECARRIERLORDACGLUESCREENDUMMY_2 = 2007
- MECHASPINECRAWLERACGLUESCREENDUMMY_2 = 2008
- MECHASPORECRAWLERACGLUESCREENDUMMY_2 = 2009
- TROOPERMENGSKACGLUESCREENDUMMY_2 = 2010
- MEDIVACMENGSKACGLUESCREENDUMMY_2 = 2011
- BLIMPMENGSKACGLUESCREENDUMMY_2 = 2012
- MARAUDERMENGSKACGLUESCREENDUMMY_2 = 2013
- GHOSTMENGSKACGLUESCREENDUMMY_2 = 2014
- SIEGETANKMENGSKACGLUESCREENDUMMY_2 = 2015
- THORMENGSKACGLUESCREENDUMMY_2 = 2016
- VIKINGMENGSKACGLUESCREENDUMMY_2 = 2017
- BATTLECRUISERMENGSKACGLUESCREENDUMMY_2 = 2018
- BUNKERDEPOTMENGSKACGLUESCREENDUMMY_2 = 2019
- MISSILETURRETMENGSKACGLUESCREENDUMMY_2 = 2020
- ARTILLERYMENGSKACGLUESCREENDUMMY_2 = 2021
- LOADOUTSPRAY1_2 = 2022
- LOADOUTSPRAY2_2 = 2023
- LOADOUTSPRAY3_2 = 2024
- LOADOUTSPRAY4_2 = 2025
- LOADOUTSPRAY5_2 = 2026
- LOADOUTSPRAY6_2 = 2027
- LOADOUTSPRAY7_2 = 2028
- LOADOUTSPRAY8_2 = 2029
- LOADOUTSPRAY9_2 = 2030
- LOADOUTSPRAY10_2 = 2031
- LOADOUTSPRAY11_2 = 2032
- LOADOUTSPRAY12_2 = 2033
- LOADOUTSPRAY13_2 = 2034
- LOADOUTSPRAY14_2 = 2035
- COLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 2036
- COLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 2037
- COLLAPSIBLEROCKTOWERPUSHUNITRAMPLEFTGREEN = 2038
- COLLAPSIBLEROCKTOWERPUSHUNITRAMPRIGHTGREEN = 2039
- COLLAPSIBLEROCKTOWERRAMPLEFTGREEN = 2040
- COLLAPSIBLEROCKTOWERRAMPRIGHTGREEN = 2041
- DUMMYUNIT000 = 2042
- DUMMYUNIT001 = 2043
- DUMMYUNIT002 = 2044
- DUMMYUNIT003 = 2045
- DUMMYUNIT004 = 2046
- DUMMYUNIT005 = 2047
- DUMMYUNIT006 = 2048
- DUMMYUNIT007 = 2049
- DUMMYUNIT008 = 2050
- DUMMYUNIT009 = 2051
- DUMMYUNIT010 = 2052
- DUMMYUNIT011 = 2053
- DUMMYUNIT012 = 2054
- DUMMYUNIT013 = 2055
- DUMMYUNIT014 = 2056
+ COLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 1999
+ COLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 2000
+ COLLAPSIBLEROCKTOWERPUSHUNITRAMPLEFTGREEN = 2001
+ COLLAPSIBLEROCKTOWERPUSHUNITRAMPRIGHTGREEN = 2002
+ COLLAPSIBLEROCKTOWERRAMPLEFTGREEN = 2003
+ COLLAPSIBLEROCKTOWERRAMPRIGHTGREEN = 2004
def __repr__(self) -> str:
return f"UnitTypeId.{self.name}"
diff --git a/sc2/ids/upgrade_id.py b/sc2/ids/upgrade_id.py
index fb3a48c2..4be6cbfe 100644
--- a/sc2/ids/upgrade_id.py
+++ b/sc2/ids/upgrade_id.py
@@ -1,6 +1,8 @@
+# pyre-ignore-all-errors[14]
+from __future__ import annotations
+
# DO NOT EDIT!
# This file was automatically generated by "generate_ids.py"
-
import enum
@@ -301,13 +303,16 @@ class UpgradeId(enum.Enum):
DIGGINGCLAWS = 293
CARRIERCARRIERCAPACITY = 294
CARRIERLEASHRANGEUPGRADE = 295
- TEMPESTGROUNDATTACKUPGRADE = 296
- ENHANCEDSHOCKWAVES = 297
- MICROBIALSHROUD = 298
- SUNDERINGIMPACT = 299
- AMPLIFIEDSHIELDING = 300
- PSIONICAMPLIFIERS = 301
- SECRETEDCOATING = 302
+ HURRICANETHRUSTERS = 296
+ TEMPESTGROUNDATTACKUPGRADE = 297
+ FRENZY = 298
+ MICROBIALSHROUD = 299
+ INTERFERENCEMATRIX = 300
+ SUNDERINGIMPACT = 301
+ AMPLIFIEDSHIELDING = 302
+ PSIONICAMPLIFIERS = 303
+ SECRETEDCOATING = 304
+ ENHANCEDSHOCKWAVES = 305
def __repr__(self) -> str:
return f"UpgradeId.{self.name}"
diff --git a/sc2/main.py b/sc2/main.py
index 5e1b3621..fd86c6a7 100644
--- a/sc2/main.py
+++ b/sc2/main.py
@@ -1,9 +1,8 @@
-# pylint: disable=W0212
+# pyre-ignore-all-errors[6, 11, 16, 21, 29]
from __future__ import annotations
import asyncio
import json
-import os
import platform
import signal
import sys
@@ -11,7 +10,6 @@
from dataclasses import dataclass
from io import BytesIO
from pathlib import Path
-from typing import Dict, List, Optional, Tuple, Union
import mpyq
import portpicker
@@ -27,9 +25,9 @@
from sc2.maps import Map
from sc2.player import AbstractPlayer, Bot, BotProcess, Human
from sc2.portconfig import Portconfig
-from sc2.protocol import ConnectionAlreadyClosed, ProtocolError
+from sc2.protocol import ConnectionAlreadyClosedError, ProtocolError
from sc2.proxy import Proxy
-from sc2.sc2process import SC2Process, kill_switch
+from sc2.sc2process import KillSwitch, SC2Process
# Set the global logging level
logger.remove()
@@ -47,14 +45,14 @@ class GameMatch:
"""
map_sc2: Map
- players: List[AbstractPlayer]
+ players: list[AbstractPlayer]
realtime: bool = False
- random_seed: int = None
- disable_fog: bool = None
- sc2_config: List[Dict] = None
- game_time_limit: int = None
+ random_seed: int | None = None
+ disable_fog: bool | None = None
+ sc2_config: list[dict] | None = None
+ game_time_limit: int | None = None
- def __post_init__(self):
+ def __post_init__(self) -> None:
# avoid players sharing names
if len(self.players) > 1 and self.players[0].name is not None and self.players[0].name == self.players[1].name:
self.players[1].name += "2"
@@ -66,14 +64,14 @@ def __post_init__(self):
self.sc2_config = [{}]
while len(self.sc2_config) < len(self.players):
self.sc2_config += self.sc2_config
- self.sc2_config = self.sc2_config[:len(self.players)]
+ self.sc2_config = self.sc2_config[: len(self.players)]
@property
def needed_sc2_count(self) -> int:
return sum(player.needs_sc2 for player in self.players)
@property
- def host_game_kwargs(self) -> Dict:
+ def host_game_kwargs(self) -> dict:
return {
"map_settings": self.map_sc2,
"players": self.players,
@@ -82,7 +80,7 @@ def host_game_kwargs(self) -> Dict:
"disable_fog": self.disable_fog,
}
- def __repr__(self):
+ def __repr__(self) -> str:
p1 = self.players[0]
p1 = p1.name if p1.name else p1
p2 = self.players[1]
@@ -104,13 +102,12 @@ async def _play_game_human(client, player_id, realtime, game_time_limit):
await client.step()
-# pylint: disable=R0912,R0911,R0914
async def _play_game_ai(
- client: Client, player_id: int, ai: BotAI, realtime: bool, game_time_limit: Optional[int]
+ client: Client, player_id: int, ai: BotAI, realtime: bool, game_time_limit: int | None
) -> Result:
- gs: GameState = None
+ gs: GameState | None = None
- async def initialize_first_step() -> Optional[Result]:
+ async def initialize_first_step() -> Result | None:
nonlocal gs
ai._initialize_variables()
@@ -135,7 +132,7 @@ async def initialize_first_step() -> Optional[Result]:
ai._prepare_first_step()
await ai.on_start()
# TODO Catching too general exception Exception (broad-except)
- # pylint: disable=W0703
+
except Exception as e:
logger.exception(f"Caught unknown exception in AI on_start: {e}")
logger.error("Resigning due to previous error")
@@ -154,7 +151,7 @@ async def run_bot_iteration(iteration: int):
# In on_step various errors can occur - log properly
try:
await ai.on_step(iteration)
- except (AttributeError, ) as e:
+ except (AttributeError,) as e:
logger.exception(f"Caught exception: {e}")
raise
except Exception as e:
@@ -206,12 +203,7 @@ async def run_bot_iteration(iteration: int):
async def _play_game(
- player: AbstractPlayer,
- client: Client,
- realtime,
- portconfig,
- game_time_limit=None,
- rgb_render_config=None
+ player: AbstractPlayer, client: Client, realtime, portconfig, game_time_limit=None, rgb_render_config=None
) -> Result:
assert isinstance(realtime, bool), repr(realtime)
@@ -233,7 +225,7 @@ async def _play_game(
return result
-async def _play_replay(client, ai, realtime=False, player_id=0):
+async def _play_replay(client, ai, realtime: bool = False, player_id: int = 0):
ai._initialize_variables()
game_data = await client.get_game_data()
@@ -257,7 +249,7 @@ async def _play_replay(client, ai, realtime=False, player_id=0):
try:
await ai.on_start()
# TODO Catching too general exception Exception (broad-except)
- # pylint: disable=W0703
+
except Exception as e:
logger.exception(f"Caught unknown exception in AI replay on_start: {e}")
await ai.on_end(Result.Defeat)
@@ -293,7 +285,6 @@ async def _play_replay(client, ai, realtime=False, player_id=0):
await ai.on_step(iteration)
await ai._after_step()
- # pylint: disable=W0703
# TODO Catching too general exception Exception (broad-except)
except Exception as e:
if isinstance(e, ProtocolError) and e.is_game_over_error:
@@ -313,10 +304,9 @@ async def _play_replay(client, ai, realtime=False, player_id=0):
logger.debug("Running AI step: done")
- if not realtime:
- if not client.in_game: # Client left (resigned) the game
- await ai.on_end(Result.Victory)
- return Result.Victory
+ if not realtime and not client.in_game: # Client left (resigned) the game
+ await ai.on_end(Result.Victory)
+ return Result.Victory
await client.step() # unindent one line to work in realtime
@@ -340,7 +330,7 @@ async def _setup_host_game(
async def _host_game(
map_settings,
players,
- realtime=False,
+ realtime: bool = False,
portconfig=None,
save_replay_as=None,
game_time_limit=None,
@@ -349,10 +339,9 @@ async def _host_game(
sc2_version=None,
disable_fog=None,
):
-
assert players, "Can't create a game without players"
- assert any(isinstance(p, (Human, Bot)) for p in players)
+ assert any((isinstance(p, (Human, Bot))) for p in players)
async with SC2Process(
fullscreen=players[0].fullscreen, render=rgb_render_config is not None, sc2_version=sc2_version
@@ -371,7 +360,7 @@ async def _host_game(
await client.save_replay(client.save_replay_path)
try:
await client.leave()
- except ConnectionAlreadyClosed:
+ except ConnectionAlreadyClosedError:
logger.error("Connection was closed before the game ended")
await client.quit()
@@ -404,7 +393,7 @@ async def _host_game_aiter(
if save_replay_as is not None:
await client.save_replay(save_replay_as)
await client.leave()
- except ConnectionAlreadyClosed:
+ except ConnectionAlreadyClosedError:
logger.error("Connection was closed before the game ended")
return
@@ -426,8 +415,9 @@ async def _join_game(
portconfig,
save_replay_as=None,
game_time_limit=None,
+ sc2_version=None,
):
- async with SC2Process(fullscreen=players[1].fullscreen) as server:
+ async with SC2Process(fullscreen=players[1].fullscreen, sc2_version=sc2_version) as server:
await server.ping()
client = Client(server._ws)
@@ -440,7 +430,7 @@ async def _join_game(
await client.save_replay(save_replay_as)
try:
await client.leave()
- except ConnectionAlreadyClosed:
+ except ConnectionAlreadyClosedError:
logger.error("Connection was closed before the game ended")
await client.quit()
@@ -459,8 +449,8 @@ async def _host_replay(replay_path, ai, realtime, _portconfig, base_build, data_
return result
-def get_replay_version(replay_path: Union[str, Path]) -> Tuple[str, str]:
- with open(replay_path, 'rb') as f:
+def get_replay_version(replay_path: str | Path) -> tuple[str, str]:
+ with Path(replay_path).open("rb") as f:
replay_data = f.read()
replay_io = BytesIO()
replay_io.write(replay_data)
@@ -471,13 +461,13 @@ def get_replay_version(replay_path: Union[str, Path]) -> Tuple[str, str]:
# TODO Deprecate run_game function in favor of run_multiple_games
-def run_game(map_settings, players, **kwargs) -> Union[Result, List[Optional[Result]]]:
+def run_game(map_settings, players, **kwargs) -> Result | list[Result | None]:
"""
Returns a single Result enum if the game was against the built-in computer.
Returns a list of two Result enums if the game was "Human vs Bot" or "Bot vs Bot".
"""
if sum(isinstance(p, (Human, Bot)) for p in players) > 1:
- host_only_args = ["save_replay_as", "rgb_render_config", "random_seed", "sc2_version", "disable_fog"]
+ host_only_args = ["save_replay_as", "rgb_render_config", "random_seed", "disable_fog"]
join_kwargs = {k: v for k, v in kwargs.items() if k not in host_only_args}
portconfig = Portconfig()
@@ -486,10 +476,10 @@ async def run_host_and_join():
return await asyncio.gather(
_host_game(map_settings, players, **kwargs, portconfig=portconfig),
_join_game(players, **join_kwargs, portconfig=portconfig),
- return_exceptions=True
+ return_exceptions=True,
)
- result: List[Result] = asyncio.run(run_host_and_join())
+ result: list[Result] = asyncio.run(run_host_and_join())
assert isinstance(result, list)
assert all(isinstance(r, Result) for r in result)
else:
@@ -498,12 +488,12 @@ async def run_host_and_join():
return result
-def run_replay(ai, replay_path, realtime=False, observed_id=0):
+def run_replay(ai, replay_path: Path | str, realtime: bool = False, observed_id: int = 0):
portconfig = Portconfig()
- assert os.path.isfile(replay_path), f"Replay does not exist at the given path: {replay_path}"
- assert os.path.isabs(
- replay_path
- ), f'Replay path has to be an absolute path, e.g. "C:/replays/my_replay.SC2Replay" but given path was "{replay_path}"'
+ assert Path(replay_path).is_file(), f"Replay does not exist at the given path: {replay_path}"
+ assert Path(replay_path).is_absolute(), (
+ f'Replay path has to be an absolute path, e.g. "C:/replays/my_replay.SC2Replay" but given path was "{replay_path}"'
+ )
base_build, data_version = get_replay_version(replay_path)
result = asyncio.get_event_loop().run_until_complete(
_host_replay(replay_path, ai, realtime, portconfig, base_build, data_version, observed_id)
@@ -512,13 +502,13 @@ def run_replay(ai, replay_path, realtime=False, observed_id=0):
async def play_from_websocket(
- ws_connection: Union[str, ClientWebSocketResponse],
+ ws_connection: str | ClientWebSocketResponse,
player: AbstractPlayer,
realtime: bool = False,
- portconfig: Portconfig = None,
- save_replay_as=None,
- game_time_limit: int = None,
- should_close=True,
+ portconfig: Portconfig | None = None,
+ save_replay_as: str | None = None,
+ game_time_limit: int | None = None,
+ should_close: bool = True,
):
"""Use this to play when the match is handled externally e.g. for bot ladder games.
Portconfig MUST be specified if not playing vs Computer.
@@ -537,7 +527,7 @@ async def play_from_websocket(
result = await _play_game(player, client, realtime, portconfig, game_time_limit=game_time_limit)
if save_replay_as is not None:
await client.save_replay(save_replay_as)
- except ConnectionAlreadyClosed:
+ except ConnectionAlreadyClosedError:
logger.error("Connection was closed before the game ended")
return None
finally:
@@ -549,7 +539,7 @@ async def play_from_websocket(
return result
-async def run_match(controllers: List[Controller], match: GameMatch, close_ws=True):
+async def run_match(controllers: list[Controller], match: GameMatch, close_ws: bool = True):
await _setup_host_game(controllers[0], **match.host_game_kwargs)
# Setup portconfig beforehand, so all players use the same ports
@@ -595,9 +585,9 @@ async def run_match(controllers: List[Controller], match: GameMatch, close_ws=Tr
return process_results(match.players, async_results)
-def process_results(players: List[AbstractPlayer], async_results: List[Result]) -> Dict[AbstractPlayer, Result]:
+def process_results(players: list[AbstractPlayer], async_results: list[Result]) -> dict[AbstractPlayer, Result]:
opp_res = {Result.Victory: Result.Defeat, Result.Defeat: Result.Victory, Result.Tie: Result.Tie}
- result: Dict[AbstractPlayer, Result] = {}
+ result: dict[AbstractPlayer, Result] = {}
i = 0
for player in players:
if player.needs_sc2:
@@ -615,8 +605,7 @@ def process_results(players: List[AbstractPlayer], async_results: List[Result])
return result
-# pylint: disable=R0912
-async def maintain_SCII_count(count: int, controllers: List[Controller], proc_args: List[Dict] = None):
+async def maintain_SCII_count(count: int, controllers: list[Controller], proc_args: list[dict] | None = None) -> None:
"""Modifies the given list of controllers to reflect the desired amount of SCII processes"""
# kill unhealthy ones.
if controllers:
@@ -639,8 +628,8 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar
i += 1
for c in to_remove:
c._process._clean(verbose=False)
- if c._process in kill_switch._to_kill:
- kill_switch._to_kill.remove(c._process)
+ if c._process in KillSwitch._to_kill:
+ KillSwitch._to_kill.remove(c._process)
controllers.remove(c)
# spawn more
@@ -656,14 +645,13 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar
for _ in range(3):
if platform.system() == "Linux":
# Works on linux: start one client after the other
- # pylint: disable=C2801
+
new_controllers = [await asyncio.wait_for(sc.__aenter__(), timeout=50) for sc in extra]
else:
# Doesnt seem to work on linux: starting 2 clients nearly at the same time
new_controllers = await asyncio.wait_for(
- # pylint: disable=C2801
asyncio.gather(*[sc.__aenter__() for sc in extra], return_exceptions=True),
- timeout=50
+ timeout=50,
)
controllers.extend(c for c in new_controllers if isinstance(c, Controller))
@@ -684,17 +672,18 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar
logger.info(f"Removing SCII listening to {proc._port}")
await proc._close_connection()
proc._clean(verbose=False)
- if proc in kill_switch._to_kill:
- kill_switch._to_kill.remove(proc)
+ if proc in KillSwitch._to_kill:
+ KillSwitch._to_kill.remove(proc)
-def run_multiple_games(matches: List[GameMatch]):
+def run_multiple_games(matches: list[GameMatch]):
return asyncio.get_event_loop().run_until_complete(a_run_multiple_games(matches))
# TODO Catching too general exception Exception (broad-except)
-# pylint: disable=W0703
-async def a_run_multiple_games(matches: List[GameMatch]) -> List[Dict[AbstractPlayer, Result]]:
+
+
+async def a_run_multiple_games(matches: list[GameMatch]) -> list[dict[AbstractPlayer, Result]]:
"""Run multiple matches.
Non-python bots are supported.
When playing bot vs bot, this is less likely to fatally crash than repeating run_game()
@@ -719,13 +708,14 @@ async def a_run_multiple_games(matches: List[GameMatch]) -> List[Dict[AbstractPl
if dont_restart: # Keeping them alive after a non-computer match can cause crashes
await maintain_SCII_count(0, controllers, m.sc2_config)
results.append(result)
- kill_switch.kill_all()
+ KillSwitch.kill_all()
return results
# TODO Catching too general exception Exception (broad-except)
-# pylint: disable=W0703
-async def a_run_multiple_games_nokill(matches: List[GameMatch]) -> List[Dict[AbstractPlayer, Result]]:
+
+
+async def a_run_multiple_games_nokill(matches: list[GameMatch]) -> list[dict[AbstractPlayer, Result]]:
"""Run multiple matches while reusing SCII processes.
Prone to crashes and stalls
"""
@@ -762,7 +752,7 @@ async def a_run_multiple_games_nokill(matches: List[GameMatch]) -> List[Dict[Abs
# Fire the killswitch manually, instead of letting the winning player fire it.
await asyncio.wait_for(asyncio.gather(*(c._process._close_connection() for c in controllers)), timeout=50)
- kill_switch.kill_all()
+ KillSwitch.kill_all()
signal.signal(signal.SIGINT, signal.SIG_DFL)
return results
diff --git a/sc2/maps.py b/sc2/maps.py
index 0cbf624c..f5e4e2cb 100644
--- a/sc2/maps.py
+++ b/sc2/maps.py
@@ -21,8 +21,7 @@ def get(name: str) -> Map:
class Map:
-
- def __init__(self, path: Path):
+ def __init__(self, path: Path) -> None:
self.path = path
if self.path.is_absolute():
@@ -35,15 +34,15 @@ def __init__(self, path: Path):
self.relative_path = self.path
@property
- def name(self):
+ def name(self) -> str:
return self.path.stem
@property
- def data(self):
- with open(self.path, "rb") as f:
+ def data(self) -> bytes:
+ with Path(self.path).open("rb") as f:
return f.read()
- def __repr__(self):
+ def __repr__(self) -> str:
return f"Map({self.path})"
@classmethod
diff --git a/sc2/observer_ai.py b/sc2/observer_ai.py
index 1f049039..7cd23c99 100644
--- a/sc2/observer_ai.py
+++ b/sc2/observer_ai.py
@@ -1,56 +1,34 @@
+# pyre-ignore-all-errors[6, 11, 16]
"""
This class is very experimental and probably not up to date and needs to be refurbished.
If it works, you can watch replays with it.
"""
-# pylint: disable=W0201,W0212
from __future__ import annotations
-from typing import TYPE_CHECKING, List, Union
-
from sc2.bot_ai_internal import BotAIInternal
from sc2.data import Alert, Result
-from sc2.game_data import GameData
from sc2.ids.ability_id import AbilityId
from sc2.ids.upgrade_id import UpgradeId
from sc2.position import Point2
from sc2.unit import Unit
from sc2.units import Units
-if TYPE_CHECKING:
- from sc2.client import Client
- from sc2.game_info import GameInfo
-
class ObserverAI(BotAIInternal):
"""Base class for bots."""
@property
def time(self) -> float:
- """ Returns time in seconds, assumes the game is played on 'faster' """
+ """Returns time in seconds, assumes the game is played on 'faster'"""
return self.state.game_loop / 22.4 # / (1/1.4) * (1/16)
@property
def time_formatted(self) -> str:
- """ Returns time as string in min:sec format """
+ """Returns time as string in min:sec format"""
t = self.time
return f"{int(t // 60):02}:{int(t % 60):02}"
- @property
- def game_info(self) -> GameInfo:
- """ See game_info.py """
- return self._game_info
-
- @property
- def game_data(self) -> GameData:
- """ See game_data.py """
- return self._game_data
-
- @property
- def client(self) -> Client:
- """ See client.py """
- return self._client
-
def alert(self, alert_code: Alert) -> bool:
"""
Check if alert is triggered in the current step.
@@ -101,13 +79,13 @@ def start_location(self) -> Point2:
return self.game_info.player_start_location
@property
- def enemy_start_locations(self) -> List[Point2]:
+ def enemy_start_locations(self) -> list[Point2]:
"""Possible start locations for enemies."""
return self.game_info.start_locations
async def get_available_abilities(
- self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False
- ) -> List[List[AbilityId]]:
+ self, units: list[Unit] | Units, ignore_resource_requirements: bool = False
+ ) -> list[list[AbilityId]]:
"""Returns available abilities of one or more units. Right now only checks cooldown, energy cost, and whether the ability has been researched.
Examples::
@@ -122,7 +100,7 @@ async def get_available_abilities(
:param ignore_resource_requirements:"""
return await self.client.query_available_abilities(units, ignore_resource_requirements)
- async def on_unit_destroyed(self, unit_tag: int):
+ async def on_unit_destroyed(self, unit_tag: int) -> None:
"""
Override this in your bot class.
This will event will be called when a unit (or structure, friendly or enemy) dies.
@@ -131,12 +109,12 @@ async def on_unit_destroyed(self, unit_tag: int):
:param unit_tag:
"""
- async def on_unit_created(self, unit: Unit):
+ async def on_unit_created(self, unit: Unit) -> None:
"""Override this in your bot class. This function is called when a unit is created.
:param unit:"""
- async def on_building_construction_started(self, unit: Unit):
+ async def on_building_construction_started(self, unit: Unit) -> None:
"""
Override this in your bot class.
This function is called when a building construction has started.
@@ -144,7 +122,7 @@ async def on_building_construction_started(self, unit: Unit):
:param unit:
"""
- async def on_building_construction_complete(self, unit: Unit):
+ async def on_building_construction_complete(self, unit: Unit) -> None:
"""
Override this in your bot class. This function is called when a building
construction is completed.
@@ -152,14 +130,14 @@ async def on_building_construction_complete(self, unit: Unit):
:param unit:
"""
- async def on_upgrade_complete(self, upgrade: UpgradeId):
+ async def on_upgrade_complete(self, upgrade: UpgradeId) -> None:
"""
Override this in your bot class. This function is called with the upgrade id of an upgrade that was not finished last step and is now.
:param upgrade:
"""
- async def on_start(self):
+ async def on_start(self) -> None:
"""
Override this in your bot class. This function is called after "on_start".
At this point, game_data, game_info and the first iteration of game_state (self.state) are available.
@@ -175,7 +153,7 @@ async def on_step(self, iteration: int):
"""
raise NotImplementedError
- async def on_end(self, game_result: Result):
+ async def on_end(self, game_result: Result) -> None:
"""Override this in your bot class. This function is called at the end of a game.
:param game_result:"""
diff --git a/sc2/paths.py b/sc2/paths.py
index 79131cde..10ec26bb 100644
--- a/sc2/paths.py
+++ b/sc2/paths.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import platform
import re
@@ -18,7 +20,7 @@
"WineLinux": "~/.wine/drive_c/Program Files (x86)/StarCraft II",
}
-USERPATH = {
+USERPATH: dict[str, str | None] = {
"Windows": "Documents\\StarCraft II\\ExecuteInfo.txt",
"WSL1": "Documents/StarCraft II/ExecuteInfo.txt",
"WSL2": "Documents/StarCraft II/ExecuteInfo.txt",
@@ -36,7 +38,7 @@
"WineLinux": "SC2_x64.exe",
}
-CWD = {
+CWD: dict[str, str | None] = {
"Windows": "Support64",
"WSL1": "Support64",
"WSL2": "Support64",
@@ -67,20 +69,20 @@ def get_user_sc2_install():
"""Attempts to find a user's SC2 install if their OS has ExecuteInfo.txt"""
if USERPATH[PF]:
einfo = str(get_home() / Path(USERPATH[PF]))
- if os.path.isfile(einfo):
- with open(einfo) as f:
+ if Path(einfo).is_file():
+ with Path(einfo).open() as f:
content = f.read()
if content:
base = re.search(r" = (.*)Versions", content).group(1)
if PF in {"WSL1", "WSL2"}:
base = str(wsl.win_path_to_wsl_path(base))
- if os.path.exists(base):
+ if Path(base).exists():
return base
return None
-def get_env():
+def get_env() -> None:
# TODO: Linux env conf from: https://github.com/deepmind/pysc2/blob/master/pysc2/run_configs/platforms.py
return None
@@ -122,35 +124,32 @@ def latest_executeble(versions_dir, base_build=None):
class _MetaPaths(type):
- """"Lazily loads paths to allow importing the library even if SC2 isn't installed."""
+ """ "Lazily loads paths to allow importing the library even if SC2 isn't installed."""
- # pylint: disable=C0203
- def __setup(self):
+ def __setup(cls):
if PF not in BASEDIR:
logger.critical(f"Unsupported platform '{PF}'")
sys.exit(1)
try:
base = os.environ.get("SC2PATH") or get_user_sc2_install() or BASEDIR[PF]
- self.BASE = Path(base).expanduser()
- self.EXECUTABLE = latest_executeble(self.BASE / "Versions")
- self.CWD = self.BASE / CWD[PF] if CWD[PF] else None
+ cls.BASE = Path(base).expanduser()
+ cls.EXECUTABLE = latest_executeble(cls.BASE / "Versions")
+ cls.CWD = cls.BASE / CWD[PF] if CWD[PF] else None
- self.REPLAYS = self.BASE / "Replays"
+ cls.REPLAYS = cls.BASE / "Replays"
- if (self.BASE / "maps").exists():
- self.MAPS = self.BASE / "maps"
+ if (cls.BASE / "maps").exists():
+ cls.MAPS = cls.BASE / "maps"
else:
- self.MAPS = self.BASE / "Maps"
+ cls.MAPS = cls.BASE / "Maps"
except FileNotFoundError as e:
logger.critical(f"SC2 installation not found: File '{e.filename}' does not exist.")
sys.exit(1)
- # pylint: disable=C0203
- def __getattr__(self, attr):
- # pylint: disable=E1120
- self.__setup()
- return getattr(self, attr)
+ def __getattr__(cls, attr):
+ cls.__setup()
+ return getattr(cls, attr)
class Paths(metaclass=_MetaPaths):
diff --git a/sc2/pixel_map.py b/sc2/pixel_map.py
index 30dd40d7..6871a516 100644
--- a/sc2/pixel_map.py
+++ b/sc2/pixel_map.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+
+from collections.abc import Callable
from pathlib import Path
-from typing import Callable, FrozenSet, List, Set, Tuple, Union
import numpy as np
@@ -7,8 +9,7 @@
class PixelMap:
-
- def __init__(self, proto, in_bits: bool = False):
+ def __init__(self, proto, in_bits: bool = False) -> None:
"""
:param proto:
:param in_bits:
@@ -17,9 +18,9 @@ def __init__(self, proto, in_bits: bool = False):
# Used for copying pixelmaps
self._in_bits: bool = in_bits
- assert self.width * self.height == (8 if in_bits else 1) * len(
- self._proto.data
- ), f"{self.width * self.height} {(8 if in_bits else 1)*len(self._proto.data)}"
+ assert self.width * self.height == (8 if in_bits else 1) * len(self._proto.data), (
+ f"{self.width * self.height} {(8 if in_bits else 1) * len(self._proto.data)}"
+ )
buffer_data = np.frombuffer(self._proto.data, dtype=np.uint8)
if in_bits:
buffer_data = np.unpackbits(buffer_data)
@@ -41,34 +42,34 @@ def bits_per_pixel(self) -> int:
def bytes_per_pixel(self) -> int:
return self._proto.bits_per_pixel // 8
- def __getitem__(self, pos: Tuple[int, int]) -> int:
- """ Example usage: is_pathable = self._game_info.pathing_grid[Point2((20, 20))] != 0 """
+ def __getitem__(self, pos: tuple[int, int]) -> int:
+ """Example usage: is_pathable = self._game_info.pathing_grid[Point2((20, 20))] != 0"""
assert 0 <= pos[0] < self.width, f"x is {pos[0]}, self.width is {self.width}"
assert 0 <= pos[1] < self.height, f"y is {pos[1]}, self.height is {self.height}"
return int(self.data_numpy[pos[1], pos[0]])
- def __setitem__(self, pos: Tuple[int, int], value: int):
- """ Example usage: self._game_info.pathing_grid[Point2((20, 20))] = 255 """
+ def __setitem__(self, pos: tuple[int, int], value: int) -> None:
+ """Example usage: self._game_info.pathing_grid[Point2((20, 20))] = 255"""
assert 0 <= pos[0] < self.width, f"x is {pos[0]}, self.width is {self.width}"
assert 0 <= pos[1] < self.height, f"y is {pos[1]}, self.height is {self.height}"
- assert (
- 0 <= value <= 254 * self._in_bits + 1
- ), f"value is {value}, it should be between 0 and {254 * self._in_bits + 1}"
+ assert 0 <= value <= 254 * self._in_bits + 1, (
+ f"value is {value}, it should be between 0 and {254 * self._in_bits + 1}"
+ )
assert isinstance(value, int), f"value is of type {type(value)}, it should be an integer"
self.data_numpy[pos[1], pos[0]] = value
- def is_set(self, p: Tuple[int, int]) -> bool:
+ def is_set(self, p: tuple[int, int]) -> bool:
return self[p] != 0
- def is_empty(self, p: Tuple[int, int]) -> bool:
+ def is_empty(self, p: tuple[int, int]) -> bool:
return not self.is_set(p)
- def copy(self) -> "PixelMap":
+ def copy(self) -> PixelMap:
return PixelMap(self._proto, in_bits=self._in_bits)
- def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> Set[Point2]:
- nodes: Set[Point2] = set()
- queue: List[Point2] = [start_point]
+ def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> set[Point2]:
+ nodes: set[Point2] = set()
+ queue: list[Point2] = [start_point]
while queue:
x, y = queue.pop()
@@ -84,8 +85,8 @@ def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> Set[Po
queue += [Point2((x + a, y + b)) for a in [-1, 0, 1] for b in [-1, 0, 1] if not (a == 0 and b == 0)]
return nodes
- def flood_fill_all(self, pred: Callable[[int], bool]) -> Set[FrozenSet[Point2]]:
- groups: Set[FrozenSet[Point2]] = set()
+ def flood_fill_all(self, pred: Callable[[int], bool]) -> set[frozenset[Point2]]:
+ groups: set[frozenset[Point2]] = set()
for x in range(self.width):
for y in range(self.height):
@@ -103,17 +104,16 @@ def print(self, wide: bool = False) -> None:
print("#" if self.is_set((x, y)) else " ", end=(" " if wide else ""))
print("")
- def save_image(self, filename: Union[str, Path]):
+ def save_image(self, filename: str | Path) -> None:
data = [(0, 0, self[x, y]) for y in range(self.height) for x in range(self.width)]
- # pylint: disable=C0415
+
from PIL import Image
im = Image.new("RGB", (self.width, self.height))
- im.putdata(data) # type: ignore
+ im.putdata(data)
im.save(filename)
- def plot(self):
- # pylint: disable=C0415
+ def plot(self) -> None:
import matplotlib.pyplot as plt
plt.imshow(self.data_numpy, origin="lower")
diff --git a/sc2/player.py b/sc2/player.py
index e1259198..74ee5463 100644
--- a/sc2/player.py
+++ b/sc2/player.py
@@ -1,22 +1,23 @@
+# pyre-ignore-all-errors[6, 11, 16, 29]
+from __future__ import annotations
+
from abc import ABC
from pathlib import Path
-from typing import List, Union
from sc2.bot_ai import BotAI
from sc2.data import AIBuild, Difficulty, PlayerType, Race
class AbstractPlayer(ABC):
-
def __init__(
self,
p_type: PlayerType,
race: Race = None,
- name: str = None,
+ name: str | None = None,
difficulty=None,
ai_build=None,
- fullscreen=False
- ):
+ fullscreen: bool = False,
+ ) -> None:
assert isinstance(p_type, PlayerType), f"p_type is of type {type(p_type)}"
assert name is None or isinstance(name, str), f"name is of type {type(name)}"
@@ -44,24 +45,22 @@ def __init__(
assert ai_build is None
@property
- def needs_sc2(self):
+ def needs_sc2(self) -> bool:
return not isinstance(self, Computer)
class Human(AbstractPlayer):
-
- def __init__(self, race, name=None, fullscreen=False):
+ def __init__(self, race, name: str | None = None, fullscreen: bool = False) -> None:
super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen)
- def __str__(self):
+ def __str__(self) -> str:
if self.name is not None:
- return f"Human({self.race._name_}, name={self.name !r})"
+ return f"Human({self.race._name_}, name={self.name!r})"
return f"Human({self.race._name_})"
class Bot(AbstractPlayer):
-
- def __init__(self, race, ai, name=None, fullscreen=False):
+ def __init__(self, race, ai, name: str | None = None, fullscreen: bool = False) -> None:
"""
AI can be None if this player object is just used to inform the
server about player types.
@@ -70,39 +69,45 @@ def __init__(self, race, ai, name=None, fullscreen=False):
super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen)
self.ai = ai
- def __str__(self):
+ def __str__(self) -> str:
if self.name is not None:
- return f"Bot {self.ai.__class__.__name__}({self.race._name_}), name={self.name !r})"
+ return f"Bot {self.ai.__class__.__name__}({self.race._name_}), name={self.name!r})"
return f"Bot {self.ai.__class__.__name__}({self.race._name_})"
class Computer(AbstractPlayer):
-
- def __init__(self, race, difficulty=Difficulty.Easy, ai_build=AIBuild.RandomBuild):
+ def __init__(self, race, difficulty=Difficulty.Easy, ai_build=AIBuild.RandomBuild) -> None:
super().__init__(PlayerType.Computer, race, difficulty=difficulty, ai_build=ai_build)
- def __str__(self):
+ def __str__(self) -> str:
return f"Computer {self.difficulty._name_}({self.race._name_}, {self.ai_build.name})"
class Observer(AbstractPlayer):
-
- def __init__(self):
+ def __init__(self) -> None:
super().__init__(PlayerType.Observer)
- def __str__(self):
+ def __str__(self) -> str:
return "Observer"
class Player(AbstractPlayer):
-
- def __init__(self, player_id, p_type, requested_race, difficulty=None, actual_race=None, name=None, ai_build=None):
+ def __init__(
+ self,
+ player_id: int,
+ p_type,
+ requested_race,
+ difficulty=None,
+ actual_race=None,
+ name: str | None = None,
+ ai_build=None,
+ ) -> None:
super().__init__(p_type, requested_race, difficulty=difficulty, name=name, ai_build=ai_build)
self.id: int = player_id
self.actual_race: Race = actual_race
@classmethod
- def from_proto(cls, proto):
+ def from_proto(cls, proto) -> Player:
if PlayerType(proto.type) == PlayerType.Observer:
return cls(proto.player_id, PlayerType(proto.type), None, None, None)
return cls(
@@ -136,17 +141,17 @@ class BotProcess(AbstractPlayer):
def __init__(
self,
- path: Union[str, Path],
- launch_list: List[str],
+ path: str | Path,
+ launch_list: list[str],
race: Race,
- name=None,
- sc2port_arg="--GamePort",
- hostaddress_arg="--LadderServer",
- match_arg="--StartPort",
- realtime_arg="--RealTime",
- other_args: str = None,
- stdout: str = None,
- ):
+ name: str | None = None,
+ sc2port_arg: str = "--GamePort",
+ hostaddress_arg: str = "--LadderServer",
+ match_arg: str = "--StartPort",
+ realtime_arg: str = "--RealTime",
+ other_args: str | None = None,
+ stdout: str | None = None,
+ ) -> None:
super().__init__(PlayerType.Participant, race, name=name)
assert Path(path).exists()
self.path = path
@@ -158,16 +163,12 @@ def __init__(
self.other_args = other_args
self.stdout = stdout
- def __repr__(self):
+ def __repr__(self) -> str:
if self.name is not None:
return f"Bot {self.name}({self.race.name} from {self.launch_list})"
return f"Bot({self.race.name} from {self.launch_list})"
- def cmd_line(self,
- sc2port: Union[int, str],
- matchport: Union[int, str],
- hostaddress: str,
- realtime: bool = False) -> List[str]:
+ def cmd_line(self, sc2port: int | str, matchport: int | str, hostaddress: str, realtime: bool = False) -> list[str]:
"""
:param sc2port: the port that the launched sc2 instance listens to
diff --git a/sc2/portconfig.py b/sc2/portconfig.py
index 78011d89..2e646faf 100644
--- a/sc2/portconfig.py
+++ b/sc2/portconfig.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
import json
+# pyre-fixme[21]
import portpicker
@@ -22,7 +25,7 @@ class Portconfig:
E.g. for 1v1, there will be only 1 guest. For 2v2 (coming soonTM), there would be 3 guests.
"""
- def __init__(self, guests=1, server_ports=None, player_ports=None):
+ def __init__(self, guests: int = 1, server_ports=None, player_ports=None) -> None:
self.shared = None
self._picked_ports = []
if server_ports:
@@ -36,19 +39,19 @@ def __init__(self, guests=1, server_ports=None, player_ports=None):
self.players = [[portpicker.pick_unused_port() for _ in range(2)] for _ in range(guests)]
self._picked_ports.extend(port for player in self.players for port in player)
- def clean(self):
+ def clean(self) -> None:
while self._picked_ports:
portpicker.return_port(self._picked_ports.pop())
- def __str__(self):
+ def __str__(self) -> str:
return f"Portconfig(shared={self.shared}, server={self.server}, players={self.players})"
@property
- def as_json(self):
+ def as_json(self) -> str:
return json.dumps({"shared": self.shared, "server": self.server, "players": self.players})
@classmethod
- def contiguous_ports(cls, guests=1, attempts=40):
+ def contiguous_ports(cls, guests: int = 1, attempts: int = 40) -> Portconfig:
"""Returns a Portconfig with adjacent ports"""
for _ in range(attempts):
start = portpicker.pick_unused_port()
@@ -64,6 +67,6 @@ def contiguous_ports(cls, guests=1, attempts=40):
raise portpicker.NoFreePortFoundError()
@classmethod
- def from_json(cls, json_data):
+ def from_json(cls, json_data: bytearray | bytes | str) -> Portconfig:
data = json.loads(json_data)
return cls(server_ports=data["server"], player_ports=data["players"])
diff --git a/sc2/position.py b/sc2/position.py
index 86edcdd9..36a0922f 100644
--- a/sc2/position.py
+++ b/sc2/position.py
@@ -1,38 +1,39 @@
+# pyre-ignore-all-errors[6, 14, 15, 58]
from __future__ import annotations
import itertools
import math
import random
-import warnings
-from typing import TYPE_CHECKING, Iterable, List, Set, Tuple, Union
+from collections.abc import Iterable
+from typing import TYPE_CHECKING, SupportsFloat, SupportsIndex
+# pyre-fixme[21]
from s2clientprotocol import common_pb2 as common_pb
if TYPE_CHECKING:
from sc2.unit import Unit
from sc2.units import Units
-EPSILON = 10**-8
+EPSILON: float = 10**-8
-def _sign(num):
+def _sign(num: SupportsFloat | SupportsIndex) -> float:
return math.copysign(1, num)
class Pointlike(tuple):
-
@property
def position(self) -> Pointlike:
return self
- def distance_to(self, target: Union[Unit, Point2]) -> float:
+ def distance_to(self, target: Unit | Point2) -> float:
"""Calculate a single distance from a point or unit to another point or unit
:param target:"""
p = target.position
return math.hypot(self[0] - p[0], self[1] - p[1])
- def distance_to_point2(self, p: Union[Point2, Tuple[float, float]]) -> float:
+ def distance_to_point2(self, p: Point2 | tuple[float, float]) -> float:
"""Same as the function above, but should be a bit faster because of the dropped asserts
and conversion.
@@ -44,31 +45,9 @@ def _distance_squared(self, p2: Point2) -> float:
This is to speed up the sorting process.
:param p2:"""
- return (self[0] - p2[0])**2 + (self[1] - p2[1])**2
-
- def is_closer_than(self, distance: Union[int, float], p: Union[Unit, Point2]) -> bool:
- """Check if another point (or unit) is closer than the given distance.
-
- :param distance:
- :param p:"""
- warnings.warn(
- 'position.is_closer_than is deprecated and will be deleted soon', DeprecationWarning, stacklevel=2
- )
- p = p.position
- return self.distance_to_point2(p) < distance
-
- def is_further_than(self, distance: Union[int, float], p: Union[Unit, Point2]) -> bool:
- """Check if another point (or unit) is further than the given distance.
-
- :param distance:
- :param p:"""
- warnings.warn(
- 'position.is_further_than is deprecated and will be deleted soon', DeprecationWarning, stacklevel=2
- )
- p = p.position
- return self.distance_to_point2(p) > distance
+ return (self[0] - p2[0]) ** 2 + (self[1] - p2[1]) ** 2
- def sort_by_distance(self, ps: Union[Units, Iterable[Point2]]) -> List[Point2]:
+ def sort_by_distance(self, ps: Units | Iterable[Point2]) -> list[Point2]:
"""This returns the target points sorted as list.
You should not pass a set or dict since those are not sortable.
If you want to sort your units towards a point, use 'units.sorted_by_distance_to(point)' instead.
@@ -76,15 +55,15 @@ def sort_by_distance(self, ps: Union[Units, Iterable[Point2]]) -> List[Point2]:
:param ps:"""
return sorted(ps, key=lambda p: self.distance_to_point2(p.position))
- def closest(self, ps: Union[Units, Iterable[Point2]]) -> Union[Unit, Point2]:
+ def closest(self, ps: Units | Iterable[Point2]) -> Unit | Point2:
"""This function assumes the 2d distance is meant
:param ps:"""
assert ps, "ps is empty"
- # pylint: disable=W0108
+
return min(ps, key=lambda p: self.distance_to(p))
- def distance_to_closest(self, ps: Union[Units, Iterable[Point2]]) -> float:
+ def distance_to_closest(self, ps: Units | Iterable[Point2]) -> float:
"""This function assumes the 2d distance is meant
:param ps:"""
assert ps, "ps is empty"
@@ -96,15 +75,15 @@ def distance_to_closest(self, ps: Union[Units, Iterable[Point2]]) -> float:
closest_distance = distance
return closest_distance
- def furthest(self, ps: Union[Units, Iterable[Point2]]) -> Union[Unit, Pointlike]:
+ def furthest(self, ps: Units | Iterable[Point2]) -> Unit | Pointlike:
"""This function assumes the 2d distance is meant
:param ps: Units object, or iterable of Unit or Point2"""
assert ps, "ps is empty"
- # pylint: disable=W0108
+
return max(ps, key=lambda p: self.distance_to(p))
- def distance_to_furthest(self, ps: Union[Units, Iterable[Point2]]) -> float:
+ def distance_to_furthest(self, ps: Units | Iterable[Point2]) -> float:
"""This function assumes the 2d distance is meant
:param ps:"""
@@ -122,16 +101,16 @@ def offset(self, p) -> Pointlike:
:param p:
"""
- return self.__class__(a + b for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0))
+ return self.__class__(a + b for a, b in itertools.zip_longest(self, p[: len(self)], fillvalue=0))
- def unit_axes_towards(self, p):
+ def unit_axes_towards(self, p) -> Pointlike:
"""
:param p:
"""
- return self.__class__(_sign(b - a) for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0))
+ return self.__class__(_sign(b - a) for a, b in itertools.zip_longest(self, p[: len(self)], fillvalue=0))
- def towards(self, p: Union[Unit, Pointlike], distance: Union[int, float] = 1, limit: bool = False) -> Pointlike:
+ def towards(self, p: Unit | Pointlike, distance: int | float = 1, limit: bool = False) -> Pointlike:
"""
:param p:
@@ -148,22 +127,20 @@ def towards(self, p: Union[Unit, Pointlike], distance: Union[int, float] = 1, li
if limit:
distance = min(d, distance)
return self.__class__(
- a + (b - a) / d * distance for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0)
+ a + (b - a) / d * distance for a, b in itertools.zip_longest(self, p[: len(self)], fillvalue=0)
)
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
try:
return all(abs(a - b) <= EPSILON for a, b in itertools.zip_longest(self, other, fillvalue=0))
except TypeError:
return False
- def __hash__(self):
+ def __hash__(self) -> int:
return hash(tuple(self))
-# pylint: disable=R0904
class Point2(Pointlike):
-
@classmethod
def from_proto(cls, data) -> Point2:
"""
@@ -172,10 +149,12 @@ def from_proto(cls, data) -> Point2:
return cls((data.x, data.y))
@property
+ # pyre-fixme[11]
def as_Point2D(self) -> common_pb.Point2D:
return common_pb.Point2D(x=self.x, y=self.y)
@property
+ # pyre-fixme[11]
def as_PointI(self) -> common_pb.PointI:
"""Represents points on the minimap. Values must be between 0 and 64."""
return common_pb.PointI(x=self.x, y=self.y)
@@ -186,12 +165,12 @@ def rounded(self) -> Point2:
@property
def length(self) -> float:
- """ This property exists in case Point2 is used as a vector. """
+ """This property exists in case Point2 is used as a vector."""
return math.hypot(self[0], self[1])
@property
def normalized(self) -> Point2:
- """ This property exists in case Point2 is used as a vector. """
+ """This property exists in case Point2 is used as a vector."""
length = self.length
# Cannot normalize if length is zero
assert length
@@ -213,10 +192,14 @@ def to2(self) -> Point2:
def to3(self) -> Point3:
return Point3((*self, 0))
- def offset(self, p: Point2):
+ def round(self, decimals: int) -> Point2:
+ """Rounds each number in the tuple to the amount of given decimals."""
+ return Point2((round(self[0], decimals), round(self[1], decimals)))
+
+ def offset(self, p: Point2) -> Point2:
return Point2((self[0] + p[0], self[1] + p[1]))
- def random_on_distance(self, distance):
+ def random_on_distance(self, distance) -> Point2:
if isinstance(distance, (tuple, list)): # interval
distance = distance[0] + random.random() * (distance[1] - distance[0])
@@ -228,40 +211,40 @@ def random_on_distance(self, distance):
def towards_with_random_angle(
self,
- p: Union[Point2, Point3],
- distance: Union[int, float] = 1,
- max_difference: Union[int, float] = (math.pi / 4),
+ p: Point2 | Point3,
+ distance: int | float = 1,
+ max_difference: int | float = (math.pi / 4),
) -> Point2:
tx, ty = self.to2.towards(p.to2, 1)
angle = math.atan2(ty - self.y, tx - self.x)
angle = (angle - max_difference) + max_difference * 2 * random.random()
return Point2((self.x + math.cos(angle) * distance, self.y + math.sin(angle) * distance))
- def circle_intersection(self, p: Point2, r: Union[int, float]) -> Set[Point2]:
+ def circle_intersection(self, p: Point2, r: int | float) -> set[Point2]:
"""self is point1, p is point2, r is the radius for circles originating in both points
Used in ramp finding
:param p:
:param r:"""
assert self != p, "self is equal to p"
- distanceBetweenPoints = self.distance_to(p)
- assert r >= distanceBetweenPoints / 2
+ distance_between_points = self.distance_to(p)
+ assert r >= distance_between_points / 2
# remaining distance from center towards the intersection, using pythagoras
- remainingDistanceFromCenter = (r**2 - (distanceBetweenPoints / 2)**2)**0.5
+ remaining_distance_from_center = (r**2 - (distance_between_points / 2) ** 2) ** 0.5
# center of both points
- offsetToCenter = Point2(((p.x - self.x) / 2, (p.y - self.y) / 2))
- center = self.offset(offsetToCenter)
+ offset_to_center = Point2(((p.x - self.x) / 2, (p.y - self.y) / 2))
+ center = self.offset(offset_to_center)
# stretch offset vector in the ratio of remaining distance from center to intersection
- vectorStretchFactor = remainingDistanceFromCenter / (distanceBetweenPoints / 2)
- v = offsetToCenter
- offsetToCenterStretched = Point2((v.x * vectorStretchFactor, v.y * vectorStretchFactor))
+ vector_stretch_factor = remaining_distance_from_center / (distance_between_points / 2)
+ v = offset_to_center
+ offset_to_center_stretched = Point2((v.x * vector_stretch_factor, v.y * vector_stretch_factor))
# rotate vector by 90° and -90°
- vectorRotated1 = Point2((offsetToCenterStretched.y, -offsetToCenterStretched.x))
- vectorRotated2 = Point2((-offsetToCenterStretched.y, offsetToCenterStretched.x))
- intersect1 = center.offset(vectorRotated1)
- intersect2 = center.offset(vectorRotated2)
+ vector_rotated_1 = Point2((offset_to_center_stretched.y, -offset_to_center_stretched.x))
+ vector_rotated_2 = Point2((-offset_to_center_stretched.y, offset_to_center_stretched.x))
+ intersect1 = center.offset(vector_rotated_1)
+ intersect2 = center.offset(vector_rotated_2)
return {intersect1, intersect2}
@property
@@ -298,29 +281,28 @@ def __abs__(self) -> float:
return math.hypot(self.x, self.y)
def __bool__(self) -> bool:
- if self.x != 0 or self.y != 0:
- return True
- return False
+ return self.x != 0 or self.y != 0
- def __mul__(self, other: Union[int, float, Point2]) -> Point2:
+ def __mul__(self, other: int | float | Point2) -> Point2:
try:
+ # pyre-ignore[16]
return self.__class__((self.x * other.x, self.y * other.y))
except AttributeError:
return self.__class__((self.x * other, self.y * other))
- def __rmul__(self, other: Union[int, float, Point2]) -> Point2:
+ def __rmul__(self, other: int | float | Point2) -> Point2:
return self.__mul__(other)
- def __truediv__(self, other: Union[int, float, Point2]) -> Point2:
+ def __truediv__(self, other: int | float | Point2) -> Point2:
if isinstance(other, self.__class__):
return self.__class__((self.x / other.x, self.y / other.y))
return self.__class__((self.x / other, self.y / other))
- def is_same_as(self, other: Point2, dist=0.001) -> bool:
+ def is_same_as(self, other: Point2, dist: float = 0.001) -> bool:
return self.distance_to_point2(other) <= dist
def direction_vector(self, other: Point2) -> Point2:
- """ Converts a vector to a direction that can face vertically, horizontally or diagonal or be zero, e.g. (0, 0), (1, -1), (1, 0) """
+ """Converts a vector to a direction that can face vertically, horizontally or diagonal or be zero, e.g. (0, 0), (1, -1), (1, 0)"""
return self.__class__((_sign(other.x - self.x), _sign(other.y - self.y)))
def manhattan_distance(self, other: Point2) -> float:
@@ -330,18 +312,17 @@ def manhattan_distance(self, other: Point2) -> float:
return abs(other.x - self.x) + abs(other.y - self.y)
@staticmethod
- def center(units_or_points: Iterable[Point2]) -> Point2:
+ def center(points: list[Point2]) -> Point2:
"""Returns the central point for points in list
- :param units_or_points:"""
+ :param points:"""
s = Point2((0, 0))
- for p in units_or_points:
+ for p in points:
s += p
- return s / len(units_or_points)
+ return s / len(points)
class Point3(Point2):
-
@classmethod
def from_proto(cls, data) -> Point3:
"""
@@ -350,6 +331,7 @@ def from_proto(cls, data) -> Point3:
return cls((data.x, data.y, data.z))
@property
+ # pyre-fixme[11]
def as_Point(self) -> common_pb.Point:
return common_pb.Point(x=self.x, y=self.y, z=self.z)
@@ -365,14 +347,14 @@ def z(self) -> float:
def to3(self) -> Point3:
return Point3(self)
- def __add__(self, other: Union[Point2, Point3]) -> Point3:
+ def __add__(self, other: Point2 | Point3) -> Point3:
if not isinstance(other, Point3) and isinstance(other, Point2):
return Point3((self.x + other.x, self.y + other.y, self.z))
+ # pyre-ignore[16]
return Point3((self.x + other.x, self.y + other.y, self.z + other.z))
class Size(Point2):
-
@property
def width(self) -> float:
return self[0]
@@ -383,9 +365,8 @@ def height(self) -> float:
class Rect(tuple):
-
@classmethod
- def from_proto(cls, data):
+ def from_proto(cls, data) -> Rect:
"""
:param data:
"""
@@ -410,12 +391,12 @@ def height(self) -> float:
@property
def right(self) -> float:
- """ Returns the x-coordinate of the rectangle of its right side. """
+ """Returns the x-coordinate of the rectangle of its right side."""
return self.x + self.width
@property
def top(self) -> float:
- """ Returns the y-coordinate of the rectangle of its top side. """
+ """Returns the y-coordinate of the rectangle of its top side."""
return self.y + self.height
@property
@@ -426,5 +407,5 @@ def size(self) -> Size:
def center(self) -> Point2:
return Point2((self.x + self.width / 2, self.y + self.height / 2))
- def offset(self, p):
+ def offset(self, p) -> Rect:
return self.__class__((self[0] + p[0], self[1] + p[1], self[2], self[3]))
diff --git a/sc2/power_source.py b/sc2/power_source.py
index b143aefc..8c64bb62 100644
--- a/sc2/power_source.py
+++ b/sc2/power_source.py
@@ -1,34 +1,37 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+
from sc2.position import Point2
+@dataclass
class PowerSource:
+ position: Point2
+ radius: float
+ unit_tag: int
- @classmethod
- def from_proto(cls, proto):
- return cls(Point2.from_proto(proto.pos), proto.radius, proto.tag)
+ def __post_init__(self) -> None:
+ assert self.radius > 0
- def __init__(self, position, radius, unit_tag):
- assert isinstance(position, Point2)
- assert radius > 0
- self.position = position
- self.radius = radius
- self.unit_tag = unit_tag
+ @classmethod
+ def from_proto(cls, proto) -> PowerSource:
+ return PowerSource(Point2.from_proto(proto.pos), proto.radius, proto.tag)
- def covers(self, position):
+ def covers(self, position: Point2) -> bool:
return self.position.distance_to(position) <= self.radius
- def __repr__(self):
+ def __repr__(self) -> str:
return f"PowerSource({self.position}, {self.radius})"
+@dataclass
class PsionicMatrix:
+ sources: list[PowerSource]
@classmethod
- def from_proto(cls, proto):
- return cls([PowerSource.from_proto(p) for p in proto])
-
- def __init__(self, sources):
- self.sources = sources
+ def from_proto(cls, proto) -> PsionicMatrix:
+ return PsionicMatrix([PowerSource.from_proto(p) for p in proto])
- def covers(self, position):
+ def covers(self, position: Point2) -> bool:
return any(source.covers(position) for source in self.sources)
diff --git a/sc2/protocol.py b/sc2/protocol.py
index 8fc72684..5577b08f 100644
--- a/sc2/protocol.py
+++ b/sc2/protocol.py
@@ -1,43 +1,46 @@
+from __future__ import annotations
+
import asyncio
import sys
from contextlib import suppress
-from aiohttp import ClientWebSocketResponse
+from aiohttp.client_ws import ClientWebSocketResponse
from loguru import logger
+
+# pyre-fixme[21]
from s2clientprotocol import sc2api_pb2 as sc_pb
from sc2.data import Status
class ProtocolError(Exception):
-
@property
def is_game_over_error(self) -> bool:
return self.args[0] in ["['Game has already ended']", "['Not supported if game has already ended']"]
-class ConnectionAlreadyClosed(ProtocolError):
+class ConnectionAlreadyClosedError(ProtocolError):
pass
class Protocol:
-
- def __init__(self, ws):
+ def __init__(self, ws: ClientWebSocketResponse) -> None:
"""
A class for communicating with an SCII application.
:param ws: the websocket (type: aiohttp.ClientWebSocketResponse) used to communicate with a specific SCII app
"""
assert ws
self._ws: ClientWebSocketResponse = ws
- self._status: Status = None
+ # pyre-fixme[11]
+ self._status: Status | None = None
async def __request(self, request):
- logger.debug(f"Sending request: {request !r}")
+ logger.debug(f"Sending request: {request!r}")
try:
await self._ws.send_bytes(request.SerializeToString())
except TypeError as exc:
logger.exception("Cannot send: Connection already closed.")
- raise ConnectionAlreadyClosed("Connection already closed.") from exc
+ raise ConnectionAlreadyClosedError("Connection already closed.") from exc
logger.debug("Request sent")
response = sc_pb.Response()
@@ -46,9 +49,9 @@ async def __request(self, request):
except TypeError as exc:
if self._status == Status.ended:
logger.info("Cannot receive: Game has already ended.")
- raise ConnectionAlreadyClosed("Game has already ended") from exc
+ raise ConnectionAlreadyClosedError("Game has already ended") from exc
logger.error("Cannot receive: Connection already closed.")
- raise ConnectionAlreadyClosed("Connection already closed.") from exc
+ raise ConnectionAlreadyClosedError("Connection already closed.") from exc
except asyncio.CancelledError:
# If request is sent, the response must be received before reraising cancel
try:
@@ -82,6 +85,6 @@ async def ping(self):
result = await self._execute(ping=sc_pb.RequestPing())
return result
- async def quit(self):
- with suppress(ConnectionAlreadyClosed, ConnectionResetError):
+ async def quit(self) -> None:
+ with suppress(ConnectionAlreadyClosedError, ConnectionResetError):
await self._execute(quit=sc_pb.RequestQuit())
diff --git a/sc2/proxy.py b/sc2/proxy.py
index 2e204178..f2690322 100644
--- a/sc2/proxy.py
+++ b/sc2/proxy.py
@@ -1,13 +1,19 @@
-# pylint: disable=W0212
+# pyre-ignore-all-errors[16, 29]
+from __future__ import annotations
+
import asyncio
import os
import platform
import subprocess
import time
import traceback
+from pathlib import Path
from aiohttp import WSMsgType, web
+from aiohttp.web_ws import WebSocketResponse
from loguru import logger
+
+# pyre-fixme[21]
from s2clientprotocol import sc2api_pb2 as sc_pb
from sc2.controller import Controller
@@ -26,9 +32,9 @@ def __init__(
controller: Controller,
player: BotProcess,
proxyport: int,
- game_time_limit: int = None,
+ game_time_limit: int | None = None,
realtime: bool = False,
- ):
+ ) -> None:
self.controller = controller
self.player = player
self.port = proxyport
@@ -39,10 +45,10 @@ def __init__(
)
self.result = None
- self.player_id: int = None
+ self.player_id: int | None = None
self.done = False
- async def parse_request(self, msg):
+ async def parse_request(self, msg) -> None:
request = sc_pb.Request()
request.ParseFromString(msg.data)
if request.HasField("quit"):
@@ -58,7 +64,7 @@ async def parse_request(self, msg):
await self.controller._ws.send_bytes(request.SerializeToString())
# TODO Catching too general exception Exception (broad-except)
- # pylint: disable=W0703
+
async def get_response(self):
response_bytes = None
try:
@@ -91,38 +97,34 @@ async def parse_response(self, response_bytes):
logger.info(f"Controller({self.player.name}): {self.controller._status}->{new_status}")
self.controller._status = new_status
- if self.player_id is None:
- if response.HasField("join_game"):
- self.player_id = response.join_game.player_id
- logger.info(f"Proxy({self.player.name}): got join_game for {self.player_id}")
-
- if self.result is None:
- if response.HasField("observation"):
- obs: sc_pb.ResponseObservation = response.observation
- if obs.player_result:
- self.result = {pr.player_id: Result(pr.result) for pr in obs.player_result}
- elif (
- self.timeout_loop and obs.HasField("observation") and obs.observation.game_loop > self.timeout_loop
- ):
- self.result = {i: Result.Tie for i in range(1, 3)}
- logger.info(f"Proxy({self.player.name}) timing out")
- act = [sc_pb.Action(action_chat=sc_pb.ActionChat(message="Proxy: Timing out"))]
- await self.controller._execute(action=sc_pb.RequestAction(actions=act))
+ if self.player_id is None and response.HasField("join_game"):
+ self.player_id = response.join_game.player_id
+ logger.info(f"Proxy({self.player.name}): got join_game for {self.player_id}")
+
+ if self.result is None and response.HasField("observation"):
+ obs: sc_pb.ResponseObservation = response.observation
+ if obs.player_result:
+ self.result = {pr.player_id: Result(pr.result) for pr in obs.player_result}
+ elif self.timeout_loop and obs.HasField("observation") and obs.observation.game_loop > self.timeout_loop:
+ self.result = {i: Result.Tie for i in range(1, 3)} # noqa: C420
+ logger.info(f"Proxy({self.player.name}) timing out")
+ act = [sc_pb.Action(action_chat=sc_pb.ActionChat(message="Proxy: Timing out"))]
+ await self.controller._execute(action=sc_pb.RequestAction(actions=act))
return response
- async def get_result(self):
+ async def get_result(self) -> None:
try:
res = await self.controller.ping()
if res.status in {Status.in_game, Status.in_replay, Status.ended}:
res = await self.controller._execute(observation=sc_pb.RequestObservation())
if res.HasField("observation") and res.observation.player_result:
self.result = {pr.player_id: Result(pr.result) for pr in res.observation.player_result}
- # pylint: disable=W0703
+
# TODO Catching too general exception Exception (broad-except)
except Exception as e:
logger.exception(f"Caught unknown exception: {e}")
- async def proxy_handler(self, request):
+ async def proxy_handler(self, request) -> WebSocketResponse:
bot_ws = web.WebSocketResponse(receive_timeout=30)
await bot_ws.prepare(request)
try:
@@ -130,7 +132,6 @@ async def proxy_handler(self, request):
if msg.data is None:
raise TypeError(f"data is None, {msg}")
if msg.data and msg.type == WSMsgType.BINARY:
-
await self.parse_request(msg)
response_bytes = await self.get_response()
@@ -144,7 +145,7 @@ async def proxy_handler(self, request):
logger.error("Client shutdown")
else:
logger.error("Incorrect message type")
- # pylint: disable=W0703
+
# TODO Catching too general exception Exception (broad-except)
except Exception as e:
logger.exception(f"Caught unknown exception: {e}")
@@ -157,14 +158,13 @@ async def proxy_handler(self, request):
if self.controller._status in {Status.in_game, Status.in_replay}:
await self.controller._execute(leave_game=sc_pb.RequestLeaveGame())
await bot_ws.close()
- # pylint: disable=W0703
+
# TODO Catching too general exception Exception (broad-except)
except Exception as e:
logger.exception(f"Caught unknown exception during surrender: {e}")
self.done = True
return bot_ws
- # pylint: disable=R0912
async def play_with_proxy(self, startport):
logger.info(f"Proxy({self.port}): Starting app")
app = web.Application()
@@ -185,7 +185,7 @@ async def play_with_proxy(self, startport):
if self.player.stdout is None:
bot_process = subprocess.Popen(player_command_line, stdout=subprocess.DEVNULL, **subproc_args)
else:
- with open(self.player.stdout, "w+") as out:
+ with Path(self.player.stdout).open("w+") as out:
bot_process = subprocess.Popen(player_command_line, stdout=out, **subproc_args)
while self.result is None:
@@ -204,13 +204,13 @@ async def play_with_proxy(self, startport):
await asyncio.sleep(5)
# cleanup
- logger.info(f"({self.port}): cleaning up {self.player !r}")
+ logger.info(f"({self.port}): cleaning up {self.player!r}")
for _i in range(3):
if isinstance(bot_process, subprocess.Popen):
if bot_process.stdout and not bot_process.stdout.closed: # should not run anymore
logger.info(f"==================output for player {self.player.name}")
- for l in bot_process.stdout.readlines():
- logger.opt(raw=True).info(l.decode("utf-8"))
+ for line in bot_process.stdout.readlines():
+ logger.opt(raw=True).info(line.decode("utf-8"))
bot_process.stdout.close()
logger.info("==================")
bot_process.terminate()
@@ -223,7 +223,7 @@ async def play_with_proxy(self, startport):
bot_process.wait()
try:
await apprunner.cleanup()
- # pylint: disable=W0703
+
# TODO Catching too general exception Exception (broad-except)
except Exception as e:
logger.exception(f"Caught unknown exception during cleaning: {e}")
diff --git a/sc2/renderer.py b/sc2/renderer.py
index 60aceb0c..4d9f94ff 100644
--- a/sc2/renderer.py
+++ b/sc2/renderer.py
@@ -1,13 +1,13 @@
import datetime
+# pyre-ignore[21]
from s2clientprotocol import score_pb2 as score_pb
from sc2.position import Point2
class Renderer:
-
- def __init__(self, client, map_size, minimap_size):
+ def __init__(self, client, map_size, minimap_size) -> None:
self._client = client
self._window = None
@@ -22,7 +22,7 @@ def __init__(self, client, map_size, minimap_size):
self._text_score = None
self._text_time = None
- async def render(self, observation):
+ async def render(self, observation) -> None:
render_data = observation.observation.render_data
map_size = render_data.map.size
@@ -37,14 +37,16 @@ async def render(self, observation):
minimap_pitch = -minimap_width * 3
if not self._window:
- # pylint: disable=C0415
from pyglet.image import ImageData
from pyglet.text import Label
from pyglet.window import Window
self._window = Window(width=map_width, height=map_height)
+ # pyre-fixme[16]
self._window.on_mouse_press = self._on_mouse_press
+ # pyre-fixme[16]
self._window.on_mouse_release = self._on_mouse_release
+ # pyre-fixme[16]
self._window.on_mouse_drag = self._on_mouse_drag
self._map_image = ImageData(map_width, map_height, "RGB", map_data, map_pitch)
self._minimap_image = ImageData(minimap_width, minimap_height, "RGB", minimap_data, minimap_pitch)
@@ -107,7 +109,6 @@ async def render(self, observation):
self._text_vespene.text = str(observation.observation.player_common.vespene)
self._text_minerals.text = str(observation.observation.player_common.minerals)
if observation.observation.HasField("score"):
- # pylint: disable=W0212
self._text_score.text = f"{score_pb._SCORE_SCORETYPE.values_by_number[observation.observation.score.score_type].name} score: {observation.observation.score.score}"
await self._update_window()
@@ -116,7 +117,7 @@ async def render(self, observation):
await self._client.move_camera_spatial(Point2((self._mouse_x, self._minimap_size[0] - self._mouse_y)))
self._mouse_x, self._mouse_y = None, None
- async def _update_window(self):
+ async def _update_window(self) -> None:
self._window.switch_to()
self._window.dispatch_events()
@@ -132,21 +133,21 @@ async def _update_window(self):
self._window.flip()
- def _on_mouse_press(self, x, y, button, _modifiers):
+ def _on_mouse_press(self, x, y, button, _modifiers) -> None:
if button != 1: # 1: mouse.LEFT
return
if x > self._minimap_size[0] or y > self._minimap_size[1]:
return
self._mouse_x, self._mouse_y = x, y
- def _on_mouse_release(self, x, y, button, _modifiers):
+ def _on_mouse_release(self, x, y, button, _modifiers) -> None:
if button != 1: # 1: mouse.LEFT
return
if x > self._minimap_size[0] or y > self._minimap_size[1]:
return
self._mouse_x, self._mouse_y = x, y
- def _on_mouse_drag(self, x, y, _dx, _dy, buttons, _modifiers):
+ def _on_mouse_drag(self, x, y, _dx, _dy, buttons, _modifiers) -> None:
if not buttons & 1: # 1: mouse.LEFT
return
if x > self._minimap_size[0] or y > self._minimap_size[1]:
diff --git a/sc2/sc2process.py b/sc2/sc2process.py
index 1c25d9c2..846dc480 100644
--- a/sc2/sc2process.py
+++ b/sc2/sc2process.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import asyncio
import os
import os.path
@@ -8,10 +10,14 @@
import tempfile
import time
from contextlib import suppress
-from typing import Any, Dict, List, Optional, Tuple, Union
+from pathlib import Path
+from typing import Any
import aiohttp
+
+# pyre-ignore[21]
import portpicker
+from aiohttp.client_ws import ClientWebSocketResponse
from loguru import logger
from sc2 import paths, wsl
@@ -20,19 +26,18 @@
from sc2.versions import VERSIONS
-class kill_switch:
- _to_kill: List[Any] = []
+class KillSwitch:
+ _to_kill: list[Any] = []
@classmethod
- def add(cls, value):
+ def add(cls, value) -> None:
logger.debug("kill_switch: Add switch")
cls._to_kill.append(value)
@classmethod
- def kill_all(cls):
+ def kill_all(cls) -> None:
logger.info(f"kill_switch: Process cleanup for {len(cls._to_kill)} processes")
for p in cls._to_kill:
- # pylint: disable=W0212
p._clean(verbose=False)
@@ -54,21 +59,21 @@ class SC2Process:
def __init__(
self,
- host: Optional[str] = None,
- port: Optional[int] = None,
+ host: str | None = None,
+ port: int | None = None,
fullscreen: bool = False,
- resolution: Optional[Union[List[int], Tuple[int, int]]] = None,
- placement: Optional[Union[List[int], Tuple[int, int]]] = None,
+ resolution: list[int] | tuple[int, int] | None = None,
+ placement: list[int] | tuple[int, int] | None = None,
render: bool = False,
- sc2_version: str = None,
- base_build: str = None,
- data_hash: str = None,
+ sc2_version: str | None = None,
+ base_build: str | None = None,
+ data_hash: str | None = None,
) -> None:
assert isinstance(host, str) or host is None
assert isinstance(port, int) or port is None
self._render = render
- self._arguments: Dict[str, str] = {"-displayMode": str(int(fullscreen))}
+ self._arguments: dict[str, str] = {"-displayMode": str(int(fullscreen))}
if not fullscreen:
if resolution and len(resolution) == 2:
self._arguments["-windowwidth"] = str(resolution[0])
@@ -86,7 +91,7 @@ def __init__(
self._port = port
self._used_portpicker = bool(port is None)
self._tmp_dir = tempfile.mkdtemp(prefix="SC2_")
- self._process: subprocess = None
+ self._process: subprocess.Popen | None = None
self._session = None
self._ws = None
self._sc2_version = sc2_version
@@ -94,12 +99,12 @@ def __init__(
self._data_hash = data_hash
async def __aenter__(self) -> Controller:
- kill_switch.add(self)
+ KillSwitch.add(self)
def signal_handler(*_args):
# unused arguments: signal handling library expects all signal
# callback handlers to accept two positional arguments
- kill_switch.kill_all()
+ KillSwitch.kill_all()
signal.signal(signal.SIGINT, signal_handler)
@@ -113,13 +118,13 @@ def signal_handler(*_args):
return Controller(self._ws, self)
- async def __aexit__(self, *args):
+ async def __aexit__(self, *args) -> None:
await self._close_connection()
- kill_switch.kill_all()
+ KillSwitch.kill_all()
signal.signal(signal.SIGINT, signal.SIG_DFL)
@property
- def ws_url(self):
+ def ws_url(self) -> str:
return f"ws://{self._host}:{self._port}/sc2api"
@property
@@ -128,19 +133,31 @@ def versions(self):
https://github.com/Blizzard/s2client-proto/blob/master/buildinfo/versions.json"""
return VERSIONS
- def find_data_hash(self, target_sc2_version: str) -> Optional[str]:
- """ Returns the data hash from the matching version string. """
+ def find_data_hash(self, target_sc2_version: str) -> str | None:
+ """Returns the data hash from the matching version string."""
version: dict
for version in self.versions:
if version["label"] == target_sc2_version:
return version["data-hash"]
return None
+ def find_base_dir(self, target_sc2_version: str) -> str | None:
+ """Returns the base directory from the matching version string."""
+ version: dict
+ for version in self.versions:
+ if version["label"] == target_sc2_version:
+ return "Base" + str(version["base-version"])
+ return None
+
def _launch(self):
+ if self._sc2_version and not self._base_build:
+ self._base_build = self.find_base_dir(self._sc2_version)
+
if self._base_build:
executable = str(paths.latest_executeble(Paths.BASE / "Versions", self._base_build))
else:
executable = str(Paths.EXECUTABLE)
+
if self._port is None:
self._port = portpicker.pick_unused_port()
self._used_portpicker = True
@@ -161,18 +178,15 @@ def _launch(self):
if self._sc2_version:
def special_match(strg: str):
- """ Tests if the specified version is in the versions.py dict. """
- for version in self.versions:
- if version["label"] == strg:
- return True
- return False
+ """Tests if the specified version is in the versions.py dict."""
+ return any(version["label"] == strg for version in self.versions)
valid_version_string = special_match(self._sc2_version)
if valid_version_string:
self._data_hash = self.find_data_hash(self._sc2_version)
- assert (
- self._data_hash is not None
- ), f"StarCraft 2 Client version ({self._sc2_version}) was not found inside sc2/versions.py file. Please check your spelling or check the versions.py file."
+ assert self._data_hash is not None, (
+ f"StarCraft 2 Client version ({self._sc2_version}) was not found inside sc2/versions.py file. Please check your spelling or check the versions.py file."
+ )
else:
logger.warning(
@@ -197,11 +211,11 @@ def special_match(strg: str):
args,
cwd=sc2_cwd,
# Suppress Wine error messages
- stderr=subprocess.DEVNULL
+ stderr=subprocess.DEVNULL,
# , env=run_config.env
)
- async def _connect(self):
+ async def _connect(self) -> ClientWebSocketResponse:
# How long it waits for SC2 to start (in seconds)
for i in range(180):
if self._process is None:
@@ -227,7 +241,7 @@ async def _connect(self):
logger.debug("Websocket connection to SC2 process timed out")
raise TimeoutError("Websocket")
- async def _close_connection(self):
+ async def _close_connection(self) -> None:
logger.info(f"Closing connection at {self._port}...")
if self._ws is not None:
@@ -236,12 +250,12 @@ async def _close_connection(self):
if self._session is not None:
await self._session.close()
- # pylint: disable=R0912
- def _clean(self, verbose=True):
+ def _clean(self, verbose: bool = True) -> None:
if verbose:
logger.info("Cleaning up...")
if self._process is not None:
+ assert isinstance(self._process, subprocess.Popen)
if paths.PF in {"WSL1", "WSL2"}:
if wsl.kill(self._process):
logger.error("KILLED")
@@ -258,11 +272,10 @@ def _clean(self, verbose=True):
# Try to kill wineserver on linux
if paths.PF in {"Linux", "WineLinux"}:
# Command wineserver not detected
- with suppress(FileNotFoundError):
- with subprocess.Popen(["wineserver", "-k"]) as p:
- p.wait()
+ with suppress(FileNotFoundError), subprocess.Popen(["wineserver", "-k"]) as p:
+ p.wait()
- if os.path.exists(self._tmp_dir):
+ if Path(self._tmp_dir).exists():
shutil.rmtree(self._tmp_dir)
self._process = None
diff --git a/sc2/score.py b/sc2/score.py
index 808ee938..9b8f5f2c 100644
--- a/sc2/score.py
+++ b/sc2/score.py
@@ -1,10 +1,9 @@
-# pylint: disable=R0904
class ScoreDetails:
"""Accessable in self.state.score during step function
For more information, see https://github.com/Blizzard/s2client-proto/blob/master/s2clientprotocol/score.proto
"""
- def __init__(self, proto):
+ def __init__(self, proto) -> None:
self._data = proto
self._proto = proto.score_details
diff --git a/sc2/unit.py b/sc2/unit.py
index 2ac62429..07b63e90 100644
--- a/sc2/unit.py
+++ b/sc2/unit.py
@@ -1,11 +1,13 @@
-# pylint: disable=W0212
+# pyre-ignore-all-errors[11, 16, 29]
from __future__ import annotations
import math
import warnings
+from dataclasses import dataclass
from functools import cached_property
-from typing import TYPE_CHECKING, Any, Callable, FrozenSet, List, Optional, Set, Tuple, TypeVar, Union
+from typing import TYPE_CHECKING, Any
+from sc2.cache import CacheDict
from sc2.constants import (
CAN_BE_ATTACKED,
DAMAGE_BONUS_PER_UPGRADE,
@@ -63,45 +65,42 @@
from sc2.game_data import AbilityData, UnitTypeData
-class UnitOrder:
+@dataclass
+class RallyTarget:
+ point: Point2
+ tag: int | None = None
@classmethod
- def from_proto(cls, proto, bot_object: BotAI):
+ def from_proto(cls, proto: Any) -> RallyTarget:
return cls(
- bot_object.game_data.abilities[proto.ability_id],
- (proto.target_world_space_pos if proto.HasField("target_world_space_pos") else proto.target_unit_tag),
- proto.progress,
+ Point2.from_proto(proto.point),
+ proto.tag if proto.HasField("tag") else None,
)
- def __init__(self, ability: AbilityData, target, progress: float = 0):
- """
- :param ability:
- :param target:
- :param progress:
- """
- self.ability: AbilityData = ability
- # This can be an int (if target is unit) or proto Point2 object, which needs to be converted using 'Point2.from_proto(target)'
- self.target = target
- self.progress: float = progress
-
- def __repr__(self) -> str:
- return f"UnitOrder({self.ability}, {self.target}, {self.progress})"
-
-
-T = TypeVar("T")
+@dataclass
+class UnitOrder:
+ ability: AbilityData # TODO: Should this be AbilityId instead?
+ target: int | Point2 | None = None
+ progress: float = 0
-class CacheDict(dict):
+ @classmethod
+ def from_proto(cls, proto: Any, bot_object: BotAI) -> UnitOrder:
+ target: int | Point2 | None = proto.target_unit_tag
+ if proto.HasField("target_world_space_pos"):
+ target = Point2.from_proto(proto.target_world_space_pos)
+ elif proto.HasField("target_unit_tag"):
+ target = proto.target_unit_tag
+ return cls(
+ ability=bot_object.game_data.abilities[proto.ability_id],
+ target=target,
+ progress=proto.progress,
+ )
- def retrieve_and_set(self, key: Any, func: Callable[[], T]) -> T:
- """ Either return the value at a certain key, or set the return value of a function to that key, then return that value. """
- if key in self:
- return self[key]
- self[key] = func()
- return self[key]
+ def __repr__(self) -> str:
+ return f"UnitOrder({self.ability}, {self.target}, {self.progress})"
-# pylint: disable=R0904
class Unit:
class_cache = CacheDict()
@@ -111,7 +110,7 @@ def __init__(
bot_object: BotAI,
distance_calculation_index: int = -1,
base_build: int = -1,
- ):
+ ) -> None:
"""
:param proto_data:
:param bot_object:
@@ -126,84 +125,84 @@ def __init__(
self.distance_calculation_index: int = distance_calculation_index
def __repr__(self) -> str:
- """ Returns string of this form: Unit(name='SCV', tag=4396941328). """
- return f"Unit(name={self.name !r}, tag={self.tag})"
+ """Returns string of this form: Unit(name='SCV', tag=4396941328)."""
+ return f"Unit(name={self.name!r}, tag={self.tag})"
@property
def type_id(self) -> UnitTypeId:
- """ UnitTypeId found in sc2/ids/unit_typeid. """
+ """UnitTypeId found in sc2/ids/unit_typeid."""
unit_type: int = self._proto.unit_type
return self.class_cache.retrieve_and_set(unit_type, lambda: UnitTypeId(unit_type))
@cached_property
def _type_data(self) -> UnitTypeData:
- """ Provides the unit type data. """
+ """Provides the unit type data."""
return self._bot_object.game_data.units[self._proto.unit_type]
@cached_property
- def _creation_ability(self) -> AbilityData:
- """ Provides the AbilityData of the creation ability of this unit. """
+ def _creation_ability(self) -> AbilityData | None:
+ """Provides the AbilityData of the creation ability of this unit."""
return self._type_data.creation_ability
@property
def name(self) -> str:
- """ Returns the name of the unit. """
+ """Returns the name of the unit."""
return self._type_data.name
@cached_property
def race(self) -> Race:
- """ Returns the race of the unit """
+ """Returns the race of the unit"""
return Race(self._type_data._proto.race)
- @property
+ @cached_property
def tag(self) -> int:
- """ Returns the unique tag of the unit. """
+ """Returns the unique tag of the unit."""
return self._proto.tag
@property
def is_structure(self) -> bool:
- """ Checks if the unit is a structure. """
+ """Checks if the unit is a structure."""
return IS_STRUCTURE in self._type_data.attributes
@property
def is_light(self) -> bool:
- """ Checks if the unit has the 'light' attribute. """
+ """Checks if the unit has the 'light' attribute."""
return IS_LIGHT in self._type_data.attributes
@property
def is_armored(self) -> bool:
- """ Checks if the unit has the 'armored' attribute. """
+ """Checks if the unit has the 'armored' attribute."""
return IS_ARMORED in self._type_data.attributes
@property
def is_biological(self) -> bool:
- """ Checks if the unit has the 'biological' attribute. """
+ """Checks if the unit has the 'biological' attribute."""
return IS_BIOLOGICAL in self._type_data.attributes
@property
def is_mechanical(self) -> bool:
- """ Checks if the unit has the 'mechanical' attribute. """
+ """Checks if the unit has the 'mechanical' attribute."""
return IS_MECHANICAL in self._type_data.attributes
@property
def is_massive(self) -> bool:
- """ Checks if the unit has the 'massive' attribute. """
+ """Checks if the unit has the 'massive' attribute."""
return IS_MASSIVE in self._type_data.attributes
@property
def is_psionic(self) -> bool:
- """ Checks if the unit has the 'psionic' attribute. """
+ """Checks if the unit has the 'psionic' attribute."""
return IS_PSIONIC in self._type_data.attributes
@cached_property
- def tech_alias(self) -> Optional[List[UnitTypeId]]:
+ def tech_alias(self) -> list[UnitTypeId] | None:
"""Building tech equality, e.g. OrbitalCommand is the same as CommandCenter
For Hive, this returns [UnitTypeId.Hatchery, UnitTypeId.Lair]
For SCV, this returns None"""
return self._type_data.tech_alias
@cached_property
- def unit_alias(self) -> Optional[UnitTypeId]:
+ def unit_alias(self) -> UnitTypeId | None:
"""Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand
For flying OrbitalCommand, this returns UnitTypeId.OrbitalCommand
For SCV, this returns None"""
@@ -211,23 +210,23 @@ def unit_alias(self) -> Optional[UnitTypeId]:
@cached_property
def _weapons(self):
- """ Returns the weapons of the unit. """
+ """Returns the weapons of the unit."""
return self._type_data._proto.weapons
@cached_property
def can_attack(self) -> bool:
- """ Checks if the unit can attack at all. """
+ """Checks if the unit can attack at all."""
# TODO BATTLECRUISER doesnt have weapons in proto?!
return bool(self._weapons) or self.type_id in {UNIT_BATTLECRUISER, UNIT_ORACLE}
@property
def can_attack_both(self) -> bool:
- """ Checks if the unit can attack both ground and air units. """
+ """Checks if the unit can attack both ground and air units."""
return self.can_attack_ground and self.can_attack_air
@cached_property
def can_attack_ground(self) -> bool:
- """ Checks if the unit can attack ground units. """
+ """Checks if the unit can attack ground units."""
if self.type_id in {UNIT_BATTLECRUISER, UNIT_ORACLE}:
return True
if self._weapons:
@@ -236,7 +235,7 @@ def can_attack_ground(self) -> bool:
@cached_property
def ground_dps(self) -> float:
- """ Returns the dps against ground units. Does not include upgrades. """
+ """Returns the dps against ground units. Does not include upgrades."""
if self.can_attack_ground:
weapon = next((weapon for weapon in self._weapons if weapon.type in TARGET_GROUND), None)
if weapon:
@@ -245,7 +244,7 @@ def ground_dps(self) -> float:
@cached_property
def ground_range(self) -> float:
- """ Returns the range against ground units. Does not include upgrades. """
+ """Returns the range against ground units. Does not include upgrades."""
if self.type_id == UNIT_ORACLE:
return 4
if self.type_id == UNIT_BATTLECRUISER:
@@ -258,7 +257,7 @@ def ground_range(self) -> float:
@cached_property
def can_attack_air(self) -> bool:
- """ Checks if the unit can air attack at all. Does not include upgrades. """
+ """Checks if the unit can air attack at all. Does not include upgrades."""
if self.type_id == UNIT_BATTLECRUISER:
return True
if self._weapons:
@@ -267,7 +266,7 @@ def can_attack_air(self) -> bool:
@cached_property
def air_dps(self) -> float:
- """ Returns the dps against air units. Does not include upgrades. """
+ """Returns the dps against air units. Does not include upgrades."""
if self.can_attack_air:
weapon = next((weapon for weapon in self._weapons if weapon.type in TARGET_AIR), None)
if weapon:
@@ -276,7 +275,7 @@ def air_dps(self) -> float:
@cached_property
def air_range(self) -> float:
- """ Returns the range against air units. Does not include upgrades. """
+ """Returns the range against air units. Does not include upgrades."""
if self.type_id == UNIT_BATTLECRUISER:
return 6
if self.can_attack_air:
@@ -286,7 +285,7 @@ def air_range(self) -> float:
return 0
@cached_property
- def bonus_damage(self) -> Optional[Tuple[int, str]]:
+ def bonus_damage(self) -> tuple[int, str] | None:
"""Returns a tuple of form '(bonus damage, armor type)' if unit does 'bonus damage' against 'armor type'.
Possible armor typs are: 'Light', 'Armored', 'Biological', 'Mechanical', 'Psionic', 'Massive', 'Structure'."""
# TODO: Consider units with ability attacks (Oracle, Baneling) or multiple attacks (Thor).
@@ -294,18 +293,17 @@ def bonus_damage(self) -> Optional[Tuple[int, str]]:
for weapon in self._weapons:
if weapon.damage_bonus:
b = weapon.damage_bonus[0]
- bonus: int = b.bonus
- return bonus, Attribute(b.attribute).name
+ return b.bonus, Attribute(b.attribute).name
return None
@property
def armor(self) -> float:
- """ Returns the armor of the unit. Does not include upgrades """
+ """Returns the armor of the unit. Does not include upgrades"""
return self._type_data._proto.armor
@property
def sight_range(self) -> float:
- """ Returns the sight range of the unit. """
+ """Returns the sight range of the unit."""
return self._type_data._proto.sight_range
@property
@@ -317,10 +315,10 @@ def movement_speed(self) -> float:
@cached_property
def real_speed(self) -> float:
- """ See 'calculate_speed'. """
+ """See 'calculate_speed'."""
return self.calculate_speed()
- def calculate_speed(self, upgrades: Set[UpgradeId] = None) -> float:
+ def calculate_speed(self, upgrades: set[UpgradeId] | None = None) -> float:
"""Calculates the movement speed of the unit including buffs and upgrades.
Note: Upgrades only work with own units. Use "upgrades" param to set expected enemy upgrades.
@@ -334,7 +332,7 @@ def calculate_speed(self, upgrades: Set[UpgradeId] = None) -> float:
upgrades = self._bot_object.state.upgrades
if upgrades and unit_type in SPEED_UPGRADE_DICT:
- upgrade_id: Optional[UpgradeId] = SPEED_UPGRADE_DICT.get(unit_type, None)
+ upgrade_id: UpgradeId | None = SPEED_UPGRADE_DICT.get(unit_type, None)
if upgrade_id and upgrade_id in upgrades:
speed *= SPEED_INCREASE_DICT.get(unit_type, 1)
@@ -347,7 +345,7 @@ def calculate_speed(self, upgrades: Set[UpgradeId] = None) -> float:
# Off creep upgrades
elif upgrades:
- upgrade_id2: Optional[UpgradeId] = OFF_CREEP_SPEED_UPGRADE_DICT.get(unit_type, None)
+ upgrade_id2: UpgradeId | None = OFF_CREEP_SPEED_UPGRADE_DICT.get(unit_type, None)
if upgrade_id2:
speed *= OFF_CREEP_SPEED_INCREASE_DICT[unit_type]
@@ -376,49 +374,49 @@ def distance_per_step(self) -> float:
@property
def distance_to_weapon_ready(self) -> float:
- """ Distance a unit can travel before it's weapon is ready to be fired again."""
+ """Distance a unit can travel before it's weapon is ready to be fired again."""
return (self.real_speed / 22.4) * self.weapon_cooldown
@property
def is_mineral_field(self) -> bool:
- """ Checks if the unit is a mineral field. """
+ """Checks if the unit is a mineral field."""
return self._type_data.has_minerals
@property
def is_vespene_geyser(self) -> bool:
- """ Checks if the unit is a non-empty vespene geyser or gas extraction building. """
+ """Checks if the unit is a non-empty vespene geyser or gas extraction building."""
return self._type_data.has_vespene
@property
def health(self) -> float:
- """ Returns the health of the unit. Does not include shields. """
+ """Returns the health of the unit. Does not include shields."""
return self._proto.health
@property
def health_max(self) -> float:
- """ Returns the maximum health of the unit. Does not include shields. """
+ """Returns the maximum health of the unit. Does not include shields."""
return self._proto.health_max
@cached_property
def health_percentage(self) -> float:
- """ Returns the percentage of health the unit has. Does not include shields. """
+ """Returns the percentage of health the unit has. Does not include shields."""
if not self._proto.health_max:
return 0
return self._proto.health / self._proto.health_max
@property
def shield(self) -> float:
- """ Returns the shield points the unit has. Returns 0 for non-protoss units. """
+ """Returns the shield points the unit has. Returns 0 for non-protoss units."""
return self._proto.shield
@property
def shield_max(self) -> float:
- """ Returns the maximum shield points the unit can have. Returns 0 for non-protoss units. """
+ """Returns the maximum shield points the unit can have. Returns 0 for non-protoss units."""
return self._proto.shield_max
@cached_property
def shield_percentage(self) -> float:
- """ Returns the percentage of shield points the unit has. Returns 0 for non-protoss units. """
+ """Returns the percentage of shield points the unit has. Returns 0 for non-protoss units."""
if not self._proto.shield_max:
return 0
return self._proto.shield / self._proto.shield_max
@@ -434,34 +432,34 @@ def shield_health_percentage(self) -> float:
@property
def energy(self) -> float:
- """ Returns the amount of energy the unit has. Returns 0 for units without energy. """
+ """Returns the amount of energy the unit has. Returns 0 for units without energy."""
return self._proto.energy
@property
def energy_max(self) -> float:
- """ Returns the maximum amount of energy the unit can have. Returns 0 for units without energy. """
+ """Returns the maximum amount of energy the unit can have. Returns 0 for units without energy."""
return self._proto.energy_max
@cached_property
def energy_percentage(self) -> float:
- """ Returns the percentage of amount of energy the unit has. Returns 0 for units without energy. """
+ """Returns the percentage of amount of energy the unit has. Returns 0 for units without energy."""
if not self._proto.energy_max:
return 0
return self._proto.energy / self._proto.energy_max
@property
def age_in_frames(self) -> int:
- """ Returns how old the unit object data is (in game frames). This age does not reflect the unit was created / trained / morphed! """
+ """Returns how old the unit object data is (in game frames). This age does not reflect the unit was created / trained / morphed!"""
return self._bot_object.state.game_loop - self.game_loop
@property
def age(self) -> float:
- """ Returns how old the unit object data is (in game seconds). This age does not reflect when the unit was created / trained / morphed! """
+ """Returns how old the unit object data is (in game seconds). This age does not reflect when the unit was created / trained / morphed!"""
return (self._bot_object.state.game_loop - self.game_loop) / 22.4
@property
def is_memory(self) -> bool:
- """ Returns True if this Unit object is referenced from the future and is outdated. """
+ """Returns True if this Unit object is referenced from the future and is outdated."""
return self.game_loop != self._bot_object.state.game_loop
@cached_property
@@ -505,50 +503,50 @@ def is_placeholder(self) -> bool:
@property
def alliance(self) -> Alliance:
- """ Returns the team the unit belongs to. """
+ """Returns the team the unit belongs to."""
return self._proto.alliance
@property
def is_mine(self) -> bool:
- """ Checks if the unit is controlled by the bot. """
+ """Checks if the unit is controlled by the bot."""
return self._proto.alliance == IS_MINE
@property
def is_enemy(self) -> bool:
- """ Checks if the unit is hostile. """
+ """Checks if the unit is hostile."""
return self._proto.alliance == IS_ENEMY
@property
def owner_id(self) -> int:
- """ Returns the owner of the unit. This is a value of 1 or 2 in a two player game. """
+ """Returns the owner of the unit. This is a value of 1 or 2 in a two player game."""
return self._proto.owner
@property
- def position_tuple(self) -> Tuple[float, float]:
- """ Returns the 2d position of the unit as tuple without conversion to Point2. """
+ def position_tuple(self) -> tuple[float, float]:
+ """Returns the 2d position of the unit as tuple without conversion to Point2."""
return self._proto.pos.x, self._proto.pos.y
@cached_property
def position(self) -> Point2:
- """ Returns the 2d position of the unit. """
+ """Returns the 2d position of the unit."""
return Point2.from_proto(self._proto.pos)
@cached_property
def position3d(self) -> Point3:
- """ Returns the 3d position of the unit. """
+ """Returns the 3d position of the unit."""
return Point3.from_proto(self._proto.pos)
- def distance_to(self, p: Union[Unit, Point2]) -> float:
+ def distance_to(self, p: Unit | Point2) -> float:
"""Using the 2d distance between self and p.
To calculate the 3d distance, use unit.position3d.distance_to(p)
:param p:
"""
if isinstance(p, Unit):
- return self._bot_object._distance_squared_unit_to_unit(self, p)**0.5
+ return self._bot_object._distance_squared_unit_to_unit(self, p) ** 0.5
return self._bot_object.distance_math_hypot(self.position_tuple, p)
- def distance_to_squared(self, p: Union[Unit, Point2]) -> float:
+ def distance_to_squared(self, p: Unit | Point2) -> float:
"""Using the 2d distance squared between self and p. Slightly faster than distance_to, so when filtering a lot of units, this function is recommended to be used.
To calculate the 3d distance, use unit.position3d.distance_to(p)
@@ -573,13 +571,11 @@ def target_in_range(self, target: Unit, bonus_distance: float = 0) -> bool:
else:
return False
return (
- self._bot_object._distance_squared_unit_to_unit(self, target) <=
- (self.radius + target.radius + unit_attack_range + bonus_distance)**2
+ self._bot_object._distance_squared_unit_to_unit(self, target)
+ <= (self.radius + target.radius + unit_attack_range + bonus_distance) ** 2
)
- def in_ability_cast_range(
- self, ability_id: AbilityId, target: Union[Unit, Point2], bonus_distance: float = 0
- ) -> bool:
+ def in_ability_cast_range(self, ability_id: AbilityId, target: Unit | Point2, bonus_distance: float = 0) -> bool:
"""Test if a unit is able to cast an ability on the target without checking ability cooldown (like stalker blink) or if ability is made available through research (like HT storm).
:param ability_id:
@@ -590,32 +586,27 @@ def in_ability_cast_range(
assert cast_range > 0, f"Checking for an ability ({ability_id}) that has no cast range"
ability_target_type = self._bot_object.game_data.abilities[ability_id.value]._proto.target
# For casting abilities that target other units, like transfuse, feedback, snipe, yamato
- if (
- ability_target_type in {Target.Unit.value, Target.PointOrUnit.value} # type: ignore
- and isinstance(target, Unit)
- ):
+ if ability_target_type in {Target.Unit.value, Target.PointOrUnit.value} and isinstance(target, Unit):
return (
- self._bot_object._distance_squared_unit_to_unit(self, target) <=
- (cast_range + self.radius + target.radius + bonus_distance)**2
+ self._bot_object._distance_squared_unit_to_unit(self, target)
+ <= (cast_range + self.radius + target.radius + bonus_distance) ** 2
)
# For casting abilities on the ground, like queen creep tumor, ravager bile, HT storm
- if (
- ability_target_type in {Target.Point.value, Target.PointOrUnit.value} # type: ignore
- and isinstance(target, (Point2, tuple))
+ if ability_target_type in {Target.Point.value, Target.PointOrUnit.value} and isinstance(
+ target, (Point2, tuple)
):
return (
- self._bot_object._distance_pos_to_pos(self.position_tuple, target) <=
- cast_range + self.radius + bonus_distance
+ self._bot_object._distance_pos_to_pos(self.position_tuple, target)
+ <= cast_range + self.radius + bonus_distance
)
return False
- # pylint: disable=R0912,R0911
def calculate_damage_vs_target(
self,
target: Unit,
ignore_armor: bool = False,
include_overkill_damage: bool = True,
- ) -> Tuple[float, float, float]:
+ ) -> tuple[float, float, float]:
"""Returns a tuple of: [potential damage against target, attack speed, attack range]
Returns the properly calculated damage per full-attack against the target unit.
Returns (0, 0, 0) if this unit can't attack the target unit.
@@ -653,7 +644,8 @@ def calculate_damage_vs_target(
enemy_shield_armor = target.shield_upgrade_level
# Ultralisk armor upgrade, only works if target belongs to the bot calling this function
if (
- target.type_id in {UnitTypeId.ULTRALISK, UnitTypeId.ULTRALISKBURROWED} and target.is_mine
+ target.type_id in {UnitTypeId.ULTRALISK, UnitTypeId.ULTRALISKBURROWED}
+ and target.is_mine
and UpgradeId.CHITINOUSPLATING in target._bot_object.state.upgrades
):
enemy_armor += 2
@@ -675,20 +667,22 @@ def calculate_damage_vs_target(
return weapon_damage, 0.224, 6
# Fast return for bunkers, since they don't have a weapon similar to BCs
- if self.type_id == UnitTypeId.BUNKER:
- if self.is_enemy:
- if self.is_active:
- # Expect fully loaded bunker with marines
- return (24, 0.854, 6)
- return (0, 0, 0)
+ if self.type_id == UnitTypeId.BUNKER and self.is_enemy:
+ if self.is_active:
+ # Expect fully loaded bunker with marines
+ return (24, 0.854, 6)
+ return (0, 0, 0)
# TODO if bunker belongs to us, use passengers and upgrade level to calculate damage
- required_target_type: Set[int] = (
+ required_target_type: set[int] = (
TARGET_BOTH
- if target.type_id == UnitTypeId.COLOSSUS else TARGET_GROUND if not target.is_flying else TARGET_AIR
+ if target.type_id == UnitTypeId.COLOSSUS
+ else TARGET_GROUND
+ if not target.is_flying
+ else TARGET_AIR
)
# Contains total damage, attack speed and attack range
- damages: List[Tuple[float, float, float]] = []
+ damages: list[tuple[float, float, float]] = []
for weapon in self._weapons:
if weapon.type not in required_target_type:
continue
@@ -698,26 +692,29 @@ def calculate_damage_vs_target(
weapon_speed: float = weapon.speed
weapon_range: float = weapon.range
bonus_damage_per_upgrade = (
- 0 if not self.attack_upgrade_level else
- DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(None, 1)
+ 0
+ if not self.attack_upgrade_level
+ else DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(None, 1)
)
damage_per_attack: float = weapon.damage + self.attack_upgrade_level * bonus_damage_per_upgrade
# Remaining damage after all damage is dealt to shield
remaining_damage: float = 0
# Calculate bonus damage against target
- boni: List[float] = []
+ boni: list[float] = []
# TODO: hardcode hellbats when they have blueflame or attack upgrades
for bonus in weapon.damage_bonus:
# More about damage bonus https://github.com/Blizzard/s2client-proto/blob/b73eb59ac7f2c52b2ca585db4399f2d3202e102a/s2clientprotocol/data.proto#L55
if bonus.attribute in target._type_data.attributes:
bonus_damage_per_upgrade = (
- 0 if not self.attack_upgrade_level else
- DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(bonus.attribute, 0)
+ 0
+ if not self.attack_upgrade_level
+ else DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(bonus.attribute, 0)
)
# Hardcode blueflame damage bonus from hellions
if (
- bonus.attribute == IS_LIGHT and self.type_id == UnitTypeId.HELLION
+ bonus.attribute == IS_LIGHT
+ and self.type_id == UnitTypeId.HELLION
and UpgradeId.HIGHCAPACITYBARRELS in self._bot_object.state.upgrades
):
bonus_damage_per_upgrade += 5
@@ -769,11 +766,12 @@ def calculate_damage_vs_target(
UnitTypeId.MISSILETURRET,
UnitTypeId.AUTOTURRET,
}:
- upgrades: Set[UpgradeId] = self._bot_object.state.upgrades
+ upgrades: set[UpgradeId] = self._bot_object.state.upgrades
if (
self.type_id == UnitTypeId.ZERGLING
# Attack speed calculation only works for our unit
- and self.is_mine and UpgradeId.ZERGLINGATTACKSPEED in upgrades
+ and self.is_mine
+ and UpgradeId.ZERGLINGATTACKSPEED in upgrades
):
# 0.696044921875 for zerglings divided through 1.4 equals (+40% attack speed bonus from the upgrade):
weapon_speed /= 1.4
@@ -797,7 +795,8 @@ def calculate_damage_vs_target(
weapon_range += 2
elif (
self.type_id in {UnitTypeId.PLANETARYFORTRESS, UnitTypeId.MISSILETURRET, UnitTypeId.AUTOTURRET}
- and self.is_mine and UpgradeId.HISECAUTOTRACKING in upgrades
+ and self.is_mine
+ and UpgradeId.HISECAUTOTRACKING in upgrades
):
weapon_range += 1
@@ -822,8 +821,9 @@ def calculate_dps_vs_target(
:param ignore_armor:
:param include_overkill_damage:
"""
- calc_tuple: Tuple[float, float,
- float] = self.calculate_damage_vs_target(target, ignore_armor, include_overkill_damage)
+ calc_tuple: tuple[float, float, float] = self.calculate_damage_vs_target(
+ target, ignore_armor, include_overkill_damage
+ )
# TODO fix for real time? The result may have to be multiplied by 1.4 because of game_speed=normal
if calc_tuple[1] == 0:
return 0
@@ -850,7 +850,7 @@ def is_facing(self, other_unit: Unit, angle_error: float = 0.05) -> bool:
return angle_difference < angle_error
@property
- def footprint_radius(self) -> Optional[float]:
+ def footprint_radius(self) -> float | None:
"""For structures only.
For townhalls this returns 2.5
For barracks, spawning pool, gateway, this returns 1.5
@@ -863,17 +863,17 @@ def footprint_radius(self) -> Optional[float]:
@property
def radius(self) -> float:
- """ Half of unit size. See https://liquipedia.net/starcraft2/Unit_Statistics_(Legacy_of_the_Void) """
+ """Half of unit size. See https://liquipedia.net/starcraft2/Unit_Statistics_(Legacy_of_the_Void)"""
return self._proto.radius
@property
def build_progress(self) -> float:
- """ Returns completion in range [0,1]."""
+ """Returns completion in range [0,1]."""
return self._proto.build_progress
@property
def is_ready(self) -> bool:
- """ Checks if the unit is completed. """
+ """Checks if the unit is completed."""
return self.build_progress == 1
@property
@@ -885,42 +885,42 @@ def cloak(self) -> CloakState:
@property
def is_cloaked(self) -> bool:
- """ Checks if the unit is cloaked. """
+ """Checks if the unit is cloaked."""
return self._proto.cloak in IS_CLOAKED
@property
def is_revealed(self) -> bool:
- """ Checks if the unit is revealed. """
+ """Checks if the unit is revealed."""
return self._proto.cloak == IS_REVEALED
@property
def can_be_attacked(self) -> bool:
- """ Checks if the unit is revealed or not cloaked and therefore can be attacked. """
+ """Checks if the unit is revealed or not cloaked and therefore can be attacked."""
return self._proto.cloak in CAN_BE_ATTACKED
@cached_property
- def buffs(self) -> FrozenSet[BuffId]:
- """ Returns the set of current buffs the unit has. """
+ def buffs(self) -> frozenset[BuffId]:
+ """Returns the set of current buffs the unit has."""
return frozenset(BuffId(buff_id) for buff_id in self._proto.buff_ids)
@cached_property
def is_carrying_minerals(self) -> bool:
- """ Checks if a worker or MULE is carrying (gold-)minerals. """
+ """Checks if a worker or MULE is carrying (gold-)minerals."""
return not IS_CARRYING_MINERALS.isdisjoint(self.buffs)
@cached_property
def is_carrying_vespene(self) -> bool:
- """ Checks if a worker is carrying vespene gas. """
+ """Checks if a worker is carrying vespene gas."""
return not IS_CARRYING_VESPENE.isdisjoint(self.buffs)
@cached_property
def is_carrying_resource(self) -> bool:
- """ Checks if a worker is carrying a resource. """
+ """Checks if a worker is carrying a resource."""
return not IS_CARRYING_RESOURCES.isdisjoint(self.buffs)
@property
def detect_range(self) -> float:
- """ Returns the detection distance of the unit. """
+ """Returns the detection distance of the unit."""
return self._proto.detect_range
@cached_property
@@ -935,39 +935,39 @@ def radar_range(self) -> float:
@property
def is_selected(self) -> bool:
- """ Checks if the unit is currently selected. """
+ """Checks if the unit is currently selected."""
return self._proto.is_selected
@property
def is_on_screen(self) -> bool:
- """ Checks if the unit is on the screen. """
+ """Checks if the unit is on the screen."""
return self._proto.is_on_screen
@property
def is_blip(self) -> bool:
- """ Checks if the unit is detected by a sensor tower. """
+ """Checks if the unit is detected by a sensor tower."""
return self._proto.is_blip
@property
def is_powered(self) -> bool:
- """ Checks if the unit is powered by a pylon or warppism. """
+ """Checks if the unit is powered by a pylon or warppism."""
return self._proto.is_powered
@property
def is_active(self) -> bool:
- """ Checks if the unit has an order (e.g. unit is currently moving or attacking, structure is currently training or researching). """
+ """Checks if the unit has an order (e.g. unit is currently moving or attacking, structure is currently training or researching)."""
return self._proto.is_active
# PROPERTIES BELOW THIS COMMENT ARE NOT POPULATED FOR SNAPSHOTS
@property
def mineral_contents(self) -> int:
- """ Returns the amount of minerals remaining in a mineral field. """
+ """Returns the amount of minerals remaining in a mineral field."""
return self._proto.mineral_contents
@property
def vespene_contents(self) -> int:
- """ Returns the amount of gas remaining in a geyser. """
+ """Returns the amount of gas remaining in a geyser."""
return self._proto.vespene_contents
@property
@@ -978,17 +978,17 @@ def has_vespene(self) -> bool:
@property
def is_flying(self) -> bool:
- """ Checks if the unit is flying. """
+ """Checks if the unit is flying."""
return self._proto.is_flying or self.has_buff(BuffId.GRAVITONBEAM)
@property
def is_burrowed(self) -> bool:
- """ Checks if the unit is burrowed. """
+ """Checks if the unit is burrowed."""
return self._proto.is_burrowed
@property
def is_hallucination(self) -> bool:
- """ Returns True if the unit is your own hallucination or detected. """
+ """Returns True if the unit is your own hallucination or detected."""
return self._proto.is_hallucination
@property
@@ -999,7 +999,7 @@ def attack_upgrade_level(self) -> int:
@property
def armor_upgrade_level(self) -> int:
- """ Returns the upgrade level of the units armor. """
+ """Returns the upgrade level of the units armor."""
return self._proto.armor_upgrade_level
@property
@@ -1023,13 +1023,13 @@ def buff_duration_max(self) -> int:
# PROPERTIES BELOW THIS COMMENT ARE NOT POPULATED FOR ENEMIES
@cached_property
- def orders(self) -> Tuple[UnitOrder, ...]:
- """ Returns the a list of the current orders. """
+ def orders(self) -> list[UnitOrder]:
+ """Returns the a list of the current orders."""
# TODO: add examples on how to use unit orders
- return tuple(UnitOrder.from_proto(order, self._bot_object) for order in self._proto.orders)
+ return [UnitOrder.from_proto(order, self._bot_object) for order in self._proto.orders]
@cached_property
- def order_target(self) -> Optional[Union[int, Point2]]:
+ def order_target(self) -> int | Point2 | None:
"""Returns the target tag (if it is a Unit) or Point2 (if it is a Position)
from the first order, returns None if the unit is idle"""
if self.orders:
@@ -1041,10 +1041,10 @@ def order_target(self) -> Optional[Union[int, Point2]]:
@property
def is_idle(self) -> bool:
- """ Checks if unit is idle. """
+ """Checks if unit is idle."""
return not self._proto.orders
- def is_using_ability(self, abilities: Union[AbilityId, Set[AbilityId]]) -> bool:
+ def is_using_ability(self, abilities: AbilityId | set[AbilityId]) -> bool:
"""Check if the unit is using one of the given abilities.
Only works for own units."""
if not self.orders:
@@ -1114,17 +1114,17 @@ def add_on_tag(self) -> int:
@property
def has_add_on(self) -> bool:
- """ Checks if unit has an addon attached. """
+ """Checks if unit has an addon attached."""
return bool(self._proto.add_on_tag)
@cached_property
def has_techlab(self) -> bool:
- """Check if a structure is connected to a techlab addon. This should only ever return True for BARRACKS, FACTORY, STARPORT. """
+ """Check if a structure is connected to a techlab addon. This should only ever return True for BARRACKS, FACTORY, STARPORT."""
return self.add_on_tag in self._bot_object.techlab_tags
@cached_property
def has_reactor(self) -> bool:
- """Check if a structure is connected to a reactor addon. This should only ever return True for BARRACKS, FACTORY, STARPORT. """
+ """Check if a structure is connected to a reactor addon. This should only ever return True for BARRACKS, FACTORY, STARPORT."""
return self.add_on_tag in self._bot_object.reactor_tags
@cached_property
@@ -1150,13 +1150,13 @@ def add_on_position(self) -> Point2:
return self.position.offset(Point2((2.5, -0.5)))
@cached_property
- def passengers(self) -> Set[Unit]:
- """ Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """
+ def passengers(self) -> set[Unit]:
+ """Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism."""
return {Unit(unit, self._bot_object) for unit in self._proto.passengers}
@cached_property
- def passengers_tags(self) -> Set[int]:
- """ Returns the tags of the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """
+ def passengers_tags(self) -> set[int]:
+ """Returns the tags of the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism."""
return {unit.tag for unit in self._proto.passengers}
@property
@@ -1167,27 +1167,27 @@ def cargo_used(self) -> int:
@property
def has_cargo(self) -> bool:
- """ Checks if this unit has any units loaded. """
+ """Checks if this unit has any units loaded."""
return bool(self._proto.cargo_space_taken)
@property
def cargo_size(self) -> int:
- """ Returns the amount of cargo space the unit needs. """
+ """Returns the amount of cargo space the unit needs."""
return self._type_data.cargo_size
@property
def cargo_max(self) -> int:
- """ How much cargo space is available at maximum. """
+ """How much cargo space is available at maximum."""
return self._proto.cargo_space_max
@property
def cargo_left(self) -> int:
- """ Returns how much cargo space is currently left in the unit. """
+ """Returns how much cargo space is currently left in the unit."""
return self._proto.cargo_space_max - self._proto.cargo_space_taken
@property
def assigned_harvesters(self) -> int:
- """ Returns the number of workers currently gathering resources at a geyser or mining base."""
+ """Returns the number of workers currently gathering resources at a geyser or mining base."""
return self._proto.assigned_harvesters
@property
@@ -1229,7 +1229,10 @@ def engaged_target_tag(self) -> int:
# TODO What does this do?
return self._proto.engaged_target_tag
- # TODO: Add rally targets https://github.com/Blizzard/s2client-proto/commit/80484692fa9e0ea6e7be04e728e4f5995c64daa3#diff-3b331650a4f7c9271a579b31cf771ed5R88-R92
+ @cached_property
+ def rally_targets(self) -> list[RallyTarget]:
+ """Returns the queue of rallytargets of the structure."""
+ return [RallyTarget.from_proto(rally_target) for rally_target in self._proto.rally_targets]
# Unit functions
@@ -1246,7 +1249,7 @@ def train(
unit: UnitTypeId,
queue: bool = False,
can_afford_check: bool = False,
- ) -> Union[UnitCommand, bool]:
+ ) -> UnitCommand | bool:
"""Orders unit to train another 'unit'.
Usage: COMMANDCENTER.train(SCV)
@@ -1264,14 +1267,15 @@ def train(
def build(
self,
unit: UnitTypeId,
- position: Point2 = None,
+ position: Point2 | Unit | None = None,
queue: bool = False,
can_afford_check: bool = False,
- ) -> Union[UnitCommand, bool]:
+ ) -> UnitCommand | bool:
"""Orders unit to build another 'unit' at 'position'.
Usage::
SCV.build(COMMANDCENTER, position)
+ hatchery.build(UnitTypeId.LAIR)
# Target for refinery, assimilator and extractor needs to be the vespene geysir unit, not its position
SCV.build(REFINERY, target_vespene_geysir)
@@ -1281,9 +1285,9 @@ def build(
:param can_afford_check:
"""
if unit in {UnitTypeId.EXTRACTOR, UnitTypeId.ASSIMILATOR, UnitTypeId.REFINERY}:
- assert isinstance(
- position, Unit
- ), "When building the gas structure, the target needs to be a unit (the vespene geysir) not the position of the vespene geysir."
+ assert isinstance(position, Unit), (
+ "When building the gas structure, the target needs to be a unit (the vespene geysir) not the position of the vespene geysir."
+ )
return self(
self._bot_object.game_data.units[unit.value].creation_ability.id,
target=position,
@@ -1297,7 +1301,7 @@ def build_gas(
target_geysir: Unit,
queue: bool = False,
can_afford_check: bool = False,
- ) -> Union[UnitCommand, bool]:
+ ) -> UnitCommand | bool:
"""Orders unit to build another 'unit' at 'position'.
Usage::
@@ -1309,9 +1313,9 @@ def build_gas(
:param can_afford_check:
"""
gas_structure_type_id: UnitTypeId = race_gas[self._bot_object.race]
- assert isinstance(
- target_geysir, Unit
- ), "When building the gas structure, the target needs to be a unit (the vespene geysir) not the position of the vespene geysir."
+ assert isinstance(target_geysir, Unit), (
+ "When building the gas structure, the target needs to be a unit (the vespene geysir) not the position of the vespene geysir."
+ )
return self(
self._bot_object.game_data.units[gas_structure_type_id.value].creation_ability.id,
target=target_geysir,
@@ -1325,7 +1329,7 @@ def research(
upgrade: UpgradeId,
queue: bool = False,
can_afford_check: bool = False,
- ) -> Union[UnitCommand, bool]:
+ ) -> UnitCommand | bool:
"""Orders unit to research 'upgrade'.
Requires UpgradeId to be passed instead of AbilityId.
@@ -1345,7 +1349,7 @@ def warp_in(
unit: UnitTypeId,
position: Point2,
can_afford_check: bool = False,
- ) -> Union[UnitCommand, bool]:
+ ) -> UnitCommand | bool:
"""Orders Warpgate to warp in 'unit' at 'position'.
:param unit:
@@ -1361,7 +1365,7 @@ def warp_in(
can_afford_check=can_afford_check,
)
- def attack(self, target: Union[Unit, Point2], queue: bool = False) -> Union[UnitCommand, bool]:
+ def attack(self, target: Unit | Point2, queue: bool = False) -> UnitCommand | bool:
"""Orders unit to attack. Target can be a Unit or Point2.
Attacking a position will make the unit move there and attack everything on its way.
@@ -1370,7 +1374,7 @@ def attack(self, target: Union[Unit, Point2], queue: bool = False) -> Union[Unit
"""
return self(AbilityId.ATTACK, target=target, queue=queue)
- def smart(self, target: Union[Unit, Point2], queue: bool = False) -> Union[UnitCommand, bool]:
+ def smart(self, target: Unit | Point2, queue: bool = False) -> UnitCommand | bool:
"""Orders the smart command. Equivalent to a right-click order.
:param target:
@@ -1378,7 +1382,7 @@ def smart(self, target: Union[Unit, Point2], queue: bool = False) -> Union[UnitC
"""
return self(AbilityId.SMART, target=target, queue=queue)
- def gather(self, target: Unit, queue: bool = False) -> Union[UnitCommand, bool]:
+ def gather(self, target: Unit, queue: bool = False) -> UnitCommand | bool:
"""Orders a unit to gather minerals or gas.
'Target' must be a mineral patch or a gas extraction building.
@@ -1387,15 +1391,14 @@ def gather(self, target: Unit, queue: bool = False) -> Union[UnitCommand, bool]:
"""
return self(AbilityId.HARVEST_GATHER, target=target, queue=queue)
- def return_resource(self, target: Unit = None, queue: bool = False) -> Union[UnitCommand, bool]:
- """Orders the unit to return resource. Does not need a 'target'.
+ def return_resource(self, queue: bool = False) -> UnitCommand | bool:
+ """Orders the unit to return resource to the nearest townhall.
- :param target:
:param queue:
"""
- return self(AbilityId.HARVEST_RETURN, target=target, queue=queue)
+ return self(AbilityId.HARVEST_RETURN, target=None, queue=queue)
- def move(self, position: Union[Unit, Point2], queue: bool = False) -> Union[UnitCommand, bool]:
+ def move(self, position: Unit | Point2, queue: bool = False) -> UnitCommand | bool:
"""Orders the unit to move to 'position'.
Target can be a Unit (to follow that unit) or Point2.
@@ -1404,14 +1407,14 @@ def move(self, position: Union[Unit, Point2], queue: bool = False) -> Union[Unit
"""
return self(AbilityId.MOVE_MOVE, target=position, queue=queue)
- def hold_position(self, queue: bool = False) -> Union[UnitCommand, bool]:
+ def hold_position(self, queue: bool = False) -> UnitCommand | bool:
"""Orders a unit to stop moving. It will not move until it gets new orders.
:param queue:
"""
return self(AbilityId.HOLDPOSITION, queue=queue)
- def stop(self, queue: bool = False) -> Union[UnitCommand, bool]:
+ def stop(self, queue: bool = False) -> UnitCommand | bool:
"""Orders a unit to stop, but can start to move on its own
if it is attacked, enemy unit is in range or other friendly
units need the space.
@@ -1420,7 +1423,7 @@ def stop(self, queue: bool = False) -> Union[UnitCommand, bool]:
"""
return self(AbilityId.STOP, queue=queue)
- def patrol(self, position: Point2, queue: bool = False) -> Union[UnitCommand, bool]:
+ def patrol(self, position: Point2, queue: bool = False) -> UnitCommand | bool:
"""Orders a unit to patrol between position it has when the command starts and the target position.
Can be queued up to seven patrol points. If the last point is the same as the starting
point, the unit will patrol in a circle.
@@ -1430,7 +1433,7 @@ def patrol(self, position: Point2, queue: bool = False) -> Union[UnitCommand, bo
"""
return self(AbilityId.PATROL, target=position, queue=queue)
- def repair(self, repair_target: Unit, queue: bool = False) -> Union[UnitCommand, bool]:
+ def repair(self, repair_target: Unit, queue: bool = False) -> UnitCommand | bool:
"""Order an SCV or MULE to repair.
:param repair_target:
@@ -1441,7 +1444,7 @@ def repair(self, repair_target: Unit, queue: bool = False) -> Union[UnitCommand,
def __hash__(self) -> int:
return self.tag
- def __eq__(self, other: Union[Unit, Any]) -> bool:
+ def __eq__(self, other: Unit | Any) -> bool:
"""
:param other:
"""
@@ -1450,13 +1453,13 @@ def __eq__(self, other: Union[Unit, Any]) -> bool:
def __call__(
self,
ability: AbilityId,
- target: Optional[Union[Point2, Unit]] = None,
+ target: Point2 | Unit | None = None,
queue: bool = False,
subtract_cost: bool = False,
subtract_supply: bool = False,
can_afford_check: bool = False,
- ) -> Union[UnitCommand, bool]:
- """ Deprecated: Stop using self.do() - This may be removed in the future.
+ ) -> UnitCommand | bool:
+ """Deprecated: Stop using self.do() - This may be removed in the future.
:param ability:
:param target:
diff --git a/sc2/unit_command.py b/sc2/unit_command.py
index 00a47406..3f17d96a 100644
--- a/sc2/unit_command.py
+++ b/sc2/unit_command.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Tuple, Union
+from typing import TYPE_CHECKING
from sc2.constants import COMBINEABLE_ABILITIES
from sc2.ids.ability_id import AbilityId
@@ -11,8 +11,9 @@
class UnitCommand:
-
- def __init__(self, ability: AbilityId, unit: Unit, target: Union[Unit, Point2] = None, queue: bool = False):
+ def __init__(
+ self, ability: AbilityId, unit: Unit, target: Unit | Point2 | None = None, queue: bool = False
+ ) -> None:
"""
:param ability:
:param unit:
@@ -35,8 +36,8 @@ def __init__(self, ability: AbilityId, unit: Unit, target: Union[Unit, Point2] =
self.queue = queue
@property
- def combining_tuple(self) -> Tuple[AbilityId, Union[Unit, Point2], bool, bool]:
+ def combining_tuple(self) -> tuple[AbilityId, Unit | Point2 | None, bool, bool]:
return self.ability, self.target, self.queue, self.ability in COMBINEABLE_ABILITIES
- def __repr__(self):
+ def __repr__(self) -> str:
return f"UnitCommand({self.ability}, {self.unit}, {self.target}, {self.queue})"
diff --git a/sc2/units.py b/sc2/units.py
index 21db7e14..38813601 100644
--- a/sc2/units.py
+++ b/sc2/units.py
@@ -1,10 +1,10 @@
-# pylint: disable=W0212
+# pyre-ignore-all-errors[14, 15, 16]
from __future__ import annotations
import random
-from functools import cached_property
+from collections.abc import Callable, Generator, Iterable
from itertools import chain
-from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, List, Optional, Set, Tuple, Union
+from typing import TYPE_CHECKING, Any
from sc2.ids.unit_typeid import UnitTypeId
from sc2.position import Point2
@@ -14,16 +14,14 @@
from sc2.bot_ai import BotAI
-# pylint: disable=R0904
class Units(list):
"""A collection of Unit objects. Makes it easy to select units by selectors."""
@classmethod
- def from_proto(cls, units, bot_object: BotAI):
- # pylint: disable=E1120
- return cls((Unit(raw_unit, bot_object=bot_object) for raw_unit in units))
+ def from_proto(cls, units, bot_object: BotAI) -> Units:
+ return cls((Unit(raw_unit, bot_object=bot_object) for raw_unit in units), bot_object)
- def __init__(self, units: Iterable[Unit], bot_object: BotAI):
+ def __init__(self, units: Iterable[Unit], bot_object: BotAI) -> None:
"""
:param units:
:param bot_object:
@@ -31,24 +29,16 @@ def __init__(self, units: Iterable[Unit], bot_object: BotAI):
super().__init__(units)
self._bot_object = bot_object
- def __call__(self, unit_types: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
+ def __call__(self, unit_types: UnitTypeId | Iterable[UnitTypeId]) -> Units:
"""Creates a new mutable Units object from Units or list object.
:param unit_types:
"""
return self.of_type(unit_types)
- def __iter__(self) -> Generator[Unit]:
+ def __iter__(self) -> Generator[Unit, None, None]:
return (item for item in super().__iter__())
- # TODO Deprecate in favor of Units.__call__
- def select(self, units: Iterable[Unit]):
- """Creates a new mutable Units object from Units or list object.
-
- :param units:
- """
- return Units(units, self._bot_object)
-
def copy(self) -> Units:
"""Creates a new mutable Units object from Units or list object.
@@ -113,7 +103,7 @@ def empty(self) -> bool:
def exists(self) -> bool:
return bool(self)
- def find_by_tag(self, tag: int) -> Optional[Unit]:
+ def find_by_tag(self, tag: int) -> Unit | None:
"""
:param tag:
"""
@@ -149,11 +139,11 @@ def random(self) -> Unit:
assert self, "Units object is empty"
return random.choice(self)
- def random_or(self, other: any) -> Unit:
+ def random_or(self, other: Any) -> Unit:
return random.choice(self) if self else other
def random_group_of(self, n: int) -> Units:
- """ Returns self if n >= self.amount. """
+ """Returns self if n >= self.amount."""
if n < 1:
return Units([], self._bot_object)
if n >= self.amount:
@@ -185,7 +175,7 @@ def in_attack_range_of(self, unit: Unit, bonus_distance: float = 0) -> Units:
"""
return self.filter(lambda x: unit.target_in_range(x, bonus_distance=bonus_distance))
- def closest_distance_to(self, position: Union[Unit, Point2]) -> float:
+ def closest_distance_to(self, position: Unit | Point2) -> float:
"""Returns the distance between the closest unit from this group to the target unit.
Example::
@@ -200,10 +190,10 @@ def closest_distance_to(self, position: Union[Unit, Point2]) -> float:
"""
assert self, "Units object is empty"
if isinstance(position, Unit):
- return min(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self)**0.5
+ return min(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self) ** 0.5
return min(self._bot_object._distance_units_to_pos(self, position))
- def furthest_distance_to(self, position: Union[Unit, Point2]) -> float:
+ def furthest_distance_to(self, position: Unit | Point2) -> float:
"""Returns the distance between the furthest unit from this group to the target unit
@@ -219,10 +209,10 @@ def furthest_distance_to(self, position: Union[Unit, Point2]) -> float:
"""
assert self, "Units object is empty"
if isinstance(position, Unit):
- return max(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self)**0.5
+ return max(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self) ** 0.5
return max(self._bot_object._distance_units_to_pos(self, position))
- def closest_to(self, position: Union[Unit, Point2]) -> Unit:
+ def closest_to(self, position: Unit | Point2) -> Unit:
"""Returns the closest unit (from this Units object) to the target unit or position.
Example::
@@ -245,7 +235,7 @@ def closest_to(self, position: Union[Unit, Point2]) -> Unit:
distances = self._bot_object._distance_units_to_pos(self, position)
return min(((unit, dist) for unit, dist in zip(self, distances)), key=lambda my_tuple: my_tuple[1])[0]
- def furthest_to(self, position: Union[Unit, Point2]) -> Unit:
+ def furthest_to(self, position: Unit | Point2) -> Unit:
"""Returns the furhest unit (from this Units object) to the target unit or position.
Example::
@@ -267,7 +257,7 @@ def furthest_to(self, position: Union[Unit, Point2]) -> Unit:
distances = self._bot_object._distance_units_to_pos(self, position)
return max(((unit, dist) for unit, dist in zip(self, distances)), key=lambda my_tuple: my_tuple[1])[0]
- def closer_than(self, distance: float, position: Union[Unit, Point2]) -> Units:
+ def closer_than(self, distance: float, position: Unit | Point2) -> Units:
"""Returns all units (from this Units object) that are closer than 'distance' away from target unit or position.
Example::
@@ -286,13 +276,14 @@ def closer_than(self, distance: float, position: Union[Unit, Point2]) -> Units:
if isinstance(position, Unit):
distance_squared = distance**2
return self.subgroup(
- unit for unit in self
+ unit
+ for unit in self
if self._bot_object._distance_squared_unit_to_unit(unit, position) < distance_squared
)
distances = self._bot_object._distance_units_to_pos(self, position)
return self.subgroup(unit for unit, dist in zip(self, distances) if dist < distance)
- def further_than(self, distance: float, position: Union[Unit, Point2]) -> Units:
+ def further_than(self, distance: float, position: Unit | Point2) -> Units:
"""Returns all units (from this Units object) that are further than 'distance' away from target unit or position.
Example::
@@ -311,14 +302,15 @@ def further_than(self, distance: float, position: Union[Unit, Point2]) -> Units:
if isinstance(position, Unit):
distance_squared = distance**2
return self.subgroup(
- unit for unit in self
+ unit
+ for unit in self
if distance_squared < self._bot_object._distance_squared_unit_to_unit(unit, position)
)
distances = self._bot_object._distance_units_to_pos(self, position)
return self.subgroup(unit for unit, dist in zip(self, distances) if distance < dist)
def in_distance_between(
- self, position: Union[Unit, Point2, Tuple[float, float]], distance1: float, distance2: float
+ self, position: Unit | Point2 | tuple[float, float], distance1: float, distance2: float
) -> Units:
"""Returns units that are further than distance1 and closer than distance2 to unit or position.
@@ -340,13 +332,16 @@ def in_distance_between(
distance1_squared = distance1**2
distance2_squared = distance2**2
return self.subgroup(
- unit for unit in self if
- distance1_squared < self._bot_object._distance_squared_unit_to_unit(unit, position) < distance2_squared
+ unit
+ for unit in self
+ if distance1_squared
+ < self._bot_object._distance_squared_unit_to_unit(unit, position)
+ < distance2_squared
)
distances = self._bot_object._distance_units_to_pos(self, position)
return self.subgroup(unit for unit, dist in zip(self, distances) if distance1 < dist < distance2)
- def closest_n_units(self, position: Union[Unit, Point2], n: int) -> Units:
+ def closest_n_units(self, position: Unit | Point2, n: int) -> Units:
"""Returns the n closest units in distance to position.
Example::
@@ -364,7 +359,7 @@ def closest_n_units(self, position: Union[Unit, Point2], n: int) -> Units:
return self
return self.subgroup(self._list_sorted_by_distance_to(position)[:n])
- def furthest_n_units(self, position: Union[Unit, Point2], n: int) -> Units:
+ def furthest_n_units(self, position: Unit | Point2, n: int) -> Units:
"""Returns the n furhest units in distance to position.
Example::
@@ -402,7 +397,9 @@ def in_distance_of_group(self, other_units: Units, distance: float) -> Units:
return self.subgroup([])
return self.subgroup(
- self_unit for self_unit in self if any(
+ self_unit
+ for self_unit in self
+ if any(
self._bot_object._distance_squared_unit_to_unit(self_unit, other_unit) < distance_squared
for other_unit in other_units
)
@@ -419,11 +416,12 @@ def in_closest_distance_to_group(self, other_units: Units) -> Unit:
assert other_units, "Given units object is empty"
return min(
self,
- key=lambda self_unit:
- min(self._bot_object._distance_squared_unit_to_unit(self_unit, other_unit) for other_unit in other_units),
+ key=lambda self_unit: min(
+ self._bot_object._distance_squared_unit_to_unit(self_unit, other_unit) for other_unit in other_units
+ ),
)
- def _list_sorted_closest_to_distance(self, position: Union[Unit, Point2], distance: float) -> List[Unit]:
+ def _list_sorted_closest_to_distance(self, position: Unit | Point2, distance: float) -> list[Unit]:
"""This function should be a bit faster than using units.sorted(key=lambda u: u.distance_to(position))
:param position:
@@ -457,7 +455,6 @@ def n_furthest_to_distance(self, position: Point2, distance: float, n: int) -> U
"""
return self.subgroup(self._list_sorted_closest_to_distance(position=position, distance=distance)[-n:])
- # TODO Deprecate in favor of Units.__call__
def subgroup(self, units: Iterable[Unit]) -> Units:
"""Creates a new mutable Units object from Units or list object.
@@ -491,7 +488,7 @@ def filter(self, pred: Callable[[Unit], Any]) -> Units:
def sorted(self, key: Callable[[Unit], Any], reverse: bool = False) -> Units:
return self.subgroup(sorted(self, key=key, reverse=reverse))
- def _list_sorted_by_distance_to(self, position: Union[Unit, Point2], reverse: bool = False) -> List[Unit]:
+ def _list_sorted_by_distance_to(self, position: Unit | Point2, reverse: bool = False) -> list[Unit]:
"""This function should be a bit faster than using units.sorted(key=lambda u: u.distance_to(position))
:param position:
@@ -505,7 +502,7 @@ def _list_sorted_by_distance_to(self, position: Union[Unit, Point2], reverse: bo
unit_dist_dict = {unit.tag: dist for unit, dist in zip(self, distances)}
return sorted(self, key=lambda unit2: unit_dist_dict[unit2.tag], reverse=reverse)
- def sorted_by_distance_to(self, position: Union[Unit, Point2], reverse: bool = False) -> Units:
+ def sorted_by_distance_to(self, position: Unit | Point2, reverse: bool = False) -> Units:
"""This function should be a bit faster than using units.sorted(key=lambda u: u.distance_to(position))
:param position:
@@ -541,7 +538,7 @@ def tags_not_in(self, other: Iterable[int]) -> Units:
"""
return self.filter(lambda unit: unit.tag not in other)
- def of_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
+ def of_type(self, other: UnitTypeId | Iterable[UnitTypeId]) -> Units:
"""Filters all units that are of a specific type
Example::
@@ -557,7 +554,7 @@ def of_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
other = set(other)
return self.filter(lambda unit: unit.type_id in other)
- def exclude_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
+ def exclude_type(self, other: UnitTypeId | Iterable[UnitTypeId]) -> Units:
"""Filters all units that are not of a specific type
Example::
@@ -573,7 +570,7 @@ def exclude_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
other = set(other)
return self.filter(lambda unit: unit.type_id not in other)
- def same_tech(self, other: Set[UnitTypeId]) -> Units:
+ def same_tech(self, other: set[UnitTypeId]) -> Units:
"""Returns all structures that have the same base structure.
Untested: This should return the equivalents for WarpPrism, Observer, Overseer, SupplyDepot and others
@@ -597,20 +594,20 @@ def same_tech(self, other: Set[UnitTypeId]) -> Units:
:param other:
"""
assert isinstance(other, set), (
- "Please use a set as this filter function is already fairly slow. For example" +
- " 'self.units.same_tech({UnitTypeId.LAIR})'"
+ "Please use a set as this filter function is already fairly slow. For example"
+ + " 'self.units.same_tech({UnitTypeId.LAIR})'"
)
- tech_alias_types: Set[int] = {u.value for u in other}
+ tech_alias_types: set[int] = {u.value for u in other}
unit_data = self._bot_object.game_data.units
- for unitType in other:
- for same in unit_data[unitType.value]._proto.tech_alias:
+ for unit_type in other:
+ for same in unit_data[unit_type.value]._proto.tech_alias:
tech_alias_types.add(same)
return self.filter(
- lambda unit: unit._proto.unit_type in tech_alias_types or
- any(same in tech_alias_types for same in unit._type_data._proto.tech_alias)
+ lambda unit: unit._proto.unit_type in tech_alias_types
+ or any(same in tech_alias_types for same in unit._type_data._proto.tech_alias)
)
- def same_unit(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
+ def same_unit(self, other: UnitTypeId | Iterable[UnitTypeId]) -> Units:
"""Returns all units that have the same base unit while being in different modes.
Untested: This should return the equivalents for WarpPrism, Observer, Overseer, SupplyDepot and other units that have different modes but still act as the same unit
@@ -632,96 +629,95 @@ def same_unit(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units:
"""
if isinstance(other, UnitTypeId):
other = {other}
- unit_alias_types: Set[int] = {u.value for u in other}
+ unit_alias_types: set[int] = {u.value for u in other}
unit_data = self._bot_object.game_data.units
- for unitType in other:
- unit_alias_types.add(unit_data[unitType.value]._proto.unit_alias)
+ for unit_type in other:
+ unit_alias_types.add(unit_data[unit_type.value]._proto.unit_alias)
unit_alias_types.discard(0)
return self.filter(
- lambda unit: unit._proto.unit_type in unit_alias_types or unit._type_data._proto.unit_alias in
- unit_alias_types
+ lambda unit: unit._proto.unit_type in unit_alias_types
+ or unit._type_data._proto.unit_alias in unit_alias_types
)
- @cached_property
+ @property
def center(self) -> Point2:
- """ Returns the central position of all units. """
+ """Returns the central position of all units."""
assert self, "Units object is empty"
- amount = self.amount
return Point2(
(
- sum(unit._proto.pos.x for unit in self) / amount,
- sum(unit._proto.pos.y for unit in self) / amount,
+ sum(unit._proto.pos.x for unit in self) / self.amount,
+ sum(unit._proto.pos.y for unit in self) / self.amount,
)
)
@property
def selected(self) -> Units:
- """ Returns all units that are selected by the human player. """
+ """Returns all units that are selected by the human player."""
return self.filter(lambda unit: unit.is_selected)
@property
- def tags(self) -> Set[int]:
- """ Returns all unit tags as a set. """
+ def tags(self) -> set[int]:
+ """Returns all unit tags as a set."""
return {unit.tag for unit in self}
@property
def ready(self) -> Units:
- """ Returns all structures that are ready (construction complete). """
+ """Returns all structures that are ready (construction complete)."""
return self.filter(lambda unit: unit.is_ready)
@property
def not_ready(self) -> Units:
- """ Returns all structures that are not ready (construction not complete). """
+ """Returns all structures that are not ready (construction not complete)."""
return self.filter(lambda unit: not unit.is_ready)
@property
def idle(self) -> Units:
- """ Returns all units or structures that are doing nothing (unit is standing still, structure is doing nothing). """
+ """Returns all units or structures that are doing nothing (unit is standing still, structure is doing nothing)."""
return self.filter(lambda unit: unit.is_idle)
@property
def owned(self) -> Units:
- """ Deprecated: All your units. """
+ """Deprecated: All your units."""
return self.filter(lambda unit: unit.is_mine)
@property
def enemy(self) -> Units:
- """ Deprecated: All enemy units."""
+ """Deprecated: All enemy units."""
return self.filter(lambda unit: unit.is_enemy)
@property
def flying(self) -> Units:
- """ Returns all units that are flying. """
+ """Returns all units that are flying."""
return self.filter(lambda unit: unit.is_flying)
@property
def not_flying(self) -> Units:
- """ Returns all units that not are flying. """
+ """Returns all units that not are flying."""
return self.filter(lambda unit: not unit.is_flying)
@property
def structure(self) -> Units:
- """ Deprecated: All structures. """
+ """Deprecated: All structures."""
return self.filter(lambda unit: unit.is_structure)
@property
def not_structure(self) -> Units:
- """ Deprecated: All units that are not structures. """
+ """Deprecated: All units that are not structures."""
return self.filter(lambda unit: not unit.is_structure)
@property
def gathering(self) -> Units:
- """ Returns all workers that are mining minerals or vespene (gather command). """
+ """Returns all workers that are mining minerals or vespene (gather command)."""
return self.filter(lambda unit: unit.is_gathering)
@property
def returning(self) -> Units:
- """ Returns all workers that are carrying minerals or vespene and are returning to a townhall. """
+ """Returns all workers that are carrying minerals or vespene and are returning to a townhall."""
return self.filter(lambda unit: unit.is_returning)
@property
def collecting(self) -> Units:
- """ Returns all workers that are mining or returning resources. """
+ """Returns all workers that are mining or returning resources."""
return self.filter(lambda unit: unit.is_collecting)
@property
@@ -732,15 +728,15 @@ def visible(self) -> Units:
@property
def mineral_field(self) -> Units:
- """ Returns all units that are mineral fields. """
+ """Returns all units that are mineral fields."""
return self.filter(lambda unit: unit.is_mineral_field)
@property
def vespene_geyser(self) -> Units:
- """ Returns all units that are vespene geysers. """
+ """Returns all units that are vespene geysers."""
return self.filter(lambda unit: unit.is_vespene_geyser)
@property
def prefer_idle(self) -> Units:
- """ Sorts units based on if they are idle. Idle units come first. """
+ """Sorts units based on if they are idle. Idle units come first."""
return self.sorted(lambda unit: unit.is_idle, reverse=True)
diff --git a/sc2/versions.py b/sc2/versions.py
index 06d9c606..96c2f35f 100644
--- a/sc2/versions.py
+++ b/sc2/versions.py
@@ -367,4 +367,212 @@
"replay-hash": "6A60E59031A7DB1B272EE87E51E4C7CD",
"version": 75800,
},
+ {
+ "base-version": 76052,
+ "data-hash": "D0F1A68AA88BA90369A84CD1439AA1C3",
+ "fixed-hash": "",
+ "label": "4.10.2",
+ "replay-hash": "",
+ "version": 76052,
+ },
+ {
+ "base-version": 76114,
+ "data-hash": "CDB276D311F707C29BA664B7754A7293",
+ "fixed-hash": "",
+ "label": "4.10.3",
+ "replay-hash": "",
+ "version": 76114,
+ },
+ {
+ "base-version": 76811,
+ "data-hash": "FF9FA4EACEC5F06DEB27BD297D73ED67",
+ "fixed-hash": "",
+ "label": "4.10.4",
+ "replay-hash": "",
+ "version": 76811,
+ },
+ {
+ "base-version": 77379,
+ "data-hash": "70E774E722A58287EF37D487605CD384",
+ "fixed-hash": "",
+ "label": "4.11.0",
+ "replay-hash": "",
+ "version": 77379,
+ },
+ {
+ "base-version": 77379,
+ "data-hash": "F92D1127A291722120AC816F09B2E583",
+ "fixed-hash": "",
+ "label": "4.11.1",
+ "replay-hash": "",
+ "version": 77474,
+ },
+ {
+ "base-version": 77535,
+ "data-hash": "FC43E0897FCC93E4632AC57CBC5A2137",
+ "fixed-hash": "",
+ "label": "4.11.2",
+ "replay-hash": "",
+ "version": 77535,
+ },
+ {
+ "base-version": 77661,
+ "data-hash": "A15B8E4247434B020086354F39856C51",
+ "fixed-hash": "",
+ "label": "4.11.3",
+ "replay-hash": "",
+ "version": 77661,
+ },
+ {
+ "base-version": 78285,
+ "data-hash": "69493AFAB5C7B45DDB2F3442FD60F0CF",
+ "fixed-hash": "21D2EBD5C79DECB3642214BAD4A7EF56",
+ "label": "4.11.4",
+ "replay-hash": "CAB5C056EDBDA415C552074BF363CC85",
+ "version": 78285,
+ },
+ {
+ "base-version": 79998,
+ "data-hash": "B47567DEE5DC23373BFF57194538DFD3",
+ "fixed-hash": "0A698A1B072BC4B087F44DDEF0BE361E",
+ "label": "4.12.0",
+ "replay-hash": "9E15AA09E15FE3AF3655126CEEC7FF42",
+ "version": 79998,
+ },
+ {
+ "base-version": 80188,
+ "data-hash": "44DED5AED024D23177C742FC227C615A",
+ "fixed-hash": "0A698A1B072BC4B087F44DDEF0BE361E",
+ "label": "4.12.1",
+ "replay-hash": "9E15AA09E15FE3AF3655126CEEC7FF42",
+ "version": 80188,
+ },
+ {
+ "base-version": 80949,
+ "data-hash": "9AE39C332883B8BF6AA190286183ED72",
+ "fixed-hash": "DACEAFAB8B983C08ACD31ABC085A0052",
+ "label": "5.0.0",
+ "replay-hash": "28C41277C5837AABF9838B64ACC6BDCF",
+ "version": 80949,
+ },
+ {
+ "base-version": 81009,
+ "data-hash": "0D28678BC32E7F67A238F19CD3E0A2CE",
+ "fixed-hash": "DACEAFAB8B983C08ACD31ABC085A0052",
+ "label": "5.0.1",
+ "replay-hash": "28C41277C5837AABF9838B64ACC6BDCF",
+ "version": 81009,
+ },
+ {
+ "base-version": 81102,
+ "data-hash": "DC0A1182FB4ABBE8E29E3EC13CF46F68",
+ "fixed-hash": "0C193BD5F63BBAB79D798278F8B2548E",
+ "label": "5.0.2",
+ "replay-hash": "08BB9D4CAE25B57160A6E4AD7B8E1A5A",
+ "version": 81102,
+ },
+ {
+ "base-version": 81433,
+ "data-hash": "5FD8D4B6B52723B44862DF29F232CF31",
+ "fixed-hash": "4FC35CEA63509AB06AA80AACC1B3B700",
+ "label": "5.0.3",
+ "replay-hash": "0920F1BD722655B41DA096B98CC0912D",
+ "version": 81433,
+ },
+ {
+ "base-version": 82457,
+ "data-hash": "D2707E265785612D12B381AF6ED9DBF4",
+ "fixed-hash": "ED05F0DB335D003FBC3C7DEF69911114",
+ "label": "5.0.4",
+ "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF",
+ "version": 82457,
+ },
+ {
+ "base-version": 82893,
+ "data-hash": "D795328C01B8A711947CC62AA9750445",
+ "fixed-hash": "ED05F0DB335D003FBC3C7DEF69911114",
+ "label": "5.0.5",
+ "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF",
+ "version": 82893,
+ },
+ {
+ "base-version": 83830,
+ "data-hash": "B4745D6A4F982A3143C183D8ACB6C3E3",
+ "fixed-hash": "ed05f0db335d003fbc3c7def69911114",
+ "label": "5.0.6",
+ "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF",
+ "version": 83830,
+ },
+ {
+ "base-version": 84643,
+ "data-hash": "A389D1F7DF9DD792FBE980533B7119FF",
+ "fixed-hash": "368DE29820A74F5BE747543AC02DB3F8",
+ "label": "5.0.7",
+ "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF",
+ "version": 84643,
+ },
+ {
+ "base-version": 86383,
+ "data-hash": "22EAC562CD0C6A31FB2C2C21E3AA3680",
+ "fixed-hash": "B19F4D8B87A2835F9447CA17EDD40C1E",
+ "label": "5.0.8",
+ "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF",
+ "version": 86383,
+ },
+ {
+ "base-version": 87702,
+ "data-hash": "F799E093428D419FD634CCE9B925218C",
+ "fixed-hash": "B19F4D8B87A2835F9447CA17EDD40C1E",
+ "label": "5.0.9",
+ "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF",
+ "version": 87702,
+ },
+ {
+ "base-version": 88500,
+ "data-hash": "F38043A301B034A78AD13F558257DCF8",
+ "fixed-hash": "F3853B6E3B6013415CAC30EF3B27564B",
+ "label": "5.0.10",
+ "replay-hash": "A79CD3B6C6DADB0ECAEFA06E6D18E47B",
+ "version": 88500,
+ },
+ {
+ "base-version": 89720,
+ "data-hash": "D371D4D7D1E6C131B24A09FC0E758547",
+ "fixed-hash": "F3853B6E3B6013415CAC30EF3B27564B",
+ "label": "5.0.11",
+ "replay-hash": "A79CD3B6C6DADB0ECAEFA06E6D18E47B",
+ "version": 89720,
+ },
+ {
+ "base-version": 91115,
+ "data-hash": "7857A76754FEB47C823D18993C476BF0",
+ "fixed-hash": "99E19D19DA59112C1744A83CB49614A5",
+ "label": "5.0.12",
+ "replay-hash": "BE64E420B329BD2A7D10EEBC0039D6E5",
+ "version": 89720,
+ },
+ {
+ "base-version": 92028,
+ "data-hash": "2B7746A6706F919775EF1BADFC95EA1C",
+ "fixed-hash": "163B1CDF46F09B621F6312CD6901228E",
+ "label": "5.0.13",
+ "replay-hash": "BE64E420B329BD2A7D10EEBC0039D6E5",
+ "version": 92028,
+ },
+ {
+ "base-version": 93333,
+ "data-hash": "446907060311fb1cc29eb31e547bb9fd",
+ "fixed-hash": "BE86048D1DCE8650E1655D2FE2B665A8",
+ "label": "5.0.14.93333",
+ "replay-hash": "BE64E420B329BD2A7D10EEBC0039D6E5",
+ "version": 93333,
+ },
+ {
+ "base-version": 94137,
+ "data-hash": "519EE8D06E384469C652DD58FC6016AC",
+ "fixed-hash": "B100C340B3D0797CBE914AE091A68653",
+ "label": "5.0.14.94137",
+ "replay-hash": "BE64E420B329BD2A7D10EEBC0039D6E5",
+ "version": 94137,
+ },
]
diff --git a/sc2/wsl.py b/sc2/wsl.py
index af3d614e..4f2a3cdd 100644
--- a/sc2/wsl.py
+++ b/sc2/wsl.py
@@ -1,4 +1,5 @@
-# pylint: disable=R0911,W1510
+from __future__ import annotations
+
import os
import re
import subprocess
@@ -10,12 +11,12 @@
## accessed directly by any bot clients
-def win_path_to_wsl_path(path):
+def win_path_to_wsl_path(path) -> Path:
"""Convert a path like C:\\foo to /mnt/c/foo"""
return Path("/mnt") / PureWindowsPath(re.sub("^([A-Z]):", lambda m: m.group(1).lower(), path))
-def wsl_path_to_win_path(path):
+def wsl_path_to_win_path(path) -> PureWindowsPath:
"""Convert a path like /mnt/c/foo to C:\\foo"""
return PureWindowsPath(re.sub("^/mnt/([a-z])", lambda m: m.group(1).upper() + ":", path))
@@ -39,7 +40,7 @@ def get_wsl_home():
}"""
-def run(popen_args, sc2_cwd):
+def run(popen_args, sc2_cwd) -> subprocess.Popen[str]:
"""Run SC2 in Windows and get the pid so that it can be killed later."""
path = wsl_path_to_win_path(popen_args[0])
args = " ".join(popen_args[1:])
@@ -53,7 +54,7 @@ def run(popen_args, sc2_cwd):
)
-def kill(wsl_process):
+def kill(wsl_process) -> bool:
"""Needed to kill a process started with WSL. Returns true if killed successfully."""
# HACK: subprocess and WSL1 appear to have a nasty interaction where
# any streams are never closed and the process is never considered killed,
@@ -69,7 +70,7 @@ def kill(wsl_process):
return proc.returncode == 0 # Returns 128 on failure
-def detect():
+def detect() -> str | None:
"""Detect the current running version of WSL, and bail out if it doesn't exist"""
# Allow disabling WSL detection with an environment variable
if os.getenv("SC2_WSL_DETECT", "1") == "0":
diff --git a/test/Dockerfile b/test/Dockerfile
index ade10412..f1c8e42d 100644
--- a/test/Dockerfile
+++ b/test/Dockerfile
@@ -1,12 +1,12 @@
# Buildable via command from root folder
-# docker build -t test_image -f test/Dockerfile --build-arg PYTHON_VERSION=3.8 --build-arg SC2_VERSION=4.10 .
+# docker build -t test_image -f test/Dockerfile --build-arg PYTHON_VERSION=3.10 --build-arg SC2_VERSION=4.10 .
# For more info see https://github.com/BurnySc2/python-sc2-docker
-ARG PYTHON_VERSION=3.8
+ARG PYTHON_VERSION=3.10
ARG SC2_VERSION=4.10
ARG VERSION_NUMBER=1.0.0
-FROM burnysc2/python-sc2-docker:py_$PYTHON_VERSION-sc2_$SC2_VERSION-v$VERSION_NUMBER
+FROM burnysc2/python-sc2-docker:py_$PYTHON_VERSION-sc2_$SC2_VERSION-v$VERSION_NUMBER-squashed
# Debugging purposes
RUN echo $PYTHON_VERSION
@@ -18,12 +18,12 @@ ADD . /root/python-sc2
# Install the python-sc2 library and its requirements (s2clientprotocol etc.) to python
WORKDIR /root/python-sc2
-RUN pip install --no-cache-dir poetry \
+RUN pip install --no-cache-dir uv \
# This will not include dev dependencies
- && poetry export -f requirements.txt --output requirements.txt --without-hashes \
+ && uv export --format requirements-txt --output-file requirements.txt --no-hashes \
&& pip install --no-cache-dir -r requirements.txt
# This will be executed during the container run instead:
-# docker run test_image -c "poetry run python examples/protoss/cannon_rush.py"
+# docker run test_image -c "uv run python examples/protoss/cannon_rush.py"
ENTRYPOINT [ "/bin/bash" ]
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/autotest_bot.py b/test/autotest_bot.py
index 7f176015..10abb82e 100644
--- a/test/autotest_bot.py
+++ b/test/autotest_bot.py
@@ -1,7 +1,4 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+from __future__ import annotations
from loguru import logger
@@ -19,7 +16,6 @@
class TestBot(BotAI):
-
def __init__(self):
BotAI.__init__(self)
# The time the bot has to complete all tests, here: the number of game seconds
@@ -28,7 +24,8 @@ def __init__(self):
# Check how many test action functions we have
# At least 4 tests because we test properties and variables
self.action_tests = [
- getattr(self, f"test_botai_actions{index}") for index in range(4000)
+ getattr(self, f"test_botai_actions{index}")
+ for index in range(4000)
if hasattr(getattr(self, f"test_botai_actions{index}", 0), "__call__")
]
self.tests_done_by_name = set()
@@ -39,8 +36,6 @@ def __init__(self):
# There will be 20 iterations of the bot doing nothing between tests
self.iteration_wait_time_between_actions = 20
- self.scv_action_list = ["move", "patrol", "attack", "hold", "scan_move"]
-
# Variables for test_botai_actions11
async def on_start(self):
@@ -70,13 +65,13 @@ async def on_step(self, iteration):
# Exit bot
if iteration > 100:
- logger.info("Tests completed after {} seconds".format(round(self.time, 1)))
+ logger.info(f"Tests completed after {round(self.time, 1)} seconds")
exit(0)
async def clean_up_center(self):
map_center = self.game_info.map_center
# Remove everything close to map center
- my_units = self.units | self.structures
+ my_units = self.all_own_units
if my_units:
my_units = my_units.closer_than(20, map_center)
if my_units:
@@ -145,8 +140,8 @@ async def test_game_info_static_variables(self):
assert len(self.game_info.player_races) == 2, self.game_info.player_races
self.tests_done_by_name.add("test_game_info_static_variables")
- # Test BotAI action: train SCV
async def test_botai_actions1(self):
+ # Test BotAI action: train SCV
while self.already_pending(UnitTypeId.SCV) < 1:
if self.can_afford(UnitTypeId.SCV):
self.townhalls.random.train(UnitTypeId.SCV)
@@ -154,7 +149,6 @@ async def test_botai_actions1(self):
await self._advance_steps(2)
logger.warning("Action test 01 successful.")
- return
# Test BotAI action: move all SCVs to center of map
async def test_botai_actions2(self):
@@ -162,16 +156,20 @@ async def test_botai_actions2(self):
def temp_filter(unit: Unit):
return (
- unit.is_moving or unit.is_patrolling or unit.orders and unit.orders[0] == AbilityId.HOLDPOSITION_HOLD
+ unit.is_moving
+ or unit.is_patrolling
+ or unit.orders
+ and unit.orders[0] == AbilityId.HOLDPOSITION_HOLD
or unit.is_attacking
)
- while self.units.filter(lambda unit: temp_filter(unit)).amount < len(self.scv_action_list):
+ scv_action_list = ["move", "patrol", "attack", "hold", "scan_move"]
+ while self.units.filter(lambda unit: temp_filter(unit)).amount < len(scv_action_list):
scv: Unit
for index, scv in enumerate(self.workers):
- if index > len(self.scv_action_list):
+ if index > len(scv_action_list):
scv.stop()
- action = self.scv_action_list[index % len(self.scv_action_list)]
+ action = scv_action_list[index % len(scv_action_list)]
if action == "move":
scv.move(center)
elif action == "patrol":
@@ -185,10 +183,9 @@ def temp_filter(unit: Unit):
await self._advance_steps(2)
logger.warning("Action test 02 successful.")
- return
- # Test BotAI action: move some scvs to the center, some to minerals
async def test_botai_actions3(self):
+ # Test BotAI action: move some scvs to the center, some to minerals
center = self.game_info.map_center
while self.units.filter(lambda x: x.is_moving).amount < 6 and self.units.gathering.amount >= 6:
@@ -204,10 +201,9 @@ async def test_botai_actions3(self):
await self._advance_steps(2)
await self._advance_steps(2)
logger.warning("Action test 03 successful.")
- return
- # Test BotAI action: move all SCVs to mine minerals near townhall
async def test_botai_actions4(self):
+ # Test BotAI action: move all SCVs to mine minerals near townhall
while self.units.gathering.amount < 12:
mf = self.mineral_field.closest_to(self.townhalls.random)
for scv in self.workers:
@@ -216,10 +212,9 @@ async def test_botai_actions4(self):
await self._advance_steps(2)
await self._advance_steps(2)
logger.warning("Action test 04 successful.")
- return
- # Test BotAI action: self.expand_now() which tests for get_next_expansion, select_build_worker, can_place, find_placement, build and can_afford
async def test_botai_actions5(self):
+ # Test BotAI action: self.expand_now() which tests for get_next_expansion, select_build_worker, can_place, find_placement, build and can_afford
# Wait till worker has started construction of CC
while 1:
if self.can_afford(UnitTypeId.COMMANDCENTER):
@@ -241,10 +236,9 @@ async def test_botai_actions5(self):
await self._advance_steps(2)
logger.warning("Action test 05 successful.")
- return
- # Test if reaper grenade shows up in effects
async def test_botai_actions6(self):
+ # Test if reaper grenade shows up in effects
center = self.game_info.map_center
while 1:
@@ -268,10 +262,9 @@ async def test_botai_actions6(self):
# Wait for effectts to time out
await self._advance_steps(100)
logger.warning("Action test 06 successful.")
- return
- # Test ravager effects
async def test_botai_actions7(self):
+ # Test ravager effects
center = self.game_info.map_center
while 1:
if self.units(UnitTypeId.RAVAGER).amount < 10:
@@ -293,10 +286,9 @@ async def test_botai_actions7(self):
# Wait for effectts to time out
await self._advance_steps(100)
logger.warning("Action test 07 successful.")
- return
- # Test if train function works on hatchery, lair, hive
async def test_botai_actions8(self):
+ # Test if train function works on hatchery, lair, hive
center = self.game_info.map_center
if not self.structures(UnitTypeId.HIVE):
await self.client.debug_create_unit([[UnitTypeId.HIVE, 1, center, 1]])
@@ -326,42 +318,50 @@ async def test_botai_actions8(self):
await self.client.debug_kill_unit(townhalls | queens | pool)
await self._advance_steps(2)
logger.warning("Action test 08 successful.")
- return
- # Morph an archon from 2 high templars
async def test_botai_actions9(self):
+ # Morph an archon from 2 high templars
center = self.game_info.map_center
- target_amount = 2
- HTs = self.units(UnitTypeId.HIGHTEMPLAR)
- archons = self.units(UnitTypeId.ARCHON)
+ await self.client.debug_create_unit(
+ [
+ [UnitTypeId.HIGHTEMPLAR, 1, center, 1],
+ [UnitTypeId.DARKTEMPLAR, 1, center + Point2((5, 0)), 1],
+ ]
+ )
+ await self._advance_steps(4)
+ assert self.already_pending(UnitTypeId.ARCHON) == 0
while 1:
- HTs = self.units(UnitTypeId.HIGHTEMPLAR)
- if HTs.amount < target_amount:
- await self.client.debug_create_unit([[UnitTypeId.HIGHTEMPLAR, target_amount - HTs.amount, center, 1]])
+ for templar in self.units.of_type({UnitTypeId.HIGHTEMPLAR, UnitTypeId.DARKTEMPLAR}):
+ templar(AbilityId.MORPH_ARCHON)
+
+ await self._advance_steps(4)
+ templars = self.units.of_type({UnitTypeId.HIGHTEMPLAR, UnitTypeId.DARKTEMPLAR})
+ archons = self.units(UnitTypeId.ARCHON)
+ if templars.amount > 0:
+ # High templars are on their way to morph ot morph has started
+ assert self.already_pending(UnitTypeId.ARCHON) == 1
else:
- for ht in HTs:
- ht(AbilityId.MORPH_ARCHON)
+ # Morph started
+ assert self.already_pending(UnitTypeId.ARCHON) == archons.not_ready.amount
- await self._advance_steps(2)
# Check if condition is met
- HTs = self.units(UnitTypeId.HIGHTEMPLAR)
- archons = self.units(UnitTypeId.ARCHON)
- if archons.amount == 1:
+ if archons.ready.amount == 1:
+ assert templars.amount == 0
+ assert self.already_pending(UnitTypeId.ARCHON) == 0
break
# Cleanup
if archons:
await self.client.debug_kill_unit(archons)
- if HTs:
- await self.client.debug_kill_unit(HTs)
+ if templars:
+ await self.client.debug_kill_unit(templars)
await self._advance_steps(2)
logger.warning("Action test 09 successful.")
- return
- # Morph 400 banelings from 400 lings in the same frame
async def test_botai_actions10(self):
+ # Morph 400 banelings from 400 lings in the same frame
center = self.game_info.map_center
target_amount = 400
@@ -372,17 +372,15 @@ async def test_botai_actions10(self):
bane_cocoons = self.units(UnitTypeId.BANELINGCOCOON)
# Cheat money, need 10k/10k to morph 400 lings to 400 banes
- if not banes and not bane_cocoons:
- if self.minerals < 10_000:
- await self.client.debug_all_resources()
- elif self.vespene < 10_000:
- await self.client.debug_all_resources()
+ if not banes and not bane_cocoons and (self.minerals < 10_000 or self.vespene < 10_000):
+ await self.client.debug_all_resources()
# Spawn units
if not bane_nests:
await self.client.debug_create_unit([[UnitTypeId.BANELINGNEST, 1, center, 1]])
- if banes.amount + bane_cocoons.amount + lings.amount < target_amount:
- await self.client.debug_create_unit([[UnitTypeId.ZERGLING, target_amount - lings.amount, center, 1]])
+ current_amount = banes.amount + bane_cocoons.amount + lings.amount
+ if current_amount < target_amount:
+ await self.client.debug_create_unit([[UnitTypeId.ZERGLING, target_amount - current_amount, center, 1]])
if lings.amount >= target_amount and self.minerals >= 10_000 and self.vespene >= 10_000:
for ling in lings:
@@ -401,10 +399,9 @@ async def test_botai_actions10(self):
await self.client.debug_kill_unit(lings | banes | bane_nests | bane_cocoons)
await self._advance_steps(2)
logger.warning("Action test 10 successful.")
- return
- # Trigger anti armor missile of raven against enemy unit and check if buff was received
async def test_botai_actions11(self):
+ # Trigger anti armor missile of raven against enemy unit and check if buff was received
await self.clean_up_center()
await self.clean_up_center()
@@ -436,10 +433,8 @@ async def test_botai_actions11(self):
logger.warning("Action test 11 successful.")
await self.clean_up_center()
- # Test if structures_without_construction_SCVs works after killing the scv
async def test_botai_actions12(self):
- map_center: Point2 = self.game_info.map_center
-
+ # Test if structures_without_construction_SCVs works after killing the scv
# Wait till can afford depot
while not self.can_afford(UnitTypeId.SUPPLYDEPOT):
await self.client.debug_all_resources()
@@ -484,12 +479,12 @@ async def test_botai_actions12(self):
class EmptyBot(BotAI):
-
async def on_start(self):
if self.units:
await self.client.debug_kill_unit(self.units)
async def on_step(self, iteration: int):
+ # pyre-ignore[16]
map_center = self.game_info.map_center
enemies = self.enemy_units | self.enemy_structures
if enemies:
diff --git a/test/battery_overcharge_bot.py b/test/battery_overcharge_bot.py
index 266d8b82..d948244a 100644
--- a/test/battery_overcharge_bot.py
+++ b/test/battery_overcharge_bot.py
@@ -1,10 +1,6 @@
"""
This bot tests if battery overcharge crashes the bot.
"""
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
from loguru import logger
@@ -18,16 +14,13 @@
class BatteryOverchargeBot(BotAI):
-
async def on_start(self):
- """ Spawn requires structures. """
+ """Spawn requires structures."""
await self.client.debug_create_unit(
[
[UnitTypeId.PYLON, 1, self.start_location.towards(self.game_info.map_center, 5), 1],
- [UnitTypeId.SHIELDBATTERY, 1,
- self.start_location.towards(self.game_info.map_center, 5), 1],
- [UnitTypeId.CYBERNETICSCORE, 1,
- self.start_location.towards(self.game_info.map_center, 5), 1],
+ [UnitTypeId.SHIELDBATTERY, 1, self.start_location.towards(self.game_info.map_center, 5), 1],
+ [UnitTypeId.CYBERNETICSCORE, 1, self.start_location.towards(self.game_info.map_center, 5), 1],
]
)
@@ -42,15 +35,14 @@ async def on_step(self, iteration):
nexus(AbilityId.BATTERYOVERCHARGE_BATTERYOVERCHARGE, battery)
if iteration > 20:
- logger.warning(f"Success, bot did not crash. Exiting bot.")
+ logger.warning("Success, bot did not crash. Exiting bot.")
await self.client.leave()
def main():
run_game(
maps.get("AcropolisLE"),
- [Bot(Race.Protoss, BatteryOverchargeBot()),
- Computer(Race.Terran, Difficulty.Medium)],
+ [Bot(Race.Protoss, BatteryOverchargeBot()), Computer(Race.Terran, Difficulty.Medium)],
realtime=False,
disable_fog=True,
)
diff --git a/test/benchmark_array_creation.py b/test/benchmark_array_creation.py
index 5fa56d2e..b0e47757 100644
--- a/test/benchmark_array_creation.py
+++ b/test/benchmark_array_creation.py
@@ -1,10 +1,6 @@
"""
Testing what the fastest way is to create a 1D Array with 2 values
"""
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import random
@@ -144,4 +140,4 @@ def test_numpy_ones(benchmark):
# Run this file using
-# poetry run pytest test/test_benchmark_array_creation.py --benchmark-compare
+# uv run pytest test/test_benchmark_array_creation.py --benchmark-compare
diff --git a/test/benchmark_bot_ai_init.py b/test/benchmark_bot_ai_init.py
new file mode 100644
index 00000000..bbcc9129
--- /dev/null
+++ b/test/benchmark_bot_ai_init.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+from typing import Any
+
+from test.test_pickled_data import MAPS, build_bot_object_from_pickle_data, load_map_pickle_data
+
+
+def _test_run_bot_ai_init_on_all_maps(pickle_data: list[tuple[Any, Any, Any]]):
+ for data in pickle_data:
+ build_bot_object_from_pickle_data(*data)
+
+
+def test_bench_bot_ai_init(benchmark):
+ # Load pickle files outside of benchmark
+ map_pickle_data: list[tuple[Any, Any, Any]] = [load_map_pickle_data(path) for path in MAPS]
+ _result = benchmark(_test_run_bot_ai_init_on_all_maps, map_pickle_data)
+
+
+# Run this file using
+# uv run pytest test/benchmark_bot_ai_init.py --benchmark-compare --benchmark-min-rounds=5
diff --git a/test/benchmark_distance_two_points.py b/test/benchmark_distance_two_points.py
index dc23cb67..9527a107 100644
--- a/test/benchmark_distance_two_points.py
+++ b/test/benchmark_distance_two_points.py
@@ -1,12 +1,9 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+# pyre-ignore-all-errors[21]
+from __future__ import annotations
import math
import platform
import random
-from typing import Union
import numpy as np
from scipy.spatial import distance as scipydistance
@@ -15,15 +12,15 @@
from sc2.position import Point2
PYTHON_VERSION = platform.python_version_tuple()
-USING_PYTHON_3_8: bool = ("3", "8") <= PYTHON_VERSION
+USING_PYTHON_3_8: bool = PYTHON_VERSION >= ("3", "8")
def distance_to_python_raw(s, p):
- return ((s[0] - p[0])**2 + (s[1] - p[1])**2)**0.5
+ return ((s[0] - p[0]) ** 2 + (s[1] - p[1]) ** 2) ** 0.5
def distance_to_squared_python_raw(s, p):
- return (s[0] - p[0])**2 + (s[1] - p[1])**2
+ return (s[0] - p[0]) ** 2 + (s[1] - p[1]) ** 2
if USING_PYTHON_3_8:
@@ -36,35 +33,35 @@ def distance_to_math_hypot(s, p):
return math.hypot((s[0] - p[0]), (s[1] - p[1]))
-def distance_scipy_euclidean(p1, p2) -> Union[int, float]:
- """ Distance calculation using scipy """
+def distance_scipy_euclidean(p1, p2) -> int | float:
+ """Distance calculation using scipy"""
dist = scipydistance.euclidean(p1, p2)
# dist = distance.cdist(p1.T, p2.T, "euclidean")
return dist
def distance_numpy_linalg_norm(p1, p2):
- """ Distance calculation using numpy """
+ """Distance calculation using numpy"""
return np.linalg.norm(p1 - p2)
-def distance_sum_squared_sqrt(p1, p2) -> Union[int, float]:
- """ Distance calculation using numpy """
- return np.sqrt(np.sum((p1 - p2)**2))
+def distance_sum_squared_sqrt(p1, p2) -> int | float:
+ """Distance calculation using numpy"""
+ return np.sqrt(np.sum((p1 - p2) ** 2))
-def distance_sum_squared(p1, p2) -> Union[int, float]:
- """ Distance calculation using numpy """
- return np.sum((p1 - p2)**2, axis=0)
+def distance_sum_squared(p1, p2) -> int | float:
+ """Distance calculation using numpy"""
+ return np.sum((p1 - p2) ** 2, axis=0)
# @njit
-# def distance_python_raw_njit(p1: Point2, p2: Point2) -> Union[int, float]:
+# def distance_python_raw_njit(p1: Point2, p2: Point2) -> int | float:
# """ The built in Point2 distance function rewritten differently with njit, same structure as distance02 """
# return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5
# @njit
-# def distance_python_raw_square_njit(p1: Point2, p2: Point2) -> Union[int, float]:
+# def distance_python_raw_square_njit(p1: Point2, p2: Point2) -> int | float:
# """ The built in Point2 distance function rewritten differently with njit, same structure as distance02 """
# return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
@@ -74,12 +71,12 @@ def distance_sum_squared(p1, p2) -> Union[int, float]:
# return np.linalg.norm(p1 - p2)
# @njit("float64(float64[:], float64[:])")
-# def distance_numpy_square_sum_sqrt_njit(p1, p2) -> Union[int, float]:
+# def distance_numpy_square_sum_sqrt_njit(p1, p2) -> int | float:
# """ Distance calculation using numpy + numba, same structure as distance13 """
# return np.sqrt(np.sum((p1 - p2) ** 2))
# @njit("float64(float64[:], float64[:])")
-# def distance_numpy_square_sum_njit(p1, p2) -> Union[int, float]:
+# def distance_numpy_square_sum_njit(p1, p2) -> int | float:
# """ Distance calculation using numpy + numba, same structure as distance13 """
# return np.sum((p1 - p2) ** 2, axis=0)
@@ -95,9 +92,7 @@ def distance_sum_squared(p1, p2) -> Union[int, float]:
def check_result(result1, result2, accuracy=1e-5):
- if abs(result1 - result2) <= accuracy:
- return True
- return False
+ return abs(result1 - result2) <= accuracy
if USING_PYTHON_3_8:
@@ -166,4 +161,4 @@ def test_distance_sum_squared(benchmark):
# assert check_result(result, correct_result ** 2)
# Run this file using
-# poetry run pytest test/test_benchmark_distance_two_points.py --benchmark-compare
+# uv run pytest test/test_benchmark_distance_two_points.py --benchmark-compare
diff --git a/test/benchmark_distances_cdist.py b/test/benchmark_distances_cdist.py
index 0f6b36ac..fdcfd7b8 100644
--- a/test/benchmark_distances_cdist.py
+++ b/test/benchmark_distances_cdist.py
@@ -1,8 +1,4 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
+# pyre-ignore-all-errors[21]
import random
import numpy as np
@@ -54,11 +50,6 @@ def distance_matrix_scipy_cdist_jensenshannon(ps):
return cdist(ps, ps, "jensenshannon")
-def distance_matrix_scipy_cdist_kulsinski(ps):
- # Calculate distances between each of the points
- return cdist(ps, ps, "kulsinski")
-
-
def distance_matrix_scipy_cdist_mahalanobis(ps):
# Calculate distances between each of the points
return cdist(ps, ps, "mahalanobis")
@@ -134,8 +125,7 @@ def distance_matrix_scipy_pdist_squared(ps):
min_value = 0
max_value = 300
points = np.array(
- [np.array([random.uniform(min_value, max_value),
- random.uniform(min_value, max_value)]) for _ in range(amount)]
+ [np.array([random.uniform(min_value, max_value), random.uniform(min_value, max_value)]) for _ in range(amount)]
)
@@ -184,11 +174,6 @@ def test_distance_matrix_scipy_cdist_jensenshannon(benchmark):
# assert check_result(result, correct_result)
-def test_distance_matrix_scipy_cdist_kulsinski(benchmark):
- result = benchmark(distance_matrix_scipy_cdist_kulsinski, points)
- # assert check_result(result, correct_result)
-
-
def test_distance_matrix_scipy_cdist_mahalanobis(benchmark):
result = benchmark(distance_matrix_scipy_cdist_mahalanobis, points)
# assert check_result(result, correct_result)
@@ -260,4 +245,4 @@ def test_distance_matrix_scipy_pdist_squared(benchmark):
# Run this file using
-# poetry run pytest test/test_benchmark_distances_cdist.py --benchmark-compare
+# uv run pytest test/test_benchmark_distances_cdist.py --benchmark-compare
diff --git a/test/benchmark_distances_points_to_point.py b/test/benchmark_distances_points_to_point.py
index 973559dc..cd36c8d8 100644
--- a/test/benchmark_distances_points_to_point.py
+++ b/test/benchmark_distances_points_to_point.py
@@ -1,11 +1,8 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+# pyre-ignore-all-errors[21]
+from __future__ import annotations
import math
import random
-from typing import List, Tuple
import numpy as np
from scipy.spatial.distance import cdist
@@ -20,28 +17,28 @@ def distance_matrix_scipy_cdist_squared(ps, p1):
def distance_numpy_basic_1(ps, p1):
- """ Distance calculation using numpy """
+ """Distance calculation using numpy"""
flat_units = (item for sublist in ps for item in sublist)
units_np = np.fromiter(flat_units, dtype=float, count=2 * len(ps)).reshape((-1, 2))
point_np = np.fromiter(p1, dtype=float, count=2).reshape((-1, 2))
# Subtract and then square the values
- nppoints = (units_np - point_np)**2
+ nppoints = (units_np - point_np) ** 2
# Calc the sum of each vector
nppoints = nppoints.sum(axis=1)
return nppoints
def distance_numpy_basic_2(ps, p1):
- """ Distance calculation using numpy """
+ """Distance calculation using numpy"""
flat_units = (item for sublist in ps for item in sublist)
units_np = np.fromiter(flat_units, dtype=float, count=2 * len(ps)).reshape((-1, 2))
point_np = np.fromiter(p1, dtype=float, count=2).reshape((-1, 2))
- dist_2 = np.sum((units_np - point_np)**2, axis=1)
+ dist_2 = np.sum((units_np - point_np) ** 2, axis=1)
return dist_2
def distance_numpy_einsum(ps, p1):
- """ Distance calculation using numpy einstein sum """
+ """Distance calculation using numpy einstein sum"""
flat_units = (item for sublist in ps for item in sublist)
units_np = np.fromiter(flat_units, dtype=float, count=2 * len(ps)).reshape((-1, 2))
point_np = np.fromiter(p1, dtype=float, count=2).reshape((-1, 2))
@@ -51,7 +48,7 @@ def distance_numpy_einsum(ps, p1):
def distance_numpy_einsum_pre_converted(ps, p1):
- """ Distance calculation using numpy einstein sum """
+ """Distance calculation using numpy einstein sum"""
deltas = ps - p1
dist_2 = np.einsum("ij,ij->i", deltas, deltas)
return dist_2
@@ -88,18 +85,18 @@ def distance_numpy_einsum_pre_converted(ps, p1):
def distance_pure_python(ps, p1):
- """ Distance calculation using numpy with jit(nopython=True) """
+ """Distance calculation using numpy with jit(nopython=True)"""
distances = []
x1 = p1[0]
y1 = p1[1]
for x0, y0 in ps:
- distance_squared = (x0 - x1)**2 + (y0 - y1)**2
+ distance_squared = (x0 - x1) ** 2 + (y0 - y1) ** 2
distances.append(distance_squared)
return distances
def distance_math_hypot(ps, p1):
- """ Distance calculation using math.hypot """
+ """Distance calculation using math.hypot"""
distances = []
x1 = p1[0]
y1 = p1[1]
@@ -115,8 +112,8 @@ def distance_math_hypot(ps, p1):
min_value = 0
max_value = 250
-point: Tuple[float, float] = (random.uniform(min_value, max_value), random.uniform(min_value, max_value))
-units: List[Tuple[float, float]] = [
+point: tuple[float, float] = (random.uniform(min_value, max_value), random.uniform(min_value, max_value))
+units: list[tuple[float, float]] = [
(random.uniform(min_value, max_value), random.uniform(min_value, max_value)) for _ in range(amount)
]
@@ -174,4 +171,4 @@ def test_distance_math_hypot(benchmark):
# Run this file using
-# poetry run pytest test/test_benchmark_distances_points_to_point.py --benchmark-compare
+# uv run pytest test/test_benchmark_distances_points_to_point.py --benchmark-compare
diff --git a/test/benchmark_distances_units.py b/test/benchmark_distances_units.py
index 04bceeff..11d81462 100644
--- a/test/benchmark_distances_units.py
+++ b/test/benchmark_distances_units.py
@@ -1,8 +1,4 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
+# pyre-ignore-all-errors[21]
import math
import random
@@ -35,8 +31,7 @@ def distance_matrix_scipy_pdist_squared(ps):
min_value = 0
max_value = 250
points = np.array(
- [np.array([random.uniform(min_value, max_value),
- random.uniform(min_value, max_value)]) for _ in range(amount)]
+ [np.array([random.uniform(min_value, max_value), random.uniform(min_value, max_value)]) for _ in range(amount)]
)
m1 = distance_matrix_scipy_cdist(points)
@@ -46,7 +41,7 @@ def distance_matrix_scipy_pdist_squared(ps):
def calc_row_idx(k, n):
- return int(math.ceil((1 / 2.0) * (-((-8 * k + 4 * n**2 - 4 * n - 7)**0.5) + 2 * n - 1) - 1))
+ return int(math.ceil((1 / 2.0) * (-((-8 * k + 4 * n**2 - 4 * n - 7) ** 0.5) + 2 * n - 1) - 1))
def elem_in_i_rows(i, n):
@@ -122,4 +117,4 @@ def test_distance_matrix_scipy_pdist_squared(benchmark):
# Run this file using
-# poetry run pytest test/test_benchmark_distances_units.py --benchmark-compare
+# uv run pytest test/test_benchmark_distances_units.py --benchmark-compare
diff --git a/test/benchmark_prepare_units.py b/test/benchmark_prepare_units.py
new file mode 100644
index 00000000..51aeb7d7
--- /dev/null
+++ b/test/benchmark_prepare_units.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from test.test_pickled_data import MAPS, get_map_specific_bot
+
+if TYPE_CHECKING:
+ from sc2.bot_ai import BotAI
+
+
+def _run_prepare_units(bot_objects: list[BotAI]):
+ for bot_object in bot_objects:
+ bot_object._prepare_units()
+
+
+def test_bench_prepare_units(benchmark):
+ bot_objects = [get_map_specific_bot(map_) for map_ in MAPS]
+ _result = benchmark(_run_prepare_units, bot_objects)
+
+
+# Run this file using
+# uv run pytest test/benchmark_prepare_units.py --benchmark-compare
diff --git a/test/conftest.py b/test/conftest.py
deleted file mode 100644
index 7725d541..00000000
--- a/test/conftest.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import pytest
-
-
-def pytest_addoption(parser):
- parser.addoption("--runslow", action="store_true", default=False, help="run slow tests")
-
-
-def pytest_collection_modifyitems(config, items):
- if config.getoption("--runslow"):
- # --runslow given in cli: do not skip slow tests
- return
- skip_slow = pytest.mark.skip(reason="need --runslow option to run")
- for item in items:
- if "slow" in item.keywords:
- item.add_marker(skip_slow)
diff --git a/test/damagetest_bot.py b/test/damagetest_bot.py
index 298a2ce9..962c2bd8 100644
--- a/test/damagetest_bot.py
+++ b/test/damagetest_bot.py
@@ -1,7 +1,5 @@
-import os
-import sys
+from __future__ import annotations
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import math
from loguru import logger
@@ -16,7 +14,6 @@
class TestBot(BotAI):
-
def __init__(self):
# The time the bot has to complete all tests, here: the number of game seconds
self.game_time_timeout_limit = 20 * 60 # 20 minutes ingame time
@@ -24,7 +21,8 @@ def __init__(self):
# Check how many test action functions we have
# At least 4 tests because we test properties and variables
self.action_tests = [
- getattr(self, f"test_botai_actions{index}") for index in range(4000)
+ getattr(self, f"test_botai_actions{index}")
+ for index in range(4000)
if hasattr(getattr(self, f"test_botai_actions{index}", 0), "__call__")
]
self.tests_target = 4
@@ -53,7 +51,7 @@ async def on_step(self, iteration):
# Exit bot
if iteration > 100:
- logger.info("Tests completed after {} seconds".format(round(self.time, 1)))
+ logger.info(f"Tests completed after {round(self.time, 1)} seconds")
exit(0)
async def clean_up_center(self):
@@ -186,7 +184,7 @@ def get_attacker_and_defender():
return attacker, defender
def do_some_unit_property_tests(attacker: Unit, defender: Unit):
- """ Some tests that are not covered by test_pickled_data.py """
+ """Some tests that are not covered by test_pickled_data.py"""
# TODO move unit unrelated tests elsewhere
self.step_time
self.units_created
@@ -238,21 +236,18 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit):
for upgrade_level in upgrade_levels:
if upgrade_level != 0:
await self.client.debug_upgrade()
- # await self._advance_steps(5)
for attacker_type in attacker_units:
for defender_type in defender_units:
# DT, Thor, Tempest one-shots workers, so skip test
- if (
- attacker_type in {
- UnitTypeId.DARKTEMPLAR,
- UnitTypeId.TEMPEST,
- UnitTypeId.THOR,
- UnitTypeId.THORAP,
- UnitTypeId.LIBERATORAG,
- UnitTypeId.PLANETARYFORTRESS,
- UnitTypeId.ARCHON,
- } and defender_type in {UnitTypeId.PROBE, UnitTypeId.DRONE, UnitTypeId.SCV, UnitTypeId.MULE}
- ):
+ if attacker_type in {
+ UnitTypeId.DARKTEMPLAR,
+ UnitTypeId.TEMPEST,
+ UnitTypeId.THOR,
+ UnitTypeId.THORAP,
+ UnitTypeId.LIBERATORAG,
+ UnitTypeId.PLANETARYFORTRESS,
+ UnitTypeId.ARCHON,
+ } and defender_type in {UnitTypeId.PROBE, UnitTypeId.DRONE, UnitTypeId.SCV, UnitTypeId.MULE}:
continue
# Spawn units
@@ -264,7 +259,9 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit):
# Wait for units to spawn
attacker, defender = get_attacker_and_defender()
while (
- attacker is None or defender is None or attacker.type_id != attacker_type
+ attacker is None
+ or defender is None
+ or attacker.type_id != attacker_type
or defender.type_id != defender_type
):
await self._advance_steps(1)
@@ -303,12 +300,12 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit):
# f"Attacker type: {attacker_type}, defender health: {defender.health} / {defender.health_max}, defender shield: {defender.shield} / {defender.shield_max}, expected damage: {expected_damage}, real damage so far: {real_damage}, attacker weapon cooldown: {attacker.weapon_cooldown}"
# )
max_steps -= 1
- assert (
- max_steps > 0
- ), f"Step limit reached. Test timed out for attacker {attacker_type} and defender {defender_type}"
- assert (
- expected_damage == real_damage
- ), f"Expected damage does not match real damage: Unit type {attacker_type} (attack upgrade: {attacker.attack_upgrade_level}) deals {real_damage} damage against {defender_type} (armor upgrade: {defender.armor_upgrade_level} and shield upgrade: {defender.shield_upgrade_level}) but calculated damage was {expected_damage}, attacker weapons: \n{attacker._weapons}"
+ assert max_steps > 0, (
+ f"Step limit reached. Test timed out for attacker {attacker_type} and defender {defender_type}"
+ )
+ assert expected_damage == real_damage, (
+ f"Expected damage does not match real damage: Unit type {attacker_type} (attack upgrade: {attacker.attack_upgrade_level}) deals {real_damage} damage against {defender_type} (armor upgrade: {defender.armor_upgrade_level} and shield upgrade: {defender.shield_upgrade_level}) but calculated damage was {expected_damage}, attacker weapons: \n{attacker._weapons}"
+ )
await self.clean_up_center()
@@ -319,12 +316,12 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit):
class EmptyBot(BotAI):
-
async def on_start(self):
if self.units:
await self.client.debug_kill_unit(self.units)
async def on_step(self, iteration: int):
+ # pyre-ignore[16]
map_center = self.game_info.map_center
enemies = self.enemy_units | self.enemy_structures
if enemies:
diff --git a/test/generate_pickle_files_bot.py b/test/generate_pickle_files_bot.py
index 4cd9d44a..2d420c7f 100644
--- a/test/generate_pickle_files_bot.py
+++ b/test/generate_pickle_files_bot.py
@@ -1,35 +1,31 @@
+# pyre-ignore-all-errors[16]
"""
This "bot" will loop over several available ladder maps and generate the pickle file in the "/test/pickle_data/" subfolder.
These will then be used to run tests from the test script "test_pickled_data.py"
"""
-import os
-import sys
-
-from sc2 import maps
-from sc2.bot_ai import BotAI
-from sc2.main import run_game
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import lzma
-import os
import pickle
-from typing import Set
+from pathlib import Path
from loguru import logger
+
+# pyre-ignore[21]
from s2clientprotocol import sc2api_pb2 as sc_pb
+from sc2 import maps
+from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.game_data import GameData
from sc2.game_info import GameInfo
from sc2.game_state import GameState
from sc2.ids.unit_typeid import UnitTypeId
+from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.protocol import ProtocolError
class ExporterBot(BotAI):
-
def __init__(self):
BotAI.__init__(self)
self.map_name: str = None
@@ -37,18 +33,18 @@ def __init__(self):
async def on_step(self, iteration):
pass
- def get_pickle_file_path(self) -> str:
- folder_path = os.path.dirname(__file__)
+ def get_pickle_file_path(self) -> Path:
+ folder_path = Path(__file__).parent
subfolder_name = "pickle_data"
file_name = f"{self.map_name}.xz"
- file_path = os.path.join(folder_path, subfolder_name, file_name)
+ file_path = folder_path / subfolder_name / file_name
return file_path
- def get_combat_file_path(self) -> str:
- folder_path = os.path.dirname(__file__)
+ def get_combat_file_path(self) -> Path:
+ folder_path = Path(__file__).parent
subfolder_name = "combat_data"
file_name = f"{self.map_name}.xz"
- file_path = os.path.join(folder_path, subfolder_name, file_name)
+ file_path = folder_path / subfolder_name / file_name
return file_path
async def store_data_to_file(self, file_path: str):
@@ -66,7 +62,7 @@ async def store_data_to_file(self, file_path: str):
_game_info = GameInfo(raw_game_info.game_info)
_game_state = GameState(raw_observation)
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
+ Path(file_path).parent.mkdir(exist_ok=True, parents=True)
with lzma.open(file_path, "wb") as f:
pickle.dump([raw_game_data, raw_game_info, raw_observation], f)
@@ -81,14 +77,15 @@ async def on_start(self):
await self.client.debug_god()
# Spawn one of each unit
- # await self.client.debug_create_unit([[unit_id, 1, self.game_info.map_center, 1] for unit_id in self.game_data.units])
- valid_units: Set[UnitTypeId] = {
+ valid_units: set[UnitTypeId] = {
UnitTypeId(unit_id)
for unit_id, data in self.game_data.units.items()
- if data._proto.race != Race.NoRace and data._proto.race != Race.Random and data._proto.available
+ if data._proto.race != Race.NoRace
+ and data._proto.race != Race.Random
+ and data._proto.available
# Dont cloak units
- and UnitTypeId(unit_id) != UnitTypeId.MOTHERSHIP and
- (data._proto.mineral_cost or data._proto.movement_speed or data._proto.weapons)
+ and UnitTypeId(unit_id) != UnitTypeId.MOTHERSHIP
+ and (data._proto.mineral_cost or data._proto.movement_speed or data._proto.weapons)
}
# Create units for self
@@ -104,11 +101,9 @@ async def on_start(self):
await self.store_data_to_file(file_path)
await self.client.leave()
- return
def main():
-
maps_ = [
"16-BitLE",
"2000AtmospheresAIE",
@@ -117,6 +112,7 @@ def main():
"AcidPlantLE",
"AcolyteLE",
"AcropolisLE",
+ "AncientCisternAIE",
"Artana",
"AscensiontoAiurLE",
"AutomatonLE",
@@ -140,6 +136,7 @@ def main():
"DefendersLandingLE",
"DigitalFrontier",
"DiscoBloodbathLE",
+ "DragonScalesAIE",
"DreamcatcherLE",
"EastwatchLE",
"Ephemeron",
@@ -151,12 +148,15 @@ def main():
"FractureLE",
"FrostLE",
"GlitteringAshesAIE",
+ "GoldenauraAIE",
"GoldenWall506",
"GoldenWallLE",
+ "GresvanAIE",
"HardwireAIE",
"HonorgroundsLE",
"IceandChrome506",
"IceandChromeLE",
+ "InfestationStationAIE",
"InsideAndOutAIE",
"InterloperLE",
"JagannathaAIE",
@@ -183,6 +183,7 @@ def main():
"RedshiftLE",
"Reminiscence",
"RomanticideAIE",
+ "RoyalBloodAIE",
"Sanglune",
"SequencerLE",
"SimulacrumLE",
@@ -200,6 +201,12 @@ def main():
"WorldofSleepersLE",
"YearZeroLE",
"ZenLE",
+ "Equilibrium513AIE",
+ "GoldenAura513AIE",
+ "HardLead513AIE",
+ "Oceanborn513AIE",
+ "SiteDelta513AIE",
+ "Gresvan513AIE",
]
for map_ in maps_:
@@ -207,7 +214,7 @@ def main():
bot = ExporterBot()
bot.map_name = map_
file_path = bot.get_pickle_file_path()
- if os.path.isfile(file_path):
+ if Path(file_path).is_file():
logger.warning(
f"Pickle file for map {map_} was already generated. Skipping. If you wish to re-generate files, please remove them first."
)
diff --git a/test/pickle_data/16-BitLE.xz b/test/pickle_data/16-BitLE.xz
index 887d496b..580ae967 100644
Binary files a/test/pickle_data/16-BitLE.xz and b/test/pickle_data/16-BitLE.xz differ
diff --git a/test/pickle_data/2000AtmospheresAIE.xz b/test/pickle_data/2000AtmospheresAIE.xz
index 7399f434..7d2306cc 100644
Binary files a/test/pickle_data/2000AtmospheresAIE.xz and b/test/pickle_data/2000AtmospheresAIE.xz differ
diff --git a/test/pickle_data/AbiogenesisLE.xz b/test/pickle_data/AbiogenesisLE.xz
index 73c00125..d817dceb 100644
Binary files a/test/pickle_data/AbiogenesisLE.xz and b/test/pickle_data/AbiogenesisLE.xz differ
diff --git a/test/pickle_data/AbyssalReefLE.xz b/test/pickle_data/AbyssalReefLE.xz
index 46bf0ee7..ca02d931 100644
Binary files a/test/pickle_data/AbyssalReefLE.xz and b/test/pickle_data/AbyssalReefLE.xz differ
diff --git a/test/pickle_data/AcidPlantLE.xz b/test/pickle_data/AcidPlantLE.xz
index 30083a70..6cd33ba6 100644
Binary files a/test/pickle_data/AcidPlantLE.xz and b/test/pickle_data/AcidPlantLE.xz differ
diff --git a/test/pickle_data/AcolyteLE.xz b/test/pickle_data/AcolyteLE.xz
index 229134e5..f325e65d 100644
Binary files a/test/pickle_data/AcolyteLE.xz and b/test/pickle_data/AcolyteLE.xz differ
diff --git a/test/pickle_data/AcropolisLE.xz b/test/pickle_data/AcropolisLE.xz
index 3604f496..30a76532 100644
Binary files a/test/pickle_data/AcropolisLE.xz and b/test/pickle_data/AcropolisLE.xz differ
diff --git a/test/pickle_data/AncientCisternAIE.xz b/test/pickle_data/AncientCisternAIE.xz
new file mode 100644
index 00000000..cd69a61b
Binary files /dev/null and b/test/pickle_data/AncientCisternAIE.xz differ
diff --git a/test/pickle_data/Artana.xz b/test/pickle_data/Artana.xz
index 32d93bab..6641e30c 100644
Binary files a/test/pickle_data/Artana.xz and b/test/pickle_data/Artana.xz differ
diff --git a/test/pickle_data/AscensiontoAiurLE.xz b/test/pickle_data/AscensiontoAiurLE.xz
index 4466b1d4..b2b39fca 100644
Binary files a/test/pickle_data/AscensiontoAiurLE.xz and b/test/pickle_data/AscensiontoAiurLE.xz differ
diff --git a/test/pickle_data/AutomatonLE.xz b/test/pickle_data/AutomatonLE.xz
index bd623c64..a1528b71 100644
Binary files a/test/pickle_data/AutomatonLE.xz and b/test/pickle_data/AutomatonLE.xz differ
diff --git a/test/pickle_data/BackwaterLE.xz b/test/pickle_data/BackwaterLE.xz
index ca99562c..c483ede9 100644
Binary files a/test/pickle_data/BackwaterLE.xz and b/test/pickle_data/BackwaterLE.xz differ
diff --git a/test/pickle_data/Bandwidth.xz b/test/pickle_data/Bandwidth.xz
index c2ec2052..0f954bd2 100644
Binary files a/test/pickle_data/Bandwidth.xz and b/test/pickle_data/Bandwidth.xz differ
diff --git a/test/pickle_data/BattleontheBoardwalkLE.xz b/test/pickle_data/BattleontheBoardwalkLE.xz
index cbd32909..42ff627c 100644
Binary files a/test/pickle_data/BattleontheBoardwalkLE.xz and b/test/pickle_data/BattleontheBoardwalkLE.xz differ
diff --git a/test/pickle_data/BelShirVestigeLE.xz b/test/pickle_data/BelShirVestigeLE.xz
index 83542b4f..8b3a137c 100644
Binary files a/test/pickle_data/BelShirVestigeLE.xz and b/test/pickle_data/BelShirVestigeLE.xz differ
diff --git a/test/pickle_data/BerlingradAIE.xz b/test/pickle_data/BerlingradAIE.xz
index db94ef37..53f42caf 100644
Binary files a/test/pickle_data/BerlingradAIE.xz and b/test/pickle_data/BerlingradAIE.xz differ
diff --git a/test/pickle_data/BlackburnAIE.xz b/test/pickle_data/BlackburnAIE.xz
index 7b7cc0c2..ab881a32 100644
Binary files a/test/pickle_data/BlackburnAIE.xz and b/test/pickle_data/BlackburnAIE.xz differ
diff --git a/test/pickle_data/BlackpinkLE.xz b/test/pickle_data/BlackpinkLE.xz
index b5b20656..a08c185d 100644
Binary files a/test/pickle_data/BlackpinkLE.xz and b/test/pickle_data/BlackpinkLE.xz differ
diff --git a/test/pickle_data/BlueshiftLE.xz b/test/pickle_data/BlueshiftLE.xz
index afe9c3af..16dc0d4f 100644
Binary files a/test/pickle_data/BlueshiftLE.xz and b/test/pickle_data/BlueshiftLE.xz differ
diff --git a/test/pickle_data/CactusValleyLE.xz b/test/pickle_data/CactusValleyLE.xz
index c1611b89..0a8034c0 100644
Binary files a/test/pickle_data/CactusValleyLE.xz and b/test/pickle_data/CactusValleyLE.xz differ
diff --git a/test/pickle_data/CatalystLE.xz b/test/pickle_data/CatalystLE.xz
index be94d1ad..de8a2201 100644
Binary files a/test/pickle_data/CatalystLE.xz and b/test/pickle_data/CatalystLE.xz differ
diff --git a/test/pickle_data/CeruleanFallLE.xz b/test/pickle_data/CeruleanFallLE.xz
index 40367a70..aae102e8 100644
Binary files a/test/pickle_data/CeruleanFallLE.xz and b/test/pickle_data/CeruleanFallLE.xz differ
diff --git a/test/pickle_data/CrystalCavern.xz b/test/pickle_data/CrystalCavern.xz
index 53794fe7..eed235bc 100644
Binary files a/test/pickle_data/CrystalCavern.xz and b/test/pickle_data/CrystalCavern.xz differ
diff --git a/test/pickle_data/CuriousMindsAIE.xz b/test/pickle_data/CuriousMindsAIE.xz
index 32f7f618..485f7999 100644
Binary files a/test/pickle_data/CuriousMindsAIE.xz and b/test/pickle_data/CuriousMindsAIE.xz differ
diff --git a/test/pickle_data/CyberForestLE.xz b/test/pickle_data/CyberForestLE.xz
index 0882b284..96a1be2f 100644
Binary files a/test/pickle_data/CyberForestLE.xz and b/test/pickle_data/CyberForestLE.xz differ
diff --git a/test/pickle_data/DarknessSanctuaryLE.xz b/test/pickle_data/DarknessSanctuaryLE.xz
index 6372f110..447fd318 100644
Binary files a/test/pickle_data/DarknessSanctuaryLE.xz and b/test/pickle_data/DarknessSanctuaryLE.xz differ
diff --git a/test/pickle_data/DeathAura506.xz b/test/pickle_data/DeathAura506.xz
index ffee4817..51443faa 100644
Binary files a/test/pickle_data/DeathAura506.xz and b/test/pickle_data/DeathAura506.xz differ
diff --git a/test/pickle_data/DeathAuraLE.xz b/test/pickle_data/DeathAuraLE.xz
index 04e3d71b..ad14c5ef 100644
Binary files a/test/pickle_data/DeathAuraLE.xz and b/test/pickle_data/DeathAuraLE.xz differ
diff --git a/test/pickle_data/DefendersLandingLE.xz b/test/pickle_data/DefendersLandingLE.xz
index f09a40a9..66e32d66 100644
Binary files a/test/pickle_data/DefendersLandingLE.xz and b/test/pickle_data/DefendersLandingLE.xz differ
diff --git a/test/pickle_data/DigitalFrontier.xz b/test/pickle_data/DigitalFrontier.xz
index 660c6d68..725df672 100644
Binary files a/test/pickle_data/DigitalFrontier.xz and b/test/pickle_data/DigitalFrontier.xz differ
diff --git a/test/pickle_data/DiscoBloodbathLE.xz b/test/pickle_data/DiscoBloodbathLE.xz
index 137b4669..ba1ce870 100644
Binary files a/test/pickle_data/DiscoBloodbathLE.xz and b/test/pickle_data/DiscoBloodbathLE.xz differ
diff --git a/test/pickle_data/DragonScalesAIE.xz b/test/pickle_data/DragonScalesAIE.xz
new file mode 100644
index 00000000..0a17d5ef
Binary files /dev/null and b/test/pickle_data/DragonScalesAIE.xz differ
diff --git a/test/pickle_data/DreamcatcherLE.xz b/test/pickle_data/DreamcatcherLE.xz
index 55a91d41..d328e958 100644
Binary files a/test/pickle_data/DreamcatcherLE.xz and b/test/pickle_data/DreamcatcherLE.xz differ
diff --git a/test/pickle_data/EastwatchLE.xz b/test/pickle_data/EastwatchLE.xz
index e27c7bb8..09da7bf2 100644
Binary files a/test/pickle_data/EastwatchLE.xz and b/test/pickle_data/EastwatchLE.xz differ
diff --git a/test/pickle_data/Ephemeron.xz b/test/pickle_data/Ephemeron.xz
index 961a0fbe..47fce171 100644
Binary files a/test/pickle_data/Ephemeron.xz and b/test/pickle_data/Ephemeron.xz differ
diff --git a/test/pickle_data/EphemeronLE.xz b/test/pickle_data/EphemeronLE.xz
index a47fda61..2c74fd16 100644
Binary files a/test/pickle_data/EphemeronLE.xz and b/test/pickle_data/EphemeronLE.xz differ
diff --git a/test/pickle_data/Equilibrium513AIE.xz b/test/pickle_data/Equilibrium513AIE.xz
new file mode 100644
index 00000000..439e5017
Binary files /dev/null and b/test/pickle_data/Equilibrium513AIE.xz differ
diff --git a/test/pickle_data/EternalEmpire506.xz b/test/pickle_data/EternalEmpire506.xz
index 81d75bae..ab85e862 100644
Binary files a/test/pickle_data/EternalEmpire506.xz and b/test/pickle_data/EternalEmpire506.xz differ
diff --git a/test/pickle_data/EternalEmpireLE.xz b/test/pickle_data/EternalEmpireLE.xz
index 72bd3be5..48698dc6 100644
Binary files a/test/pickle_data/EternalEmpireLE.xz and b/test/pickle_data/EternalEmpireLE.xz differ
diff --git a/test/pickle_data/EverDream506.xz b/test/pickle_data/EverDream506.xz
index d144cb0c..9603b7ae 100644
Binary files a/test/pickle_data/EverDream506.xz and b/test/pickle_data/EverDream506.xz differ
diff --git a/test/pickle_data/EverDreamLE.xz b/test/pickle_data/EverDreamLE.xz
index 3ffc935e..0ac3b06a 100644
Binary files a/test/pickle_data/EverDreamLE.xz and b/test/pickle_data/EverDreamLE.xz differ
diff --git a/test/pickle_data/FractureLE.xz b/test/pickle_data/FractureLE.xz
index a6badd03..6324335f 100644
Binary files a/test/pickle_data/FractureLE.xz and b/test/pickle_data/FractureLE.xz differ
diff --git a/test/pickle_data/FrostLE.xz b/test/pickle_data/FrostLE.xz
index 2e1fe7c2..811df0a9 100644
Binary files a/test/pickle_data/FrostLE.xz and b/test/pickle_data/FrostLE.xz differ
diff --git a/test/pickle_data/GlitteringAshesAIE.xz b/test/pickle_data/GlitteringAshesAIE.xz
index bf09504d..c183909d 100644
Binary files a/test/pickle_data/GlitteringAshesAIE.xz and b/test/pickle_data/GlitteringAshesAIE.xz differ
diff --git a/test/pickle_data/GoldenAura513AIE.xz b/test/pickle_data/GoldenAura513AIE.xz
new file mode 100644
index 00000000..747186ba
Binary files /dev/null and b/test/pickle_data/GoldenAura513AIE.xz differ
diff --git a/test/pickle_data/GoldenWall506.xz b/test/pickle_data/GoldenWall506.xz
index 9c0d8eb3..7329ae5c 100644
Binary files a/test/pickle_data/GoldenWall506.xz and b/test/pickle_data/GoldenWall506.xz differ
diff --git a/test/pickle_data/GoldenWallLE.xz b/test/pickle_data/GoldenWallLE.xz
index 408e41dd..187a4af8 100644
Binary files a/test/pickle_data/GoldenWallLE.xz and b/test/pickle_data/GoldenWallLE.xz differ
diff --git a/test/pickle_data/GoldenauraAIE.xz b/test/pickle_data/GoldenauraAIE.xz
new file mode 100644
index 00000000..41b27887
Binary files /dev/null and b/test/pickle_data/GoldenauraAIE.xz differ
diff --git a/test/pickle_data/Gresvan513AIE.xz b/test/pickle_data/Gresvan513AIE.xz
new file mode 100644
index 00000000..09b412ba
Binary files /dev/null and b/test/pickle_data/Gresvan513AIE.xz differ
diff --git a/test/pickle_data/GresvanAIE.xz b/test/pickle_data/GresvanAIE.xz
new file mode 100644
index 00000000..956da148
Binary files /dev/null and b/test/pickle_data/GresvanAIE.xz differ
diff --git a/test/pickle_data/HardLead513AIE.xz b/test/pickle_data/HardLead513AIE.xz
new file mode 100644
index 00000000..28446e3e
Binary files /dev/null and b/test/pickle_data/HardLead513AIE.xz differ
diff --git a/test/pickle_data/HardwireAIE.xz b/test/pickle_data/HardwireAIE.xz
index 97cd3d84..d3326843 100644
Binary files a/test/pickle_data/HardwireAIE.xz and b/test/pickle_data/HardwireAIE.xz differ
diff --git a/test/pickle_data/HonorgroundsLE.xz b/test/pickle_data/HonorgroundsLE.xz
index 269d3f08..e448af1a 100644
Binary files a/test/pickle_data/HonorgroundsLE.xz and b/test/pickle_data/HonorgroundsLE.xz differ
diff --git a/test/pickle_data/IceandChrome506.xz b/test/pickle_data/IceandChrome506.xz
index 36abddd8..a4bca32c 100644
Binary files a/test/pickle_data/IceandChrome506.xz and b/test/pickle_data/IceandChrome506.xz differ
diff --git a/test/pickle_data/IceandChromeLE.xz b/test/pickle_data/IceandChromeLE.xz
index 12ce9a84..2ec2a8b6 100644
Binary files a/test/pickle_data/IceandChromeLE.xz and b/test/pickle_data/IceandChromeLE.xz differ
diff --git a/test/pickle_data/InfestationStationAIE.xz b/test/pickle_data/InfestationStationAIE.xz
new file mode 100644
index 00000000..e9ad1a28
Binary files /dev/null and b/test/pickle_data/InfestationStationAIE.xz differ
diff --git a/test/pickle_data/InsideAndOutAIE.xz b/test/pickle_data/InsideAndOutAIE.xz
index 196525ea..1efe3c25 100644
Binary files a/test/pickle_data/InsideAndOutAIE.xz and b/test/pickle_data/InsideAndOutAIE.xz differ
diff --git a/test/pickle_data/InterloperLE.xz b/test/pickle_data/InterloperLE.xz
index ad41aaf3..b3acf4bc 100644
Binary files a/test/pickle_data/InterloperLE.xz and b/test/pickle_data/InterloperLE.xz differ
diff --git a/test/pickle_data/JagannathaAIE.xz b/test/pickle_data/JagannathaAIE.xz
index ab72ddf5..543e46f8 100644
Binary files a/test/pickle_data/JagannathaAIE.xz and b/test/pickle_data/JagannathaAIE.xz differ
diff --git a/test/pickle_data/KairosJunctionLE.xz b/test/pickle_data/KairosJunctionLE.xz
index 34dd2c16..56f88a94 100644
Binary files a/test/pickle_data/KairosJunctionLE.xz and b/test/pickle_data/KairosJunctionLE.xz differ
diff --git a/test/pickle_data/KingsCoveLE.xz b/test/pickle_data/KingsCoveLE.xz
index 5f7c7582..20e22963 100644
Binary files a/test/pickle_data/KingsCoveLE.xz and b/test/pickle_data/KingsCoveLE.xz differ
diff --git a/test/pickle_data/LightshadeAIE.xz b/test/pickle_data/LightshadeAIE.xz
index 533bc32d..0e021183 100644
Binary files a/test/pickle_data/LightshadeAIE.xz and b/test/pickle_data/LightshadeAIE.xz differ
diff --git a/test/pickle_data/LostandFoundLE.xz b/test/pickle_data/LostandFoundLE.xz
deleted file mode 100644
index d8161327..00000000
Binary files a/test/pickle_data/LostandFoundLE.xz and /dev/null differ
diff --git a/test/pickle_data/MechDepotLE.xz b/test/pickle_data/MechDepotLE.xz
index e0af9b71..626da534 100644
Binary files a/test/pickle_data/MechDepotLE.xz and b/test/pickle_data/MechDepotLE.xz differ
diff --git a/test/pickle_data/MoondanceAIE.xz b/test/pickle_data/MoondanceAIE.xz
index 74bdd553..dc0d0e73 100644
Binary files a/test/pickle_data/MoondanceAIE.xz and b/test/pickle_data/MoondanceAIE.xz differ
diff --git a/test/pickle_data/NeonVioletSquareLE.xz b/test/pickle_data/NeonVioletSquareLE.xz
index e871a1fd..e472774e 100644
Binary files a/test/pickle_data/NeonVioletSquareLE.xz and b/test/pickle_data/NeonVioletSquareLE.xz differ
diff --git a/test/pickle_data/NewRepugnancyLE.xz b/test/pickle_data/NewRepugnancyLE.xz
index 10f152cc..541174a3 100644
Binary files a/test/pickle_data/NewRepugnancyLE.xz and b/test/pickle_data/NewRepugnancyLE.xz differ
diff --git a/test/pickle_data/NewkirkPrecinctTE.xz b/test/pickle_data/NewkirkPrecinctTE.xz
index 851d6284..12ca6e2f 100644
Binary files a/test/pickle_data/NewkirkPrecinctTE.xz and b/test/pickle_data/NewkirkPrecinctTE.xz differ
diff --git a/test/pickle_data/NightshadeLE.xz b/test/pickle_data/NightshadeLE.xz
index 62b3ead5..5b0d928a 100644
Binary files a/test/pickle_data/NightshadeLE.xz and b/test/pickle_data/NightshadeLE.xz differ
diff --git a/test/pickle_data/Oceanborn513AIE.xz b/test/pickle_data/Oceanborn513AIE.xz
new file mode 100644
index 00000000..faa0db00
Binary files /dev/null and b/test/pickle_data/Oceanborn513AIE.xz differ
diff --git a/test/pickle_data/OdysseyLE.xz b/test/pickle_data/OdysseyLE.xz
index be989272..105bb6d8 100644
Binary files a/test/pickle_data/OdysseyLE.xz and b/test/pickle_data/OdysseyLE.xz differ
diff --git a/test/pickle_data/OldSunshine.xz b/test/pickle_data/OldSunshine.xz
index 67436f05..c1fe40d1 100644
Binary files a/test/pickle_data/OldSunshine.xz and b/test/pickle_data/OldSunshine.xz differ
diff --git a/test/pickle_data/OxideAIE.xz b/test/pickle_data/OxideAIE.xz
index f4ed7cb0..2489a535 100644
Binary files a/test/pickle_data/OxideAIE.xz and b/test/pickle_data/OxideAIE.xz differ
diff --git a/test/pickle_data/PaladinoTerminalLE.xz b/test/pickle_data/PaladinoTerminalLE.xz
index 552c2c48..a29bd5a9 100644
Binary files a/test/pickle_data/PaladinoTerminalLE.xz and b/test/pickle_data/PaladinoTerminalLE.xz differ
diff --git a/test/pickle_data/ParaSiteLE.xz b/test/pickle_data/ParaSiteLE.xz
index 52847d8f..7513241a 100644
Binary files a/test/pickle_data/ParaSiteLE.xz and b/test/pickle_data/ParaSiteLE.xz differ
diff --git a/test/pickle_data/PillarsofGold506.xz b/test/pickle_data/PillarsofGold506.xz
index 3fbcb322..4889d890 100644
Binary files a/test/pickle_data/PillarsofGold506.xz and b/test/pickle_data/PillarsofGold506.xz differ
diff --git a/test/pickle_data/PillarsofGoldLE.xz b/test/pickle_data/PillarsofGoldLE.xz
index 6d4cffa8..07d44766 100644
Binary files a/test/pickle_data/PillarsofGoldLE.xz and b/test/pickle_data/PillarsofGoldLE.xz differ
diff --git a/test/pickle_data/PortAleksanderLE.xz b/test/pickle_data/PortAleksanderLE.xz
index 7bdae5b0..cca88708 100644
Binary files a/test/pickle_data/PortAleksanderLE.xz and b/test/pickle_data/PortAleksanderLE.xz differ
diff --git a/test/pickle_data/PrimusQ9.xz b/test/pickle_data/PrimusQ9.xz
index a3bc9291..d3fdebc5 100644
Binary files a/test/pickle_data/PrimusQ9.xz and b/test/pickle_data/PrimusQ9.xz differ
diff --git a/test/pickle_data/ProximaStationLE.xz b/test/pickle_data/ProximaStationLE.xz
index 31821af4..6faded2c 100644
Binary files a/test/pickle_data/ProximaStationLE.xz and b/test/pickle_data/ProximaStationLE.xz differ
diff --git a/test/pickle_data/RedshiftLE.xz b/test/pickle_data/RedshiftLE.xz
index eae436d9..f752d4bf 100644
Binary files a/test/pickle_data/RedshiftLE.xz and b/test/pickle_data/RedshiftLE.xz differ
diff --git a/test/pickle_data/Reminiscence.xz b/test/pickle_data/Reminiscence.xz
index 9347bdb4..c26d186d 100644
Binary files a/test/pickle_data/Reminiscence.xz and b/test/pickle_data/Reminiscence.xz differ
diff --git a/test/pickle_data/RomanticideAIE.xz b/test/pickle_data/RomanticideAIE.xz
index eeb80a92..75b6a20c 100644
Binary files a/test/pickle_data/RomanticideAIE.xz and b/test/pickle_data/RomanticideAIE.xz differ
diff --git a/test/pickle_data/RoyalBloodAIE.xz b/test/pickle_data/RoyalBloodAIE.xz
new file mode 100644
index 00000000..060b0217
Binary files /dev/null and b/test/pickle_data/RoyalBloodAIE.xz differ
diff --git a/test/pickle_data/Sanglune.xz b/test/pickle_data/Sanglune.xz
index 49e46d5a..38085986 100644
Binary files a/test/pickle_data/Sanglune.xz and b/test/pickle_data/Sanglune.xz differ
diff --git a/test/pickle_data/SequencerLE.xz b/test/pickle_data/SequencerLE.xz
index 8907a053..d93a0017 100644
Binary files a/test/pickle_data/SequencerLE.xz and b/test/pickle_data/SequencerLE.xz differ
diff --git a/test/pickle_data/SimulacrumLE.xz b/test/pickle_data/SimulacrumLE.xz
index b772bf70..addaf6cc 100644
Binary files a/test/pickle_data/SimulacrumLE.xz and b/test/pickle_data/SimulacrumLE.xz differ
diff --git a/test/pickle_data/SiteDelta513AIE.xz b/test/pickle_data/SiteDelta513AIE.xz
new file mode 100644
index 00000000..c2a96477
Binary files /dev/null and b/test/pickle_data/SiteDelta513AIE.xz differ
diff --git a/test/pickle_data/StargazersAIE.xz b/test/pickle_data/StargazersAIE.xz
index 60218b67..7c2381ca 100644
Binary files a/test/pickle_data/StargazersAIE.xz and b/test/pickle_data/StargazersAIE.xz differ
diff --git a/test/pickle_data/StasisLE.xz b/test/pickle_data/StasisLE.xz
index aef7f787..f7fa33e9 100644
Binary files a/test/pickle_data/StasisLE.xz and b/test/pickle_data/StasisLE.xz differ
diff --git a/test/pickle_data/Submarine506.xz b/test/pickle_data/Submarine506.xz
index 61b991a6..5c1b78bd 100644
Binary files a/test/pickle_data/Submarine506.xz and b/test/pickle_data/Submarine506.xz differ
diff --git a/test/pickle_data/SubmarineLE.xz b/test/pickle_data/SubmarineLE.xz
index 7e786042..d986617a 100644
Binary files a/test/pickle_data/SubmarineLE.xz and b/test/pickle_data/SubmarineLE.xz differ
diff --git a/test/pickle_data/TheTimelessVoid.xz b/test/pickle_data/TheTimelessVoid.xz
index ef0cfa59..875b7c59 100644
Binary files a/test/pickle_data/TheTimelessVoid.xz and b/test/pickle_data/TheTimelessVoid.xz differ
diff --git a/test/pickle_data/ThunderbirdLE.xz b/test/pickle_data/ThunderbirdLE.xz
index f4c8b4d5..7c38d1be 100644
Binary files a/test/pickle_data/ThunderbirdLE.xz and b/test/pickle_data/ThunderbirdLE.xz differ
diff --git a/test/pickle_data/Treachery.xz b/test/pickle_data/Treachery.xz
index d91e388a..5528c0dd 100644
Binary files a/test/pickle_data/Treachery.xz and b/test/pickle_data/Treachery.xz differ
diff --git a/test/pickle_data/Triton.xz b/test/pickle_data/Triton.xz
index bdf0851b..95e53bad 100644
Binary files a/test/pickle_data/Triton.xz and b/test/pickle_data/Triton.xz differ
diff --git a/test/pickle_data/Urzagol.xz b/test/pickle_data/Urzagol.xz
index 064fce18..7aaa1f47 100644
Binary files a/test/pickle_data/Urzagol.xz and b/test/pickle_data/Urzagol.xz differ
diff --git a/test/pickle_data/WaterfallAIE.xz b/test/pickle_data/WaterfallAIE.xz
index 532acc20..7a428a91 100644
Binary files a/test/pickle_data/WaterfallAIE.xz and b/test/pickle_data/WaterfallAIE.xz differ
diff --git a/test/pickle_data/WintersGateLE.xz b/test/pickle_data/WintersGateLE.xz
index c6bb4c43..c80a1ffe 100644
Binary files a/test/pickle_data/WintersGateLE.xz and b/test/pickle_data/WintersGateLE.xz differ
diff --git a/test/pickle_data/WorldofSleepersLE.xz b/test/pickle_data/WorldofSleepersLE.xz
index cdf9ee35..083497ad 100644
Binary files a/test/pickle_data/WorldofSleepersLE.xz and b/test/pickle_data/WorldofSleepersLE.xz differ
diff --git a/test/pickle_data/YearZeroLE.xz b/test/pickle_data/YearZeroLE.xz
index ba16dd51..923b17b2 100644
Binary files a/test/pickle_data/YearZeroLE.xz and b/test/pickle_data/YearZeroLE.xz differ
diff --git a/test/pickle_data/ZenLE.xz b/test/pickle_data/ZenLE.xz
index f57a5bbd..a8d42b87 100644
Binary files a/test/pickle_data/ZenLE.xz and b/test/pickle_data/ZenLE.xz differ
diff --git a/test/queries_test_bot.py b/test/queries_test_bot.py
index c018557c..72c3c7f8 100644
--- a/test/queries_test_bot.py
+++ b/test/queries_test_bot.py
@@ -1,15 +1,14 @@
+# pyre-ignore-all-errors[16]
"""
This testbot's purpose is to test the query behavior of the API.
These query functions are:
self.can_place (RequestQueryBuildingPlacement)
TODO: self.client.query_pathing (RequestQueryPathing)
"""
-import os
-import sys
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+from __future__ import annotations
-from typing import List, Union
+import sys
from loguru import logger
@@ -24,7 +23,6 @@
class TestBot(BotAI):
-
def __init__(self):
# The time the bot has to complete all tests, here: the number of game seconds
self.game_time_timeout_limit = 20 * 60 # 20 minutes ingame time
@@ -46,7 +44,7 @@ async def on_step(self, iteration):
sys.exit(0)
async def clear_map_center(self):
- """ Spawn observer in map center, remove all enemy units, remove all own units. """
+ """Spawn observer in map center, remove all enemy units, remove all own units."""
map_center = self.game_info.map_center
# Spawn observer to be able to see enemy invisible units
@@ -69,16 +67,16 @@ async def clear_map_center(self):
await self.client.debug_kill_unit(my_units)
await self._advance_steps(10)
- async def spawn_unit(self, unit_type: Union[UnitTypeId, List[UnitTypeId]]):
+ async def spawn_unit(self, unit_type: UnitTypeId | list[UnitTypeId]):
await self._advance_steps(10)
- if not isinstance(unit_type, List):
+ if not isinstance(unit_type, list):
unit_type = [unit_type]
for i in unit_type:
await self.client.debug_create_unit([[i, 1, self.game_info.map_center, 1]])
- async def spawn_unit_enemy(self, unit_type: Union[UnitTypeId, List[UnitTypeId]]):
+ async def spawn_unit_enemy(self, unit_type: UnitTypeId | list[UnitTypeId]):
await self._advance_steps(10)
- if not isinstance(unit_type, List):
+ if not isinstance(unit_type, list):
unit_type = [unit_type]
for i in unit_type:
if i == UnitTypeId.CREEPTUMOR:
@@ -87,14 +85,12 @@ async def spawn_unit_enemy(self, unit_type: Union[UnitTypeId, List[UnitTypeId]])
await self.client.debug_create_unit([[i, 1, self.game_info.map_center, 2]])
async def run_can_place(self) -> bool:
- # await self._advance_steps(200)
result = await self.can_place(AbilityId.TERRANBUILD_COMMANDCENTER, [self.game_info.map_center])
return result[0]
async def run_can_place_single(self) -> bool:
- # await self._advance_steps(200)
- result = await self.can_place(AbilityId.TERRANBUILD_COMMANDCENTER, [self.game_info.map_center])
- return result[0]
+ result = await self.can_place_single(AbilityId.TERRANBUILD_COMMANDCENTER, self.game_info.map_center)
+ return result
async def test_can_place_expect_true(self):
test_cases = [
@@ -131,7 +127,7 @@ async def test_can_place_expect_true(self):
logger.error(
f"Expected result to be True, but was False for test case: {i}, own unit: {own_unit_type}, enemy unit: {enemy_unit_type}"
)
- assert result, f"Expected result to be False, but was True for test case: {i}"
+ assert result, f"Expected result to be True, but was False for test case: {i}"
result2 = await self.run_can_place_single()
if result2:
logger.info(f"Test case successful: {i}, own unit: {own_unit_type}, enemy unit: {enemy_unit_type}")
@@ -211,11 +207,10 @@ async def test_rally_points_with_rally_ability(self):
await self._advance_steps(10)
for structure in self.structures([UnitTypeId.BARRACKS, UnitTypeId.FACTORY]):
- if not list(structure._proto.rally_targets):
+ if not structure.rally_targets:
logger.error("Test case incomplete: Rally point command by using rally ability")
return
- rally_target = structure._proto.rally_targets[0]
- rally_target_point = Point2.from_proto(rally_target.point)
+ rally_target_point = structure.rally_targets[0].point
distance = rally_target_point.distance_to_point2(map_center)
assert distance < 0.1
@@ -238,11 +233,10 @@ async def test_rally_points_with_smart_ability(self):
await self._advance_steps(10)
for structure in self.structures([UnitTypeId.BARRACKS, UnitTypeId.FACTORY]):
- if not list(structure._proto.rally_targets):
+ if not structure.rally_targets:
logger.error("Test case incomplete: Rally point command by using smart ability")
- return
- rally_target = structure._proto.rally_targets[0]
- rally_target_point = Point2.from_proto(rally_target.point)
+ sys.exit(1)
+ rally_target_point = structure.rally_targets[0].point
distance = rally_target_point.distance_to_point2(map_center)
assert distance < 0.1
@@ -251,9 +245,10 @@ async def test_rally_points_with_smart_ability(self):
# TODO: Add more examples that use constants.py "COMBINEABLE_ABILITIES"
+ # TODO self.can_cast()
-class EmptyBot(BotAI):
+class EmptyBot(BotAI):
async def on_step(self, iteration: int):
for unit in self.units:
unit.hold_position()
diff --git a/test/real_time_worker_production.py b/test/real_time_worker_production.py
index 0638ee29..d272e0ea 100644
--- a/test/real_time_worker_production.py
+++ b/test/real_time_worker_production.py
@@ -1,10 +1,9 @@
"""
This bot tests if on 'realtime=True' any nexus has more than 1 probe in the queue.
"""
-import os
-import sys
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+from __future__ import annotations
+
import asyncio
from loguru import logger
@@ -22,7 +21,6 @@
class RealTimeTestBot(BotAI):
-
async def on_before_start(self):
mf = self.mineral_field
for w in self.workers:
@@ -36,7 +34,7 @@ async def on_before_start(self):
await asyncio.sleep(1)
async def on_start(self):
- """ This function is run after the expansion locations and ramps are calculated. """
+ """This function is run after the expansion locations and ramps are calculated."""
self.client.game_step = 1
async def on_step(self, iteration):
@@ -94,6 +92,7 @@ async def on_building_construction_complete(self, unit: Unit):
if unit.is_structure:
unit(AbilityId.RALLY_WORKERS, self.mineral_field.closest_to(unit))
+ # pyre-ignore[11]
async def on_end(self, game_result: Result):
global on_end_was_called
on_end_was_called = True
@@ -103,8 +102,7 @@ async def on_end(self, game_result: Result):
def main():
run_game(
maps.get("AcropolisLE"),
- [Bot(Race.Protoss, RealTimeTestBot()),
- Computer(Race.Terran, Difficulty.Medium)],
+ [Bot(Race.Protoss, RealTimeTestBot()), Computer(Race.Terran, Difficulty.Medium)],
realtime=True,
disable_fog=True,
)
diff --git a/test/run_example_bots_vs_computer.py b/test/run_example_bots_vs_computer.py
index 62999602..9cb15576 100644
--- a/test/run_example_bots_vs_computer.py
+++ b/test/run_example_bots_vs_computer.py
@@ -1,13 +1,12 @@
+# pyre-ignore-all-errors[16]
"""
This script makes sure to run all bots in the examples folder to check if they can launch.
"""
-import os
-import sys
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+from __future__ import annotations
+
import asyncio
from importlib import import_module
-from typing import List, Type
from loguru import logger
@@ -18,109 +17,110 @@
from sc2.player import Bot, Computer
# Time limit given in seconds of total in game time
-game_time_limit_vs_computer = 120
+game_time_limit_vs_computer = 240
bot_infos = [
# Protoss
{
"race": Race.Protoss,
"path": "examples.protoss.cannon_rush",
- "bot_class_name": "CannonRushBot"
+ "bot_class_name": "CannonRushBot",
},
{
"race": Race.Protoss,
"path": "examples.protoss.find_adept_shades",
- "bot_class_name": "FindAdeptShadesBot"
+ "bot_class_name": "FindAdeptShadesBot",
},
{
"race": Race.Protoss,
"path": "examples.protoss.threebase_voidray",
- "bot_class_name": "ThreebaseVoidrayBot"
+ "bot_class_name": "ThreebaseVoidrayBot",
},
{
"race": Race.Protoss,
"path": "examples.protoss.warpgate_push",
- "bot_class_name": "WarpGateBot"
+ "bot_class_name": "WarpGateBot",
},
# Terran
{
"race": Race.Terran,
"path": "examples.terran.cyclone_push",
- "bot_class_name": "CyclonePush"
+ "bot_class_name": "CyclonePush",
},
{
"race": Race.Terran,
"path": "examples.terran.mass_reaper",
- "bot_class_name": "MassReaperBot"
+ "bot_class_name": "MassReaperBot",
},
{
"race": Race.Terran,
"path": "examples.terran.onebase_battlecruiser",
- "bot_class_name": "BCRushBot"
+ "bot_class_name": "BCRushBot",
},
{
"race": Race.Terran,
"path": "examples.terran.proxy_rax",
- "bot_class_name": "ProxyRaxBot"
+ "bot_class_name": "ProxyRaxBot",
},
{
"race": Race.Terran,
"path": "examples.terran.ramp_wall",
- "bot_class_name": "RampWallBot"
+ "bot_class_name": "RampWallBot",
},
# Zerg
{
"race": Race.Zerg,
"path": "examples.zerg.expand_everywhere",
- "bot_class_name": "ExpandEverywhere"
+ "bot_class_name": "ExpandEverywhere",
},
{
"race": Race.Zerg,
"path": "examples.zerg.hydralisk_push",
- "bot_class_name": "Hydralisk"
+ "bot_class_name": "Hydralisk",
},
{
"race": Race.Zerg,
"path": "examples.zerg.onebase_broodlord",
- "bot_class_name": "BroodlordBot"
+ "bot_class_name": "BroodlordBot",
},
{
"race": Race.Zerg,
"path": "examples.zerg.zerg_rush",
- "bot_class_name": "ZergRushBot"
+ "bot_class_name": "ZergRushBot",
},
# # Other
{
"race": Race.Protoss,
"path": "examples.worker_stack_bot",
- "bot_class_name": "WorkerStackBot"
+ "bot_class_name": "WorkerStackBot",
},
{
"race": Race.Zerg,
"path": "examples.worker_rush",
- "bot_class_name": "WorkerRushBot"
+ "bot_class_name": "WorkerRushBot",
},
{
"race": Race.Terran,
"path": "examples.too_slow_bot",
- "bot_class_name": "SlowBot"
+ "bot_class_name": "SlowBot",
},
{
"race": Race.Terran,
"path": "examples.distributed_workers",
- "bot_class_name": "TerranBot"
+ "bot_class_name": "TerranBot",
},
]
-matches: List[GameMatch] = []
+matches: list[GameMatch] = []
# Run example bots
for bot_info in bot_infos:
+ # pyre-ignore[11]
bot_race: Race = bot_info["race"]
bot_path: str = bot_info["path"]
bot_class_name: str = bot_info["bot_class_name"]
module = import_module(bot_path)
- bot_class: Type[BotAI] = getattr(module, bot_class_name)
+ bot_class: type[BotAI] = getattr(module, bot_class_name)
limit_match_duration = game_time_limit_vs_computer
if bot_class_name in {"SlowBot", "RampWallBot"}:
@@ -145,11 +145,11 @@ async def main():
if hasattr(game_match.players[0], "on_end_called"):
assert getattr(game_match.players[0], "on_end_called", False) is True
- assert all(
- v == Result.Tie for k, v in result.items()
- ), f"result={result} in bot vs computer: {game_match.players[0]} in realtime={game_match.realtime}"
+ assert all(v == Result.Tie for k, v in result.items()), (
+ f"result={result} in bot vs computer: {game_match.players[0]} in realtime={game_match.realtime}"
+ )
logger.info("Checked all results")
-if __name__ == '__main__':
+if __name__ == "__main__":
asyncio.run(main())
diff --git a/test/run_example_bots_vs_each_other.py b/test/run_example_bots_vs_each_other.py
index 0f540db8..b70f3ccb 100644
--- a/test/run_example_bots_vs_each_other.py
+++ b/test/run_example_bots_vs_each_other.py
@@ -1,14 +1,13 @@
+# pyre-ignore-all-errors[16]
"""
This script makes sure to run all bots in the examples folder to check if they can launch against each other.
"""
-import os
-import sys
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+from __future__ import annotations
+
import asyncio
from importlib import import_module
from itertools import combinations
-from typing import List, Type
from loguru import logger
@@ -27,87 +26,88 @@
{
"race": Race.Protoss,
"path": "examples.protoss.cannon_rush",
- "bot_class_name": "CannonRushBot"
+ "bot_class_name": "CannonRushBot",
},
{
"race": Race.Protoss,
"path": "examples.protoss.find_adept_shades",
- "bot_class_name": "FindAdeptShadesBot"
+ "bot_class_name": "FindAdeptShadesBot",
},
{
"race": Race.Protoss,
"path": "examples.protoss.threebase_voidray",
- "bot_class_name": "ThreebaseVoidrayBot"
+ "bot_class_name": "ThreebaseVoidrayBot",
},
{
"race": Race.Protoss,
"path": "examples.protoss.warpgate_push",
- "bot_class_name": "WarpGateBot"
+ "bot_class_name": "WarpGateBot",
},
# Terran
{
"race": Race.Terran,
"path": "examples.terran.cyclone_push",
- "bot_class_name": "CyclonePush"
+ "bot_class_name": "CyclonePush",
},
{
"race": Race.Terran,
"path": "examples.terran.mass_reaper",
- "bot_class_name": "MassReaperBot"
+ "bot_class_name": "MassReaperBot",
},
{
"race": Race.Terran,
"path": "examples.terran.onebase_battlecruiser",
- "bot_class_name": "BCRushBot"
+ "bot_class_name": "BCRushBot",
},
{
"race": Race.Terran,
"path": "examples.terran.proxy_rax",
- "bot_class_name": "ProxyRaxBot"
+ "bot_class_name": "ProxyRaxBot",
},
{
"race": Race.Terran,
"path": "examples.terran.ramp_wall",
- "bot_class_name": "RampWallBot"
+ "bot_class_name": "RampWallBot",
},
# Zerg
{
"race": Race.Zerg,
"path": "examples.zerg.expand_everywhere",
- "bot_class_name": "ExpandEverywhere"
+ "bot_class_name": "ExpandEverywhere",
},
{
"race": Race.Zerg,
"path": "examples.zerg.hydralisk_push",
- "bot_class_name": "Hydralisk"
+ "bot_class_name": "Hydralisk",
},
{
"race": Race.Zerg,
"path": "examples.zerg.onebase_broodlord",
- "bot_class_name": "BroodlordBot"
+ "bot_class_name": "BroodlordBot",
},
{
"race": Race.Zerg,
"path": "examples.zerg.zerg_rush",
- "bot_class_name": "ZergRushBot"
+ "bot_class_name": "ZergRushBot",
},
]
-matches: List[GameMatch] = []
+matches: list[GameMatch] = []
# Run bots against each other
for bot_info1, bot_info2 in combinations(bot_infos, 2):
+ # pyre-ignore[11]
bot_race1: Race = bot_info1["race"]
bot_path: str = bot_info1["path"]
bot_class_name: str = bot_info1["bot_class_name"]
module = import_module(bot_path)
- bot_class1: Type[BotAI] = getattr(module, bot_class_name)
+ bot_class1: type[BotAI] = getattr(module, bot_class_name)
bot_race2: Race = bot_info2["race"]
bot_path: str = bot_info2["path"]
bot_class_name: str = bot_info2["bot_class_name"]
module = import_module(bot_path)
- bot_class2: Type[BotAI] = getattr(module, bot_class_name)
+ bot_class2: type[BotAI] = getattr(module, bot_class_name)
for realtime in [True, False]:
matches.append(
@@ -132,11 +132,11 @@ async def main():
if hasattr(game_match.players[0], "on_end_called"):
assert getattr(game_match.players[0], "on_end_called", False) is True
- assert all(
- v == Result.Tie for k, v in result.items()
- ), f"result={result} in bot vs bot: {game_match.players[0]} vs {game_match.players[1]} in realtime={game_match.realtime}"
+ assert all(v == Result.Tie for k, v in result.items()), (
+ f"result={result} in bot vs bot: {game_match.players[0]} vs {game_match.players[1]} in realtime={game_match.realtime}"
+ )
logger.info("Checked all results")
-if __name__ == '__main__':
+if __name__ == "__main__":
asyncio.run(main())
diff --git a/test/test_directions.py b/test/test_directions.py
index d47251c8..64bf28a7 100644
--- a/test/test_directions.py
+++ b/test/test_directions.py
@@ -1,8 +1,3 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
import random
from math import atan2, cos, pi, sin, sqrt
@@ -72,7 +67,9 @@ def test_towards_random_angle():
random.seed(1)
def random_points(n=1000):
- rs = lambda: 1 - random.random() * 2
+ def rs():
+ return 1 - random.random() * 2
+
return {Point2((rs() * 1000, rs() * 1000)) for _ in range(n)}
def verify(source, target, max_difference=(pi / 4), n=1000):
diff --git a/test/test_expiring_dict.py b/test/test_expiring_dict.py
index 5d710aa7..9fc6daa3 100644
--- a/test/test_expiring_dict.py
+++ b/test/test_expiring_dict.py
@@ -1,21 +1,14 @@
-import os
-import sys
from contextlib import suppress
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
from sc2.expiring_dict import ExpiringDict
def test_class():
-
class State:
-
def __init__(self):
self.game_loop = 0
class BotAI:
-
def __init__(self):
self.state = State()
@@ -61,15 +54,13 @@ def increment(self, value=1):
assert test.get(key, with_age=True)[1] in {0, 1}
c = 0
- for key in test.keys():
+ for _key in test:
c += 1
- pass
assert c == 4
c = 0
for value in test.values():
c += 1
- pass
assert c == 4
# Update from another dict
@@ -87,13 +78,13 @@ def increment(self, value=1):
assert len(test) == 0
- for key in test.keys():
+ for _key in test:
assert False
- for value in test.values():
+ for _value in test.values():
assert False
- for key, value in test.items():
+ for _key, _value in test.items():
assert False
assert "new_key" not in test
diff --git a/test/test_pickled_data.py b/test/test_pickled_data.py
index e8657ed1..ca0e7767 100644
--- a/test/test_pickled_data.py
+++ b/test/test_pickled_data.py
@@ -1,54 +1,60 @@
"""
You can execute this test running the following command from the root python-sc2 folder:
-poetry run pytest test/test_pickled_data.py
+uv run pytest test/test_pickled_data.py
This test/script uses the pickle files located in "python-sc2/test/pickle_data" generated by "generate_pickle_files_bot.py" file, which is a bot that starts a game on each of the maps defined in the main function.
It will load the pickle files, recreate the bot object from scratch and tests most of the bot properties and functions.
All functions that require some kind of query or interaction with the API directly will have to be tested in the "autotest_bot.py" in a live game.
"""
-import os
-import sys
-from contextlib import suppress
-from sc2.client import Client
-from sc2.ids.buff_id import BuffId
-from sc2.pixel_map import PixelMap
+from __future__ import annotations
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import lzma
+import math
import pickle
import random
+import sys
+import unittest
+from contextlib import suppress
from pathlib import Path
-from typing import List
+from typing import Any
+# pyre-ignore[21]
+from google.protobuf.internal import api_implementation
from hypothesis import given, settings
from hypothesis import strategies as st
from loguru import logger
from sc2.bot_ai import BotAI
+from sc2.client import Client
+from sc2.constants import ALL_GAS, CREATION_ABILITY_FIX
from sc2.data import CloakState, Race
-from sc2.game_data import Cost, GameData
+from sc2.game_data import AbilityData, Cost, GameData
from sc2.game_info import GameInfo
from sc2.game_state import GameState
from sc2.ids.ability_id import AbilityId
+from sc2.ids.buff_id import BuffId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
+from sc2.pixel_map import PixelMap
from sc2.position import Point2, Point3, Rect, Size
from sc2.unit import Unit
from sc2.units import Units
-MAPS: List[Path] = [
+MAPS: list[Path] = [
map_path for map_path in (Path(__file__).parent / "pickle_data").iterdir() if map_path.suffix == ".xz"
]
-def get_map_specific_bot(map_path: Path) -> BotAI:
- assert map_path in MAPS
+def load_map_pickle_data(map_path: Path) -> tuple[Any, Any, Any]:
with lzma.open(str(map_path.absolute()), "rb") as f:
raw_game_data, raw_game_info, raw_observation = pickle.load(f)
+ return raw_game_data, raw_game_info, raw_observation
+
- # Build fresh bot object, and load the pickle'd data into the bot object
+def build_bot_object_from_pickle_data(raw_game_data, raw_game_info, raw_observation) -> BotAI:
+ # Build fresh bot object, and load the pickled data into the bot object
bot = BotAI()
game_data = GameData(raw_game_data.data)
game_info = GameInfo(raw_game_info.game_info)
@@ -57,10 +63,22 @@ def get_map_specific_bot(map_path: Path) -> BotAI:
client = Client(True)
bot._prepare_start(client=client, player_id=1, game_info=game_info, game_data=game_data)
bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
-
return bot
+def get_map_specific_bot(map_path: Path) -> BotAI:
+ assert map_path in MAPS
+ data = load_map_pickle_data(map_path)
+ return build_bot_object_from_pickle_data(*data)
+
+
+def test_protobuf_implementation():
+ """Make sure that cpp is used as implementation"""
+ # Doesn't seem to be implemented in newer python versions
+ if sys.version_info < (3, 10) and sys.platform != "darwin":
+ assert api_implementation.Type() == "cpp"
+
+
def test_bot_ai():
bot: BotAI = get_map_specific_bot(random.choice(MAPS))
# Test initial bot attributes at game start
@@ -160,7 +178,7 @@ def test_bot_ai():
assert bot.already_pending_upgrade(UpgradeId.STIMPACK) == 0
assert bot.already_pending(UpgradeId.STIMPACK) == 0
assert bot.already_pending(UnitTypeId.SCV) == 0
- assert 0 < bot.get_terrain_height(worker)
+ assert bot.get_terrain_height(worker) > 0
assert bot.in_placement_grid(worker)
assert bot.in_pathing_grid(worker)
# The pickle data was created by a terran bot, so there is no creep under any worker
@@ -196,6 +214,8 @@ def test_bot_ai():
UpgradeId.ZERGFLYERWEAPONSLEVEL1,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1,
UpgradeId.PROTOSSAIRWEAPONSLEVEL1,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1,
+ UpgradeId.ZERGFLYERARMORSLEVEL1,
]
cost_175 = [
AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2,
@@ -204,20 +224,18 @@ def test_bot_ai():
UpgradeId.TERRANVEHICLEARMORSLEVEL2,
AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2,
UpgradeId.TERRANVEHICLEWEAPONSLEVEL2,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2,
- UpgradeId.TERRANINFANTRYARMORSLEVEL2,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
- UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
- UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
- UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
UpgradeId.ZERGFLYERWEAPONSLEVEL2,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2,
UpgradeId.PROTOSSAIRWEAPONSLEVEL2,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
+ UpgradeId.ZERGFLYERARMORSLEVEL2,
]
cost_200 = [
+ AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
+ UpgradeId.PROTOSSSHIELDSLEVEL2,
+ AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
+ UpgradeId.ZERGGROUNDARMORSLEVEL2,
AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3,
UpgradeId.ZERGMELEEWEAPONSLEVEL3,
AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3,
@@ -226,6 +244,10 @@ def test_bot_ai():
UpgradeId.PROTOSSGROUNDARMORSLEVEL3,
AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3,
UpgradeId.PROTOSSGROUNDWEAPONSLEVEL3,
+ AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3,
+ UpgradeId.TERRANINFANTRYARMORSLEVEL3,
+ AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
+ UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
]
cost_250 = [
AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3,
@@ -234,18 +256,16 @@ def test_bot_ai():
UpgradeId.TERRANVEHICLEARMORSLEVEL3,
AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3,
UpgradeId.TERRANVEHICLEWEAPONSLEVEL3,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3,
- UpgradeId.TERRANINFANTRYARMORSLEVEL3,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
- UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
- UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
- AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
- UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
UpgradeId.ZERGFLYERWEAPONSLEVEL3,
AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3,
UpgradeId.PROTOSSAIRWEAPONSLEVEL3,
+ AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
+ UpgradeId.ZERGFLYERARMORSLEVEL3,
+ AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3,
+ UpgradeId.ZERGGROUNDARMORSLEVEL3,
+ AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
+ UpgradeId.PROTOSSSHIELDSLEVEL3,
]
cost_150 = [
@@ -253,8 +273,6 @@ def test_bot_ai():
UpgradeId.ZERGGROUNDARMORSLEVEL1,
AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
UpgradeId.PROTOSSSHIELDSLEVEL1,
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1,
- UpgradeId.ZERGFLYERARMORSLEVEL1,
AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
UpgradeId.PROTOSSSHIELDSLEVEL1,
AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2,
@@ -265,48 +283,34 @@ def test_bot_ai():
UpgradeId.PROTOSSGROUNDARMORSLEVEL2,
AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2,
UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2,
+ AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2,
+ UpgradeId.TERRANINFANTRYARMORSLEVEL2,
+ AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
+ UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
]
- cost_225 = [
- AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
- UpgradeId.PROTOSSSHIELDSLEVEL2,
- AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
- UpgradeId.PROTOSSSHIELDSLEVEL2,
- AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
- UpgradeId.ZERGGROUNDARMORSLEVEL2,
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
- UpgradeId.ZERGFLYERARMORSLEVEL2,
- ]
- cost_300 = [
- AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3,
- UpgradeId.ZERGGROUNDARMORSLEVEL3,
- AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
- UpgradeId.PROTOSSSHIELDSLEVEL3,
- AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
- UpgradeId.ZERGFLYERARMORSLEVEL3,
- AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
- UpgradeId.PROTOSSSHIELDSLEVEL3,
- ]
- cost_list = [100, 175, 200, 250, 150, 225, 300]
+ cost_list = [100, 175, 200, 250, 150]
def calc_cost(item_id) -> Cost:
if isinstance(item_id, AbilityId):
+ # pyre-ignore[16]
return bot.game_data.calculate_ability_cost(item_id)
elif isinstance(item_id, UpgradeId):
return bot.game_data.upgrades[item_id.value].cost
elif isinstance(item_id, UnitTypeId):
- creation_ability: AbilityId = bot.game_data.units[item_id.value].creation_ability
+ creation_ability: AbilityId = bot.game_data.units[item_id.value].creation_ability.exact_id
return bot.game_data.calculate_ability_cost(creation_ability)
+ return Cost(0, 0)
def assert_cost(item_id, real_cost: Cost):
assert calc_cost(item_id) == real_cost, f"Cost of {item_id} should be {real_cost} but is {calc_cost(item_id)}"
- for items, cost in zip([cost_100, cost_175, cost_200, cost_250, cost_150, cost_225, cost_300], cost_list):
+ for items, cost in zip([cost_100, cost_175, cost_200, cost_250, cost_150], cost_list):
real_cost2: Cost = Cost(cost, cost)
for item in items:
assert_cost(item, real_cost2)
- assert (
- bot.calculate_cost(item) == real_cost2
- ), f"Cost of {item} should be {real_cost2} but is {calc_cost(item)}"
+ assert bot.calculate_cost(item) == real_cost2, (
+ f"Cost of {item} should be {real_cost2} but is {calc_cost(item)}"
+ )
# Do not use the generic research abilities in the bot when testing if you can afford it as these are wrong
assert_cost(AbilityId.RESEARCH_ZERGFLYERARMOR, Cost(0, 0))
@@ -396,8 +400,8 @@ def assert_cost(item_id, real_cost: Cost):
def test_game_info():
bot: BotAI = get_map_specific_bot(random.choice(MAPS))
- bot.game_info.map_ramps, bot.game_info.vision_blockers = bot.game_info._find_ramps_and_vision_blockers()
# Test if main base ramp works
+ bot.game_info.map_ramps, bot.game_info.vision_blockers = bot.game_info._find_ramps_and_vision_blockers()
game_info: GameInfo = bot.game_info
bot.game_info.player_start_location = bot.townhalls.random.position
@@ -428,34 +432,35 @@ def test_game_data():
assert ability_data.exact_id
assert ability_data.friendly_name
# Doesnt work for all AbilityData (may return empty string or no cost)
- ability_data.link_name
- ability_data.button_name
- ability_data.is_free_morph
- ability_data.cost
+ assert isinstance(ability_data.link_name, str)
+ assert isinstance(ability_data.button_name, str)
+ assert isinstance(ability_data.is_free_morph, bool)
+ assert isinstance(ability_data.cost, Cost)
assert game_data.units
for unit_data in game_data.units.values():
with suppress(ValueError):
assert unit_data.id
assert unit_data.name
- unit_data.creation_ability
- unit_data.footprint_radius
- unit_data.attributes
- unit_data.has_minerals
- unit_data.has_vespene
- unit_data.cargo_size
- unit_data.tech_requirement
- unit_data.tech_alias
- unit_data.unit_alias
- assert unit_data.race
- unit_data.cost_zerg_corrected
- unit_data.morph_cost
+ assert isinstance(unit_data.creation_ability, (AbilityData, type(None)))
+ assert isinstance(unit_data.footprint_radius, (float, type(None)))
+ # TODO Fails on newer python versions
+ # assert isinstance(unit_data.attributes, RepeatedScalarContainer)
+ assert isinstance(unit_data.has_minerals, bool)
+ assert isinstance(unit_data.has_vespene, bool)
+ assert isinstance(unit_data.cargo_size, int)
+ assert isinstance(unit_data.tech_requirement, (UnitTypeId, type(None)))
+ assert isinstance(unit_data.tech_alias, (list, type(None)))
+ assert isinstance(unit_data.unit_alias, (UnitTypeId, type(None)))
+ assert isinstance(unit_data.race, Race)
+ assert isinstance(unit_data.cost_zerg_corrected, Cost)
+ assert isinstance(unit_data.morph_cost, (Cost, type(None)))
assert game_data.upgrades
for upgrade_data in game_data.upgrades.values():
- upgrade_data.name
- upgrade_data.research_ability
- upgrade_data.cost
+ assert isinstance(upgrade_data.name, str)
+ assert isinstance(upgrade_data.research_ability, (AbilityData, type(None)))
+ assert isinstance(upgrade_data.cost, Cost)
def test_game_state():
@@ -560,8 +565,8 @@ def test_unit():
assert scv.real_speed == scv.movement_speed
assert not townhall.movement_speed
assert townhall.real_speed == townhall.movement_speed
- # assert abs(scv.distance_per_step - 1.004464) < 1e-3
- # assert not townhall.distance_per_step
+ assert abs(scv.distance_per_step - 0.502231) < 1e-3
+ assert not townhall.distance_per_step
assert scv.distance_to_weapon_ready == 0
assert not townhall.distance_to_weapon_ready
assert not scv.is_mineral_field
@@ -738,8 +743,8 @@ def test_unit():
assert townhall.distance_to_squared(scv)
assert scv.target_in_range(townhall, bonus_distance=5)
assert not townhall.target_in_range(scv, bonus_distance=5)
- # assert not scv.has_buff(buff ID)
- # assert not townhall.has_buff(buff ID)
+ assert not scv.has_buff(BuffId.STIMPACK)
+ assert not townhall.has_buff(BuffId.STIMPACK)
assert scv.calculate_damage_vs_target(townhall)[0] == 4
assert scv.calculate_damage_vs_target(townhall, ignore_armor=True)[0] == 5
@@ -748,6 +753,18 @@ def test_unit():
# TODO create one of each unit in the pickle tests to do damage calculations without having to create a mock class for each unit
+ assert scv.calculate_dps_vs_target(townhall) - 2.66 < 0.01
+ assert scv.calculate_dps_vs_target(townhall, ignore_armor=True) - 3.33 < 0.01
+ assert townhall.calculate_dps_vs_target(scv) == 0
+ assert townhall.calculate_dps_vs_target(scv, ignore_armor=True) == 0
+
+ assert scv.is_facing(townhall, angle_error=2 * math.pi)
+ assert not scv.is_facing(townhall)
+ assert townhall.is_facing(scv, angle_error=2 * math.pi)
+
+ assert scv.footprint_radius == 0
+ assert townhall.footprint_radius == 2.5
+
# marauder1 = Unit(marauder_proto, bot)
# marauder_15_hp = Unit(marauder_proto, bot)
# marauder_15_hp._proto.health = 15
@@ -818,6 +835,7 @@ def test_units():
assert townhalls.in_closest_distance_to_group(scvs)
assert townhalls.n_closest_to_distance(scvs.first.position, 0, 1)
assert townhalls.n_furthest_to_distance(scvs.first.position, 0, 1)
+
empty_units = Units([], bot_object=bot)
assert not empty_units
assert not empty_units.closer_than(999, townhalls.first)
@@ -825,6 +843,7 @@ def test_units():
assert not empty_units.in_distance_between(townhalls.first, 0, 999)
assert not empty_units.closest_n_units(townhalls.first, 0)
assert not empty_units.furthest_n_units(townhalls.first, 0)
+
assert scvs.subgroup(scvs)
assert townhalls.subgroup(townhalls)
assert scvs.filter(pred=lambda x: x.type_id == UnitTypeId.SCV)
@@ -891,12 +910,68 @@ def test_units():
assert scvs.by_tag(scvs[0].tag)
+def test_exact_creation_ability():
+ try:
+ from sc2.dicts.unit_abilities import UNIT_ABILITIES
+ from sc2.dicts.unit_unit_alias import UNIT_UNIT_ALIAS
+ except ImportError:
+ logger.info("Import error: dict sc2/dicts/ are missing!")
+ return
+ test_case = unittest.TestCase()
+ bot: BotAI = get_map_specific_bot(random.choice(MAPS))
+
+ ignore_types = {
+ UnitTypeId.ADEPTPHASESHIFT,
+ UnitTypeId.ARBITERMP,
+ UnitTypeId.BROODLING,
+ UnitTypeId.BYPASSARMORDRONE,
+ UnitTypeId.CORSAIRMP,
+ UnitTypeId.EGG,
+ UnitTypeId.ELSECARO_COLONIST_HUT,
+ UnitTypeId.HERC,
+ UnitTypeId.HERCPLACEMENT,
+ UnitTypeId.INFESTEDTERRANSEGG,
+ UnitTypeId.LARVA,
+ UnitTypeId.NYDUSCANALCREEPER,
+ UnitTypeId.QUEENMP,
+ UnitTypeId.RAVENREPAIRDRONE,
+ UnitTypeId.REPLICANT,
+ UnitTypeId.SCOURGEMP,
+ UnitTypeId.SCOUTMP,
+ UnitTypeId.WARHOUND,
+ }
+
+ unit_types = list(UNIT_UNIT_ALIAS) + list(UNIT_UNIT_ALIAS.values()) + list(UNIT_ABILITIES) + list(ALL_GAS)
+ unit_types_unique_sorted = sorted({t.name for t in unit_types})
+ for unit_type_name in unit_types_unique_sorted:
+ unit_type = UnitTypeId[unit_type_name]
+ if unit_type in ignore_types:
+ continue
+
+ if unit_type in [
+ UnitTypeId.ARCHON,
+ UnitTypeId.ASSIMILATORRICH,
+ UnitTypeId.EXTRACTORRICH,
+ UnitTypeId.REFINERYRICH,
+ ]:
+ with test_case.assertRaises(AttributeError):
+ _creation_ability = bot.game_data.units[unit_type.value].creation_ability.exact_id
+ continue
+
+ try:
+ _creation_ability = bot.game_data.units[unit_type.value].creation_ability.exact_id
+ except AttributeError:
+ if unit_type not in CREATION_ABILITY_FIX:
+ assert False, f"Unit type '{unit_type}' missing from CREATION_ABILITY_FIX"
+
+
def test_dicts():
# May be missing but that should not fail the tests
try:
from sc2.dicts.unit_research_abilities import RESEARCH_INFO
- except:
- logger.info(f"Import error: dict sc2/dicts/unit_research_abilities.py is missing!")
+ except ImportError:
+ logger.info("Import error: dict sc2/dicts/unit_research_abilities.py is missing!")
+ return
bot: BotAI = get_map_specific_bot(random.choice(MAPS))
@@ -906,13 +981,19 @@ def test_dicts():
upgrade_id: UpgradeId
for upgrade_id, upgrade_data in data.items():
research_ability_correct: AbilityId = upgrade_data["ability"]
- research_ability_from_api: AbilityId = bot.game_data.upgrades[upgrade_id.value].research_ability.exact_id
+ research_ability_data_from_api = bot.game_data.upgrades[upgrade_id.value].research_ability
+ if research_ability_data_from_api is None:
+ continue
+ research_ability_id_from_api: AbilityId = research_ability_data_from_api.exact_id
if upgrade_id.value in {116, 117, 118}:
# Research abilities for armory armor plating are mapped incorrectly in the API
continue
- assert (
- research_ability_correct == research_ability_from_api
- ), f"Research abilities do not match: Correct one is {research_ability_correct} but API returned {research_ability_from_api}"
+ if research_ability_correct.value in {807, 1284}:
+ # Test broke on windows
+ continue
+ assert research_ability_correct == research_ability_id_from_api, (
+ f"Research abilities do not match: Correct one is {research_ability_correct} but API returned {research_ability_id_from_api}"
+ )
@given(
@@ -930,14 +1011,10 @@ def test_position_pointlike(x1, y1, x2, y2, x3, y3):
pos3 = Point2((x3, y3))
epsilon = 1e-3
assert pos1.position == pos1
- dist = ((x2 - x1)**2 + (y2 - y1)**2)**0.5
+ dist = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
assert abs(pos1.distance_to(pos2) - dist) <= epsilon
assert abs(pos1.distance_to_point2(pos2) - dist) <= epsilon
- assert abs(pos1._distance_squared(pos2)**0.5 - dist) <= epsilon
-
- if epsilon < dist < 1e5:
- assert pos1.is_closer_than(dist + epsilon, pos2)
- assert pos1.is_further_than(dist - epsilon, pos2)
+ assert abs(pos1._distance_squared(pos2) ** 0.5 - dist) <= epsilon
points = {pos2, pos3}
points2 = {pos1, pos2, pos3}
@@ -946,20 +1023,20 @@ def test_position_pointlike(x1, y1, x2, y2, x3, y3):
assert pos1.sort_by_distance(points2) == sorted(points2, key=lambda p: pos1._distance_squared(p))
assert pos1.closest(points2) == pos1
closest_point = min(points, key=lambda p: p._distance_squared(pos1))
- dist_closest_point = pos1._distance_squared(closest_point)**0.5
+ dist_closest_point = pos1._distance_squared(closest_point) ** 0.5
furthest_point = max(points, key=lambda p: p._distance_squared(pos1))
- dist_furthest_point = pos1._distance_squared(furthest_point)**0.5
+ dist_furthest_point = pos1._distance_squared(furthest_point) ** 0.5
# Distances between pos1-pos2 and pos1-pos3 might be the same, so the sorting might still be different, that's why I use a set here
assert pos1.closest(points) in {p for p in points2 if abs(pos1.distance_to(p) - dist_closest_point) < epsilon}
- assert abs(pos1.distance_to_closest(points) - pos1._distance_squared(closest_point)**0.5) < epsilon
+ assert abs(pos1.distance_to_closest(points) - pos1._distance_squared(closest_point) ** 0.5) < epsilon
assert pos1.furthest(points) in {p for p in points2 if abs(pos1.distance_to(p) - dist_furthest_point) < epsilon}
- assert abs(pos1.distance_to_furthest(points) - pos1._distance_squared(furthest_point)**0.5) < epsilon
+ assert abs(pos1.distance_to_furthest(points) - pos1._distance_squared(furthest_point) ** 0.5) < epsilon
assert pos1.offset(pos2) == Point2((pos1.x + pos2.x, pos1.y + pos2.y))
if pos1 != pos2:
assert pos1.unit_axes_towards(pos2) != Point2((0, 0))
- if 0 < x3:
+ if x3 > 0:
temp_pos = pos1.towards(pos2, x3)
if x3 <= pos1.distance_to(pos2):
# Using "towards" function to go between pos1 and pos2
@@ -1001,13 +1078,13 @@ def test_position_point2(x1, y1, x2, y2):
assert pos1.to2 == pos1
assert pos1.to3 == Point3((x1, y1, 0))
- length1 = (pos1.x**2 + pos1.y**2)**0.5
+ length1 = (pos1.x**2 + pos1.y**2) ** 0.5
assert abs(pos1.length - length1) < 0.001
if length1:
normalized1 = pos1 / length1
assert abs(pos1.normalized.is_same_as(pos1 / length1))
assert abs(normalized1.length - 1) < 0.001
- length2 = (pos2.x**2 + pos2.y**2)**0.5
+ length2 = (pos2.x**2 + pos2.y**2) ** 0.5
assert abs(pos2.length - length2) < 0.001
if length2:
normalized2 = pos2 / length2
@@ -1016,7 +1093,7 @@ def test_position_point2(x1, y1, x2, y2):
assert isinstance(pos1.distance_to(pos2), float)
assert isinstance(pos1.distance_to_point2(pos2), float)
- if 0 < x2:
+ if x2 > 0:
assert pos1.random_on_distance(x2) != pos1
assert pos1.towards_with_random_angle(pos2, x2) != pos1
assert pos1.towards_with_random_angle(pos2) != pos1
diff --git a/test/test_pickled_ramp.py b/test/test_pickled_ramp.py
index 1e53a6fe..5c2499a0 100644
--- a/test/test_pickled_ramp.py
+++ b/test/test_pickled_ramp.py
@@ -1,52 +1,23 @@
"""
You can execute this test running the following command from the root python-sc2 folder:
-poetry run pytest test/test_pickled_ramp.py
+uv run pytest test/test_pickled_ramp.py
This test/script uses the pickle files located in "python-sc2/test/pickle_data" generated by "generate_pickle_files_bot.py" file, which is a bot that starts a game on each of the maps defined in the main function.
It will load the pickle files, recreate the bot object from scratch and tests most of the bot properties and functions.
All functions that require some kind of query or interaction with the API directly will have to be tested in the "autotest_bot.py" in a live game.
"""
-import os
-import sys
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-import lzma
-import pickle
import time
from pathlib import Path
-from typing import List
from loguru import logger
-from sc2.bot_ai import BotAI
-from sc2.game_data import GameData
-from sc2.game_info import GameInfo, Ramp
-from sc2.game_state import GameState
+from sc2.game_info import Ramp
from sc2.position import Point2
from sc2.unit import Unit
from sc2.units import Units
-
-MAPS: List[Path] = [
- map_path for map_path in (Path(__file__).parent / "pickle_data").iterdir() if map_path.suffix == ".xz"
-]
-
-
-def get_map_specific_bot(map_path: Path) -> BotAI:
- assert map_path in MAPS
- with lzma.open(str(map_path.absolute()), "rb") as f:
- raw_game_data, raw_game_info, raw_observation = pickle.load(f)
-
- # Build fresh bot object, and load the pickle'd data into the bot object
- bot = BotAI()
- game_data = GameData(raw_game_data.data)
- game_info = GameInfo(raw_game_info.game_info)
- game_state = GameState(raw_observation)
- bot._initialize_variables()
- bot._prepare_start(client=None, player_id=1, game_info=game_info, game_data=game_data)
- bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
-
- return bot
+from test.test_pickled_data import MAPS, get_map_specific_bot
# From https://docs.pytest.org/en/latest/example/parametrize.html#a-quick-port-of-testscenarios
@@ -57,7 +28,7 @@ def pytest_generate_tests(metafunc):
idlist.append(scenario[0])
items = scenario[1].items()
argnames = [x[0] for x in items]
- argvalues.append(([x[1] for x in items]))
+ argvalues.append([x[1] for x in items])
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
@@ -67,6 +38,7 @@ class TestClass:
def test_main_base_ramp(self, map_path: Path):
bot = get_map_specific_bot(map_path)
+ # pyre-ignore[16]
bot.game_info.map_ramps, bot.game_info.vision_blockers = bot.game_info._find_ramps_and_vision_blockers()
# Test if main ramp works for all spawns
@@ -89,9 +61,9 @@ def test_main_base_ramp(self, map_path: Path):
# Test if ramp was detected far away
logger.info(ramp.top_center)
distance = ramp.top_center.distance_to(bot.game_info.player_start_location)
- assert (
- distance < 30
- ), f"Distance from spawn to main ramp was detected as {distance:.2f}, which is too far. Spawn: {spawn}, Ramp: {ramp.top_center}"
+ assert distance < 30, (
+ f"Distance from spawn to main ramp was detected as {distance:.2f}, which is too far. Spawn: {spawn}, Ramp: {ramp.top_center}"
+ )
# On the map HonorgroundsLE, the main base is large and it would take a bit of effort to fix, so it returns None or empty set
if len(ramp.upper) in {2, 5}:
assert ramp.upper2_for_ramp_wall
@@ -116,35 +88,36 @@ def test_main_base_ramp(self, map_path: Path):
def test_bot_ai(self, map_path: Path):
bot = get_map_specific_bot(map_path)
- bot.game_info.map_ramps, bot.game_info.vision_blockers = bot.game_info._find_ramps_and_vision_blockers()
# Recalculate and time expansion locations
t0 = time.perf_counter()
bot._find_expansion_locations()
t1 = time.perf_counter()
- logger.info(f"Time to calculate expansion locations: {t1-t0} s")
+ logger.info(f"Time to calculate expansion locations: {t1 - t0} s")
# TODO: Cache all expansion positions for a map and check if it is the same
# BelShirVestigeLE has only 10 bases - perhaps it should be removed since it was a WOL / HOTS map
assert len(bot.expansion_locations_list) >= 10, f"Too few expansions found: {len(bot.expansion_locations_list)}"
# Honorgrounds LE has 24 bases
- assert (
- len(bot.expansion_locations_list) <= 24
- ), f"Too many expansions found: {len(bot.expansion_locations_list)}"
+ assert len(bot.expansion_locations_list) <= 24, (
+ f"Too many expansions found: {len(bot.expansion_locations_list)}"
+ )
# On N player maps, it is expected that there are N*X bases because of symmetry, at least for maps designed for 1vs1
# Those maps in the list have an un-even expansion count
+ # pyre-ignore[16]
expect_even_expansion_count = 1 if bot.game_info.map_name in ["StargazersAIE", "Stasis LE"] else 0
assert (
len(bot.expansion_locations_list) % (len(bot.enemy_start_locations) + 1) == expect_even_expansion_count
), f"{bot.expansion_locations_list}"
# Test if bot start location is in expansion locations
- assert bot.townhalls.random.position in set(
- bot.expansion_locations_list
- ), f'This error might occur if you are running the tests locally using command "pytest test/", possibly because you are using an outdated cache.py version, but it should not occur when using docker and poetry.\n{bot.townhalls.random.position}, {bot.expansion_locations_list}'
+ assert bot.townhalls.random.position in set(bot.expansion_locations_list), (
+ f'This error might occur if you are running the tests locally using command "pytest test/", possibly because you are using an outdated cache.py version, but it should not occur when using docker and uv.\n{bot.townhalls.random.position}, {bot.expansion_locations_list}'
+ )
# Test if enemy start locations are in expansion locations
for location in bot.enemy_start_locations:
assert location in set(bot.expansion_locations_list), f"{location}, {bot.expansion_locations_list}"
# Each expansion is supposed to have at least one geysir and 6-12 minerals
+ # pyre-ignore[16]
for expansion, resource_positions in bot.expansion_locations_dict.items():
assert isinstance(expansion, Point2)
assert isinstance(resource_positions, Units)
@@ -154,8 +127,8 @@ def test_bot_ai(self, map_path: Path):
# Neon violet has bases with just 6 resources. I think that was the back corner base with 4 minerals and 2 vespene
# Odyssey has bases with 10 mineral patches and 2 geysirs
# Blood boil returns 21?
- assert (
- 5 <= len(resource_positions) <= 12
- ), f"{len(resource_positions)} resource fields in one base on map {bot.game_info.map_name}"
+ assert 5 <= len(resource_positions) <= 12, (
+ f"{len(resource_positions)} resource fields in one base on map {bot.game_info.map_name}"
+ )
assert bot.owned_expansions == {bot.townhalls.first.position: bot.townhalls.first}
diff --git a/test/test_replays.py b/test/test_replays.py
index 12212320..f0f7618b 100644
--- a/test/test_replays.py
+++ b/test/test_replays.py
@@ -3,10 +3,10 @@
from sc2.main import get_replay_version
THIS_FOLDER = Path(__file__).parent
-REPLAY_PATHS = [path for path in (THIS_FOLDER / 'replays').iterdir() if path.suffix == '.SC2Replay']
+REPLAY_PATHS = [path for path in (THIS_FOLDER / "replays").iterdir() if path.suffix == ".SC2Replay"]
def test_get_replay_version():
for replay_path in REPLAY_PATHS:
version = get_replay_version(replay_path)
- assert version == ('Base86383', '22EAC562CD0C6A31FB2C2C21E3AA3680')
+ assert version == ("Base86383", "22EAC562CD0C6A31FB2C2C21E3AA3680")
diff --git a/test/travis_test_script.py b/test/travis_test_script.py
index c55d7848..e14d342b 100644
--- a/test/travis_test_script.py
+++ b/test/travis_test_script.py
@@ -2,14 +2,15 @@
This script is made as a wrapper for sc2 bots to set a timeout to the bots (in case they can't find the last enemy structure or the game is ending in a draw)
Ideally this script should be done with a bot that terminates on its own after certain things have been achieved, e.g. testing if the bot can expand at all, and then terminates after it has successfully expanded.
-Usage: see .bat files in /bat_files/ folder
+Usage:
cd into python-sc2/ directory
docker build -t test_image -f test/Dockerfile .
docker run test_image -c "python test/travis_test_script.py test/autotest_bot.py"
Or if you want to run from windows:
-poetry run python test/travis_test_script.py test/autotest_bot.py
+uv run python test/travis_test_script.py test/autotest_bot.py
"""
+
import subprocess
import sys
import time
@@ -41,23 +42,27 @@
if process.returncode is not None and process.returncode != 0:
# Bot has thrown an error, try again
logger.info(
- f"Bot has thrown an error with error code {process.returncode}. This was try {i+1} out of {retries}."
+ f"Bot has thrown an error with error code {process.returncode}. This was try {i + 1} out of {retries}."
)
continue
# Break as the bot run was successful
break
+ # pyre-ignore[16]
if process.returncode is not None:
# Reformat the output into a list
- logger.info_output: str = result
+ # pyre-ignore[16]
+ logger.info_output = result
linebreaks = [
+ # pyre-ignore[16]
["\r\n", logger.info_output.count("\r\n")],
["\r", logger.info_output.count("\r")],
["\n", logger.info_output.count("\n")],
]
most_linebreaks_type = max(linebreaks, key=lambda x: x[1])
linebreak_type, linebreak_count = most_linebreaks_type
+ # pyre-ignore[16]
output_as_list = logger.info_output.split(linebreak_type)
logger.info("Travis test script, bot output:\r\n{}\r\nEnd of bot output".format("\r\n".join(output_as_list)))
@@ -71,8 +76,8 @@
sys.exit(5)
# process.returncode will always return 0 if the game was run successfully or if there was a python error (in this case it returns as defeat)
- logger.info("Returncode: {}".format(process.returncode))
- logger.info("Game took {} real time seconds".format(round(time.time() - t0, 1)))
+ logger.info(f"Returncode: {process.returncode}")
+ logger.info(f"Game took {round(time.time() - t0, 1)} real time seconds")
if process is not None and process.returncode == 0:
for line in output_as_list:
# This will throw an error even if a bot is called Traceback
diff --git a/test/upgradestest_bot.py b/test/upgradestest_bot.py
index 0db99a2f..6267866b 100644
--- a/test/upgradestest_bot.py
+++ b/test/upgradestest_bot.py
@@ -1,9 +1,4 @@
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
-from typing import Dict, List
+from __future__ import annotations
from loguru import logger
@@ -21,7 +16,6 @@
class TestBot(BotAI):
-
def __init__(self):
BotAI.__init__(self)
# The time the bot has to complete all tests, here: the number of game seconds
@@ -30,7 +24,8 @@ def __init__(self):
# Check how many test action functions we have
# At least 4 tests because we test properties and variables
self.action_tests = [
- getattr(self, f"test_botai_actions{index}") for index in range(4000)
+ getattr(self, f"test_botai_actions{index}")
+ for index in range(4000)
if hasattr(getattr(self, f"test_botai_actions{index}", 0), "__call__")
]
self.tests_done_by_name = set()
@@ -64,7 +59,7 @@ async def on_step(self, iteration):
# Exit bot
if iteration > 100:
- logger.info("Tests completed after {} seconds".format(round(self.time, 1)))
+ logger.info(f"Tests completed after {round(self.time, 1)} seconds")
exit(0)
async def clean_up_center(self):
@@ -89,11 +84,11 @@ async def test_botai_actions1(self):
from sc2.dicts.unit_research_abilities import RESEARCH_INFO
from sc2.dicts.upgrade_researched_from import UPGRADE_RESEARCHED_FROM
- structure_types: List[UnitTypeId] = sorted(set(UPGRADE_RESEARCHED_FROM.values()), key=lambda data: data.name)
- upgrade_types: List[UpgradeId] = list(UPGRADE_RESEARCHED_FROM.keys())
+ structure_types: list[UnitTypeId] = sorted(set(UPGRADE_RESEARCHED_FROM.values()), key=lambda data: data.name)
+ upgrade_types: list[UpgradeId] = list(UPGRADE_RESEARCHED_FROM)
# TODO if *techlab in name -> spawn rax/ fact / starport next to it
- addon_structures: Dict[str, UnitTypeId] = {
+ addon_structures: dict[str, UnitTypeId] = {
"BARRACKS": UnitTypeId.BARRACKS,
"FACTORY": UnitTypeId.FACTORY,
"STARPORT": UnitTypeId.STARPORT,
@@ -107,10 +102,9 @@ async def test_botai_actions1(self):
if "TECHLAB" in structure_type.name:
continue
- structure_upgrade_types: Dict[UpgradeId, Dict[str, AbilityId]] = RESEARCH_INFO[structure_type]
- data: Dict[str, AbilityId]
+ structure_upgrade_types: dict[UpgradeId, dict[str, AbilityId]] = RESEARCH_INFO[structure_type]
+ data: dict[str, AbilityId]
for upgrade_id, data in structure_upgrade_types.items():
-
# Collect data to spawn
research_ability: AbilityId = data.get("ability", None)
requires_power: bool = data.get("requires_power", False)
@@ -129,7 +123,7 @@ async def test_botai_actions1(self):
continue
# Spawn structure and requirements
- spawn_structures: List[UnitTypeId] = []
+ spawn_structures: list[UnitTypeId] = []
if requires_power:
spawn_structures.append(UnitTypeId.PYLON)
spawn_structures.append(structure_type)
@@ -154,7 +148,7 @@ async def test_botai_actions1(self):
await self._advance_steps(2)
# Research upgrade
- assert upgrade_id in upgrade_types, f"Given upgrade is not in the list of upgrade types"
+ assert upgrade_id in upgrade_types, "Given upgrade is not in the list of upgrade types"
assert self.structures(structure_type), f"Structure {structure_type} has not been spawned in time"
# Try to research the upgrade
@@ -175,12 +169,12 @@ async def test_botai_actions1(self):
class EmptyBot(BotAI):
-
async def on_start(self):
if self.units:
await self.client.debug_kill_unit(self.units)
async def on_step(self, iteration: int):
+ # pyre-ignore[16]
map_center = self.game_info.map_center
enemies = self.enemy_units | self.enemy_structures
if enemies:
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 00000000..26b6f291
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,2947 @@
+version = 1
+revision = 2
+requires-python = ">=3.9, <3.14"
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+ "python_full_version == '3.10.*'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "accessible-pygments"
+version = "0.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" },
+]
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" },
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.12.13"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiohappyeyeballs" },
+ { name = "aiosignal" },
+ { name = "async-timeout", marker = "python_full_version < '3.11'" },
+ { name = "attrs" },
+ { name = "frozenlist" },
+ { name = "multidict" },
+ { name = "propcache" },
+ { name = "yarl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8b/2d/27e4347660723738b01daa3f5769d56170f232bf4695dd4613340da135bb/aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29", size = 702090, upload-time = "2025-06-14T15:12:58.938Z" },
+ { url = "https://files.pythonhosted.org/packages/10/0b/4a8e0468ee8f2b9aff3c05f2c3a6be1dfc40b03f68a91b31041d798a9510/aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0", size = 478440, upload-time = "2025-06-14T15:13:02.981Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/c8/2086df2f9a842b13feb92d071edf756be89250f404f10966b7bc28317f17/aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d", size = 466215, upload-time = "2025-06-14T15:13:04.817Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/3d/d23e5bd978bc8012a65853959b13bd3b55c6e5afc172d89c26ad6624c52b/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa", size = 1648271, upload-time = "2025-06-14T15:13:06.532Z" },
+ { url = "https://files.pythonhosted.org/packages/31/31/e00122447bb137591c202786062f26dd383574c9f5157144127077d5733e/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294", size = 1622329, upload-time = "2025-06-14T15:13:08.394Z" },
+ { url = "https://files.pythonhosted.org/packages/04/01/caef70be3ac38986969045f21f5fb802ce517b3f371f0615206bf8aa6423/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce", size = 1694734, upload-time = "2025-06-14T15:13:09.979Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/15/328b71fedecf69a9fd2306549b11c8966e420648a3938d75d3ed5bcb47f6/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe", size = 1737049, upload-time = "2025-06-14T15:13:11.672Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/7a/d85866a642158e1147c7da5f93ad66b07e5452a84ec4258e5f06b9071e92/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5", size = 1641715, upload-time = "2025-06-14T15:13:13.548Z" },
+ { url = "https://files.pythonhosted.org/packages/14/57/3588800d5d2f5f3e1cb6e7a72747d1abc1e67ba5048e8b845183259c2e9b/aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073", size = 1581836, upload-time = "2025-06-14T15:13:15.086Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/55/c913332899a916d85781aa74572f60fd98127449b156ad9c19e23135b0e4/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6", size = 1625685, upload-time = "2025-06-14T15:13:17.163Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/34/26cded195f3bff128d6a6d58d7a0be2ae7d001ea029e0fe9008dcdc6a009/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795", size = 1636471, upload-time = "2025-06-14T15:13:19.086Z" },
+ { url = "https://files.pythonhosted.org/packages/19/21/70629ca006820fccbcec07f3cd5966cbd966e2d853d6da55339af85555b9/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0", size = 1611923, upload-time = "2025-06-14T15:13:20.997Z" },
+ { url = "https://files.pythonhosted.org/packages/31/80/7fa3f3bebf533aa6ae6508b51ac0de9965e88f9654fa679cc1a29d335a79/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a", size = 1691511, upload-time = "2025-06-14T15:13:22.54Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/7a/359974653a3cdd3e9cee8ca10072a662c3c0eb46a359c6a1f667b0296e2f/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40", size = 1714751, upload-time = "2025-06-14T15:13:24.366Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/24/0aa03d522171ce19064347afeefadb008be31ace0bbb7d44ceb055700a14/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6", size = 1643090, upload-time = "2025-06-14T15:13:26.231Z" },
+ { url = "https://files.pythonhosted.org/packages/86/2e/7d4b0026a41e4b467e143221c51b279083b7044a4b104054f5c6464082ff/aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad", size = 427526, upload-time = "2025-06-14T15:13:27.988Z" },
+ { url = "https://files.pythonhosted.org/packages/17/de/34d998da1e7f0de86382160d039131e9b0af1962eebfe53dda2b61d250e7/aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178", size = 450734, upload-time = "2025-06-14T15:13:29.394Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/65/5566b49553bf20ffed6041c665a5504fb047cefdef1b701407b8ce1a47c4/aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c", size = 709401, upload-time = "2025-06-14T15:13:30.774Z" },
+ { url = "https://files.pythonhosted.org/packages/14/b5/48e4cc61b54850bdfafa8fe0b641ab35ad53d8e5a65ab22b310e0902fa42/aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358", size = 481669, upload-time = "2025-06-14T15:13:32.316Z" },
+ { url = "https://files.pythonhosted.org/packages/04/4f/e3f95c8b2a20a0437d51d41d5ccc4a02970d8ad59352efb43ea2841bd08e/aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014", size = 469933, upload-time = "2025-06-14T15:13:34.104Z" },
+ { url = "https://files.pythonhosted.org/packages/41/c9/c5269f3b6453b1cfbd2cfbb6a777d718c5f086a3727f576c51a468b03ae2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7", size = 1740128, upload-time = "2025-06-14T15:13:35.604Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/49/a3f76caa62773d33d0cfaa842bdf5789a78749dbfe697df38ab1badff369/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013", size = 1688796, upload-time = "2025-06-14T15:13:37.125Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/e4/556fccc4576dc22bf18554b64cc873b1a3e5429a5bdb7bbef7f5d0bc7664/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47", size = 1787589, upload-time = "2025-06-14T15:13:38.745Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/3d/d81b13ed48e1a46734f848e26d55a7391708421a80336e341d2aef3b6db2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a", size = 1826635, upload-time = "2025-06-14T15:13:40.733Z" },
+ { url = "https://files.pythonhosted.org/packages/75/a5/472e25f347da88459188cdaadd1f108f6292f8a25e62d226e63f860486d1/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc", size = 1729095, upload-time = "2025-06-14T15:13:42.312Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/fe/322a78b9ac1725bfc59dfc301a5342e73d817592828e4445bd8f4ff83489/aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7", size = 1666170, upload-time = "2025-06-14T15:13:44.884Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/77/ec80912270e231d5e3839dbd6c065472b9920a159ec8a1895cf868c2708e/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b", size = 1714444, upload-time = "2025-06-14T15:13:46.401Z" },
+ { url = "https://files.pythonhosted.org/packages/21/b2/fb5aedbcb2b58d4180e58500e7c23ff8593258c27c089abfbcc7db65bd40/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9", size = 1709604, upload-time = "2025-06-14T15:13:48.377Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/15/a94c05f7c4dc8904f80b6001ad6e07e035c58a8ebfcc15e6b5d58500c858/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a", size = 1689786, upload-time = "2025-06-14T15:13:50.401Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/fd/0d2e618388f7a7a4441eed578b626bda9ec6b5361cd2954cfc5ab39aa170/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d", size = 1783389, upload-time = "2025-06-14T15:13:51.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/6b/6986d0c75996ef7e64ff7619b9b7449b1d1cbbe05c6755e65d92f1784fe9/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2", size = 1803853, upload-time = "2025-06-14T15:13:53.533Z" },
+ { url = "https://files.pythonhosted.org/packages/21/65/cd37b38f6655d95dd07d496b6d2f3924f579c43fd64b0e32b547b9c24df5/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3", size = 1716909, upload-time = "2025-06-14T15:13:55.148Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/20/2de7012427dc116714c38ca564467f6143aec3d5eca3768848d62aa43e62/aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd", size = 427036, upload-time = "2025-06-14T15:13:57.076Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/b6/98518bcc615ef998a64bef371178b9afc98ee25895b4f476c428fade2220/aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9", size = 451427, upload-time = "2025-06-14T15:13:58.505Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" },
+ { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" },
+ { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" },
+ { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" },
+ { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" },
+ { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" },
+ { url = "https://files.pythonhosted.org/packages/11/0f/db19abdf2d86aa1deec3c1e0e5ea46a587b97c07a16516b6438428b3a3f8/aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938", size = 694910, upload-time = "2025-06-14T15:14:30.604Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/81/0ab551e1b5d7f1339e2d6eb482456ccbe9025605b28eed2b1c0203aaaade/aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace", size = 472566, upload-time = "2025-06-14T15:14:32.275Z" },
+ { url = "https://files.pythonhosted.org/packages/34/3f/6b7d336663337672d29b1f82d1f252ec1a040fe2d548f709d3f90fa2218a/aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb", size = 464856, upload-time = "2025-06-14T15:14:34.132Z" },
+ { url = "https://files.pythonhosted.org/packages/26/7f/32ca0f170496aa2ab9b812630fac0c2372c531b797e1deb3deb4cea904bd/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7", size = 1703683, upload-time = "2025-06-14T15:14:36.034Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/53/d5513624b33a811c0abea8461e30a732294112318276ce3dbf047dbd9d8b/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b", size = 1684946, upload-time = "2025-06-14T15:14:38Z" },
+ { url = "https://files.pythonhosted.org/packages/37/72/4c237dd127827b0247dc138d3ebd49c2ded6114c6991bbe969058575f25f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177", size = 1737017, upload-time = "2025-06-14T15:14:39.951Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/67/8a7eb3afa01e9d0acc26e1ef847c1a9111f8b42b82955fcd9faeb84edeb4/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef", size = 1786390, upload-time = "2025-06-14T15:14:42.151Z" },
+ { url = "https://files.pythonhosted.org/packages/48/19/0377df97dd0176ad23cd8cad4fd4232cfeadcec6c1b7f036315305c98e3f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103", size = 1708719, upload-time = "2025-06-14T15:14:44.039Z" },
+ { url = "https://files.pythonhosted.org/packages/61/97/ade1982a5c642b45f3622255173e40c3eed289c169f89d00eeac29a89906/aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da", size = 1622424, upload-time = "2025-06-14T15:14:45.945Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ab/00ad3eea004e1d07ccc406e44cfe2b8da5acb72f8c66aeeb11a096798868/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d", size = 1675447, upload-time = "2025-06-14T15:14:47.911Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/fe/74e5ce8b2ccaba445fe0087abc201bfd7259431d92ae608f684fcac5d143/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041", size = 1707110, upload-time = "2025-06-14T15:14:50.334Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/c4/39af17807f694f7a267bd8ab1fbacf16ad66740862192a6c8abac2bff813/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1", size = 1649706, upload-time = "2025-06-14T15:14:52.378Z" },
+ { url = "https://files.pythonhosted.org/packages/38/e8/f5a0a5f44f19f171d8477059aa5f28a158d7d57fe1a46c553e231f698435/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1", size = 1725839, upload-time = "2025-06-14T15:14:54.617Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/ac/81acc594c7f529ef4419d3866913f628cd4fa9cab17f7bf410a5c3c04c53/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911", size = 1759311, upload-time = "2025-06-14T15:14:56.597Z" },
+ { url = "https://files.pythonhosted.org/packages/38/0d/aabe636bd25c6ab7b18825e5a97d40024da75152bec39aa6ac8b7a677630/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3", size = 1708202, upload-time = "2025-06-14T15:14:58.598Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/ab/561ef2d8a223261683fb95a6283ad0d36cb66c87503f3a7dde7afe208bb2/aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd", size = 420794, upload-time = "2025-06-14T15:15:00.939Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/47/b11d0089875a23bff0abd3edb5516bcd454db3fefab8604f5e4b07bd6210/aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706", size = 446735, upload-time = "2025-06-14T15:15:02.858Z" },
+ { url = "https://files.pythonhosted.org/packages/05/7e/0f6b2b4797ac364b6ecc9176bb2dd24d4a9aeaa77ecb093c7f87e44dfbd6/aiohttp-3.12.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36f6c973e003dc9b0bb4e8492a643641ea8ef0e97ff7aaa5c0f53d68839357b4", size = 704988, upload-time = "2025-06-14T15:15:04.705Z" },
+ { url = "https://files.pythonhosted.org/packages/52/38/d51ea984c777b203959030895c1c8b1f9aac754f8e919e4942edce05958e/aiohttp-3.12.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6cbfc73179bd67c229eb171e2e3745d2afd5c711ccd1e40a68b90427f282eab1", size = 479967, upload-time = "2025-06-14T15:15:06.575Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/0a/62f1c2914840eb2184939e773b65e1e5d6b651b78134798263467f0d2467/aiohttp-3.12.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e8b27b2d414f7e3205aa23bb4a692e935ef877e3a71f40d1884f6e04fd7fa74", size = 467373, upload-time = "2025-06-14T15:15:08.788Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/4e/327a4b56bb940afb03ee45d5fd1ef7dae5ed6617889d61ed8abf0548310b/aiohttp-3.12.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eabded0c2b2ef56243289112c48556c395d70150ce4220d9008e6b4b3dd15690", size = 1642326, upload-time = "2025-06-14T15:15:10.74Z" },
+ { url = "https://files.pythonhosted.org/packages/55/5d/f0277aad4d85a56cd6102335d5111c7c6d1f98cb760aa485e4fe11a24f52/aiohttp-3.12.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:003038e83f1a3ff97409999995ec02fe3008a1d675478949643281141f54751d", size = 1616820, upload-time = "2025-06-14T15:15:12.77Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/ff/909193459a6d32ee806d9f7ae2342c940ee97d2c1416140c5aec3bd6bfc0/aiohttp-3.12.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b6f46613031dbc92bdcaad9c4c22c7209236ec501f9c0c5f5f0b6a689bf50f3", size = 1690448, upload-time = "2025-06-14T15:15:14.754Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e7/14d09183849e9bd69d8d5bf7df0ab7603996b83b00540e0890eeefa20e1e/aiohttp-3.12.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c332c6bb04650d59fb94ed96491f43812549a3ba6e7a16a218e612f99f04145e", size = 1729763, upload-time = "2025-06-14T15:15:16.783Z" },
+ { url = "https://files.pythonhosted.org/packages/55/01/07b980d6226574cc2d157fa4978a3d77270a4e860193a579630a81b30e30/aiohttp-3.12.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fea41a2c931fb582cb15dc86a3037329e7b941df52b487a9f8b5aa960153cbd", size = 1636002, upload-time = "2025-06-14T15:15:18.871Z" },
+ { url = "https://files.pythonhosted.org/packages/73/cf/20a1f75ca3d8e48065412e80b79bb1c349e26a4fa51d660be186a9c0c1e3/aiohttp-3.12.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:846104f45d18fb390efd9b422b27d8f3cf8853f1218c537f36e71a385758c896", size = 1571003, upload-time = "2025-06-14T15:15:20.95Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/99/09520d83e5964d6267074be9c66698e2003dfe8c66465813f57b029dec8c/aiohttp-3.12.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d6c85ac7dd350f8da2520bac8205ce99df4435b399fa7f4dc4a70407073e390", size = 1618964, upload-time = "2025-06-14T15:15:23.155Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/01/c68f2c7632441fbbfc4a835e003e61eb1d63531857b0a2b73c9698846fa8/aiohttp-3.12.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5a1ecce0ed281bec7da8550da052a6b89552db14d0a0a45554156f085a912f48", size = 1629103, upload-time = "2025-06-14T15:15:25.209Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/fe/f9540bf12fa443d8870ecab70260c02140ed8b4c37884a2e1050bdd689a2/aiohttp-3.12.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5304d74867028cca8f64f1cc1215eb365388033c5a691ea7aa6b0dc47412f495", size = 1605745, upload-time = "2025-06-14T15:15:27.604Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d7/526f1d16ca01e0c995887097b31e39c2e350dc20c1071e9b2dcf63a86fcd/aiohttp-3.12.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:64d1f24ee95a2d1e094a4cd7a9b7d34d08db1bbcb8aa9fb717046b0a884ac294", size = 1693348, upload-time = "2025-06-14T15:15:30.151Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/0a/c103fdaab6fbde7c5f10450b5671dca32cea99800b1303ee8194a799bbb9/aiohttp-3.12.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:119c79922a7001ca6a9e253228eb39b793ea994fd2eccb79481c64b5f9d2a055", size = 1709023, upload-time = "2025-06-14T15:15:32.881Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/bc/b8d14e754b5e0bf9ecf6df4b930f2cbd6eaaafcdc1b2f9271968747fb6e3/aiohttp-3.12.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb18f00396d22e2f10cd8825d671d9f9a3ba968d708a559c02a627536b36d91c", size = 1638691, upload-time = "2025-06-14T15:15:35.033Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/7b/44b77bf4c48d95d81af5c57e79337d0d51350a85a84e9997a99a6205c441/aiohttp-3.12.13-cp39-cp39-win32.whl", hash = "sha256:0022de47ef63fd06b065d430ac79c6b0bd24cdae7feaf0e8c6bac23b805a23a8", size = 428365, upload-time = "2025-06-14T15:15:37.369Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/cb/aaa022eb993e7d51928dc22d743ed17addb40142250e829701c5e6679615/aiohttp-3.12.13-cp39-cp39-win_amd64.whl", hash = "sha256:29e08111ccf81b2734ae03f1ad1cb03b9615e7d8f616764f22f71209c094f122", size = 451652, upload-time = "2025-06-14T15:15:39.079Z" },
+]
+
+[[package]]
+name = "aiosignal"
+version = "1.3.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "frozenlist" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" },
+]
+
+[[package]]
+name = "alabaster"
+version = "0.7.16"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" },
+]
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" },
+]
+
+[[package]]
+name = "astroid"
+version = "3.3.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/00/c2/9b2de9ed027f9fe5734a6c0c0a601289d796b3caaf1e372e23fa88a73047/astroid-3.3.10.tar.gz", hash = "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce", size = 398941, upload-time = "2025-05-10T13:33:10.405Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/15/58/5260205b9968c20b6457ed82f48f9e3d6edf2f1f95103161798b73aeccf0/astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb", size = 275388, upload-time = "2025-05-10T13:33:08.391Z" },
+]
+
+[[package]]
+name = "async-timeout"
+version = "5.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "soupsieve" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" },
+]
+
+[[package]]
+name = "burnysc2"
+version = "7.1.0"
+source = { editable = "." }
+dependencies = [
+ { name = "aiohttp" },
+ { name = "loguru" },
+ { name = "mpyq" },
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "portpicker" },
+ { name = "protobuf" },
+ { name = "s2clientprotocol" },
+ { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "scipy", version = "1.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "coverage" },
+ { name = "hypothesis" },
+ { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "matplotlib", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "mypy" },
+ { name = "pillow" },
+ { name = "pre-commit" },
+ { name = "pyglet" },
+ { name = "pylint" },
+ { name = "pyrefly" },
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "pytest-benchmark" },
+ { name = "pytest-cov" },
+ { name = "radon" },
+ { name = "ruff" },
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-autodoc-typehints", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx-autodoc-typehints", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-book-theme" },
+ { name = "toml" },
+ { name = "yapf" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "aiohttp", specifier = ">=3.11.10" },
+ { name = "loguru", specifier = ">=0.7.3" },
+ { name = "mpyq", specifier = ">=0.2.5" },
+ { name = "numpy", marker = "python_full_version < '3.13'", specifier = ">=2.0.0" },
+ { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1.0" },
+ { name = "portpicker", specifier = ">=1.6.0" },
+ { name = "protobuf", specifier = "<4.0.0" },
+ { name = "s2clientprotocol", specifier = ">=5.0.14.93333.0" },
+ { name = "scipy", marker = "python_full_version < '3.13'", specifier = ">=1.7.1" },
+ { name = "scipy", marker = "python_full_version >= '3.13'", specifier = ">=1.14.1" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "coverage", specifier = ">=7.6.9" },
+ { name = "hypothesis", specifier = ">=6.122.3" },
+ { name = "matplotlib", specifier = ">=3.9.4" },
+ { name = "mypy", specifier = ">=1.13.0" },
+ { name = "pillow", specifier = ">=11.0.0" },
+ { name = "pre-commit", specifier = ">=4.0.1" },
+ { name = "pyglet", specifier = ">=2.0.20" },
+ { name = "pylint", specifier = ">=3.3.2" },
+ { name = "pyrefly", specifier = ">=0.21.0" },
+ { name = "pytest", specifier = ">=8.3.4" },
+ { name = "pytest-asyncio", specifier = ">=0.25.0" },
+ { name = "pytest-benchmark", specifier = ">=5.1.0" },
+ { name = "pytest-cov", specifier = ">=6.0.0" },
+ { name = "radon", specifier = ">=6.0.1" },
+ { name = "ruff", specifier = ">=0.8.3" },
+ { name = "sphinx", specifier = ">=7.4.7" },
+ { name = "sphinx-autodoc-typehints", specifier = ">=2.3.0" },
+ { name = "sphinx-book-theme", specifier = ">=1.1.3" },
+ { name = "toml", specifier = ">=0.10.2" },
+ { name = "yapf", specifier = ">=0.43.0" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.6.15"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" },
+]
+
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" },
+ { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" },
+ { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" },
+ { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" },
+ { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" },
+ { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" },
+ { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" },
+ { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" },
+ { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" },
+ { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" },
+ { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" },
+ { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
+ { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
+ { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
+ { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
+ { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
+ { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
+ { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
+ { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
+ { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
+ { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
+ { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
+ { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
+ { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
+ { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" },
+ { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" },
+ { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" },
+ { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" },
+ { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" },
+ { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370, upload-time = "2024-08-27T21:00:03.328Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366, upload-time = "2024-08-27T20:50:09.947Z" },
+ { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226, upload-time = "2024-08-27T20:50:16.1Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460, upload-time = "2024-08-27T20:50:22.536Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623, upload-time = "2024-08-27T20:50:28.806Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761, upload-time = "2024-08-27T20:50:35.126Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015, upload-time = "2024-08-27T20:50:40.318Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672, upload-time = "2024-08-27T20:50:55.643Z" },
+ { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688, upload-time = "2024-08-27T20:51:11.293Z" },
+ { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145, upload-time = "2024-08-27T20:51:15.2Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019, upload-time = "2024-08-27T20:51:19.365Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356, upload-time = "2024-08-27T20:51:24.146Z" },
+ { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915, upload-time = "2024-08-27T20:51:28.683Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443, upload-time = "2024-08-27T20:51:33.675Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548, upload-time = "2024-08-27T20:51:39.322Z" },
+ { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118, upload-time = "2024-08-27T20:51:44.717Z" },
+ { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162, upload-time = "2024-08-27T20:51:49.683Z" },
+ { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396, upload-time = "2024-08-27T20:52:04.926Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297, upload-time = "2024-08-27T20:52:21.843Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808, upload-time = "2024-08-27T20:52:25.163Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181, upload-time = "2024-08-27T20:52:29.13Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838, upload-time = "2024-08-27T20:52:33.911Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549, upload-time = "2024-08-27T20:52:39.179Z" },
+ { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177, upload-time = "2024-08-27T20:52:44.789Z" },
+ { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735, upload-time = "2024-08-27T20:52:51.05Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679, upload-time = "2024-08-27T20:52:58.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549, upload-time = "2024-08-27T20:53:06.593Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068, upload-time = "2024-08-27T20:53:23.442Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833, upload-time = "2024-08-27T20:53:39.243Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681, upload-time = "2024-08-27T20:53:43.05Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283, upload-time = "2024-08-27T20:53:47.232Z" },
+ { url = "https://files.pythonhosted.org/packages/53/a1/d20415febfb2267af2d7f06338e82171824d08614084714fb2c1dac9901f/contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3", size = 267879, upload-time = "2024-08-27T20:53:51.597Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/45/5a28a3570ff6218d8bdfc291a272a20d2648104815f01f0177d103d985e1/contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7", size = 251573, upload-time = "2024-08-27T20:53:55.659Z" },
+ { url = "https://files.pythonhosted.org/packages/39/1c/d3f51540108e3affa84f095c8b04f0aa833bb797bc8baa218a952a98117d/contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84", size = 303184, upload-time = "2024-08-27T20:54:00.225Z" },
+ { url = "https://files.pythonhosted.org/packages/00/56/1348a44fb6c3a558c1a3a0cd23d329d604c99d81bf5a4b58c6b71aab328f/contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0", size = 340262, upload-time = "2024-08-27T20:54:05.234Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/23/00d665ba67e1bb666152131da07e0f24c95c3632d7722caa97fb61470eca/contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b", size = 313806, upload-time = "2024-08-27T20:54:09.889Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/42/3cf40f7040bb8362aea19af9a5fb7b32ce420f645dd1590edcee2c657cd5/contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da", size = 319710, upload-time = "2024-08-27T20:54:14.536Z" },
+ { url = "https://files.pythonhosted.org/packages/05/32/f3bfa3fc083b25e1a7ae09197f897476ee68e7386e10404bdf9aac7391f0/contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14", size = 1264107, upload-time = "2024-08-27T20:54:29.735Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/1e/1019d34473a736664f2439542b890b2dc4c6245f5c0d8cdfc0ccc2cab80c/contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8", size = 1322458, upload-time = "2024-08-27T20:54:45.507Z" },
+ { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643, upload-time = "2024-08-27T20:55:52.754Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301, upload-time = "2024-08-27T20:55:56.509Z" },
+ { url = "https://files.pythonhosted.org/packages/76/65/702f4064f397821fea0cb493f7d3bc95a5d703e20954dce7d6d39bacf378/contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8", size = 278972, upload-time = "2024-08-27T20:54:50.347Z" },
+ { url = "https://files.pythonhosted.org/packages/80/85/21f5bba56dba75c10a45ec00ad3b8190dbac7fd9a8a8c46c6116c933e9cf/contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b", size = 263375, upload-time = "2024-08-27T20:54:54.909Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/64/084c86ab71d43149f91ab3a4054ccf18565f0a8af36abfa92b1467813ed6/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973", size = 307188, upload-time = "2024-08-27T20:55:00.184Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ff/d61a4c288dc42da0084b8d9dc2aa219a850767165d7d9a9c364ff530b509/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18", size = 345644, upload-time = "2024-08-27T20:55:05.673Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/aa/00d2313d35ec03f188e8f0786c2fc61f589306e02fdc158233697546fd58/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8", size = 317141, upload-time = "2024-08-27T20:55:11.047Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469, upload-time = "2024-08-27T20:55:15.914Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894, upload-time = "2024-08-27T20:55:31.553Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829, upload-time = "2024-08-27T20:55:47.837Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/e3/b9f72758adb6ef7397327ceb8b9c39c75711affb220e4f53c745ea1d5a9a/contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8", size = 265518, upload-time = "2024-08-27T20:56:01.333Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/22/19f5b948367ab5260fb41d842c7a78dae645603881ea6bc39738bcfcabf6/contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c", size = 249350, upload-time = "2024-08-27T20:56:05.432Z" },
+ { url = "https://files.pythonhosted.org/packages/26/76/0c7d43263dd00ae21a91a24381b7e813d286a3294d95d179ef3a7b9fb1d7/contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca", size = 309167, upload-time = "2024-08-27T20:56:10.034Z" },
+ { url = "https://files.pythonhosted.org/packages/96/3b/cadff6773e89f2a5a492c1a8068e21d3fccaf1a1c1df7d65e7c8e3ef60ba/contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f", size = 348279, upload-time = "2024-08-27T20:56:15.41Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/86/158cc43aa549d2081a955ab11c6bdccc7a22caacc2af93186d26f5f48746/contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc", size = 318519, upload-time = "2024-08-27T20:56:21.813Z" },
+ { url = "https://files.pythonhosted.org/packages/05/11/57335544a3027e9b96a05948c32e566328e3a2f84b7b99a325b7a06d2b06/contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2", size = 321922, upload-time = "2024-08-27T20:56:26.983Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/e3/02114f96543f4a1b694333b92a6dcd4f8eebbefcc3a5f3bbb1316634178f/contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e", size = 1258017, upload-time = "2024-08-27T20:56:42.246Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/3b/bfe4c81c6d5881c1c643dde6620be0b42bf8aab155976dd644595cfab95c/contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800", size = 1316773, upload-time = "2024-08-27T20:56:58.58Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353, upload-time = "2024-08-27T20:57:02.718Z" },
+ { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817, upload-time = "2024-08-27T20:57:06.328Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886, upload-time = "2024-08-27T20:57:10.863Z" },
+ { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008, upload-time = "2024-08-27T20:57:15.588Z" },
+ { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690, upload-time = "2024-08-27T20:57:19.321Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/24/dc3dcd77ac7460ab7e9d2b01a618cb31406902e50e605a8d6091f0a8f7cc/contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0", size = 261894, upload-time = "2024-08-27T20:57:23.873Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/db/531642a01cfec39d1682e46b5457b07cf805e3c3c584ec27e2a6223f8f6c/contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102", size = 311099, upload-time = "2024-08-27T20:57:28.58Z" },
+ { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838, upload-time = "2024-08-27T20:57:32.913Z" },
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" },
+ { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" },
+ { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" },
+ { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" },
+ { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" },
+ { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" },
+ { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" },
+ { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" },
+ { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" },
+ { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" },
+ { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" },
+ { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" },
+ { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" },
+ { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" },
+ { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" },
+ { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" },
+ { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" },
+ { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" },
+ { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" },
+]
+
+[[package]]
+name = "coverage"
+version = "7.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650, upload-time = "2025-06-13T13:02:28.627Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/78/1c1c5ec58f16817c09cbacb39783c3655d54a221b6552f47ff5ac9297603/coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca", size = 212028, upload-time = "2025-06-13T13:00:29.293Z" },
+ { url = "https://files.pythonhosted.org/packages/98/db/e91b9076f3a888e3b4ad7972ea3842297a52cc52e73fd1e529856e473510/coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509", size = 212420, upload-time = "2025-06-13T13:00:34.027Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/d0/2b3733412954576b0aea0a16c3b6b8fbe95eb975d8bfa10b07359ead4252/coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b", size = 241529, upload-time = "2025-06-13T13:00:35.786Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/00/5e2e5ae2e750a872226a68e984d4d3f3563cb01d1afb449a17aa819bc2c4/coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3", size = 239403, upload-time = "2025-06-13T13:00:37.399Z" },
+ { url = "https://files.pythonhosted.org/packages/37/3b/a2c27736035156b0a7c20683afe7df498480c0dfdf503b8c878a21b6d7fb/coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3", size = 240548, upload-time = "2025-06-13T13:00:39.647Z" },
+ { url = "https://files.pythonhosted.org/packages/98/f5/13d5fc074c3c0e0dc80422d9535814abf190f1254d7c3451590dc4f8b18c/coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5", size = 240459, upload-time = "2025-06-13T13:00:40.934Z" },
+ { url = "https://files.pythonhosted.org/packages/36/24/24b9676ea06102df824c4a56ffd13dc9da7904478db519efa877d16527d5/coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187", size = 239128, upload-time = "2025-06-13T13:00:42.343Z" },
+ { url = "https://files.pythonhosted.org/packages/be/05/242b7a7d491b369ac5fee7908a6e5ba42b3030450f3ad62c645b40c23e0e/coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce", size = 239402, upload-time = "2025-06-13T13:00:43.634Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e0/4de7f87192fa65c9c8fbaeb75507e124f82396b71de1797da5602898be32/coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70", size = 214518, upload-time = "2025-06-13T13:00:45.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ab/5e4e2fe458907d2a65fab62c773671cfc5ac704f1e7a9ddd91996f66e3c2/coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe", size = 215436, upload-time = "2025-06-13T13:00:47.245Z" },
+ { url = "https://files.pythonhosted.org/packages/60/34/fa69372a07d0903a78ac103422ad34db72281c9fc625eba94ac1185da66f/coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582", size = 212146, upload-time = "2025-06-13T13:00:48.496Z" },
+ { url = "https://files.pythonhosted.org/packages/27/f0/da1894915d2767f093f081c42afeba18e760f12fdd7a2f4acbe00564d767/coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86", size = 212536, upload-time = "2025-06-13T13:00:51.535Z" },
+ { url = "https://files.pythonhosted.org/packages/10/d5/3fc33b06e41e390f88eef111226a24e4504d216ab8e5d1a7089aa5a3c87a/coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed", size = 245092, upload-time = "2025-06-13T13:00:52.883Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/39/7aa901c14977aba637b78e95800edf77f29f5a380d29768c5b66f258305b/coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d", size = 242806, upload-time = "2025-06-13T13:00:54.571Z" },
+ { url = "https://files.pythonhosted.org/packages/43/fc/30e5cfeaf560b1fc1989227adedc11019ce4bb7cce59d65db34fe0c2d963/coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338", size = 244610, upload-time = "2025-06-13T13:00:56.932Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/15/cca62b13f39650bc87b2b92bb03bce7f0e79dd0bf2c7529e9fc7393e4d60/coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875", size = 244257, upload-time = "2025-06-13T13:00:58.545Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/1a/c0f2abe92c29e1464dbd0ff9d56cb6c88ae2b9e21becdb38bea31fcb2f6c/coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250", size = 242309, upload-time = "2025-06-13T13:00:59.836Z" },
+ { url = "https://files.pythonhosted.org/packages/57/8d/c6fd70848bd9bf88fa90df2af5636589a8126d2170f3aade21ed53f2b67a/coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c", size = 242898, upload-time = "2025-06-13T13:01:02.506Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/9e/6ca46c7bff4675f09a66fe2797cd1ad6a24f14c9c7c3b3ebe0470a6e30b8/coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32", size = 214561, upload-time = "2025-06-13T13:01:04.012Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/30/166978c6302010742dabcdc425fa0f938fa5a800908e39aff37a7a876a13/coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125", size = 215493, upload-time = "2025-06-13T13:01:05.702Z" },
+ { url = "https://files.pythonhosted.org/packages/60/07/a6d2342cd80a5be9f0eeab115bc5ebb3917b4a64c2953534273cf9bc7ae6/coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e", size = 213869, upload-time = "2025-06-13T13:01:09.345Z" },
+ { url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336, upload-time = "2025-06-13T13:01:10.909Z" },
+ { url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571, upload-time = "2025-06-13T13:01:12.518Z" },
+ { url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377, upload-time = "2025-06-13T13:01:14.87Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394, upload-time = "2025-06-13T13:01:16.23Z" },
+ { url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586, upload-time = "2025-06-13T13:01:17.532Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396, upload-time = "2025-06-13T13:01:19.164Z" },
+ { url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577, upload-time = "2025-06-13T13:01:22.433Z" },
+ { url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809, upload-time = "2025-06-13T13:01:24.143Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724, upload-time = "2025-06-13T13:01:25.435Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535, upload-time = "2025-06-13T13:01:27.861Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904, upload-time = "2025-06-13T13:01:29.202Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/a7/a027970c991ca90f24e968999f7d509332daf6b8c3533d68633930aaebac/coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631", size = 212358, upload-time = "2025-06-13T13:01:30.909Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/48/6aaed3651ae83b231556750280682528fea8ac7f1232834573472d83e459/coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f", size = 212620, upload-time = "2025-06-13T13:01:32.256Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/2a/f4b613f3b44d8b9f144847c89151992b2b6b79cbc506dee89ad0c35f209d/coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd", size = 245788, upload-time = "2025-06-13T13:01:33.948Z" },
+ { url = "https://files.pythonhosted.org/packages/04/d2/de4fdc03af5e4e035ef420ed26a703c6ad3d7a07aff2e959eb84e3b19ca8/coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86", size = 243001, upload-time = "2025-06-13T13:01:35.285Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/e8/eed18aa5583b0423ab7f04e34659e51101135c41cd1dcb33ac1d7013a6d6/coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43", size = 244985, upload-time = "2025-06-13T13:01:36.712Z" },
+ { url = "https://files.pythonhosted.org/packages/17/f8/ae9e5cce8885728c934eaa58ebfa8281d488ef2afa81c3dbc8ee9e6d80db/coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1", size = 245152, upload-time = "2025-06-13T13:01:39.303Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/c8/272c01ae792bb3af9b30fac14d71d63371db227980682836ec388e2c57c0/coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751", size = 243123, upload-time = "2025-06-13T13:01:40.727Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d0/2819a1e3086143c094ab446e3bdf07138527a7b88cb235c488e78150ba7a/coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67", size = 244506, upload-time = "2025-06-13T13:01:42.184Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/4e/9f6117b89152df7b6112f65c7a4ed1f2f5ec8e60c4be8f351d91e7acc848/coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643", size = 214766, upload-time = "2025-06-13T13:01:44.482Z" },
+ { url = "https://files.pythonhosted.org/packages/27/0f/4b59f7c93b52c2c4ce7387c5a4e135e49891bb3b7408dcc98fe44033bbe0/coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a", size = 215568, upload-time = "2025-06-13T13:01:45.772Z" },
+ { url = "https://files.pythonhosted.org/packages/09/1e/9679826336f8c67b9c39a359352882b24a8a7aee48d4c9cad08d38d7510f/coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d", size = 213939, upload-time = "2025-06-13T13:01:47.087Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/5b/5c6b4e7a407359a2e3b27bf9c8a7b658127975def62077d441b93a30dbe8/coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0", size = 213079, upload-time = "2025-06-13T13:01:48.554Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/22/1e2e07279fd2fd97ae26c01cc2186e2258850e9ec125ae87184225662e89/coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d", size = 213299, upload-time = "2025-06-13T13:01:49.997Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c0/4c5125a4b69d66b8c85986d3321520f628756cf524af810baab0790c7647/coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f", size = 256535, upload-time = "2025-06-13T13:01:51.314Z" },
+ { url = "https://files.pythonhosted.org/packages/81/8b/e36a04889dda9960be4263e95e777e7b46f1bb4fc32202612c130a20c4da/coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029", size = 252756, upload-time = "2025-06-13T13:01:54.403Z" },
+ { url = "https://files.pythonhosted.org/packages/98/82/be04eff8083a09a4622ecd0e1f31a2c563dbea3ed848069e7b0445043a70/coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece", size = 254912, upload-time = "2025-06-13T13:01:56.769Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/25/c26610a2c7f018508a5ab958e5b3202d900422cf7cdca7670b6b8ca4e8df/coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683", size = 256144, upload-time = "2025-06-13T13:01:58.19Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/8b/fb9425c4684066c79e863f1e6e7ecebb49e3a64d9f7f7860ef1688c56f4a/coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f", size = 254257, upload-time = "2025-06-13T13:01:59.645Z" },
+ { url = "https://files.pythonhosted.org/packages/93/df/27b882f54157fc1131e0e215b0da3b8d608d9b8ef79a045280118a8f98fe/coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10", size = 255094, upload-time = "2025-06-13T13:02:01.37Z" },
+ { url = "https://files.pythonhosted.org/packages/41/5f/cad1c3dbed8b3ee9e16fa832afe365b4e3eeab1fb6edb65ebbf745eabc92/coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363", size = 215437, upload-time = "2025-06-13T13:02:02.905Z" },
+ { url = "https://files.pythonhosted.org/packages/99/4d/fad293bf081c0e43331ca745ff63673badc20afea2104b431cdd8c278b4c/coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7", size = 216605, upload-time = "2025-06-13T13:02:05.638Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/56/4ee027d5965fc7fc126d7ec1187529cc30cc7d740846e1ecb5e92d31b224/coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c", size = 214392, upload-time = "2025-06-13T13:02:07.642Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/d6/c41dd9b02bf16ec001aaf1cbef665537606899a3db1094e78f5ae17540ca/coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951", size = 212029, upload-time = "2025-06-13T13:02:09.058Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/c0/40420d81d731f84c3916dcdf0506b3e6c6570817bff2576b83f780914ae6/coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58", size = 212407, upload-time = "2025-06-13T13:02:11.151Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/87/f0db7d62d0e09f14d6d2f6ae8c7274a2f09edf74895a34b412a0601e375a/coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71", size = 241160, upload-time = "2025-06-13T13:02:12.864Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/b7/3337c064f058a5d7696c4867159651a5b5fb01a5202bcf37362f0c51400e/coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55", size = 239027, upload-time = "2025-06-13T13:02:14.294Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/a9/5898a283f66d1bd413c32c2e0e05408196fd4f37e206e2b06c6e0c626e0e/coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b", size = 240145, upload-time = "2025-06-13T13:02:15.745Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/33/d96e3350078a3c423c549cb5b2ba970de24c5257954d3e4066e2b2152d30/coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7", size = 239871, upload-time = "2025-06-13T13:02:17.344Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/6e/6fb946072455f71a820cac144d49d11747a0f1a21038060a68d2d0200499/coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385", size = 238122, upload-time = "2025-06-13T13:02:18.849Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/5c/bc43f25c8586840ce25a796a8111acf6a2b5f0909ba89a10d41ccff3920d/coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed", size = 239058, upload-time = "2025-06-13T13:02:21.423Z" },
+ { url = "https://files.pythonhosted.org/packages/11/d8/ce2007418dd7fd00ff8c8b898bb150bb4bac2d6a86df05d7b88a07ff595f/coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d", size = 214532, upload-time = "2025-06-13T13:02:22.857Z" },
+ { url = "https://files.pythonhosted.org/packages/20/21/334e76fa246e92e6d69cab217f7c8a70ae0cc8f01438bd0544103f29528e/coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244", size = 215439, upload-time = "2025-06-13T13:02:24.268Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/e5/c723545c3fd3204ebde3b4cc4b927dce709d3b6dc577754bb57f63ca4a4a/coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514", size = 204009, upload-time = "2025-06-13T13:02:25.787Z" },
+ { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000, upload-time = "2025-06-13T13:02:27.173Z" },
+]
+
+[package.optional-dependencies]
+toml = [
+ { name = "tomli", marker = "python_full_version <= '3.11'" },
+]
+
+[[package]]
+name = "cycler"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
+]
+
+[[package]]
+name = "dill"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" },
+]
+
+[[package]]
+name = "distlib"
+version = "0.3.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" },
+]
+
+[[package]]
+name = "docutils"
+version = "0.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
+]
+
+[[package]]
+name = "filelock"
+version = "3.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
+]
+
+[[package]]
+name = "fonttools"
+version = "4.58.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026, upload-time = "2025-06-13T17:25:15.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/86/d22c24caa574449b56e994ed1a96d23b23af85557fb62a92df96439d3f6c/fonttools-4.58.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:834542f13fee7625ad753b2db035edb674b07522fcbdd0ed9e9a9e2a1034467f", size = 2748349, upload-time = "2025-06-13T17:23:49.179Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/b8/384aca93856def00e7de30341f1e27f439694857d82c35d74a809c705ed0/fonttools-4.58.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e6c61ce330142525296170cd65666e46121fc0d44383cbbcfa39cf8f58383df", size = 2318565, upload-time = "2025-06-13T17:23:52.144Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/f2/273edfdc8d9db89ecfbbf659bd894f7e07b6d53448b19837a4bdba148d17/fonttools-4.58.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9c75f8faa29579c0fbf29b56ae6a3660c6c025f3b671803cb6a9caa7e4e3a98", size = 4838855, upload-time = "2025-06-13T17:23:54.039Z" },
+ { url = "https://files.pythonhosted.org/packages/13/fa/403703548c093c30b52ab37e109b369558afa221130e67f06bef7513f28a/fonttools-4.58.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88dedcedbd5549e35b2ea3db3de02579c27e62e51af56779c021e7b33caadd0e", size = 4767637, upload-time = "2025-06-13T17:23:56.17Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/a8/3380e1e0bff6defb0f81c9abf274a5b4a0f30bc8cab4fd4e346c6f923b4c/fonttools-4.58.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae80a895adab43586f4da1521d58fd4f4377cef322ee0cc205abcefa3a5effc3", size = 4819397, upload-time = "2025-06-13T17:23:58.263Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/1b/99e47eb17a8ca51d808622a4658584fa8f340857438a4e9d7ac326d4a041/fonttools-4.58.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d3acc7f0d151da116e87a182aefb569cf0a3c8e0fd4c9cd0a7c1e7d3e7adb26", size = 4926641, upload-time = "2025-06-13T17:24:00.368Z" },
+ { url = "https://files.pythonhosted.org/packages/31/75/415254408f038e35b36c8525fc31feb8561f98445688dd2267c23eafd7a2/fonttools-4.58.4-cp310-cp310-win32.whl", hash = "sha256:1244f69686008e7e8d2581d9f37eef330a73fee3843f1107993eb82c9d306577", size = 2201917, upload-time = "2025-06-13T17:24:02.587Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/69/f019a15ed2946317c5318e1bcc8876f8a54a313848604ad1d4cfc4c07916/fonttools-4.58.4-cp310-cp310-win_amd64.whl", hash = "sha256:2a66c0af8a01eb2b78645af60f3b787de5fe5eb1fd8348163715b80bdbfbde1f", size = 2246327, upload-time = "2025-06-13T17:24:04.087Z" },
+ { url = "https://files.pythonhosted.org/packages/17/7b/cc6e9bb41bab223bd2dc70ba0b21386b85f604e27f4c3206b4205085a2ab/fonttools-4.58.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3841991c9ee2dc0562eb7f23d333d34ce81e8e27c903846f0487da21e0028eb", size = 2768901, upload-time = "2025-06-13T17:24:05.901Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/15/98d75df9f2b4e7605f3260359ad6e18e027c11fa549f74fce567270ac891/fonttools-4.58.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c98f91b6a9604e7ffb5ece6ea346fa617f967c2c0944228801246ed56084664", size = 2328696, upload-time = "2025-06-13T17:24:09.18Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/c8/dc92b80f5452c9c40164e01b3f78f04b835a00e673bd9355ca257008ff61/fonttools-4.58.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab9f891eb687ddf6a4e5f82901e00f992e18012ca97ab7acd15f13632acd14c1", size = 5018830, upload-time = "2025-06-13T17:24:11.282Z" },
+ { url = "https://files.pythonhosted.org/packages/19/48/8322cf177680505d6b0b6062e204f01860cb573466a88077a9b795cb70e8/fonttools-4.58.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:891c5771e8f0094b7c0dc90eda8fc75e72930b32581418f2c285a9feedfd9a68", size = 4960922, upload-time = "2025-06-13T17:24:14.9Z" },
+ { url = "https://files.pythonhosted.org/packages/14/e0/2aff149ed7eb0916de36da513d473c6fff574a7146891ce42de914899395/fonttools-4.58.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:43ba4d9646045c375d22e3473b7d82b18b31ee2ac715cd94220ffab7bc2d5c1d", size = 4997135, upload-time = "2025-06-13T17:24:16.959Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/6f/4d9829b29a64a2e63a121cb11ecb1b6a9524086eef3e35470949837a1692/fonttools-4.58.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33d19f16e6d2ffd6669bda574a6589941f6c99a8d5cfb9f464038244c71555de", size = 5108701, upload-time = "2025-06-13T17:24:18.849Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/1e/2d656ddd1b0cd0d222f44b2d008052c2689e66b702b9af1cd8903ddce319/fonttools-4.58.4-cp311-cp311-win32.whl", hash = "sha256:b59e5109b907da19dc9df1287454821a34a75f2632a491dd406e46ff432c2a24", size = 2200177, upload-time = "2025-06-13T17:24:20.823Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/83/ba71ad053fddf4157cb0697c8da8eff6718d059f2a22986fa5f312b49c92/fonttools-4.58.4-cp311-cp311-win_amd64.whl", hash = "sha256:3d471a5b567a0d1648f2e148c9a8bcf00d9ac76eb89e976d9976582044cc2509", size = 2247892, upload-time = "2025-06-13T17:24:22.927Z" },
+ { url = "https://files.pythonhosted.org/packages/04/3c/1d1792bfe91ef46f22a3d23b4deb514c325e73c17d4f196b385b5e2faf1c/fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6", size = 2754082, upload-time = "2025-06-13T17:24:24.862Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/1f/2b261689c901a1c3bc57a6690b0b9fc21a9a93a8b0c83aae911d3149f34e/fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d", size = 2321677, upload-time = "2025-06-13T17:24:26.815Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/6b/4607add1755a1e6581ae1fc0c9a640648e0d9cdd6591cc2d581c2e07b8c3/fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f", size = 4896354, upload-time = "2025-06-13T17:24:28.428Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/95/34b4f483643d0cb11a1f830b72c03fdd18dbd3792d77a2eb2e130a96fada/fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa", size = 4941633, upload-time = "2025-06-13T17:24:30.568Z" },
+ { url = "https://files.pythonhosted.org/packages/81/ac/9bafbdb7694059c960de523e643fa5a61dd2f698f3f72c0ca18ae99257c7/fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e", size = 4886170, upload-time = "2025-06-13T17:24:32.724Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851, upload-time = "2025-06-13T17:24:35.034Z" },
+ { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428, upload-time = "2025-06-13T17:24:36.996Z" },
+ { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649, upload-time = "2025-06-13T17:24:38.985Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197, upload-time = "2025-06-13T17:24:40.645Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272, upload-time = "2025-06-13T17:24:43.428Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184, upload-time = "2025-06-13T17:24:45.527Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445, upload-time = "2025-06-13T17:24:47.647Z" },
+ { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800, upload-time = "2025-06-13T17:24:49.766Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259, upload-time = "2025-06-13T17:24:51.754Z" },
+ { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824, upload-time = "2025-06-13T17:24:54.324Z" },
+ { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382, upload-time = "2025-06-13T17:24:56.291Z" },
+ { url = "https://files.pythonhosted.org/packages/45/20/787d70ba4cb831706fa587c56ee472a88ebc28752be660f4b58e598af6fc/fonttools-4.58.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca773fe7812e4e1197ee4e63b9691e89650ab55f679e12ac86052d2fe0d152cd", size = 2754537, upload-time = "2025-06-13T17:24:57.851Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a5/ccb7ef1b8ab4bbf48f7753b6df512b61e73af82cd27aa486a03d6afb8635/fonttools-4.58.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e31289101221910f44245472e02b1a2f7d671c6d06a45c07b354ecb25829ad92", size = 2321715, upload-time = "2025-06-13T17:24:59.863Z" },
+ { url = "https://files.pythonhosted.org/packages/20/5c/b361a7eae95950afaadb7049f55b214b619cb5368086cb3253726fe0c478/fonttools-4.58.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c9e3c01475bb9602cb617f69f02c4ba7ab7784d93f0b0d685e84286f4c1a10", size = 4819004, upload-time = "2025-06-13T17:25:01.591Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/3006fbb1f57704cd60af82fb8127788cfb102f12d39c39fb5996af595cf3/fonttools-4.58.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e00a826f2bc745a010341ac102082fe5e3fb9f0861b90ed9ff32277598813711", size = 4749072, upload-time = "2025-06-13T17:25:03.334Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/42/ea79e2c3d5e4441e4508d6456b268a7de275452f3dba3a13fc9d73f3e03d/fonttools-4.58.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc75e72e9d2a4ad0935c59713bd38679d51c6fefab1eadde80e3ed4c2a11ea84", size = 4802023, upload-time = "2025-06-13T17:25:05.486Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/70/90a196f57faa2bcd1485710c6d08eedceca500cdf2166640b3478e72072c/fonttools-4.58.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f57a795e540059ce3de68508acfaaf177899b39c36ef0a2833b2308db98c71f1", size = 4911103, upload-time = "2025-06-13T17:25:07.505Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/3f/a7d38e606e98701dbcb6198406c8b554a77ed06c5b21e425251813fd3775/fonttools-4.58.4-cp39-cp39-win32.whl", hash = "sha256:a7d04f64c88b48ede655abcf76f2b2952f04933567884d99be7c89e0a4495131", size = 1471393, upload-time = "2025-06-13T17:25:09.587Z" },
+ { url = "https://files.pythonhosted.org/packages/37/6e/08158deaebeb5b0c7a0fb251ca6827defb5f5159958a23ba427e0b677e95/fonttools-4.58.4-cp39-cp39-win_amd64.whl", hash = "sha256:5a8bc5dfd425c89b1c38380bc138787b0a830f761b82b37139aa080915503b69", size = 1515901, upload-time = "2025-06-13T17:25:11.336Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660, upload-time = "2025-06-13T17:25:13.321Z" },
+]
+
+[[package]]
+name = "frozenlist"
+version = "1.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" },
+ { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" },
+ { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" },
+ { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" },
+ { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" },
+ { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" },
+ { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" },
+ { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" },
+ { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" },
+ { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" },
+ { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" },
+ { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" },
+ { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" },
+ { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" },
+ { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" },
+ { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" },
+ { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" },
+ { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" },
+ { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" },
+ { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" },
+ { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" },
+ { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" },
+ { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" },
+ { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" },
+ { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" },
+ { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" },
+ { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" },
+ { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" },
+ { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" },
+ { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" },
+ { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" },
+ { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" },
+ { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" },
+ { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" },
+ { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" },
+ { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" },
+ { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" },
+ { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" },
+ { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/b1/ee59496f51cd244039330015d60f13ce5a54a0f2bd8d79e4a4a375ab7469/frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630", size = 82434, upload-time = "2025-06-09T23:02:05.195Z" },
+ { url = "https://files.pythonhosted.org/packages/75/e1/d518391ce36a6279b3fa5bc14327dde80bcb646bb50d059c6ca0756b8d05/frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71", size = 48232, upload-time = "2025-06-09T23:02:07.728Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/8d/a0d04f28b6e821a9685c22e67b5fb798a5a7b68752f104bfbc2dccf080c4/frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44", size = 47186, upload-time = "2025-06-09T23:02:09.243Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3a/a5334c0535c8b7c78eeabda1579179e44fe3d644e07118e59a2276dedaf1/frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878", size = 226617, upload-time = "2025-06-09T23:02:10.949Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/67/8258d971f519dc3f278c55069a775096cda6610a267b53f6248152b72b2f/frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb", size = 224179, upload-time = "2025-06-09T23:02:12.603Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/89/8225905bf889b97c6d935dd3aeb45668461e59d415cb019619383a8a7c3b/frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6", size = 235783, upload-time = "2025-06-09T23:02:14.678Z" },
+ { url = "https://files.pythonhosted.org/packages/54/6e/ef52375aa93d4bc510d061df06205fa6dcfd94cd631dd22956b09128f0d4/frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35", size = 229210, upload-time = "2025-06-09T23:02:16.313Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/55/62c87d1a6547bfbcd645df10432c129100c5bd0fd92a384de6e3378b07c1/frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87", size = 215994, upload-time = "2025-06-09T23:02:17.9Z" },
+ { url = "https://files.pythonhosted.org/packages/45/d2/263fea1f658b8ad648c7d94d18a87bca7e8c67bd6a1bbf5445b1bd5b158c/frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677", size = 225122, upload-time = "2025-06-09T23:02:19.479Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/22/7145e35d12fb368d92124f679bea87309495e2e9ddf14c6533990cb69218/frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938", size = 224019, upload-time = "2025-06-09T23:02:20.969Z" },
+ { url = "https://files.pythonhosted.org/packages/44/1e/7dae8c54301beb87bcafc6144b9a103bfd2c8f38078c7902984c9a0c4e5b/frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2", size = 239925, upload-time = "2025-06-09T23:02:22.466Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/1e/99c93e54aa382e949a98976a73b9b20c3aae6d9d893f31bbe4991f64e3a8/frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319", size = 220881, upload-time = "2025-06-09T23:02:24.521Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/9c/ca5105fa7fb5abdfa8837581be790447ae051da75d32f25c8f81082ffc45/frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890", size = 234046, upload-time = "2025-06-09T23:02:26.206Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/4d/e99014756093b4ddbb67fb8f0df11fe7a415760d69ace98e2ac6d5d43402/frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd", size = 235756, upload-time = "2025-06-09T23:02:27.79Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/72/a19a40bcdaa28a51add2aaa3a1a294ec357f36f27bd836a012e070c5e8a5/frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb", size = 222894, upload-time = "2025-06-09T23:02:29.848Z" },
+ { url = "https://files.pythonhosted.org/packages/08/49/0042469993e023a758af81db68c76907cd29e847d772334d4d201cbe9a42/frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e", size = 39848, upload-time = "2025-06-09T23:02:31.413Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/45/827d86ee475c877f5f766fbc23fb6acb6fada9e52f1c9720e2ba3eae32da/frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63", size = 44102, upload-time = "2025-06-09T23:02:32.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" },
+]
+
+[[package]]
+name = "hypothesis"
+version = "6.135.16"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "sortedcontainers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3f/91/c8d9af23d6a2a0feb7490ab10a3d78e6ac35ae75887c7fe442d14ad6d6f9/hypothesis-6.135.16.tar.gz", hash = "sha256:6131ea0b698e69bad62aae915988b8d00a6ac974351d0830db74c5fffc68c418", size = 452900, upload-time = "2025-06-26T03:36:33.624Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/73/e1/196cd69190ba95f2538d577417ac6383a10cb1fabc06917b54e1e437dcaa/hypothesis-6.135.16-py3-none-any.whl", hash = "sha256:0a64697ef0afa4532535209a9bcd99919d59093ff894622e8a001fb773b59d8a", size = 519164, upload-time = "2025-06-26T03:36:30.12Z" },
+]
+
+[[package]]
+name = "identify"
+version = "2.6.12"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
+]
+
+[[package]]
+name = "importlib-resources"
+version = "6.5.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+]
+
+[[package]]
+name = "isort"
+version = "6.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.7"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286, upload-time = "2024-09-04T09:39:44.302Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440, upload-time = "2024-09-04T09:03:44.9Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758, upload-time = "2024-09-04T09:03:46.582Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311, upload-time = "2024-09-04T09:03:47.973Z" },
+ { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109, upload-time = "2024-09-04T09:03:49.281Z" },
+ { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814, upload-time = "2024-09-04T09:03:51.444Z" },
+ { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881, upload-time = "2024-09-04T09:03:53.357Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972, upload-time = "2024-09-04T09:03:55.082Z" },
+ { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787, upload-time = "2024-09-04T09:03:56.588Z" },
+ { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212, upload-time = "2024-09-04T09:03:58.557Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399, upload-time = "2024-09-04T09:04:00.178Z" },
+ { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688, upload-time = "2024-09-04T09:04:02.216Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493, upload-time = "2024-09-04T09:04:04.571Z" },
+ { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191, upload-time = "2024-09-04T09:04:05.969Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644, upload-time = "2024-09-04T09:04:07.408Z" },
+ { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877, upload-time = "2024-09-04T09:04:08.869Z" },
+ { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347, upload-time = "2024-09-04T09:04:10.106Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442, upload-time = "2024-09-04T09:04:11.432Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762, upload-time = "2024-09-04T09:04:12.468Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319, upload-time = "2024-09-04T09:04:13.635Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260, upload-time = "2024-09-04T09:04:14.878Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589, upload-time = "2024-09-04T09:04:16.514Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080, upload-time = "2024-09-04T09:04:18.322Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049, upload-time = "2024-09-04T09:04:20.266Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376, upload-time = "2024-09-04T09:04:22.419Z" },
+ { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231, upload-time = "2024-09-04T09:04:24.526Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634, upload-time = "2024-09-04T09:04:25.899Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024, upload-time = "2024-09-04T09:04:28.523Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484, upload-time = "2024-09-04T09:04:30.547Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078, upload-time = "2024-09-04T09:04:33.218Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645, upload-time = "2024-09-04T09:04:34.371Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022, upload-time = "2024-09-04T09:04:35.786Z" },
+ { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536, upload-time = "2024-09-04T09:04:37.525Z" },
+ { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808, upload-time = "2024-09-04T09:04:38.637Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531, upload-time = "2024-09-04T09:04:39.694Z" },
+ { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894, upload-time = "2024-09-04T09:04:41.6Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296, upload-time = "2024-09-04T09:04:42.886Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450, upload-time = "2024-09-04T09:04:46.284Z" },
+ { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168, upload-time = "2024-09-04T09:04:47.91Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308, upload-time = "2024-09-04T09:04:49.465Z" },
+ { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186, upload-time = "2024-09-04T09:04:50.949Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877, upload-time = "2024-09-04T09:04:52.388Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204, upload-time = "2024-09-04T09:04:54.385Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461, upload-time = "2024-09-04T09:04:56.307Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358, upload-time = "2024-09-04T09:04:57.922Z" },
+ { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119, upload-time = "2024-09-04T09:04:59.332Z" },
+ { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367, upload-time = "2024-09-04T09:05:00.804Z" },
+ { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884, upload-time = "2024-09-04T09:05:01.924Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528, upload-time = "2024-09-04T09:05:02.983Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913, upload-time = "2024-09-04T09:05:04.072Z" },
+ { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627, upload-time = "2024-09-04T09:05:05.119Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888, upload-time = "2024-09-04T09:05:06.191Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145, upload-time = "2024-09-04T09:05:07.919Z" },
+ { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448, upload-time = "2024-09-04T09:05:10.01Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750, upload-time = "2024-09-04T09:05:11.598Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175, upload-time = "2024-09-04T09:05:13.22Z" },
+ { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963, upload-time = "2024-09-04T09:05:15.925Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220, upload-time = "2024-09-04T09:05:17.434Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463, upload-time = "2024-09-04T09:05:18.997Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842, upload-time = "2024-09-04T09:05:21.299Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635, upload-time = "2024-09-04T09:05:23.588Z" },
+ { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556, upload-time = "2024-09-04T09:05:25.907Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364, upload-time = "2024-09-04T09:05:27.184Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887, upload-time = "2024-09-04T09:05:28.372Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530, upload-time = "2024-09-04T09:05:30.225Z" },
+ { url = "https://files.pythonhosted.org/packages/11/88/37ea0ea64512997b13d69772db8dcdc3bfca5442cda3a5e4bb943652ee3e/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd", size = 122449, upload-time = "2024-09-04T09:05:55.311Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/45/5a5c46078362cb3882dcacad687c503089263c017ca1241e0483857791eb/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583", size = 65757, upload-time = "2024-09-04T09:05:56.906Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/be/a6ae58978772f685d48dd2e84460937761c53c4bbd84e42b0336473d9775/kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417", size = 64312, upload-time = "2024-09-04T09:05:58.384Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/04/18ef6f452d311e1e1eb180c9bf5589187fa1f042db877e6fe443ef10099c/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904", size = 1626966, upload-time = "2024-09-04T09:05:59.855Z" },
+ { url = "https://files.pythonhosted.org/packages/21/b1/40655f6c3fa11ce740e8a964fa8e4c0479c87d6a7944b95af799c7a55dfe/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a", size = 1607044, upload-time = "2024-09-04T09:06:02.16Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/93/af67dbcfb9b3323bbd2c2db1385a7139d8f77630e4a37bb945b57188eb2d/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8", size = 1391879, upload-time = "2024-09-04T09:06:03.908Z" },
+ { url = "https://files.pythonhosted.org/packages/40/6f/d60770ef98e77b365d96061d090c0cd9e23418121c55fff188fa4bdf0b54/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2", size = 1504751, upload-time = "2024-09-04T09:06:05.58Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/3a/5f38667d313e983c432f3fcd86932177519ed8790c724e07d77d1de0188a/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88", size = 1436990, upload-time = "2024-09-04T09:06:08.126Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/3b/1520301a47326e6a6043b502647e42892be33b3f051e9791cc8bb43f1a32/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde", size = 2191122, upload-time = "2024-09-04T09:06:10.345Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/c4/eb52da300c166239a2233f1f9c4a1b767dfab98fae27681bfb7ea4873cb6/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c", size = 2338126, upload-time = "2024-09-04T09:06:12.321Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/cb/42b92fd5eadd708dd9107c089e817945500685f3437ce1fd387efebc6d6e/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2", size = 2298313, upload-time = "2024-09-04T09:06:14.562Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/eb/be25aa791fe5fc75a8b1e0c965e00f942496bc04635c9aae8035f6b76dcd/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb", size = 2437784, upload-time = "2024-09-04T09:06:16.767Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/22/30a66be7f3368d76ff95689e1c2e28d382383952964ab15330a15d8bfd03/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327", size = 2253988, upload-time = "2024-09-04T09:06:18.705Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980, upload-time = "2024-09-04T09:06:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847, upload-time = "2024-09-04T09:06:21.407Z" },
+ { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494, upload-time = "2024-09-04T09:06:22.648Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491, upload-time = "2024-09-04T09:06:24.188Z" },
+ { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648, upload-time = "2024-09-04T09:06:25.559Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257, upload-time = "2024-09-04T09:06:27.038Z" },
+ { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906, upload-time = "2024-09-04T09:06:28.48Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951, upload-time = "2024-09-04T09:06:29.966Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715, upload-time = "2024-09-04T09:06:31.489Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/df/ce37d9b26f07ab90880923c94d12a6ff4d27447096b4c849bfc4339ccfdf/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39", size = 58666, upload-time = "2024-09-04T09:06:43.756Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/d3/e4b04f43bc629ac8e186b77b2b1a251cdfa5b7610fa189dc0db622672ce6/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e", size = 57088, upload-time = "2024-09-04T09:06:45.406Z" },
+ { url = "https://files.pythonhosted.org/packages/30/1c/752df58e2d339e670a535514d2db4fe8c842ce459776b8080fbe08ebb98e/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608", size = 84321, upload-time = "2024-09-04T09:06:47.557Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/f8/fe6484e847bc6e238ec9f9828089fb2c0bb53f2f5f3a79351fde5b565e4f/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674", size = 80776, upload-time = "2024-09-04T09:06:49.235Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/57/d7163c0379f250ef763aba85330a19feefb5ce6cb541ade853aaba881524/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225", size = 79984, upload-time = "2024-09-04T09:06:51.336Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811, upload-time = "2024-09-04T09:06:53.078Z" },
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.8"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623, upload-time = "2024-12-24T18:28:17.687Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720, upload-time = "2024-12-24T18:28:19.158Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413, upload-time = "2024-12-24T18:28:20.064Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826, upload-time = "2024-12-24T18:28:21.203Z" },
+ { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231, upload-time = "2024-12-24T18:28:23.851Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938, upload-time = "2024-12-24T18:28:26.687Z" },
+ { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799, upload-time = "2024-12-24T18:28:30.538Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362, upload-time = "2024-12-24T18:28:32.943Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695, upload-time = "2024-12-24T18:28:35.641Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802, upload-time = "2024-12-24T18:28:38.357Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646, upload-time = "2024-12-24T18:28:40.941Z" },
+ { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260, upload-time = "2024-12-24T18:28:42.273Z" },
+ { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633, upload-time = "2024-12-24T18:28:44.87Z" },
+ { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885, upload-time = "2024-12-24T18:28:47.346Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175, upload-time = "2024-12-24T18:28:49.651Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload-time = "2024-12-24T18:28:51.826Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload-time = "2024-12-24T18:28:54.256Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload-time = "2024-12-24T18:28:55.184Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload-time = "2024-12-24T18:28:57.493Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload-time = "2024-12-24T18:29:00.077Z" },
+ { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload-time = "2024-12-24T18:29:01.401Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload-time = "2024-12-24T18:29:02.685Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload-time = "2024-12-24T18:29:04.113Z" },
+ { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload-time = "2024-12-24T18:29:05.488Z" },
+ { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload-time = "2024-12-24T18:29:06.79Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload-time = "2024-12-24T18:29:08.24Z" },
+ { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload-time = "2024-12-24T18:29:09.653Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload-time = "2024-12-24T18:29:12.644Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload-time = "2024-12-24T18:29:14.089Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload-time = "2024-12-24T18:29:15.892Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" },
+ { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" },
+ { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" },
+ { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" },
+ { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" },
+ { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" },
+ { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" },
+ { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" },
+ { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" },
+ { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" },
+ { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" },
+ { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" },
+ { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" },
+ { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" },
+ { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" },
+ { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403, upload-time = "2024-12-24T18:30:41.372Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657, upload-time = "2024-12-24T18:30:42.392Z" },
+ { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948, upload-time = "2024-12-24T18:30:44.703Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186, upload-time = "2024-12-24T18:30:45.654Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279, upload-time = "2024-12-24T18:30:47.951Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload-time = "2024-12-24T18:30:48.903Z" },
+]
+
+[[package]]
+name = "loguru"
+version = "0.7.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "win32-setctime", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
+]
+
+[[package]]
+name = "mando"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/35/24/cd70d5ae6d35962be752feccb7dca80b5e0c2d450e995b16abd6275f3296/mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500", size = 37868, upload-time = "2022-02-24T08:12:27.316Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a", size = 28149, upload-time = "2022-02-24T08:12:25.24Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" },
+ { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" },
+ { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" },
+ { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" },
+ { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" },
+ { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" },
+ { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" },
+ { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" },
+ { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" },
+ { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" },
+ { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
+ { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
+ { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
+ { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
+ { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
+ { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
+ { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
+ { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" },
+ { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" },
+ { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" },
+ { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" },
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.9.4"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "cycler", marker = "python_full_version < '3.10'" },
+ { name = "fonttools", marker = "python_full_version < '3.10'" },
+ { name = "importlib-resources", marker = "python_full_version < '3.10'" },
+ { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pillow", marker = "python_full_version < '3.10'" },
+ { name = "pyparsing", marker = "python_full_version < '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529, upload-time = "2024-12-13T05:56:34.184Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/94/27d2e2c30d54b56c7b764acc1874a909e34d1965a427fc7092bb6a588b63/matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50", size = 7885089, upload-time = "2024-12-13T05:54:24.224Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/25/828273307e40a68eb8e9df832b6b2aaad075864fdc1de4b1b81e40b09e48/matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff", size = 7770600, upload-time = "2024-12-13T05:54:27.214Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/65/f841a422ec994da5123368d76b126acf4fc02ea7459b6e37c4891b555b83/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26", size = 8200138, upload-time = "2024-12-13T05:54:29.497Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/272aca07a38804d93b6050813de41ca7ab0e29ba7a9dd098e12037c919a9/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50", size = 8312711, upload-time = "2024-12-13T05:54:34.396Z" },
+ { url = "https://files.pythonhosted.org/packages/98/37/f13e23b233c526b7e27ad61be0a771894a079e0f7494a10d8d81557e0e9a/matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5", size = 9090622, upload-time = "2024-12-13T05:54:36.808Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211, upload-time = "2024-12-13T05:54:40.596Z" },
+ { url = "https://files.pythonhosted.org/packages/74/4b/65be7959a8fa118a3929b49a842de5b78bb55475236fcf64f3e308ff74a0/matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c", size = 7894430, upload-time = "2024-12-13T05:54:44.049Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/18/80f70d91896e0a517b4a051c3fd540daa131630fd75e02e250365353b253/matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099", size = 7780045, upload-time = "2024-12-13T05:54:46.414Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/73/ccb381026e3238c5c25c3609ba4157b2d1a617ec98d65a8b4ee4e1e74d02/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249", size = 8209906, upload-time = "2024-12-13T05:54:49.459Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/33/1648da77b74741c89f5ea95cbf42a291b4b364f2660b316318811404ed97/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423", size = 8322873, upload-time = "2024-12-13T05:54:53.066Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d3/8447ba78bc6593c9044c372d1609f8ea10fb1e071e7a9e0747bea74fc16c/matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e", size = 9099566, upload-time = "2024-12-13T05:54:55.522Z" },
+ { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065, upload-time = "2024-12-13T05:54:58.337Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/2b/c918bf6c19d6445d1cefe3d2e42cb740fb997e14ab19d4daeb6a7ab8a157/matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70", size = 7891131, upload-time = "2024-12-13T05:55:02.837Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/e5/b4e8fc601ca302afeeabf45f30e706a445c7979a180e3a978b78b2b681a4/matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483", size = 7776365, upload-time = "2024-12-13T05:55:05.158Z" },
+ { url = "https://files.pythonhosted.org/packages/99/06/b991886c506506476e5d83625c5970c656a491b9f80161458fed94597808/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f", size = 8200707, upload-time = "2024-12-13T05:55:09.48Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/e2/556b627498cb27e61026f2d1ba86a78ad1b836fef0996bef5440e8bc9559/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00", size = 8313761, upload-time = "2024-12-13T05:55:12.95Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ff/165af33ec766ff818306ea88e91f9f60d2a6ed543be1eb122a98acbf3b0d/matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0", size = 9095284, upload-time = "2024-12-13T05:55:16.199Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160, upload-time = "2024-12-13T05:55:19.991Z" },
+ { url = "https://files.pythonhosted.org/packages/49/b1/999f89a7556d101b23a2f0b54f1b6e140d73f56804da1398f2f0bc0924bc/matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6", size = 7891499, upload-time = "2024-12-13T05:55:22.142Z" },
+ { url = "https://files.pythonhosted.org/packages/87/7b/06a32b13a684977653396a1bfcd34d4e7539c5d55c8cbfaa8ae04d47e4a9/matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45", size = 7776802, upload-time = "2024-12-13T05:55:25.947Z" },
+ { url = "https://files.pythonhosted.org/packages/65/87/ac498451aff739e515891bbb92e566f3c7ef31891aaa878402a71f9b0910/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858", size = 8200802, upload-time = "2024-12-13T05:55:28.461Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/6b/9eb761c00e1cb838f6c92e5f25dcda3f56a87a52f6cb8fdfa561e6cf6a13/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64", size = 8313880, upload-time = "2024-12-13T05:55:30.965Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/a2/c8eaa600e2085eec7e38cbbcc58a30fc78f8224939d31d3152bdafc01fd1/matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df", size = 9094637, upload-time = "2024-12-13T05:55:33.701Z" },
+ { url = "https://files.pythonhosted.org/packages/71/1f/c6e1daea55b7bfeb3d84c6cb1abc449f6a02b181e7e2a5e4db34c3afb793/matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799", size = 7841311, upload-time = "2024-12-13T05:55:36.737Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3a/2757d3f7d388b14dd48f5a83bea65b6d69f000e86b8f28f74d86e0d375bd/matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb", size = 7919989, upload-time = "2024-12-13T05:55:39.024Z" },
+ { url = "https://files.pythonhosted.org/packages/24/28/f5077c79a4f521589a37fe1062d6a6ea3534e068213f7357e7cfffc2e17a/matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a", size = 7809417, upload-time = "2024-12-13T05:55:42.412Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c8/c523fd2963156692916a8eb7d4069084cf729359f7955cf09075deddfeaf/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c", size = 8226258, upload-time = "2024-12-13T05:55:47.259Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/88/499bf4b8fa9349b6f5c0cf4cead0ebe5da9d67769129f1b5651e5ac51fbc/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764", size = 8335849, upload-time = "2024-12-13T05:55:49.763Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/9f/20a4156b9726188646a030774ee337d5ff695a965be45ce4dbcb9312c170/matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041", size = 9102152, upload-time = "2024-12-13T05:55:51.997Z" },
+ { url = "https://files.pythonhosted.org/packages/10/11/237f9c3a4e8d810b1759b67ff2da7c32c04f9c80aa475e7beb36ed43a8fb/matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965", size = 7896987, upload-time = "2024-12-13T05:55:55.941Z" },
+ { url = "https://files.pythonhosted.org/packages/56/eb/501b465c9fef28f158e414ea3a417913dc2ac748564c7ed41535f23445b4/matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c", size = 7885919, upload-time = "2024-12-13T05:55:59.66Z" },
+ { url = "https://files.pythonhosted.org/packages/da/36/236fbd868b6c91309a5206bd90c3f881f4f44b2d997cd1d6239ef652f878/matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7", size = 7771486, upload-time = "2024-12-13T05:56:04.264Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/4b/105caf2d54d5ed11d9f4335398f5103001a03515f2126c936a752ccf1461/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e", size = 8201838, upload-time = "2024-12-13T05:56:06.792Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/a7/bb01188fb4013d34d274caf44a2f8091255b0497438e8b6c0a7c1710c692/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c", size = 8314492, upload-time = "2024-12-13T05:56:09.964Z" },
+ { url = "https://files.pythonhosted.org/packages/33/19/02e1a37f7141fc605b193e927d0a9cdf9dc124a20b9e68793f4ffea19695/matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb", size = 9092500, upload-time = "2024-12-13T05:56:13.55Z" },
+ { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962, upload-time = "2024-12-13T05:56:16.358Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/22/2ef6a364cd3f565442b0b055e0599744f1e4314ec7326cdaaa48a4d864d7/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c", size = 7877995, upload-time = "2024-12-13T05:56:18.805Z" },
+ { url = "https://files.pythonhosted.org/packages/87/b8/2737456e566e9f4d94ae76b8aa0d953d9acb847714f9a7ad80184474f5be/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca", size = 7769300, upload-time = "2024-12-13T05:56:21.315Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/1f/e709c6ec7b5321e6568769baa288c7178e60a93a9da9e682b39450da0e29/matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db", size = 8313423, upload-time = "2024-12-13T05:56:26.719Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624, upload-time = "2024-12-13T05:56:29.359Z" },
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.10.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "cycler", marker = "python_full_version >= '3.10'" },
+ { name = "fonttools", marker = "python_full_version >= '3.10'" },
+ { name = "kiwisolver", version = "1.4.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "packaging", marker = "python_full_version >= '3.10'" },
+ { name = "pillow", marker = "python_full_version >= '3.10'" },
+ { name = "pyparsing", marker = "python_full_version >= '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862, upload-time = "2025-05-08T19:09:39.563Z" },
+ { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149, upload-time = "2025-05-08T19:09:42.413Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719, upload-time = "2025-05-08T19:09:44.901Z" },
+ { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801, upload-time = "2025-05-08T19:09:47.404Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111, upload-time = "2025-05-08T19:09:49.474Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213, upload-time = "2025-05-08T19:09:51.489Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" },
+ { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" },
+ { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" },
+ { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" },
+ { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" },
+]
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
+]
+
+[[package]]
+name = "mpyq"
+version = "0.2.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ff/10/76041d97aa01e4d0f93481942b4faf5652123acdd90fbff4e40bb8d9024c/mpyq-0.2.5.tar.gz", hash = "sha256:30aaf5962be569f3f2b53978060cd047434ee4f5a215925dd6ff0fef04ec0007", size = 8677, upload-time = "2014-03-17T02:25:41.54Z" }
+
+[[package]]
+name = "multidict"
+version = "6.6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/aa/6d/84d6dbf9a855c09504bdffd4a2c82c6b82cc7b4d69101b64491873967d88/multidict-6.6.0.tar.gz", hash = "sha256:460b213769cb8691b5ba2f12e53522acd95eb5b2602497d4d7e64069a61e5941", size = 99841, upload-time = "2025-06-27T09:51:54.73Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/98/fb/3821993b4027c5acf8449789318614ff67da71f4de9d386911eeaf6ba945/multidict-6.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d7913e6d0953b6d65c74290da65bc33d60d32a48bbe0bf2398ea1c5a2626e0b2", size = 76908, upload-time = "2025-06-27T09:49:23.988Z" },
+ { url = "https://files.pythonhosted.org/packages/64/e8/641eb9fd4e6691d3c74deae9bb5d1569c722d772c3183f89d4b24f0a1378/multidict-6.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8552e89a546408d3f78f1efd1c48e46077b68e59b6d5607498dd0a44df60b87c", size = 44831, upload-time = "2025-06-27T09:49:26.151Z" },
+ { url = "https://files.pythonhosted.org/packages/80/c9/6b87a1562506364145a7ab321c24c48a85e6d584b0abbb5a607480e8f449/multidict-6.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:54318d7991887e3e557e71e97fee3fc152db235a26edbbc62079a75e263d8fef", size = 44494, upload-time = "2025-06-27T09:49:27.645Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/14/4dee445e0987255e5a8318f2c8dd4e42ebfcc28be6ff1b8ad2939c372939/multidict-6.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2cdd2a2b1d35debdc367aca97709d20fc6cfc18e88f5b85a47b478e19b990b54", size = 244954, upload-time = "2025-06-27T09:49:29.245Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/bd/4966863765fdd213253eff0555d24a75de49dbe44ab0fbab5b5761cab2ce/multidict-6.6.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0d60aeb062bf15d8ec5ec2547b2f5a06090692b79414c0b26fcc94709e64d650", size = 221809, upload-time = "2025-06-27T09:49:31.017Z" },
+ { url = "https://files.pythonhosted.org/packages/46/77/6b76605fe4d102d71c8b1b98f6c3106344459f55dc3538e34073d0b654f1/multidict-6.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6e24583ab8e2b66370edd1a3b6cb2979b4866aff1e73b10bf61e46033c2dc1b", size = 254622, upload-time = "2025-06-27T09:49:32.362Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/37/d0e9dd5fb3cfd3db4062817e50ec6ecd017dbc6614cb5c09e49bf469e767/multidict-6.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d710b49cdf38e158ba9ba6819ea9bf1041e87e3d36abcd577d2836b51a7eb373", size = 250788, upload-time = "2025-06-27T09:49:34.376Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/19/c1603c63be00df967093b86cabb5cdc264e1f162eedae7731c2db1142309/multidict-6.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f812ed66bfd06b7d67a1f3d46b1644b88bdfe8aea6b290a1411ab08bcd93f08a", size = 243439, upload-time = "2025-06-27T09:49:36.375Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/99/23c6d819905ab01a4a37590c98a6ea04d65515f04ed76b9bc2c179553163/multidict-6.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b62dc87d3a55d0e9753f5afdd7df67a5fb8ef1b43e449b9a8a2c4b8f71ecf1f", size = 240733, upload-time = "2025-06-27T09:49:37.721Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/1f/53d45fa11f9d1ea2bb02143b6615adbf142615517e7a99dd6097dc4ac7ee/multidict-6.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b524f005fc749bec8fd0997aff1de72be136d7fe8a528062f779f659765071fc", size = 233989, upload-time = "2025-06-27T09:49:39.494Z" },
+ { url = "https://files.pythonhosted.org/packages/35/20/ea9e4ccd734fae5484cf23834efc9e5fa52c6fdc9b98e31d9e9018d1d01a/multidict-6.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3f153e2cb8a5a9b34c95ffcdcc3eed0d62ea4b48a5c668b818c3d03c58061296", size = 250909, upload-time = "2025-06-27T09:49:41.189Z" },
+ { url = "https://files.pythonhosted.org/packages/67/80/e02b26ef75703ecc65cc1ccd09e08a605024da24ffe0d6e1a28b38a70f7a/multidict-6.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1b23869a750e9cb32b2c4a95edf081adc45cc684d4f8ebe0c15f830d5cb0e878", size = 242628, upload-time = "2025-06-27T09:49:42.552Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/99/6e58d1795ac200b31bada5d2c98fcebb7cb0ef76468a9def74d522c96c25/multidict-6.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2fe3aa2280cd573eb26afa6c9030e66a6394c763f5325399d4cae76fca24c758", size = 239149, upload-time = "2025-06-27T09:49:43.964Z" },
+ { url = "https://files.pythonhosted.org/packages/19/3e/220977ae6007b0a953b550148d696eb2a03f98501b3f7242d0240999d7cd/multidict-6.6.0-cp310-cp310-win32.whl", hash = "sha256:3234b25ccf0d90666f10fceb2a8ae9d9a47b5d4e1e94eb32924d42e2ae369e74", size = 41454, upload-time = "2025-06-27T09:49:45.219Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/50/4f48a9aa8fbfba2414b9b47e509191d20fed9d8246bf311d9e106474d9db/multidict-6.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd58e43381f943f9d613c87bf0f1cf7340964dd2bea86e3f7a21c81c50bbc9fb", size = 45369, upload-time = "2025-06-27T09:49:46.707Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/24/b116b5a78fc247c7005d54f809f695042c3ff8a98a4d9cc8cdcceabb9106/multidict-6.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:b7ee8eed2ba1e46d7f60a2ec5d9866285daec3c7e0685dcfa5dbfd0ed6a173d0", size = 43080, upload-time = "2025-06-27T09:49:47.937Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/8e/2a652624dae24b4e94e17794a2fd3d3f0cb0e6276829052b4c5b1a4a7226/multidict-6.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5eb5444dd0dc4c2e0f180d7e216fe2a713d45b5648fec2832ff4a78100270d6a", size = 76355, upload-time = "2025-06-27T09:49:49.065Z" },
+ { url = "https://files.pythonhosted.org/packages/56/9a/9b1ce7353c8a0da1ff682740c58273daa42a748c7757f41e61e824305656/multidict-6.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:522cafe660896c471fc667c53d5081416c435a7ab88e183d8bcd75c6f993fb27", size = 44561, upload-time = "2025-06-27T09:49:50.256Z" },
+ { url = "https://files.pythonhosted.org/packages/00/6d/99f8b848b8b1297692b22f56de50fb79c7d3efabfae042a4efef5b956325/multidict-6.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b4898814f97d28c2a6a5989cb605840ad0545a8f2bad38a5d3a75071b673ec6", size = 44222, upload-time = "2025-06-27T09:49:51.403Z" },
+ { url = "https://files.pythonhosted.org/packages/71/61/8cd3c9cb51641ef2a2aa69cd5e724fdab1c6d5c7ad6919399d44faada723/multidict-6.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec93a0f75742ffcb14a0c15dedcafb37e69860a76fc009d0463c534701443f2f", size = 248242, upload-time = "2025-06-27T09:49:52.731Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/5c/c1e469a4c7d700d4ddbfbf50dfc8bdd61626ca67f95180074cc93ac354b2/multidict-6.6.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db158941bbed55f980a30125cc9d027f272af76e11f4c7204e3c458c277a5098", size = 224761, upload-time = "2025-06-27T09:49:54.055Z" },
+ { url = "https://files.pythonhosted.org/packages/27/76/04cd7fa6df2bec67aed1e920250af99bef637a17c35d7011a8e08cc9a088/multidict-6.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:561164b6e0998a49b72b17dd9f484ef785bcf836a5ce525b58a0970c563cbb6e", size = 257772, upload-time = "2025-06-27T09:49:55.845Z" },
+ { url = "https://files.pythonhosted.org/packages/04/90/3612caeb061645b83871b82d4eaa3025898443e94952309ca373e4a3ee99/multidict-6.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aed62dc3bf5bba3c64f123e15d05005e22a18b3d95b990996b1c3a9aa12c4611", size = 255327, upload-time = "2025-06-27T09:49:57.271Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f1/dee9537a66a85b793f17c24bea64d2d0eecc160a8867ffdb27a9de779e9e/multidict-6.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c38f0b501487246b1ac68cd6159459789af9f95ac6b35eb14f7f74e41b3f8eb5", size = 247179, upload-time = "2025-06-27T09:49:58.743Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/f6/a7f650c14963ed642383e218ae5f91503810367e095c1090e6b583dc3326/multidict-6.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5737e9abbde209f7f9805fed605f9623d65b7822bfa9e18cb0f94b6f8fa6c0fd", size = 244077, upload-time = "2025-06-27T09:50:00.109Z" },
+ { url = "https://files.pythonhosted.org/packages/83/fc/4cab751b313354fa3c061aad91576f8ab4d265c33491e46156de85951dbd/multidict-6.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8fad001e4fbda4a14f6f6466e78c73f51dad18da0a831378a564050b9790b7de", size = 238920, upload-time = "2025-06-27T09:50:01.876Z" },
+ { url = "https://files.pythonhosted.org/packages/37/fb/bc11bf8c12c62df7a5616d79e443322c6d29eb7d487af37c697a16a8ade1/multidict-6.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0c9e7ce1fff62bd094b5adb349336fc965e29ae401e0db413986a85cfbfeb11d", size = 254293, upload-time = "2025-06-27T09:50:03.336Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/98/ce6ab86c41d48f38370fadebf7ba5ff1ea5a6c4fa1cc765b4688c3872ffc/multidict-6.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1f9fb3a923d84843807a24f0250028f5802e97469c496a6ed0eee9ef7ed455a2", size = 247190, upload-time = "2025-06-27T09:50:04.699Z" },
+ { url = "https://files.pythonhosted.org/packages/84/cb/1c35255028b3aeda8c2876ff8b8b4f8b04d1f28a6a5fcccb0c9a02886792/multidict-6.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:50f62cd84cf042a7d586759bc83059d1c2b1c00ae3f2481d112cdf711e6cb15c", size = 242926, upload-time = "2025-06-27T09:50:06.112Z" },
+ { url = "https://files.pythonhosted.org/packages/70/b9/503da6e5a176a6b2b14c228716f1b080214d7a1239d7a8fbfb871e437767/multidict-6.6.0-cp311-cp311-win32.whl", hash = "sha256:855fc84169a98ee9dde3805716c3a18959a8803069866e48512edd6a5a59fffc", size = 41352, upload-time = "2025-06-27T09:50:07.416Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/31/10955118cbc4dcf0c8579f1c9b7c212780651e8de628b66d61654fe784cc/multidict-6.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:e86d6f67647159f6b96df10504b7f00c17f12370588ea7202b78fc3867d1c900", size = 45379, upload-time = "2025-06-27T09:50:08.513Z" },
+ { url = "https://files.pythonhosted.org/packages/11/eb/f69ee7bdd3e26c66711d208f7becad87c7f75d364b47efd040f5e8b9757e/multidict-6.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:afbb6d962c355863a6f39a1558db875fcaa0cc1116acbb7086e8fa0e86a642ed", size = 43004, upload-time = "2025-06-27T09:50:09.687Z" },
+ { url = "https://files.pythonhosted.org/packages/32/7b/767bd6b1b0565ac04070222e42c66dbfe7d1c3215a218db3e0e5ca878b41/multidict-6.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b95809f30d197efa899b5e08a38cf8d6658f3acfa5f5c984e0fe6bc21245aeb", size = 76514, upload-time = "2025-06-27T09:50:10.915Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/8f/2bd636957abb149b55c42baf96cb6be06c884fae7729bf27280cf1005d8a/multidict-6.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c146b37f0a719df5f82e0dccc2ecbcbcccae75e762d696b5b26990aef65e6ac4", size = 45355, upload-time = "2025-06-27T09:50:12.431Z" },
+ { url = "https://files.pythonhosted.org/packages/80/54/6fa0de18d4da8011cb00def260b0f7632900d7549f59b55228c9c9be26ef/multidict-6.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d36d3cd27eba1f7aa209839ccce79b9601abbd82e9b503f33f16652072e347da", size = 43613, upload-time = "2025-06-27T09:50:13.623Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/73/ee599e249ccad06f2dcfdcdb87d4f30a7386128ccb601e6f39609f31949a/multidict-6.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e1676ed48d42e3db21a18030a149bff12ed564185453777814722ec8c67f26", size = 256970, upload-time = "2025-06-27T09:50:14.942Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/96/f36dd4b3ff52e52befda68bc5c46c15e93c0f11edc60b184cbe72e6aff56/multidict-6.6.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1201db24a4b55921cf5db90cbd9a31a44c0bb2eba8ee5f50e330c0b2080fa00", size = 241875, upload-time = "2025-06-27T09:50:16.33Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/77/63d7057fab7b5a0b3d50d21b24b17ea8b66d5b06b2cfd0d8e83befc45f9e/multidict-6.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a2a7242da589b5879857646847e806dad51b6de6fab8de3c0330ea60656d915", size = 267398, upload-time = "2025-06-27T09:50:17.792Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/2f/39d3b8769b0e72f30b62e7b5f0c38d4ce98d7da485517ed8aae50ea57e6b/multidict-6.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8175c3ec6a7ed880ccf576a80a95f2b559a97158662698db6c8fbeffdf982123", size = 268908, upload-time = "2025-06-27T09:50:19.191Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/15/bea3b7376dbb70e8c2fa413655890a5062988746cc42501f01f194adfa8d/multidict-6.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a5e7c0e6ef7e98ea7601c672f067e491bd286531293c62930b10ade50120af2", size = 256905, upload-time = "2025-06-27T09:50:20.575Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/9e/e989430e46877ca9cf9ab6224b3616250b4aacb129d27f91f9347fbe0bfa/multidict-6.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cfb725d2379d7c54958cce23a0fd8ff5b3d8dd1f4e2741a44a548eddefad6eae", size = 252221, upload-time = "2025-06-27T09:50:21.991Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/c1/2ac4c1ad6ccc6e8227fdc993d494a2a8f2d379dc6c2d5dc0a3b4330a2cd4/multidict-6.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6dbff377ce9e67a5cae6c5989a4963816d70d52a9f6bf01dd04aadaa9ca31dba", size = 249186, upload-time = "2025-06-27T09:50:24.574Z" },
+ { url = "https://files.pythonhosted.org/packages/22/3f/3f21091cbb14fc333949bed0a481a3f9061199ef2a3f7b341a6d48bf1bc7/multidict-6.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b04670b6d3251dfc1761e8a8c58cd1ccb28c1fc8041ed7dc0b1e741bd7753b02", size = 262862, upload-time = "2025-06-27T09:50:26.066Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/ab/384b7afc28869dbd34bea5c97ecd6cbfe467a928fe189f7018cc67db2ebc/multidict-6.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:20da2c7faa1bddc3fda31258debcbcc7033f33094f4d89b3b6269570bd7b132d", size = 258965, upload-time = "2025-06-27T09:50:27.589Z" },
+ { url = "https://files.pythonhosted.org/packages/16/2f/ed01b63b4da243f76ca69157d9ed708598914306883330c8d18fa853425a/multidict-6.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a848558168b6c39bca54c57dacc27eac708b479b1ff92469a7465ead6619334", size = 252138, upload-time = "2025-06-27T09:50:29.04Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/d1/ca152a9b8cd23811e316effe4e9bf74606ac45b50bb6e435ed4ac782637c/multidict-6.6.0-cp312-cp312-win32.whl", hash = "sha256:a066dc45b29ce247a2ddbccc2cf20ce99f95e849a7624cf3cdfd7d50b1261098", size = 41966, upload-time = "2025-06-27T09:50:30.684Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/c8/df3e38a1d9e4ce125ebf2f025e8db4032d0f1a534c4f8179ac51e5b3cced/multidict-6.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:74fa779e729bb20dd7ce9bbc2b4b704f4134b6763ea8f4a13d259aed044812fd", size = 45586, upload-time = "2025-06-27T09:50:31.846Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/3a/bccfbbaed68aec312e6c187c570943a63a7fad328198b5cd608718884108/multidict-6.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:860ddc224123efb788812f16329d629722c68ca687c0d4410f4ad26a9197cc73", size = 43279, upload-time = "2025-06-27T09:50:33.093Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/10/5d58c3739adc1b1322df7300ec0b40fba13a138b292fa350b59ab8329783/multidict-6.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e26114b8e3da8137bb39e2820eef09005c0ab468b2cca384f429a2104c48f6d1", size = 75827, upload-time = "2025-06-27T09:50:34.37Z" },
+ { url = "https://files.pythonhosted.org/packages/14/11/713fd1b5cff3ae3a3d458073460d1efe33b469da079daca1cc2706a25e96/multidict-6.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bf72082eba16b22f63ef8553e1d245c56bf92868976f089ae3f572e91e2dd197", size = 45012, upload-time = "2025-06-27T09:50:35.607Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/bd/9518933da0bdec068ed16ea9bead13a9d5e1bc8584af329f242ba4886395/multidict-6.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57afe4cdc5ee0c001af224f259a20b906df8ddbb9b9af932817a374bf98cd857", size = 43279, upload-time = "2025-06-27T09:50:37.183Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/2e/28f3bb3c8ad6c74f78cba89e5ace84c026b331647dde7f1f32dc6ad018c5/multidict-6.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d18cde7f12df1f9d42bafbe01ed0af48e8f6605ee632aaf3788ada861193175", size = 255396, upload-time = "2025-06-27T09:50:38.524Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ef/13f4031ba9d4407e3042bf4d19b89a4c27d3e381a8b122b48a3755fcd43d/multidict-6.6.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:11ccf3fa5cdf0475706307be90ab60bb1865cd8814c7cac6f3c9e54dda094a57", size = 239929, upload-time = "2025-06-27T09:50:39.919Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/0d/7b5c3deeb4bdb44b91b56b4a317af54bafa1d697eaff30a6eb16e3d81f06/multidict-6.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:690e7fd86c1def94f080ce514922fb6b62b6327ab10b229e1a8a7ecfc4e88200", size = 266139, upload-time = "2025-06-27T09:50:41.466Z" },
+ { url = "https://files.pythonhosted.org/packages/82/b7/8a64535737ed19211fa7cbc76635bd1fea50665a9d6d293b63791ec2e746/multidict-6.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c92cb8bc15c3152ccdb53093c12eb56e661bf404f5674c055007dc979c869f7", size = 267222, upload-time = "2025-06-27T09:50:43.081Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/d2/05a85c85f3be3f3130d6d029c280d61965a96d019f42adbb03eb95bbbe6f/multidict-6.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:760a4970d6ce435b0c71a68c4a86fcd0fad50c760c948891d60af4d3486401f6", size = 254095, upload-time = "2025-06-27T09:50:44.502Z" },
+ { url = "https://files.pythonhosted.org/packages/76/cd/1b667e7f56e0970310f646d29a02657db5105eb33b1de5509aa543da5216/multidict-6.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:606b94703e1949fd0044ea72aab11a7b9d92492e86fd5886c099d1a7655961ca", size = 250780, upload-time = "2025-06-27T09:50:46.094Z" },
+ { url = "https://files.pythonhosted.org/packages/72/60/72d7fc97b88a594bfb3d5415829833dd77bce6ae505c94e3ca21d358a7b3/multidict-6.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9c73131cd1f46669c9b28632be3ee3be611aef38c0fe5ee9f8d5632e9722229f", size = 249031, upload-time = "2025-06-27T09:50:47.668Z" },
+ { url = "https://files.pythonhosted.org/packages/05/49/a892295218fc986884df7b99ec53411086d6c5137bc221f5791d7190b744/multidict-6.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3f76f25eea979b6e39993380acb56422eb8a10c44e13ef4f5d3c82c797cb157d", size = 261192, upload-time = "2025-06-27T09:50:49.195Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/68/0ecea658316bd826e666eb309c27f4b9d6635ff41e7d1426ba4c709b2c78/multidict-6.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b9a1135f8a0bf7959fb03bca6b98308521cecc6883e4a334a9ae4edecf3d90c", size = 257521, upload-time = "2025-06-27T09:50:50.802Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/98/e465b36fdd2bd80781ad98303f9a804f5c916d592aa055210dca3f16a519/multidict-6.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ff8f1043a727649ce698642065b279ee18b36e0d7cbdb7583d7edac6ae804392", size = 249403, upload-time = "2025-06-27T09:50:52.437Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/9e/0a2063333cd39287fb8497713b186b6d86bfbb3a64a67defbf849d7871a3/multidict-6.6.0-cp313-cp313-win32.whl", hash = "sha256:e53dcb79923cc0c7ef0ac41aac6e4ea4cf8aa1c7bc7f354c014cf386e9c28639", size = 41776, upload-time = "2025-06-27T09:50:53.887Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/67/8d029a8577e29181da4d7504c2d4be43a15ca8179c1e0e27f008645b0232/multidict-6.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:c0ac2049db3dca5fade0390817f94e1945e248297c90bf0b7596127105f3f54f", size = 45401, upload-time = "2025-06-27T09:50:55.563Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/e1/b1b921846eb50c76cca9bb4b1e05438e71c5bbfd1be5240c2e98bc44d98b/multidict-6.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:fe16f2823f50a10f13cf094cc09c9e76c3b483064975c482eda0d830175746bc", size = 43097, upload-time = "2025-06-27T09:50:56.99Z" },
+ { url = "https://files.pythonhosted.org/packages/01/96/11dec4734a699357b9f1f5217047011e22c3c04ef8c0daafbdb4914fbd9b/multidict-6.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:55243ada517cd453ede3be68ab65260af5389adcb8be5f4c1c7cdec63bbeef5d", size = 82775, upload-time = "2025-06-27T09:50:58.31Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/0b/4128fb611bcd0045d29cd51e214f475529d425ac0c316d22e52090ff7860/multidict-6.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d614de950f7dd9d295590a5b3017dd1f0a5278a97d15a10d037a2f24e7f6d65b", size = 48329, upload-time = "2025-06-27T09:50:59.581Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/c2/460deaf50a11df6fadf10b88739f58c8443b30b7ae7c650b83a0741379a1/multidict-6.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d12ce09473c3f497d8944c210899043686f88b811970edc5eb6486f413caa267", size = 46695, upload-time = "2025-06-27T09:51:00.916Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/fe/8c84812a9d42f86722dc421df906f427d6ee7a670267e5c53e63ef4dc284/multidict-6.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d5a2c6f673c0b5f8bd1049208a313d7e038972aa2ab898bd486f1d29a8c62130", size = 249833, upload-time = "2025-06-27T09:51:02.39Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/8b/3435951b9f940a3e574f2b514e938811aa41fd696a10a9d0ea69db4986a7/multidict-6.6.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ff27fc5526b8740735612ea32d8fab2f79e83824b8f9e7f2b88c9e1db28d6f79", size = 228800, upload-time = "2025-06-27T09:51:03.97Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/17/a1f2fe66ee547152d6bfefb3654b2df3730fabdfea8c0d9f30459e6dc8c0/multidict-6.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:279bfd45fecc0d9cdb6926b2a58381cae0514689d6fab67e39a88304301da90a", size = 256563, upload-time = "2025-06-27T09:51:05.773Z" },
+ { url = "https://files.pythonhosted.org/packages/57/f1/4ec89ff9d74bbd8e4ab8c7808e630773dd91151e1f08ec88d052e870319f/multidict-6.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b28f421e6f8b444f636bbf4b99e01db5adeb673691ebb764eb39c17dc64179cd", size = 256001, upload-time = "2025-06-27T09:51:07.324Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/3e/7b69b5a51db23f5a6464801982ea98c3d9ad1dc855c5fc5cc481d43bc3fe/multidict-6.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11537e9e25241a98746f265230569d7230ad2d8f0d26e863f974e1c991ff5a45", size = 246732, upload-time = "2025-06-27T09:51:09.198Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/8b/a9f4ab7806cc7252c6b177daa426091497fbdf4f043564de19cedbcd4689/multidict-6.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e5b1647506370075513fb19424141853f5cc68dbba38559655dcaafce4d99f27", size = 244897, upload-time = "2025-06-27T09:51:10.793Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/93/14c7500f717958a2a6af78f94326a4792495af51ec7c65d0f7e0bad35d99/multidict-6.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fe2bab539a912c3aa24dd3f96e4f6a45b9fac819184fa1d09aec8f289bd7f3ab", size = 234065, upload-time = "2025-06-27T09:51:12.625Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/71/2eb2ceeaf0fc91b8edaa2aa4f2b76d82f8d41705b76b4d47b4b002e0da88/multidict-6.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:9d30a1ef323867e71e96c62434cc52b072160e4f9be0169ec2fea516d61003dd", size = 251228, upload-time = "2025-06-27T09:51:14.175Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/05/f8984acea1a76929cc84a9c8a927f8c756e23be1d11da725b56c2d249f8d/multidict-6.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0b9cc871bc3e580224f9f3c0cd172a1d91e5f4e6c1164d039e3e6f9542f09bf3", size = 245416, upload-time = "2025-06-27T09:51:17.252Z" },
+ { url = "https://files.pythonhosted.org/packages/10/7b/1f8fb6487bb5e7cb1e824cc54e93dabda7bf8aadd87a6d7e1c7f82e114b5/multidict-6.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aa98b25a25eaefd8728cffab14066bdc10b30168d4dd32039c5191d2dc863631", size = 241841, upload-time = "2025-06-27T09:51:19.075Z" },
+ { url = "https://files.pythonhosted.org/packages/59/30/5f1b87484a85e2a1e245e49b8533016164852f69a68d00d538a9c4ec5a62/multidict-6.6.0-cp313-cp313t-win32.whl", hash = "sha256:b62d2907e8014c3e65b7725271029085aaf8885d34f5bab526cd960bcf40905f", size = 47755, upload-time = "2025-06-27T09:51:20.497Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/a3/a21a783d10ec1132e81ea808fd2977838ae01e06377991e3d1308e86e47a/multidict-6.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:954591356227721d7557a9f9ea0f80235608f2dc99c5bb1869f654e890528358", size = 52897, upload-time = "2025-06-27T09:51:21.79Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/c7/103af64747f755681e7ee6077a558f8aeaa689504d191fca4b12df75e8c7/multidict-6.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:14b3d44838170996d217b168de2c9dd1cefbb9de6a18c8cfd07cec141b489e41", size = 45329, upload-time = "2025-06-27T09:51:23.935Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e3/c61011b0ecc388f6b1ffe98dedadf683a66c0d7227d3127a346c1647f0f0/multidict-6.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87de7abfcebdbed9bdb5d7fe1a7e8585057dffee752bd617d578dcf437fc7bb", size = 76869, upload-time = "2025-06-27T09:51:25.231Z" },
+ { url = "https://files.pythonhosted.org/packages/76/33/8e6ce73b29480c4b24f5a77f9265389799ad1b1ec6b0b933172a8bb40b57/multidict-6.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:715e82e2afd84e7fb614fc1cb382e543796869036fb7af199abfb4237badf203", size = 44818, upload-time = "2025-06-27T09:51:26.482Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/92/3cb1abe6d9acc703985feb2729edbe3f3b1a04c10d1c5f536200305f0f80/multidict-6.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d344e6667a8a77deab4d18565e8494d720c3372461ab812f7e1edd4f6b5422aa", size = 44472, upload-time = "2025-06-27T09:51:28.377Z" },
+ { url = "https://files.pythonhosted.org/packages/09/c3/ae0584eb4560d16a6396146fc587e959b4a8bc3cf6e8be0c90880bbdb310/multidict-6.6.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe61def5a869558956e242365492055288f9317ae7059b13cb44b13fb8bbaa89", size = 242878, upload-time = "2025-06-27T09:51:29.942Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/ba/9facb69dae3e41c7978759ac95e30ad7703ffdeaf2f8e7001837822412ff/multidict-6.6.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:557b196480183ba62850c02af58f995a7436724470b9fe6571717b5bc3c953b5", size = 219066, upload-time = "2025-06-27T09:51:32.295Z" },
+ { url = "https://files.pythonhosted.org/packages/52/77/dac9fd95a999c220b9e9c7a8238b2affa084c0f544958091ec810898d982/multidict-6.6.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8783c2e4290d25b4b4804796048b3ab531f37bffa178291805e2671128ea865f", size = 252689, upload-time = "2025-06-27T09:51:34.534Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e5/82ae7bdded1f50aa6c5b5bb989aafd8003f75f5d8bcb0a4a3d025cb6035c/multidict-6.6.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ba928222e0d0e261d4059c074ed18d8103740184c81b3a4303950103808ee7e", size = 248841, upload-time = "2025-06-27T09:51:36.146Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/d9/cd537886ef03f2367f193cb24fd86afd3e04abecd8e25d09cea297ea6f9b/multidict-6.6.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12efd613c7ec9a8f90e8a586580c15ed325bb4d7fc98b57732ff6876e49849b8", size = 240965, upload-time = "2025-06-27T09:51:38.053Z" },
+ { url = "https://files.pythonhosted.org/packages/79/80/ac5726ff10d5842cb356ab2a247175a251af7eb1373dcb4931db0d9bc525/multidict-6.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a10f61cf9e5616833189a2a14a2713700160fbcbede8a8fa7ae38e86f6cfb6c", size = 239110, upload-time = "2025-06-27T09:51:39.95Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/79/495ad2227e930dc91e164376fa964ab7ac9dc1158e8a775a4bf604202601/multidict-6.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c91d56070ceb776c12dfe52fdf07e6291c1044511674f6433c80f16a96e130a5", size = 232452, upload-time = "2025-06-27T09:51:41.461Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/94/6d17b9bdc4cc1e1bbf9a6ba074eedae99f822e90e654bf041ce837c18f85/multidict-6.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:bbd8f9e6ea509fc7dcbbe7fedcdc6c5a3b14b199df037d545643a51c2e18b93f", size = 248810, upload-time = "2025-06-27T09:51:43.052Z" },
+ { url = "https://files.pythonhosted.org/packages/33/1c/d2d25fe0b04632773aa8d9daa2b2b76f72797ad59a76a19cd4589da01bbb/multidict-6.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:027048e15f907757137e66b2820aa3852e9f9680e81acda1b9fe1a9e5a9dcc89", size = 240620, upload-time = "2025-06-27T09:51:45.578Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/23/adbbc4b4c5b1d1ba06cc13f8c1c7dfa7be8eda5037e0ff66d4b5ab3dac67/multidict-6.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a70531e2b53858787c5398cb7d2d05a99d243cc00f88b283e045dfd92bdde9fb", size = 236905, upload-time = "2025-06-27T09:51:47.376Z" },
+ { url = "https://files.pythonhosted.org/packages/88/c5/8eec2a263a5a8ea45d8209f9e099063b95c689e06799ebc6cde2bc5350dd/multidict-6.6.0-cp39-cp39-win32.whl", hash = "sha256:179fe3828f51fe07e820796b4a613ac4996f0337f579ee87bcbd3d9d9b90070c", size = 41475, upload-time = "2025-06-27T09:51:48.856Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/ff/d3475682f887eb18d237d02af97c825f9216438e7b0f1dfdbdb4fb962644/multidict-6.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:00e0b40a915534b0dc122e3e72213d0aa0d8abedc7168c5f5d4fcece14371b32", size = 45556, upload-time = "2025-06-27T09:51:50.503Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/52/7286ebe2336c8c889a33637c2c9eb13e05402612aca2bfa0044332e83708/multidict-6.6.0-cp39-cp39-win_arm64.whl", hash = "sha256:0a902ed2836e2bd6ab37c5fe39686a81f0bb8190c69f5d7a952845ae6cf138c0", size = 43070, upload-time = "2025-06-27T09:51:51.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/8a/35b72900b432516674bef955c2b41100a45a735f0ac5085eb2acbfcd5465/multidict-6.6.0-py3-none-any.whl", hash = "sha256:447df643754e273681fda37764a89880d32c86cab102bfc05c1e8359ebcf0980", size = 12297, upload-time = "2025-06-27T09:51:53.07Z" },
+]
+
+[[package]]
+name = "mypy"
+version = "1.16.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mypy-extensions" },
+ { name = "pathspec" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644, upload-time = "2025-06-16T16:51:11.649Z" },
+ { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033, upload-time = "2025-06-16T16:35:30.089Z" },
+ { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645, upload-time = "2025-06-16T16:35:48.49Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986, upload-time = "2025-06-16T16:48:39.526Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632, upload-time = "2025-06-16T16:36:08.195Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391, upload-time = "2025-06-16T16:37:56.151Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557, upload-time = "2025-06-16T16:37:21.421Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921, upload-time = "2025-06-16T16:51:28.659Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887, upload-time = "2025-06-16T16:50:53.627Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658, upload-time = "2025-06-16T16:33:55.002Z" },
+ { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486, upload-time = "2025-06-16T16:37:03.301Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482, upload-time = "2025-06-16T16:47:37.48Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493, upload-time = "2025-06-16T16:47:01.683Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687, upload-time = "2025-06-16T16:48:19.367Z" },
+ { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723, upload-time = "2025-06-16T16:49:20.912Z" },
+ { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980, upload-time = "2025-06-16T16:37:40.929Z" },
+ { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328, upload-time = "2025-06-16T16:34:35.099Z" },
+ { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321, upload-time = "2025-06-16T16:48:58.823Z" },
+ { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480, upload-time = "2025-06-16T16:47:56.205Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538, upload-time = "2025-06-16T16:46:43.92Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839, upload-time = "2025-06-16T16:36:28.039Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634, upload-time = "2025-06-16T16:50:34.441Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584, upload-time = "2025-06-16T16:34:54.857Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886, upload-time = "2025-06-16T16:36:43.589Z" },
+ { url = "https://files.pythonhosted.org/packages/49/5e/ed1e6a7344005df11dfd58b0fdd59ce939a0ba9f7ed37754bf20670b74db/mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069", size = 10959511, upload-time = "2025-06-16T16:47:21.945Z" },
+ { url = "https://files.pythonhosted.org/packages/30/88/a7cbc2541e91fe04f43d9e4577264b260fecedb9bccb64ffb1a34b7e6c22/mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da", size = 10075555, upload-time = "2025-06-16T16:50:14.084Z" },
+ { url = "https://files.pythonhosted.org/packages/93/f7/c62b1e31a32fbd1546cca5e0a2e5f181be5761265ad1f2e94f2a306fa906/mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c", size = 11874169, upload-time = "2025-06-16T16:49:42.276Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/15/db580a28034657fb6cb87af2f8996435a5b19d429ea4dcd6e1c73d418e60/mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383", size = 12610060, upload-time = "2025-06-16T16:34:15.215Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/78/c17f48f6843048fa92d1489d3095e99324f2a8c420f831a04ccc454e2e51/mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40", size = 12875199, upload-time = "2025-06-16T16:35:14.448Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/d6/ed42167d0a42680381653fd251d877382351e1bd2c6dd8a818764be3beb1/mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b", size = 9487033, upload-time = "2025-06-16T16:49:57.907Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.0.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" },
+ { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" },
+ { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" },
+ { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" },
+ { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" },
+ { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" },
+ { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" },
+ { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" },
+ { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" },
+ { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" },
+ { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" },
+ { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" },
+ { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" },
+ { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" },
+ { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.2.6"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
+ { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
+ { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
+ { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
+ { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
+ { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
+ { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
+ { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
+ { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
+ { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
+ { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
+ { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
+ { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
+ { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
+ { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
+ { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
+ { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
+ { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
+ { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
+ { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
+ { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
+ { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
+ { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
+ { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
+ { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
+ { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
+ { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.3.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/19/d7c972dfe90a353dbd3efbbe1d14a5951de80c99c9dc1b93cd998d51dc0f/numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b", size = 20390372, upload-time = "2025-06-21T12:28:33.469Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b0/c7/87c64d7ab426156530676000c94784ef55676df2f13b2796f97722464124/numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070", size = 21199346, upload-time = "2025-06-21T11:47:47.57Z" },
+ { url = "https://files.pythonhosted.org/packages/58/0e/0966c2f44beeac12af8d836e5b5f826a407cf34c45cb73ddcdfce9f5960b/numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae", size = 14361143, upload-time = "2025-06-21T11:48:10.766Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/31/6e35a247acb1bfc19226791dfc7d4c30002cd4e620e11e58b0ddf836fe52/numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a", size = 5378989, upload-time = "2025-06-21T11:48:19.998Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/25/93b621219bb6f5a2d4e713a824522c69ab1f06a57cd571cda70e2e31af44/numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e", size = 6912890, upload-time = "2025-06-21T11:48:31.376Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/60/6b06ed98d11fb32e27fb59468b42383f3877146d3ee639f733776b6ac596/numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db", size = 14569032, upload-time = "2025-06-21T11:48:52.563Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c9/9bec03675192077467a9c7c2bdd1f2e922bd01d3a69b15c3a0fdcd8548f6/numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb", size = 16930354, upload-time = "2025-06-21T11:49:17.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/e2/5756a00cabcf50a3f527a0c968b2b4881c62b1379223931853114fa04cda/numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93", size = 15879605, upload-time = "2025-06-21T11:49:41.161Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/86/a471f65f0a86f1ca62dcc90b9fa46174dd48f50214e5446bc16a775646c5/numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115", size = 18666994, upload-time = "2025-06-21T11:50:08.516Z" },
+ { url = "https://files.pythonhosted.org/packages/43/a6/482a53e469b32be6500aaf61cfafd1de7a0b0d484babf679209c3298852e/numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369", size = 6603672, upload-time = "2025-06-21T11:50:19.584Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/fb/bb613f4122c310a13ec67585c70e14b03bfc7ebabd24f4d5138b97371d7c/numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff", size = 13024015, upload-time = "2025-06-21T11:50:39.139Z" },
+ { url = "https://files.pythonhosted.org/packages/51/58/2d842825af9a0c041aca246dc92eb725e1bc5e1c9ac89712625db0c4e11c/numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a", size = 10456989, upload-time = "2025-06-21T11:50:55.616Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/56/71ad5022e2f63cfe0ca93559403d0edef14aea70a841d640bd13cdba578e/numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d", size = 20896664, upload-time = "2025-06-21T12:15:30.845Z" },
+ { url = "https://files.pythonhosted.org/packages/25/65/2db52ba049813670f7f987cc5db6dac9be7cd95e923cc6832b3d32d87cef/numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29", size = 14131078, upload-time = "2025-06-21T12:15:52.23Z" },
+ { url = "https://files.pythonhosted.org/packages/57/dd/28fa3c17b0e751047ac928c1e1b6990238faad76e9b147e585b573d9d1bd/numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc", size = 5112554, upload-time = "2025-06-21T12:16:01.434Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/fc/84ea0cba8e760c4644b708b6819d91784c290288c27aca916115e3311d17/numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943", size = 6646560, upload-time = "2025-06-21T12:16:11.895Z" },
+ { url = "https://files.pythonhosted.org/packages/61/b2/512b0c2ddec985ad1e496b0bd853eeb572315c0f07cd6997473ced8f15e2/numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25", size = 14260638, upload-time = "2025-06-21T12:16:32.611Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/45/c51cb248e679a6c6ab14b7a8e3ead3f4a3fe7425fc7a6f98b3f147bec532/numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660", size = 16632729, upload-time = "2025-06-21T12:16:57.439Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/ff/feb4be2e5c09a3da161b412019caf47183099cbea1132fd98061808c2df2/numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952", size = 15565330, upload-time = "2025-06-21T12:17:20.638Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/6d/ceafe87587101e9ab0d370e4f6e5f3f3a85b9a697f2318738e5e7e176ce3/numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77", size = 18361734, upload-time = "2025-06-21T12:17:47.938Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/19/0fb49a3ea088be691f040c9bf1817e4669a339d6e98579f91859b902c636/numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab", size = 6320411, upload-time = "2025-06-21T12:17:58.475Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/3e/e28f4c1dd9e042eb57a3eb652f200225e311b608632bc727ae378623d4f8/numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76", size = 12734973, upload-time = "2025-06-21T12:18:17.601Z" },
+ { url = "https://files.pythonhosted.org/packages/04/a8/8a5e9079dc722acf53522b8f8842e79541ea81835e9b5483388701421073/numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30", size = 10191491, upload-time = "2025-06-21T12:18:33.585Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/bd/35ad97006d8abff8631293f8ea6adf07b0108ce6fec68da3c3fcca1197f2/numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8", size = 20889381, upload-time = "2025-06-21T12:19:04.103Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/4f/df5923874d8095b6062495b39729178eef4a922119cee32a12ee1bd4664c/numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e", size = 14152726, upload-time = "2025-06-21T12:19:25.599Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/0f/a1f269b125806212a876f7efb049b06c6f8772cf0121139f97774cd95626/numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0", size = 5105145, upload-time = "2025-06-21T12:19:34.782Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/63/a7f7fd5f375b0361682f6ffbf686787e82b7bbd561268e4f30afad2bb3c0/numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d", size = 6639409, upload-time = "2025-06-21T12:19:45.228Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/0d/1854a4121af895aab383f4aa233748f1df4671ef331d898e32426756a8a6/numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1", size = 14257630, upload-time = "2025-06-21T12:20:06.544Z" },
+ { url = "https://files.pythonhosted.org/packages/50/30/af1b277b443f2fb08acf1c55ce9d68ee540043f158630d62cef012750f9f/numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1", size = 16627546, upload-time = "2025-06-21T12:20:31.002Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/ec/3b68220c277e463095342d254c61be8144c31208db18d3fd8ef02712bcd6/numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0", size = 15562538, upload-time = "2025-06-21T12:20:54.322Z" },
+ { url = "https://files.pythonhosted.org/packages/77/2b/4014f2bcc4404484021c74d4c5ee8eb3de7e3f7ac75f06672f8dcf85140a/numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8", size = 18360327, upload-time = "2025-06-21T12:21:21.053Z" },
+ { url = "https://files.pythonhosted.org/packages/40/8d/2ddd6c9b30fcf920837b8672f6c65590c7d92e43084c25fc65edc22e93ca/numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8", size = 6312330, upload-time = "2025-06-21T12:25:07.447Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/c8/beaba449925988d415efccb45bf977ff8327a02f655090627318f6398c7b/numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42", size = 12731565, upload-time = "2025-06-21T12:25:26.444Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/c3/5c0c575d7ec78c1126998071f58facfc124006635da75b090805e642c62e/numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e", size = 10190262, upload-time = "2025-06-21T12:25:42.196Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/19/a029cd335cf72f79d2644dcfc22d90f09caa86265cbbde3b5702ccef6890/numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8", size = 20987593, upload-time = "2025-06-21T12:21:51.664Z" },
+ { url = "https://files.pythonhosted.org/packages/25/91/8ea8894406209107d9ce19b66314194675d31761fe2cb3c84fe2eeae2f37/numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb", size = 14300523, upload-time = "2025-06-21T12:22:13.583Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/7f/06187b0066eefc9e7ce77d5f2ddb4e314a55220ad62dd0bfc9f2c44bac14/numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee", size = 5227993, upload-time = "2025-06-21T12:22:22.53Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/ec/a926c293c605fa75e9cfb09f1e4840098ed46d2edaa6e2152ee35dc01ed3/numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992", size = 6736652, upload-time = "2025-06-21T12:22:33.629Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/62/d68e52fb6fde5586650d4c0ce0b05ff3a48ad4df4ffd1b8866479d1d671d/numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c", size = 14331561, upload-time = "2025-06-21T12:22:55.056Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/ec/b74d3f2430960044bdad6900d9f5edc2dc0fb8bf5a0be0f65287bf2cbe27/numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48", size = 16693349, upload-time = "2025-06-21T12:23:20.53Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/15/def96774b9d7eb198ddadfcbd20281b20ebb510580419197e225f5c55c3e/numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee", size = 15642053, upload-time = "2025-06-21T12:23:43.697Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/57/c3203974762a759540c6ae71d0ea2341c1fa41d84e4971a8e76d7141678a/numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280", size = 18434184, upload-time = "2025-06-21T12:24:10.708Z" },
+ { url = "https://files.pythonhosted.org/packages/22/8a/ccdf201457ed8ac6245187850aff4ca56a79edbea4829f4e9f14d46fa9a5/numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e", size = 6440678, upload-time = "2025-06-21T12:24:21.596Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/7e/7f431d8bd8eb7e03d79294aed238b1b0b174b3148570d03a8a8a8f6a0da9/numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc", size = 12870697, upload-time = "2025-06-21T12:24:40.644Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/ca/af82bf0fad4c3e573c6930ed743b5308492ff19917c7caaf2f9b6f9e2e98/numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244", size = 10260376, upload-time = "2025-06-21T12:24:56.884Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/34/facc13b9b42ddca30498fc51f7f73c3d0f2be179943a4b4da8686e259740/numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3", size = 21070637, upload-time = "2025-06-21T12:26:12.518Z" },
+ { url = "https://files.pythonhosted.org/packages/65/b6/41b705d9dbae04649b529fc9bd3387664c3281c7cd78b404a4efe73dcc45/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b", size = 5304087, upload-time = "2025-06-21T12:26:22.294Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/fe3ac1902bff7a4934a22d49e1c9d71a623204d654d4cc43c6e8fe337fcb/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7", size = 6817588, upload-time = "2025-06-21T12:26:32.939Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/ee/89bedf69c36ace1ac8f59e97811c1f5031e179a37e4821c3a230bf750142/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df", size = 14399010, upload-time = "2025-06-21T12:26:54.086Z" },
+ { url = "https://files.pythonhosted.org/packages/15/08/e00e7070ede29b2b176165eba18d6f9784d5349be3c0c1218338e79c27fd/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68", size = 16752042, upload-time = "2025-06-21T12:27:19.018Z" },
+ { url = "https://files.pythonhosted.org/packages/48/6b/1c6b515a83d5564b1698a61efa245727c8feecf308f4091f565988519d20/numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb", size = 12927246, upload-time = "2025-06-21T12:27:38.618Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "11.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/8b/b158ad57ed44d3cc54db8d68ad7c0a58b8fc0e4c7a3f995f9d62d5b464a1/pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047", size = 3198442, upload-time = "2025-04-12T17:47:10.666Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f8/bb5d956142f86c2d6cc36704943fa761f2d2e4c48b7436fd0a85c20f1713/pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95", size = 3030553, upload-time = "2025-04-12T17:47:13.153Z" },
+ { url = "https://files.pythonhosted.org/packages/22/7f/0e413bb3e2aa797b9ca2c5c38cb2e2e45d88654e5b12da91ad446964cfae/pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61", size = 4405503, upload-time = "2025-04-12T17:47:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/b4/cc647f4d13f3eb837d3065824aa58b9bcf10821f029dc79955ee43f793bd/pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1", size = 4490648, upload-time = "2025-04-12T17:47:17.37Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/6f/240b772a3b35cdd7384166461567aa6713799b4e78d180c555bd284844ea/pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c", size = 4508937, upload-time = "2025-04-12T17:47:19.066Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/5e/7ca9c815ade5fdca18853db86d812f2f188212792780208bdb37a0a6aef4/pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d", size = 4599802, upload-time = "2025-04-12T17:47:21.404Z" },
+ { url = "https://files.pythonhosted.org/packages/02/81/c3d9d38ce0c4878a77245d4cf2c46d45a4ad0f93000227910a46caff52f3/pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97", size = 4576717, upload-time = "2025-04-12T17:47:23.571Z" },
+ { url = "https://files.pythonhosted.org/packages/42/49/52b719b89ac7da3185b8d29c94d0e6aec8140059e3d8adcaa46da3751180/pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579", size = 4654874, upload-time = "2025-04-12T17:47:25.783Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/0b/ede75063ba6023798267023dc0d0401f13695d228194d2242d5a7ba2f964/pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d", size = 2331717, upload-time = "2025-04-12T17:47:28.922Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/3c/9831da3edea527c2ed9a09f31a2c04e77cd705847f13b69ca60269eec370/pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad", size = 2676204, upload-time = "2025-04-12T17:47:31.283Z" },
+ { url = "https://files.pythonhosted.org/packages/01/97/1f66ff8a1503d8cbfc5bae4dc99d54c6ec1e22ad2b946241365320caabc2/pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2", size = 2414767, upload-time = "2025-04-12T17:47:34.655Z" },
+ { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" },
+ { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" },
+ { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" },
+ { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" },
+ { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" },
+ { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" },
+ { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" },
+ { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" },
+ { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" },
+ { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" },
+ { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" },
+ { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" },
+ { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" },
+ { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" },
+ { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" },
+ { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" },
+ { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" },
+ { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" },
+ { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" },
+ { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" },
+ { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" },
+ { url = "https://files.pythonhosted.org/packages/21/3a/c1835d1c7cf83559e95b4f4ed07ab0bb7acc689712adfce406b3f456e9fd/pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8", size = 3198391, upload-time = "2025-04-12T17:49:10.122Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/4d/dcb7a9af3fc1e8653267c38ed622605d9d1793349274b3ef7af06457e257/pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909", size = 3030573, upload-time = "2025-04-12T17:49:11.938Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/29/530ca098c1a1eb31d4e163d317d0e24e6d2ead907991c69ca5b663de1bc5/pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928", size = 4398677, upload-time = "2025-04-12T17:49:13.861Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ee/0e5e51db34de1690264e5f30dcd25328c540aa11d50a3bc0b540e2a445b6/pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79", size = 4484986, upload-time = "2025-04-12T17:49:15.948Z" },
+ { url = "https://files.pythonhosted.org/packages/93/7d/bc723b41ce3d2c28532c47678ec988974f731b5c6fadd5b3a4fba9015e4f/pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35", size = 4501897, upload-time = "2025-04-12T17:49:17.839Z" },
+ { url = "https://files.pythonhosted.org/packages/be/0b/532e31abc7389617ddff12551af625a9b03cd61d2989fa595e43c470ec67/pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb", size = 4592618, upload-time = "2025-04-12T17:49:19.7Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/f0/21ed6499a6216fef753e2e2254a19d08bff3747108ba042422383f3e9faa/pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a", size = 4570493, upload-time = "2025-04-12T17:49:21.703Z" },
+ { url = "https://files.pythonhosted.org/packages/68/de/17004ddb8ab855573fe1127ab0168d11378cdfe4a7ee2a792a70ff2e9ba7/pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36", size = 4647748, upload-time = "2025-04-12T17:49:23.579Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/23/82ecb486384bb3578115c509d4a00bb52f463ee700a5ca1be53da3c88c19/pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67", size = 2331731, upload-time = "2025-04-12T17:49:25.58Z" },
+ { url = "https://files.pythonhosted.org/packages/58/bb/87efd58b3689537a623d44dbb2550ef0bb5ff6a62769707a0fe8b1a7bdeb/pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1", size = 2676346, upload-time = "2025-04-12T17:49:27.342Z" },
+ { url = "https://files.pythonhosted.org/packages/80/08/dc268475b22887b816e5dcfae31bce897f524b4646bab130c2142c9b2400/pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e", size = 2414623, upload-time = "2025-04-12T17:49:29.139Z" },
+ { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727, upload-time = "2025-04-12T17:49:31.898Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833, upload-time = "2025-04-12T17:49:34.2Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472, upload-time = "2025-04-12T17:49:36.294Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/1b/e35d8a158e21372ecc48aac9c453518cfe23907bb82f950d6e1c72811eb0/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0", size = 3459976, upload-time = "2025-04-12T17:49:38.988Z" },
+ { url = "https://files.pythonhosted.org/packages/26/da/2c11d03b765efff0ccc473f1c4186dc2770110464f2177efaed9cf6fae01/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01", size = 3527133, upload-time = "2025-04-12T17:49:40.985Z" },
+ { url = "https://files.pythonhosted.org/packages/79/1a/4e85bd7cadf78412c2a3069249a09c32ef3323650fd3005c97cca7aa21df/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193", size = 3571555, upload-time = "2025-04-12T17:49:42.964Z" },
+ { url = "https://files.pythonhosted.org/packages/69/03/239939915216de1e95e0ce2334bf17a7870ae185eb390fab6d706aadbfc0/pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013", size = 2674713, upload-time = "2025-04-12T17:49:44.944Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" },
+ { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" },
+ { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "portpicker"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "psutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4d/d0/cda2fc582f09510c84cd6b7d7b9e22a02d4e45dbad2b2ef1c6edd7847e00/portpicker-1.6.0.tar.gz", hash = "sha256:bd507fd6f96f65ee02781f2e674e9dc6c99bbfa6e3c39992e3916204c9d431fa", size = 25676, upload-time = "2023-08-15T04:37:08.865Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/2d/440e4d7041fff89f28f483733eb617127aa866135c2dc719e05893f089e1/portpicker-1.6.0-py3-none-any.whl", hash = "sha256:b2787a41404cf7edbe29b07b9e0ed863b09f2665dcc01c1eb0c2261c1e7d0755", size = 16613, upload-time = "2023-08-15T04:37:07.327Z" },
+]
+
+[[package]]
+name = "pre-commit"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cfgv" },
+ { name = "identify" },
+ { name = "nodeenv" },
+ { name = "pyyaml" },
+ { name = "virtualenv" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
+]
+
+[[package]]
+name = "propcache"
+version = "0.3.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" },
+ { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" },
+ { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" },
+ { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" },
+ { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" },
+ { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" },
+ { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" },
+ { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" },
+ { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" },
+ { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" },
+ { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" },
+ { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" },
+ { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" },
+ { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" },
+ { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" },
+ { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" },
+ { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" },
+ { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" },
+ { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" },
+ { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" },
+ { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" },
+ { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" },
+ { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" },
+ { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" },
+ { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" },
+ { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/39/8ea9bcfaaff16fd0b0fc901ee522e24c9ec44b4ca0229cfffb8066a06959/propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5", size = 74678, upload-time = "2025-06-09T22:55:41.227Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/85/cab84c86966e1d354cf90cdc4ba52f32f99a5bca92a1529d666d957d7686/propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4", size = 43829, upload-time = "2025-06-09T22:55:42.417Z" },
+ { url = "https://files.pythonhosted.org/packages/23/f7/9cb719749152d8b26d63801b3220ce2d3931312b2744d2b3a088b0ee9947/propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2", size = 43729, upload-time = "2025-06-09T22:55:43.651Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/a2/0b2b5a210ff311260002a315f6f9531b65a36064dfb804655432b2f7d3e3/propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d", size = 204483, upload-time = "2025-06-09T22:55:45.327Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/e0/7aff5de0c535f783b0c8be5bdb750c305c1961d69fbb136939926e155d98/propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec", size = 217425, upload-time = "2025-06-09T22:55:46.729Z" },
+ { url = "https://files.pythonhosted.org/packages/92/1d/65fa889eb3b2a7d6e4ed3c2b568a9cb8817547a1450b572de7bf24872800/propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701", size = 214723, upload-time = "2025-06-09T22:55:48.342Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e2/eecf6989870988dfd731de408a6fa366e853d361a06c2133b5878ce821ad/propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef", size = 200166, upload-time = "2025-06-09T22:55:49.775Z" },
+ { url = "https://files.pythonhosted.org/packages/12/06/c32be4950967f18f77489268488c7cdc78cbfc65a8ba8101b15e526b83dc/propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1", size = 194004, upload-time = "2025-06-09T22:55:51.335Z" },
+ { url = "https://files.pythonhosted.org/packages/46/6c/17b521a6b3b7cbe277a4064ff0aa9129dd8c89f425a5a9b6b4dd51cc3ff4/propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886", size = 203075, upload-time = "2025-06-09T22:55:52.681Z" },
+ { url = "https://files.pythonhosted.org/packages/62/cb/3bdba2b736b3e45bc0e40f4370f745b3e711d439ffbffe3ae416393eece9/propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b", size = 195407, upload-time = "2025-06-09T22:55:54.048Z" },
+ { url = "https://files.pythonhosted.org/packages/29/bd/760c5c6a60a4a2c55a421bc34a25ba3919d49dee411ddb9d1493bb51d46e/propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb", size = 196045, upload-time = "2025-06-09T22:55:55.485Z" },
+ { url = "https://files.pythonhosted.org/packages/76/58/ced2757a46f55b8c84358d6ab8de4faf57cba831c51e823654da7144b13a/propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea", size = 208432, upload-time = "2025-06-09T22:55:56.884Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/ec/d98ea8d5a4d8fe0e372033f5254eddf3254344c0c5dc6c49ab84349e4733/propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb", size = 210100, upload-time = "2025-06-09T22:55:58.498Z" },
+ { url = "https://files.pythonhosted.org/packages/56/84/b6d8a7ecf3f62d7dd09d9d10bbf89fad6837970ef868b35b5ffa0d24d9de/propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe", size = 200712, upload-time = "2025-06-09T22:55:59.906Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/32/889f4903ddfe4a9dc61da71ee58b763758cf2d608fe1decede06e6467f8d/propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1", size = 38187, upload-time = "2025-06-09T22:56:01.212Z" },
+ { url = "https://files.pythonhosted.org/packages/67/74/d666795fb9ba1dc139d30de64f3b6fd1ff9c9d3d96ccfdb992cd715ce5d2/propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9", size = 42025, upload-time = "2025-06-09T22:56:02.875Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" },
+]
+
+[[package]]
+name = "protobuf"
+version = "3.20.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/55/5b/e3d951e34f8356e5feecacd12a8e3b258a1da6d9a03ad1770f28925f29bc/protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2", size = 216768, upload-time = "2022-09-29T22:39:47.592Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/28/55/b80e8567ec327c060fa39b242392e25690c8899c489ecd7bb65b46b7bb55/protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99", size = 918427, upload-time = "2022-09-29T22:10:52.591Z" },
+ { url = "https://files.pythonhosted.org/packages/31/be/80a9c6f16dfa4d41be3edbe655349778ae30882407fa8275eb46b4d34854/protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e", size = 1051042, upload-time = "2022-09-29T22:10:54.629Z" },
+ { url = "https://files.pythonhosted.org/packages/db/96/948d3fcc1fa816e7ae1d27af59b9d8c5c5e582f3994fd14394f31da95b99/protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c", size = 780167, upload-time = "2022-09-29T22:10:56.276Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/5e/fc6feb366b0a9f28e0a2de3b062667c521cd9517d4ff55077b8f351ba2f3/protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7", size = 904029, upload-time = "2022-09-29T22:10:57.866Z" },
+ { url = "https://files.pythonhosted.org/packages/00/e7/d23c439c55c90ae2e52184363162f7079ca3e7d86205b411d4e9dc266f81/protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b", size = 982826, upload-time = "2022-09-29T22:11:20.978Z" },
+ { url = "https://files.pythonhosted.org/packages/99/25/5825472ecd911f4ac2ac4e9ab039a48b6d03874e2add92fb633e080bf3eb/protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b", size = 918423, upload-time = "2022-09-29T22:11:22.651Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/df/ec3ecb8c940b36121c7b77c10acebf3d1c736498aa2f1fe3b6231ee44e76/protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402", size = 1019250, upload-time = "2022-09-29T22:11:24.259Z" },
+ { url = "https://files.pythonhosted.org/packages/36/8b/433071fed0058322090a55021bdc8da76d16c7bc9823f5795797803dd6d0/protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480", size = 780270, upload-time = "2022-09-29T22:11:25.927Z" },
+ { url = "https://files.pythonhosted.org/packages/11/a5/e52b731415ad6ef3d841e9e6e337a690249e800cc7c06f0749afab26348c/protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7", size = 904215, upload-time = "2022-09-29T22:11:28.056Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/14/619e24a4c70df2901e1f4dbc50a6291eb63a759172558df326347dce1f0d/protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db", size = 162128, upload-time = "2022-09-29T22:39:44.547Z" },
+]
+
+[[package]]
+name = "psutil"
+version = "7.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" },
+ { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" },
+ { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
+]
+
+[[package]]
+name = "py-cpuinfo"
+version = "9.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" },
+]
+
+[[package]]
+name = "pydata-sphinx-theme"
+version = "0.15.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "accessible-pygments" },
+ { name = "babel" },
+ { name = "beautifulsoup4" },
+ { name = "docutils" },
+ { name = "packaging" },
+ { name = "pygments" },
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/67/ea/3ab478cccacc2e8ef69892c42c44ae547bae089f356c4b47caf61730958d/pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d", size = 2400673, upload-time = "2024-06-25T19:28:45.041Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157, upload-time = "2024-06-25T19:28:42.383Z" },
+]
+
+[[package]]
+name = "pyglet"
+version = "2.1.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f7/bc/0533ccb30566ee59b540d700dbbf916dafa89132a4d582d0fd1fe158243d/pyglet-2.1.6.tar.gz", hash = "sha256:18483880b1411b39692eaf7756819285797b1aaf9ef63d40eb9f9b5d01c63416", size = 6546705, upload-time = "2025-04-27T01:12:30.995Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/ad/e16f9b56c4a935934341e385753d0d0a2a83b7d320e52906b44f32698feb/pyglet-2.1.6-py3-none-any.whl", hash = "sha256:52ef9e75f3969b6a28bfa5c223e50ff03a05c2baa67bfe00d2a9eec4e831a7c5", size = 983998, upload-time = "2025-04-27T01:12:26.307Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pylint"
+version = "3.3.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "astroid" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "dill" },
+ { name = "isort" },
+ { name = "mccabe" },
+ { name = "platformdirs" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "tomlkit" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1c/e4/83e487d3ddd64ab27749b66137b26dc0c5b5c161be680e6beffdc99070b3/pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", size = 1520709, upload-time = "2025-05-04T17:07:51.089Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e8/83/bff755d09e31b5d25cc7fdc4bf3915d1a404e181f1abf0359af376845c24/pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d", size = 522565, upload-time = "2025-05-04T17:07:48.714Z" },
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" },
+]
+
+[[package]]
+name = "pyrefly"
+version = "0.21.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/46/3f/8a30ed93cb027a18080d7e670b2bbf14135b031fe74443eaa850494d9aa8/pyrefly-0.21.0.tar.gz", hash = "sha256:e05a083047dcba25e730c7e0c70b3dc48ba420f17ef73265f169bc95f487a99d", size = 1056016, upload-time = "2025-06-23T17:45:22.033Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/dd/5b1a4a3a713be65e2af02563f8baa70ea69d594d681cb1c36b38319eee90/pyrefly-0.21.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b44e6172421de12fa14d380bcd416fa64d474aec9f1829e6985b18471b1fcee1", size = 5820971, upload-time = "2025-06-23T17:45:04.928Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/40/df55322e761b798903c951ad5699585046f9e926e6b3b6686cb0056024a4/pyrefly-0.21.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0d7ea3b86ac3c8680b290389ca3706cbbb046899247a963ac7384c57880e6f2f", size = 5404314, upload-time = "2025-06-23T17:45:07.007Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/a5/b3d526bf75ab8708cc85cd0db571af0179b566964fe2c9aae717f3a03090/pyrefly-0.21.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:101ecb84b0442e85a92bcb15a2dfd844f7a6abae8f980661849c71102447443e", size = 5611017, upload-time = "2025-06-23T17:45:08.989Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/02/4d4b0ddade7e2980a13e14062770535666a84dfab3293c33e154dfff6ae1/pyrefly-0.21.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b934004ddcdcbe55efeb732d0c0155a781a184e374fec9cd031e819ac1b92eff", size = 6285992, upload-time = "2025-06-23T17:45:11.991Z" },
+ { url = "https://files.pythonhosted.org/packages/29/7c/2c3922ee3bdd82a827a893a80aeddf119e38ce8406035a6eda6ae480885e/pyrefly-0.21.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c66446bc5f7e912dab923adf93f636307c834ab53ddd9422e56d101910d960a", size = 6066455, upload-time = "2025-06-23T17:45:13.587Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/c8/c8d391f3535046cb79154d4dfcc01b0d022d5af2701627cbe9a518dd50bc/pyrefly-0.21.0-py3-none-win32.whl", hash = "sha256:a4cb8acf2dc831759cb43fa0326e07085cb21da7202212e70d9478bfc40b7a28", size = 5569924, upload-time = "2025-06-23T17:45:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/85/79/58c94192acbc234ff9290c22063bf8f11d65a7b61eb61f9260fa6fb4b1f3/pyrefly-0.21.0-py3-none-win_amd64.whl", hash = "sha256:765d19f2b48d5dd3dae0752676e2d6e388025fa6337947031ff628160b8cf568", size = 5946646, upload-time = "2025-06-23T17:45:17.433Z" },
+ { url = "https://files.pythonhosted.org/packages/75/6e/d476584e93e3c63609dde26a57bc23f732c22b0e9bd17462282268aec758/pyrefly-0.21.0-py3-none-win_arm64.whl", hash = "sha256:a3fc4fffb625a5610b68fc3bf07e4d33b5b1c279512e0251b3d3d4f7c3ed1541", size = 5595515, upload-time = "2025-06-23T17:45:19.4Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" },
+]
+
+[[package]]
+name = "pytest-benchmark"
+version = "5.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "py-cpuinfo" },
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/39/d0/a8bd08d641b393db3be3819b03e2d9bb8760ca8479080a26a5f6e540e99c/pytest-benchmark-5.1.0.tar.gz", hash = "sha256:9ea661cdc292e8231f7cd4c10b0319e56a2118e2c09d9f50e1b3d150d2aca105", size = 337810, upload-time = "2024-10-30T11:51:48.521Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/d6/b41653199ea09d5969d4e385df9bbfd9a100f28ca7e824ce7c0a016e3053/pytest_benchmark-5.1.0-py3-none-any.whl", hash = "sha256:922de2dfa3033c227c96da942d1878191afa135a29485fb942e85dff1c592c89", size = 44259, upload-time = "2024-10-30T11:51:45.94Z" },
+]
+
+[[package]]
+name = "pytest-cov"
+version = "6.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "coverage", extra = ["toml"] },
+ { name = "pluggy" },
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" },
+ { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" },
+ { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
+ { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
+ { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
+ { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
+ { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
+ { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
+ { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
+ { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
+ { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
+ { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
+ { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
+ { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" },
+ { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" },
+ { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" },
+]
+
+[[package]]
+name = "radon"
+version = "6.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama" },
+ { name = "mando" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/6d/98e61600febf6bd929cf04154537c39dc577ce414bafbfc24a286c4fa76d/radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5", size = 1874992, upload-time = "2023-03-26T06:24:38.868Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859", size = 52784, upload-time = "2023-03-26T06:24:33.949Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
+]
+
+[[package]]
+name = "roman-numerals-py"
+version = "3.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426, upload-time = "2025-06-26T20:34:14.784Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649, upload-time = "2025-06-26T20:33:39.242Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201, upload-time = "2025-06-26T20:33:42.207Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769, upload-time = "2025-06-26T20:33:44.102Z" },
+ { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902, upload-time = "2025-06-26T20:33:45.98Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002, upload-time = "2025-06-26T20:33:47.81Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522, upload-time = "2025-06-26T20:33:49.857Z" },
+ { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264, upload-time = "2025-06-26T20:33:52.199Z" },
+ { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882, upload-time = "2025-06-26T20:33:54.231Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941, upload-time = "2025-06-26T20:33:56.202Z" },
+ { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887, upload-time = "2025-06-26T20:33:58.47Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742, upload-time = "2025-06-26T20:34:00.465Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909, upload-time = "2025-06-26T20:34:02.603Z" },
+ { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005, upload-time = "2025-06-26T20:34:04.723Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579, upload-time = "2025-06-26T20:34:06.766Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495, upload-time = "2025-06-26T20:34:08.718Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485, upload-time = "2025-06-26T20:34:11.008Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209, upload-time = "2025-06-26T20:34:12.928Z" },
+]
+
+[[package]]
+name = "s2clientprotocol"
+version = "5.0.14.93333.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c0/11/33ab328601a07619448b3c1e1a49b886677766af0c9ad516fe5ff733624a/s2clientprotocol-5.0.14.93333.0-py2.py3-none-any.whl", hash = "sha256:f3d40aa76f78c51e0cad8efdb220910d4d0540fa00ed1b4cdfe7994f6ffa238d", size = 55607, upload-time = "2024-12-04T23:53:22.186Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.13.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076, upload-time = "2024-05-23T03:19:01.687Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232, upload-time = "2024-05-23T03:19:09.089Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202, upload-time = "2024-05-23T03:19:15.138Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335, upload-time = "2024-05-23T03:19:21.984Z" },
+ { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728, upload-time = "2024-05-23T03:19:28.225Z" },
+ { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588, upload-time = "2024-05-23T03:19:35.661Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805, upload-time = "2024-05-23T03:19:43.081Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687, upload-time = "2024-05-23T03:19:48.799Z" },
+ { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638, upload-time = "2024-05-23T03:19:55.104Z" },
+ { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931, upload-time = "2024-05-23T03:20:01.82Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145, upload-time = "2024-05-23T03:20:09.173Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227, upload-time = "2024-05-23T03:20:16.433Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301, upload-time = "2024-05-23T03:20:23.538Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348, upload-time = "2024-05-23T03:20:29.885Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062, upload-time = "2024-05-23T03:20:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311, upload-time = "2024-05-23T03:20:42.086Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493, upload-time = "2024-05-23T03:20:48.292Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955, upload-time = "2024-05-23T03:20:55.091Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927, upload-time = "2024-05-23T03:21:01.95Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538, upload-time = "2024-05-23T03:21:07.634Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190, upload-time = "2024-05-23T03:21:14.41Z" },
+ { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244, upload-time = "2024-05-23T03:21:21.827Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637, upload-time = "2024-05-23T03:21:28.729Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440, upload-time = "2024-05-23T03:21:35.888Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.15.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" },
+ { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" },
+ { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" },
+ { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" },
+ { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" },
+ { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" },
+ { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" },
+ { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" },
+ { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" },
+ { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" },
+ { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" },
+ { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" },
+ { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" },
+ { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" },
+ { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" },
+ { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" },
+ { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" },
+ { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" },
+ { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" },
+ { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.16.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+]
+dependencies = [
+ { name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/81/18/b06a83f0c5ee8cddbde5e3f3d0bb9b702abfa5136ef6d4620ff67df7eee5/scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62", size = 30581216, upload-time = "2025-06-22T16:27:55.782Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d9/f8/53fc4884df6b88afd5f5f00240bdc49fee2999c7eff3acf5953eb15bc6f8/scipy-1.16.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:deec06d831b8f6b5fb0b652433be6a09db29e996368ce5911faf673e78d20085", size = 36447362, upload-time = "2025-06-22T16:18:17.817Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/25/fad8aa228fa828705142a275fc593d701b1817c98361a2d6b526167d07bc/scipy-1.16.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d30c0fe579bb901c61ab4bb7f3eeb7281f0d4c4a7b52dbf563c89da4fd2949be", size = 28547120, upload-time = "2025-06-22T16:18:24.117Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/be/d324ddf6b89fd1c32fecc307f04d095ce84abb52d2e88fab29d0cd8dc7a8/scipy-1.16.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:b2243561b45257f7391d0f49972fca90d46b79b8dbcb9b2cb0f9df928d370ad4", size = 20818922, upload-time = "2025-06-22T16:18:28.035Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/e0/cf3f39e399ac83fd0f3ba81ccc5438baba7cfe02176be0da55ff3396f126/scipy-1.16.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e6d7dfc148135e9712d87c5f7e4f2ddc1304d1582cb3a7d698bbadedb61c7afd", size = 23409695, upload-time = "2025-06-22T16:18:32.497Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/61/d92714489c511d3ffd6830ac0eb7f74f243679119eed8b9048e56b9525a1/scipy-1.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90452f6a9f3fe5a2cf3748e7be14f9cc7d9b124dce19667b54f5b429d680d539", size = 33444586, upload-time = "2025-06-22T16:18:37.992Z" },
+ { url = "https://files.pythonhosted.org/packages/af/2c/40108915fd340c830aee332bb85a9160f99e90893e58008b659b9f3dddc0/scipy-1.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a2f0bf2f58031c8701a8b601df41701d2a7be17c7ffac0a4816aeba89c4cdac8", size = 35284126, upload-time = "2025-06-22T16:18:43.605Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/30/e9eb0ad3d0858df35d6c703cba0a7e16a18a56a9e6b211d861fc6f261c5f/scipy-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c4abb4c11fc0b857474241b812ce69ffa6464b4bd8f4ecb786cf240367a36a7", size = 35608257, upload-time = "2025-06-22T16:18:49.09Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/ff/950ee3e0d612b375110d8cda211c1f787764b4c75e418a4b71f4a5b1e07f/scipy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b370f8f6ac6ef99815b0d5c9f02e7ade77b33007d74802efc8316c8db98fd11e", size = 38040541, upload-time = "2025-06-22T16:18:55.077Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/c9/750d34788288d64ffbc94fdb4562f40f609d3f5ef27ab4f3a4ad00c9033e/scipy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:a16ba90847249bedce8aa404a83fb8334b825ec4a8e742ce6012a7a5e639f95c", size = 38570814, upload-time = "2025-06-22T16:19:00.912Z" },
+ { url = "https://files.pythonhosted.org/packages/01/c0/c943bc8d2bbd28123ad0f4f1eef62525fa1723e84d136b32965dcb6bad3a/scipy-1.16.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7eb6bd33cef4afb9fa5f1fb25df8feeb1e52d94f21a44f1d17805b41b1da3180", size = 36459071, upload-time = "2025-06-22T16:19:06.605Z" },
+ { url = "https://files.pythonhosted.org/packages/99/0d/270e2e9f1a4db6ffbf84c9a0b648499842046e4e0d9b2275d150711b3aba/scipy-1.16.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1dbc8fdba23e4d80394ddfab7a56808e3e6489176d559c6c71935b11a2d59db1", size = 28490500, upload-time = "2025-06-22T16:19:11.775Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/22/01d7ddb07cff937d4326198ec8d10831367a708c3da72dfd9b7ceaf13028/scipy-1.16.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7dcf42c380e1e3737b343dec21095c9a9ad3f9cbe06f9c05830b44b1786c9e90", size = 20762345, upload-time = "2025-06-22T16:19:15.813Z" },
+ { url = "https://files.pythonhosted.org/packages/34/7f/87fd69856569ccdd2a5873fe5d7b5bbf2ad9289d7311d6a3605ebde3a94b/scipy-1.16.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26ec28675f4a9d41587266084c626b02899db373717d9312fa96ab17ca1ae94d", size = 23418563, upload-time = "2025-06-22T16:19:20.746Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/f1/e4f4324fef7f54160ab749efbab6a4bf43678a9eb2e9817ed71a0a2fd8de/scipy-1.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:952358b7e58bd3197cfbd2f2f2ba829f258404bdf5db59514b515a8fe7a36c52", size = 33203951, upload-time = "2025-06-22T16:19:25.813Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f0/b6ac354a956384fd8abee2debbb624648125b298f2c4a7b4f0d6248048a5/scipy-1.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03931b4e870c6fef5b5c0970d52c9f6ddd8c8d3e934a98f09308377eba6f3824", size = 35070225, upload-time = "2025-06-22T16:19:31.416Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/73/5cbe4a3fd4bc3e2d67ffad02c88b83edc88f381b73ab982f48f3df1a7790/scipy-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:512c4f4f85912767c351a0306824ccca6fd91307a9f4318efe8fdbd9d30562ef", size = 35389070, upload-time = "2025-06-22T16:19:37.387Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e8/a60da80ab9ed68b31ea5a9c6dfd3c2f199347429f229bf7f939a90d96383/scipy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e69f798847e9add03d512eaf5081a9a5c9a98757d12e52e6186ed9681247a1ac", size = 37825287, upload-time = "2025-06-22T16:19:43.375Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/b5/29fece1a74c6a94247f8a6fb93f5b28b533338e9c34fdcc9cfe7a939a767/scipy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:adf9b1999323ba335adc5d1dc7add4781cb5a4b0ef1e98b79768c05c796c4e49", size = 38431929, upload-time = "2025-06-22T16:19:49.385Z" },
+ { url = "https://files.pythonhosted.org/packages/46/95/0746417bc24be0c2a7b7563946d61f670a3b491b76adede420e9d173841f/scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451", size = 36418162, upload-time = "2025-06-22T16:19:56.3Z" },
+ { url = "https://files.pythonhosted.org/packages/19/5a/914355a74481b8e4bbccf67259bbde171348a3f160b67b4945fbc5f5c1e5/scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e", size = 28465985, upload-time = "2025-06-22T16:20:01.238Z" },
+ { url = "https://files.pythonhosted.org/packages/58/46/63477fc1246063855969cbefdcee8c648ba4b17f67370bd542ba56368d0b/scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974", size = 20737961, upload-time = "2025-06-22T16:20:05.913Z" },
+ { url = "https://files.pythonhosted.org/packages/93/86/0fbb5588b73555e40f9d3d6dde24ee6fac7d8e301a27f6f0cab9d8f66ff2/scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc", size = 23377941, upload-time = "2025-06-22T16:20:10.668Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/80/a561f2bf4c2da89fa631b3cbf31d120e21ea95db71fd9ec00cb0247c7a93/scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351", size = 33196703, upload-time = "2025-06-22T16:20:16.097Z" },
+ { url = "https://files.pythonhosted.org/packages/11/6b/3443abcd0707d52e48eb315e33cc669a95e29fc102229919646f5a501171/scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32", size = 35083410, upload-time = "2025-06-22T16:20:21.734Z" },
+ { url = "https://files.pythonhosted.org/packages/20/ab/eb0fc00e1e48961f1bd69b7ad7e7266896fe5bad4ead91b5fc6b3561bba4/scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b", size = 35387829, upload-time = "2025-06-22T16:20:27.548Z" },
+ { url = "https://files.pythonhosted.org/packages/57/9e/d6fc64e41fad5d481c029ee5a49eefc17f0b8071d636a02ceee44d4a0de2/scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358", size = 37841356, upload-time = "2025-06-22T16:20:35.112Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/a7/4c94bbe91f12126b8bf6709b2471900577b7373a4fd1f431f28ba6f81115/scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe", size = 38403710, upload-time = "2025-06-22T16:21:54.473Z" },
+ { url = "https://files.pythonhosted.org/packages/47/20/965da8497f6226e8fa90ad3447b82ed0e28d942532e92dd8b91b43f100d4/scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47", size = 36813833, upload-time = "2025-06-22T16:20:43.925Z" },
+ { url = "https://files.pythonhosted.org/packages/28/f4/197580c3dac2d234e948806e164601c2df6f0078ed9f5ad4a62685b7c331/scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08", size = 28974431, upload-time = "2025-06-22T16:20:51.302Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/fc/e18b8550048d9224426e76906694c60028dbdb65d28b1372b5503914b89d/scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176", size = 21246454, upload-time = "2025-06-22T16:20:57.276Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/48/07b97d167e0d6a324bfd7484cd0c209cc27338b67e5deadae578cf48e809/scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed", size = 23772979, upload-time = "2025-06-22T16:21:03.363Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/4f/9efbd3f70baf9582edf271db3002b7882c875ddd37dc97f0f675ad68679f/scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938", size = 33341972, upload-time = "2025-06-22T16:21:11.14Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/dc/9e496a3c5dbe24e76ee24525155ab7f659c20180bab058ef2c5fa7d9119c/scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1", size = 35185476, upload-time = "2025-06-22T16:21:19.156Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/b3/21001cff985a122ba434c33f2c9d7d1dc3b669827e94f4fc4e1fe8b9dfd8/scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706", size = 35570990, upload-time = "2025-06-22T16:21:27.797Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/d3/7ba42647d6709251cdf97043d0c107e0317e152fa2f76873b656b509ff55/scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e", size = 37950262, upload-time = "2025-06-22T16:21:36.976Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/c4/231cac7a8385394ebbbb4f1ca662203e9d8c332825ab4f36ffc3ead09a42/scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db", size = 38515076, upload-time = "2025-06-22T16:21:45.694Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "7.4.7"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "babel", marker = "python_full_version < '3.10'" },
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version < '3.10'" },
+ { name = "imagesize", marker = "python_full_version < '3.10'" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2", marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pygments", marker = "python_full_version < '3.10'" },
+ { name = "requests", marker = "python_full_version < '3.10'" },
+ { name = "snowballstemmer", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.10'" },
+ { name = "tomli", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "8.1.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "babel", marker = "python_full_version == '3.10.*'" },
+ { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version == '3.10.*'" },
+ { name = "imagesize", marker = "python_full_version == '3.10.*'" },
+ { name = "jinja2", marker = "python_full_version == '3.10.*'" },
+ { name = "packaging", marker = "python_full_version == '3.10.*'" },
+ { name = "pygments", marker = "python_full_version == '3.10.*'" },
+ { name = "requests", marker = "python_full_version == '3.10.*'" },
+ { name = "snowballstemmer", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.10.*'" },
+ { name = "tomli", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "8.2.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+]
+dependencies = [
+ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "babel", marker = "python_full_version >= '3.11'" },
+ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version >= '3.11'" },
+ { name = "imagesize", marker = "python_full_version >= '3.11'" },
+ { name = "jinja2", marker = "python_full_version >= '3.11'" },
+ { name = "packaging", marker = "python_full_version >= '3.11'" },
+ { name = "pygments", marker = "python_full_version >= '3.11'" },
+ { name = "requests", marker = "python_full_version >= '3.11'" },
+ { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" },
+ { name = "snowballstemmer", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" },
+]
+
+[[package]]
+name = "sphinx-autodoc-typehints"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/cd/03e7b917230dc057922130a79ba0240df1693bfd76727ea33fae84b39138/sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084", size = 40709, upload-time = "2024-08-29T16:25:48.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/f3/e0a4ce49da4b6f4e4ce84b3c39a0677831884cb9d8a87ccbf1e9e56e53ac/sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67", size = 19836, upload-time = "2024-08-29T16:25:46.707Z" },
+]
+
+[[package]]
+name = "sphinx-autodoc-typehints"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/26/f0/43c6a5ff3e7b08a8c3b32f81b859f1b518ccc31e45f22e2b41ced38be7b9/sphinx_autodoc_typehints-3.0.1.tar.gz", hash = "sha256:b9b40dd15dee54f6f810c924f863f9cf1c54f9f3265c495140ea01be7f44fa55", size = 36282, upload-time = "2025-01-16T18:25:30.958Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3c/dc/dc46c5c7c566b7ec5e8f860f9c89533bf03c0e6aadc96fb9b337867e4460/sphinx_autodoc_typehints-3.0.1-py3-none-any.whl", hash = "sha256:4b64b676a14b5b79cefb6628a6dc8070e320d4963e8ff640a2f3e9390ae9045a", size = 20245, upload-time = "2025-01-16T18:25:27.394Z" },
+]
+
+[[package]]
+name = "sphinx-autodoc-typehints"
+version = "3.2.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version == '3.12.*'",
+ "python_full_version == '3.11.*'",
+]
+dependencies = [
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/93/68/a388a9b8f066cd865d9daa65af589d097efbfab9a8c302d2cb2daa43b52e/sphinx_autodoc_typehints-3.2.0.tar.gz", hash = "sha256:107ac98bc8b4837202c88c0736d59d6da44076e65a0d7d7d543a78631f662a9b", size = 36724, upload-time = "2025-04-25T16:53:25.872Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/c7/8aab362e86cbf887e58be749a78d20ad743e1eb2c73c2b13d4761f39a104/sphinx_autodoc_typehints-3.2.0-py3-none-any.whl", hash = "sha256:884b39be23b1d884dcc825d4680c9c6357a476936e3b381a67ae80091984eb49", size = 20563, upload-time = "2025-04-25T16:53:24.492Z" },
+]
+
+[[package]]
+name = "sphinx-book-theme"
+version = "1.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pydata-sphinx-theme" },
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/45/19/d002ed96bdc7738c15847c730e1e88282d738263deac705d5713b4d8fa94/sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed", size = 439188, upload-time = "2025-02-20T16:32:32.581Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1", size = 433952, upload-time = "2025-02-20T16:32:31.009Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
+]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
+ { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
+ { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
+ { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
+ { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
+ { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
+ { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
+ { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
+ { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
+ { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
+ { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
+ { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
+]
+
+[[package]]
+name = "tomlkit"
+version = "0.13.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
+]
+
+[[package]]
+name = "virtualenv"
+version = "20.31.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "distlib" },
+ { name = "filelock" },
+ { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" },
+]
+
+[[package]]
+name = "win32-setctime"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
+]
+
+[[package]]
+name = "yapf"
+version = "0.43.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "platformdirs" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" },
+]
+
+[[package]]
+name = "yarl"
+version = "1.20.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "multidict" },
+ { name = "propcache" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" },
+ { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" },
+ { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" },
+ { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" },
+ { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" },
+ { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" },
+ { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" },
+ { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" },
+ { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" },
+ { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" },
+ { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" },
+ { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" },
+ { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" },
+ { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" },
+ { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" },
+ { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" },
+ { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" },
+ { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" },
+ { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" },
+ { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" },
+ { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" },
+ { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" },
+ { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" },
+ { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" },
+ { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" },
+ { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" },
+ { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" },
+ { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" },
+ { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" },
+ { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" },
+ { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" },
+ { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" },
+ { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" },
+ { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" },
+ { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" },
+ { url = "https://files.pythonhosted.org/packages/01/75/0d37402d208d025afa6b5b8eb80e466d267d3fd1927db8e317d29a94a4cb/yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3", size = 134259, upload-time = "2025-06-10T00:45:29.882Z" },
+ { url = "https://files.pythonhosted.org/packages/73/84/1fb6c85ae0cf9901046f07d0ac9eb162f7ce6d95db541130aa542ed377e6/yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b", size = 91269, upload-time = "2025-06-10T00:45:32.917Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/9c/eae746b24c4ea29a5accba9a06c197a70fa38a49c7df244e0d3951108861/yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983", size = 89995, upload-time = "2025-06-10T00:45:35.066Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/30/693e71003ec4bc1daf2e4cf7c478c417d0985e0a8e8f00b2230d517876fc/yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805", size = 325253, upload-time = "2025-06-10T00:45:37.052Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/a2/5264dbebf90763139aeb0b0b3154763239398400f754ae19a0518b654117/yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba", size = 320897, upload-time = "2025-06-10T00:45:39.962Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/17/77c7a89b3c05856489777e922f41db79ab4faf58621886df40d812c7facd/yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e", size = 340696, upload-time = "2025-06-10T00:45:41.915Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/55/28409330b8ef5f2f681f5b478150496ec9cf3309b149dab7ec8ab5cfa3f0/yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723", size = 335064, upload-time = "2025-06-10T00:45:43.893Z" },
+ { url = "https://files.pythonhosted.org/packages/85/58/cb0257cbd4002828ff735f44d3c5b6966c4fd1fc8cc1cd3cd8a143fbc513/yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000", size = 327256, upload-time = "2025-06-10T00:45:46.393Z" },
+ { url = "https://files.pythonhosted.org/packages/53/f6/c77960370cfa46f6fb3d6a5a79a49d3abfdb9ef92556badc2dcd2748bc2a/yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5", size = 316389, upload-time = "2025-06-10T00:45:48.358Z" },
+ { url = "https://files.pythonhosted.org/packages/64/ab/be0b10b8e029553c10905b6b00c64ecad3ebc8ace44b02293a62579343f6/yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c", size = 340481, upload-time = "2025-06-10T00:45:50.663Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/c3/3f327bd3905a4916029bf5feb7f86dcf864c7704f099715f62155fb386b2/yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240", size = 336941, upload-time = "2025-06-10T00:45:52.554Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/42/040bdd5d3b3bb02b4a6ace4ed4075e02f85df964d6e6cb321795d2a6496a/yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee", size = 339936, upload-time = "2025-06-10T00:45:54.919Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/1c/911867b8e8c7463b84dfdc275e0d99b04b66ad5132b503f184fe76be8ea4/yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010", size = 360163, upload-time = "2025-06-10T00:45:56.87Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/31/8c389f6c6ca0379b57b2da87f1f126c834777b4931c5ee8427dd65d0ff6b/yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8", size = 359108, upload-time = "2025-06-10T00:45:58.869Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/09/ae4a649fb3964324c70a3e2b61f45e566d9ffc0affd2b974cbf628957673/yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d", size = 351875, upload-time = "2025-06-10T00:46:01.45Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/43/bbb4ed4c34d5bb62b48bf957f68cd43f736f79059d4f85225ab1ef80f4b9/yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06", size = 82293, upload-time = "2025-06-10T00:46:03.763Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/cd/ce185848a7dba68ea69e932674b5c1a42a1852123584bccc5443120f857c/yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00", size = 87385, upload-time = "2025-06-10T00:46:05.655Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]