mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-25 11:53:23 -07:00
Compare commits
10 Commits
NewSoupVi-
...
archipidle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c562c5cde9 | ||
|
|
fc46eb4329 | ||
|
|
5aa137be52 | ||
|
|
37fbe9fe8f | ||
|
|
4b65469dbb | ||
|
|
f735776143 | ||
|
|
96b0a604a2 | ||
|
|
2f6dfd5d29 | ||
|
|
5d5a5fd705 | ||
|
|
3d06b1798a |
60
.github/workflows/build.yml
vendored
60
.github/workflows/build.yml
vendored
@@ -40,10 +40,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python setup.py build_exe --yes
|
python setup.py build_exe --yes
|
||||||
if ( $? -eq $false ) {
|
|
||||||
Write-Error "setup.py failed!"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$NAME="$(ls build | Select-String -Pattern 'exe')".Split('.',2)[1]
|
$NAME="$(ls build | Select-String -Pattern 'exe')".Split('.',2)[1]
|
||||||
$ZIP_NAME="Archipelago_$NAME.7z"
|
$ZIP_NAME="Archipelago_$NAME.7z"
|
||||||
echo "$NAME -> $ZIP_NAME"
|
echo "$NAME -> $ZIP_NAME"
|
||||||
@@ -53,6 +49,12 @@ jobs:
|
|||||||
Rename-Item "exe.$NAME" Archipelago
|
Rename-Item "exe.$NAME" Archipelago
|
||||||
7z a -mx=9 -mhe=on -ms "../dist/$ZIP_NAME" Archipelago
|
7z a -mx=9 -mhe=on -ms "../dist/$ZIP_NAME" Archipelago
|
||||||
Rename-Item Archipelago "exe.$NAME" # inno_setup.iss expects the original name
|
Rename-Item Archipelago "exe.$NAME" # inno_setup.iss expects the original name
|
||||||
|
- name: Store 7z
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.ZIP_NAME }}
|
||||||
|
path: dist/${{ env.ZIP_NAME }}
|
||||||
|
retention-days: 7 # keep for 7 days, should be enough
|
||||||
- name: Build Setup
|
- name: Build Setup
|
||||||
run: |
|
run: |
|
||||||
& "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe" inno_setup.iss /DNO_SIGNTOOL
|
& "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe" inno_setup.iss /DNO_SIGNTOOL
|
||||||
@@ -63,38 +65,11 @@ jobs:
|
|||||||
$contents = Get-ChildItem -Path setups/*.exe -Force -Recurse
|
$contents = Get-ChildItem -Path setups/*.exe -Force -Recurse
|
||||||
$SETUP_NAME=$contents[0].Name
|
$SETUP_NAME=$contents[0].Name
|
||||||
echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV
|
echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV
|
||||||
- name: Check build loads expected worlds
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd build/exe*
|
|
||||||
mv Players/Templates/meta.yaml .
|
|
||||||
ls -1 Players/Templates | sort > setup-player-templates.txt
|
|
||||||
rm -R Players/Templates
|
|
||||||
timeout 30 ./ArchipelagoLauncher "Generate Template Options" || true
|
|
||||||
ls -1 Players/Templates | sort > generated-player-templates.txt
|
|
||||||
cmp setup-player-templates.txt generated-player-templates.txt \
|
|
||||||
|| diff setup-player-templates.txt generated-player-templates.txt
|
|
||||||
mv meta.yaml Players/Templates/
|
|
||||||
- name: Test Generate
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd build/exe*
|
|
||||||
cp Players/Templates/Clique.yaml Players/
|
|
||||||
timeout 30 ./ArchipelagoGenerate
|
|
||||||
- name: Store 7z
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ env.ZIP_NAME }}
|
|
||||||
path: dist/${{ env.ZIP_NAME }}
|
|
||||||
compression-level: 0 # .7z is incompressible by zip
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 7 # keep for 7 days, should be enough
|
|
||||||
- name: Store Setup
|
- name: Store Setup
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.SETUP_NAME }}
|
name: ${{ env.SETUP_NAME }}
|
||||||
path: setups/${{ env.SETUP_NAME }}
|
path: setups/${{ env.SETUP_NAME }}
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 7 # keep for 7 days, should be enough
|
retention-days: 7 # keep for 7 days, should be enough
|
||||||
|
|
||||||
build-ubuntu2004:
|
build-ubuntu2004:
|
||||||
@@ -135,7 +110,7 @@ jobs:
|
|||||||
echo -e "setup.py dist output:\n `ls dist`"
|
echo -e "setup.py dist output:\n `ls dist`"
|
||||||
cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd ..
|
cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd ..
|
||||||
export TAR_NAME="${APPIMAGE_NAME%.AppImage}.tar.gz"
|
export TAR_NAME="${APPIMAGE_NAME%.AppImage}.tar.gz"
|
||||||
(cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -cv Archipelago | gzip -8 > ../dist/$TAR_NAME && mv Archipelago "$DIR_NAME")
|
(cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -czvf ../dist/$TAR_NAME Archipelago && mv Archipelago "$DIR_NAME")
|
||||||
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
|
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
|
||||||
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
|
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
|
||||||
# - copy code above to release.yml -
|
# - copy code above to release.yml -
|
||||||
@@ -143,36 +118,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
python setup.py build_exe --yes
|
python setup.py build_exe --yes
|
||||||
- name: Check build loads expected worlds
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd build/exe*
|
|
||||||
mv Players/Templates/meta.yaml .
|
|
||||||
ls -1 Players/Templates | sort > setup-player-templates.txt
|
|
||||||
rm -R Players/Templates
|
|
||||||
timeout 30 ./ArchipelagoLauncher "Generate Template Options" || true
|
|
||||||
ls -1 Players/Templates | sort > generated-player-templates.txt
|
|
||||||
cmp setup-player-templates.txt generated-player-templates.txt \
|
|
||||||
|| diff setup-player-templates.txt generated-player-templates.txt
|
|
||||||
mv meta.yaml Players/Templates/
|
|
||||||
- name: Test Generate
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd build/exe*
|
|
||||||
cp Players/Templates/Clique.yaml Players/
|
|
||||||
timeout 30 ./ArchipelagoGenerate
|
|
||||||
- name: Store AppImage
|
- name: Store AppImage
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APPIMAGE_NAME }}
|
name: ${{ env.APPIMAGE_NAME }}
|
||||||
path: dist/${{ env.APPIMAGE_NAME }}
|
path: dist/${{ env.APPIMAGE_NAME }}
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: Store .tar.gz
|
- name: Store .tar.gz
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.TAR_NAME }}
|
name: ${{ env.TAR_NAME }}
|
||||||
path: dist/${{ env.TAR_NAME }}
|
path: dist/${{ env.TAR_NAME }}
|
||||||
compression-level: 0 # .gz is incompressible by zip
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|||||||
54
.github/workflows/ctest.yml
vendored
54
.github/workflows/ctest.yml
vendored
@@ -1,54 +0,0 @@
|
|||||||
# Run CMake / CTest C++ unit tests
|
|
||||||
|
|
||||||
name: ctest
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '**.cc?'
|
|
||||||
- '**.cpp'
|
|
||||||
- '**.cxx'
|
|
||||||
- '**.hh?'
|
|
||||||
- '**.hpp'
|
|
||||||
- '**.hxx'
|
|
||||||
- '**.CMakeLists'
|
|
||||||
- '.github/workflows/ctest.yml'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**.cc?'
|
|
||||||
- '**.cpp'
|
|
||||||
- '**.cxx'
|
|
||||||
- '**.hh?'
|
|
||||||
- '**.hpp'
|
|
||||||
- '**.hxx'
|
|
||||||
- '**.CMakeLists'
|
|
||||||
- '.github/workflows/ctest.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ctest:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
name: Test C++ ${{ matrix.os }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ilammy/msvc-dev-cmd@v1
|
|
||||||
if: startsWith(matrix.os,'windows')
|
|
||||||
- uses: Bacondish2023/setup-googletest@v1
|
|
||||||
with:
|
|
||||||
build-type: 'Release'
|
|
||||||
- name: Build tests
|
|
||||||
run: |
|
|
||||||
cd test/cpp
|
|
||||||
mkdir build
|
|
||||||
cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build/ --config Release
|
|
||||||
ls
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
cd test/cpp
|
|
||||||
ctest --test-dir build/ -C Release --output-on-failure
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
|||||||
echo -e "setup.py dist output:\n `ls dist`"
|
echo -e "setup.py dist output:\n `ls dist`"
|
||||||
cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd ..
|
cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd ..
|
||||||
export TAR_NAME="${APPIMAGE_NAME%.AppImage}.tar.gz"
|
export TAR_NAME="${APPIMAGE_NAME%.AppImage}.tar.gz"
|
||||||
(cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -cv Archipelago | gzip -8 > ../dist/$TAR_NAME && mv Archipelago "$DIR_NAME")
|
(cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -czvf ../dist/$TAR_NAME Archipelago && mv Archipelago "$DIR_NAME")
|
||||||
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
|
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
|
||||||
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
|
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
|
||||||
# - code above copied from build.yml -
|
# - code above copied from build.yml -
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -178,7 +178,6 @@ dmypy.json
|
|||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
# Cython intermediates
|
# Cython intermediates
|
||||||
_speedups.c
|
|
||||||
_speedups.cpp
|
_speedups.cpp
|
||||||
_speedups.html
|
_speedups.html
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class AdventureContext(CommonContext):
|
|||||||
self.local_item_locations = {}
|
self.local_item_locations = {}
|
||||||
self.dragon_speed_info = {}
|
self.dragon_speed_info = {}
|
||||||
|
|
||||||
options = Utils.get_settings()
|
options = Utils.get_options()
|
||||||
self.display_msgs = options["adventure_options"]["display_msgs"]
|
self.display_msgs = options["adventure_options"]["display_msgs"]
|
||||||
|
|
||||||
async def server_auth(self, password_requested: bool = False):
|
async def server_auth(self, password_requested: bool = False):
|
||||||
@@ -102,7 +102,7 @@ class AdventureContext(CommonContext):
|
|||||||
def on_package(self, cmd: str, args: dict):
|
def on_package(self, cmd: str, args: dict):
|
||||||
if cmd == 'Connected':
|
if cmd == 'Connected':
|
||||||
self.locations_array = None
|
self.locations_array = None
|
||||||
if Utils.get_settings()["adventure_options"].get("death_link", False):
|
if Utils.get_options()["adventure_options"].get("death_link", False):
|
||||||
self.set_deathlink = True
|
self.set_deathlink = True
|
||||||
async_start(self.get_freeincarnates_used())
|
async_start(self.get_freeincarnates_used())
|
||||||
elif cmd == "RoomInfo":
|
elif cmd == "RoomInfo":
|
||||||
@@ -415,8 +415,8 @@ async def atari_sync_task(ctx: AdventureContext):
|
|||||||
|
|
||||||
|
|
||||||
async def run_game(romfile):
|
async def run_game(romfile):
|
||||||
auto_start = Utils.get_settings()["adventure_options"].get("rom_start", True)
|
auto_start = Utils.get_options()["adventure_options"].get("rom_start", True)
|
||||||
rom_args = Utils.get_settings()["adventure_options"].get("rom_args")
|
rom_args = Utils.get_options()["adventure_options"].get("rom_args")
|
||||||
if auto_start is True:
|
if auto_start is True:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(romfile)
|
webbrowser.open(romfile)
|
||||||
|
|||||||
@@ -493,11 +493,6 @@ class CommonContext:
|
|||||||
"""Gets called before sending a Say to the server from the user.
|
"""Gets called before sending a Say to the server from the user.
|
||||||
Returned text is sent, or sending is aborted if None is returned."""
|
Returned text is sent, or sending is aborted if None is returned."""
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def on_ui_command(self, text: str) -> None:
|
|
||||||
"""Gets called by kivy when the user executes a command starting with `/` or `!`.
|
|
||||||
The command processor is still called; this is just intended for command echoing."""
|
|
||||||
self.ui.print_json([{"text": text, "type": "color", "color": "orange"}])
|
|
||||||
|
|
||||||
def update_permissions(self, permissions: typing.Dict[str, int]):
|
def update_permissions(self, permissions: typing.Dict[str, int]):
|
||||||
for permission_name, permission_flag in permissions.items():
|
for permission_name, permission_flag in permissions.items():
|
||||||
|
|||||||
6
Fill.py
6
Fill.py
@@ -483,15 +483,15 @@ def distribute_items_restrictive(multiworld: MultiWorld,
|
|||||||
if panic_method == "swap":
|
if panic_method == "swap":
|
||||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||||
swap=True,
|
swap=True,
|
||||||
name="Progression", single_player_placement=multiworld.players == 1)
|
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
|
||||||
elif panic_method == "raise":
|
elif panic_method == "raise":
|
||||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||||
swap=False,
|
swap=False,
|
||||||
name="Progression", single_player_placement=multiworld.players == 1)
|
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
|
||||||
elif panic_method == "start_inventory":
|
elif panic_method == "start_inventory":
|
||||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||||
swap=False, allow_partial=True,
|
swap=False, allow_partial=True,
|
||||||
name="Progression", single_player_placement=multiworld.players == 1)
|
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
|
||||||
if progitempool:
|
if progitempool:
|
||||||
for item in progitempool:
|
for item in progitempool:
|
||||||
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
|
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
|
||||||
|
|||||||
40
Generate.py
40
Generate.py
@@ -1,12 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import copy
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import sys
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
@@ -17,16 +15,21 @@ import ModuleUpdate
|
|||||||
|
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
|
||||||
|
import copy
|
||||||
import Utils
|
import Utils
|
||||||
import Options
|
import Options
|
||||||
from BaseClasses import seeddigits, get_seed, PlandoOptions
|
from BaseClasses import seeddigits, get_seed, PlandoOptions
|
||||||
|
from Main import main as ERmain
|
||||||
|
from settings import get_settings
|
||||||
from Utils import parse_yamls, version_tuple, __version__, tuplize_version
|
from Utils import parse_yamls, version_tuple, __version__, tuplize_version
|
||||||
|
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||||
|
from worlds.AutoWorld import AutoWorldRegister
|
||||||
|
from worlds import failed_world_loads
|
||||||
|
|
||||||
|
|
||||||
def mystery_argparse():
|
def mystery_argparse():
|
||||||
from settings import get_settings
|
options = get_settings()
|
||||||
settings = get_settings()
|
defaults = options.generator
|
||||||
defaults = settings.generator
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="CMD Generation Interface, defaults come from host.yaml.")
|
parser = argparse.ArgumentParser(description="CMD Generation Interface, defaults come from host.yaml.")
|
||||||
parser.add_argument('--weights_file_path', default=defaults.weights_file_path,
|
parser.add_argument('--weights_file_path', default=defaults.weights_file_path,
|
||||||
@@ -38,7 +41,7 @@ def mystery_argparse():
|
|||||||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||||
parser.add_argument('--multi', default=defaults.players, type=lambda value: max(int(value), 1))
|
parser.add_argument('--multi', default=defaults.players, type=lambda value: max(int(value), 1))
|
||||||
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
|
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
|
||||||
parser.add_argument('--outputpath', default=settings.general_options.output_path,
|
parser.add_argument('--outputpath', default=options.general_options.output_path,
|
||||||
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
|
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
|
||||||
parser.add_argument('--race', action='store_true', default=defaults.race)
|
parser.add_argument('--race', action='store_true', default=defaults.race)
|
||||||
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
|
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
|
||||||
@@ -58,23 +61,20 @@ def mystery_argparse():
|
|||||||
if not os.path.isabs(args.meta_file_path):
|
if not os.path.isabs(args.meta_file_path):
|
||||||
args.meta_file_path = os.path.join(args.player_files_path, args.meta_file_path)
|
args.meta_file_path = os.path.join(args.player_files_path, args.meta_file_path)
|
||||||
args.plando: PlandoOptions = PlandoOptions.from_option_string(args.plando)
|
args.plando: PlandoOptions = PlandoOptions.from_option_string(args.plando)
|
||||||
return args
|
return args, options
|
||||||
|
|
||||||
|
|
||||||
def get_seed_name(random_source) -> str:
|
def get_seed_name(random_source) -> str:
|
||||||
return f"{random_source.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)
|
return f"{random_source.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None, callback=ERmain):
|
||||||
# __name__ == "__main__" check so unittests that already imported worlds don't trip this.
|
|
||||||
if __name__ == "__main__" and "worlds" in sys.modules:
|
|
||||||
raise Exception("Worlds system should not be loaded before logging init.")
|
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
args = mystery_argparse()
|
args, options = mystery_argparse()
|
||||||
|
else:
|
||||||
|
options = get_settings()
|
||||||
|
|
||||||
seed = get_seed(args.seed)
|
seed = get_seed(args.seed)
|
||||||
|
|
||||||
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
|
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
seed_name = get_seed_name(random)
|
seed_name = get_seed_name(random)
|
||||||
@@ -143,9 +143,6 @@ def main(args=None):
|
|||||||
raise Exception(f"No weights found. "
|
raise Exception(f"No weights found. "
|
||||||
f"Provide a general weights file ({args.weights_file_path}) or individual player files. "
|
f"Provide a general weights file ({args.weights_file_path}) or individual player files. "
|
||||||
f"A mix is also permitted.")
|
f"A mix is also permitted.")
|
||||||
|
|
||||||
from worlds.AutoWorld import AutoWorldRegister
|
|
||||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
|
||||||
erargs = parse_arguments(['--multi', str(args.multi)])
|
erargs = parse_arguments(['--multi', str(args.multi)])
|
||||||
erargs.seed = seed
|
erargs.seed = seed
|
||||||
erargs.plando_options = args.plando
|
erargs.plando_options = args.plando
|
||||||
@@ -237,8 +234,7 @@ def main(args=None):
|
|||||||
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
|
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
|
||||||
yaml.dump(important, f)
|
yaml.dump(important, f)
|
||||||
|
|
||||||
from Main import main as ERmain
|
return callback(erargs, seed)
|
||||||
return ERmain(erargs, seed)
|
|
||||||
|
|
||||||
|
|
||||||
def read_weights_yamls(path) -> Tuple[Any, ...]:
|
def read_weights_yamls(path) -> Tuple[Any, ...]:
|
||||||
@@ -363,8 +359,6 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
|
|||||||
|
|
||||||
|
|
||||||
def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
|
def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
|
||||||
from worlds import AutoWorldRegister
|
|
||||||
|
|
||||||
if not game:
|
if not game:
|
||||||
return get_choice(option_key, category_dict)
|
return get_choice(option_key, category_dict)
|
||||||
if game in AutoWorldRegister.world_types:
|
if game in AutoWorldRegister.world_types:
|
||||||
@@ -442,13 +436,10 @@ def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Options.OptionError(f"Error generating option {option_key} in {ret.game}") from e
|
raise Options.OptionError(f"Error generating option {option_key} in {ret.game}") from e
|
||||||
else:
|
else:
|
||||||
from worlds import AutoWorldRegister
|
|
||||||
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
|
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
|
||||||
|
|
||||||
|
|
||||||
def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses):
|
def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses):
|
||||||
from worlds import AutoWorldRegister
|
|
||||||
|
|
||||||
if "linked_options" in weights:
|
if "linked_options" in weights:
|
||||||
weights = roll_linked_options(weights)
|
weights = roll_linked_options(weights)
|
||||||
|
|
||||||
@@ -475,7 +466,6 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
|||||||
|
|
||||||
ret.game = get_choice("game", weights)
|
ret.game = get_choice("game", weights)
|
||||||
if ret.game not in AutoWorldRegister.world_types:
|
if ret.game not in AutoWorldRegister.world_types:
|
||||||
from worlds import failed_world_loads
|
|
||||||
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + failed_world_loads, limit=1)[0]
|
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + failed_world_loads, limit=1)[0]
|
||||||
if picks[0] in failed_world_loads:
|
if picks[0] in failed_world_loads:
|
||||||
raise Exception(f"No functional world found to handle game {ret.game}. "
|
raise Exception(f"No functional world found to handle game {ret.game}. "
|
||||||
|
|||||||
@@ -198,8 +198,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
|
|||||||
"slateblue": "6D8BE8",
|
"slateblue": "6D8BE8",
|
||||||
"plum": "AF99EF",
|
"plum": "AF99EF",
|
||||||
"salmon": "FA8072",
|
"salmon": "FA8072",
|
||||||
"white": "FFFFFF",
|
"white": "FFFFFF"
|
||||||
"orange": "FF7700",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
|
|||||||
1
Utils.py
1
Utils.py
@@ -553,7 +553,6 @@ def init_logging(name: str, loglevel: typing.Union[str, int] = logging.INFO, wri
|
|||||||
f"Archipelago ({__version__}) logging initialized"
|
f"Archipelago ({__version__}) logging initialized"
|
||||||
f" on {platform.platform()}"
|
f" on {platform.platform()}"
|
||||||
f" running Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
f" running Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
||||||
f"{' (frozen)' if is_frozen() else ''}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
|
|||||||
worlds[game] = world
|
worlds[game] = world
|
||||||
|
|
||||||
base_target_path = Utils.local_path("WebHostLib", "static", "generated", "docs")
|
base_target_path = Utils.local_path("WebHostLib", "static", "generated", "docs")
|
||||||
shutil.rmtree(base_target_path, ignore_errors=True)
|
|
||||||
for game, world in worlds.items():
|
for game, world in worlds.items():
|
||||||
# copy files from world's docs folder to the generated folder
|
# copy files from world's docs folder to the generated folder
|
||||||
target_path = os.path.join(base_target_path, game)
|
target_path = os.path.join(base_target_path, game)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from uuid import UUID
|
|||||||
from flask import Blueprint, abort, url_for
|
from flask import Blueprint, abort, url_for
|
||||||
|
|
||||||
import worlds.Files
|
import worlds.Files
|
||||||
|
from .. import cache
|
||||||
from ..models import Room, Seed
|
from ..models import Room, Seed
|
||||||
|
|
||||||
api_endpoints = Blueprint('api', __name__, url_prefix="/api")
|
api_endpoints = Blueprint('api', __name__, url_prefix="/api")
|
||||||
@@ -48,4 +49,21 @@ def room_info(room: UUID):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
from . import generate, user, datapackage # trigger registration
|
@api_endpoints.route('/datapackage')
|
||||||
|
@cache.cached()
|
||||||
|
def get_datapackage():
|
||||||
|
from worlds import network_data_package
|
||||||
|
return network_data_package
|
||||||
|
|
||||||
|
|
||||||
|
@api_endpoints.route('/datapackage_checksum')
|
||||||
|
@cache.cached()
|
||||||
|
def get_datapackage_checksums():
|
||||||
|
from worlds import network_data_package
|
||||||
|
version_package = {
|
||||||
|
game: game_data["checksum"] for game, game_data in network_data_package["games"].items()
|
||||||
|
}
|
||||||
|
return version_package
|
||||||
|
|
||||||
|
|
||||||
|
from . import generate, user # trigger registration
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
from flask import abort
|
|
||||||
|
|
||||||
from Utils import restricted_loads
|
|
||||||
from WebHostLib import cache
|
|
||||||
from WebHostLib.models import GameDataPackage
|
|
||||||
from . import api_endpoints
|
|
||||||
|
|
||||||
|
|
||||||
@api_endpoints.route('/datapackage')
|
|
||||||
@cache.cached()
|
|
||||||
def get_datapackage():
|
|
||||||
from worlds import network_data_package
|
|
||||||
return network_data_package
|
|
||||||
|
|
||||||
|
|
||||||
@api_endpoints.route('/datapackage/<string:checksum>')
|
|
||||||
@cache.memoize(timeout=3600)
|
|
||||||
def get_datapackage_by_checksum(checksum: str):
|
|
||||||
package = GameDataPackage.get(checksum=checksum)
|
|
||||||
if package:
|
|
||||||
return restricted_loads(package.data)
|
|
||||||
return abort(404)
|
|
||||||
|
|
||||||
|
|
||||||
@api_endpoints.route('/datapackage_checksum')
|
|
||||||
@cache.cached()
|
|
||||||
def get_datapackage_checksums():
|
|
||||||
from worlds import network_data_package
|
|
||||||
version_package = {
|
|
||||||
game: game_data["checksum"] for game, game_data in network_data_package["games"].items()
|
|
||||||
}
|
|
||||||
return version_package
|
|
||||||
@@ -18,11 +18,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for id, name in option.name_lookup.items() %}
|
{% for id, name in option.name_lookup.items() %}
|
||||||
{% if name != 'random' %}
|
{% if name != 'random' %}
|
||||||
{% if option.default != 'random' %}
|
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.get_option_name(option.default)|lower == name|lower else None) }}
|
||||||
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.get_option_name(option.default)|lower == name else None) }}
|
|
||||||
{% else %}
|
|
||||||
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ RandomRow(option_name, option) }}
|
{{ RandomRow(option_name, option) }}
|
||||||
@@ -38,7 +34,7 @@
|
|||||||
Normal range: {{ option.range_start }} - {{ option.range_end }}
|
Normal range: {{ option.range_start }} - {{ option.range_end }}
|
||||||
{% if option.special_range_names %}
|
{% if option.special_range_names %}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
The following values have special meanings, and may fall outside the normal range.
|
The following values has special meaning, and may fall outside the normal range.
|
||||||
<ul>
|
<ul>
|
||||||
{% for name, value in option.special_range_names.items() %}
|
{% for name, value in option.special_range_names.items() %}
|
||||||
<li>{{ value }}: {{ name }}</li>
|
<li>{{ value }}: {{ name }}</li>
|
||||||
@@ -47,7 +43,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="add-option-div">
|
<div class="add-option-div">
|
||||||
<input type="number" class="range-option-value" data-option="{{ option_name }}" />
|
<input type="number" class="range-option-value" data-option="{{ option_name }}" />
|
||||||
<button type="button" class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
<button class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="range-rows" data-option="{{ option_name }}">
|
<table class="range-rows" data-option="{{ option_name }}">
|
||||||
@@ -72,7 +68,7 @@
|
|||||||
This option allows custom values only. Please enter your desired values below.
|
This option allows custom values only. Please enter your desired values below.
|
||||||
<div class="custom-value-wrapper">
|
<div class="custom-value-wrapper">
|
||||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||||
<button type="button" data-option="{{ option_name }}">Add</button>
|
<button data-option="{{ option_name }}">Add</button>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -89,18 +85,14 @@
|
|||||||
Custom values are also allowed for this option. To create one, enter it into the input box below.
|
Custom values are also allowed for this option. To create one, enter it into the input box below.
|
||||||
<div class="custom-value-wrapper">
|
<div class="custom-value-wrapper">
|
||||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||||
<button type="button" data-option="{{ option_name }}">Add</button>
|
<button data-option="{{ option_name }}">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for id, name in option.name_lookup.items() %}
|
{% for id, name in option.name_lookup.items() %}
|
||||||
{% if name != 'random' %}
|
{% if name != 'random' %}
|
||||||
{% if option.default != 'random' %}
|
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.get_option_name(option.default)|lower == name else None) }}
|
||||||
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.get_option_name(option.default)|lower == name else None) }}
|
|
||||||
{% else %}
|
|
||||||
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ RandomRow(option_name, option) }}
|
{{ RandomRow(option_name, option) }}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#cython: language_level=3
|
#cython: language_level=3
|
||||||
#distutils: language = c
|
#distutils: language = c++
|
||||||
#distutils: depends = intset.h
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Provides faster implementation of some core parts.
|
Provides faster implementation of some core parts.
|
||||||
@@ -14,6 +13,7 @@ from cpython cimport PyObject
|
|||||||
from typing import Any, Dict, Iterable, Iterator, Generator, Sequence, Tuple, TypeVar, Union, Set, List, TYPE_CHECKING
|
from typing import Any, Dict, Iterable, Iterator, Generator, Sequence, Tuple, TypeVar, Union, Set, List, TYPE_CHECKING
|
||||||
from cymem.cymem cimport Pool
|
from cymem.cymem cimport Pool
|
||||||
from libc.stdint cimport int64_t, uint32_t
|
from libc.stdint cimport int64_t, uint32_t
|
||||||
|
from libcpp.set cimport set as std_set
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
cdef extern from *:
|
cdef extern from *:
|
||||||
@@ -31,27 +31,6 @@ ctypedef int64_t ap_id_t
|
|||||||
cdef ap_player_t MAX_PLAYER_ID = 1000000 # limit the size of indexing array
|
cdef ap_player_t MAX_PLAYER_ID = 1000000 # limit the size of indexing array
|
||||||
cdef size_t INVALID_SIZE = <size_t>(-1) # this is all 0xff... adding 1 results in 0, but it's not negative
|
cdef size_t INVALID_SIZE = <size_t>(-1) # this is all 0xff... adding 1 results in 0, but it's not negative
|
||||||
|
|
||||||
# configure INTSET for player
|
|
||||||
cdef extern from *:
|
|
||||||
"""
|
|
||||||
#define INTSET_NAME ap_player_set
|
|
||||||
#define INTSET_TYPE uint32_t // has to match ap_player_t
|
|
||||||
"""
|
|
||||||
|
|
||||||
# create INTSET for player
|
|
||||||
cdef extern from "intset.h":
|
|
||||||
"""
|
|
||||||
#undef INTSET_NAME
|
|
||||||
#undef INTSET_TYPE
|
|
||||||
"""
|
|
||||||
ctypedef struct ap_player_set:
|
|
||||||
pass
|
|
||||||
|
|
||||||
ap_player_set* ap_player_set_new(size_t bucket_count) nogil
|
|
||||||
void ap_player_set_free(ap_player_set* set) nogil
|
|
||||||
bint ap_player_set_add(ap_player_set* set, ap_player_t val) nogil
|
|
||||||
bint ap_player_set_contains(ap_player_set* set, ap_player_t val) nogil
|
|
||||||
|
|
||||||
|
|
||||||
cdef struct LocationEntry:
|
cdef struct LocationEntry:
|
||||||
# layout is so that
|
# layout is so that
|
||||||
@@ -206,7 +185,7 @@ cdef class LocationStore:
|
|||||||
def find_item(self, slots: Set[int], seeked_item_id: int) -> Generator[Tuple[int, int, int, int, int], None, None]:
|
def find_item(self, slots: Set[int], seeked_item_id: int) -> Generator[Tuple[int, int, int, int, int], None, None]:
|
||||||
cdef ap_id_t item = seeked_item_id
|
cdef ap_id_t item = seeked_item_id
|
||||||
cdef ap_player_t receiver
|
cdef ap_player_t receiver
|
||||||
cdef ap_player_set* receivers
|
cdef std_set[ap_player_t] receivers
|
||||||
cdef size_t slot_count = len(slots)
|
cdef size_t slot_count = len(slots)
|
||||||
if slot_count == 1:
|
if slot_count == 1:
|
||||||
# specialized implementation for single slot
|
# specialized implementation for single slot
|
||||||
@@ -218,20 +197,13 @@ cdef class LocationStore:
|
|||||||
yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags
|
yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags
|
||||||
elif slot_count:
|
elif slot_count:
|
||||||
# generic implementation with lookup in set
|
# generic implementation with lookup in set
|
||||||
receivers = ap_player_set_new(min(1023, slot_count)) # limit top level struct to 16KB
|
for receiver in slots:
|
||||||
if not receivers:
|
receivers.insert(receiver)
|
||||||
raise MemoryError()
|
with nogil:
|
||||||
try:
|
for entry in self.entries[:self.entry_count]:
|
||||||
for receiver in slots:
|
if entry.item == item and receivers.count(entry.receiver):
|
||||||
if not ap_player_set_add(receivers, receiver):
|
with gil:
|
||||||
raise MemoryError()
|
yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags
|
||||||
with nogil:
|
|
||||||
for entry in self.entries[:self.entry_count]:
|
|
||||||
if entry.item == item and ap_player_set_contains(receivers, entry.receiver):
|
|
||||||
with gil:
|
|
||||||
yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags
|
|
||||||
finally:
|
|
||||||
ap_player_set_free(receivers)
|
|
||||||
|
|
||||||
def get_for_player(self, slot: int) -> Dict[int, Set[int]]:
|
def get_for_player(self, slot: int) -> Dict[int, Set[int]]:
|
||||||
cdef ap_player_t receiver = slot
|
cdef ap_player_t receiver = slot
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
# This file is used when doing pyximport
|
# This file is required to get pyximport to work with C++.
|
||||||
import os
|
# Switching from std::set to a pure C implementation is still on the table to simplify everything.
|
||||||
|
|
||||||
def make_ext(modname, pyxfilename):
|
def make_ext(modname, pyxfilename):
|
||||||
from distutils.extension import Extension
|
from distutils.extension import Extension
|
||||||
return Extension(name=modname,
|
return Extension(name=modname,
|
||||||
sources=[pyxfilename],
|
sources=[pyxfilename],
|
||||||
depends=["intset.h"],
|
language='c++')
|
||||||
include_dirs=[os.getcwd()],
|
|
||||||
language="c")
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
plum: "AF99EF" # typically progression item
|
plum: "AF99EF" # typically progression item
|
||||||
salmon: "FA8072" # typically trap item
|
salmon: "FA8072" # typically trap item
|
||||||
white: "FFFFFF" # not used, if you want to change the generic text color change color in Label
|
white: "FFFFFF" # not used, if you want to change the generic text color change color in Label
|
||||||
orange: "FF7700" # Used for command echo
|
|
||||||
<Label>:
|
<Label>:
|
||||||
color: "FFFFFF"
|
color: "FFFFFF"
|
||||||
<TabbedPanel>:
|
<TabbedPanel>:
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
/worlds/heretic/ @Daivuk
|
/worlds/heretic/ @Daivuk
|
||||||
|
|
||||||
# Hollow Knight
|
# Hollow Knight
|
||||||
/worlds/hk/ @BadMagic100 @qwint
|
/worlds/hk/ @BadMagic100 @ThePhar
|
||||||
|
|
||||||
# Hylics 2
|
# Hylics 2
|
||||||
/worlds/hylics2/ @TRPG0
|
/worlds/hylics2/ @TRPG0
|
||||||
|
|||||||
@@ -89,9 +89,6 @@ Type: files; Name: "{app}\ArchipelagoPokemonClient.exe"
|
|||||||
Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua"
|
Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua"
|
||||||
Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy"
|
Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy"
|
||||||
Type: dirifempty; Name: "{app}\lib\worlds\rogue-legacy"
|
Type: dirifempty; Name: "{app}\lib\worlds\rogue-legacy"
|
||||||
Type: files; Name: "{app}\lib\worlds\sc2wol.apworld"
|
|
||||||
Type: filesandordirs; Name: "{app}\lib\worlds\sc2wol"
|
|
||||||
Type: dirifempty; Name: "{app}\lib\worlds\sc2wol"
|
|
||||||
Type: filesandordirs; Name: "{app}\lib\worlds\bk_sudoku"
|
Type: filesandordirs; Name: "{app}\lib\worlds\bk_sudoku"
|
||||||
Type: dirifempty; Name: "{app}\lib\worlds\bk_sudoku"
|
Type: dirifempty; Name: "{app}\lib\worlds\bk_sudoku"
|
||||||
Type: files; Name: "{app}\ArchipelagoLauncher(DEBUG).exe"
|
Type: files; Name: "{app}\ArchipelagoLauncher(DEBUG).exe"
|
||||||
|
|||||||
135
intset.h
135
intset.h
@@ -1,135 +0,0 @@
|
|||||||
/* A specialized unordered_set implementation for literals, where bucket_count
|
|
||||||
* is defined at initialization rather than increased automatically.
|
|
||||||
*/
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifndef INTSET_NAME
|
|
||||||
#error "Please #define INTSET_NAME ... before including intset.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef INTSET_TYPE
|
|
||||||
#error "Please #define INTSET_TYPE ... before including intset.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* macros to generate unique names from INTSET_NAME */
|
|
||||||
#ifndef INTSET_CONCAT
|
|
||||||
#define INTSET_CONCAT_(a, b) a ## b
|
|
||||||
#define INTSET_CONCAT(a, b) INTSET_CONCAT_(a, b)
|
|
||||||
#define INTSET_FUNC_(a, b) INTSET_CONCAT(a, _ ## b)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define INTSET_FUNC(name) INTSET_FUNC_(INTSET_NAME, name)
|
|
||||||
#define INTSET_BUCKET INTSET_CONCAT(INTSET_NAME, Bucket)
|
|
||||||
#define INTSET_UNION INTSET_CONCAT(INTSET_NAME, Union)
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#pragma warning(push)
|
|
||||||
#pragma warning(disable : 4200)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
size_t count;
|
|
||||||
union INTSET_UNION {
|
|
||||||
INTSET_TYPE val;
|
|
||||||
INTSET_TYPE *data;
|
|
||||||
} v;
|
|
||||||
} INTSET_BUCKET;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
size_t bucket_count;
|
|
||||||
INTSET_BUCKET buckets[];
|
|
||||||
} INTSET_NAME;
|
|
||||||
|
|
||||||
static INTSET_NAME *INTSET_FUNC(new)(size_t buckets)
|
|
||||||
{
|
|
||||||
size_t i, size;
|
|
||||||
INTSET_NAME *set;
|
|
||||||
|
|
||||||
if (buckets < 1)
|
|
||||||
buckets = 1;
|
|
||||||
if ((SIZE_MAX - sizeof(INTSET_NAME)) / sizeof(INTSET_BUCKET) < buckets)
|
|
||||||
return NULL;
|
|
||||||
size = sizeof(INTSET_NAME) + buckets * sizeof(INTSET_BUCKET);
|
|
||||||
set = (INTSET_NAME*)malloc(size);
|
|
||||||
if (!set)
|
|
||||||
return NULL;
|
|
||||||
memset(set, 0, size); /* gcc -fanalyzer does not understand this sets all buckets' count to 0 */
|
|
||||||
for (i = 0; i < buckets; i++) {
|
|
||||||
set->buckets[i].count = 0;
|
|
||||||
}
|
|
||||||
set->bucket_count = buckets;
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void INTSET_FUNC(free)(INTSET_NAME *set)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
if (!set)
|
|
||||||
return;
|
|
||||||
for (i = 0; i < set->bucket_count; i++) {
|
|
||||||
if (set->buckets[i].count > 1)
|
|
||||||
free(set->buckets[i].v.data);
|
|
||||||
}
|
|
||||||
free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool INTSET_FUNC(contains)(INTSET_NAME *set, INTSET_TYPE val)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
INTSET_BUCKET* bucket = &set->buckets[(size_t)val % set->bucket_count];
|
|
||||||
if (bucket->count == 1)
|
|
||||||
return bucket->v.val == val;
|
|
||||||
for (i = 0; i < bucket->count; ++i) {
|
|
||||||
if (bucket->v.data[i] == val)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool INTSET_FUNC(add)(INTSET_NAME *set, INTSET_TYPE val)
|
|
||||||
{
|
|
||||||
INTSET_BUCKET* bucket;
|
|
||||||
|
|
||||||
if (INTSET_FUNC(contains)(set, val))
|
|
||||||
return true; /* ok */
|
|
||||||
|
|
||||||
bucket = &set->buckets[(size_t)val % set->bucket_count];
|
|
||||||
if (bucket->count == 0) {
|
|
||||||
bucket->v.val = val;
|
|
||||||
bucket->count = 1;
|
|
||||||
} else if (bucket->count == 1) {
|
|
||||||
INTSET_TYPE old = bucket->v.val;
|
|
||||||
bucket->v.data = (INTSET_TYPE*)malloc(2 * sizeof(INTSET_TYPE));
|
|
||||||
if (!bucket->v.data) {
|
|
||||||
bucket->v.val = old;
|
|
||||||
return false; /* error */
|
|
||||||
}
|
|
||||||
bucket->v.data[0] = old;
|
|
||||||
bucket->v.data[1] = val;
|
|
||||||
bucket->count = 2;
|
|
||||||
} else {
|
|
||||||
size_t new_bucket_size;
|
|
||||||
INTSET_TYPE* new_bucket_data;
|
|
||||||
|
|
||||||
new_bucket_size = (bucket->count + 1) * sizeof(INTSET_TYPE);
|
|
||||||
new_bucket_data = (INTSET_TYPE*)realloc(bucket->v.data, new_bucket_size);
|
|
||||||
if (!new_bucket_data)
|
|
||||||
return false; /* error */
|
|
||||||
bucket->v.data = new_bucket_data;
|
|
||||||
bucket->v.data[bucket->count++] = val;
|
|
||||||
}
|
|
||||||
return true; /* success */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#pragma warning(pop)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#undef INTSET_FUNC
|
|
||||||
#undef INTSET_BUCKET
|
|
||||||
#undef INTSET_UNION
|
|
||||||
62
kvui.py
62
kvui.py
@@ -3,7 +3,6 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
import typing
|
import typing
|
||||||
import re
|
import re
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import ctypes
|
import ctypes
|
||||||
@@ -381,57 +380,6 @@ class ConnectBarTextInput(TextInput):
|
|||||||
return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo)
|
return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo)
|
||||||
|
|
||||||
|
|
||||||
def is_command_input(string: str) -> bool:
|
|
||||||
return len(string) > 0 and string[0] in "/!"
|
|
||||||
|
|
||||||
|
|
||||||
class CommandPromptTextInput(TextInput):
|
|
||||||
MAXIMUM_HISTORY_MESSAGES = 50
|
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self._command_history_index = -1
|
|
||||||
self._command_history: typing.Deque[str] = deque(maxlen=CommandPromptTextInput.MAXIMUM_HISTORY_MESSAGES)
|
|
||||||
|
|
||||||
def update_history(self, new_entry: str) -> None:
|
|
||||||
self._command_history_index = -1
|
|
||||||
if is_command_input(new_entry):
|
|
||||||
self._command_history.appendleft(new_entry)
|
|
||||||
|
|
||||||
def keyboard_on_key_down(
|
|
||||||
self,
|
|
||||||
window,
|
|
||||||
keycode: typing.Tuple[int, str],
|
|
||||||
text: typing.Optional[str],
|
|
||||||
modifiers: typing.List[str]
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
:param window: The kivy window object
|
|
||||||
:param keycode: A tuple of (keycode, keyname). Keynames are always lowercase
|
|
||||||
:param text: The text printed by this key, not accounting for modifiers, or `None` if no text.
|
|
||||||
Seems to pretty naively interpret the keycode as unicode, so numlock can return odd characters.
|
|
||||||
:param modifiers: A list of string modifiers, like `ctrl` or `numlock`
|
|
||||||
"""
|
|
||||||
if keycode[1] == 'up':
|
|
||||||
self._change_to_history_text_if_available(self._command_history_index + 1)
|
|
||||||
return True
|
|
||||||
if keycode[1] == 'down':
|
|
||||||
self._change_to_history_text_if_available(self._command_history_index - 1)
|
|
||||||
return True
|
|
||||||
return super().keyboard_on_key_down(window, keycode, text, modifiers)
|
|
||||||
|
|
||||||
def _change_to_history_text_if_available(self, new_index: int) -> None:
|
|
||||||
if new_index < -1:
|
|
||||||
return
|
|
||||||
if new_index >= len(self._command_history):
|
|
||||||
return
|
|
||||||
self._command_history_index = new_index
|
|
||||||
if new_index == -1:
|
|
||||||
self.text = ""
|
|
||||||
return
|
|
||||||
self.text = self._command_history[self._command_history_index]
|
|
||||||
|
|
||||||
|
|
||||||
class MessageBox(Popup):
|
class MessageBox(Popup):
|
||||||
class MessageBoxLabel(Label):
|
class MessageBoxLabel(Label):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -467,7 +415,7 @@ class GameManager(App):
|
|||||||
self.commandprocessor = ctx.command_processor(ctx)
|
self.commandprocessor = ctx.command_processor(ctx)
|
||||||
self.icon = r"data/icon.png"
|
self.icon = r"data/icon.png"
|
||||||
self.json_to_kivy_parser = KivyJSONtoTextParser(ctx)
|
self.json_to_kivy_parser = KivyJSONtoTextParser(ctx)
|
||||||
self.log_panels: typing.Dict[str, Widget] = {}
|
self.log_panels = {}
|
||||||
|
|
||||||
# keep track of last used command to autofill on click
|
# keep track of last used command to autofill on click
|
||||||
self.last_autofillable_command = "hint"
|
self.last_autofillable_command = "hint"
|
||||||
@@ -551,7 +499,7 @@ class GameManager(App):
|
|||||||
info_button = Button(size=(dp(100), dp(30)), text="Command:", size_hint_x=None)
|
info_button = Button(size=(dp(100), dp(30)), text="Command:", size_hint_x=None)
|
||||||
info_button.bind(on_release=self.command_button_action)
|
info_button.bind(on_release=self.command_button_action)
|
||||||
bottom_layout.add_widget(info_button)
|
bottom_layout.add_widget(info_button)
|
||||||
self.textinput = CommandPromptTextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False)
|
self.textinput = TextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False)
|
||||||
self.textinput.bind(on_text_validate=self.on_message)
|
self.textinput.bind(on_text_validate=self.on_message)
|
||||||
self.textinput.text_validate_unfocus = False
|
self.textinput.text_validate_unfocus = False
|
||||||
bottom_layout.add_widget(self.textinput)
|
bottom_layout.add_widget(self.textinput)
|
||||||
@@ -609,18 +557,14 @@ class GameManager(App):
|
|||||||
|
|
||||||
self.ctx.exit_event.set()
|
self.ctx.exit_event.set()
|
||||||
|
|
||||||
def on_message(self, textinput: CommandPromptTextInput):
|
def on_message(self, textinput: TextInput):
|
||||||
try:
|
try:
|
||||||
input_text = textinput.text.strip()
|
input_text = textinput.text.strip()
|
||||||
textinput.text = ""
|
textinput.text = ""
|
||||||
textinput.update_history(input_text)
|
|
||||||
|
|
||||||
if self.ctx.input_requests > 0:
|
if self.ctx.input_requests > 0:
|
||||||
self.ctx.input_requests -= 1
|
self.ctx.input_requests -= 1
|
||||||
self.ctx.input_queue.put_nowait(input_text)
|
self.ctx.input_queue.put_nowait(input_text)
|
||||||
elif is_command_input(input_text):
|
|
||||||
self.ctx.on_ui_command(input_text)
|
|
||||||
self.commandprocessor(input_text)
|
|
||||||
elif input_text:
|
elif input_text:
|
||||||
self.commandprocessor(input_text)
|
self.commandprocessor(input_text)
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
|
||||||
project(ap-cpp-tests)
|
|
||||||
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
find_package(GTest REQUIRED)
|
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
|
||||||
add_definitions("/source-charset:utf-8")
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "/MTd")
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "/MT")
|
|
||||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
||||||
# enable static analysis for gcc
|
|
||||||
add_compile_options(-fanalyzer -Werror)
|
|
||||||
# disable stuff that gets triggered by googletest
|
|
||||||
add_compile_options(-Wno-analyzer-malloc-leak)
|
|
||||||
# enable asan for gcc
|
|
||||||
add_compile_options(-fsanitize=address)
|
|
||||||
add_link_options(-fsanitize=address)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
add_executable(test_default)
|
|
||||||
|
|
||||||
target_include_directories(test_default
|
|
||||||
PRIVATE
|
|
||||||
${GTEST_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(test_default
|
|
||||||
${GTEST_BOTH_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
add_test(
|
|
||||||
NAME test_default
|
|
||||||
COMMAND test_default
|
|
||||||
)
|
|
||||||
|
|
||||||
set_property(
|
|
||||||
TEST test_default
|
|
||||||
PROPERTY ENVIRONMENT "ASAN_OPTIONS=allocator_may_return_null=1"
|
|
||||||
)
|
|
||||||
|
|
||||||
file(GLOB ITEMS *)
|
|
||||||
foreach(item ${ITEMS})
|
|
||||||
if(IS_DIRECTORY ${item} AND EXISTS ${item}/CMakeLists.txt)
|
|
||||||
message(${item})
|
|
||||||
add_subdirectory(${item})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# C++ tests
|
|
||||||
|
|
||||||
Test framework for C and C++ code in AP.
|
|
||||||
|
|
||||||
## Adding a Test
|
|
||||||
|
|
||||||
### GoogleTest
|
|
||||||
|
|
||||||
Adding GoogleTests is as simple as creating a directory with
|
|
||||||
* one or more `test_*.cpp` files that define tests using
|
|
||||||
[GoogleTest API](https://google.github.io/googletest/)
|
|
||||||
* a `CMakeLists.txt` that adds the .cpp files to `test_default` target using
|
|
||||||
[target_sources](https://cmake.org/cmake/help/latest/command/target_sources.html)
|
|
||||||
|
|
||||||
### CTest
|
|
||||||
|
|
||||||
If either GoogleTest is not suitable for the test or the build flags / sources / libraries are incompatible,
|
|
||||||
you can add another CTest to the project using add_target and add_test, similar to how it's done for `test_default`.
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
* Install [CMake](https://cmake.org/).
|
|
||||||
* Build and/or install GoogleTest and make sure
|
|
||||||
[CMake can find it](https://cmake.org/cmake/help/latest/module/FindGTest.html), or
|
|
||||||
[create a parent `CMakeLists.txt` that fetches GoogleTest](https://google.github.io/googletest/quickstart-cmake.html).
|
|
||||||
* Enter the directory with the top-most `CMakeLists.txt` and run
|
|
||||||
```sh
|
|
||||||
mkdir build
|
|
||||||
cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build/ --config Release && \
|
|
||||||
ctest --test-dir build/ -C Release --output-on-failure
|
|
||||||
```
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
target_sources(test_default
|
|
||||||
PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_intset.cpp
|
|
||||||
)
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
#include <limits>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
// uint32Set
|
|
||||||
#define INTSET_NAME uint32Set
|
|
||||||
#define INTSET_TYPE uint32_t
|
|
||||||
#include "../../../intset.h"
|
|
||||||
#undef INTSET_NAME
|
|
||||||
#undef INTSET_TYPE
|
|
||||||
|
|
||||||
// int64Set
|
|
||||||
#define INTSET_NAME int64Set
|
|
||||||
#define INTSET_TYPE int64_t
|
|
||||||
#include "../../../intset.h"
|
|
||||||
|
|
||||||
|
|
||||||
TEST(IntsetTest, ZeroBuckets)
|
|
||||||
{
|
|
||||||
// trying to allocate with zero buckets has to either fail or be functioning
|
|
||||||
uint32Set *set = uint32Set_new(0);
|
|
||||||
if (!set)
|
|
||||||
return; // failed -> OK
|
|
||||||
|
|
||||||
EXPECT_FALSE(uint32Set_contains(set, 1));
|
|
||||||
EXPECT_TRUE(uint32Set_add(set, 1));
|
|
||||||
EXPECT_TRUE(uint32Set_contains(set, 1));
|
|
||||||
uint32Set_free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IntsetTest, Duplicate)
|
|
||||||
{
|
|
||||||
// adding the same number again can't fail
|
|
||||||
uint32Set *set = uint32Set_new(2);
|
|
||||||
ASSERT_TRUE(set);
|
|
||||||
EXPECT_TRUE(uint32Set_add(set, 0));
|
|
||||||
EXPECT_TRUE(uint32Set_add(set, 0));
|
|
||||||
EXPECT_TRUE(uint32Set_contains(set, 0));
|
|
||||||
uint32Set_free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IntsetTest, SetAllocFailure)
|
|
||||||
{
|
|
||||||
// try to allocate 100TB of RAM, should fail and return NULL
|
|
||||||
if (sizeof(size_t) < 8)
|
|
||||||
GTEST_SKIP() << "Alloc error not testable on 32bit";
|
|
||||||
int64Set *set = int64Set_new(6250000000000ULL);
|
|
||||||
EXPECT_FALSE(set);
|
|
||||||
int64Set_free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IntsetTest, SetAllocOverflow)
|
|
||||||
{
|
|
||||||
// try to overflow argument passed to malloc
|
|
||||||
int64Set *set = int64Set_new(std::numeric_limits<size_t>::max());
|
|
||||||
EXPECT_FALSE(set);
|
|
||||||
int64Set_free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IntsetTest, NullFree)
|
|
||||||
{
|
|
||||||
// free(NULL) should not try to free buckets
|
|
||||||
uint32Set_free(NULL);
|
|
||||||
int64Set_free(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IntsetTest, BucketRealloc)
|
|
||||||
{
|
|
||||||
// add a couple of values to the same bucket to test growing the bucket
|
|
||||||
uint32Set* set = uint32Set_new(1);
|
|
||||||
ASSERT_TRUE(set);
|
|
||||||
EXPECT_FALSE(uint32Set_contains(set, 0));
|
|
||||||
EXPECT_TRUE(uint32Set_add(set, 0));
|
|
||||||
EXPECT_TRUE(uint32Set_contains(set, 0));
|
|
||||||
for (uint32_t i = 1; i < 32; ++i) {
|
|
||||||
EXPECT_TRUE(uint32Set_add(set, i));
|
|
||||||
EXPECT_TRUE(uint32Set_contains(set, i - 1));
|
|
||||||
EXPECT_TRUE(uint32Set_contains(set, i));
|
|
||||||
EXPECT_FALSE(uint32Set_contains(set, i + 1));
|
|
||||||
}
|
|
||||||
uint32Set_free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IntSet, Max)
|
|
||||||
{
|
|
||||||
constexpr auto n = std::numeric_limits<uint32_t>::max();
|
|
||||||
uint32Set *set = uint32Set_new(1);
|
|
||||||
ASSERT_TRUE(set);
|
|
||||||
EXPECT_FALSE(uint32Set_contains(set, n));
|
|
||||||
EXPECT_TRUE(uint32Set_add(set, n));
|
|
||||||
EXPECT_TRUE(uint32Set_contains(set, n));
|
|
||||||
uint32Set_free(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(InsetTest, Negative)
|
|
||||||
{
|
|
||||||
constexpr auto n = std::numeric_limits<int64_t>::min();
|
|
||||||
static_assert(n < 0, "n not negative");
|
|
||||||
int64Set *set = int64Set_new(3);
|
|
||||||
ASSERT_TRUE(set);
|
|
||||||
EXPECT_FALSE(int64Set_contains(set, n));
|
|
||||||
EXPECT_TRUE(int64Set_add(set, n));
|
|
||||||
EXPECT_TRUE(int64Set_contains(set, n));
|
|
||||||
int64Set_free(set);
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ from argparse import Namespace
|
|||||||
from typing import List, Optional, Tuple, Type, Union
|
from typing import List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
|
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
|
||||||
from worlds import network_data_package
|
|
||||||
from worlds.AutoWorld import World, call_all
|
from worlds.AutoWorld import World, call_all
|
||||||
|
|
||||||
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
|
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
|
||||||
@@ -61,10 +60,6 @@ class TestWorld(World):
|
|||||||
hidden = True
|
hidden = True
|
||||||
|
|
||||||
|
|
||||||
# add our test world to the data package, so we can test it later
|
|
||||||
network_data_package["games"][TestWorld.game] = TestWorld.get_data_package_data()
|
|
||||||
|
|
||||||
|
|
||||||
def generate_test_multiworld(players: int = 1) -> MultiWorld:
|
def generate_test_multiworld(players: int = 1) -> MultiWorld:
|
||||||
"""
|
"""
|
||||||
Generates a multiworld using a special Test Case World class, and seed of 0.
|
Generates a multiworld using a special Test Case World class, and seed of 0.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from Fill import distribute_items_restrictive
|
from Fill import distribute_items_restrictive
|
||||||
from worlds import network_data_package
|
|
||||||
from worlds.AutoWorld import AutoWorldRegister, call_all
|
from worlds.AutoWorld import AutoWorldRegister, call_all
|
||||||
from . import setup_solo_multiworld
|
from . import setup_solo_multiworld
|
||||||
|
|
||||||
@@ -85,4 +84,3 @@ class TestIDs(unittest.TestCase):
|
|||||||
f"{loc_name} is not a valid item name for location_name_to_id")
|
f"{loc_name} is not a valid item name for location_name_to_id")
|
||||||
self.assertIsInstance(loc_id, int,
|
self.assertIsInstance(loc_id, int,
|
||||||
f"{loc_id} for {loc_name} should be an int")
|
f"{loc_id} for {loc_name} should be an int")
|
||||||
self.assertEqual(datapackage["checksum"], network_data_package["games"][gamename]["checksum"])
|
|
||||||
|
|||||||
@@ -66,19 +66,12 @@ def create_room(app_client: "FlaskClient", seed: str, auto_start: bool = False)
|
|||||||
def start_room(app_client: "FlaskClient", room_id: str, timeout: float = 30) -> str:
|
def start_room(app_client: "FlaskClient", room_id: str, timeout: float = 30) -> str:
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
import pony.orm
|
|
||||||
|
|
||||||
poll_interval = .2
|
poll_interval = .2
|
||||||
|
|
||||||
print(f"Starting room {room_id}")
|
print(f"Starting room {room_id}")
|
||||||
no_timeout = timeout <= 0
|
no_timeout = timeout <= 0
|
||||||
while no_timeout or timeout > 0:
|
while no_timeout or timeout > 0:
|
||||||
try:
|
response = app_client.get(f"/room/{room_id}")
|
||||||
response = app_client.get(f"/room/{room_id}")
|
|
||||||
except pony.orm.core.OptimisticCheckError:
|
|
||||||
# hoster wrote to room during our transaction
|
|
||||||
continue
|
|
||||||
|
|
||||||
assert response.status_code == 200, f"Starting room for {room_id} failed: status {response.status_code}"
|
assert response.status_code == 200, f"Starting room for {room_id} failed: status {response.status_code}"
|
||||||
match = re.search(r"/connect ([\w:.\-]+)", response.text)
|
match = re.search(r"/connect ([\w:.\-]+)", response.text)
|
||||||
if match:
|
if match:
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# Tests for _speedups.LocationStore and NetUtils._LocationStore
|
# Tests for _speedups.LocationStore and NetUtils._LocationStore
|
||||||
import os
|
|
||||||
import typing
|
import typing
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
@@ -8,8 +7,6 @@ from NetUtils import LocationStore, _LocationStore
|
|||||||
State = typing.Dict[typing.Tuple[int, int], typing.Set[int]]
|
State = typing.Dict[typing.Tuple[int, int], typing.Set[int]]
|
||||||
RawLocations = typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int]]]
|
RawLocations = typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int]]]
|
||||||
|
|
||||||
ci = bool(os.environ.get("CI")) # always set in GitHub actions
|
|
||||||
|
|
||||||
sample_data: RawLocations = {
|
sample_data: RawLocations = {
|
||||||
1: {
|
1: {
|
||||||
11: (21, 2, 7),
|
11: (21, 2, 7),
|
||||||
@@ -27,9 +24,6 @@ sample_data: RawLocations = {
|
|||||||
3: {
|
3: {
|
||||||
9: (99, 4, 0),
|
9: (99, 4, 0),
|
||||||
},
|
},
|
||||||
5: {
|
|
||||||
9: (99, 5, 0),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
empty_state: State = {
|
empty_state: State = {
|
||||||
@@ -51,14 +45,14 @@ class Base:
|
|||||||
store: typing.Union[LocationStore, _LocationStore]
|
store: typing.Union[LocationStore, _LocationStore]
|
||||||
|
|
||||||
def test_len(self) -> None:
|
def test_len(self) -> None:
|
||||||
self.assertEqual(len(self.store), 5)
|
self.assertEqual(len(self.store), 4)
|
||||||
self.assertEqual(len(self.store[1]), 3)
|
self.assertEqual(len(self.store[1]), 3)
|
||||||
|
|
||||||
def test_key_error(self) -> None:
|
def test_key_error(self) -> None:
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
_ = self.store[0]
|
_ = self.store[0]
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
_ = self.store[6]
|
_ = self.store[5]
|
||||||
locations = self.store[1] # no Exception
|
locations = self.store[1] # no Exception
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
_ = locations[7]
|
_ = locations[7]
|
||||||
@@ -77,7 +71,7 @@ class Base:
|
|||||||
self.assertEqual(self.store[1].get(10, (None, None, None)), (None, None, None))
|
self.assertEqual(self.store[1].get(10, (None, None, None)), (None, None, None))
|
||||||
|
|
||||||
def test_iter(self) -> None:
|
def test_iter(self) -> None:
|
||||||
self.assertEqual(sorted(self.store), [1, 2, 3, 4, 5])
|
self.assertEqual(sorted(self.store), [1, 2, 3, 4])
|
||||||
self.assertEqual(len(self.store), len(sample_data))
|
self.assertEqual(len(self.store), len(sample_data))
|
||||||
self.assertEqual(list(self.store[1]), [11, 12, 13])
|
self.assertEqual(list(self.store[1]), [11, 12, 13])
|
||||||
self.assertEqual(len(self.store[1]), len(sample_data[1]))
|
self.assertEqual(len(self.store[1]), len(sample_data[1]))
|
||||||
@@ -91,26 +85,13 @@ class Base:
|
|||||||
self.assertEqual(sorted(self.store[1].items())[0][1], self.store[1][11])
|
self.assertEqual(sorted(self.store[1].items())[0][1], self.store[1][11])
|
||||||
|
|
||||||
def test_find_item(self) -> None:
|
def test_find_item(self) -> None:
|
||||||
# empty player set
|
|
||||||
self.assertEqual(sorted(self.store.find_item(set(), 99)), [])
|
self.assertEqual(sorted(self.store.find_item(set(), 99)), [])
|
||||||
# no such player, single
|
|
||||||
self.assertEqual(sorted(self.store.find_item({6}, 99)), [])
|
|
||||||
# no such player, set
|
|
||||||
self.assertEqual(sorted(self.store.find_item({7, 8, 9}, 99)), [])
|
|
||||||
# no such item
|
|
||||||
self.assertEqual(sorted(self.store.find_item({3}, 1)), [])
|
self.assertEqual(sorted(self.store.find_item({3}, 1)), [])
|
||||||
# valid matches
|
self.assertEqual(sorted(self.store.find_item({5}, 99)), [])
|
||||||
self.assertEqual(sorted(self.store.find_item({3}, 99)),
|
self.assertEqual(sorted(self.store.find_item({3}, 99)),
|
||||||
[(4, 9, 99, 3, 0)])
|
[(4, 9, 99, 3, 0)])
|
||||||
self.assertEqual(sorted(self.store.find_item({3, 4}, 99)),
|
self.assertEqual(sorted(self.store.find_item({3, 4}, 99)),
|
||||||
[(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)])
|
[(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)])
|
||||||
self.assertEqual(sorted(self.store.find_item({2, 3, 4}, 99)),
|
|
||||||
[(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)])
|
|
||||||
# test hash collision in set
|
|
||||||
self.assertEqual(sorted(self.store.find_item({3, 5}, 99)),
|
|
||||||
[(4, 9, 99, 3, 0), (5, 9, 99, 5, 0)])
|
|
||||||
self.assertEqual(sorted(self.store.find_item(set(range(2048)), 13)),
|
|
||||||
[(1, 13, 13, 1, 0)])
|
|
||||||
|
|
||||||
def test_get_for_player(self) -> None:
|
def test_get_for_player(self) -> None:
|
||||||
self.assertEqual(self.store.get_for_player(3), {4: {9}})
|
self.assertEqual(self.store.get_for_player(3), {4: {9}})
|
||||||
@@ -215,20 +196,18 @@ class TestPurePythonLocationStoreConstructor(Base.TestLocationStoreConstructor):
|
|||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(LocationStore is _LocationStore and not ci, "_speedups not available")
|
@unittest.skipIf(LocationStore is _LocationStore, "_speedups not available")
|
||||||
class TestSpeedupsLocationStore(Base.TestLocationStore):
|
class TestSpeedupsLocationStore(Base.TestLocationStore):
|
||||||
"""Run base method tests for cython implementation."""
|
"""Run base method tests for cython implementation."""
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.assertFalse(LocationStore is _LocationStore, "Failed to load _speedups")
|
|
||||||
self.store = LocationStore(sample_data)
|
self.store = LocationStore(sample_data)
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(LocationStore is _LocationStore and not ci, "_speedups not available")
|
@unittest.skipIf(LocationStore is _LocationStore, "_speedups not available")
|
||||||
class TestSpeedupsLocationStoreConstructor(Base.TestLocationStoreConstructor):
|
class TestSpeedupsLocationStoreConstructor(Base.TestLocationStoreConstructor):
|
||||||
"""Run base constructor tests and tests the additional constraints for cython implementation."""
|
"""Run base constructor tests and tests the additional constraints for cython implementation."""
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.assertFalse(LocationStore is _LocationStore, "Failed to load _speedups")
|
|
||||||
self.type = LocationStore
|
self.type = LocationStore
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ class WebWorldRegister(type):
|
|||||||
assert group.options, "A custom defined Option Group must contain at least one Option."
|
assert group.options, "A custom defined Option Group must contain at least one Option."
|
||||||
# catch incorrectly titled versions of the prebuilt groups so they don't create extra groups
|
# catch incorrectly titled versions of the prebuilt groups so they don't create extra groups
|
||||||
title_name = group.name.title()
|
title_name = group.name.title()
|
||||||
assert title_name not in prebuilt_options or title_name == group.name, \
|
if title_name in prebuilt_options:
|
||||||
f"Prebuilt group name \"{group.name}\" must be \"{title_name}\""
|
group.name = title_name
|
||||||
|
|
||||||
if group.name == "Item & Location Options":
|
if group.name == "Item & Location Options":
|
||||||
assert not any(option in item_and_loc_options for option in group.options), \
|
assert not any(option in item_and_loc_options for option in group.options), \
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import importlib.util
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -108,9 +107,8 @@ for folder in (folder for folder in (user_folder, local_folder) if folder):
|
|||||||
if not entry.name.startswith(("_", ".")):
|
if not entry.name.startswith(("_", ".")):
|
||||||
file_name = entry.name if relative else os.path.join(folder, entry.name)
|
file_name = entry.name if relative else os.path.join(folder, entry.name)
|
||||||
if entry.is_dir():
|
if entry.is_dir():
|
||||||
if os.path.isfile(os.path.join(entry.path, '__init__.py')):
|
init_file_path = os.path.join(entry.path, '__init__.py')
|
||||||
world_sources.append(WorldSource(file_name, relative=relative))
|
if os.path.isfile(init_file_path):
|
||||||
elif os.path.isfile(os.path.join(entry.path, '__init__.pyc')):
|
|
||||||
world_sources.append(WorldSource(file_name, relative=relative))
|
world_sources.append(WorldSource(file_name, relative=relative))
|
||||||
else:
|
else:
|
||||||
logging.warning(f"excluding {entry.name} from world sources because it has no __init__.py")
|
logging.warning(f"excluding {entry.name} from world sources because it has no __init__.py")
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ class kill_switch:
|
|||||||
logger.debug("kill_switch: Add switch")
|
logger.debug("kill_switch: Add switch")
|
||||||
cls._to_kill.append(value)
|
cls._to_kill.append(value)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def kill(cls, value):
|
|
||||||
logger.info(f"kill_switch: Process cleanup for 1 process")
|
|
||||||
value._clean(verbose=False)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def kill_all(cls):
|
def kill_all(cls):
|
||||||
logger.info(f"kill_switch: Process cleanup for {len(cls._to_kill)} processes")
|
logger.info(f"kill_switch: Process cleanup for {len(cls._to_kill)} processes")
|
||||||
@@ -121,7 +116,7 @@ class SC2Process:
|
|||||||
async def __aexit__(self, *args):
|
async def __aexit__(self, *args):
|
||||||
logger.exception("async exit")
|
logger.exception("async exit")
|
||||||
await self._close_connection()
|
await self._close_connection()
|
||||||
kill_switch.kill(self)
|
kill_switch.kill_all()
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle
|
||||||
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
|
|
||||||
|
|
||||||
|
|
||||||
class FreeincarnateMax(Range):
|
class FreeincarnateMax(Range):
|
||||||
@@ -224,22 +223,22 @@ class StartCastle(Choice):
|
|||||||
option_white = 2
|
option_white = 2
|
||||||
default = option_yellow
|
default = option_yellow
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AdventureOptions(PerGameCommonOptions):
|
|
||||||
dragon_slay_check: DragonSlayCheck
|
|
||||||
death_link: DeathLink
|
|
||||||
bat_logic: BatLogic
|
|
||||||
freeincarnate_max: FreeincarnateMax
|
|
||||||
dragon_rando_type: DragonRandoType
|
|
||||||
connector_multi_slot: ConnectorMultiSlot
|
|
||||||
yorgle_speed: YorgleStartingSpeed
|
|
||||||
yorgle_min_speed: YorgleMinimumSpeed
|
|
||||||
grundle_speed: GrundleStartingSpeed
|
|
||||||
grundle_min_speed: GrundleMinimumSpeed
|
|
||||||
rhindle_speed: RhindleStartingSpeed
|
|
||||||
rhindle_min_speed: RhindleMinimumSpeed
|
|
||||||
difficulty_switch_a: DifficultySwitchA
|
|
||||||
difficulty_switch_b: DifficultySwitchB
|
|
||||||
start_castle: StartCastle
|
|
||||||
|
|
||||||
|
adventure_option_definitions: Dict[str, type(Option)] = {
|
||||||
|
"dragon_slay_check": DragonSlayCheck,
|
||||||
|
"death_link": DeathLink,
|
||||||
|
"bat_logic": BatLogic,
|
||||||
|
"freeincarnate_max": FreeincarnateMax,
|
||||||
|
"dragon_rando_type": DragonRandoType,
|
||||||
|
"connector_multi_slot": ConnectorMultiSlot,
|
||||||
|
"yorgle_speed": YorgleStartingSpeed,
|
||||||
|
"yorgle_min_speed": YorgleMinimumSpeed,
|
||||||
|
"grundle_speed": GrundleStartingSpeed,
|
||||||
|
"grundle_min_speed": GrundleMinimumSpeed,
|
||||||
|
"rhindle_speed": RhindleStartingSpeed,
|
||||||
|
"rhindle_min_speed": RhindleMinimumSpeed,
|
||||||
|
"difficulty_switch_a": DifficultySwitchA,
|
||||||
|
"difficulty_switch_b": DifficultySwitchB,
|
||||||
|
"start_castle": StartCastle,
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
|
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
|
||||||
from Options import PerGameCommonOptions
|
|
||||||
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
|
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
|
|||||||
connect(world, player, target, source, rule, True)
|
connect(world, player, target, source, rule, True)
|
||||||
|
|
||||||
|
|
||||||
def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
||||||
|
|
||||||
menu = Region("Menu", player, multiworld)
|
menu = Region("Menu", player, multiworld)
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player
|
|||||||
credits_room_far_side.exits.append(Entrance(player, "CreditsFromFarSide", credits_room_far_side))
|
credits_room_far_side.exits.append(Entrance(player, "CreditsFromFarSide", credits_room_far_side))
|
||||||
multiworld.regions.append(credits_room_far_side)
|
multiworld.regions.append(credits_room_far_side)
|
||||||
|
|
||||||
dragon_slay_check = options.dragon_slay_check.value
|
dragon_slay_check = multiworld.dragon_slay_check[player].value
|
||||||
priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
|
priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
|
||||||
|
|
||||||
for name, location_data in location_table.items():
|
for name, location_data in location_table.items():
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from BaseClasses import LocationProgressType
|
|||||||
|
|
||||||
def set_rules(self) -> None:
|
def set_rules(self) -> None:
|
||||||
world = self.multiworld
|
world = self.multiworld
|
||||||
use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic
|
use_bat_logic = world.bat_logic[self.player].value == BatLogic.option_use_logic
|
||||||
|
|
||||||
set_rule(world.get_entrance("YellowCastlePort", self.player),
|
set_rule(world.get_entrance("YellowCastlePort", self.player),
|
||||||
lambda state: state.has("Yellow Key", self.player))
|
lambda state: state.has("Yellow Key", self.player))
|
||||||
@@ -28,7 +28,7 @@ def set_rules(self) -> None:
|
|||||||
lambda state: state.has("Bridge", self.player) or
|
lambda state: state.has("Bridge", self.player) or
|
||||||
state.has("Magnet", self.player))
|
state.has("Magnet", self.player))
|
||||||
|
|
||||||
dragon_slay_check = self.options.dragon_slay_check.value
|
dragon_slay_check = world.dragon_slay_check[self.player].value
|
||||||
if dragon_slay_check:
|
if dragon_slay_check:
|
||||||
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
||||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ from Options import AssembleOptions
|
|||||||
from worlds.AutoWorld import WebWorld, World
|
from worlds.AutoWorld import WebWorld, World
|
||||||
from Fill import fill_restrictive
|
from Fill import fill_restrictive
|
||||||
from worlds.generic.Rules import add_rule, set_rule
|
from worlds.generic.Rules import add_rule, set_rule
|
||||||
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \
|
from .Options import adventure_option_definitions, DragonRandoType, DifficultySwitchA, DifficultySwitchB
|
||||||
AdventureOptions
|
|
||||||
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
|
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
|
||||||
AdventureAutoCollectLocation
|
AdventureAutoCollectLocation
|
||||||
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
|
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
|
||||||
@@ -110,7 +109,7 @@ class AdventureWorld(World):
|
|||||||
game: ClassVar[str] = "Adventure"
|
game: ClassVar[str] = "Adventure"
|
||||||
web: ClassVar[WebWorld] = AdventureWeb()
|
web: ClassVar[WebWorld] = AdventureWeb()
|
||||||
|
|
||||||
options_dataclass = AdventureOptions
|
option_definitions: ClassVar[Dict[str, AssembleOptions]] = adventure_option_definitions
|
||||||
settings: ClassVar[AdventureSettings]
|
settings: ClassVar[AdventureSettings]
|
||||||
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
|
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
|
||||||
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
||||||
@@ -150,18 +149,18 @@ class AdventureWorld(World):
|
|||||||
bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21]
|
bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21]
|
||||||
self.rom_name.extend([0] * (21 - len(self.rom_name)))
|
self.rom_name.extend([0] * (21 - len(self.rom_name)))
|
||||||
|
|
||||||
self.dragon_rando_type = self.options.dragon_rando_type.value
|
self.dragon_rando_type = self.multiworld.dragon_rando_type[self.player].value
|
||||||
self.dragon_slay_check = self.options.dragon_slay_check.value
|
self.dragon_slay_check = self.multiworld.dragon_slay_check[self.player].value
|
||||||
self.connector_multi_slot = self.options.connector_multi_slot.value
|
self.connector_multi_slot = self.multiworld.connector_multi_slot[self.player].value
|
||||||
self.yorgle_speed = self.options.yorgle_speed.value
|
self.yorgle_speed = self.multiworld.yorgle_speed[self.player].value
|
||||||
self.yorgle_min_speed = self.options.yorgle_min_speed.value
|
self.yorgle_min_speed = self.multiworld.yorgle_min_speed[self.player].value
|
||||||
self.grundle_speed = self.options.grundle_speed.value
|
self.grundle_speed = self.multiworld.grundle_speed[self.player].value
|
||||||
self.grundle_min_speed = self.options.grundle_min_speed.value
|
self.grundle_min_speed = self.multiworld.grundle_min_speed[self.player].value
|
||||||
self.rhindle_speed = self.options.rhindle_speed.value
|
self.rhindle_speed = self.multiworld.rhindle_speed[self.player].value
|
||||||
self.rhindle_min_speed = self.options.rhindle_min_speed.value
|
self.rhindle_min_speed = self.multiworld.rhindle_min_speed[self.player].value
|
||||||
self.difficulty_switch_a = self.options.difficulty_switch_a.value
|
self.difficulty_switch_a = self.multiworld.difficulty_switch_a[self.player].value
|
||||||
self.difficulty_switch_b = self.options.difficulty_switch_b.value
|
self.difficulty_switch_b = self.multiworld.difficulty_switch_b[self.player].value
|
||||||
self.start_castle = self.options.start_castle.value
|
self.start_castle = self.multiworld.start_castle[self.player].value
|
||||||
self.created_items = 0
|
self.created_items = 0
|
||||||
|
|
||||||
if self.dragon_slay_check == 0:
|
if self.dragon_slay_check == 0:
|
||||||
@@ -228,7 +227,7 @@ class AdventureWorld(World):
|
|||||||
extra_filler_count = num_locations - self.created_items
|
extra_filler_count = num_locations - self.created_items
|
||||||
|
|
||||||
# traps would probably go here, if enabled
|
# traps would probably go here, if enabled
|
||||||
freeincarnate_max = self.options.freeincarnate_max.value
|
freeincarnate_max = self.multiworld.freeincarnate_max[self.player].value
|
||||||
actual_freeincarnates = min(extra_filler_count, freeincarnate_max)
|
actual_freeincarnates = min(extra_filler_count, freeincarnate_max)
|
||||||
self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)]
|
self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)]
|
||||||
self.created_items += actual_freeincarnates
|
self.created_items += actual_freeincarnates
|
||||||
@@ -248,7 +247,7 @@ class AdventureWorld(World):
|
|||||||
self.created_items += 1
|
self.created_items += 1
|
||||||
|
|
||||||
def create_regions(self) -> None:
|
def create_regions(self) -> None:
|
||||||
create_regions(self.options, self.multiworld, self.player, self.dragon_rooms)
|
create_regions(self.multiworld, self.player, self.dragon_rooms)
|
||||||
|
|
||||||
set_rules = set_rules
|
set_rules = set_rules
|
||||||
|
|
||||||
@@ -355,7 +354,7 @@ class AdventureWorld(World):
|
|||||||
auto_collect_locations: [AdventureAutoCollectLocation] = []
|
auto_collect_locations: [AdventureAutoCollectLocation] = []
|
||||||
local_item_to_location: {int, int} = {}
|
local_item_to_location: {int, int} = {}
|
||||||
bat_no_touch_locs: [LocationData] = []
|
bat_no_touch_locs: [LocationData] = []
|
||||||
bat_logic: int = self.options.bat_logic.value
|
bat_logic: int = self.multiworld.bat_logic[self.player].value
|
||||||
try:
|
try:
|
||||||
rom_deltas: { int, int } = {}
|
rom_deltas: { int, int } = {}
|
||||||
self.place_dragons(rom_deltas)
|
self.place_dragons(rom_deltas)
|
||||||
@@ -422,7 +421,7 @@ class AdventureWorld(World):
|
|||||||
item_position_data_start = get_item_position_data_start(unplaced_item.table_index)
|
item_position_data_start = get_item_position_data_start(unplaced_item.table_index)
|
||||||
rom_deltas[item_position_data_start] = 0xff
|
rom_deltas[item_position_data_start] = 0xff
|
||||||
|
|
||||||
if self.options.connector_multi_slot.value:
|
if self.multiworld.connector_multi_slot[self.player].value:
|
||||||
rom_deltas[connector_port_offset] = (self.player & 0xff)
|
rom_deltas[connector_port_offset] = (self.player & 0xff)
|
||||||
else:
|
else:
|
||||||
rom_deltas[connector_port_offset] = 0
|
rom_deltas[connector_port_offset] = 0
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class AquariaLocations:
|
|||||||
"Mithalas City, second bulb at the end of the top path": 698040,
|
"Mithalas City, second bulb at the end of the top path": 698040,
|
||||||
"Mithalas City, bulb in the top path": 698036,
|
"Mithalas City, bulb in the top path": 698036,
|
||||||
"Mithalas City, Mithalas Pot": 698174,
|
"Mithalas City, Mithalas Pot": 698174,
|
||||||
"Mithalas City, urn in the Castle flower tube entrance": 698128,
|
"Mithalas City, urn in the Cathedral flower tube entrance": 698128,
|
||||||
}
|
}
|
||||||
|
|
||||||
locations_mithalas_city_fishpass = {
|
locations_mithalas_city_fishpass = {
|
||||||
@@ -246,7 +246,7 @@ class AquariaLocations:
|
|||||||
"Kelp Forest top left area, bulb in the bottom left clearing": 698044,
|
"Kelp Forest top left area, bulb in the bottom left clearing": 698044,
|
||||||
"Kelp Forest top left area, bulb in the path down from the top left clearing": 698045,
|
"Kelp Forest top left area, bulb in the path down from the top left clearing": 698045,
|
||||||
"Kelp Forest top left area, bulb in the top left clearing": 698046,
|
"Kelp Forest top left area, bulb in the top left clearing": 698046,
|
||||||
"Kelp Forest top left area, Jelly Egg": 698185,
|
"Kelp Forest top left, Jelly Egg": 698185,
|
||||||
}
|
}
|
||||||
|
|
||||||
locations_forest_tl_fp = {
|
locations_forest_tl_fp = {
|
||||||
@@ -332,7 +332,7 @@ class AquariaLocations:
|
|||||||
}
|
}
|
||||||
|
|
||||||
locations_veil_tr_l = {
|
locations_veil_tr_l = {
|
||||||
"The Veil top right area, bulb at the top of the waterfall": 698080,
|
"The Veil top right area, bulb in the top of the waterfall": 698080,
|
||||||
"The Veil top right area, Transturtle": 698210,
|
"The Veil top right area, Transturtle": 698210,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -771,7 +771,6 @@ class AquariaRegions:
|
|||||||
self.__connect_regions("Sunken City left area", "Sunken City boss area",
|
self.__connect_regions("Sunken City left area", "Sunken City boss area",
|
||||||
self.sunken_city_l, self.sunken_city_boss,
|
self.sunken_city_l, self.sunken_city_boss,
|
||||||
lambda state: _has_beast_form(state, self.player) and
|
lambda state: _has_beast_form(state, self.player) and
|
||||||
_has_sun_form(state, self.player) and
|
|
||||||
_has_energy_form(state, self.player) and
|
_has_energy_form(state, self.player) and
|
||||||
_has_bind_song(state, self.player))
|
_has_bind_song(state, self.player))
|
||||||
|
|
||||||
@@ -984,7 +983,7 @@ class AquariaRegions:
|
|||||||
lambda state: _has_damaging_item(state, self.player))
|
lambda state: _has_damaging_item(state, self.player))
|
||||||
add_rule(self.multiworld.get_location("Mithalas City, third urn in the city reserve", self.player),
|
add_rule(self.multiworld.get_location("Mithalas City, third urn in the city reserve", self.player),
|
||||||
lambda state: _has_damaging_item(state, self.player))
|
lambda state: _has_damaging_item(state, self.player))
|
||||||
add_rule(self.multiworld.get_location("Mithalas City, urn in the Castle flower tube entrance", self.player),
|
add_rule(self.multiworld.get_location("Mithalas City, urn in the Cathedral flower tube entrance", self.player),
|
||||||
lambda state: _has_damaging_item(state, self.player))
|
lambda state: _has_damaging_item(state, self.player))
|
||||||
add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bedroom", self.player),
|
add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bedroom", self.player),
|
||||||
lambda state: _has_damaging_item(state, self.player))
|
lambda state: _has_damaging_item(state, self.player))
|
||||||
@@ -1024,7 +1023,7 @@ class AquariaRegions:
|
|||||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||||
add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player),
|
add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player),
|
||||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||||
add_rule(self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", self.player),
|
add_rule(self.multiworld.get_location("The Veil top right area, bulb in the top of the waterfall", self.player),
|
||||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||||
|
|
||||||
def __adjusting_under_rock_location(self) -> None:
|
def __adjusting_under_rock_location(self) -> None:
|
||||||
@@ -1176,7 +1175,7 @@ class AquariaRegions:
|
|||||||
self.multiworld.get_location("Sun Worm path, second cliff bulb",
|
self.multiworld.get_location("Sun Worm path, second cliff bulb",
|
||||||
self.player).item_rule =\
|
self.player).item_rule =\
|
||||||
lambda item: item.classification != ItemClassification.progression
|
lambda item: item.classification != ItemClassification.progression
|
||||||
self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall",
|
self.multiworld.get_location("The Veil top right area, bulb in the top of the waterfall",
|
||||||
self.player).item_rule =\
|
self.player).item_rule =\
|
||||||
lambda item: item.classification != ItemClassification.progression
|
lambda item: item.classification != ItemClassification.progression
|
||||||
self.multiworld.get_location("Bubble Cave, bulb in the left cave wall",
|
self.multiworld.get_location("Bubble Cave, bulb in the left cave wall",
|
||||||
|
|||||||
@@ -167,10 +167,14 @@ class AquariaWorld(World):
|
|||||||
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
|
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
|
||||||
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
|
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
|
||||||
for name, data in item_table.items():
|
for name, data in item_table.items():
|
||||||
if name not in self.exclude:
|
if name in precollected:
|
||||||
for i in range(data.count):
|
precollected.remove(name)
|
||||||
item = self.create_item(name)
|
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
|
||||||
self.multiworld.itempool.append(item)
|
else:
|
||||||
|
if name not in self.exclude:
|
||||||
|
for i in range(data.count):
|
||||||
|
item = self.create_item(name)
|
||||||
|
self.multiworld.itempool.append(item)
|
||||||
|
|
||||||
def set_rules(self) -> None:
|
def set_rules(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ after_home_water_locations = [
|
|||||||
"Mithalas City, second bulb at the end of the top path",
|
"Mithalas City, second bulb at the end of the top path",
|
||||||
"Mithalas City, bulb in the top path",
|
"Mithalas City, bulb in the top path",
|
||||||
"Mithalas City, Mithalas Pot",
|
"Mithalas City, Mithalas Pot",
|
||||||
"Mithalas City, urn in the Castle flower tube entrance",
|
"Mithalas City, urn in the Cathedral flower tube entrance",
|
||||||
"Mithalas City, Doll",
|
"Mithalas City, Doll",
|
||||||
"Mithalas City, urn inside a home fish pass",
|
"Mithalas City, urn inside a home fish pass",
|
||||||
"Mithalas City Castle, bulb in the flesh hole",
|
"Mithalas City Castle, bulb in the flesh hole",
|
||||||
@@ -93,7 +93,7 @@ after_home_water_locations = [
|
|||||||
"Kelp Forest top left area, bulb in the bottom left clearing",
|
"Kelp Forest top left area, bulb in the bottom left clearing",
|
||||||
"Kelp Forest top left area, bulb in the path down from the top left clearing",
|
"Kelp Forest top left area, bulb in the path down from the top left clearing",
|
||||||
"Kelp Forest top left area, bulb in the top left clearing",
|
"Kelp Forest top left area, bulb in the top left clearing",
|
||||||
"Kelp Forest top left area, Jelly Egg",
|
"Kelp Forest top left, Jelly Egg",
|
||||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||||
"Kelp Forest top left area, Verse Egg",
|
"Kelp Forest top left area, Verse Egg",
|
||||||
"Kelp Forest top right area, bulb under the rock in the right path",
|
"Kelp Forest top right area, bulb under the rock in the right path",
|
||||||
@@ -125,7 +125,7 @@ after_home_water_locations = [
|
|||||||
"Turtle cave, Urchin Costume",
|
"Turtle cave, Urchin Costume",
|
||||||
"The Veil top right area, bulb in the middle of the wall jump cliff",
|
"The Veil top right area, bulb in the middle of the wall jump cliff",
|
||||||
"The Veil top right area, Golden Starfish",
|
"The Veil top right area, Golden Starfish",
|
||||||
"The Veil top right area, bulb at the top of the waterfall",
|
"The Veil top right area, bulb in the top of the waterfall",
|
||||||
"The Veil top right area, Transturtle",
|
"The Veil top right area, Transturtle",
|
||||||
"The Veil bottom area, bulb in the left path",
|
"The Veil bottom area, bulb in the left path",
|
||||||
"The Veil bottom area, bulb in the spirit path",
|
"The Veil bottom area, bulb in the spirit path",
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ class BeastFormAccessTest(AquariaTestBase):
|
|||||||
"Mithalas City, second bulb at the end of the top path",
|
"Mithalas City, second bulb at the end of the top path",
|
||||||
"Mithalas City, bulb in the top path",
|
"Mithalas City, bulb in the top path",
|
||||||
"Mithalas City, Mithalas Pot",
|
"Mithalas City, Mithalas Pot",
|
||||||
"Mithalas City, urn in the Castle flower tube entrance",
|
"Mithalas City, urn in the Cathedral flower tube entrance",
|
||||||
"Mermog cave, Piranha Egg",
|
"Mermog cave, Piranha Egg",
|
||||||
"Mithalas Cathedral, Mithalan Dress",
|
"Mithalas Cathedral, Mithalan Dress",
|
||||||
"Turtle cave, bulb in Bubble Cliff",
|
"Turtle cave, bulb in Bubble Cliff",
|
||||||
"Turtle cave, Urchin Costume",
|
"Turtle cave, Urchin Costume",
|
||||||
"Sun Worm path, first cliff bulb",
|
"Sun Worm path, first cliff bulb",
|
||||||
"Sun Worm path, second cliff bulb",
|
"Sun Worm path, second cliff bulb",
|
||||||
"The Veil top right area, bulb at the top of the waterfall",
|
"The Veil top right area, bulb in the top of the waterfall",
|
||||||
"Bubble Cave, bulb in the left cave wall",
|
"Bubble Cave, bulb in the left cave wall",
|
||||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||||
"Bubble Cave, Verse Egg",
|
"Bubble Cave, Verse Egg",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
|
|||||||
"Final Boss area, bulb in the boss third form room",
|
"Final Boss area, bulb in the boss third form room",
|
||||||
"Sun Worm path, first cliff bulb",
|
"Sun Worm path, first cliff bulb",
|
||||||
"Sun Worm path, second cliff bulb",
|
"Sun Worm path, second cliff bulb",
|
||||||
"The Veil top right area, bulb at the top of the waterfall",
|
"The Veil top right area, bulb in the top of the waterfall",
|
||||||
"Bubble Cave, bulb in the left cave wall",
|
"Bubble Cave, bulb in the left cave wall",
|
||||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||||
"Bubble Cave, Verse Egg",
|
"Bubble Cave, Verse Egg",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
|
|||||||
"Final Boss area, bulb in the boss third form room",
|
"Final Boss area, bulb in the boss third form room",
|
||||||
"Sun Worm path, first cliff bulb",
|
"Sun Worm path, first cliff bulb",
|
||||||
"Sun Worm path, second cliff bulb",
|
"Sun Worm path, second cliff bulb",
|
||||||
"The Veil top right area, bulb at the top of the waterfall",
|
"The Veil top right area, bulb in the top of the waterfall",
|
||||||
"Bubble Cave, bulb in the left cave wall",
|
"Bubble Cave, bulb in the left cave wall",
|
||||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||||
"Bubble Cave, Verse Egg",
|
"Bubble Cave, Verse Egg",
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ class SunFormAccessTest(AquariaTestBase):
|
|||||||
"Abyss right area, bulb behind the rock in the whale room",
|
"Abyss right area, bulb behind the rock in the whale room",
|
||||||
"Octopus Cave, Dumbo Egg",
|
"Octopus Cave, Dumbo Egg",
|
||||||
"Beating Octopus Prime",
|
"Beating Octopus Prime",
|
||||||
"Sunken City, bulb on top of the boss area",
|
|
||||||
"Beating the Golem",
|
|
||||||
"Sunken City cleared",
|
|
||||||
"Final Boss area, bulb in the boss third form room",
|
"Final Boss area, bulb in the boss third form room",
|
||||||
"Objective complete"
|
"Objective complete"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -64,4 +64,3 @@ item_name_groups = ({
|
|||||||
})
|
})
|
||||||
item_name_groups['Horizontal'] = item_name_groups['Cloak'] | item_name_groups['CDash']
|
item_name_groups['Horizontal'] = item_name_groups['Cloak'] | item_name_groups['CDash']
|
||||||
item_name_groups['Vertical'] = item_name_groups['Claw'] | {'Monarch_Wings'}
|
item_name_groups['Vertical'] = item_name_groups['Claw'] | {'Monarch_Wings'}
|
||||||
item_name_groups['Skills'] |= item_name_groups['Vertical'] | item_name_groups['Horizontal']
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from schema import And, Optional, Or, Schema
|
|||||||
|
|
||||||
from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, \
|
from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, \
|
||||||
PlandoConnections, Range, StartInventoryPool, Toggle, Visibility
|
PlandoConnections, Range, StartInventoryPool, Toggle, Visibility
|
||||||
from .portals import CHECKPOINTS, PORTALS, SHOP_POINTS
|
from worlds.messenger.portals import CHECKPOINTS, PORTALS, SHOP_POINTS
|
||||||
|
|
||||||
|
|
||||||
class MessengerAccessibility(Accessibility):
|
class MessengerAccessibility(Accessibility):
|
||||||
|
|||||||
@@ -306,7 +306,8 @@ def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None:
|
|||||||
if world.options.scale_stats:
|
if world.options.scale_stats:
|
||||||
patch.write_token(APTokenTypes.WRITE, 0xD00002, bytes([0x1]))
|
patch.write_token(APTokenTypes.WRITE, 0xD00002, bytes([0x1]))
|
||||||
|
|
||||||
patch.write_token(APTokenTypes.WRITE, 0xD00003, bytes([world.options.xp_multiplier.value]))
|
if world.options.xp_multiplier:
|
||||||
|
patch.write_token(APTokenTypes.WRITE, 0xD00003, bytes([world.options.xp_multiplier.value]))
|
||||||
|
|
||||||
if world.options.tattle_hp:
|
if world.options.tattle_hp:
|
||||||
patch.write_token(APTokenTypes.WRITE, 0xD00000, bytes([0x1]))
|
patch.write_token(APTokenTypes.WRITE, 0xD00000, bytes([0x1]))
|
||||||
|
|||||||
@@ -249,7 +249,9 @@ class MuseDashWorld(World):
|
|||||||
|
|
||||||
def create_regions(self) -> None:
|
def create_regions(self) -> None:
|
||||||
menu_region = Region("Menu", self.player, self.multiworld)
|
menu_region = Region("Menu", self.player, self.multiworld)
|
||||||
self.multiworld.regions += [menu_region]
|
song_select_region = Region("Song Select", self.player, self.multiworld)
|
||||||
|
self.multiworld.regions += [menu_region, song_select_region]
|
||||||
|
menu_region.connect(song_select_region)
|
||||||
|
|
||||||
# Make a collection of all songs available for this rando.
|
# Make a collection of all songs available for this rando.
|
||||||
# 1. All starting songs
|
# 1. All starting songs
|
||||||
@@ -263,16 +265,18 @@ class MuseDashWorld(World):
|
|||||||
self.random.shuffle(included_song_copy)
|
self.random.shuffle(included_song_copy)
|
||||||
all_selected_locations.extend(included_song_copy)
|
all_selected_locations.extend(included_song_copy)
|
||||||
|
|
||||||
# Adds 2 item locations per song/album to the menu region.
|
# Make a region per song/album, then adds 1-2 item locations to them
|
||||||
for i in range(0, len(all_selected_locations)):
|
for i in range(0, len(all_selected_locations)):
|
||||||
name = all_selected_locations[i]
|
name = all_selected_locations[i]
|
||||||
loc1 = MuseDashLocation(self.player, name + "-0", self.md_collection.song_locations[name + "-0"], menu_region)
|
region = Region(name, self.player, self.multiworld)
|
||||||
loc1.access_rule = lambda state, place=name: state.has(place, self.player)
|
self.multiworld.regions.append(region)
|
||||||
menu_region.locations.append(loc1)
|
song_select_region.connect(region, name, lambda state, place=name: state.has(place, self.player))
|
||||||
|
|
||||||
loc2 = MuseDashLocation(self.player, name + "-1", self.md_collection.song_locations[name + "-1"], menu_region)
|
# Muse Dash requires 2 locations per song to be *interesting*. Balanced out by filler.
|
||||||
loc2.access_rule = lambda state, place=name: state.has(place, self.player)
|
region.add_locations({
|
||||||
menu_region.locations.append(loc2)
|
name + "-0": self.md_collection.song_locations[name + "-0"],
|
||||||
|
name + "-1": self.md_collection.song_locations[name + "-1"]
|
||||||
|
}, MuseDashLocation)
|
||||||
|
|
||||||
def set_rules(self) -> None:
|
def set_rules(self) -> None:
|
||||||
self.multiworld.completion_condition[self.player] = lambda state: \
|
self.multiworld.completion_condition[self.player] = lambda state: \
|
||||||
|
|||||||
@@ -107,10 +107,10 @@ class ColouredMessage:
|
|||||||
def coloured(self, text: str, colour: str) -> 'ColouredMessage':
|
def coloured(self, text: str, colour: str) -> 'ColouredMessage':
|
||||||
add_json_text(self.parts, text, type="color", color=colour)
|
add_json_text(self.parts, text, type="color", color=colour)
|
||||||
return self
|
return self
|
||||||
def location(self, location_id: int, player_id: int) -> 'ColouredMessage':
|
def location(self, location_id: int, player_id: int = 0) -> 'ColouredMessage':
|
||||||
add_json_location(self.parts, location_id, player_id)
|
add_json_location(self.parts, location_id, player_id)
|
||||||
return self
|
return self
|
||||||
def item(self, item_id: int, player_id: int, flags: int = 0) -> 'ColouredMessage':
|
def item(self, item_id: int, player_id: int = 0, flags: int = 0) -> 'ColouredMessage':
|
||||||
add_json_item(self.parts, item_id, player_id, flags)
|
add_json_item(self.parts, item_id, player_id, flags)
|
||||||
return self
|
return self
|
||||||
def player(self, player_id: int) -> 'ColouredMessage':
|
def player(self, player_id: int) -> 'ColouredMessage':
|
||||||
@@ -122,6 +122,7 @@ class ColouredMessage:
|
|||||||
|
|
||||||
class StarcraftClientProcessor(ClientCommandProcessor):
|
class StarcraftClientProcessor(ClientCommandProcessor):
|
||||||
ctx: SC2Context
|
ctx: SC2Context
|
||||||
|
echo_commands = True
|
||||||
|
|
||||||
def formatted_print(self, text: str) -> None:
|
def formatted_print(self, text: str) -> None:
|
||||||
"""Prints with kivy formatting to the GUI, and also prints to command-line and to all logs"""
|
"""Prints with kivy formatting to the GUI, and also prints to command-line and to all logs"""
|
||||||
@@ -256,7 +257,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
|||||||
for item in received_items_of_this_type:
|
for item in received_items_of_this_type:
|
||||||
print_faction_title()
|
print_faction_title()
|
||||||
has_printed_faction_title = True
|
has_printed_faction_title = True
|
||||||
(ColouredMessage('* ').item(item.item, self.ctx.slot, flags=item.flags)
|
(ColouredMessage('* ').item(item.item, flags=item.flags)
|
||||||
(" from ").location(item.location, self.ctx.slot)
|
(" from ").location(item.location, self.ctx.slot)
|
||||||
(" by ").player(item.player)
|
(" by ").player(item.player)
|
||||||
).send(self.ctx)
|
).send(self.ctx)
|
||||||
@@ -277,7 +278,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
|||||||
received_items_of_this_type = items_received.get(child_item, [])
|
received_items_of_this_type = items_received.get(child_item, [])
|
||||||
for item in received_items_of_this_type:
|
for item in received_items_of_this_type:
|
||||||
filter_match_count += len(received_items_of_this_type)
|
filter_match_count += len(received_items_of_this_type)
|
||||||
(ColouredMessage(' * ').item(item.item, self.ctx.slot, flags=item.flags)
|
(ColouredMessage(' * ').item(item.item, flags=item.flags)
|
||||||
(" from ").location(item.location, self.ctx.slot)
|
(" from ").location(item.location, self.ctx.slot)
|
||||||
(" by ").player(item.player)
|
(" by ").player(item.player)
|
||||||
).send(self.ctx)
|
).send(self.ctx)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from worlds.generic.Rules import forbid_items_for_player, add_rule
|
from worlds.generic.Rules import forbid_items_for_player, add_rule
|
||||||
from .Options import Goal, GoldenFeatherProgression, MinShopCheckLogic, ShopCheckLogic
|
from worlds.shorthike.Options import Goal, GoldenFeatherProgression, MinShopCheckLogic, ShopCheckLogic
|
||||||
|
|
||||||
|
|
||||||
def create_rules(self, location_table):
|
def create_rules(self, location_table):
|
||||||
multiworld = self.multiworld
|
multiworld = self.multiworld
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w
|
|||||||
logic = TimespinnerLogic(world, player, precalculated_weights)
|
logic = TimespinnerLogic(world, player, precalculated_weights)
|
||||||
|
|
||||||
connect(world, player, 'Lake desolation', 'Lower lake desolation', lambda state: flooded.flood_lake_desolation or logic.has_timestop(state) or state.has('Talaria Attachment', player))
|
connect(world, player, 'Lake desolation', 'Lower lake desolation', lambda state: flooded.flood_lake_desolation or logic.has_timestop(state) or state.has('Talaria Attachment', player))
|
||||||
connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player), "Upper Lake Serene")
|
connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player))
|
||||||
connect(world, player, 'Lake desolation', 'Skeleton Shaft', lambda state: flooded.flood_lake_desolation or logic.has_doublejump(state))
|
connect(world, player, 'Lake desolation', 'Skeleton Shaft', lambda state: flooded.flood_lake_desolation or logic.has_doublejump(state))
|
||||||
connect(world, player, 'Lake desolation', 'Space time continuum', logic.has_teleport)
|
connect(world, player, 'Lake desolation', 'Space time continuum', logic.has_teleport)
|
||||||
connect(world, player, 'Upper lake desolation', 'Lake desolation')
|
connect(world, player, 'Upper lake desolation', 'Lake desolation')
|
||||||
@@ -80,7 +80,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w
|
|||||||
connect(world, player, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport)
|
connect(world, player, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport)
|
||||||
connect(world, player, 'Eastern lake desolation', 'Library')
|
connect(world, player, 'Eastern lake desolation', 'Library')
|
||||||
connect(world, player, 'Eastern lake desolation', 'Lower lake desolation')
|
connect(world, player, 'Eastern lake desolation', 'Lower lake desolation')
|
||||||
connect(world, player, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player), "Upper Lake Serene")
|
connect(world, player, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player))
|
||||||
connect(world, player, 'Library', 'Eastern lake desolation')
|
connect(world, player, 'Library', 'Eastern lake desolation')
|
||||||
connect(world, player, 'Library', 'Library top', lambda state: logic.has_doublejump(state) or state.has('Talaria Attachment', player))
|
connect(world, player, 'Library', 'Library top', lambda state: logic.has_doublejump(state) or state.has('Talaria Attachment', player))
|
||||||
connect(world, player, 'Library', 'Varndagroth tower left', logic.has_keycard_D)
|
connect(world, player, 'Library', 'Varndagroth tower left', logic.has_keycard_D)
|
||||||
@@ -185,7 +185,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w
|
|||||||
if is_option_enabled(world, player, "GyreArchives"):
|
if is_option_enabled(world, player, "GyreArchives"):
|
||||||
connect(world, player, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player))
|
connect(world, player, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player))
|
||||||
connect(world, player, 'Ravenlord\'s Lair', 'The lab (upper)')
|
connect(world, player, 'Ravenlord\'s Lair', 'The lab (upper)')
|
||||||
connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player), "Refugee Camp")
|
connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player))
|
||||||
connect(world, player, 'Ifrit\'s Lair', 'Library top')
|
connect(world, player, 'Ifrit\'s Lair', 'Library top')
|
||||||
|
|
||||||
|
|
||||||
@@ -242,19 +242,11 @@ def connectStartingRegion(world: MultiWorld, player: int):
|
|||||||
|
|
||||||
|
|
||||||
def connect(world: MultiWorld, player: int, source: str, target: str,
|
def connect(world: MultiWorld, player: int, source: str, target: str,
|
||||||
rule: Optional[Callable[[CollectionState], bool]] = None,
|
rule: Optional[Callable[[CollectionState], bool]] = None):
|
||||||
indirect: str = ""):
|
|
||||||
|
|
||||||
sourceRegion = world.get_region(source, player)
|
sourceRegion = world.get_region(source, player)
|
||||||
targetRegion = world.get_region(target, player)
|
targetRegion = world.get_region(target, player)
|
||||||
entrance = sourceRegion.connect(targetRegion, rule=rule)
|
sourceRegion.connect(targetRegion, rule=rule)
|
||||||
|
|
||||||
if indirect:
|
|
||||||
indirectRegion = world.get_region(indirect, player)
|
|
||||||
if indirectRegion in world.indirect_connections:
|
|
||||||
world.indirect_connections[indirectRegion].add(entrance)
|
|
||||||
else:
|
|
||||||
world.indirect_connections[indirectRegion] = {entrance}
|
|
||||||
|
|
||||||
|
|
||||||
def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]:
|
def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from NetUtils import ClientStatus, NetworkItem
|
|||||||
|
|
||||||
import worlds._bizhawk as bizhawk
|
import worlds._bizhawk as bizhawk
|
||||||
from worlds._bizhawk.client import BizHawkClient
|
from worlds._bizhawk.client import BizHawkClient
|
||||||
from . import item_to_index
|
from worlds.yugioh06 import item_to_index
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from worlds._bizhawk.context import BizHawkClientContext
|
from worlds._bizhawk.context import BizHawkClientContext
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from typing import Dict, List, NamedTuple, Optional, Union
|
|||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from worlds.generic.Rules import CollectionRule
|
from worlds.generic.Rules import CollectionRule
|
||||||
|
|
||||||
from . import item_to_index, tier_1_opponents, yugioh06_difficulty
|
from worlds.yugioh06 import item_to_index, tier_1_opponents, yugioh06_difficulty
|
||||||
from .locations import special
|
from worlds.yugioh06.locations import special
|
||||||
|
|
||||||
|
|
||||||
class OpponentData(NamedTuple):
|
class OpponentData(NamedTuple):
|
||||||
|
|||||||
Reference in New Issue
Block a user