mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-06-01 08:10:10 -07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 316444cc5a | |||
| 7971961166 | |||
| 9246bd9541 | |||
| 30fa0658b0 | |||
| 44a0c44036 |
+1
-1
@@ -46,11 +46,11 @@ dist
|
||||
/prof/
|
||||
README.html
|
||||
.vs/
|
||||
EnemizerCLI/
|
||||
/Players/
|
||||
/SNI/
|
||||
/sni-*/
|
||||
/appimagetool*
|
||||
/host.yaml
|
||||
/options.yaml
|
||||
/config.yaml
|
||||
/logs/
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"../test/programs/test_multi_server.py",
|
||||
"../test/utils/__init__.py",
|
||||
"../test/webhost/test_descriptions.py",
|
||||
"../test/webhost/test_suuid.py",
|
||||
"../worlds/AutoSNIClient.py",
|
||||
"type_check.py"
|
||||
],
|
||||
|
||||
+21
-124
@@ -11,146 +11,43 @@ on:
|
||||
- "!.github/workflows/**"
|
||||
- ".github/workflows/docker.yml"
|
||||
branches:
|
||||
- "main"
|
||||
- "dock-dev"
|
||||
tags:
|
||||
- "v?[0-9]+.[0-9]+.[0-9]*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image-name: ${{ steps.image.outputs.name }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
package-name: ${{ steps.package.outputs.name }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Set lowercase image name
|
||||
id: image
|
||||
run: |
|
||||
echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set package name
|
||||
id: package
|
||||
run: |
|
||||
echo "name=$(basename ${GITHUB_REPOSITORY,,})" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
|
||||
tags: |
|
||||
type=ref,event=branch,enable={{is_not_default_branch}}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=nightly,enable={{is_default_branch}}
|
||||
|
||||
- name: Compute final tags
|
||||
id: final-tags
|
||||
run: |
|
||||
readarray -t tags <<< "${{ steps.meta.outputs.tags }}"
|
||||
|
||||
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
tag="${{ github.ref_name }}"
|
||||
if [[ "$tag" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
full_latest="${{ env.REGISTRY }}/${{ steps.image.outputs.name }}:latest"
|
||||
# Check if latest is already in tags to avoid duplicates
|
||||
if ! printf '%s\n' "${tags[@]}" | grep -q "^$full_latest$"; then
|
||||
tags+=("$full_latest")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set multiline output
|
||||
echo "tags<<EOF" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "${tags[@]}" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: prepare
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: amd64
|
||||
runner: ubuntu-latest
|
||||
suffix: amd64
|
||||
cache-scope: amd64
|
||||
- platform: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
suffix: arm64
|
||||
cache-scope: arm64
|
||||
contents: read
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Compute suffixed tags
|
||||
id: tags
|
||||
run: |
|
||||
readarray -t tags <<< "${{ needs.prepare.outputs.tags }}"
|
||||
suffixed=()
|
||||
for t in "${tags[@]}"; do
|
||||
suffixed+=("$t-${{ matrix.suffix }}")
|
||||
done
|
||||
echo "tags=$(IFS=','; echo "${suffixed[*]}")" >> $GITHUB_OUTPUT
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ubufugu/dockipelago
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v7.0.0
|
||||
id: push
|
||||
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/${{ matrix.platform }}
|
||||
push: true
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
labels: ${{ needs.prepare.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ matrix.cache-scope }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.cache-scope }}
|
||||
provenance: false
|
||||
|
||||
manifest:
|
||||
needs: [prepare, build]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push multi-arch manifest
|
||||
run: |
|
||||
readarray -t tag_array <<< "${{ needs.prepare.outputs.tags }}"
|
||||
|
||||
for tag in "${tag_array[@]}"; do
|
||||
docker manifest create "$tag" \
|
||||
"$tag-amd64" \
|
||||
"$tag-arm64"
|
||||
|
||||
docker manifest push "$tag"
|
||||
done
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # tag x.y.z will become "Archipelago x.y.z"
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
with:
|
||||
draft: true # don't publish right away, especially since windows build is added by hand
|
||||
prerelease: false
|
||||
@@ -97,15 +97,13 @@ jobs:
|
||||
build/exe.*/ArchipelagoServer.exe
|
||||
setups/*
|
||||
- name: Add to Release
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
with:
|
||||
draft: true # see above
|
||||
prerelease: false
|
||||
name: Archipelago ${{ env.RELEASE_VERSION }}
|
||||
files: |
|
||||
setups/*
|
||||
fail_on_unmatched_files: true
|
||||
overwrite_files: false # Windows release is usually built by hand
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -167,14 +165,12 @@ jobs:
|
||||
build/exe.*/ArchipelagoServer
|
||||
dist/*
|
||||
- name: Add to Release
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
with:
|
||||
draft: true # see above
|
||||
prerelease: false
|
||||
name: Archipelago ${{ env.RELEASE_VERSION }}
|
||||
files: |
|
||||
dist/*
|
||||
fail_on_unmatched_files: true
|
||||
overwrite_files: false # should never happen; avoids accidentally changing a release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -45,8 +45,11 @@ EnemizerCLI/
|
||||
/SNI/
|
||||
/sni-*/
|
||||
/appimagetool*
|
||||
<<<<<<< Updated upstream
|
||||
/VC_redist.x64.exe
|
||||
/host.yaml
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
/options.yaml
|
||||
/config.yaml
|
||||
/logs/
|
||||
|
||||
+1
-5
@@ -1069,7 +1069,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
||||
if "players" in args:
|
||||
ctx.consume_players_package(args["players"])
|
||||
if "hint_points" in args:
|
||||
ctx.hint_points = args["hint_points"]
|
||||
ctx.hint_points = args['hint_points']
|
||||
if "checked_locations" in args:
|
||||
checked = set(args["checked_locations"])
|
||||
ctx.checked_locations |= checked
|
||||
@@ -1077,10 +1077,6 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
||||
if "permissions" in args:
|
||||
ctx.update_permissions(args["permissions"])
|
||||
|
||||
# Update hint info for local display
|
||||
if "hint_cost" in args:
|
||||
ctx.hint_cost = int(args["hint_cost"])
|
||||
|
||||
elif cmd == 'Print':
|
||||
ctx.on_print(args)
|
||||
|
||||
|
||||
+33
@@ -1,5 +1,23 @@
|
||||
# hadolint global ignore=SC1090,SC1091
|
||||
|
||||
# Source
|
||||
FROM scratch AS release
|
||||
WORKDIR /release
|
||||
ADD https://github.com/Ijwu/Enemizer/releases/latest/download/ubuntu.16.04-x64.zip Enemizer.zip
|
||||
|
||||
# Enemizer
|
||||
FROM alpine:3.21 AS enemizer
|
||||
ARG TARGETARCH
|
||||
WORKDIR /release
|
||||
COPY --from=release /release/Enemizer.zip .
|
||||
|
||||
# No release for arm architecture. Skip.
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
apk add unzip=6.0-r15 --no-cache && \
|
||||
unzip -u Enemizer.zip -d EnemizerCLI && \
|
||||
chmod -R 777 EnemizerCLI; \
|
||||
else touch EnemizerCLI; fi
|
||||
|
||||
# Cython builder stage
|
||||
FROM python:3.12 AS cython-builder
|
||||
|
||||
@@ -63,6 +81,15 @@ RUN apt-get purge -y \
|
||||
g++ && \
|
||||
apt-get autoremove -y
|
||||
|
||||
# Copy necessary components
|
||||
COPY --from=enemizer /release/EnemizerCLI /tmp/EnemizerCLI
|
||||
|
||||
# No release for arm architecture. Skip.
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
cp -r /tmp/EnemizerCLI EnemizerCLI; \
|
||||
fi; \
|
||||
rm -rf /tmp/EnemizerCLI
|
||||
|
||||
# Define health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:${PORT:-80} || exit 1
|
||||
@@ -70,4 +97,10 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
# Ensure no runtime ModuleUpdate.
|
||||
ENV SKIP_REQUIREMENTS_UPDATE=true
|
||||
|
||||
# Port range for Archipelago rooms. I choose only ports 49152-49162
|
||||
ARG MAX_PORT=49162
|
||||
|
||||
RUN sed -i "s/65535/${MAX_PORT}/" WebHostLib/customserver.py
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT [ "python", "WebHost.py" ]
|
||||
|
||||
+2
-13
@@ -40,8 +40,6 @@ def mystery_argparse(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
|
||||
parser.add_argument('--outputpath', default=settings.general_options.output_path,
|
||||
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
|
||||
parser.add_argument('--allow_quantity', action="store_true", default=defaults.allow_quantity,
|
||||
help='Allows the use of the quantity option in yamls. Default is the set value in the host.yaml.')
|
||||
parser.add_argument('--race', action='store_true', default=defaults.race)
|
||||
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
|
||||
parser.add_argument('--log_level', default=defaults.loglevel, help='Sets log level')
|
||||
@@ -125,7 +123,6 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
|
||||
player_id: int = 1
|
||||
player_files: dict[int, str] = {}
|
||||
player_errors: list[str] = []
|
||||
allow_quantity = args.allow_quantity
|
||||
for file in os.scandir(args.player_files_path):
|
||||
fname = file.name
|
||||
if file.is_file() and not fname.startswith(".") and not fname.lower().endswith(".ini") and \
|
||||
@@ -137,14 +134,7 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
|
||||
if yaml is None:
|
||||
logging.warning(f"Ignoring empty yaml document #{doc_idx + 1} in {fname}")
|
||||
else:
|
||||
quantity = yaml.get("quantity", 1)
|
||||
if quantity <= 0:
|
||||
raise ValueError("A quantity of 0 or less is invalid. Please change it to at least 1.")
|
||||
if not allow_quantity and quantity > 1:
|
||||
raise ValueError("Quantity greater than 1 is deactivated by host settings.")
|
||||
|
||||
for _ in range(quantity):
|
||||
weights_for_file.append(yaml)
|
||||
weights_for_file.append(yaml)
|
||||
weights_cache[fname] = tuple(weights_for_file)
|
||||
|
||||
except Exception as e:
|
||||
@@ -585,8 +575,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
||||
raise Exception(f"Invalid game: {ret.game}")
|
||||
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) + list(failed_world_loads.keys()),
|
||||
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:
|
||||
raise Exception(f"No functional world found to handle game {ret.game}. "
|
||||
f"Did you mean '{picks[0]}' ({picks[1]}% sure)? "
|
||||
|
||||
-35
@@ -36,7 +36,6 @@ if __name__ == "__main__":
|
||||
init_logging('Launcher')
|
||||
|
||||
from worlds.LauncherComponents import Component, components, icon_paths, SuffixIdentifier, Type
|
||||
from worlds import failed_world_loads
|
||||
|
||||
|
||||
def open_host_yaml():
|
||||
@@ -276,7 +275,6 @@ def run_gui(launch_components: list[Component], args: Any) -> None:
|
||||
search_box: MDTextField = ObjectProperty(None)
|
||||
cards: list[LauncherCard]
|
||||
current_filter: Sequence[str | Type] | None
|
||||
failed_worlds: bool = bool(failed_world_loads)
|
||||
|
||||
def __init__(self, ctx=None, components=None, args=None):
|
||||
self.title = self.base_title + " " + Utils.__version__
|
||||
@@ -424,39 +422,6 @@ def run_gui(launch_components: list[Component], args: Any) -> None:
|
||||
MDSnackbar(MDSnackbarText(text=open_text), y=dp(24), pos_hint={"center_x": 0.5},
|
||||
size_hint_x=0.5).open()
|
||||
|
||||
@staticmethod
|
||||
def copy_to_clipboard(text):
|
||||
from kivy.core.clipboard import Clipboard
|
||||
Clipboard.copy(text)
|
||||
MDSnackbar(MDSnackbarText(text="Copied to clipboard."), y=dp(24), pos_hint={"center_x": 0.5},
|
||||
size_hint_x=0.5).open()
|
||||
|
||||
def display_failed(self):
|
||||
"""Display a dialog showing the exceptions produced by any world that failed to load during
|
||||
initialization."""
|
||||
if not self.failed_worlds:
|
||||
return
|
||||
from kivymd.uix.dialog import MDDialog, MDDialogIcon, MDDialogHeadlineText, MDDialogContentContainer
|
||||
from kivymd.uix.divider import MDDivider
|
||||
from kivymd.uix.list import MDListItem, MDListItemHeadlineText, MDListItemSupportingText
|
||||
entries = []
|
||||
for world, reason in failed_world_loads.items():
|
||||
entries.append(MDListItem(
|
||||
MDListItemHeadlineText(text=world),
|
||||
MDListItemSupportingText(text=reason),
|
||||
on_release=lambda x, r=reason: self.copy_to_clipboard(r)
|
||||
))
|
||||
dialog = MDDialog(
|
||||
MDDialogIcon(icon="alert"),
|
||||
MDDialogHeadlineText(text="Failed World Loads"),
|
||||
MDDialogContentContainer(
|
||||
MDDivider(),
|
||||
*entries,
|
||||
orientation="vertical",
|
||||
)
|
||||
)
|
||||
dialog.open()
|
||||
|
||||
def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None:
|
||||
""" When a patch file is dropped into the window, run the associated component. """
|
||||
file, component = identify(filename.decode())
|
||||
|
||||
+2
-2
@@ -241,8 +241,8 @@ async def gba_sync_task(ctx: MMBN3Context):
|
||||
await ctx.server_auth(False)
|
||||
else:
|
||||
if not ctx.version_warning:
|
||||
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}. "
|
||||
"Please update to the latest version. "
|
||||
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}."
|
||||
"Please update to the latest version."
|
||||
"Your connection to the Archipelago server will not be accepted.")
|
||||
ctx.version_warning = True
|
||||
except asyncio.TimeoutError:
|
||||
|
||||
+2
-2
@@ -2633,8 +2633,8 @@ def parse_args() -> argparse.Namespace:
|
||||
goal: !remaining can be used after goal completion
|
||||
''')
|
||||
parser.add_argument('--auto_shutdown', default=defaults["auto_shutdown"], type=int,
|
||||
help="automatically shut down the server after this many seconds without new location checks. "
|
||||
"0 to keep running.")
|
||||
help="automatically shut down the server after this many minutes without new location checks. "
|
||||
"0 to keep running. Not yet implemented.")
|
||||
parser.add_argument('--use_embedded_options', action="store_true",
|
||||
help='retrieve release, remaining and hint options from the multidata file,'
|
||||
' instead of host.yaml')
|
||||
|
||||
@@ -527,11 +527,7 @@ else:
|
||||
except ImportError:
|
||||
pyximport = None
|
||||
try:
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
old_level = logger.level
|
||||
from _speedups import LocationStore
|
||||
logger.setLevel(old_level)
|
||||
except ImportError:
|
||||
warnings.warn("_speedups not available. Falling back to pure python LocationStore. "
|
||||
"Install a matching C++ compiler for your platform to compile _speedups.")
|
||||
|
||||
+30
-56
@@ -212,13 +212,6 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
|
||||
else:
|
||||
return cls.name_lookup[value]
|
||||
|
||||
def __eq__(self, other: typing.Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.value == other.value
|
||||
if isinstance(other, Option):
|
||||
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||
return self.value == other
|
||||
|
||||
def __int__(self) -> T:
|
||||
return self.value
|
||||
|
||||
@@ -937,34 +930,13 @@ class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mappin
|
||||
class OptionCounter(OptionDict):
|
||||
min: int | None = None
|
||||
max: int | None = None
|
||||
cull_zeroes: bool = False
|
||||
|
||||
def __init__(self, value: dict[str, int]) -> None:
|
||||
cleaned_dict = {}
|
||||
|
||||
invalid_value_errors = []
|
||||
for key, value in value.items():
|
||||
if not isinstance(value, (int, float)) or int(value) != value:
|
||||
invalid_value_errors += [f"Invalid value {value} for key {key}, must be an integer."]
|
||||
continue
|
||||
|
||||
if self.cull_zeroes and value == 0:
|
||||
continue
|
||||
|
||||
cleaned_dict[key] = int(value)
|
||||
|
||||
if invalid_value_errors:
|
||||
type_errors = [f"For option {self.__class__.__name__}:"] + invalid_value_errors
|
||||
raise TypeError("\n".join(invalid_value_errors))
|
||||
|
||||
super(OptionCounter, self).__init__(collections.Counter(cleaned_dict))
|
||||
super(OptionCounter, self).__init__(collections.Counter(value))
|
||||
|
||||
def verify(self, world: type[World], player_name: str, plando_options: PlandoOptions) -> None:
|
||||
super(OptionCounter, self).verify(world, player_name, plando_options)
|
||||
|
||||
self.verify_values()
|
||||
|
||||
def verify_values(self):
|
||||
range_errors = []
|
||||
|
||||
if self.max is not None:
|
||||
@@ -987,8 +959,13 @@ class OptionCounter(OptionDict):
|
||||
class ItemDict(OptionCounter):
|
||||
verify_item_name = True
|
||||
|
||||
# Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter
|
||||
cull_zeroes = True
|
||||
min = 0
|
||||
|
||||
def __init__(self, value: dict[str, int]) -> None:
|
||||
# Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter
|
||||
value = {item_name: amount for item_name, amount in value.items() if amount != 0}
|
||||
|
||||
super(ItemDict, self).__init__(value)
|
||||
|
||||
|
||||
class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
|
||||
@@ -1469,7 +1446,7 @@ class NonLocalItems(ItemSet):
|
||||
|
||||
|
||||
class StartInventory(ItemDict):
|
||||
"""Start with the specified amount of these items. Example: {Bomb: 1, Arrow: 3} """
|
||||
"""Start with the specified amount of these items. Example: "Bomb: 1" """
|
||||
verify_item_name = True
|
||||
display_name = "Start Inventory"
|
||||
rich_text_doc = True
|
||||
@@ -1477,7 +1454,7 @@ class StartInventory(ItemDict):
|
||||
|
||||
|
||||
class StartInventoryPool(StartInventory):
|
||||
"""Start with the specified amount of these items and don't place them in the world. Example: {Bomb: 1, Arrow: 3}
|
||||
"""Start with the specified amount of these items and don't place them in the world. Example: "Bomb: 1"
|
||||
|
||||
The game decides what the replacement items will be.
|
||||
"""
|
||||
@@ -1856,30 +1833,27 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
if not world.hidden or generate_hidden:
|
||||
try:
|
||||
presets = world.web.options_presets.copy()
|
||||
presets.update({"": {}})
|
||||
presets = world.web.options_presets.copy()
|
||||
presets.update({"": {}})
|
||||
|
||||
option_groups = get_option_groups(world)
|
||||
for name, preset in presets.items():
|
||||
res = template.render(
|
||||
option_groups=option_groups,
|
||||
__version__=__version__,
|
||||
game=game_name,
|
||||
world_version=world.world_version.as_simple_string(),
|
||||
yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
cleandoc=cleandoc,
|
||||
preset_name=name,
|
||||
preset=preset,
|
||||
)
|
||||
preset_name = f" - {name}" if name else ""
|
||||
with open(os.path.join(preset_folder if name else target_folder,
|
||||
get_file_safe_name(game_name + preset_name) + ".yaml"),
|
||||
"w", encoding="utf-8-sig") as f:
|
||||
f.write(res)
|
||||
except Exception as ex:
|
||||
raise Exception(f"Template generation failed for world {game_name}") from ex
|
||||
option_groups = get_option_groups(world)
|
||||
for name, preset in presets.items():
|
||||
res = template.render(
|
||||
option_groups=option_groups,
|
||||
__version__=__version__,
|
||||
game=game_name,
|
||||
world_version=world.world_version.as_simple_string(),
|
||||
yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
cleandoc=cleandoc,
|
||||
preset_name=name,
|
||||
preset=preset,
|
||||
)
|
||||
preset_name = f" - {name}" if name else ""
|
||||
with open(os.path.join(preset_folder if name else target_folder,
|
||||
get_file_safe_name(game_name + preset_name) + ".yaml"),
|
||||
"w", encoding="utf-8-sig") as f:
|
||||
f.write(res)
|
||||
|
||||
|
||||
def dump_player_options(multiworld: MultiWorld) -> None:
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ class UndertaleCommandProcessor(ClientCommandProcessor):
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
os.makedirs(name=Utils.user_path("Undertale"), exist_ok=True)
|
||||
tempInstall = steaminstall
|
||||
if tempInstall and not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||
tempInstall = None
|
||||
if tempInstall is None:
|
||||
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
||||
|
||||
@@ -52,7 +52,7 @@ class Version(typing.NamedTuple):
|
||||
return ".".join(str(item) for item in self)
|
||||
|
||||
|
||||
__version__ = "0.6.8"
|
||||
__version__ = "0.6.7"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
@@ -450,10 +450,13 @@ safe_builtins = frozenset((
|
||||
|
||||
|
||||
class RestrictedUnpickler(pickle.Unpickler):
|
||||
generic_properties_module: Optional[object]
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super(RestrictedUnpickler, self).__init__(*args, **kwargs)
|
||||
self.options_module = importlib.import_module("Options")
|
||||
self.net_utils_module = importlib.import_module("NetUtils")
|
||||
self.generic_properties_module = None
|
||||
|
||||
def find_class(self, module: str, name: str) -> type:
|
||||
if module == "builtins" and name in safe_builtins:
|
||||
@@ -467,6 +470,10 @@ class RestrictedUnpickler(pickle.Unpickler):
|
||||
"SlotType", "NetworkSlot", "HintStatus"}:
|
||||
return getattr(self.net_utils_module, name)
|
||||
# Options and Plando are unpickled by WebHost -> Generate
|
||||
if module == "worlds.generic" and name == "PlandoItem":
|
||||
if not self.generic_properties_module:
|
||||
self.generic_properties_module = importlib.import_module("worlds.generic")
|
||||
return getattr(self.generic_properties_module, name)
|
||||
# pep 8 specifies that modules should have "all-lowercase names" (options, not Options)
|
||||
if module.lower().endswith("options"):
|
||||
if module == "Options":
|
||||
|
||||
@@ -48,8 +48,6 @@ app.config["JOB_THRESHOLD"] = 1
|
||||
app.config["JOB_TIME"] = 600
|
||||
# maximum time in seconds since last activity for a room to be hosted
|
||||
app.config["MAX_ROOM_TIMEOUT"] = 259200
|
||||
# minimum time in days since last activity for a room to be deleted. 0 to disable.
|
||||
app.config["ROOM_AUTO_DELETE"] = 0
|
||||
# memory limit for generator processes in bytes
|
||||
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
|
||||
|
||||
@@ -73,9 +71,7 @@ CLI(app)
|
||||
|
||||
|
||||
def to_python(value: str) -> uuid.UUID:
|
||||
if "=" in value or any(c.isspace() for c in value):
|
||||
raise ValueError("Invalid UUID format")
|
||||
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=' * (-len(value) % 4)))
|
||||
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=='))
|
||||
|
||||
|
||||
def to_url(value: uuid.UUID) -> str:
|
||||
|
||||
@@ -100,18 +100,13 @@ def init_generator(config: dict[str, Any]) -> None:
|
||||
db.generate_mapping()
|
||||
|
||||
|
||||
def cleanup(config: dict[str, Any]):
|
||||
"""delete unowned or old user-content"""
|
||||
auto_delete: int = config.get("ROOM_AUTO_DELETE", 0)
|
||||
def cleanup():
|
||||
"""delete unowned user-content"""
|
||||
with db_session:
|
||||
# >>> bool(uuid.UUID(int=0))
|
||||
# True
|
||||
rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True)
|
||||
seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True)
|
||||
if auto_delete > 0:
|
||||
cutoff = utcnow() - timedelta(days=auto_delete)
|
||||
rooms += Room.select(lambda room: room.last_activity < cutoff).delete(bulk=True)
|
||||
seeds += Seed.select(lambda seed: not seed.rooms and seed.creation_time < cutoff).delete(bulk=True)
|
||||
slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True)
|
||||
# Command gets deleted by ponyorm Cascade Delete, as Room is Required
|
||||
if rooms or seeds or slots:
|
||||
@@ -123,7 +118,7 @@ def autohost(config: dict):
|
||||
stop_event = _stop_event
|
||||
try:
|
||||
with Locker("autohost"):
|
||||
cleanup(config)
|
||||
cleanup()
|
||||
hosters = []
|
||||
for x in range(config["HOSTERS"]):
|
||||
hoster = MultiworldInstance(config, x)
|
||||
|
||||
@@ -10,5 +10,5 @@ Flask-Cors==6.0.2
|
||||
bokeh==3.8.2
|
||||
markupsafe==3.0.3
|
||||
setproctitle==1.3.7
|
||||
mistune==3.2.1
|
||||
mistune==3.2.0
|
||||
docutils==0.22.4
|
||||
|
||||
@@ -123,26 +123,12 @@ window.addEventListener('load', () => {
|
||||
});
|
||||
|
||||
const addRangeRow = (optionName) => {
|
||||
const inputQuery = `input[data-option="${optionName}"]`;
|
||||
const inputQuery = `input[type=number][data-option="${optionName}"].range-option-value`;
|
||||
const inputTarget = document.querySelector(inputQuery);
|
||||
const newValue = inputTarget.value;
|
||||
switch (inputTarget.type) {
|
||||
case 'number':
|
||||
if (!/^-?\d+$/.test(newValue)) {
|
||||
alert('Range values must be a positive or negative integer!');
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'text':
|
||||
if (newValue === "") {
|
||||
alert('Range values for text must be a non-empty string!');
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error(`Found unsupported input type: ${inputTarget.type}`);
|
||||
return;
|
||||
break;
|
||||
if (!/^-?\d+$/.test(newValue)) {
|
||||
alert('Range values must be a positive or negative integer!');
|
||||
return;
|
||||
}
|
||||
inputTarget.value = '';
|
||||
const tBody = document.querySelector(`table[data-option="${optionName}"].range-rows tbody`);
|
||||
|
||||
@@ -68,9 +68,6 @@
|
||||
<a href="{{ world.web.bug_report_page }}">Report a Bug</a>
|
||||
{% endif %}
|
||||
</details>
|
||||
{% if "authors" in world.manifest %}
|
||||
<p>Authors: {{ world.manifest["authors"] | join(", ") }}</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -71,10 +71,10 @@
|
||||
<div class="hint-text">
|
||||
This option allows custom values only. Please enter your desired values below.
|
||||
<div class="custom-value-wrapper">
|
||||
<input type="text" class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" data-option="{{ option_name }}">Add</button>
|
||||
</div>
|
||||
<table class="range-rows" data-option="{{ option_name }}">
|
||||
<table>
|
||||
<tbody>
|
||||
{% if option.default %}
|
||||
{{ RangeRow(option_name, option, option.default, option.default) }}
|
||||
@@ -88,11 +88,11 @@
|
||||
<div class="hint-text">
|
||||
Custom values are also allowed for this option. To create one, enter it into the input box below.
|
||||
<div class="custom-value-wrapper">
|
||||
<input type="text" class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" data-option="{{ option_name }}">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="range-rows" data-option="{{ option_name }}">
|
||||
<table>
|
||||
<tbody>
|
||||
{% for id, name in option.name_lookup.items() %}
|
||||
{% if name != 'random' %}
|
||||
|
||||
@@ -140,15 +140,6 @@ MDFloatLayout:
|
||||
|
||||
MDNavigationDrawerDivider:
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
MDIconButton:
|
||||
icon: "alert" if app.failed_worlds else ""
|
||||
theme_text_color: "Custom"
|
||||
text_color: "D23C42"
|
||||
disabled: not app.failed_worlds
|
||||
on_release: app.display_failed()
|
||||
|
||||
|
||||
MDGridLayout:
|
||||
id: main_layout
|
||||
|
||||
@@ -92,9 +92,8 @@ for setup).
|
||||
|
||||
The base World class can be found in [AutoWorld](/worlds/AutoWorld.py). Methods available for your world to call
|
||||
during generation can be found in [BaseClasses](/BaseClasses.py) and [Fill](/Fill.py). Some examples and documentation
|
||||
regarding the API can be found in the [world api doc](/docs/world%20api.md), and the [APQuest](/worlds/apquest/) world
|
||||
is a complete world implementation that functions as an introduction to world development. Before publishing, make sure
|
||||
to also check out [world maintainer.md](/docs/world%20maintainer.md).
|
||||
regarding the API can be found in the [world api doc](/docs/world%20api.md). Before publishing, make sure to also
|
||||
check out [world maintainer.md](/docs/world%20maintainer.md).
|
||||
|
||||
### Hard Requirements
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ There are also the following optional fields:
|
||||
* `world_version` - an arbitrary version for that world in order to only load the newest valid world.
|
||||
An APWorld without a world_version is always treated as older than one with a version
|
||||
(**Must** use exactly the format `"major.minor.build"`, e.g. `1.0.0`)
|
||||
* `authors` - a list of authors of the world. Displayed in user-facing places like the Supported Games page
|
||||
on WebHost. Should always be a list of strings.
|
||||
* `authors` - a list of authors, to eventually be displayed in various user-facing places such as WebHost and
|
||||
package managers. Should always be a list of strings.
|
||||
|
||||
If the APWorld is packaged as an `.apworld` zip file, it also needs to have `version` and `compatible_version`,
|
||||
which refer to the version of the APContainer packaging scheme defined in [Files.py](../worlds/Files.py).
|
||||
|
||||
@@ -77,6 +77,15 @@ Changes made to `docker-compose.yaml` can be applied by running `docker compose
|
||||
It is possible to carry out these deployment steps on Windows under [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
|
||||
|
||||
## Optional: A Link to the Past Enemizer
|
||||
|
||||
Only required to generate seeds that include A Link to the Past with certain options enabled. You will receive an
|
||||
error if it is required.
|
||||
Enemizer can be enabled on `x86_64` platform architecture, and is included in the image build process. Enemizer requires a version 1.0 Japanese "Zelda no Densetsu" `.sfc` rom file to be placed in the application directory:
|
||||
`docker run archipelago -v "/path/to/zelda.sfc:/app/Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"`.
|
||||
Enemizer is not currently available for `aarch64`.
|
||||
|
||||
|
||||
## Optional: Git
|
||||
|
||||
Building the image requires a local copy of the ArchipelagoMW source code.
|
||||
|
||||
+61
-162
@@ -1,7 +1,6 @@
|
||||
# Rule Builder
|
||||
|
||||
This document describes the API provided for the rule builder. Using this API provides you with with a simple interface
|
||||
to define rules and the following advantages:
|
||||
This document describes the API provided for the rule builder. Using this API provides you with with a simple interface to define rules and the following advantages:
|
||||
|
||||
- Rule classes that avoid all the common pitfalls
|
||||
- Logic optimization
|
||||
@@ -13,21 +12,13 @@ to define rules and the following advantages:
|
||||
|
||||
The rule builder consists of 3 main parts:
|
||||
|
||||
1. The rules, which are classes that inherit from `rule_builder.rules.Rule`. These are what you write for your logic.
|
||||
They can be combined and take into account your world's options. There are a number of default rules listed below,
|
||||
and you can create as many custom rules for your world as needed. When assigning the rules to a location or entrance
|
||||
they must be resolved.
|
||||
2. Resolved rules, which are classes that inherit from `rule_builder.rules.Rule.Resolved`. These are the optimized rules
|
||||
specific to one player that are set as a location or entrance's access rule. You generally shouldn't be directly
|
||||
creating these but they'll be created when assigning rules to locations or entrances. These are what power the
|
||||
human-readable logic explanations.
|
||||
3. The optional rule builder world subclass `CachedRuleBuilderWorld`, which is a class your world can inherit from
|
||||
instead of `World`. It adds a caching system to the rules that will lazy evaluate and cache the result.
|
||||
1. The rules, which are classes that inherit from `rule_builder.rules.Rule`. These are what you write for your logic. They can be combined and take into account your world's options. There are a number of default rules listed below, and you can create as many custom rules for your world as needed. When assigning the rules to a location or entrance they must be resolved.
|
||||
1. Resolved rules, which are classes that inherit from `rule_builder.rules.Rule.Resolved`. These are the optimized rules specific to one player that are set as a location or entrance's access rule. You generally shouldn't be directly creating these but they'll be created when assigning rules to locations or entrances. These are what power the human-readable logic explanations.
|
||||
1. The optional rule builder world subclass `CachedRuleBuilderWorld`, which is a class your world can inherit from instead of `World`. It adds a caching system to the rules that will lazy evaluate and cache the result.
|
||||
|
||||
## Usage
|
||||
|
||||
For the most part the only difference in usage is instead of writing lambdas for your logic, you write static Rule
|
||||
objects. You then must use `world.set_rule` to assign the rule to a location or entrance.
|
||||
For the most part the only difference in usage is instead of writing lambdas for your logic, you write static Rule objects. You then must use `world.set_rule` to assign the rule to a location or entrance.
|
||||
|
||||
```python
|
||||
# In your world's create_regions method
|
||||
@@ -41,7 +32,6 @@ The rule builder comes with a number of rules by default:
|
||||
- `False_`: Always returns false
|
||||
- `And`: Checks that all child rules are true (also provided by `&` operator)
|
||||
- `Or`: Checks that at least one child rule is true (also provided by `|` operator)
|
||||
- `AtLeast`: Checks that at least some count of rules is true
|
||||
- `Has`: Checks that the player has the given item with the given count (default 1)
|
||||
- `HasAll`: Checks that the player has all given items
|
||||
- `HasAny`: Checks that the player has at least one of the given items
|
||||
@@ -50,22 +40,18 @@ The rule builder comes with a number of rules by default:
|
||||
- `HasFromList`: Checks that the player has some number of given items
|
||||
- `HasFromListUnique`: Checks that the player has some number of given items, ignoring duplicates of the same item
|
||||
- `HasGroup`: Checks that the player has some number of items from a given item group
|
||||
- `HasGroupUnique`: Checks that the player has some number of items from a given item group, ignoring duplicates of the
|
||||
same item
|
||||
- `HasGroupUnique`: Checks that the player has some number of items from a given item group, ignoring duplicates of the same item
|
||||
- `CanReachLocation`: Checks that the player can logically reach the given location
|
||||
- `CanReachRegion`: Checks that the player can logically reach the given region
|
||||
- `CanReachEntrance`: Checks that the player can logically reach the given entrance
|
||||
|
||||
You can combine these rules together to describe the logic required for something. For example, to check if a player
|
||||
either has `Movement ability` or they have both `Key 1` and `Key 2`, you can do:
|
||||
You can combine these rules together to describe the logic required for something. For example, to check if a player either has `Movement ability` or they have both `Key 1` and `Key 2`, you can do:
|
||||
|
||||
```python
|
||||
rule = Has("Movement ability") | HasAll("Key 1", "Key 2")
|
||||
```
|
||||
|
||||
> ⚠️ Composing rules with the `and` and `or` keywords will not work. You must use the bitwise `&` and `|` operators. In
|
||||
> order to catch mistakes, the rule builder will not let you do boolean operations. As a consequence, in order to check
|
||||
> if a rule is defined you must use `if rule is not None`.
|
||||
> ⚠️ Composing rules with the `and` and `or` keywords will not work. You must use the bitwise `&` and `|` operators. In order to catch mistakes, the rule builder will not let you do boolean operations. As a consequence, in order to check if a rule is defined you must use `if rule is not None`.
|
||||
|
||||
### Assigning rules
|
||||
|
||||
@@ -75,16 +61,13 @@ When assigning the rule you must use the `set_rule` helper to correctly resolve
|
||||
self.set_rule(location_or_entrance, rule)
|
||||
```
|
||||
|
||||
There is also a `create_entrance` helper that will resolve the rule, check if it's `False`, and if not create the
|
||||
entrance and set the rule. This allows you to skip creating entrances that will never be valid. You can also specify
|
||||
`force_creation=True` if you would like to create the entrance even if the rule is `False`.
|
||||
There is also a `create_entrance` helper that will resolve the rule, check if it's `False`, and if not create the entrance and set the rule. This allows you to skip creating entrances that will never be valid. You can also specify `force_creation=True` if you would like to create the entrance even if the rule is `False`.
|
||||
|
||||
```python
|
||||
self.create_entrance(from_region, to_region, rule)
|
||||
```
|
||||
|
||||
> ⚠️ If you use a `CanReachLocation` rule on an entrance, you will either have to create the locations first, or specify
|
||||
> the location's parent region name with the `parent_region_name` argument of `CanReachLocation`.
|
||||
> ⚠️ If you use a `CanReachLocation` rule on an entrance, you will either have to create the locations first, or specify the location's parent region name with the `parent_region_name` argument of `CanReachLocation`.
|
||||
|
||||
You can also set a rule for your world's completion condition:
|
||||
|
||||
@@ -94,42 +77,21 @@ self.set_completion_rule(rule)
|
||||
|
||||
### Restricting options
|
||||
|
||||
Every rule allows you to specify which options it's applicable for. You can provide the argument `options` which is an
|
||||
iterable of `OptionFilter` instances. When resolved, if no filters are provided or all of them pass then the rule will
|
||||
resolve as normal. Otherwise, the rule will be replaced with `True` or `False` depending on what `filtered_resolution`
|
||||
is set to, which defaults to `False`.
|
||||
Every rule allows you to specify which options it's applicable for. You can provide the argument `options` which is an iterable of `OptionFilter` instances. Rules that pass the options check will be resolved as normal, and those that fail will be resolved as `False`.
|
||||
|
||||
```python
|
||||
rule1 = Has(
|
||||
"Fast Travel Spell",
|
||||
options=[OptionFilter(RandoFastTravel, RandoFastTravel.option_true)],
|
||||
)
|
||||
rule2 = Has(
|
||||
"Starting Party Member",
|
||||
options=[OptionFilter(RandoParty, 1)], # option attributes are suggested but any value works
|
||||
filtered_resolution=True,
|
||||
)
|
||||
```
|
||||
If you want a comparison that isn't equals, you can specify with the `operator` argument. The following operators are allowed:
|
||||
|
||||
If you want a comparison that isn't equals, you can specify with the `operator` argument. The following operators are
|
||||
allowed:
|
||||
- `eq`: `==`
|
||||
- `ne`: `!=`
|
||||
- `gt`: `>`
|
||||
- `lt`: `<`
|
||||
- `ge`: `>=`
|
||||
- `le`: `<=`
|
||||
- `contains`: `in`
|
||||
|
||||
- `eq`: `option_value == filter_value`
|
||||
- `ne`: `option_value != filter_value`
|
||||
- `gt`: `option_value > filter_value`
|
||||
- `lt`: `option_value < filter_value`
|
||||
- `ge`: `option_value >= filter_value`
|
||||
- `le`: `option_value <= filter_value`
|
||||
- `in`: `option_value in filter_value`
|
||||
- `contains`: `filter_value in option_value` (note reversed operands)
|
||||
By default rules that are excluded by their options will default to `False`. If you want to default to `True` instead, you can specify `filtered_resolution=True` on your rule.
|
||||
|
||||
```python
|
||||
rule1 = Has("Movement Ability", options=[OptionFilter(SkipsLevel, SkipsLevel.option_hard, operator="lt")])
|
||||
rule2 = Has("Item", options=[OptionFilter(ChoiceOption, [1, 5], operator="in")])
|
||||
```
|
||||
|
||||
To check if the player has received the switch item if switches are randomized, or if they can reach the switch when not
|
||||
randomized:
|
||||
To check if the player can reach a switch, or if they've received the switch item if switches are randomized:
|
||||
|
||||
```python
|
||||
rule = (
|
||||
@@ -153,12 +115,12 @@ If you would like to provide option filters when reusing or composing rules, you
|
||||
common_rule = Has("A") | HasAny("B", "C")
|
||||
...
|
||||
rule = (
|
||||
Filtered(common_rule, options=[OptionFilter(Opt, 0)])
|
||||
| Filtered(Has("X") | CanReachRegion("Y"), options=[OptionFilter(Opt, 1)])
|
||||
Filtered(common_rule, options=[OptionFilter(Opt, 0)]),
|
||||
| Filtered(Has("X") | CanReachRegion("Y"), options=[OptionFilter(Opt, 1)]),
|
||||
)
|
||||
```
|
||||
|
||||
For convenience, you can also use the `&` and `|` operators to apply options to rules:
|
||||
You can also use the & and | operators to apply options to rules:
|
||||
|
||||
```python
|
||||
common_rule = Has("A")
|
||||
@@ -167,22 +129,14 @@ common_rule_only_on_easy = common_rule & easy_filter
|
||||
common_rule_skipped_on_easy = common_rule | easy_filter
|
||||
```
|
||||
|
||||
Combining the above, you can easily bypass a requirement based on option choices:
|
||||
|
||||
```python
|
||||
rule = Has("Some Upgrade") | OptionFilter(CombatDifficulty, CombatDifficulty.option_medium, operator="ge")
|
||||
```
|
||||
|
||||
### Field resolvers
|
||||
|
||||
When creating rules you may sometimes need to set a field to a value that depends on the world instance. You can use a
|
||||
`FieldResolver` to define how to populate that field when the rule is being resolved.
|
||||
When creating rules you may sometimes need to set a field to a value that depends on the world instance. You can use a `FieldResolver` to define how to populate that field when the rule is being resolved.
|
||||
|
||||
There are two build-in field resolvers:
|
||||
|
||||
- `FromOption`: Resolves to the value of the given option
|
||||
- `FromWorldAttr`: Resolves to the value of the given world instance attribute, can specify a dotted path `a.b.c` to get
|
||||
a nested attribute or dict item
|
||||
- `FromWorldAttr`: Resolves to the value of the given world instance attribute, can specify a dotted path `a.b.c` to get a nested attribute or dict item
|
||||
|
||||
```python
|
||||
world.options.mcguffin_count = 5
|
||||
@@ -194,8 +148,7 @@ rule = (
|
||||
# Results in Has("A", count=5) | HasGroup("Important items", count=99)
|
||||
```
|
||||
|
||||
You can define your own resolvers by creating a class that inherits from `FieldResolver`, provides your game name, and
|
||||
implements a `resolve` function:
|
||||
You can define your own resolvers by creating a class that inherits from `FieldResolver`, provides your game name, and implements a `resolve` function:
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -210,30 +163,24 @@ class FromCustomResolution(FieldResolver, game="MyGame"):
|
||||
rule = Has("Combat Level", count=FromCustomResolution("combat"))
|
||||
```
|
||||
|
||||
If you want to support rule serialization and your resolver contains non-serializable properties you may need to
|
||||
override `to_dict` or `from_dict`.
|
||||
If you want to support rule serialization and your resolver contains non-serializable properties you may need to override `to_dict` or `from_dict`.
|
||||
|
||||
## Enabling caching
|
||||
|
||||
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your
|
||||
rules.
|
||||
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your rules.
|
||||
|
||||
```python
|
||||
class MyWorld(CachedRuleBuilderWorld):
|
||||
game = "My Game"
|
||||
```
|
||||
|
||||
If your world's logic is very simple and you don't have many nested rules, the caching system may have more overhead
|
||||
cost than time it saves. You'll have to benchmark your own world to see if it should be enabled or not.
|
||||
If your world's logic is very simple and you don't have many nested rules, the caching system may have more overhead cost than time it saves. You'll have to benchmark your own world to see if it should be enabled or not.
|
||||
|
||||
### Item name mapping
|
||||
|
||||
If you have multiple real items that map to a single logic item, add a `item_mapping` class dict to your world that maps
|
||||
actual item names to real item names so the cache system knows what to invalidate.
|
||||
If you have multiple real items that map to a single logic item, add a `item_mapping` class dict to your world that maps actual item names to real item names so the cache system knows what to invalidate.
|
||||
|
||||
For example, if you have multiple `Currency x<num>` items on locations, but your rules only check a singular logical
|
||||
`Currency` item, eg `Has("Currency", 1000)`, you'll want to map each numerical currency item to the single logical
|
||||
`Currency`.
|
||||
For example, if you have multiple `Currency x<num>` items on locations, but your rules only check a singular logical `Currency` item, eg `Has("Currency", 1000)`, you'll want to map each numerical currency item to the single logical `Currency`.
|
||||
|
||||
```python
|
||||
class MyWorld(CachedRuleBuilderWorld):
|
||||
@@ -247,13 +194,9 @@ class MyWorld(CachedRuleBuilderWorld):
|
||||
|
||||
## Defining custom rules
|
||||
|
||||
You can create a custom rule by creating a class that inherits from `Rule` or any of the default rules. You must provide
|
||||
the game name as an argument to the class. It's recommended to use the `@dataclass` decorator to reduce boilerplate, and
|
||||
to also provide your world as a type argument to add correct type checking to the `_instantiate` method.
|
||||
You can create a custom rule by creating a class that inherits from `Rule` or any of the default rules. You must provide the game name as an argument to the class. It's recommended to use the `@dataclass` decorator to reduce boilerplate, and to also provide your world as a type argument to add correct type checking to the `_instantiate` method.
|
||||
|
||||
You must provide or inherit a `Resolved` child class that defines an `_evaluate` method. This class will automatically
|
||||
be converted into a frozen `dataclass`. If your world has caching enabled you may need to define one or more
|
||||
dependencies functions as outlined below.
|
||||
You must provide or inherit a `Resolved` child class that defines an `_evaluate` method. This class will automatically be converted into a frozen `dataclass`. If your world has caching enabled you may need to define one or more dependencies functions as outlined below.
|
||||
|
||||
To add a rule that checks if the user has enough mcguffins to goal, with a randomized requirement:
|
||||
|
||||
@@ -302,10 +245,7 @@ class ComplicatedFilter(Rule["MyWorld"], game="My Game"):
|
||||
|
||||
### Item dependencies
|
||||
|
||||
If your world inherits from `CachedRuleBuilderWorld` and there are items that when collected will affect the result of
|
||||
your rule evaluation, it must define an `item_dependencies` function that returns a mapping of the item name to the id
|
||||
of your rule. These dependencies will be combined to inform the caching system. It may be worthwhile to define this
|
||||
function even when caching is disabled as more things may use it in the future.
|
||||
If your world inherits from `CachedRuleBuilderWorld` and there are items that when collected will affect the result of your rule evaluation, it must define an `item_dependencies` function that returns a mapping of the item name to the id of your rule. These dependencies will be combined to inform the caching system. It may be worthwhile to define this function even when caching is disabled as more things may use it in the future.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -322,10 +262,7 @@ All of the default `Has*` rules define this function already.
|
||||
|
||||
### Region dependencies
|
||||
|
||||
If your custom rule references other regions, it must define a `region_dependencies` function that returns a mapping of
|
||||
region names to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
||||
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
||||
caching system if applicable.
|
||||
If your custom rule references other regions, it must define a `region_dependencies` function that returns a mapping of region names to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the caching system if applicable.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -342,10 +279,7 @@ The default `CanReachLocation`, `CanReachRegion`, and `CanReachEntrance` rules d
|
||||
|
||||
### Location dependencies
|
||||
|
||||
If your custom rule references other locations, it must define a `location_dependencies` function that returns a mapping
|
||||
of the location name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
||||
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
||||
caching system if applicable.
|
||||
If your custom rule references other locations, it must define a `location_dependencies` function that returns a mapping of the location name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the caching system if applicable.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -362,10 +296,7 @@ The default `CanReachLocation` rule defines this function already.
|
||||
|
||||
### Entrance dependencies
|
||||
|
||||
If your custom rule references other entrances, it must define a `entrance_dependencies` function that returns a mapping
|
||||
of the entrance name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
||||
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
||||
caching system if applicable.
|
||||
If your custom rule references other entrances, it must define a `entrance_dependencies` function that returns a mapping of the entrance name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the caching system if applicable.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -382,13 +313,9 @@ The default `CanReachEntrance` rule defines this function already.
|
||||
|
||||
### Rule explanations
|
||||
|
||||
Resolved rules have a default implementation for `explain_json` and `explain_str` functions. The former optionally
|
||||
accepts a `CollectionState` and returns a list of `JSONMessagePart` appropriate for `print_json` in a client. It will
|
||||
display a human-readable message that explains what the rule requires. The latter is similar but returns a string. It is
|
||||
useful when debugging. There is also a `__str__` method defined to check what a rule is without a state.
|
||||
Resolved rules have a default implementation for `explain_json` and `explain_str` functions. The former optionally accepts a `CollectionState` and returns a list of `JSONMessagePart` appropriate for `print_json` in a client. It will display a human-readable message that explains what the rule requires. The latter is similar but returns a string. It is useful when debugging. There is also a `__str__` method defined to check what a rule is without a state.
|
||||
|
||||
To implement a custom message with a custom rule, override the `explain_json` and/or `explain_str` method on your
|
||||
`Resolved` class:
|
||||
To implement a custom message with a custom rule, override the `explain_json` and/or `explain_str` method on your `Resolved` class:
|
||||
|
||||
```python
|
||||
class MyRule(Rule, game="My Game"):
|
||||
@@ -425,35 +352,22 @@ class MyRule(Rule, game="My Game"):
|
||||
|
||||
### Cache control
|
||||
|
||||
By default your custom rule will work through the cache system as any other rule if caching is enabled. There are two
|
||||
class attributes on the `Resolved` class you can override to change this behavior.
|
||||
By default your custom rule will work through the cache system as any other rule if caching is enabled. There are two class attributes on the `Resolved` class you can override to change this behavior.
|
||||
|
||||
- `force_recalculate`: Setting this to `True` will cause your custom rule to skip going through the caching system and
|
||||
always recalculate when being evaluated. When a rule with this flag enabled is composed with `And` or `Or` it will
|
||||
cause any parent rules to always force recalculate as well. Use this flag when it's difficult to determine when your
|
||||
rule should be marked as stale.
|
||||
- `skip_cache`: Setting this to `True` will also cause your custom rule to skip going through the caching system when
|
||||
being evaluated. However, it will **not** affect any other rules when composed with `And` or `Or`, so it must still
|
||||
define its `*_dependencies` functions as required. Use this flag when the evaluation of this rule is trivial and the
|
||||
overhead of the caching system will slow it down.
|
||||
- `force_recalculate`: Setting this to `True` will cause your custom rule to skip going through the caching system and always recalculate when being evaluated. When a rule with this flag enabled is composed with `And` or `Or` it will cause any parent rules to always force recalculate as well. Use this flag when it's difficult to determine when your rule should be marked as stale.
|
||||
- `skip_cache`: Setting this to `True` will also cause your custom rule to skip going through the caching system when being evaluated. However, it will **not** affect any other rules when composed with `And` or `Or`, so it must still define its `*_dependencies` functions as required. Use this flag when the evaluation of this rule is trivial and the overhead of the caching system will slow it down.
|
||||
|
||||
### Caveats
|
||||
|
||||
- Ensure you are passing `caching_enabled=True` in your `_instantiate` function when creating resolved rule instances if
|
||||
your world has opted into caching.
|
||||
- Ensure you are passing `caching_enabled=True` in your `_instantiate` function when creating resolved rule instances if your world has opted into caching.
|
||||
- Resolved rules are forced to be frozen dataclasses. They and all their attributes must be immutable and hashable.
|
||||
- If your rule creates child rules ensure they are being resolved through the world rather than creating `Resolved`
|
||||
instances directly.
|
||||
- If your rule creates child rules ensure they are being resolved through the world rather than creating `Resolved` instances directly.
|
||||
|
||||
## Serialization
|
||||
|
||||
The rule builder is intended to be written first in Python for optimization and type safety. To facilitate exporting the
|
||||
rules to a client or tracker, rules have a `to_dict` method that returns a JSON-compatible dict. Since the location and
|
||||
entrance logic structure varies greatly from world to world, the actual JSON dumping is left up to the world dev.
|
||||
The rule builder is intended to be written first in Python for optimization and type safety. To facilitate exporting the rules to a client or tracker, rules have a `to_dict` method that returns a JSON-compatible dict. Since the location and entrance logic structure varies greatly from world to world, the actual JSON dumping is left up to the world dev.
|
||||
|
||||
The dict contains a `rule` key with the name of the rule, an `options` key with the rule's list of option filters, and
|
||||
an `args` key that contains any other arguments the individual rule has. For example, this is what a simple `Has` rule
|
||||
would look like:
|
||||
The dict contains a `rule` key with the name of the rule, an `options` key with the rule's list of option filters, and an `args` key that contains any other arguments the individual rule has. For example, this is what a simple `Has` rule would look like:
|
||||
|
||||
```python
|
||||
{
|
||||
@@ -466,8 +380,7 @@ would look like:
|
||||
}
|
||||
```
|
||||
|
||||
For `And` and `Or` rules, instead of an `args` key, they have a `children` key containing a list of their child rules in
|
||||
the same serializable format:
|
||||
For `And` and `Or` rules, instead of an `args` key, they have a `children` key containing a list of their child rules in the same serializable format:
|
||||
|
||||
```python
|
||||
{
|
||||
@@ -551,8 +464,7 @@ class BasicLogicRule(Rule, game="My Game"):
|
||||
}
|
||||
```
|
||||
|
||||
If your logic has been done in custom JSON first, you can define a `from_dict` class method on your rules to parse it
|
||||
correctly:
|
||||
If your logic has been done in custom JSON first, you can define a `from_dict` class method on your rules to parse it correctly:
|
||||
|
||||
```python
|
||||
class BasicLogicRule(Rule, game="My Game"):
|
||||
@@ -573,14 +485,10 @@ These are properties and helpers that are available to you in your world.
|
||||
#### Methods
|
||||
|
||||
- `rule_from_dict(data)`: Create a rule instance from a deserialized dict representation
|
||||
- `register_rule_builder_dependencies()`: Register all rules that depend on location or entrance access with the
|
||||
inherited dependencies, gets called automatically after set_rules
|
||||
- `set_rule(spot: Location | Entrance, rule: Rule)`: Resolve a rule, register its dependencies, and set it on the given
|
||||
location or entrance
|
||||
- `register_rule_builder_dependencies()`: Register all rules that depend on location or entrance access with the inherited dependencies, gets called automatically after set_rules
|
||||
- `set_rule(spot: Location | Entrance, rule: Rule)`: Resolve a rule, register its dependencies, and set it on the given location or entrance
|
||||
- `set_completion_rule(rule: Rule)`: Sets the completion condition for this world
|
||||
- `create_entrance(from_region: Region, to_region: Region, rule: Rule | None, name: str | None = None, force_creation: bool = False)`:
|
||||
Attempt to create an entrance from `from_region` to `to_region`, skipping creation if `rule` is defined and evaluates
|
||||
to `False_()` unless force_creation is `True`
|
||||
- `create_entrance(from_region: Region, to_region: Region, rule: Rule | None, name: str | None = None, force_creation: bool = False)`: Attempt to create an entrance from `from_region` to `to_region`, skipping creation if `rule` is defined and evaluates to `False_()` unless force_creation is `True`
|
||||
|
||||
#### CachedRuleBuilderWorld Properties
|
||||
|
||||
@@ -593,27 +501,18 @@ The following property is only available when inheriting from `CachedRuleBuilder
|
||||
These are properties and helpers that you can use or override for custom rules.
|
||||
|
||||
- `_instantiate(world: World)`: Create a new resolved rule instance, override for custom rules as required
|
||||
- `to_dict()`: Create a JSON-compatible dict representation of this rule, override if you want to customize your rule's
|
||||
serialization
|
||||
- `from_dict(data, world_cls: type[World])`: Return a new rule instance from a deserialized representation, override if
|
||||
you've overridden `to_dict`
|
||||
- `to_dict()`: Create a JSON-compatible dict representation of this rule, override if you want to customize your rule's serialization
|
||||
- `from_dict(data, world_cls: type[World])`: Return a new rule instance from a deserialized representation, override if you've overridden `to_dict`
|
||||
- `__str__()`: Basic string representation of a rule, useful for debugging
|
||||
|
||||
#### Resolved rule API
|
||||
|
||||
- `player: int`: The slot this rule is resolved for
|
||||
- `_evaluate(state: CollectionState)`: Evaluate this rule against the given state, override this to define the logic for
|
||||
this rule
|
||||
- `item_dependencies()`: A mapping of item name to set of ids, override this if your custom rule depends on item
|
||||
collection
|
||||
- `region_dependencies()`: A mapping of region name to set of ids, override this if your custom rule depends on reaching
|
||||
regions
|
||||
- `location_dependencies()`: A mapping of location name to set of ids, override this if your custom rule depends on
|
||||
reaching locations
|
||||
- `entrance_dependencies()`: A mapping of entrance name to set of ids, override this if your custom rule depends on
|
||||
reaching entrances
|
||||
- `explain_json(state: CollectionState | None = None)`: Return a list of printJSON messages describing this rule's logic
|
||||
(and if state is defined its evaluation) in a human readable way, override to explain custom rules
|
||||
- `explain_str(state: CollectionState | None = None)`: Return a string describing this rule's logic (and if state is
|
||||
defined its evaluation) in a human readable way, override to explain custom rules, more useful for debugging
|
||||
- `_evaluate(state: CollectionState)`: Evaluate this rule against the given state, override this to define the logic for this rule
|
||||
- `item_dependencies()`: A mapping of item name to set of ids, override this if your custom rule depends on item collection
|
||||
- `region_dependencies()`: A mapping of region name to set of ids, override this if your custom rule depends on reaching regions
|
||||
- `location_dependencies()`: A mapping of location name to set of ids, override this if your custom rule depends on reaching locations
|
||||
- `entrance_dependencies()`: A mapping of entrance name to set of ids, override this if your custom rule depends on reaching entrances
|
||||
- `explain_json(state: CollectionState | None = None)`: Return a list of printJSON messages describing this rule's logic (and if state is defined its evaluation) in a human readable way, override to explain custom rules
|
||||
- `explain_str(state: CollectionState | None = None)`: Return a string describing this rule's logic (and if state is defined its evaluation) in a human readable way, override to explain custom rules, more useful for debugging
|
||||
- `__str__()`: A string describing this rule's logic without its evaluation, override to explain custom rules
|
||||
|
||||
@@ -78,6 +78,16 @@ first generate the binary distribution and then run `python setup.py bdist_appim
|
||||
put an `appimagetool` into the directory you run the command from, rename it to `appimagetool` and make it executable.
|
||||
|
||||
|
||||
## Optional: A Link to the Past Enemizer
|
||||
|
||||
Only required to generate seeds that include A Link to the Past with certain options enabled. You will receive an
|
||||
error if it is required.
|
||||
|
||||
You can get the latest Enemizer release at [Enemizer Github releases](https://github.com/Ijwu/Enemizer/releases).
|
||||
It should be dropped as "EnemizerCLI" into the root folder of the project. Alternatively, you can point the Enemizer
|
||||
setting in host.yaml at your Enemizer executable.
|
||||
|
||||
|
||||
## Optional: SNI
|
||||
|
||||
[SNI](https://github.com/alttpo/sni/blob/main/README.md) is required to use SNIClient. If not integrated into the project, it has to be started manually.
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ Unless you configured PyCharm to use pytest as a test runner, you may get import
|
||||
edit the run configuration, and set the working directory to the Archipelago directory which contains all the project files.
|
||||
|
||||
If you only want to run your world's defined tests, repeat the steps for the test directory within your world.
|
||||
Your working directory should be the root Archipelago directory and the script should be the
|
||||
Your working directory should be the directory of your world in the worlds directory and the script should be the
|
||||
tests folder within your world.
|
||||
|
||||
You can also find the 'Archipelago Unittests' as an option in the dropdown at the top of the window
|
||||
|
||||
@@ -327,11 +327,6 @@ reject the placement of an item there.
|
||||
|
||||
### Events (or "generation-only items/locations")
|
||||
|
||||
> **Warning:** If you're trying to tell the Archipelago server that the player has achieved their goal, you want to send
|
||||
a [StatusUpdate packet](network%20protocol.md#statusupdate), or however [your client library](network%20protocol.md)
|
||||
wraps it. Despite the popularity of "victory events" during generation, events have nothing to do with how goals are
|
||||
triggered during gameplay.
|
||||
|
||||
An event item or location is one that only exists during multiworld generation; the server is never made aware of them.
|
||||
Event locations can never be checked by the player, and event items cannot be received during play.
|
||||
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
general_options:
|
||||
# Where to place output files
|
||||
output_path: "output"
|
||||
# Options for MultiServer
|
||||
# Null means nothing, for the server this means to default the value
|
||||
# These overwrite command line arguments!
|
||||
server_options:
|
||||
host: null
|
||||
port: 38281
|
||||
password: null
|
||||
multidata: null
|
||||
savefile: null
|
||||
disable_save: false
|
||||
loglevel: "info"
|
||||
logtime: false
|
||||
# Allows for clients to log on and manage the server. If this is null, no remote administration is possible.
|
||||
server_password: null
|
||||
# Disallow !getitem
|
||||
disable_item_cheat: false
|
||||
# Client hint system
|
||||
# Points given to a player for each acquired item in their world
|
||||
location_check_points: 1
|
||||
# Relative point cost to receive a hint via !hint for players
|
||||
# so for example hint_cost: 20 would mean that for every 20% of available checks, you get the ability to hint,
|
||||
# for a total of 5
|
||||
hint_cost: 10
|
||||
# Release modes
|
||||
# A Release sends out the remaining items *from* a world that releases
|
||||
# "disabled" -> clients can't release,
|
||||
# "enabled" -> clients can always release
|
||||
# "auto" -> automatic release on goal completion
|
||||
# "auto-enabled" -> automatic release on goal completion and manual release is also enabled
|
||||
# "goal" -> release is allowed after goal completion
|
||||
release_mode: "auto"
|
||||
# Collect modes
|
||||
# A Collect sends the remaining items *to* a world that collects
|
||||
# "disabled" -> clients can't collect,
|
||||
# "enabled" -> clients can always collect
|
||||
# "auto" -> automatic collect on goal completion
|
||||
# "auto-enabled" -> automatic collect on goal completion and manual collect is also enabled
|
||||
# "goal" -> collect is allowed after goal completion
|
||||
collect_mode: "auto"
|
||||
# Remaining modes
|
||||
# !remaining handling, that tells a client which items remain in their pool
|
||||
# "enabled" -> Client can always ask for remaining items
|
||||
# "disabled" -> Client can never ask for remaining items
|
||||
# "goal" -> Client can ask for remaining items after goal completion
|
||||
remaining_mode: "goal"
|
||||
# Countdown modes
|
||||
# Determines whether or not a player can initiate a countdown with !countdown
|
||||
# Note that /countdown is always available to the host.
|
||||
# "enabled" -> Client can always initiate a countdown with !countdown.
|
||||
# "disabled" -> Client can never initiate a countdown with !countdown.
|
||||
# "auto" -> !countdown will be available for any room with less than 30 slots.
|
||||
countdown_mode: "auto"
|
||||
# Automatically shut down the server after this many seconds without new location checks, 0 to keep running
|
||||
auto_shutdown: 0
|
||||
# Compatibility handling
|
||||
# 2 -> Recommended for casual/cooperative play, attempt to be compatible with everything across all versions
|
||||
# 1 -> No longer in use, kept reserved in case of future use
|
||||
# 0 -> Recommended for tournaments to force a level playing field, only allow an exact version match
|
||||
compatibility: 2
|
||||
# log all server traffic, mostly for dev use
|
||||
log_network: 0
|
||||
# Options for Generation
|
||||
generator:
|
||||
# Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases
|
||||
enemizer_path: "EnemizerCLI/EnemizerCLI.Core"
|
||||
# Folder from which the player yaml files are pulled from
|
||||
player_files_path: "Players"
|
||||
# amount of players, 0 to infer from player files
|
||||
players: 0
|
||||
# general weights file, within the stated player_files_path location
|
||||
# gets used if players is higher than the amount of per-player files found to fill remaining slots
|
||||
weights_file_path: "weights.yaml"
|
||||
# Meta file name, within the stated player_files_path location
|
||||
meta_file_path: "meta.yaml"
|
||||
# Create a spoiler file
|
||||
# 0 -> None
|
||||
# 1 -> Spoiler without playthrough or paths to playthrough required items
|
||||
# 2 -> Spoiler with playthrough (viable solution to goals)
|
||||
# 3 -> Spoiler with playthrough and traversal paths towards items
|
||||
spoiler: 3
|
||||
# Create encrypted race roms and flag games as race mode
|
||||
race: 0
|
||||
# List of options that can be plando'd. Can be combined, for example "bosses, items"
|
||||
# Available options: bosses, items, texts, connections
|
||||
plando_options: "bosses, connections, texts"
|
||||
# What to do if the current item placements appear unsolvable.
|
||||
# raise -> Raise an exception and abort.
|
||||
# swap -> Attempt to fix it by swapping prior placements around. (Default)
|
||||
# start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations.
|
||||
panic_method: "swap"
|
||||
loglevel: "info"
|
||||
logtime: false
|
||||
sni_options:
|
||||
# Set this to your SNI folder location if you want the MultiClient to attempt an auto start, does nothing if not found
|
||||
sni_path: "SNI"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# True for operating system default program
|
||||
# Alternatively, a path to a program to open the .sfc file with
|
||||
snes_rom_start: true
|
||||
bizhawkclient_options:
|
||||
# The location of the EmuHawk you want to auto launch patched ROMs with
|
||||
emuhawk_path: "None"
|
||||
# Set this to true to autostart a patched ROM in BizHawk with the connector script,
|
||||
# to false to never open the patched rom automatically,
|
||||
# or to a path to an external program to open the ROM file with that instead.
|
||||
rom_start: true
|
||||
adventure_options:
|
||||
# File name of the standard NTSC Adventure rom.
|
||||
# The licensed "The 80 Classic Games" CD-ROM contains this.
|
||||
# It may also have a .a26 extension
|
||||
rom_file: "roms/ADVNTURE.BIN"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# True for operating system default program for '.a26'
|
||||
# Alternatively, a path to a program to open the .a26 file with (generally EmuHawk for multiworld)
|
||||
rom_start: true
|
||||
# Optional, additional args passed into rom_start before the .bin file
|
||||
# For example, this can be used to autoload the connector script in BizHawk
|
||||
# (see BizHawk --lua= option)
|
||||
# Windows example:
|
||||
# rom_args: "--lua=C:/ProgramData/Archipelago/data/lua/connector_adventure.lua"
|
||||
rom_args: " "
|
||||
# Set this to true to display item received messages in EmuHawk
|
||||
display_msgs: true
|
||||
ape_escape_3_options:
|
||||
# Preferences for game session management.
|
||||
# > save_state_on_room_transition: Automatically create a save state when transitioning between rooms.
|
||||
# > save_state_on_item_received: Automatically create a save state when receiving a new progressive item.
|
||||
# > save_state_on_location_check: Automatically create a save state when checking a new location.
|
||||
# > load_state_on_connect: Load a state automatically after connecting to the multiworld if the client
|
||||
# is already connected to the game and that the last save is from a save state and not a normal game save.
|
||||
save_state_on_room_transition: false
|
||||
save_state_on_item_received: true
|
||||
save_state_on_location_check: false
|
||||
load_state_on_connect: false
|
||||
# Preferences for game/client-enforcement behavior
|
||||
# > auto-equip : Automatically assign received gadgets to a face button
|
||||
auto_equip: true
|
||||
# Preferences for game generation. Only relevant for world generation and not the setup of or during play.
|
||||
# > whitelist_pgc_bypass: Allow Ape Escape 3 players to enable "PGC Bypass" as a possible outcome for
|
||||
# Lucky Ticket Consolation Prize.
|
||||
# > whitelist_instant_goal: Allow Ape Escape 3 players to enable "Instant Goal" as a possible outcome for
|
||||
# Lucky Ticket Consolation Prize.
|
||||
whitelist_pgc_bypass: false
|
||||
whitelist_instant_goal: false
|
||||
banjo_tooie_options:
|
||||
# File path of the Banjo-Tooie (USA) ROM.
|
||||
rom_path: ""
|
||||
# Folder path of where to save the patched ROM.
|
||||
patch_path: ""
|
||||
# File path of the program to automatically run.
|
||||
# Leave blank to disable.
|
||||
program_path: ""
|
||||
# Arguments to pass to the automatically run program.
|
||||
# Leave blank to disable.
|
||||
# Set to "--lua=" to automatically use the correct path for the lua connector.
|
||||
program_args: "--lua="
|
||||
# No idea
|
||||
clair_obscur_options:
|
||||
{}
|
||||
cv64_options:
|
||||
# File name of the CV64 US 1.0 rom
|
||||
rom_file: "roms/Castlevania (USA).z64"
|
||||
cv_dos_options:
|
||||
# File name of the Castlevania: Dawn of Sorrow ROM file.
|
||||
rom_file: "roms/CASTLEVANIA1_ACVEA4_00.nds"
|
||||
cvcotm_options:
|
||||
# File name of the Castlevania CotM US rom
|
||||
rom_file: "roms/Castlevania - Circle of the Moon (USA).gba"
|
||||
cvhodis_options:
|
||||
# File name of the Castlevania HoD US rom
|
||||
rom_file: "roms/Castlevania - Harmony of Dissonance (USA).gba"
|
||||
cvlod_options:
|
||||
# File name of the CVLoD US rom
|
||||
rom_file: "Castlevania - Legacy of Darkness (USA).z64"
|
||||
# Settings for the DK64 randomizer.
|
||||
dk64_options:
|
||||
# Choose the release version of the DK64 randomizer to use.
|
||||
# By setting it to master (Default) you will always pull the latest stable version.
|
||||
# By setting it to dev you will pull the latest development version.
|
||||
# If you want a specific version, you can set it to a AP version number eg: v1.0.45
|
||||
release_branch: "master"
|
||||
dkc2_options:
|
||||
# File name of the Donkey Kong Country 2 US v1.1 ROM
|
||||
rom_file: "roms/Donkey Kong Country 2 - Diddy's Kong Quest (USA).sfc"
|
||||
# Path to the user's Donkey Kong Country 2 Poptracker Pack.
|
||||
ut_poptracker_path: ""
|
||||
# Folder path of the trivia database
|
||||
# Preferably point it to /data/trivia/dkc2/
|
||||
trivia_path: "data/trivia/dkc2"
|
||||
dkc3_options:
|
||||
# File name of the DKC3 US rom
|
||||
rom_file: "roms/Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc"
|
||||
earthbound_options:
|
||||
# File name of the EarthBound US ROM
|
||||
rom_file: "roms/EarthBound.sfc"
|
||||
factorio_options:
|
||||
executable: "factorio/bin/x64/factorio"
|
||||
# by default, no settings are loaded if this file does not exist. If this file does exist, then it will be used.
|
||||
# server_settings: "factorio\\data\\server-settings.json"
|
||||
server_settings: null
|
||||
# Whether to filter item send messages displayed in-game to only those that involve you.
|
||||
filter_item_sends: false
|
||||
# Whether to filter connection changes displayed in-game.
|
||||
filter_connection_changes: false
|
||||
# Whether to send chat messages from players on the Factorio server to Archipelago.
|
||||
bridge_chat_out: true
|
||||
fe8_settings:
|
||||
# File name of your Fire Emblem: The Sacred Stones (U) ROM
|
||||
rom_file: "roms/Fire Emblem The Sacred Stones (U).gba"
|
||||
ffr_options:
|
||||
display_msgs: true
|
||||
gauntletlegends_options:
|
||||
# The location of your Retroarch folder
|
||||
retroarch_path: "None"
|
||||
# File name of the GL US rom
|
||||
rom_file: "roms/Gauntlet Legends (U) [!].z64"
|
||||
rom_start: true
|
||||
glover_options:
|
||||
# File path of the Glover (USA) ROM.
|
||||
rom_path: ""
|
||||
# Folder path of where to save the patched ROM.
|
||||
patch_path: ""
|
||||
# File path of the program to automatically run.
|
||||
# Leave blank to disable.
|
||||
program_path: ""
|
||||
# Arguments to pass to the automatically run program.
|
||||
# Leave blank to disable.
|
||||
# Set to "--lua=" to automatically use the correct path for the lua connector.
|
||||
program_args: "--lua="
|
||||
gstla_options:
|
||||
# File name of the GS TLA UE Rom
|
||||
rom_file: "roms/Golden Sun - The Lost Age (UE) [!].gba"
|
||||
hades_options:
|
||||
# Path to the StyxScribe install
|
||||
styx_scribe_path: "C:/Program Files/Steam/steamapps/common/Hades/StyxScribe.py"
|
||||
hk_options:
|
||||
# Disallows the APMapMod from showing spoiler placements.
|
||||
disable_spoilers: false
|
||||
jakanddaxter_options:
|
||||
# Path to folder containing the ArchipelaGOAL mod executables (gk.exe and goalc.exe).
|
||||
# Ensure this path contains forward slashes (/) only. This setting only applies if
|
||||
# Auto Detect Root Directory is set to false.
|
||||
root_directory: "%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal"
|
||||
# Attempt to find the OpenGOAL installation and the mod executables (gk.exe and goalc.exe)
|
||||
# automatically. If set to true, the ArchipelaGOAL Root Directory setting is ignored.
|
||||
auto_detect_root_directory: true
|
||||
# Enforce friendly player options in both single and multiplayer seeds. Disabling this allows for
|
||||
# more disruptive and challenging options, but may impact seed generation. Use at your own risk!
|
||||
enforce_friendly_options: true
|
||||
k64_options:
|
||||
# File name of the K64 EN rom
|
||||
rom_file: "roms/Kirby 64 - The Crystal Shards (USA).z64"
|
||||
kdl3_options:
|
||||
# File name of the KDL3 JP or EN rom
|
||||
rom_file: "roms/Kirby's Dream Land 3.sfc"
|
||||
ladx_options:
|
||||
# File name of the Link's Awakening DX rom
|
||||
rom_file: "roms/Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# true for operating system default program
|
||||
# Alternatively, a path to a program to open the .gbc file with
|
||||
# Examples:
|
||||
# Retroarch:
|
||||
# rom_start: "C:/RetroArch-Win64/retroarch.exe -L sameboy"
|
||||
# BizHawk:
|
||||
# rom_start: "C:/BizHawk-2.9-win-x64/EmuHawk.exe --lua=data/lua/connector_ladx_bizhawk.lua"
|
||||
rom_start: true
|
||||
# Gfxmod file, get it from upstream: https://github.com/daid/LADXR/tree/master/gfx
|
||||
# Only .bin or .bdiff files
|
||||
# The same directory will be checked for a matching text modification file
|
||||
gfx_mod_file: ""
|
||||
lttp_options:
|
||||
# File name of the v1.0 J rom
|
||||
rom_file: "roms/Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"
|
||||
lufia2ac_options:
|
||||
# File name of the US rom
|
||||
rom_file: "roms/Lufia II - Rise of the Sinistrals (USA).sfc"
|
||||
messenger_settings:
|
||||
game_path: "TheMessenger.exe"
|
||||
metroidzeromission_options:
|
||||
# File name of the Metroid: Zero Mission ROM.
|
||||
rom_file: "roms/Metroid - Zero Mission (USA).gba"
|
||||
# Set this to false to never autostart a rom (such as after patching),
|
||||
# Set it to true to have the operating system default program open the rom
|
||||
# Alternatively, set it to a path to a program to open the .gba file with
|
||||
rom_start: true
|
||||
mk64_options:
|
||||
# File name of the MK64 ROM
|
||||
rom_file: "roms/Mario Kart 64 (U) [!].z64"
|
||||
metroidfusion_options:
|
||||
# File name of the Metroid Fusion ROM
|
||||
rom_file: "roms/Metroid Fusion (USA).gba"
|
||||
rom_start: true
|
||||
display_location_found_messages: true
|
||||
mlss_options:
|
||||
# File name of the MLSS US rom
|
||||
rom_file: "roms/Mario & Luigi - Superstar Saga (U).gba"
|
||||
rom_start: true
|
||||
mm2_options:
|
||||
# File name of the MM2 EN rom
|
||||
rom_file: "roms/Mega Man 2 (USA).nes"
|
||||
mmbn3_options:
|
||||
# File name of the MMBN3 Blue US rom
|
||||
rom_file: "roms/Mega Man Battle Network 3 - Blue Version (USA).gba"
|
||||
# Set this to false to never autostart a rom (such as after patching),
|
||||
# true for operating system default program
|
||||
# Alternatively, a path to a program to open the .gba file with
|
||||
rom_start: true
|
||||
mzm_options:
|
||||
rom_file: "roms/Metroid - Zero Mission (USA).gba"
|
||||
rom_start: true
|
||||
oot_options:
|
||||
# File name of the OoT v1.0 ROM
|
||||
rom_file: "roms/The Legend of Zelda - Ocarina of Time.z64"
|
||||
# Set this to false to never autostart a rom (such as after patching),
|
||||
# true for operating system default program
|
||||
# Alternatively, a path to a program to open the .z64 file with
|
||||
rom_start: true
|
||||
paper_mario_settings:
|
||||
# File name of the Paper Mario USA ROM
|
||||
rom_file: "roms/Paper Mario (USA).z64"
|
||||
# Set this to false to never autostart a rom (such as after patching),
|
||||
# true for operating system default program
|
||||
# Alternatively, a path to a program to open the .z64 file with
|
||||
rom_start: true
|
||||
papermariottyd_options:
|
||||
# The location of the Dolphin you want to auto launch patched ROMs with
|
||||
dolphin_path: "None"
|
||||
# File name of the TTYD US iso
|
||||
rom_file: "roms/Paper Mario - The Thousand-Year Door (USA).iso"
|
||||
rom_start: true
|
||||
pmd_eos_options:
|
||||
# File name of the EoS EU rom
|
||||
rom_file: "roms/POKEDUN_SORA_C2SP01_00.nds"
|
||||
rom_start: true
|
||||
pokemon_bw_settings:
|
||||
# File name of your Pokémon Black Version ROM
|
||||
black_rom: "PokemonBlack.nds"
|
||||
# File name of your Pokémon White Version ROM
|
||||
white_rom: "PokemonWhite.nds"
|
||||
# Toggles whether Encounter Plando is enabled for players in generation.
|
||||
# If disabled, yamls that use Encounter Plando do not raise OptionErrors, but display a warning.
|
||||
enable_encounter_plando: true
|
||||
# If enabled, files inside the rom that are changed as part of the patching process (except for base patches)
|
||||
# will be dumped into a zip file next to the patched rom (for debug purposes).
|
||||
dump_patched_files: false
|
||||
pokemon_crystal_settings:
|
||||
rom_file: "roms/Pokemon - Crystal Version (UE) [C][!].gbc"
|
||||
pokemon_emerald_settings:
|
||||
# File name of your English Pokemon Emerald ROM
|
||||
rom_file: "roms/Pokemon - Emerald Version (USA, Europe).gba"
|
||||
pokemon_frlg_settings:
|
||||
# File name of your English Pokémon FireRed ROM
|
||||
firered_rom_file: "roms/Pokemon - FireRed Version (USA, Europe).gba"
|
||||
# File name of your English Pokémon LeafGreen ROM
|
||||
leafgreen_rom_file: "roms/Pokemon - LeafGreen Version (USA, Europe).gba"
|
||||
ut_poptracker_path: ""
|
||||
pokemon_platinum_settings:
|
||||
rom_file: "roms/pokeplatinum.nds"
|
||||
pokemon_rb_options:
|
||||
# File names of the Pokemon Red and Blue roms
|
||||
red_rom_file: "roms/Pokemon Red (UE) [S][!].gb"
|
||||
blue_rom_file: "roms/Pokemon Blue (UE) [S][!].gb"
|
||||
pokepinball_settings:
|
||||
# File name of the Pokemon Pinball Color US rom
|
||||
rom_file: "roms/PokemonPinball.gbc"
|
||||
portal2_options:
|
||||
# The file path of the extras.txt file (used to generate the menu in game)
|
||||
menu_file: "C:\\Program Files (x86)\\Steam\\steamapps\\sourcemods\\Portal2Archipelago\\scripts\\extras.txt"
|
||||
# The port set in the portal 2 launch options e.g. 3000
|
||||
default_portal2_port: 3000
|
||||
saving_princess_settings:
|
||||
# Path to the game executable from which files are extracted
|
||||
exe_path: "Saving Princess.exe"
|
||||
# Path to the mod installation folder
|
||||
install_folder: "Saving Princess"
|
||||
# Set this to false to never autostart the game
|
||||
launch_game: true
|
||||
# The console command that will be used to launch the game
|
||||
# The command will be executed with the installation folder as the current directory
|
||||
launch_command: "wine \"Saving Princess v0_8.exe\""
|
||||
sc2_options:
|
||||
# The starting width the client window in pixels
|
||||
window_width: 1080
|
||||
# The starting height the client window in pixels
|
||||
window_height: 720
|
||||
# Controls whether the game should start in windowed mode
|
||||
game_windowed_mode: false
|
||||
# If set to true, in-client scouting will show traps as distinct from filler
|
||||
show_traps: false
|
||||
# Overrides the disable forced-camera slot option. Possible values: `true`, `false`, `default`. Default uses slot value
|
||||
disable_forced_camera: "default"
|
||||
# Overrides the skip cutscenes slot option. Possible values: `true`, `false`, `default`. Default uses slot value
|
||||
skip_cutscenes: "default"
|
||||
# Overrides the slot's difficulty setting. Possible values: `casual`, `normal`, `hard`, `brutal`, `default`. Default uses slot value
|
||||
game_difficulty: "default"
|
||||
# Overrides the slot's gamespeed setting. Possible values: `slower`, `slow`, `normal`, `fast`, `faster`, `default`. Default uses slot value
|
||||
game_speed: "default"
|
||||
# Defines the colour of terran mission buttons in the launcher in rgb format (3 elements ranging from 0 to 1)
|
||||
terran_button_color:
|
||||
- 0.0838
|
||||
- 0.2898
|
||||
- 0.2346
|
||||
# Defines the colour of zerg mission buttons in the launcher in rgb format (3 elements ranging from 0 to 1)
|
||||
zerg_button_color:
|
||||
- 0.345
|
||||
- 0.22425
|
||||
- 0.12765
|
||||
# Defines the colour of protoss mission buttons in the launcher in rgb format (3 elements ranging from 0 to 1)
|
||||
protoss_button_color:
|
||||
- 0.18975
|
||||
- 0.2415
|
||||
- 0.345
|
||||
sf64_options:
|
||||
# File path of the Star Fox 64 v1.1 ROM.
|
||||
rom_path: ""
|
||||
# Folder path of where to save the patched ROM.
|
||||
patch_path: ""
|
||||
# File path of the program to automatically run.
|
||||
# Leave blank to disable.
|
||||
program_path: ""
|
||||
# Arguments to pass to the automatically run program.
|
||||
# Leave blank to disable.
|
||||
program_args: "--lua=\\\\wsl.localhost\\Ubuntu\\home\\ubufu\\ap-cm-1dd91ec\\Archipelago-main\\data\\lua\\connector_sf64_bizhawk.lua"
|
||||
# Whether to enable the built in logic Tracker.
|
||||
# If enabled, the 'Tracker' tab will show all unchecked locations in logic.
|
||||
enable_tracker: true
|
||||
sm_options:
|
||||
# File name of the v1.0 J rom
|
||||
rom_file: "roms/Super Metroid (JU).sfc"
|
||||
sml2_options:
|
||||
# File name of the Super Mario Land 2 1.0 ROM
|
||||
rom_file: "roms/Super Mario Land 2 - 6 Golden Coins (USA, Europe).gb"
|
||||
sms_options:
|
||||
iso_file: "roms/sms_us_2002.iso"
|
||||
smw_options:
|
||||
# File name of the SMW US rom
|
||||
rom_file: "roms/Super Mario World (USA).sfc"
|
||||
soe_options:
|
||||
# File name of the SoE US ROM
|
||||
rom_file: "roms/Secret of Evermore (USA).sfc"
|
||||
spyro2_options:
|
||||
# Permits full gemsanity options for multiplayer games.
|
||||
# Full gemsanity adds 2546 locations and an equal number of progression items.
|
||||
# These items may be local-only or spread across the multiworld.
|
||||
allow_full_gemsanity: false
|
||||
stadium_options:
|
||||
# File name of the Pokemon Stadium (US, 1.0) ROM
|
||||
rom_file: "roms/Pokemon Stadium (US, 1.0).z64"
|
||||
stardew_valley_options:
|
||||
# Allow players to pick the goal 'Allsanity'. If disallowed, generation will fail.
|
||||
allow_allsanity: true
|
||||
# Allow players to pick the goal 'Perfection'. If disallowed, generation will fail.
|
||||
allow_perfection: true
|
||||
# Allow players to pick the option 'Bundle Price: Maximum'. If disallowed, it will be replaced with 'Very Expensive'
|
||||
allow_max_bundles: true
|
||||
# Allow players to pick the option 'Entrance Randomization: Chaos'. If disallowed, it will be replaced with 'Buildings'
|
||||
allow_chaos_er: false
|
||||
# Allow players to pick the option 'Shipsanity: Everything'. If disallowed, it will be replaced with 'Full Shipment With Fish'
|
||||
allow_shipsanity_everything: true
|
||||
# Allow players to pick the option 'Hatsanity: Near Perfection OR Post Perfection'. If disallowed, it will be replaced with 'Difficult'
|
||||
allow_hatsanity_perfection: true
|
||||
# Allow players to toggle on Custom logic flags. If disallowed, it will be disabled
|
||||
allow_custom_logic: true
|
||||
# Allow players to enable Jojapocalypse. If disallowed, it will be disabled
|
||||
allow_jojapocalypse: false
|
||||
tcg_card_shop_simulator_options:
|
||||
# This limits goals to a reasonable number and sets all excessive settings to local_fill or Excluded for better sync experiences.
|
||||
limit_checks_for_syncs: false
|
||||
# Card Sanity adds pure randomness to card checks. This option disables this sanity in your multiworlds
|
||||
allow_card_sanity: true
|
||||
tloz_ooa_options:
|
||||
# File path of the OOA US rom
|
||||
rom_file: "roms/Legend of Zelda, The - Oracle of Ages (USA).gbc"
|
||||
# A factor applied to the infamous heart beep sound interval.
|
||||
# Valid values are: "vanilla", "half", "quarter", "disabled"
|
||||
heart_beep_interval: "vanilla"
|
||||
# The name of the sprite file to use (from "data/sprites/oos_ooa/").
|
||||
# Putting "link" as a value uses the default game sprite.
|
||||
# Putting "random" as a value randomly picks a sprite from your sprites directory for each generated ROM.
|
||||
character_sprite: "link"
|
||||
# The color palette used for character sprite throughout the game.
|
||||
# Valid values are: "green", "red", "blue", "orange", and "random"
|
||||
character_palette: "green"
|
||||
# Defines if you don't want to spam the buttons to swim with the mermaid suit.
|
||||
qol_mermaid_suit: true
|
||||
# When enabled, playing the flute and the harp will immobilize you during a very small amount of time compared to vanilla game.
|
||||
qol_quick_flute: true
|
||||
# Defines if you want to skip the small dance that tokkay does
|
||||
skip_tokkey_dance: false
|
||||
# Defines if you want to skip the joke you tell to the sad boi
|
||||
skip_boi_joke: false
|
||||
tloz_oos_options:
|
||||
# File name of the Oracle of Seasons US ROM
|
||||
rom_file: "roms/Legend of Zelda, The - Oracle of Seasons (USA).gbc"
|
||||
# File name of the Oracle of Ages US ROM (only needed for cross items)
|
||||
ages_rom_file: "roms/Legend of Zelda, The - Oracle of Ages (USA).gbc"
|
||||
rom_start: true
|
||||
# The name of the sprite file to use (from "data/sprites/oos_ooa/").
|
||||
# Putting "link" as a value uses the default game sprite.
|
||||
# Putting "random" as a value randomly picks a sprite from your sprites directory for each generated ROM.
|
||||
# If you want some weighted result, you can arrange the options like in your option yaml.
|
||||
character_sprite: "link"
|
||||
# The color palette used for character sprite throughout the game.
|
||||
# Valid values are: "green", "red", "blue", "orange", and "random"
|
||||
# If you want some weighted result, you can arrange the options like in your option yaml.
|
||||
# If you want a color weight to only apply to a specific sprite, you can write color|sprite: weight.
|
||||
# For example, red|link: 1 would add red in the possible palettes with a weight of 1 only if link is the selected sprite
|
||||
character_palette: "green"
|
||||
# If enabled, hidden digging spots in Subrosia are revealed as diggable tiles.
|
||||
reveal_hidden_subrosia_digging_spots: true
|
||||
# A factor applied to the infamous heart beep sound interval.
|
||||
# Valid values are: "vanilla", "half", "quarter", "disabled"
|
||||
heart_beep_interval: "vanilla"
|
||||
# If true, no music will be played in the game while sound effects remain untouched
|
||||
remove_music: false
|
||||
tloz_options:
|
||||
# File name of the Zelda 1
|
||||
rom_file: "roms/Legend of Zelda, The (U) (PRG0) [!].nes"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# true for operating system default program
|
||||
# Alternatively, a path to a program to open the .nes file with
|
||||
rom_start: true
|
||||
# Display message inside of Bizhawk
|
||||
display_msgs: true
|
||||
tloz_ph_options:
|
||||
# For use with universal tracker.
|
||||
# Toggles if universal tracker can use unlocked shortcuts and map warps to find shorter paths for /get_logical_path.
|
||||
ut_get_logical_path_shortcuts: true
|
||||
tloz_st_options:
|
||||
# Train speed for each of the 4 gears, from lowest (reverse) to highest.
|
||||
# defaults are -143, 0, 115, 193
|
||||
train_speed:
|
||||
- -143
|
||||
- 0
|
||||
- 115
|
||||
- 193
|
||||
# The train will instantly switch to the new speed when changing gears, no acceleration required.
|
||||
# Does not apply to your stop gear.
|
||||
train_snap_speed: true
|
||||
# Allows entering stations immediately on the stop gear, no matter your speed.
|
||||
train_quick_station: true
|
||||
ttyd_options:
|
||||
# The location of the Dolphin you want to auto launch patched ROMs with
|
||||
dolphin_path: "None"
|
||||
# File name of the TTYD US iso
|
||||
rom_file: "roms/Paper Mario - The Thousand-Year Door (USA).iso"
|
||||
rom_start: true
|
||||
tunic_options:
|
||||
# Disallows the TUNIC client from creating a local spoiler log.
|
||||
disable_local_spoiler: false
|
||||
# Limits the impact of Grass Randomizer on the multiworld by disallowing local_fill percentages below 95.
|
||||
limit_grass_rando: true
|
||||
# Path to the user's TUNIC Poptracker Pack.
|
||||
ut_poptracker_path: ""
|
||||
vampire_survivors_options:
|
||||
# Allow the use of unfair characters
|
||||
allow_unfair_characters: false
|
||||
voltorb_flip_settings:
|
||||
# Allows the **experimental** choice in the **Artificial Logic** option.
|
||||
allow_experimental_logic: false
|
||||
wargroove_options:
|
||||
# Locates the Wargroove root directory on your system.
|
||||
# This is used by the Wargroove client, so it knows where to send communication files to.
|
||||
root_directory: "C:/Program Files (x86)/Steam/steamapps/common/Wargroove"
|
||||
# Locates the Wargroove save file directory on your system.
|
||||
# This is used by the Wargroove client, so it knows where to send mod and save files to.
|
||||
save_directory: "%APPDATA%"
|
||||
yoshisisland_options:
|
||||
# File name of the Yoshi's Island 1.0 US rom
|
||||
rom_file: "roms/Super Mario World 2 - Yoshi's Island (U).sfc"
|
||||
yugioh06_settings:
|
||||
# File name of your Yu-Gi-Oh 2006 ROM
|
||||
rom_file: "roms/YuGiOh06.gba"
|
||||
zillion_options:
|
||||
# File name of the Zillion US rom
|
||||
rom_file: "roms/Zillion (UE) [!].sms"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# True for operating system default program
|
||||
# Alternatively, a path to a program to open the .sfc file with
|
||||
# RetroArch doesn't make it easy to launch a game from the command line.
|
||||
# You have to know the path to the emulator core library on the user's computer.
|
||||
rom_start: "retroarch"
|
||||
+3
-1
@@ -57,8 +57,9 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom
|
||||
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
|
||||
|
||||
[Files]
|
||||
Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs;
|
||||
Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs;
|
||||
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||
|
||||
[Icons]
|
||||
@@ -82,6 +83,7 @@ Type: files; Name: "{app}\*.exe"
|
||||
Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua"
|
||||
Type: files; Name: "{app}\data\lua\connector_ff1.lua"
|
||||
Type: filesandordirs; Name: "{app}\SNI\lua*"
|
||||
Type: filesandordirs; Name: "{app}\EnemizerCLI*"
|
||||
#include "installdelete.iss"
|
||||
|
||||
[Registry]
|
||||
|
||||
@@ -363,16 +363,15 @@ class ServerLabel(HoverBehavior, MDTooltip, MDBoxLayout):
|
||||
text += "\nPermissions:"
|
||||
for permission_name, permission_data in ctx.permissions.items():
|
||||
text += f"\n {permission_name}: {permission_data}"
|
||||
if ctx.total_locations and ctx.hint_cost is not None:
|
||||
if ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
else:
|
||||
min_cost = int(ctx.server_version >= (0, 3, 9))
|
||||
text += f"\nA new !hint <itemname> costs {ctx.hint_cost}% of checks made. " \
|
||||
f"For you this means every " \
|
||||
f"{max(min_cost, int(ctx.hint_cost * 0.01 * ctx.total_locations))} " \
|
||||
"location checks." \
|
||||
f"\nYou currently have {ctx.hint_points} points."
|
||||
if ctx.hint_cost is not None and ctx.total_locations:
|
||||
min_cost = int(ctx.server_version >= (0, 3, 9))
|
||||
text += f"\nA new !hint <itemname> costs {ctx.hint_cost}% of checks made. " \
|
||||
f"For you this means every " \
|
||||
f"{max(min_cost, int(ctx.hint_cost * 0.01 * ctx.total_locations))} " \
|
||||
"location checks." \
|
||||
f"\nYou currently have {ctx.hint_points} points."
|
||||
elif ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
if ctx.stored_data and "_read_race_mode" in ctx.stored_data:
|
||||
text += "\nRace mode is enabled." \
|
||||
if ctx.stored_data["_read_race_mode"] else "\nRace mode is disabled."
|
||||
|
||||
+7
-138
@@ -36,7 +36,7 @@ def _create_hash_fn(resolved_rule_cls: "CustomRuleRegister") -> Callable[..., in
|
||||
class CustomRuleRegister(type):
|
||||
"""A metaclass to contain world custom rules and automatically convert resolved rules to frozen dataclasses"""
|
||||
|
||||
resolved_rules: ClassVar[dict["Rule.Resolved", "Rule.Resolved"]] = {}
|
||||
resolved_rules: ClassVar[dict[int, "Rule.Resolved"]] = {}
|
||||
"""A cached of resolved rules to turn each unique one into a singleton"""
|
||||
|
||||
custom_rules: ClassVar[dict[str, dict[str, type["Rule[Any]"]]]] = {}
|
||||
@@ -64,9 +64,10 @@ class CustomRuleRegister(type):
|
||||
@override
|
||||
def __call__(cls, *args: Any, **kwds: Any) -> Any:
|
||||
rule = super().__call__(*args, **kwds)
|
||||
if rule in cls.resolved_rules:
|
||||
return cls.resolved_rules[rule]
|
||||
cls.resolved_rules[rule] = rule
|
||||
rule_hash = hash(rule)
|
||||
if rule_hash in cls.resolved_rules:
|
||||
return cls.resolved_rules[rule_hash]
|
||||
cls.resolved_rules[rule_hash] = rule
|
||||
return rule
|
||||
|
||||
@classmethod
|
||||
@@ -425,142 +426,13 @@ class NestedRule(Rule[TWorld], game="Archipelago"):
|
||||
return combined_deps
|
||||
|
||||
|
||||
class AtLeast(NestedRule[TWorld], game="Archipelago"):
|
||||
"""A rule that returns true when at least N child rules evaluate as true"""
|
||||
|
||||
count: int | FieldResolver
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
count: int | FieldResolver,
|
||||
*children: Rule[TWorld],
|
||||
options: Iterable[OptionFilter] = (),
|
||||
filtered_resolution: bool = False,
|
||||
) -> None:
|
||||
super().__init__(*children, options=options, filtered_resolution=filtered_resolution)
|
||||
self.count = count
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
count = resolve_field(self.count, world, int)
|
||||
if count == 0:
|
||||
return True_().resolve(world)
|
||||
|
||||
children_to_process = [c.resolve(world) for c in self.children]
|
||||
return AtLeast.from_resolved(count, world, children_to_process)
|
||||
|
||||
@classmethod
|
||||
def from_resolved(cls, count: int, world: TWorld, children_to_process: list[Rule.Resolved]) -> Rule.Resolved:
|
||||
clauses: list[Rule.Resolved] = []
|
||||
|
||||
while children_to_process:
|
||||
child = children_to_process.pop(0)
|
||||
if child.always_true:
|
||||
if count == 1:
|
||||
return child
|
||||
count -= 1
|
||||
continue
|
||||
if child.always_false:
|
||||
# falses can be ignored
|
||||
continue
|
||||
|
||||
clauses.append(child)
|
||||
|
||||
if len(clauses) < count:
|
||||
return False_().resolve(world)
|
||||
if count == 1:
|
||||
# Switch to Or which has more optimized handling
|
||||
return Or.from_resolved(world, clauses)
|
||||
if count == len(clauses):
|
||||
# Switch to And which has more optimized handling
|
||||
return And.from_resolved(world, clauses)
|
||||
return AtLeast.Resolved(
|
||||
tuple(clauses),
|
||||
count=count,
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
|
||||
@override
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
output = super().to_dict()
|
||||
count = self.count
|
||||
output["count"] = count.to_dict() if isinstance(count, FieldResolver) else count
|
||||
return output
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
|
||||
args = cls._parse_field_resolvers(data, world_cls.game)
|
||||
options = OptionFilter.multiple_from_dict(data.get("options", ()))
|
||||
children = [world_cls.rule_from_dict(c) for c in data.get("children", ())]
|
||||
return cls(
|
||||
args.pop("count"),
|
||||
*children,
|
||||
options=options,
|
||||
filtered_resolution=data.get("filtered_resolution", False),
|
||||
)
|
||||
|
||||
class Resolved(NestedRule.Resolved):
|
||||
count: int
|
||||
|
||||
@override
|
||||
def _evaluate(self, state: CollectionState) -> bool:
|
||||
count = self.count
|
||||
for rule in self.children:
|
||||
if rule(state):
|
||||
if count == 1:
|
||||
return True
|
||||
count -= 1
|
||||
return False
|
||||
|
||||
@override
|
||||
def explain_json(self, state: CollectionState | None = None) -> list[JSONMessagePart]:
|
||||
messages: list[JSONMessagePart] = []
|
||||
if state is None:
|
||||
messages = [
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": str(self.count)},
|
||||
{"type": "text", "text": " of ("},
|
||||
]
|
||||
else:
|
||||
satisfied_count = sum(1 if child(state) else 0 for child in self.children)
|
||||
messages = [
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": f"{satisfied_count}/{self.count}"},
|
||||
{"type": "text", "text": " of ("},
|
||||
]
|
||||
for i, child in enumerate(self.children):
|
||||
if i > 0:
|
||||
messages.append({"type": "text", "text": ", "})
|
||||
messages.extend(child.explain_json(state))
|
||||
messages.append({"type": "text", "text": ")"})
|
||||
return messages
|
||||
|
||||
@override
|
||||
def explain_str(self, state: CollectionState | None = None) -> str:
|
||||
clauses = ", ".join([c.explain_str(state) for c in self.children])
|
||||
if state is None:
|
||||
return f"At least {self.count} of ({clauses})"
|
||||
satisfied_count = sum(1 if child(state) else 0 for child in self.children)
|
||||
return f"At least {satisfied_count}/{self.count} of ({clauses})"
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
clauses = ", ".join([str(c) for c in self.children])
|
||||
return f"At least {self.count} of ({clauses})"
|
||||
|
||||
|
||||
@dataclasses.dataclass(init=False)
|
||||
class And(NestedRule[TWorld], game="Archipelago"):
|
||||
"""A rule that only returns true when all child rules evaluate as true"""
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
return And.from_resolved(world, [c.resolve(world) for c in self.children])
|
||||
|
||||
@classmethod
|
||||
def from_resolved(cls, world: TWorld, children_to_process: list[Rule.Resolved]) -> Rule.Resolved:
|
||||
children_to_process = [c.resolve(world) for c in self.children]
|
||||
clauses: list[Rule.Resolved] = []
|
||||
items: dict[str, int] = {}
|
||||
true_rule: Rule.Resolved | None = None
|
||||
@@ -647,10 +519,7 @@ class Or(NestedRule[TWorld], game="Archipelago"):
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
return Or.from_resolved(world, [c.resolve(world) for c in self.children])
|
||||
|
||||
@classmethod
|
||||
def from_resolved(cls, world: TWorld, children_to_process: list[Rule.Resolved]) -> Rule.Resolved:
|
||||
children_to_process = [c.resolve(world) for c in self.children]
|
||||
clauses: list[Rule.Resolved] = []
|
||||
items: dict[str, int] = {}
|
||||
|
||||
|
||||
+5
-9
@@ -98,8 +98,6 @@ class Group:
|
||||
self._changed = True
|
||||
attr = new
|
||||
# resolve the path immediately when accessing it
|
||||
if attr.exists():
|
||||
attr.__class__.validate(attr.resolve())
|
||||
return attr.__class__(attr.resolve())
|
||||
return attr
|
||||
|
||||
@@ -635,6 +633,10 @@ class ServerOptions(Group):
|
||||
class GeneratorOptions(Group):
|
||||
"""Options for Generation"""
|
||||
|
||||
class EnemizerPath(LocalFilePath):
|
||||
"""Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases"""
|
||||
is_exe = True
|
||||
|
||||
class PlayerFilesPath(OptionalUserFolderPath):
|
||||
"""Folder from which the player yaml files are pulled from"""
|
||||
# created on demand, so marked as optional
|
||||
@@ -642,12 +644,6 @@ class GeneratorOptions(Group):
|
||||
class Players(int):
|
||||
"""amount of players, 0 to infer from player files"""
|
||||
|
||||
class AllowQuantity(Bool):
|
||||
"""
|
||||
allow players to set an individual quantity for their yaml settings
|
||||
with 'false' any amounts from the players will be ignored and set to 1
|
||||
"""
|
||||
|
||||
class WeightsFilePath(str):
|
||||
"""
|
||||
general weights file, within the stated player_files_path location
|
||||
@@ -691,9 +687,9 @@ class GeneratorOptions(Group):
|
||||
start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations.
|
||||
"""
|
||||
|
||||
enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows
|
||||
player_files_path: PlayerFilesPath = PlayerFilesPath("Players")
|
||||
players: Players = Players(0)
|
||||
allow_quantity: AllowQuantity | bool = False
|
||||
weights_file_path: WeightsFilePath = WeightsFilePath("weights.yaml")
|
||||
meta_file_path: MetaFilePath = MetaFilePath("meta.yaml")
|
||||
spoiler: Spoiler = Spoiler(3)
|
||||
|
||||
@@ -201,7 +201,7 @@ if is_windows:
|
||||
icon=resolve_icon(c.icon),
|
||||
))
|
||||
|
||||
extra_data = ["LICENSE", "data", "SNI"]
|
||||
extra_data = ["LICENSE", "data", "EnemizerCLI", "SNI"]
|
||||
extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else []
|
||||
|
||||
|
||||
@@ -456,8 +456,9 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
for world_directory in folders_to_remove)
|
||||
else:
|
||||
# make sure extra programs are executable
|
||||
enemizer_exe = self.buildfolder / 'EnemizerCLI/EnemizerCLI.Core'
|
||||
sni_exe = self.buildfolder / 'SNI/sni'
|
||||
extra_exes = (sni_exe,)
|
||||
extra_exes = (enemizer_exe, sni_exe)
|
||||
for extra_exe in extra_exes:
|
||||
if extra_exe.is_file():
|
||||
extra_exe.chmod(0o755)
|
||||
|
||||
@@ -54,7 +54,7 @@ class TestImplemented(unittest.TestCase):
|
||||
|
||||
def test_no_failed_world_loads(self):
|
||||
if failed_world_loads:
|
||||
self.fail(f"The following worlds failed to load: {failed_world_loads.keys()}")
|
||||
self.fail(f"The following worlds failed to load: {failed_world_loads}")
|
||||
|
||||
def test_prefill_items(self):
|
||||
"""Test that every world can reach every location from allstate before pre_fill."""
|
||||
|
||||
@@ -12,7 +12,6 @@ from rule_builder.field_resolvers import FieldResolver, FromOption, FromWorldAtt
|
||||
from rule_builder.options import Operator, OptionFilter
|
||||
from rule_builder.rules import (
|
||||
And,
|
||||
AtLeast,
|
||||
CanReachEntrance,
|
||||
CanReachLocation,
|
||||
CanReachRegion,
|
||||
@@ -251,40 +250,6 @@ class CachedRuleBuilderTestCase(RuleBuilderTestCase):
|
||||
Or(HasAnyCount({"A": 1, "B": 2}), HasAnyCount({"A": 2, "B": 2})),
|
||||
HasAnyCount.Resolved((("A", 1), ("B", 2)), player=1),
|
||||
),
|
||||
(
|
||||
AtLeast(0, Has("A")),
|
||||
True_.Resolved(player=1),
|
||||
),
|
||||
(
|
||||
AtLeast(3, True_(), Has("A"), Has("B"), Has("C")),
|
||||
AtLeast.Resolved(
|
||||
(Has.Resolved("A", player=1), Has.Resolved("B", player=1), Has.Resolved("C", player=1)), 2, player=1
|
||||
),
|
||||
),
|
||||
(
|
||||
AtLeast(2, False_(), Has("A"), Has("B"), Has("C")),
|
||||
AtLeast.Resolved(
|
||||
(Has.Resolved("A", player=1), Has.Resolved("B", player=1), Has.Resolved("C", player=1)), 2, player=1
|
||||
),
|
||||
),
|
||||
(
|
||||
AtLeast(2, True_(), True_(), Has("A")),
|
||||
True_.Resolved(player=1),
|
||||
),
|
||||
(
|
||||
AtLeast(3, Has("A"), Has("B")),
|
||||
False_.Resolved(player=1),
|
||||
),
|
||||
(
|
||||
# This test will fail when Or(Rule, Rule) will be optimized to Rule
|
||||
AtLeast(1, Rule(), Rule()),
|
||||
Or.Resolved((Rule.Resolved(player=1), Rule.Resolved(player=1)), player=1),
|
||||
),
|
||||
(
|
||||
# This test will fail when And(Rule, Rule) will be optimized to Rule
|
||||
AtLeast(2, Rule(), Rule()),
|
||||
And.Resolved((Rule.Resolved(player=1), Rule.Resolved(player=1)), player=1),
|
||||
),
|
||||
)
|
||||
)
|
||||
class TestSimplify(RuleBuilderTestCase):
|
||||
@@ -451,15 +416,6 @@ class TestHashes(RuleBuilderTestCase):
|
||||
rule2 = HasAll("2", "2", "2", "1")
|
||||
self.assertEqual(hash(rule1.resolve(world)), hash(rule2.resolve(world)))
|
||||
|
||||
def test_hash_collision(self) -> None:
|
||||
multiworld = setup_solo_multiworld(self.world_cls, steps=("generate_early",), seed=0)
|
||||
world = multiworld.worlds[1]
|
||||
rule1 = Has("A", count=1).resolve(world)
|
||||
rule2 = Has("A", count=1 << 61).resolve(world)
|
||||
self.assertEqual(hash(rule1), hash(rule2))
|
||||
self.assertNotEqual(rule1, rule2)
|
||||
self.assertNotEqual(id(rule1), id(rule2))
|
||||
|
||||
|
||||
class TestCaching(CachedRuleBuilderTestCase):
|
||||
multiworld: MultiWorld # pyright: ignore[reportUninitializedInstanceVariable]
|
||||
@@ -666,24 +622,6 @@ class TestRules(RuleBuilderTestCase):
|
||||
self.state.remove(item)
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
|
||||
def test_at_least(self) -> None:
|
||||
# Has has to be relied on as True_ and False_ would be optimized out
|
||||
rule = AtLeast(2, Has("Item 1"), Has("Item 1"), Has("Item 2"), Has("Item 3"))
|
||||
resolved_rule = rule.resolve(self.world)
|
||||
self.world.register_rule_dependencies(resolved_rule)
|
||||
item1 = self.world.create_item("Item 1")
|
||||
item2 = self.world.create_item("Item 2")
|
||||
item3 = self.world.create_item("Item 3")
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
self.state.collect(item1)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
self.state.collect(item2)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
self.state.remove(item1)
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
self.state.collect(item3)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
|
||||
def test_has_all(self) -> None:
|
||||
rule = HasAll("Item 1", "Item 2")
|
||||
resolved_rule = rule.resolve(self.world)
|
||||
@@ -859,13 +797,8 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
OptionFilter(ChoiceOption, ChoiceOption.option_second, "ge"),
|
||||
],
|
||||
),
|
||||
AtLeast(
|
||||
FromWorldAttr("instance_data.at_least_requirement"),
|
||||
Has("i15", count=2),
|
||||
HasGroup("g2", count=3),
|
||||
),
|
||||
CanReachEntrance("e1"),
|
||||
HasGroupUnique("g3", count=5),
|
||||
HasGroupUnique("g2", count=5),
|
||||
)
|
||||
|
||||
rule_dict: ClassVar[dict[str, Any]] = {
|
||||
@@ -989,29 +922,6 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"rule": "AtLeast",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"count": {"resolver": "FromWorldAttr", "name": "instance_data.at_least_requirement"},
|
||||
"children": [
|
||||
{
|
||||
"rule": "Has",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {
|
||||
"item_name": "i15",
|
||||
"count": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"rule": "HasGroup",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {"item_name_group": "g2", "count": 3},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"rule": "CanReachEntrance",
|
||||
"options": [],
|
||||
@@ -1022,7 +932,7 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
"rule": "HasGroupUnique",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {"item_name_group": "g3", "count": 5},
|
||||
"args": {"item_name_group": "g2", "count": 5},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1054,15 +964,9 @@ class TestExplain(RuleBuilderTestCase):
|
||||
),
|
||||
player=1,
|
||||
),
|
||||
AtLeast.Resolved(
|
||||
children=(
|
||||
HasAllCounts.Resolved((("Item 6", 1), ("Item 7", 5)), player=1),
|
||||
HasAnyCount.Resolved((("Item 8", 2), ("Item 9", 3)), player=1),
|
||||
HasFromList.Resolved(("Item 10", "Item 11", "Item 12"), count=2, player=1),
|
||||
),
|
||||
count=2,
|
||||
player=1,
|
||||
),
|
||||
HasAllCounts.Resolved((("Item 6", 1), ("Item 7", 5)), player=1),
|
||||
HasAnyCount.Resolved((("Item 8", 2), ("Item 9", 3)), player=1),
|
||||
HasFromList.Resolved(("Item 10", "Item 11", "Item 12"), count=2, player=1),
|
||||
HasFromListUnique.Resolved(("Item 13", "Item 14"), player=1),
|
||||
HasGroup.Resolved("Group 1", ("Item 15", "Item 16", "Item 17"), player=1),
|
||||
HasGroupUnique.Resolved("Group 2", ("Item 18", "Item 19"), count=2, player=1),
|
||||
@@ -1127,9 +1031,6 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": "0/2"},
|
||||
{"type": "text", "text": " of ("},
|
||||
{"type": "text", "text": "Missing "},
|
||||
{"type": "color", "color": "cyan", "text": "some"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1140,7 +1041,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "Item 7"},
|
||||
{"type": "text", "text": " x5"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Missing "},
|
||||
{"type": "color", "color": "cyan", "text": "all"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1151,7 +1052,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "Item 9"},
|
||||
{"type": "text", "text": " x3"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "salmon", "text": "0/2"},
|
||||
{"type": "text", "text": " items from ("},
|
||||
@@ -1162,7 +1063,6 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "color", "color": "salmon", "text": "Item 12"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "salmon", "text": "0/1"},
|
||||
@@ -1229,9 +1129,6 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": "3/2"},
|
||||
{"type": "text", "text": " of ("},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "all"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1242,7 +1139,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "green", "text": "Item 7"},
|
||||
{"type": "text", "text": " x5"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "some"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1253,7 +1150,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "green", "text": "Item 9"},
|
||||
{"type": "text", "text": " x3"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "green", "text": "30/2"},
|
||||
{"type": "text", "text": " items from ("},
|
||||
@@ -1264,7 +1161,6 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "color", "color": "green", "text": "Item 12"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "green", "text": "2/1"},
|
||||
@@ -1299,7 +1195,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "False"},
|
||||
{"type": "text", "text": ")"},
|
||||
]
|
||||
self.assertEqual(self.resolved_rule.explain_json(self.state), expected)
|
||||
assert self.resolved_rule.explain_json(self.state) == expected
|
||||
|
||||
def test_explain_json_without_state(self) -> None:
|
||||
expected: list[JSONMessagePart] = [
|
||||
@@ -1327,9 +1223,6 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": "2"},
|
||||
{"type": "text", "text": " of ("},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "all"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1339,7 +1232,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "item_name", "flags": 1, "text": "Item 7", "player": 1},
|
||||
{"type": "text", "text": " x5"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "any"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1349,7 +1242,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "item_name", "flags": 1, "text": "Item 9", "player": 1},
|
||||
{"type": "text", "text": " x3"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "2"},
|
||||
{"type": "text", "text": "x items from ("},
|
||||
@@ -1359,7 +1252,6 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "item_name", "flags": 1, "text": "Item 12", "player": 1},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "1"},
|
||||
@@ -1393,16 +1285,16 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "False"},
|
||||
{"type": "text", "text": ")"},
|
||||
]
|
||||
self.assertEqual(self.resolved_rule.explain_json(), expected)
|
||||
assert self.resolved_rule.explain_json() == expected
|
||||
|
||||
def test_explain_str_with_state_no_items(self) -> None:
|
||||
expected = (
|
||||
"((Missing 4x Item 1",
|
||||
"| Missing some of (Missing: Item 2, Item 3)",
|
||||
"| Missing all of (Missing: Item 4, Item 5))",
|
||||
"& At least 0/2 of (Missing some of (Missing: Item 6 x1, Item 7 x5),",
|
||||
"Missing all of (Missing: Item 8 x2, Item 9 x3),",
|
||||
"Has 0/2 items from (Missing: Item 10, Item 11, Item 12))",
|
||||
"& Missing some of (Missing: Item 6 x1, Item 7 x5)",
|
||||
"& Missing all of (Missing: Item 8 x2, Item 9 x3)",
|
||||
"& Has 0/2 items from (Missing: Item 10, Item 11, Item 12)",
|
||||
"& Has 0/1 unique items from (Missing: Item 13, Item 14)",
|
||||
"& Has 0/1 items from Group 1",
|
||||
"& Has 0/2 unique items from Group 2",
|
||||
@@ -1412,7 +1304,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
self.assertEqual(self.resolved_rule.explain_str(self.state), " ".join(expected))
|
||||
assert self.resolved_rule.explain_str(self.state) == " ".join(expected)
|
||||
|
||||
def test_explain_str_with_state_all_items(self) -> None:
|
||||
self._collect_all()
|
||||
@@ -1421,9 +1313,9 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"((Has 4x Item 1",
|
||||
"| Has all of (Found: Item 2, Item 3)",
|
||||
"| Has some of (Found: Item 4, Item 5))",
|
||||
"& At least 3/2 of (Has all of (Found: Item 6 x1, Item 7 x5),",
|
||||
"Has some of (Found: Item 8 x2, Item 9 x3),",
|
||||
"Has 30/2 items from (Found: Item 10, Item 11, Item 12))",
|
||||
"& Has all of (Found: Item 6 x1, Item 7 x5)",
|
||||
"& Has some of (Found: Item 8 x2, Item 9 x3)",
|
||||
"& Has 30/2 items from (Found: Item 10, Item 11, Item 12)",
|
||||
"& Has 2/1 unique items from (Found: Item 13, Item 14)",
|
||||
"& Has 30/1 items from Group 1",
|
||||
"& Has 2/2 unique items from Group 2",
|
||||
@@ -1433,16 +1325,16 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
self.assertEqual(self.resolved_rule.explain_str(self.state), " ".join(expected))
|
||||
assert self.resolved_rule.explain_str(self.state) == " ".join(expected)
|
||||
|
||||
def test_explain_str_without_state(self) -> None:
|
||||
expected = (
|
||||
"((Has 4x Item 1",
|
||||
"| Has all of (Item 2, Item 3)",
|
||||
"| Has any of (Item 4, Item 5))",
|
||||
"& At least 2 of (Has all of (Item 6 x1, Item 7 x5),",
|
||||
"Has any of (Item 8 x2, Item 9 x3),",
|
||||
"Has 2x items from (Item 10, Item 11, Item 12))",
|
||||
"& Has all of (Item 6 x1, Item 7 x5)",
|
||||
"& Has any of (Item 8 x2, Item 9 x3)",
|
||||
"& Has 2x items from (Item 10, Item 11, Item 12)",
|
||||
"& Has a unique item from (Item 13, Item 14)",
|
||||
"& Has an item from Group 1",
|
||||
"& Has 2x unique items from Group 2",
|
||||
@@ -1452,16 +1344,16 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
self.assertEqual(self.resolved_rule.explain_str(), " ".join(expected))
|
||||
assert self.resolved_rule.explain_str() == " ".join(expected)
|
||||
|
||||
def test_str(self) -> None:
|
||||
expected = (
|
||||
"((Has 4x Item 1",
|
||||
"| Has all of (Item 2, Item 3)",
|
||||
"| Has any of (Item 4, Item 5))",
|
||||
"& At least 2 of (Has all of (Item 6 x1, Item 7 x5),",
|
||||
"Has any of (Item 8 x2, Item 9 x3),",
|
||||
"Has 2x items from (Item 10, Item 11, Item 12))",
|
||||
"& Has all of (Item 6 x1, Item 7 x5)",
|
||||
"& Has any of (Item 8 x2, Item 9 x3)",
|
||||
"& Has 2x items from (Item 10, Item 11, Item 12)",
|
||||
"& Has a unique item from (Item 13, Item 14)",
|
||||
"& Has an item from Group 1",
|
||||
"& Has 2x unique items from Group 2",
|
||||
@@ -1471,7 +1363,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
self.assertEqual(str(self.resolved_rule), " ".join(expected))
|
||||
assert str(self.resolved_rule) == " ".join(expected)
|
||||
|
||||
|
||||
@classvar_matrix(
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
"""Verify that NetUtils' enums work correctly with all supported Python versions."""
|
||||
|
||||
import pickle
|
||||
import unittest
|
||||
from enum import Enum
|
||||
from typing import Type
|
||||
|
||||
from NetUtils import ClientStatus, HintStatus, SlotType
|
||||
from Utils import restricted_loads
|
||||
|
||||
|
||||
class Base:
|
||||
class DataEnumTest(unittest.TestCase):
|
||||
type: Type[Enum]
|
||||
value: Enum
|
||||
|
||||
def test_unpickle(self) -> None:
|
||||
"""Tests that enums used in multidata or multisave can be pickled and unpickled."""
|
||||
pickled = pickle.dumps(self.value)
|
||||
unpickled = restricted_loads(pickled)
|
||||
self.assertEqual(unpickled, self.value)
|
||||
self.assertIsInstance(unpickled, self.type)
|
||||
|
||||
|
||||
class HintStatusTest(Base.DataEnumTest):
|
||||
type = HintStatus
|
||||
value = HintStatus.HINT_AVOID
|
||||
|
||||
|
||||
class ClientStatusTest(Base.DataEnumTest):
|
||||
type = ClientStatus
|
||||
value = ClientStatus.CLIENT_GOAL
|
||||
|
||||
|
||||
class SlotTypeTest(Base.DataEnumTest):
|
||||
type = SlotType
|
||||
value = SlotType.player
|
||||
@@ -1,8 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from Options import Choice, DefaultOnToggle, Toggle, OptionDict, OptionError, OptionSet, OptionList, OptionCounter
|
||||
from Options import Choice, DefaultOnToggle, Toggle
|
||||
|
||||
|
||||
class TestNumericOptions(unittest.TestCase):
|
||||
@@ -76,97 +74,3 @@ class TestNumericOptions(unittest.TestCase):
|
||||
self.assertTrue(toggle_string)
|
||||
self.assertTrue(toggle_int)
|
||||
self.assertTrue(toggle_alias)
|
||||
|
||||
|
||||
class TestContainerOptions(unittest.TestCase):
|
||||
def test_option_dict(self):
|
||||
class TestOptionDict(OptionDict):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_dict = {"D": "Foo"}
|
||||
test_option_dict = TestOptionDict(unknown_key_init_dict)
|
||||
self.assertRaises(OptionError, test_option_dict.verify_keys)
|
||||
|
||||
init_dict = {"A": "foo", "B": "bar"}
|
||||
test_option_dict = TestOptionDict(init_dict)
|
||||
|
||||
self.assertEqual(test_option_dict, init_dict) # Implicit value comparison
|
||||
self.assertEqual(test_option_dict["A"], "foo")
|
||||
self.assertIn("B", test_option_dict)
|
||||
self.assertNotIn("C", test_option_dict)
|
||||
self.assertRaises(KeyError, lambda: test_option_dict["C"])
|
||||
|
||||
def test_option_set(self):
|
||||
class TestOptionSet(OptionSet):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_set = {"D"}
|
||||
test_option_set = TestOptionSet(unknown_key_init_set)
|
||||
self.assertRaises(OptionError, test_option_set.verify_keys)
|
||||
|
||||
init_set = {"A", "B"}
|
||||
test_option_set = TestOptionSet(init_set)
|
||||
|
||||
self.assertEqual(test_option_set, init_set) # Implicit value comparison
|
||||
self.assertIn("B", test_option_set)
|
||||
self.assertNotIn("C", test_option_set)
|
||||
|
||||
def test_option_list(self):
|
||||
class TestOptionList(OptionList):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_list = ["D"]
|
||||
test_option_list = TestOptionList(unknown_key_init_list)
|
||||
self.assertRaises(OptionError, test_option_list.verify_keys)
|
||||
|
||||
init_list = ["A", "B"]
|
||||
test_option_list = TestOptionList(init_list)
|
||||
|
||||
self.assertEqual(test_option_list, init_list)
|
||||
self.assertIn("B", test_option_list)
|
||||
self.assertNotIn("C", test_option_list)
|
||||
|
||||
|
||||
def test_option_counter(self):
|
||||
class TestOptionCounter(OptionCounter):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
max = 10
|
||||
min = 0
|
||||
|
||||
unknown_key_init_dict = {"D": 5}
|
||||
test_option_counter = TestOptionCounter(unknown_key_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_keys)
|
||||
|
||||
wrong_value_type_init_dict = {"A": "B"}
|
||||
self.assertRaises(TypeError, TestOptionCounter, wrong_value_type_init_dict)
|
||||
|
||||
violates_max_init_dict = {"A": 5, "B": 11}
|
||||
test_option_counter = TestOptionCounter(violates_max_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_values)
|
||||
|
||||
violates_min_init_dict = {"A": -1, "B": 5}
|
||||
test_option_counter = TestOptionCounter(violates_min_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_values)
|
||||
|
||||
init_dict = {"A": 0, "B": 10}
|
||||
test_option_counter = TestOptionCounter(init_dict)
|
||||
self.assertEqual(test_option_counter, Counter(init_dict))
|
||||
self.assertIn("A", test_option_counter)
|
||||
self.assertNotIn("C", test_option_counter)
|
||||
self.assertEqual(test_option_counter["A"], 0)
|
||||
self.assertEqual(test_option_counter["B"], 10)
|
||||
self.assertEqual(test_option_counter["C"], 0)
|
||||
|
||||
def test_culling_option_counter(self):
|
||||
class TestCullingCounter(OptionCounter):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
cull_zeroes = True
|
||||
|
||||
init_dict = {"A": 0, "B": 10}
|
||||
test_option_counter = TestCullingCounter(init_dict)
|
||||
self.assertNotIn("A", test_option_counter)
|
||||
self.assertIn("B", test_option_counter)
|
||||
self.assertNotIn("C", test_option_counter)
|
||||
self.assertEqual(test_option_counter["A"], 0) # It's still a Counter! cull_zeroes is about "in" checks.
|
||||
self.assertEqual(test_option_counter, Counter({"B": 10}))
|
||||
|
||||
@@ -33,9 +33,4 @@ class TestBase(unittest.TestCase):
|
||||
cls.app = raw_app
|
||||
|
||||
def setUp(self) -> None:
|
||||
from WebHostLib.models import db
|
||||
from pony.orm import db_session
|
||||
with db_session:
|
||||
for entity in db.entities.values():
|
||||
entity.select().delete(bulk=True)
|
||||
self.client = self.app.test_client()
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
from datetime import timedelta
|
||||
from uuid import UUID, uuid4
|
||||
from pony.orm import db_session, commit
|
||||
|
||||
from Utils import utcnow
|
||||
from WebHostLib.autolauncher import cleanup
|
||||
from WebHostLib.models import Room, Seed, Slot
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestCleanup(TestBase):
|
||||
def test_cleanup_unowned(self) -> None:
|
||||
with db_session:
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=UUID(int=0))
|
||||
Room(id=uuid4(), owner=UUID(int=0), seed=s1)
|
||||
|
||||
s2 = Seed(id=uuid4(), multidata=b"", owner=uuid4()) # Owned
|
||||
Room(id=uuid4(), owner=UUID(int=0), seed=s2) # Unowned room of owned seed
|
||||
|
||||
Seed(id=uuid4(), multidata=b"", owner=UUID(int=0)) # Unowned seed with no rooms
|
||||
|
||||
commit()
|
||||
|
||||
cleanup({"ROOM_AUTO_DELETE": 0})
|
||||
|
||||
with db_session:
|
||||
self.assertEqual(Room.select().count(), 0) # Both rooms were unowned
|
||||
self.assertEqual(Seed.select().count(), 1) # s2 is owned
|
||||
self.assertIsNotNone(Seed.get(id=s2.id))
|
||||
|
||||
def test_cleanup_auto_delete(self) -> None:
|
||||
now = utcnow()
|
||||
old_time = now - timedelta(days=10)
|
||||
recent_time = now - timedelta(days=2)
|
||||
|
||||
with db_session:
|
||||
# Case 1: Old room, owned
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r1 = Room(id=uuid4(), owner=uuid4(), seed=s1, last_activity=old_time)
|
||||
|
||||
# Case 2: Recent room, owned
|
||||
s2 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r2 = Room(id=uuid4(), owner=uuid4(), seed=s2, last_activity=recent_time)
|
||||
|
||||
# Case 3: Old seed, no rooms, owned
|
||||
s3 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
|
||||
# Case 4: Recent seed, no rooms, owned
|
||||
s4 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=recent_time)
|
||||
|
||||
# Case 5: Old seed with recent room (should not be deleted)
|
||||
s5 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r5 = Room(id=uuid4(), owner=uuid4(), seed=s5, last_activity=recent_time)
|
||||
|
||||
commit()
|
||||
|
||||
# Delete items older than 5 days
|
||||
cleanup({"ROOM_AUTO_DELETE": 5})
|
||||
|
||||
with db_session:
|
||||
self.assertIsNone(Room.get(id=r1.id), "Old room should be deleted")
|
||||
self.assertIsNotNone(Room.get(id=r2.id), "Recent room should NOT be deleted")
|
||||
self.assertIsNone(Seed.get(id=s3.id), "Old seed without rooms should be deleted")
|
||||
self.assertIsNotNone(Seed.get(id=s4.id), "Recent seed without rooms should NOT be deleted")
|
||||
self.assertIsNotNone(Seed.get(id=s5.id), "Old seed with recent room should NOT be deleted")
|
||||
self.assertIsNotNone(Room.get(id=r5.id), "Recent room for old seed should NOT be deleted")
|
||||
|
||||
# Seeds are deleted if they have NO rooms AND are old.
|
||||
# After r1 is deleted, s1 has no rooms. Since it's old, it should be deleted.
|
||||
self.assertIsNone(Seed.get(id=s1.id), "Old seed whose only room was deleted should be deleted")
|
||||
|
||||
def test_cleanup_disabled(self) -> None:
|
||||
now = utcnow()
|
||||
old_time = now - timedelta(days=10)
|
||||
|
||||
with db_session:
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r1 = Room(id=uuid4(), owner=uuid4(), seed=s1, last_activity=old_time)
|
||||
commit()
|
||||
|
||||
cleanup({"ROOM_AUTO_DELETE": 0})
|
||||
|
||||
with db_session:
|
||||
self.assertIsNotNone(Room.get(id=r1.id), "Room should NOT be deleted when auto-delete is 0")
|
||||
self.assertIsNotNone(Seed.get(id=s1.id), "Seed should NOT be deleted when auto-delete is 0")
|
||||
|
||||
def test_cleanup_slots(self) -> None:
|
||||
now = utcnow()
|
||||
old_time = now - timedelta(days=10)
|
||||
|
||||
with db_session:
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
slot1 = Slot(player_id=1, player_name="P1", seed=s1, game="TestGame")
|
||||
|
||||
s2 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=now)
|
||||
slot2 = Slot(player_id=2, player_name="P2", seed=s2, game="TestGame")
|
||||
|
||||
commit()
|
||||
|
||||
# Delete items older than 5 days
|
||||
cleanup({"ROOM_AUTO_DELETE": 5})
|
||||
|
||||
with db_session:
|
||||
self.assertIsNone(Seed.get(id=s1.id), "Old seed should be deleted")
|
||||
self.assertIsNone(Slot.get(id=slot1.id), "Slot of deleted seed should be deleted")
|
||||
self.assertIsNotNone(Seed.get(id=s2.id), "Recent seed should NOT be deleted")
|
||||
self.assertIsNotNone(Slot.get(id=slot2.id), "Slot of recent seed should NOT be deleted")
|
||||
@@ -1,76 +0,0 @@
|
||||
import math
|
||||
from typing import Any, Callable
|
||||
from typing_extensions import override
|
||||
from uuid import uuid4
|
||||
|
||||
from werkzeug.routing import BaseConverter
|
||||
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestSUUID(TestBase):
|
||||
converter: BaseConverter
|
||||
filter: Callable[[Any], str]
|
||||
|
||||
@override
|
||||
def setUp(self) -> None:
|
||||
from werkzeug.routing import Map
|
||||
|
||||
super().setUp()
|
||||
self.converter = self.app.url_map.converters["suuid"](Map())
|
||||
self.filter = self.app.jinja_env.filters["suuid"] # type: ignore # defines how we use it, not what it can be
|
||||
|
||||
def test_is_reversible(self) -> None:
|
||||
u = uuid4()
|
||||
self.assertEqual(u, self.converter.to_python(self.converter.to_url(u)))
|
||||
s = "A" * 22 # uuid with all zeros
|
||||
self.assertEqual(s, self.converter.to_url(self.converter.to_python(s)))
|
||||
|
||||
def test_uuid_length(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("AAAA")
|
||||
|
||||
def test_padding(self) -> None:
|
||||
self.converter.to_python("A" * 22) # check that the correct value works
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 22 + "==") # converter should not allow padding
|
||||
|
||||
def test_empty(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("")
|
||||
|
||||
def test_stray_equal_signs(self) -> None:
|
||||
self.converter.to_python("A" * 22) # check that the correct value works
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 22 + "==" + "AA") # the "==AA" should not be ignored, but error out
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 20 + "==" + "AA") # the final "A"s should not be appended to the first "A"s
|
||||
|
||||
def test_stray_whitespace(self) -> None:
|
||||
s = "A" * 22
|
||||
self.converter.to_python(s) # check that the correct value works
|
||||
for char in " \t\r\n\v":
|
||||
for pos in (0, 11, 22):
|
||||
with self.subTest(char=char, pos=pos):
|
||||
s_with_whitespace = s[0:pos] + char * 4 + s[pos:] # insert 4 to make padding correct
|
||||
# check that the constructed s_with_whitespace is correct
|
||||
self.assertEqual(len(s_with_whitespace), len(s) + 4)
|
||||
self.assertEqual(s_with_whitespace[pos], char)
|
||||
# s_with_whitespace should be invalid as SUUID
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python(s_with_whitespace)
|
||||
|
||||
def test_filter_returns_valid_string(self) -> None:
|
||||
u = uuid4()
|
||||
s = self.filter(u)
|
||||
self.assertIsInstance(s, str)
|
||||
self.assertNotIn("=", s)
|
||||
self.assertEqual(len(s), math.ceil(len(u.bytes) * 4 / 3))
|
||||
|
||||
def test_filter_is_same_as_converter(self) -> None:
|
||||
u = uuid4()
|
||||
self.assertEqual(self.filter(u), self.converter.to_url(u))
|
||||
|
||||
def test_filter_bad_type(self) -> None:
|
||||
with self.assertRaises(Exception): # currently the type is not checked directly, so any exception is valid
|
||||
self.filter(None)
|
||||
+1
-5
@@ -353,8 +353,6 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""path it was loaded from"""
|
||||
world_version: ClassVar[Version] = Version(0, 0, 0)
|
||||
"""Optional world version loaded from archipelago.json"""
|
||||
manifest: ClassVar[dict[str, Any]] = {}
|
||||
"""Mapping of the world's archipelago.json manifest. Use game and world_version attrs instead for those values."""
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
assert multiworld is not None
|
||||
@@ -514,9 +512,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
"""
|
||||
If core AP removes an item from your item pool, this method is called to choose a replacement item
|
||||
so item count and location count remain equal.
|
||||
For example: plando, item_links and start_inventory_from_pool are features that may cause this.
|
||||
Called when the item pool needs to be filled with additional items to match location count.
|
||||
|
||||
Any returned item name must be for a "repeatable" item, i.e. one that it's okay to generate arbitrarily many of.
|
||||
For most worlds this will be one or more of your filler items, but the classification of these items
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .Items import pokemon_stadium_items, gym_badge_codes, box_upgrade_items, cup_tier_upgrade_items
|
||||
from .Locations import pokemon_stadium_locations, event_locations
|
||||
from NetUtils import ClientStatus
|
||||
from .Types import LocData
|
||||
import Utils
|
||||
import worlds._bizhawk as bizhawk
|
||||
from worlds._bizhawk.client import BizHawkClient
|
||||
|
||||
logger = logging.getLogger('Client')
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from worlds._bizhawk.context import BizHawkClientContext
|
||||
|
||||
class PokemonStadiumClient(BizHawkClient):
|
||||
game = 'Pokemon Stadium'
|
||||
system = 'N64'
|
||||
patch_suffix = '.apstadium'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.local_checked_locations = set()
|
||||
self.glc_loaded = False
|
||||
self.cups_loaded = False
|
||||
self.minigame_index = None
|
||||
self.minigame_done = False
|
||||
self.minigame_check_sent = False
|
||||
|
||||
async def validate_rom(self, ctx: 'BizHawkClientContext') -> bool:
|
||||
try:
|
||||
# Check ROM name
|
||||
rom_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0x20, 15, 'ROM')]))[0]).decode('ascii')
|
||||
if rom_name != 'POKEMON STADIUM':
|
||||
logger.info('Invalid ROM for Pokemon Stadium AP World')
|
||||
return False
|
||||
except bizhawk.RequestFailedError:
|
||||
return False
|
||||
|
||||
ctx.game = self.game
|
||||
ctx.items_handling = 0b111
|
||||
ctx.want_slot_data = True
|
||||
|
||||
return True
|
||||
|
||||
async def game_watcher(self, ctx: 'BizHawkClientContext') -> None:
|
||||
item_codes = {net_item.item for net_item in ctx.items_received}
|
||||
|
||||
flags = await bizhawk.read(ctx.bizhawk_ctx, [
|
||||
(0x420000, 4, 'RDRAM'), # GLC Flag
|
||||
(0x420010, 4, 'RDRAM'), # Entered Battle Flag
|
||||
(0x148AC8, 12, 'RDRAM'), # Beat Rival Flag
|
||||
(0x12FC1C, 4, 'RDRAM'), # Minigame being played
|
||||
(0x124860, 4, 'RDRAM'), # Minigame results
|
||||
(0xAE77F, 1, 'RDRAM'), # Enemy team HP slot 1
|
||||
(0xAE7D3, 1, 'RDRAM'), # Enemy team HP slot 2
|
||||
(0xAE827, 1, 'RDRAM'), # Enemy team HP slot 3
|
||||
(0x220C19, 3, 'RDRAM'), # GLC Rentals address
|
||||
(0x221D99, 3, 'RDRAM'), # GLC Registration table address
|
||||
(0x218CE9, 3, 'RDRAM'), # Poke Cup Rentals address
|
||||
(0x219E69, 3, 'RDRAM'), # Poke Cup Registration table address
|
||||
(0x218CB9, 3, 'RDRAM'), # Prime Cup Rentals address
|
||||
(0x219E39, 3, 'RDRAM'), # Prime Cup Registration table address
|
||||
(0x218C99, 3, 'RDRAM'), # Petit Cup Rentals address
|
||||
(0x219E19, 3, 'RDRAM'), # Petit Cup Registration table address
|
||||
(0x218CA9, 3, 'RDRAM'), # Pika Cup Rentals address
|
||||
(0x219E29, 3, 'RDRAM'), # Pika Cup Registration table address
|
||||
(0x420020, 4, 'RDRAM'), # Picking a Cup tier
|
||||
]
|
||||
)
|
||||
|
||||
player_has_battled = flags[1] != b'\x00\x00\x00\x00'
|
||||
battle_info = await bizhawk.read(ctx.bizhawk_ctx, [(0x0AE540, 4, 'RDRAM')])
|
||||
mode = int(battle_info[0].hex()[:2])
|
||||
gym_info = battle_info[0].hex()[4:]
|
||||
gym_number = int(battle_info[0].hex()[4:6])
|
||||
trainer_index = int(battle_info[0].hex()[6:])
|
||||
|
||||
if player_has_battled:
|
||||
player_won = all(x == b'\x00' for x in flags[5:8])
|
||||
|
||||
if player_won:
|
||||
ap_code = 20000000 + (mode * 100) + (gym_number * 10) + trainer_index
|
||||
|
||||
# If a Gym Leader was beaten or the last trainer for a Cup was beaten an additional check must be sent
|
||||
if mode == 7 and trainer_index == 4:
|
||||
locations_to_check = set([ap_code, ap_code + 1])
|
||||
elif trainer_index == 8:
|
||||
locations_to_check = set([ap_code, ap_code - trainer_index, ap_code + 1])
|
||||
else:
|
||||
locations_to_check = set([ap_code])
|
||||
|
||||
try:
|
||||
await ctx.check_locations(locations_to_check)
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x420010, [0x00, 0x00, 0x00, 0x00], 'RDRAM')])
|
||||
self.glc_loaded = False
|
||||
except:
|
||||
pass
|
||||
|
||||
glc_flag = int.from_bytes(flags[0], byteorder='big')
|
||||
if glc_flag == 2 and not self.glc_loaded:
|
||||
self.glc_loaded = True
|
||||
|
||||
self.GLC_UNLOCK_FLAGS = [
|
||||
0x147B70, # Pewter
|
||||
0x147B98, # Cerulean
|
||||
0x147BC0, # Vermilion
|
||||
0x147BE8, # Celadon
|
||||
0x147C10, # Fuchsia
|
||||
0x147C38, # Saffron
|
||||
0x147C60, # Cinnabar
|
||||
0x147C88, # Viridian
|
||||
0x147CB1, # E4 entrance
|
||||
0x147CD9, # E4 exit
|
||||
0x147D01, # E4
|
||||
]
|
||||
|
||||
# UUDDLLRR
|
||||
self.GLC_CURSOR_TARGETS = [
|
||||
0x147B84, # Brock, 00000002
|
||||
0x147BAC, # Misty, 03000103
|
||||
0x147BD4, # Surge, 04020200
|
||||
0x147BFC, # Erika, 05030500
|
||||
0x147C24, # Koga, 06040604
|
||||
0x147C4C, # Sabrina, 07050007
|
||||
0x147C74, # Blaine, 00080608
|
||||
0x147C9C, # Giovanni, 07000709
|
||||
]
|
||||
|
||||
gym_codes = [
|
||||
pokemon_stadium_items['Pewter City Key'].ap_code,
|
||||
pokemon_stadium_items['Cerulean City Key'].ap_code,
|
||||
pokemon_stadium_items['Vermillion City Key'].ap_code,
|
||||
pokemon_stadium_items['Celadon City Key'].ap_code,
|
||||
pokemon_stadium_items['Fuchsia City Key'].ap_code,
|
||||
pokemon_stadium_items['Saffron City Key'].ap_code,
|
||||
pokemon_stadium_items['Cinnabar Island Key'].ap_code,
|
||||
pokemon_stadium_items['Viridian City Key'].ap_code,
|
||||
]
|
||||
|
||||
self.unlocked_gyms = [i + 1 for i, code in enumerate(gym_codes) if code in item_codes]
|
||||
victory_road_open = set(gym_badge_codes).issubset(item_codes)
|
||||
if victory_road_open:
|
||||
self.unlocked_gyms.append(9)
|
||||
|
||||
if gym_codes[0] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[0], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_brock_cursor(ctx)
|
||||
|
||||
if gym_codes[1] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[1], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_misty_cursor(ctx)
|
||||
|
||||
if gym_codes[2] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[2], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_surge_cursor(ctx)
|
||||
|
||||
if gym_codes[3] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[3], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_erika_cursor(ctx)
|
||||
|
||||
if gym_codes[4] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[4], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_koga_cursor(ctx)
|
||||
|
||||
if gym_codes[5] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[5], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_sabrina_cursor(ctx)
|
||||
|
||||
if gym_codes[6] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[6], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_blaine_cursor(ctx)
|
||||
|
||||
if gym_codes[7] in item_codes:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[7], [0x00, 0x01], 'RDRAM')])
|
||||
await self.update_giovanni_cursor(ctx, item_codes)
|
||||
|
||||
if victory_road_open:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[8], [0x01], 'RDRAM')])
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[9], [0x01], 'RDRAM')])
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_UNLOCK_FLAGS[10], [0x01], 'RDRAM')])
|
||||
|
||||
if len(self.unlocked_gyms) > 0 and gym_info != '0804':
|
||||
first_gym = self.unlocked_gyms[0] - 1
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x147D50, [0x00, first_gym], 'RDRAM')])
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x146F38, [0x52, 0x61, 0xFF, 0x82], 'RDRAM')])
|
||||
elif glc_flag != 2 and self.glc_loaded:
|
||||
self.glc_loaded = False
|
||||
|
||||
text = flags[2].decode("ascii", errors="ignore")
|
||||
if text == 'Magnificent!':
|
||||
await ctx.check_locations(set([event_locations['Beat Rival'].ap_code]))
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x420010, [0x00, 0x00, 0x00, 0x00], 'RDRAM')])
|
||||
|
||||
cups_flag = int.from_bytes(flags[18], byteorder='big')
|
||||
if cups_flag != 0 and not self.cups_loaded:
|
||||
self.cups_loaded = True
|
||||
|
||||
if mode == 3:
|
||||
cup_tier_item = cup_tier_upgrade_items['Poké Cup - Tier Upgrade'].ap_code
|
||||
else:
|
||||
cup_tier_item = cup_tier_upgrade_items['Prime Cup - Tier Upgrade'].ap_code
|
||||
|
||||
cup_tier = sum(1 for net_item in ctx.items_received if net_item.item == cup_tier_item)
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x147018, [0x00, 0x00, 0x00, cup_tier], 'RDRAM')])
|
||||
elif cups_flag == 0:
|
||||
self.cups_loaded = False
|
||||
|
||||
# GLC Boxes
|
||||
selecting_team = flags[8] == b'\x22\x0E\x20'
|
||||
registering_team = flags[9] == b'\x22\x1F\xA0'
|
||||
if selecting_team or registering_team:
|
||||
address = 0x220E23 if selecting_team else 0x221FA3
|
||||
item = box_upgrade_items['GLC PC Box Upgrade'].ap_code
|
||||
box_count = sum(1 for net_item in ctx.items_received if net_item.item == item)
|
||||
table_size = 29 + 20 * box_count
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(address, [table_size], 'RDRAM')])
|
||||
|
||||
# Poke Boxes
|
||||
selecting_team = flags[10] == b'\x21\x8F\x10'
|
||||
registering_team = flags[11] == b'\x21\xA0\x90'
|
||||
if selecting_team or registering_team:
|
||||
address = 0x218F13 if selecting_team else 0x21A093
|
||||
item = box_upgrade_items['Poke Cup PC Box Upgrade'].ap_code
|
||||
box_count = sum(1 for net_item in ctx.items_received if net_item.item == item)
|
||||
table_size = 29 + 20 * box_count
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(address, [table_size], 'RDRAM')])
|
||||
|
||||
# Prime Boxes
|
||||
selecting_team = flags[12] == b'\x21\x8F\x10'
|
||||
registering_team = flags[13] == b'\x21\xA0\x90'
|
||||
if selecting_team or registering_team:
|
||||
address = 0x218F13 if selecting_team else 0x21A093
|
||||
item = box_upgrade_items['Prime Cup PC Box Upgrade'].ap_code
|
||||
box_count = sum(1 for net_item in ctx.items_received if net_item.item == item)
|
||||
table_size = 29 + 20 * box_count
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(address, [table_size], 'RDRAM')])
|
||||
|
||||
# Minigames
|
||||
if flags[3].startswith(b'\x00\x03\x00') and flags[3][3] in range(9):
|
||||
self.minigame_index = flags[3][3]
|
||||
|
||||
if self.minigame_index != None and flags[4] == b'\x00\x00\x00\x00':
|
||||
self.minigame_done = False
|
||||
|
||||
if self.minigame_index != None and not self.minigame_done and flags[4] == b'\x01\x00\x00\x00':
|
||||
self.minigame_done = True
|
||||
self.minigame_check_sent = False
|
||||
|
||||
if self.minigame_done and self.minigame_index != None and not self.minigame_check_sent:
|
||||
minigame_ap_acode = 20000100 + self.minigame_index
|
||||
await ctx.check_locations([minigame_ap_acode])
|
||||
|
||||
self.minigame_check_sent = True
|
||||
|
||||
# Send game clear
|
||||
if not ctx.finished_game and pokemon_stadium_items['Victory'].ap_code in item_codes:
|
||||
ctx.finished_game = True
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "StatusUpdate",
|
||||
"status": ClientStatus.CLIENT_GOAL,
|
||||
}])
|
||||
|
||||
def lowest_unlocked_from(self, lower_bound):
|
||||
for i in range(lower_bound, 9):
|
||||
if i in self.unlocked_gyms:
|
||||
return i
|
||||
return 0
|
||||
|
||||
def highest_unlocked_from(self, upper_bound):
|
||||
for i in range(upper_bound, 0, -1):
|
||||
if i in self.unlocked_gyms:
|
||||
return i
|
||||
return 0
|
||||
|
||||
async def update_brock_cursor(self, ctx):
|
||||
# Determine UP: lowest unlocked gym from 4 to 9
|
||||
up = self.lowest_unlocked_from(4)
|
||||
|
||||
# Determine RIGHT: lowest of 2 or 3 or 4 if any are unlocked
|
||||
right = 0
|
||||
misty_unlocked = 2 in self.unlocked_gyms
|
||||
surge_unlocked = 3 in self.unlocked_gyms
|
||||
erika_unlocked = 4 in self.unlocked_gyms
|
||||
|
||||
if misty_unlocked:
|
||||
right = 2
|
||||
elif surge_unlocked:
|
||||
right = 3
|
||||
elif erika_unlocked:
|
||||
right = 4
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[0], [up, 0x00, 0x00, right], 'RDRAM')])
|
||||
|
||||
async def update_misty_cursor(self, ctx):
|
||||
# Determine UP: lowest unlocked gym from 4 to 9
|
||||
up = self.lowest_unlocked_from(4)
|
||||
|
||||
# Determine LEFT: is Brock unlocked
|
||||
left = 1 if 1 in self.unlocked_gyms else 0
|
||||
|
||||
# Determine RIGHT: is Surge unlocked
|
||||
right = 3 if 3 in self.unlocked_gyms else 0
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[1], [up, 0x00, left, right], 'RDRAM')])
|
||||
|
||||
async def update_surge_cursor(self, ctx):
|
||||
# Determine UP: lowest unlocked gym from 4 to 9
|
||||
up = self.lowest_unlocked_from(4)
|
||||
|
||||
# Determine DOWN: is Misty unlocked
|
||||
down = 2 if 2 in self.unlocked_gyms else 0
|
||||
|
||||
# Determine LEFT: is Misty or Brock unlocked
|
||||
left = 0
|
||||
misty_unlocked = 2 if 2 in self.unlocked_gyms else 0
|
||||
brock_unlocked = 1 if 1 in self.unlocked_gyms else 0
|
||||
|
||||
if misty_unlocked:
|
||||
left = 2
|
||||
elif brock_unlocked:
|
||||
left = 1
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[2], [up, down, left, 0x00], 'RDRAM')])
|
||||
|
||||
async def update_erika_cursor(self, ctx):
|
||||
# Determine UP: lowest unlocked gym from 5 to 9
|
||||
up = self.lowest_unlocked_from(5)
|
||||
|
||||
# Determine DOWN: highest unlocked gym from 3 to 1
|
||||
down = self.highest_unlocked_from(3)
|
||||
|
||||
# Determine LEFT: is Koga or Sabrina unlocked
|
||||
left = 0
|
||||
koga_unlocked = 5 if 5 in self.unlocked_gyms else 0
|
||||
sabrina_unlocked = 6 if 6 in self.unlocked_gyms else 0
|
||||
|
||||
if koga_unlocked:
|
||||
left = 5
|
||||
elif sabrina_unlocked:
|
||||
left = 6
|
||||
|
||||
# Determine RIGHT: is Surge unlocked
|
||||
right = 3 if 3 in self.unlocked_gyms else 0
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[3], [up, down, left, right], 'RDRAM')])
|
||||
|
||||
async def update_koga_cursor(self, ctx):
|
||||
# Determine UP: lowest unlocked gym from 6 to 9
|
||||
up = self.lowest_unlocked_from(6)
|
||||
|
||||
# Determine DOWN: highest unlocked gym from 2 to 1
|
||||
down = self.highest_unlocked_from(2)
|
||||
|
||||
# Determine LEFT: is Sabrina unlocked
|
||||
left = 6 if 6 in self.unlocked_gyms else 0
|
||||
|
||||
# Determine RIGHT: is Erika or Surge unlocked
|
||||
right = 0
|
||||
erika_unlocked = 4 if 4 in self.unlocked_gyms else 0
|
||||
surge_unlocked = 3 if 3 in self.unlocked_gyms else 0
|
||||
|
||||
if erika_unlocked:
|
||||
right = 4
|
||||
elif surge_unlocked:
|
||||
right = 3
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[4], [up, down, left, right], 'RDRAM')])
|
||||
|
||||
async def update_sabrina_cursor(self, ctx):
|
||||
# Determine DOWN: highest unlocked gym from 5 to 1
|
||||
down = self.highest_unlocked_from(5)
|
||||
|
||||
# Determine RIGHT: is Blaine or Giovanni unlocked
|
||||
right = 0
|
||||
blaine_unlocked = 7 if 7 in self.unlocked_gyms else 0
|
||||
giovanni_unlocked = 8 if 8 in self.unlocked_gyms else 0
|
||||
|
||||
if blaine_unlocked:
|
||||
right = 7
|
||||
elif giovanni_unlocked:
|
||||
right = 8
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[5], [0x00, down, 0x00, right], 'RDRAM')])
|
||||
|
||||
async def update_blaine_cursor(self, ctx):
|
||||
# Determine DOWN: highest unlocked gym from 5 to 1
|
||||
down = self.highest_unlocked_from(5)
|
||||
|
||||
# Determine LEFT: is Sabrina unlocked
|
||||
left = 6 if 6 in self.unlocked_gyms else 0
|
||||
|
||||
# Determine RIGHT: is Giovanni unlocked or do you have all badges needed
|
||||
if 8 in self.unlocked_gyms:
|
||||
right = 8
|
||||
elif 9 in self.unlocked_gyms:
|
||||
right = 9
|
||||
else:
|
||||
right = 0
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[6], [0x00, down, left, right], 'RDRAM')])
|
||||
|
||||
async def update_giovanni_cursor(self, ctx, item_codes):
|
||||
# Determine UP: All badges obtained?
|
||||
up = 9 if set(gym_badge_codes).issubset(item_codes) else 0
|
||||
|
||||
# Determine DOWN: highest unlocked gym from 5 to 1
|
||||
down = self.highest_unlocked_from(5)
|
||||
|
||||
# Determine LEFT: is Blaine or Sabrina unlocked
|
||||
left = 0
|
||||
blaine_unlocked = 7 if 7 in self.unlocked_gyms else 0
|
||||
sabrina_unlocked = 6 if 6 in self.unlocked_gyms else 0
|
||||
|
||||
if blaine_unlocked:
|
||||
left = 7
|
||||
elif sabrina_unlocked:
|
||||
left = 6
|
||||
|
||||
# Determine RIGHT: All badges obtained?
|
||||
right = up
|
||||
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(self.GLC_CURSOR_TARGETS[7], [up, down, left, right], 'RDRAM')])
|
||||
@@ -0,0 +1,132 @@
|
||||
import logging
|
||||
import random
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
from .Types import ItemData, PokemonStadiumItem
|
||||
from .Locations import get_total_locations
|
||||
from typing import List, Dict, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import PokemonStadiumWorld
|
||||
|
||||
def create_itempool(world: 'PokemonStadiumWorld') -> List[Item]:
|
||||
item_pool: List[Item] = []
|
||||
|
||||
# This is a good place to grab anything you need from options
|
||||
|
||||
for name in pokemon_stadium_items:
|
||||
if name != 'Victory' and name not in world.starting_gym_keys:
|
||||
item_pool.append(create_item(world, name))
|
||||
|
||||
victory = create_item(world, 'Victory')
|
||||
world.multiworld.get_location('Beat Rival', world.player).place_locked_item(victory)
|
||||
|
||||
item_pool += create_multiple_items(world, 'Poké Cup - Tier Upgrade', 3, ItemClassification.progression)
|
||||
item_pool += create_multiple_items(world, 'Prime Cup - Tier Upgrade', 3, ItemClassification.progression)
|
||||
|
||||
item_pool += create_multiple_items(world, 'GLC PC Box Upgrade', 6, ItemClassification.useful)
|
||||
item_pool += create_multiple_items(world, 'Poke Cup PC Box Upgrade', 6, ItemClassification.useful)
|
||||
item_pool += create_multiple_items(world, 'Prime Cup PC Box Upgrade', 6, ItemClassification.useful)
|
||||
|
||||
item_pool += create_junk_items(world, get_total_locations(world) - len(item_pool) - 1)
|
||||
|
||||
return item_pool
|
||||
|
||||
def create_item(world: 'PokemonStadiumWorld', name: str) -> Item:
|
||||
data = item_table[name]
|
||||
return PokemonStadiumItem(name, data.classification, data.ap_code, world.player)
|
||||
|
||||
def create_multiple_items(world: "PokemonStadiumWorld", name: str, count: int, item_type: ItemClassification = ItemClassification.progression) -> List[Item]:
|
||||
data = item_table[name]
|
||||
itemlist: List[Item] = []
|
||||
|
||||
for _ in range(count):
|
||||
itemlist += [PokemonStadiumItem(name, item_type, data.ap_code, world.player)]
|
||||
|
||||
return itemlist
|
||||
|
||||
def create_junk_items(world: 'PokemonStadiumWorld', count: int) -> List[Item]:
|
||||
junk_pool: List[Item] = []
|
||||
junk_list: Dict[str, int] = {}
|
||||
|
||||
for name in item_table.keys():
|
||||
ic = item_table[name].classification
|
||||
if ic == ItemClassification.filler:
|
||||
junk_list[name] = junk_weights.get(name)
|
||||
|
||||
for _ in range(count):
|
||||
junk_pool.append(world.create_item(world.random.choices(list(junk_list.keys()), weights=list(junk_list.values()), k=1)[0]))
|
||||
|
||||
return junk_pool
|
||||
|
||||
pokemon_stadium_items = {
|
||||
# Progression items
|
||||
'Pewter City Key': ItemData(10000001, ItemClassification.progression),
|
||||
'Boulder Badge': ItemData(10000002, ItemClassification.progression),
|
||||
'Cerulean City Key': ItemData(10000003, ItemClassification.progression),
|
||||
'Cascade Badge': ItemData(10000004, ItemClassification.progression),
|
||||
'Vermillion City Key': ItemData(10000005, ItemClassification.progression),
|
||||
'Thunder Badge': ItemData(10000006, ItemClassification.progression),
|
||||
'Celadon City Key': ItemData(10000007, ItemClassification.progression),
|
||||
'Rainbow Badge': ItemData(10000008, ItemClassification.progression),
|
||||
'Fuchsia City Key': ItemData(10000009, ItemClassification.progression),
|
||||
'Soul Badge': ItemData(10000010, ItemClassification.progression),
|
||||
'Saffron City Key': ItemData(10000011, ItemClassification.progression),
|
||||
'Marsh Badge': ItemData(10000012, ItemClassification.progression),
|
||||
'Cinnabar Island Key': ItemData(10000013, ItemClassification.progression),
|
||||
'Volcano Badge': ItemData(10000014, ItemClassification.progression),
|
||||
'Viridian City Key': ItemData(10000015, ItemClassification.progression),
|
||||
'Earth Badge': ItemData(10000016, ItemClassification.progression),
|
||||
|
||||
# Victory is added here since in this organization it needs to be in the default item pool
|
||||
'Victory': ItemData(10000000, ItemClassification.progression)
|
||||
}
|
||||
|
||||
gym_keys = [
|
||||
'Pewter City Key',
|
||||
'Cerulean City Key',
|
||||
'Vermillion City Key',
|
||||
'Celadon City Key',
|
||||
'Fuchsia City Key',
|
||||
'Saffron City Key',
|
||||
'Cinnabar Island Key',
|
||||
'Viridian City Key',
|
||||
]
|
||||
|
||||
gym_badge_codes = [
|
||||
10000002,
|
||||
10000004,
|
||||
10000006,
|
||||
10000008,
|
||||
10000010,
|
||||
10000012,
|
||||
10000014,
|
||||
10000016,
|
||||
]
|
||||
|
||||
cup_tier_upgrade_items = {
|
||||
'Poké Cup - Tier Upgrade': ItemData(10000017, ItemClassification.progression),
|
||||
'Prime Cup - Tier Upgrade': ItemData(10000018, ItemClassification.progression),
|
||||
}
|
||||
|
||||
box_upgrade_items = {
|
||||
'GLC PC Box Upgrade': ItemData(10000101, ItemClassification.useful),
|
||||
'Poke Cup PC Box Upgrade' : ItemData(10000102, ItemClassification.useful),
|
||||
'Prime Cup PC Box Upgrade' : ItemData(10000103, ItemClassification.useful),
|
||||
}
|
||||
|
||||
junk_items = {
|
||||
"Pokedoll": ItemData(10000200, ItemClassification.filler, 0),
|
||||
}
|
||||
|
||||
junk_weights = {
|
||||
"Pokedoll": 40,
|
||||
}
|
||||
|
||||
item_table = {
|
||||
**pokemon_stadium_items,
|
||||
**cup_tier_upgrade_items,
|
||||
**box_upgrade_items,
|
||||
**junk_items,
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
from typing import Dict, TYPE_CHECKING
|
||||
import logging
|
||||
|
||||
from .Types import LocData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import PokemonStadiumWorld
|
||||
|
||||
def get_total_locations(world: 'PokemonStadiumWorld') -> int:
|
||||
if world.options.Trainersanity.value == 1:
|
||||
location_table.update(trainersanity_locations)
|
||||
|
||||
return len(location_table)
|
||||
|
||||
def get_location_names() -> Dict[str, int]:
|
||||
temp_loc_table = location_table.copy()
|
||||
temp_loc_table.update(trainersanity_locations)
|
||||
|
||||
names = {name: data.ap_code for name, data in temp_loc_table.items()}
|
||||
|
||||
return names
|
||||
|
||||
def is_valid_location(world: 'PokemonStadiumWorld', name) -> bool:
|
||||
return True
|
||||
|
||||
pokemon_stadium_locations = {
|
||||
'Magikarp\'s Splash': LocData(20000100, 'Kids Club'),
|
||||
'Clefairy Says': LocData(20000101, 'Kids Club'),
|
||||
'Run, Rattata, Run': LocData(20000102, 'Kids Club'),
|
||||
'Snore War': LocData(20000103, 'Kids Club'),
|
||||
'Thundering Dynamo': LocData(20000104, 'Kids Club'),
|
||||
'Sushi-Go-Round': LocData(20000105, 'Kids Club'),
|
||||
'Ekans\'s Hoop Hurl': LocData(20000106, 'Kids Club'),
|
||||
'Rock Harden': LocData(20000107, 'Kids Club'),
|
||||
'Dig! Dig! Dig!': LocData(20000108, 'Kids Club'),
|
||||
|
||||
'Poké Cup - Poké Ball - Prize': LocData(20000300, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Tier Upgrade': LocData(20000309, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Prize': LocData(20000310, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Tier Upgrade': LocData(20000319, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Prize': LocData(20000320, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Tier Upgrade': LocData(20000329, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Prize': LocData(20000330, 'Poké Cup'),
|
||||
|
||||
'Petit Cup Prize': LocData(20000400, 'Petit Cup'),
|
||||
|
||||
'Pika Cup Prize': LocData(20000500, 'Pika Cup'),
|
||||
|
||||
'Prime Cup - Poké Ball - Prize': LocData(20000600, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Tier Upgrade': LocData(20000609, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Prize': LocData(20000610, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Tier Upgrade': LocData(20000619, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Prize': LocData(20000620, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Tier Upgrade': LocData(20000629, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Prize': LocData(20000630, 'Prime Cup'),
|
||||
|
||||
'BROCK': LocData(20000704, 'Gym Leader Castle'),
|
||||
'Pewter Gym': LocData(20000705, 'Gym Leader Castle'),
|
||||
'MISTY': LocData(20000714, 'Gym Leader Castle'),
|
||||
'Cerulean Gym': LocData(20000715, 'Gym Leader Castle'),
|
||||
'SURGE': LocData(20000724, 'Gym Leader Castle'),
|
||||
'Vermillion Gym': LocData(20000725, 'Gym Leader Castle'),
|
||||
'ERIKA': LocData(20000734, 'Gym Leader Castle'),
|
||||
'Celadon Gym': LocData(20000735, 'Gym Leader Castle'),
|
||||
'KOGA': LocData(20000744, 'Gym Leader Castle'),
|
||||
'Fuchsia Gym': LocData(20000745, 'Gym Leader Castle'),
|
||||
'SABRINA': LocData(20000754, 'Gym Leader Castle'),
|
||||
'Saffron Gym': LocData(20000755, 'Gym Leader Castle'),
|
||||
'BLAINE': LocData(20000764, 'Gym Leader Castle'),
|
||||
'Cinnabar Gym': LocData(20000765, 'Gym Leader Castle'),
|
||||
'GIOVANNI': LocData(20000774, 'Gym Leader Castle'),
|
||||
'Viridian Gym': LocData(20000775, 'Gym Leader Castle'),
|
||||
}
|
||||
|
||||
event_locations = {
|
||||
'Beat Rival': LocData(20000000, 'Hall of Fame')
|
||||
}
|
||||
|
||||
trainersanity_locations = {
|
||||
'Poké Cup - Poké Ball - Bug Boy': LocData(20000301, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Lad': LocData(20000302, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Nerd': LocData(20000303, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Sailor': LocData(20000304, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Jr(F)': LocData(20000305, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Jr(M)': LocData(20000306, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Lass': LocData(20000307, 'Poké Cup'),
|
||||
'Poké Cup - Poké Ball - Pokémaniac': LocData(20000308, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Bug Boy': LocData(20000311, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Lad': LocData(20000312, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Nerd': LocData(20000313, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Sailor': LocData(20000314, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Jr(F)': LocData(20000315, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Jr(M)': LocData(20000316, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Lass': LocData(20000317, 'Poké Cup'),
|
||||
'Poké Cup - Great Ball - Pokémaniac': LocData(20000318, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Bug Boy': LocData(20000321, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Lad': LocData(20000322, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Nerd': LocData(20000323, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Sailor': LocData(20000324, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Jr(F)': LocData(20000325, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Jr(M)': LocData(20000326, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Lass': LocData(20000327, 'Poké Cup'),
|
||||
'Poké Cup - Ultra Ball - Pokémaniac': LocData(20000328, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Bug Boy': LocData(20000331, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Lad': LocData(20000332, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Nerd': LocData(20000333, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Sailor': LocData(20000334, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Jr(F)': LocData(20000335, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Jr(M)': LocData(20000336, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Lass': LocData(20000337, 'Poké Cup'),
|
||||
'Poké Cup - Master Ball - Pokémaniac': LocData(20000338, 'Poké Cup'),
|
||||
|
||||
'Petit Cup - Bug Boy': LocData(20000401, 'Petit Cup'),
|
||||
'Petit Cup - Lad': LocData(20000402, 'Petit Cup'),
|
||||
'Petit Cup - Nerd': LocData(20000403, 'Petit Cup'),
|
||||
'Petit Cup - Sailor': LocData(20000404, 'Petit Cup'),
|
||||
'Petit Cup - Jr(F)': LocData(20000405, 'Petit Cup'),
|
||||
'Petit Cup - Jr(M)': LocData(20000406, 'Petit Cup'),
|
||||
'Petit Cup - Lass': LocData(20000407, 'Petit Cup'),
|
||||
'Petit Cup - Pokémaniac': LocData(20000408, 'Petit Cup'),
|
||||
|
||||
'Pika Cup - Bug Boy': LocData(20000501, 'Pika Cup'),
|
||||
'Pika Cup - Lad': LocData(20000502, 'Pika Cup'),
|
||||
'Pika Cup - Swimmer': LocData(20000503, 'Pika Cup'),
|
||||
'Pika Cup - Burglar': LocData(20000504, 'Pika Cup'),
|
||||
'Pika Cup - Mr. Fix': LocData(20000505, 'Pika Cup'),
|
||||
'Pika Cup - Hiker': LocData(20000506, 'Pika Cup'),
|
||||
'Pika Cup - Lass': LocData(20000507, 'Pika Cup'),
|
||||
'Pika Cup - Fisher': LocData(20000508, 'Pika Cup'),
|
||||
|
||||
'Prime Cup - Poké Ball - Cue Ball': LocData(20000601, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Rocket': LocData(20000602, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Judoboy': LocData(20000603, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Gambler': LocData(20000604, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Cool(F)': LocData(20000605, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Bird Boy': LocData(20000606, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Lab Man': LocData(20000607, 'Prime Cup'),
|
||||
'Prime Cup - Poké Ball - Cool(M)': LocData(20000608, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Cue Ball': LocData(20000611, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Rocket': LocData(20000612, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Judoboy': LocData(20000613, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Gambler': LocData(20000614, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Cool(F)': LocData(20000615, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Bird Boy': LocData(20000616, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Lab Man': LocData(20000617, 'Prime Cup'),
|
||||
'Prime Cup - Great Ball - Cool(M)': LocData(20000618, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Cue Ball': LocData(20000621, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Rocket': LocData(20000622, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Judoboy': LocData(20000623, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Gambler': LocData(20000624, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Cool(F)': LocData(20000625, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Bird Boy': LocData(20000626, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Lab Man': LocData(20000627, 'Prime Cup'),
|
||||
'Prime Cup - Ultra Ball - Cool(M)': LocData(20000628, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Cue Ball': LocData(20000631, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Rocket': LocData(20000632, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Judoboy': LocData(20000633, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Gambler': LocData(20000634, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Cool(F)': LocData(20000635, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Bird Boy': LocData(20000636, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Lab Man': LocData(20000637, 'Prime Cup'),
|
||||
'Prime Cup - Master Ball - Cool(M)': LocData(20000638, 'Prime Cup'),
|
||||
|
||||
'Pewter Gym - Bug Boy': LocData(20000701, 'Gym Leader Castle'),
|
||||
'Pewter Gym - Lad': LocData(20000702, 'Gym Leader Castle'),
|
||||
'Pewter Gym - Jr(M)': LocData(20000703, 'Gym Leader Castle'),
|
||||
'Cerulean Gym - Fisher': LocData(20000711, 'Gym Leader Castle'),
|
||||
'Cerulean Gym - Jr(F)': LocData(20000712, 'Gym Leader Castle'),
|
||||
'Cerulean Gym - Swimmer': LocData(20000713, 'Gym Leader Castle'),
|
||||
'Vermillion Gym - Sailor': LocData(20000721, 'Gym Leader Castle'),
|
||||
'Vermillion Gym - Rocker': LocData(20000722, 'Gym Leader Castle'),
|
||||
'Vermillion Gym - Old Man': LocData(20000723, 'Gym Leader Castle'),
|
||||
'Celadon Gym - Lass': LocData(20000731, 'Gym Leader Castle'),
|
||||
'Celadon Gym - Beauty': LocData(20000732, 'Gym Leader Castle'),
|
||||
'Celadon Gym - Cool(F)': LocData(20000733, 'Gym Leader Castle'),
|
||||
'Fuchsia Gym - Biker': LocData(20000741, 'Gym Leader Castle'),
|
||||
'Fuchsia Gym - Tamer': LocData(20000742, 'Gym Leader Castle'),
|
||||
'Fuchsia Gym - Juggler': LocData(20000743, 'Gym Leader Castle'),
|
||||
'Saffron Gym - Cue Ball': LocData(20000751, 'Gym Leader Castle'),
|
||||
'Saffron Gym - Burglar': LocData(20000752, 'Gym Leader Castle'),
|
||||
'Saffron Gym - Medium': LocData(20000753, 'Gym Leader Castle'),
|
||||
'Cinnabar Gym - Judoboy': LocData(20000761, 'Gym Leader Castle'),
|
||||
'Cinnabar Gym - Psychic': LocData(20000762, 'Gym Leader Castle'),
|
||||
'Cinnabar Gym - Nerd': LocData(20000763, 'Gym Leader Castle'),
|
||||
'Viridian Gym - Rocket': LocData(20000771, 'Gym Leader Castle'),
|
||||
'Viridian Gym - Lab Man': LocData(20000772, 'Gym Leader Castle'),
|
||||
'Viridian Gym - Cool(M)': LocData(20000773, 'Gym Leader Castle'),
|
||||
}
|
||||
|
||||
location_table = {
|
||||
**pokemon_stadium_locations,
|
||||
**event_locations
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
from typing import List, Dict, Any
|
||||
from dataclasses import dataclass
|
||||
from worlds.AutoWorld import PerGameCommonOptions
|
||||
from Options import Choice, OptionGroup, Toggle, Range
|
||||
|
||||
def create_option_groups() -> List[OptionGroup]:
|
||||
option_group_list: List[OptionGroup] = []
|
||||
for name, options in pokemon_stadium_option_groups.items():
|
||||
option_group_list.append(OptionGroup(name=name, options=options))
|
||||
|
||||
return option_group_list
|
||||
|
||||
class VictoryCondition(Choice):
|
||||
"""
|
||||
Choose victory condition
|
||||
"""
|
||||
display_name = "Victory Condition"
|
||||
option_defeat_rival = 1
|
||||
option_clear_master_ball_cup = 2
|
||||
default = 1
|
||||
|
||||
class BaseStatTotalRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for Pokemon BST. Stat distribution per Pokemon will follow a randomly selected distribution curve.
|
||||
The higher the selection, the more extreme a curve you may see used.
|
||||
Stat changes are universal. Rental Pokemon and enemy trainer team Pokemon use the same BSTs.
|
||||
Vanilla - No change
|
||||
Low - 3 distribution types
|
||||
Medium - 4 distribution types
|
||||
High - 5 distribution types
|
||||
"""
|
||||
display_name = "BST Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class Trainersanity(Toggle):
|
||||
"""
|
||||
Toggle on to make all Trainers into checks. This option is off by default.
|
||||
"""
|
||||
display_name = 'Trainersanity'
|
||||
option_off = 0
|
||||
option_on = 1
|
||||
default = 0
|
||||
|
||||
class GymCastleTrainerRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the enemy team and movesets in Gym Leader Castle.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Gym Castle Trainer Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PokeCupTrainerRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the enemy team and movesets in Poke Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Poke Cup Trainer Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PrimeCupTrainerRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the enemy team and movesets in Prime Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Prime Cup Trainer Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PetitCupTrainerRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the enemy team and movesets in Petit Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Petit Cup Trainer Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PikaCupTrainerRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the enemy team and movesets in Pika Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Pika Cup Trainer Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class GymCastleRentalRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the rental Pokemon moves in Gym Leader Castle.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Gym Castle Rental Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PokeCupRentalRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the rental Pokemon moves in the Poke Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Poke Cup Rental Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PrimeCupRentalRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the rental Pokemon moves in the Prime Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Prime Cup Rental Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class PetitCupRentalRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the rental Pokemon moves in the Petit Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Petit Cup Rental Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
class PikaCupRentalRandomness(Choice):
|
||||
"""
|
||||
Controls the level of randomness for the rental Pokemon moves in the Pika Cup.
|
||||
Vanilla - No change
|
||||
Low - Movesets have a status, STAB, and higher attack stat aligned move. (4th move is fully random)
|
||||
Medium - Movesets have a STAB, and higher attack stat aligned move. (3rd and 4th moves are fully random)
|
||||
High - Movesets have a higher attack stat aligned move. (all other moves are fully random)
|
||||
"""
|
||||
display_name = "Pika Cup Rental Randomness"
|
||||
option_vanilla = 1
|
||||
option_low = 2
|
||||
option_medium = 3
|
||||
option_high = 4
|
||||
default = 1
|
||||
|
||||
class RentalListShuffle(Choice):
|
||||
"""
|
||||
Controls whether the rental pokemon list is randomized or not
|
||||
Instead of going in dex order, the rental tables will be shuffled
|
||||
|
||||
Off - No change
|
||||
On - All tables shuffled
|
||||
Manual: Select which tables are shuffled
|
||||
"""
|
||||
display_name = "Rental List Shuffle"
|
||||
option_off = 1
|
||||
option_on = 2
|
||||
option_manual = 3
|
||||
default = 1
|
||||
|
||||
class RentalListShuffleGLC(Choice):
|
||||
"""
|
||||
Controls whether the rental pokemon list for the Gym Leader Castle is randomized or not
|
||||
Instead of going in dex order, the rental tables will be shuffled
|
||||
This option only matters if RentalListShuffle is set to Manual mode.
|
||||
Default is set to On
|
||||
|
||||
Off - No change
|
||||
On - All tables shuffled
|
||||
"""
|
||||
display_name = "RLS Manual: Gym Leader Castle"
|
||||
option_off = 1
|
||||
option_on = 2
|
||||
default = 2
|
||||
|
||||
class RentalListShufflePokeCup(Choice):
|
||||
"""
|
||||
Controls whether the rental pokemon list for the Poke Cup is randomized or not
|
||||
Instead of going in dex order, the rental tables will be shuffled
|
||||
This option only matters if RentalListShuffle is set to Manual mode.
|
||||
Default is set to On
|
||||
|
||||
Off - No change
|
||||
On - All tables shuffled
|
||||
"""
|
||||
display_name = "RLS Manual: Poke Cup"
|
||||
option_off = 1
|
||||
option_on = 2
|
||||
default = 2
|
||||
|
||||
class RentalListShufflePrimeCup(Choice):
|
||||
"""
|
||||
Controls whether the rental pokemon list for the Prime Cup is randomized or not
|
||||
Instead of going in dex order, the rental tables will be shuffled
|
||||
This option only matters if RentalListShuffle is set to Manual mode.
|
||||
Default is set to On
|
||||
|
||||
Off - No change
|
||||
On - All tables shuffled
|
||||
"""
|
||||
display_name = "RLS Manual: Prime Cup"
|
||||
option_off = 1
|
||||
option_on = 2
|
||||
default = 2
|
||||
|
||||
class RentalListShufflePetitCup(Choice):
|
||||
"""
|
||||
Controls whether the rental pokemon list for the Petit Cup is randomized or not
|
||||
Instead of going in dex order, the rental tables will be shuffled
|
||||
This option only matters if RentalListShuffle is set to Manual mode.
|
||||
Default is set to On
|
||||
|
||||
Off - No change
|
||||
On - All tables shuffled
|
||||
"""
|
||||
display_name = "RLS Manual: Petit Cup"
|
||||
option_off = 1
|
||||
option_on = 2
|
||||
default = 2
|
||||
|
||||
class RentalListShufflePikaCup(Choice):
|
||||
"""
|
||||
Controls whether the rental pokemon list for the Pika Cup is randomized or not
|
||||
Instead of going in dex order, the rental tables will be shuffled
|
||||
This option only matters if RentalListShuffle is set to Manual mode.
|
||||
Default is set to On
|
||||
|
||||
Off - No change
|
||||
On - All tables shuffled
|
||||
"""
|
||||
display_name = "RLS Manual: Pika Cup"
|
||||
option_off = 1
|
||||
option_on = 2
|
||||
default = 2
|
||||
|
||||
|
||||
@dataclass
|
||||
class PokemonStadiumOptions(PerGameCommonOptions):
|
||||
VictoryCondition: VictoryCondition
|
||||
BaseStatTotalRandomness: BaseStatTotalRandomness
|
||||
Trainersanity: Trainersanity
|
||||
GymCastleTrainerRandomness: GymCastleTrainerRandomness
|
||||
PokeCupTrainerRandomness: PokeCupTrainerRandomness
|
||||
PrimeCupTrainerRandomness: PrimeCupTrainerRandomness
|
||||
PetitCupTrainerRandomness: PetitCupTrainerRandomness
|
||||
PikaCupTrainerRandomness: PikaCupTrainerRandomness
|
||||
GymCastleRentalRandomness: GymCastleRentalRandomness
|
||||
PokeCupRentalRandomness: PokeCupRentalRandomness
|
||||
PrimeCupRentalRandomness: PrimeCupRentalRandomness
|
||||
PetitCupRentalRandomness: PetitCupRentalRandomness
|
||||
PikaCupRentalRandomness: PikaCupRentalRandomness
|
||||
RentalListShuffle: RentalListShuffle
|
||||
RentalListShuffleGLC: RentalListShuffleGLC
|
||||
RentalListShufflePokeCup: RentalListShufflePokeCup
|
||||
RentalListShufflePrimeCup: RentalListShufflePrimeCup
|
||||
RentalListShufflePetitCup: RentalListShufflePetitCup
|
||||
RentalListShufflePikaCup: RentalListShufflePikaCup
|
||||
|
||||
|
||||
# This is where you organize your options
|
||||
# Its entirely up to you how you want to organize it
|
||||
pokemon_stadium_option_groups: Dict[str, List[Any]] = {
|
||||
"General Options": [
|
||||
VictoryCondition,
|
||||
BaseStatTotalRandomness,
|
||||
Trainersanity,
|
||||
],
|
||||
|
||||
"Enemy Trainer Pokemon Options": [
|
||||
GymCastleTrainerRandomness,
|
||||
PokeCupTrainerRandomness,
|
||||
PrimeCupTrainerRandomness,
|
||||
PetitCupTrainerRandomness,
|
||||
PikaCupTrainerRandomness,
|
||||
],
|
||||
"Rental Pokemon Options":
|
||||
[
|
||||
GymCastleRentalRandomness,
|
||||
PokeCupRentalRandomness,
|
||||
PrimeCupRentalRandomness,
|
||||
PetitCupRentalRandomness,
|
||||
PikaCupRentalRandomness,
|
||||
],
|
||||
"Shuffling Options":
|
||||
[ RentalListShuffle,
|
||||
RentalListShuffleGLC,
|
||||
RentalListShufflePokeCup,
|
||||
RentalListShufflePrimeCup,
|
||||
RentalListShufflePetitCup,
|
||||
RentalListShufflePikaCup],
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
from BaseClasses import Region
|
||||
from .Types import PokemonStadiumLocation
|
||||
from .Locations import location_table, trainersanity_locations, is_valid_location
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import PokemonStadiumWorld
|
||||
|
||||
def create_regions(world: "PokemonStadiumWorld"):
|
||||
menu = create_region(world, "Menu")
|
||||
|
||||
# ---------------------------------- Gym Leader Castle ----------------------------------
|
||||
gym_leader_castle = create_region_and_connect(world, "Gym Leader Castle", "Menu -> Gym Leader Castle", menu)
|
||||
|
||||
create_region_and_connect(world, "Elite Four", "Gym Leader Castle -> Elite Four", gym_leader_castle)
|
||||
create_region_and_connect(world, "Rival", "Elite Four -> Rival", gym_leader_castle)
|
||||
create_region_and_connect(world, "Hall of Fame", "Rival -> Hall of Fame", gym_leader_castle)
|
||||
create_region_and_connect(world, "Beat Rival", "Hall of Fame -> Beat Rival", gym_leader_castle)
|
||||
|
||||
# -------------------------------------- Kids Club --------------------------------------
|
||||
create_region_and_connect(world, "Kids Club", "Menu -> Kids Club", menu)
|
||||
|
||||
# --------------------------------------- Stadium ---------------------------------------
|
||||
stadium = create_region_and_connect(world, "Stadium", "Menu -> Stadium", menu)
|
||||
create_region_and_connect(world, "Poké Cup", "Stadium -> Poké Cup", stadium)
|
||||
create_region_and_connect(world, "Petit Cup", "Stadium -> Petit Cup", stadium)
|
||||
create_region_and_connect(world, "Pika Cup", "Stadium -> Pika Cup", stadium)
|
||||
create_region_and_connect(world, "Prime Cup", "Stadium -> Prime Cup", stadium)
|
||||
|
||||
def create_region(world: "PokemonStadiumWorld", name: str) -> Region:
|
||||
reg = Region(name, world.player, world.multiworld)
|
||||
|
||||
if world.options.Trainersanity.value == 1:
|
||||
location_table.update(trainersanity_locations)
|
||||
|
||||
for (key, data) in location_table.items():
|
||||
if data.region == name:
|
||||
if not is_valid_location(world, key):
|
||||
continue
|
||||
location = PokemonStadiumLocation(world.player, key, data.ap_code, reg)
|
||||
reg.locations.append(location)
|
||||
|
||||
world.multiworld.regions.append(reg)
|
||||
return reg
|
||||
|
||||
def create_region_and_connect(world: "PokemonStadiumWorld", name: str, entrancename: str, connected_region: Region) -> Region:
|
||||
reg: Region = create_region(world, name)
|
||||
connected_region.connect(reg, entrancename)
|
||||
return reg
|
||||
@@ -0,0 +1,136 @@
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from settings import get_settings
|
||||
import Utils
|
||||
from worlds.AutoWorld import World
|
||||
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
|
||||
|
||||
from .randomizer import stadium_randomizer
|
||||
|
||||
NOP = bytes([0x00,0x00,0x00,0x00])
|
||||
MD5Hash = "ed1378bc12115f71209a77844965ba50"
|
||||
|
||||
class PokemonStadiumProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
game = "Pokemon Stadium"
|
||||
hash = MD5Hash
|
||||
patch_file_ending = ".apstadium"
|
||||
result_file_ending = ".z64"
|
||||
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_bytes()
|
||||
|
||||
def get_base_rom_bytes() -> bytes:
|
||||
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
|
||||
if not base_rom_bytes:
|
||||
file_name = get_base_rom_path()
|
||||
base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb")))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
md5hash = basemd5.hexdigest()
|
||||
if MD5Hash !=md5hash:
|
||||
raise Exception("Supplied Rom does not match known MD5 for Pokemon Stadium")
|
||||
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
|
||||
return base_rom_bytes
|
||||
|
||||
def get_base_rom_path():
|
||||
file_name = get_settings()["stadium_options"]["rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
file_name = Utils.user_path(file_name)
|
||||
return file_name
|
||||
|
||||
def write_tokens(world:World, patch:PokemonStadiumProcedurePatch):
|
||||
# version = settings['ROMVersion']
|
||||
bst_factor = world.options.BaseStatTotalRandomness.value
|
||||
glc_trainer_factor = world.options.GymCastleTrainerRandomness.value
|
||||
pokecup_trainer_factor = world.options.PokeCupTrainerRandomness.value
|
||||
primecup_trainer_factor = world.options.PrimeCupTrainerRandomness.value
|
||||
petitcup_trainer_factor = world.options.PetitCupTrainerRandomness.value
|
||||
pikacup_trainer_factor = world.options.PikaCupTrainerRandomness.value
|
||||
glc_rental_factor = world.options.GymCastleRentalRandomness.value
|
||||
pokecup_rental_factor = world.options.PokeCupRentalRandomness.value
|
||||
primecup_rental_factor = world.options.PrimeCupRentalRandomness.value
|
||||
petitcup_rental_factor = world.options.PetitCupRentalRandomness.value
|
||||
pikacup_rental_factor = world.options.PikaCupRentalRandomness.value
|
||||
rental_list_shuffle_factor = world.options.RentalListShuffle.value
|
||||
rental_list_shuffle_glc_factor = world.options.RentalListShuffleGLC.value
|
||||
rental_list_shuffle_poke_cup_factor = world.options.RentalListShufflePokeCup.value
|
||||
rental_list_shuffle_prime_cup_factor = world.options.RentalListShufflePrimeCup.value
|
||||
rental_list_shuffle_petit_cup_factor = world.options.RentalListShufflePetitCup.value
|
||||
rental_list_shuffle_pika_cup_factor = world.options.RentalListShufflePikaCup.value
|
||||
randomizer = stadium_randomizer.Randomizer('US_1.0', bst_factor, glc_trainer_factor, pokecup_trainer_factor, primecup_trainer_factor, petitcup_trainer_factor,
|
||||
pikacup_trainer_factor, glc_rental_factor, pokecup_rental_factor, primecup_rental_factor,petitcup_rental_factor, pikacup_rental_factor,
|
||||
rental_list_shuffle_factor, rental_list_shuffle_glc_factor, rental_list_shuffle_poke_cup_factor, rental_list_shuffle_prime_cup_factor,
|
||||
rental_list_shuffle_petit_cup_factor, rental_list_shuffle_pika_cup_factor)
|
||||
|
||||
# Bypass CIC
|
||||
randomizer.disable_checksum(patch)
|
||||
if bst_factor > 1:
|
||||
randomizer.randomize_base_stats(patch)
|
||||
if glc_trainer_factor > 1:
|
||||
randomizer.randomize_glc_trainer_pokemon_round1(patch)
|
||||
if pokecup_trainer_factor > 1:
|
||||
randomizer.randomize_pokecup_trainer_pokemon_round1(patch)
|
||||
if primecup_trainer_factor > 1:
|
||||
randomizer.randomize_primecup_trainer_pokemon_round1(patch)
|
||||
if petitcup_trainer_factor > 1:
|
||||
randomizer.randomize_petitcup_trainer_pokemon_round1(patch)
|
||||
if pikacup_trainer_factor > 1:
|
||||
randomizer.randomize_pikacup_trainer_pokemon_round1(patch)
|
||||
|
||||
if glc_rental_factor > 1:
|
||||
randomizer.randomize_glc_rentals_round1(patch)
|
||||
if pokecup_rental_factor > 1:
|
||||
randomizer.randomize_pokecup_rentals(patch)
|
||||
if primecup_rental_factor > 1:
|
||||
randomizer.randomize_primecup_rentals_round1(patch)
|
||||
if petitcup_rental_factor > 1:
|
||||
randomizer.randomize_petitcup_rentals(patch)
|
||||
if pikacup_rental_factor > 1:
|
||||
randomizer.randomize_pikacup_rentals(patch)
|
||||
if rental_list_shuffle_factor > 1:
|
||||
if rental_list_shuffle_factor != 3: #Not in manual mode
|
||||
randomizer.shuffle_rentals(patch)
|
||||
else:
|
||||
if rental_list_shuffle_glc_factor > 1:
|
||||
randomizer.shuffle_glc(patch)
|
||||
if rental_list_shuffle_poke_cup_factor > 1:
|
||||
randomizer.shuffle_poke(patch)
|
||||
if rental_list_shuffle_prime_cup_factor > 1:
|
||||
randomizer.shuffle_prime(patch)
|
||||
if rental_list_shuffle_petit_cup_factor > 1:
|
||||
randomizer.shuffle_petit(patch)
|
||||
if rental_list_shuffle_pika_cup_factor > 1:
|
||||
randomizer.shuffle_pika(patch)
|
||||
|
||||
# Set GP Register to 80420000
|
||||
patch.write_token(APTokenTypes.WRITE, 0x202B8, bytes([0x3C, 0x1C, 0x80, 0x42]))
|
||||
|
||||
# Set 'Starting Battle' flag
|
||||
patch.write_token(APTokenTypes.WRITE, 0x855C, bytes([0xAF, 0x81, 0x00, 0x10]))
|
||||
|
||||
# Clear 'Starting Battle' flag
|
||||
patch.write_token(APTokenTypes.WRITE, 0x396D08, bytes([0xAF, 0x80, 0x00, 0x10]))
|
||||
|
||||
# Turn off A and B button on GLC select screen
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3B4DA8, bytes([0x50, 0x21, 0xFF, 0x82]))
|
||||
|
||||
# First instruction to set flag for GLC selection screen
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3B5548, bytes([0xAF, 0x84, 0x00, 0x00]))
|
||||
|
||||
# Second instruction to set flag for GLC selection screen
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3B55F4, bytes([0xAF, 0x82, 0x00, 0x00]))
|
||||
|
||||
# Set selecting Poke Cup tier flag
|
||||
patch.write_token(APTokenTypes.WRITE, 0x2D6A20, bytes([0xAF, 0x93, 0x00, 0x20]))
|
||||
|
||||
# Clear selecting Poke Cup tier flag
|
||||
patch.write_token(APTokenTypes.WRITE, 0x2D6DB0, bytes([0xAF, 0x80, 0x00, 0x20]))
|
||||
|
||||
# Stop game from activating unlocked gyms
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3B5728, bytes([0xA3, 0x20, 0x00, 0x01]))
|
||||
|
||||
# Write patch file
|
||||
patch.write_file("token_data.bin", patch.get_token_binary())
|
||||
@@ -0,0 +1,132 @@
|
||||
from worlds.generic.Rules import set_rule, add_item_rule
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import PokemonStadiumWorld
|
||||
|
||||
def set_rules(world: "PokemonStadiumWorld"):
|
||||
player = world.player
|
||||
options = world.options
|
||||
|
||||
# Gym Access
|
||||
set_rule(world.multiworld.get_location("Pewter Gym", player), lambda state: state.has("Pewter City Key", player))
|
||||
set_rule(world.multiworld.get_location("Cerulean Gym", player), lambda state: state.has("Cerulean City Key", player))
|
||||
set_rule(world.multiworld.get_location("Vermillion Gym", player), lambda state: state.has("Vermillion City Key", player))
|
||||
set_rule(world.multiworld.get_location("Celadon Gym", player), lambda state: state.has("Celadon City Key", player))
|
||||
set_rule(world.multiworld.get_location("Fuchsia Gym", player), lambda state: state.has("Fuchsia City Key", player))
|
||||
set_rule(world.multiworld.get_location("Saffron Gym", player), lambda state: state.has("Saffron City Key", player))
|
||||
set_rule(world.multiworld.get_location("Cinnabar Gym", player), lambda state: state.has("Cinnabar Island Key", player))
|
||||
set_rule(world.multiworld.get_location("Viridian Gym", player), lambda state: state.has("Viridian City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("BROCK", player), lambda state: state.has("Pewter City Key", player))
|
||||
set_rule(world.multiworld.get_location("MISTY", player), lambda state: state.has("Cerulean City Key", player))
|
||||
set_rule(world.multiworld.get_location("SURGE", player), lambda state: state.has("Vermillion City Key", player))
|
||||
set_rule(world.multiworld.get_location("ERIKA", player), lambda state: state.has("Celadon City Key", player))
|
||||
set_rule(world.multiworld.get_location("KOGA", player), lambda state: state.has("Fuchsia City Key", player))
|
||||
set_rule(world.multiworld.get_location("SABRINA", player), lambda state: state.has("Saffron City Key", player))
|
||||
set_rule(world.multiworld.get_location("BLAINE", player), lambda state: state.has("Cinnabar Island Key", player))
|
||||
set_rule(world.multiworld.get_location("GIOVANNI", player), lambda state: state.has("Viridian City Key", player))
|
||||
|
||||
# Cup Access
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Prize", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Prize", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Prize", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Prize", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Prize", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Prize", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
|
||||
#Trainersanity All
|
||||
if world.options.Trainersanity.value == 1:
|
||||
set_rule(world.multiworld.get_location("Pewter Gym - Bug Boy", player), lambda state: state.has("Pewter City Key", player))
|
||||
set_rule(world.multiworld.get_location("Pewter Gym - Lad", player), lambda state: state.has("Pewter City Key", player))
|
||||
set_rule(world.multiworld.get_location("Pewter Gym - Jr(M)", player), lambda state: state.has("Pewter City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Cerulean Gym - Fisher", player), lambda state: state.has("Cerulean City Key", player))
|
||||
set_rule(world.multiworld.get_location("Cerulean Gym - Jr(F)", player), lambda state: state.has("Cerulean City Key", player))
|
||||
set_rule(world.multiworld.get_location("Cerulean Gym - Swimmer", player), lambda state: state.has("Cerulean City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Vermillion Gym - Sailor", player), lambda state: state.has("Vermillion City Key", player))
|
||||
set_rule(world.multiworld.get_location("Vermillion Gym - Rocker", player), lambda state: state.has("Vermillion City Key", player))
|
||||
set_rule(world.multiworld.get_location("Vermillion Gym - Old Man", player), lambda state: state.has("Vermillion City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Celadon Gym - Lass", player), lambda state: state.has("Celadon City Key", player))
|
||||
set_rule(world.multiworld.get_location("Celadon Gym - Beauty", player), lambda state: state.has("Celadon City Key", player))
|
||||
set_rule(world.multiworld.get_location("Celadon Gym - Cool(F)", player), lambda state: state.has("Celadon City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Fuchsia Gym - Biker", player), lambda state: state.has("Fuchsia City Key", player))
|
||||
set_rule(world.multiworld.get_location("Fuchsia Gym - Tamer", player), lambda state: state.has("Fuchsia City Key", player))
|
||||
set_rule(world.multiworld.get_location("Fuchsia Gym - Juggler", player), lambda state: state.has("Fuchsia City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Saffron Gym - Cue Ball", player), lambda state: state.has("Saffron City Key", player))
|
||||
set_rule(world.multiworld.get_location("Saffron Gym - Burglar", player), lambda state: state.has("Saffron City Key", player))
|
||||
set_rule(world.multiworld.get_location("Saffron Gym - Medium", player), lambda state: state.has("Saffron City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Cinnabar Gym - Judoboy", player), lambda state: state.has("Cinnabar Island Key", player))
|
||||
set_rule(world.multiworld.get_location("Cinnabar Gym - Psychic", player), lambda state: state.has("Cinnabar Island Key", player))
|
||||
set_rule(world.multiworld.get_location("Cinnabar Gym - Nerd", player), lambda state: state.has("Cinnabar Island Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Viridian Gym - Rocket", player), lambda state: state.has("Viridian City Key", player))
|
||||
set_rule(world.multiworld.get_location("Viridian Gym - Lab Man", player), lambda state: state.has("Viridian City Key", player))
|
||||
set_rule(world.multiworld.get_location("Viridian Gym - Cool(M)", player), lambda state: state.has("Viridian City Key", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Bug Boy", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Lad", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Nerd", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Sailor", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Jr(F)", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Jr(M)", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Lass", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Great Ball - Pokémaniac", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 0)
|
||||
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Bug Boy", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Lad", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Nerd", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Sailor", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Jr(F)", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Jr(M)", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Lass", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Ultra Ball - Pokémaniac", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 1)
|
||||
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Bug Boy", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Lad", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Nerd", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Sailor", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Jr(F)", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Jr(M)", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Lass", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Poké Cup - Master Ball - Pokémaniac", player), lambda state: state.count('Poké Cup - Tier Upgrade', player) > 2)
|
||||
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Cue Ball", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Rocket", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Judoboy", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Gambler", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Cool(F)", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Bird Boy", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Lab Man", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Great Ball - Cool(M)", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 0)
|
||||
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Cue Ball", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Rocket", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Judoboy", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Gambler", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Cool(F)", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Bird Boy", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Lab Man", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Ultra Ball - Cool(M)", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 1)
|
||||
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Cue Ball", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Rocket", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Judoboy", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Gambler", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Cool(F)", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Bird Boy", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Lab Man", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
set_rule(world.multiworld.get_location("Prime Cup - Master Ball - Cool(M)", player), lambda state: state.count('Prime Cup - Tier Upgrade', player) > 2)
|
||||
|
||||
# Beat Rival Rule
|
||||
badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge", "Marsh Badge", "Volcano Badge", "Earth Badge"]
|
||||
set_rule(world.multiworld.get_location("Beat Rival", player), lambda state: state.has_all(badges, player))
|
||||
|
||||
# Victory condition rule!
|
||||
world.multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
@@ -0,0 +1,18 @@
|
||||
from enum import IntEnum
|
||||
from typing import NamedTuple, Optional
|
||||
from BaseClasses import Location, Item, ItemClassification
|
||||
|
||||
class PokemonStadiumLocation(Location):
|
||||
game = 'PokemonStadium'
|
||||
|
||||
class PokemonStadiumItem(Item):
|
||||
game = 'PokemonStadium'
|
||||
|
||||
class ItemData(NamedTuple):
|
||||
ap_code: Optional[int]
|
||||
classification: ItemClassification
|
||||
count: Optional[int] = 1
|
||||
|
||||
class LocData(NamedTuple):
|
||||
ap_code: Optional[int]
|
||||
region: Optional[str]
|
||||
@@ -0,0 +1,148 @@
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import random
|
||||
|
||||
from BaseClasses import MultiWorld, Item, Tutorial
|
||||
import settings
|
||||
from typing import Dict
|
||||
import Utils
|
||||
from worlds.AutoWorld import World, CollectionState, WebWorld
|
||||
|
||||
from .Client import PokemonStadiumClient # Unused, but required to register with BizHawkClient
|
||||
from .Items import create_item, create_itempool, gym_keys, item_table
|
||||
from .Locations import get_location_names, get_total_locations
|
||||
from .Options import PokemonStadiumOptions
|
||||
from .Regions import create_regions
|
||||
from .Rom import MD5Hash, PokemonStadiumProcedurePatch, write_tokens
|
||||
from .Rom import get_base_rom_path as get_base_rom_path
|
||||
from .Rules import set_rules
|
||||
|
||||
class PokemonStadiumSettings(settings.Group):
|
||||
class PokemonStadiumRomFile(settings.UserFilePath):
|
||||
"""File name of the Pokemon Stadium (US, 1.0) ROM"""
|
||||
description = "Pokemon Stadium (US, 1.0) ROM File"
|
||||
copy_to = "Pokemon Stadium (US, 1.0).z64"
|
||||
md5s = [PokemonStadiumProcedurePatch.hash]
|
||||
|
||||
rom_file: PokemonStadiumRomFile = PokemonStadiumRomFile(PokemonStadiumRomFile.copy_to)
|
||||
|
||||
class PokemonStadiumWeb(WebWorld):
|
||||
theme = "Party"
|
||||
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up (the game you are randomizing) for Archipelago. "
|
||||
"This guide covers single-player, multiworld, and related software.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["JCIII"]
|
||||
)]
|
||||
|
||||
class PokemonStadiumWorld(World):
|
||||
game = "Pokemon Stadium"
|
||||
|
||||
settings_key = "stadium_options"
|
||||
settings: PokemonStadiumSettings
|
||||
|
||||
item_name_to_id = {name: data.ap_code for name, data in item_table.items()}
|
||||
|
||||
location_name_to_id = get_location_names()
|
||||
|
||||
options_dataclass = PokemonStadiumOptions
|
||||
options = PokemonStadiumOptions
|
||||
|
||||
web = PokemonStadiumWeb()
|
||||
|
||||
starting_gym_keys = random.sample(gym_keys, 3)
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
super().__init__(multiworld, player)
|
||||
|
||||
def generate_early(self):
|
||||
for key in self.starting_gym_keys:
|
||||
self.multiworld.push_precollected(self.create_item(key))
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self)
|
||||
|
||||
def create_items(self):
|
||||
self.multiworld.itempool += create_itempool(self)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return create_item(self, name)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self)
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, object]:
|
||||
slot_data: Dict[str, object] = {
|
||||
"options": {
|
||||
"VictoryCondition": self.options.VictoryCondition.value,
|
||||
"BaseStatTotalRandomness": self.options.BaseStatTotalRandomness.value,
|
||||
"Trainersanity": self.options.Trainersanity.value,
|
||||
"GymCastleTrainerRandomness": self.options.GymCastleTrainerRandomness.value,
|
||||
"PokeCupTrainerRandomness": self.options.PokeCupTrainerRandomness.value,
|
||||
"PrimeCupTrainerRandomness": self.options.PrimeCupTrainerRandomness.value,
|
||||
"PetitupTrainerRandomness": self.options.PetitCupTrainerRandomness.value,
|
||||
"PikaCupTrainerRandomness": self.options.PikaCupTrainerRandomness.value,
|
||||
"GymCastleRentalRandomness": self.options.GymCastleRentalRandomness.value,
|
||||
"PokeCupRentalRandomness": self.options.PokeCupRentalRandomness.value,
|
||||
"PrimeCupRentalRandomness": self.options.PrimeCupRentalRandomness.value,
|
||||
"PetitCupRentalRandomness": self.options.PetitCupRentalRandomness.value,
|
||||
"RentalListShuffle": self.options.RentalListShuffle.value,
|
||||
"RentalListShuffleGLC": self.options.RentalListShuffleGLC.value,
|
||||
"RentalListShufflePokeCup": self.options.RentalListShufflePokeCup.value,
|
||||
"RentalListShufflePrimeCup": self.options.RentalListShufflePrimeCup.value,
|
||||
"RentalListShufflePetitCup": self.options.RentalListShufflePetitCup.value,
|
||||
"RentalListShufflePikaCup": self.options.RentalListShufflePikaCup.value,
|
||||
},
|
||||
"Seed": self.multiworld.seed_name, # to verify the server's multiworld
|
||||
"Slot": self.multiworld.player_name[self.player], # to connect to server
|
||||
"TotalLocations": get_total_locations(self) # get_total_locations(self) comes from Locations.py
|
||||
}
|
||||
|
||||
return slot_data
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
# === Step 1: Build ROM and player metadata ===
|
||||
outfilepname = f"_P{self.player}_"
|
||||
outfilepname += f"{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}"
|
||||
|
||||
# ROM name metadata (embedded in ROM for client/UI)
|
||||
self.rom_name_text = f'PokemonStadium{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:011}\0'
|
||||
self.romName = bytearray(self.rom_name_text, "utf8")[:0x20]
|
||||
self.romName.extend([0] * (0x20 - len(self.romName))) # pad to 0x20
|
||||
self.rom_name = self.romName
|
||||
|
||||
# Player name metadata
|
||||
self.playerName = bytearray(self.multiworld.player_name[self.player], "utf8")[:0x20]
|
||||
self.playerName.extend([0] * (0x20 - len(self.playerName)))
|
||||
|
||||
# === Step 3: Create procedure patch object ===
|
||||
patch = PokemonStadiumProcedurePatch(
|
||||
player=self.player,
|
||||
player_name=self.multiworld.player_name[self.player]
|
||||
)
|
||||
|
||||
# === Step 4: Apply token modifications directly ===
|
||||
write_tokens(self, patch)
|
||||
procedure = [("apply_tokens", ["token_data.bin"])]
|
||||
|
||||
# === Step 6: Finalize procedure ===
|
||||
patch.procedure = procedure
|
||||
|
||||
# Generate output file path
|
||||
out_file_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
patch_file_path = os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}")
|
||||
|
||||
# Write the final patch file (.bps)
|
||||
patch.write(patch_file_path)
|
||||
|
||||
def collect(self, state: "CollectionState", item: "Item") -> bool:
|
||||
return super().collect(state, item)
|
||||
|
||||
def remove(self, state: "CollectionState", item: "Item") -> bool:
|
||||
return super().remove(state, item)
|
||||
@@ -0,0 +1 @@
|
||||
stop making me do this
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Pokemon Stadium Randomizer
|
||||
|
||||
https://stadiumrando.com
|
||||
|
||||
## Built-in Rental Rando
|
||||
- Press START on the rental team selection screen to fill your team with random Pokemon
|
||||
|
||||
## Currently Randomizing
|
||||
- Gym Leader Castle
|
||||
- Player Rentals
|
||||
- Base stats
|
||||
- EVs and IVs
|
||||
- Moves
|
||||
- Enemies
|
||||
- Pokemon
|
||||
- Base stats
|
||||
- EVs and IVs
|
||||
- Moves
|
||||
@@ -0,0 +1,898 @@
|
||||
rom_offsets = {
|
||||
"US_1.0" : {
|
||||
"CheckSum1" : 0x63C,
|
||||
"CheckSum2" : 0x648,
|
||||
"SetBattleStartFlag": 34140,
|
||||
"BaseStats" : 465825,
|
||||
"SetGPRegister": 131768,
|
||||
"SetPokeCupFlag": 2976288,
|
||||
"ClearPokeCupFlag": 2977200,
|
||||
"Rental_Table_Input_Routine" : 3023512,
|
||||
"DefeatedNonLeaderFlag": 3761116,
|
||||
"LostToTrainerFlag": 3763336,
|
||||
"SetGLCFlag1": 3888456,
|
||||
"SetGLCFlag2": 3888628,
|
||||
"GymCastle_Round1": 9057228,
|
||||
"PokeCup_Round1": 9039244, #This starts at pokeball cup
|
||||
"PokeCup_Round2": 9159120, #Needs adjustment
|
||||
"PrimeCup_Round1": 9021260, #This starts at pokeball cup
|
||||
"PrimeCup_Round2": 9141136, #Needs adjustment
|
||||
"PetitCup_Round1": 9012268, #Starts at first pokemon first trainer
|
||||
"PetitCup_Round2": 9132144, #Needs adjustment
|
||||
"PikaCup_Round1": 9016764, #Starts at first pokemon first trainer
|
||||
"PikaCup_Round2": 9136640, #Needs adjustment
|
||||
"Mewtwo_Round1": 9081408, #Needs adjustment
|
||||
"Mewtwo_Round2": 9201344, #Needs adjustment
|
||||
|
||||
|
||||
"Rentals_GymCastle_Round1" : 9119616,
|
||||
"Rentals_PokeCup" : 9105952,
|
||||
"Rentals_PrimeCup_Round1" : 9093424,
|
||||
"Rentals_PrimeCup_Round2" : 9201920,
|
||||
"Rentals_PetitCup" : 9081984,
|
||||
"Rentals_PikaCup" : 9085776,
|
||||
},
|
||||
"PAL_1.1" : {
|
||||
"CheckSum1" : 1596,
|
||||
"CheckSum2" : 1608,
|
||||
"BaseStats" : 466337,
|
||||
"Rental_Table_Input_Routine" : 2967864,
|
||||
"Rental_Table_Header" : 7882439,
|
||||
"Rental_GymCastle_Round1_Pointer" : 8872432,
|
||||
"GymCastle_Round1": 8917964,
|
||||
"EmptyRomSpace" : 33301456,
|
||||
"EmptyRomSpaceForTables" : 33302224,
|
||||
"OffsetToNewTable" : "0174C6D000003200"
|
||||
}
|
||||
}
|
||||
|
||||
kanto_dex_names = [
|
||||
{"name": "BULBASAUR", "type": "1603", "exp": "117360", 'bst': [45, 49, 49, 45, 65 ], "gr" : "mediumslow"},
|
||||
{"name": "IVYSAUR", "type": "1603", "exp": "117360", 'bst': [60, 62, 63, 60, 80 ], "gr" : "mediumslow"},
|
||||
{"name": "VENUSAUR", "type": "1603", "exp": "117360", 'bst': [80, 82, 83, 80, 100], "gr" : "mediumslow"},
|
||||
{"name": "CHARMANDER", "type": "1414", "exp": "117360", 'bst': [39, 52, 43, 65, 50 ], "gr" : "mediumslow"},
|
||||
{"name": "CHARMELEON", "type": "1414", "exp": "117360", 'bst': [58, 64, 58, 80, 65 ], "gr" : "mediumslow"},
|
||||
{"name": "CHARIZARD", "type": "1402", "exp": "117360", 'bst': [78, 84, 78, 100, 85 ], "gr" : "mediumslow"},
|
||||
{"name": "SQUIRTLE", "type": "1515", "exp": "117360", 'bst': [44, 48, 65, 43, 50 ], "gr" : "mediumslow"},
|
||||
{"name": "WARTORTLE", "type": "1515", "exp": "117360", 'bst': [59, 63, 80, 58, 65 ], "gr" : "mediumslow"},
|
||||
{"name": "BLASTOISE", "type": "1515", "exp": "117360", 'bst': [79, 83, 100, 78, 85 ], "gr" : "mediumslow"},
|
||||
{"name": "CATERPIE", "type": "0707", "exp": "125000", 'bst': [45, 30, 35, 45, 20 ], "gr" : "mediumfast"},
|
||||
{"name": "METAPOD", "type": "0707", "exp": "125000", 'bst': [50, 20, 55, 30, 25 ], "gr" : "mediumfast"},
|
||||
{"name": "BUTTERFREE", "type": "0702", "exp": "125000", 'bst': [60, 45, 50, 70, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "WEEDLE", "type": "0703", "exp": "125000", 'bst': [40, 35, 30, 50, 20 ], "gr" : "mediumfast"},
|
||||
{"name": "KAKUNA", "type": "0703", "exp": "125000", 'bst': [45, 25, 50, 35, 25 ], "gr" : "mediumfast"},
|
||||
{"name": "BEEDRILL", "type": "0703", "exp": "125000", 'bst': [65, 80, 40, 75, 45 ], "gr" : "mediumfast"},
|
||||
{"name": "PIDGEY", "type": "0002", "exp": "117360", 'bst': [40, 45, 40, 56, 35 ], "gr" : "mediumslow"},
|
||||
{"name": "PIDGEOTTO", "type": "0002", "exp": "117360", 'bst': [63, 60, 55, 71, 50 ], "gr" : "mediumslow"},
|
||||
{"name": "PIDGEOT", "type": "0002", "exp": "117360", 'bst': [83, 80, 75, 91, 70 ], "gr" : "mediumslow"},
|
||||
{"name": "RATTATA", "type": "0000", "exp": "125000", 'bst': [30, 56, 35, 72, 25 ], "gr" : "mediumfast"},
|
||||
{"name": "RATICATE", "type": "0000", "exp": "125000", 'bst': [55, 81, 60, 97, 50 ], "gr" : "mediumfast"},
|
||||
{"name": "SPEAROW", "type": "0002", "exp": "125000", 'bst': [40, 60, 30, 70, 31 ], "gr" : "mediumfast"},
|
||||
{"name": "FEAROW", "type": "0002", "exp": "125000", 'bst': [65, 90, 65, 100, 61 ], "gr" : "mediumfast"},
|
||||
{"name": "EKANS", "type": "0303", "exp": "125000", 'bst': [35, 60, 44, 55, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "ARBOK", "type": "0303", "exp": "125000", 'bst': [60, 85, 69, 80, 65 ], "gr" : "mediumfast"},
|
||||
{"name": "PIKACHU", "type": "1717", "exp": "125000", 'bst': [35, 55, 30, 90, 50 ], "gr" : "mediumfast"},
|
||||
{"name": "RAICHU", "type": "1717", "exp": "125000", 'bst': [60, 90, 55, 100, 90 ], "gr" : "mediumfast"},
|
||||
{"name": "SANDSHREW", "type": "0404", "exp": "125000", 'bst': [50, 75, 85, 40, 30 ], "gr" : "mediumfast"},
|
||||
{"name": "SANDSLASH", "type": "0404", "exp": "125000", 'bst': [75, 100, 110, 65, 55 ], "gr" : "mediumfast"},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "117360", 'bst': [55, 47, 52, 41, 40 ], "gr" : "mediumslow"},
|
||||
{"name": "NIDORINA", "type": "0303", "exp": "117360", 'bst': [70, 62, 67, 56, 55 ], "gr" : "mediumslow"},
|
||||
{"name": "NIDOQUEEN", "type": "0304", "exp": "117360", 'bst': [90, 82, 87, 76, 75 ], "gr" : "mediumslow"},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "117360", 'bst': [46, 57, 40, 50, 40 ], "gr" : "mediumslow"},
|
||||
{"name": "NIDORINO", "type": "0303", "exp": "117360", 'bst': [61, 72, 57, 65, 55 ], "gr" : "mediumslow"},
|
||||
{"name": "NIDOKING", "type": "0304", "exp": "117360", 'bst': [81, 92, 77, 85, 75 ], "gr" : "mediumslow"},
|
||||
{"name": "CLEFAIRY", "type": "0000", "exp": "100000", 'bst': [70, 45, 48, 35, 60 ], "gr" : "fast"},
|
||||
{"name": "CLEFABLE", "type": "0000", "exp": "100000", 'bst': [95, 70, 73, 60, 85 ], "gr" : "fast"},
|
||||
{"name": "VULPIX", "type": "1414", "exp": "125000", 'bst': [38, 41, 40, 65, 65 ], "gr" : "mediumfast"},
|
||||
{"name": "NINETALES", "type": "1414", "exp": "125000", 'bst': [73, 76, 75, 100, 100], "gr" : "mediumfast"},
|
||||
{"name": "JIGGLYPUFF", "type": "0000", "exp": "100000", 'bst': [115, 45, 20, 20, 25 ], "gr" : "fast"},
|
||||
{"name": "WIGGLYTUFF", "type": "0000", "exp": "100000", 'bst': [140, 70, 45, 45, 50 ], "gr" : "fast"},
|
||||
{"name": "ZUBAT", "type": "0302", "exp": "125000", 'bst': [40, 45, 35, 55, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "GOLBAT", "type": "0302", "exp": "125000", 'bst': [75, 80, 70, 90, 75 ], "gr" : "mediumfast"},
|
||||
{"name": "ODDISH", "type": "1603", "exp": "117360", 'bst': [45, 50, 55, 30, 75 ], "gr" : "mediumslow"},
|
||||
{"name": "GLOOM", "type": "1603", "exp": "117360", 'bst': [60, 65, 70, 40, 85 ], "gr" : "mediumslow"},
|
||||
{"name": "VILEPLUME", "type": "1603", "exp": "117360", 'bst': [75, 80, 85, 50, 100], "gr" : "mediumslow"},
|
||||
{"name": "PARAS", "type": "0716", "exp": "125000", 'bst': [35, 70, 55, 25, 55 ], "gr" : "mediumfast"},
|
||||
{"name": "PARASECT", "type": "0716", "exp": "125000", 'bst': [60, 95, 80, 30, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "VENONAT", "type": "0703", "exp": "125000", 'bst': [60, 55, 50, 45, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "VENOMOTH", "type": "0703", "exp": "125000", 'bst': [70, 65, 60, 90, 90 ], "gr" : "mediumfast"},
|
||||
{"name": "DIGLETT", "type": "0404", "exp": "125000", 'bst': [10, 55, 25, 95, 45 ], "gr" : "mediumfast"},
|
||||
{"name": "DUGTRIO", "type": "0404", "exp": "125000", 'bst': [35, 80, 50, 120, 70 ], "gr" : "mediumfast"},
|
||||
{"name": "MEOWTH", "type": "0000", "exp": "125000", 'bst': [40, 45, 35, 90, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "PERSIAN", "type": "0000", "exp": "125000", 'bst': [65, 70, 60, 115, 65 ], "gr" : "mediumfast"},
|
||||
{"name": "PSYDUCK", "type": "1515", "exp": "125000", 'bst': [50, 52, 48, 55, 50 ], "gr" : "mediumfast"},
|
||||
{"name": "GOLDUCK", "type": "1515", "exp": "125000", 'bst': [80, 82, 78, 85, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "MANKEY", "type": "0101", "exp": "125000", 'bst': [40, 80, 35, 70, 35 ], "gr" : "mediumfast"},
|
||||
{"name": "PRIMEAPE", "type": "0101", "exp": "125000", 'bst': [65, 105, 60, 95, 60 ], "gr" : "mediumfast"},
|
||||
{"name": "GROWLITHE", "type": "1414", "exp": "156250", 'bst': [55, 70, 45, 60, 50 ], "gr" : "slow"},
|
||||
{"name": "ARCANINE", "type": "1414", "exp": "156250", 'bst': [90, 110, 80, 95, 80 ], "gr" : "slow"},
|
||||
{"name": "POLIWAG", "type": "1515", "exp": "117360", 'bst': [40, 50, 40, 90, 40 ], "gr" : "mediumslow"},
|
||||
{"name": "POLIWHIRL", "type": "1515", "exp": "117360", 'bst': [65, 65, 65, 90, 50 ], "gr" : "mediumslow"},
|
||||
{"name": "POLIWRATH", "type": "1501", "exp": "117360", 'bst': [90, 85, 95, 70, 70 ], "gr" : "mediumslow"},
|
||||
{"name": "ABRA", "type": "1818", "exp": "117360", 'bst': [25, 20, 15, 90, 105], "gr" : "mediumslow"},
|
||||
{"name": "KADABRA", "type": "1818", "exp": "117360", 'bst': [40, 35, 30, 105, 120], "gr" : "mediumslow"},
|
||||
{"name": "ALAKAZAM", "type": "1818", "exp": "117360", 'bst': [55, 50, 45, 120, 135], "gr" : "mediumslow"},
|
||||
{"name": "MACHOP", "type": "0101", "exp": "117360", 'bst': [70, 80, 50, 35, 35 ], "gr" : "mediumslow"},
|
||||
{"name": "MACHOKE", "type": "0101", "exp": "117360", 'bst': [80, 100, 70, 45, 50 ], "gr" : "mediumslow"},
|
||||
{"name": "MACHAMP", "type": "0101", "exp": "117360", 'bst': [90, 130, 80, 55, 65 ], "gr" : "mediumslow"},
|
||||
{"name": "BELLSPROUT", "type": "1603", "exp": "117360", 'bst': [50, 75, 35, 40, 70 ], "gr" : "mediumslow"},
|
||||
{"name": "WEEPINBELL", "type": "1603", "exp": "117360", 'bst': [65, 90, 50, 55, 85 ], "gr" : "mediumslow"},
|
||||
{"name": "VICTREEBEL", "type": "1603", "exp": "117360", 'bst': [80, 105, 65, 70, 100], "gr" : "mediumslow"},
|
||||
{"name": "TENTACOOL", "type": "1503", "exp": "156250", 'bst': [40, 40, 35, 70, 100], "gr" : "slow"},
|
||||
{"name": "TENTACRUEL", "type": "1503", "exp": "156250", 'bst': [80, 70, 65, 100, 120], "gr" : "slow"},
|
||||
{"name": "GEODUDE", "type": "0504", "exp": "117360", 'bst': [40, 80, 100, 20, 30 ], "gr" : "mediumslow"},
|
||||
{"name": "GRAVELER", "type": "0504", "exp": "117360", 'bst': [55, 95, 115, 35, 45 ], "gr" : "mediumslow"},
|
||||
{"name": "GOLEM", "type": "0504", "exp": "117360", 'bst': [80, 110, 130, 45, 55 ], "gr" : "mediumslow"},
|
||||
{"name": "PONYTA", "type": "1414", "exp": "125000", 'bst': [50, 85, 55, 90, 65 ], "gr" : "mediumfast"},
|
||||
{"name": "RAPIDASH", "type": "1414", "exp": "125000", 'bst': [65, 100, 70, 105, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "SLOWPOKE", "type": "1518", "exp": "125000", 'bst': [90, 65, 65, 15, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "SLOWBRO", "type": "1518", "exp": "125000", 'bst': [95, 75, 110, 30, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "MAGNEMITE", "type": "1717", "exp": "125000", 'bst': [25, 35, 70, 45, 95 ], "gr" : "mediumfast"},
|
||||
{"name": "MAGNETON", "type": "1717", "exp": "125000", 'bst': [50, 60, 95, 70, 120], "gr" : "mediumfast"},
|
||||
{"name": "FARFETCH'D", "type": "0002", "exp": "125000", 'bst': [52, 65, 55, 60, 58 ], "gr" : "mediumfast"},
|
||||
{"name": "DODUO", "type": "0002", "exp": "125000", 'bst': [35, 85, 45, 75, 35 ], "gr" : "mediumfast"},
|
||||
{"name": "DODRIO", "type": "0002", "exp": "125000", 'bst': [60, 110, 70, 100, 60 ], "gr" : "mediumfast"},
|
||||
{"name": "SEEL", "type": "1515", "exp": "125000", 'bst': [65, 45, 55, 45, 70 ], "gr" : "mediumfast"},
|
||||
{"name": "DEWGONG", "type": "1519", "exp": "125000", 'bst': [90, 70, 80, 70, 95 ], "gr" : "mediumfast"},
|
||||
{"name": "GRIMER", "type": "0303", "exp": "125000", 'bst': [80, 80, 50, 25, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "MUK", "type": "0303", "exp": "125000", 'bst': [105, 105, 75, 50, 65 ], "gr" : "mediumfast"},
|
||||
{"name": "SHELLDER", "type": "1515", "exp": "156250", 'bst': [30, 65, 100, 40, 45 ], "gr" : "slow"},
|
||||
{"name": "CLOYSTER", "type": "1519", "exp": "156250", 'bst': [50, 95, 180, 70, 85 ], "gr" : "slow"},
|
||||
{"name": "GASTLY", "type": "0803", "exp": "117360", 'bst': [30, 35, 30, 80, 100], "gr" : "mediumslow"},
|
||||
{"name": "HAUNTER", "type": "0803", "exp": "117360", 'bst': [45, 50, 45, 95, 115], "gr" : "mediumslow"},
|
||||
{"name": "GENGAR", "type": "0803", "exp": "117360", 'bst': [60, 65, 60, 110, 130], "gr" : "mediumslow"},
|
||||
{"name": "ONIX", "type": "0504", "exp": "125000", 'bst': [35, 45, 160, 70, 30 ], "gr" : "mediumfast"},
|
||||
{"name": "DROWZEE", "type": "1818", "exp": "125000", 'bst': [60, 48, 45, 42, 90 ], "gr" : "mediumfast"},
|
||||
{"name": "HYPNO", "type": "1818", "exp": "125000", 'bst': [85, 73, 70, 67, 115], "gr" : "mediumfast"},
|
||||
{"name": "KRABBY", "type": "1515", "exp": "125000", 'bst': [30, 105, 90, 50, 25 ], "gr" : "mediumfast"},
|
||||
{"name": "KINGLER", "type": "1515", "exp": "125000", 'bst': [55, 130, 115, 75, 50 ], "gr" : "mediumfast"},
|
||||
{"name": "VOLTORB", "type": "1717", "exp": "125000", 'bst': [40, 30, 50, 100, 55 ], "gr" : "mediumfast"},
|
||||
{"name": "ELECTRODE", "type": "1717", "exp": "125000", 'bst': [60, 50, 70, 140, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "EXEGGCUTE", "type": "1618", "exp": "156250", 'bst': [60, 40, 80, 40, 60 ], "gr" : "slow"},
|
||||
{"name": "EXEGGUTOR", "type": "1618", "exp": "156250", 'bst': [95, 95, 85, 55, 125], "gr" : "slow"},
|
||||
{"name": "CUBONE", "type": "0404", "exp": "125000", 'bst': [50, 50, 95, 35, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "MAROWAK", "type": "0404", "exp": "125000", 'bst': [60, 80, 110, 45, 50 ], "gr" : "mediumfast"},
|
||||
{"name": "HITMONLEE", "type": "0101", "exp": "125000", 'bst': [50, 120, 53, 87, 35 ], "gr" : "mediumfast"},
|
||||
{"name": "HITMONCHAN", "type": "0101", "exp": "125000", 'bst': [50, 105, 79, 76, 35 ], "gr" : "mediumfast"},
|
||||
{"name": "LICKITUNG", "type": "0000", "exp": "125000", 'bst': [90, 55, 75, 30, 60 ], "gr" : "mediumfast"},
|
||||
{"name": "KOFFING", "type": "0303", "exp": "125000", 'bst': [40, 65, 95, 35, 60 ], "gr" : "mediumfast"},
|
||||
{"name": "WEEZING", "type": "0303", "exp": "125000", 'bst': [65, 90, 120, 60, 85 ], "gr" : "mediumfast"},
|
||||
{"name": "RHYHORN", "type": "0405", "exp": "156250", 'bst': [80, 85, 95, 25, 30 ], "gr" : "slow"},
|
||||
{"name": "RHYDON", "type": "0405", "exp": "156250", 'bst': [105, 130, 120, 40, 45 ], "gr" : "slow"},
|
||||
{"name": "CHANSEY", "type": "0000", "exp": "100000", 'bst': [250, 5, 5, 50, 105], "gr" : "fast"},
|
||||
{"name": "TANGELA", "type": "1616", "exp": "125000", 'bst': [65, 55, 115, 60, 100], "gr" : "mediumfast"},
|
||||
{"name": "KANGASKHAN", "type": "0000", "exp": "125000", 'bst': [105, 95, 80, 90, 40 ], "gr" : "mediumfast"},
|
||||
{"name": "HORSEA", "type": "1515", "exp": "125000", 'bst': [30, 40, 70, 60, 70 ], "gr" : "mediumfast"},
|
||||
{"name": "SEADRA", "type": "1515", "exp": "125000", 'bst': [55, 65, 95, 85, 95 ], "gr" : "mediumfast"},
|
||||
{"name": "GOLDEEN", "type": "1515", "exp": "125000", 'bst': [45, 67, 60, 63, 50 ], "gr" : "mediumfast"},
|
||||
{"name": "SEAKING", "type": "1515", "exp": "125000", 'bst': [80, 92, 65, 68, 80 ], "gr" : "mediumfast"},
|
||||
{"name": "STARYU", "type": "1515", "exp": "156250", 'bst': [30, 45, 55, 85, 70 ], "gr" : "slow"},
|
||||
{"name": "STARMIE", "type": "1518", "exp": "156250", 'bst': [60, 75, 85, 115, 100], "gr" : "slow"},
|
||||
{"name": "MR. MIME", "type": "1818", "exp": "125000", 'bst': [40, 45, 65, 90, 100], "gr" : "mediumfast"},
|
||||
{"name": "SCYTHER", "type": "0702", "exp": "125000", 'bst': [70, 110, 80, 105, 55 ], "gr" : "mediumfast"},
|
||||
{"name": "JYNX", "type": "1918", "exp": "125000", 'bst': [65, 50, 35, 95, 95 ], "gr" : "mediumfast"},
|
||||
{"name": "ELECTABUZZ", "type": "1717", "exp": "125000", 'bst': [65, 83, 57, 105, 85 ], "gr" : "mediumfast"},
|
||||
{"name": "MAGMAR", "type": "1414", "exp": "125000", 'bst': [65, 95, 57, 93, 85 ], "gr" : "mediumfast"},
|
||||
{"name": "PINSIR", "type": "0707", "exp": "156250", 'bst': [65, 125, 100, 85, 55 ], "gr" : "slow"},
|
||||
{"name": "TAUROS", "type": "0000", "exp": "156250", 'bst': [75, 100, 95, 110, 70 ], "gr" : "slow"},
|
||||
{"name": "MAGIKARP", "type": "1515", "exp": "156250", 'bst': [20, 10, 55, 80, 20 ], "gr" : "slow"},
|
||||
{"name": "GYARADOS", "type": "1502", "exp": "156250", 'bst': [95, 125, 79, 81, 100], "gr" : "slow"},
|
||||
{"name": "LAPRAS", "type": "1519", "exp": "156250", 'bst': [130, 85, 80, 60, 95 ], "gr" : "slow"},
|
||||
{"name": "DITTO", "type": "0000", "exp": "125000", 'bst': [48, 48, 48, 48, 48 ], "gr" : "mediumfast"},
|
||||
{"name": "EEVEE", "type": "0000", "exp": "125000", 'bst': [55, 55, 50, 55, 65 ], "gr" : "mediumfast"},
|
||||
{"name": "VAPOREON", "type": "1515", "exp": "125000", 'bst': [130, 65, 60, 65, 110], "gr" : "mediumfast"},
|
||||
{"name": "JOLTEON", "type": "1717", "exp": "125000", 'bst': [65, 65, 60, 130, 110], "gr" : "mediumfast"},
|
||||
{"name": "FLAREON", "type": "1414", "exp": "125000", 'bst': [65, 130, 60, 65, 110], "gr" : "mediumfast"},
|
||||
{"name": "PORYGON", "type": "0000", "exp": "125000", 'bst': [65, 60, 70, 40, 75 ], "gr" : "mediumfast"},
|
||||
{"name": "OMANYTE", "type": "0515", "exp": "125000", 'bst': [35, 40, 100, 35, 90 ], "gr" : "mediumfast"},
|
||||
{"name": "OMASTAR", "type": "0515", "exp": "125000", 'bst': [70, 60, 125, 55, 115], "gr" : "mediumfast"},
|
||||
{"name": "KABUTO", "type": "0515", "exp": "125000", 'bst': [30, 80, 90, 55, 45 ], "gr" : "mediumfast"},
|
||||
{"name": "KABUTOPS", "type": "0515", "exp": "125000", 'bst': [60, 115, 105, 80, 70 ], "gr" : "mediumfast"},
|
||||
{"name": "AERODACTYL", "type": "0502", "exp": "156250", 'bst': [80, 105, 65, 130, 60 ], "gr" : "slow"},
|
||||
{"name": "SNORLAX", "type": "0000", "exp": "156250", 'bst': [160, 110, 65, 30, 65 ], "gr" : "slow"},
|
||||
{"name": "ARTICUNO", "type": "1902", "exp": "156250", 'bst': [90, 85, 100, 85, 125], "gr" : "slow"},
|
||||
{"name": "ZAPDOS", "type": "1702", "exp": "156250", 'bst': [90, 90, 85, 100, 125], "gr" : "slow"},
|
||||
{"name": "MOLTRES", "type": "1402", "exp": "156250", 'bst': [90, 100, 90, 90, 125], "gr" : "slow"},
|
||||
{"name": "DRATINI", "type": "1A1A", "exp": "156250", 'bst': [41, 64, 45, 50, 50 ], "gr" : "slow"},
|
||||
{"name": "DRAGONAIR", "type": "1A1A", "exp": "156250", 'bst': [61, 84, 65, 70, 70 ], "gr" : "slow"},
|
||||
{"name": "DRAGONITE", "type": "1A02", "exp": "156250", 'bst': [91, 134, 95, 80, 100], "gr" : "slow"},
|
||||
{"name": "MEWTWO", "type": "1818", "exp": "156250", 'bst': [106, 110, 90, 130, 154], "gr" : "slow"},
|
||||
{"name": "MEW", "type": "1818", "exp": "117360", 'bst': [100, 100, 100, 100, 100], "gr" : "slow"}
|
||||
]
|
||||
|
||||
GLC_list = [
|
||||
{"name": "BULBASAUR", "type": "1603", "exp": "117360", 'bst': [45, 49, 49, 45, 65 ], "Moveset": [73, 92, 34, 75]},
|
||||
{"name": "IVYSAUR", "type": "1603", "exp": "117360", 'bst': [60, 62, 63, 60, 80 ], "Moveset": [75, 79, 72, 38]},
|
||||
{"name": "VENUSAUR", "type": "1603", "exp": "117360", 'bst': [80, 82, 83, 80, 100], "Moveset": [73, 77, 76, 36]},
|
||||
{"name": "CHARMANDER", "type": "1414", "exp": "117360", 'bst': [39, 52, 43, 65, 50 ], "Moveset": [53, 163, 69, 91]},
|
||||
{"name": "CHARMELEON", "type": "1414", "exp": "117360", 'bst': [58, 64, 58, 80, 65 ], "Moveset": [126, 68, 82, 163]},
|
||||
{"name": "CHARIZARD", "type": "1402", "exp": "117360", 'bst': [78, 84, 78, 100, 85 ], "Moveset": [19, 91, 83, 102]},
|
||||
{"name": "SQUIRTLE", "type": "1515", "exp": "117360", 'bst': [44, 48, 65, 43, 50 ], "Moveset": [57, 59, 91, 69]},
|
||||
{"name": "WARTORTLE", "type": "1515", "exp": "117360", 'bst': [59, 63, 80, 58, 65 ], "Moveset": [57, 68, 66, 58]},
|
||||
{"name": "BLASTOISE", "type": "1515", "exp": "117360", 'bst': [79, 83, 100, 78, 85 ], "Moveset": [56, 117, 70, 110]},
|
||||
{"name": "CATERPIE", "type": "0707", "exp": "125000", 'bst': [45, 30, 35, 45, 20 ], "Moveset": [33, 81, 0, 0]},
|
||||
{"name": "METAPOD", "type": "0707", "exp": "125000", 'bst': [50, 20, 55, 30, 25 ], "Moveset": [33, 81, 0, 0]},
|
||||
{"name": "BUTTERFREE", "type": "0702", "exp": "125000", 'bst': [60, 45, 50, 70, 80 ], "Moveset": [94, 48, 63, 72]},
|
||||
{"name": "WEEDLE", "type": "0703", "exp": "125000", 'bst': [40, 35, 30, 50, 20 ], "Moveset": [81, 40, 0, 0]},
|
||||
{"name": "KAKUNA", "type": "0703", "exp": "125000", 'bst': [45, 25, 50, 35, 25 ], "Moveset": [81, 40, 0, 0]},
|
||||
{"name": "BEEDRILL", "type": "0703", "exp": "125000", 'bst': [65, 80, 40, 75, 45 ], "Moveset": [41, 116, 38, 72]},
|
||||
{"name": "PIDGEY", "type": "0002", "exp": "117360", 'bst': [40, 45, 40, 56, 35 ], "Moveset": [19, 38, 92, 104]},
|
||||
{"name": "PIDGEOTTO", "type": "0002", "exp": "117360", 'bst': [63, 60, 55, 71, 50 ], "Moveset": [19, 97, 28, 36]},
|
||||
{"name": "PIDGEOT", "type": "0002", "exp": "117360", 'bst': [83, 80, 75, 91, 70 ], "Moveset": [119, 19, 98, 63]},
|
||||
{"name": "RATTATA", "type": "0000", "exp": "125000", 'bst': [30, 56, 35, 72, 25 ], "Moveset": [162, 158, 59, 91]},
|
||||
{"name": "RATICATE", "type": "0000", "exp": "125000", 'bst': [55, 81, 60, 97, 50 ], "Moveset": [158, 61, 116, 91]},
|
||||
{"name": "SPEAROW", "type": "0002", "exp": "125000", 'bst': [40, 60, 30, 70, 31 ], "Moveset": [65, 119, 104, 38]},
|
||||
{"name": "FEAROW", "type": "0002", "exp": "125000", 'bst': [65, 90, 65, 100, 61 ], "Moveset": [97, 104, 19, 129]},
|
||||
{"name": "EKANS", "type": "0303", "exp": "125000", 'bst': [35, 60, 44, 55, 40 ], "Moveset": [89, 70, 137, 51]},
|
||||
{"name": "ARBOK", "type": "0303", "exp": "125000", 'bst': [60, 85, 69, 80, 65 ], "Moveset": [137, 157, 51, 91]},
|
||||
{"name": "PIKACHU", "type": "1717", "exp": "125000", 'bst': [35, 55, 30, 90, 50 ], "Moveset": [85, 69, 86, 148]},
|
||||
{"name": "RAICHU", "type": "1717", "exp": "125000", 'bst': [60, 90, 55, 100, 90 ], "Moveset": [87, 86, 45, 25]},
|
||||
{"name": "SANDSHREW", "type": "0404", "exp": "125000", 'bst': [50, 75, 85, 40, 30 ], "Moveset": [89, 163, 69, 28]},
|
||||
{"name": "SANDSLASH", "type": "0404", "exp": "125000", 'bst': [75, 100, 110, 65, 55 ], "Moveset": [91, 157, 28, 154]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "117360", 'bst': [55, 47, 52, 41, 40 ], "Moveset": [34, 92, 85, 59]},
|
||||
{"name": "NIDORINA", "type": "0303", "exp": "117360", 'bst': [70, 62, 67, 56, 55 ], "Moveset": [87, 58, 92, 34]},
|
||||
{"name": "NIDOQUEEN", "type": "0304", "exp": "117360", 'bst': [90, 82, 87, 76, 75 ], "Moveset": [24, 92, 34, 87]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "117360", 'bst': [46, 57, 40, 50, 40 ], "Moveset": [32, 92, 85, 59]},
|
||||
{"name": "NIDORINO", "type": "0303", "exp": "117360", 'bst': [61, 72, 57, 65, 55 ], "Moveset": [92, 32, 58, 38]},
|
||||
{"name": "NIDOKING", "type": "0304", "exp": "117360", 'bst': [81, 92, 77, 85, 75 ], "Moveset": [89, 32, 24, 40]},
|
||||
{"name": "CLEFAIRY", "type": "0000", "exp": "100000", 'bst': [70, 45, 48, 35, 60 ], "Moveset": [85, 59, 34, 118]},
|
||||
{"name": "CLEFABLE", "type": "0000", "exp": "100000", 'bst': [95, 70, 73, 60, 85 ], "Moveset": [47, 118, 161, 58]},
|
||||
{"name": "VULPIX", "type": "1414", "exp": "125000", 'bst': [38, 41, 40, 65, 65 ], "Moveset": [53, 115, 109, 91]},
|
||||
{"name": "NINETALES", "type": "1414", "exp": "125000", 'bst': [73, 76, 75, 100, 100], "Moveset": [109, 91, 83, 117]},
|
||||
{"name": "JIGGLYPUFF", "type": "0000", "exp": "100000", 'bst': [115, 45, 20, 20, 25 ], "Moveset": [47, 34, 69, 94]},
|
||||
{"name": "WIGGLYTUFF", "type": "0000", "exp": "100000", 'bst': [140, 70, 45, 45, 50 ], "Moveset": [47, 70, 50, 94]},
|
||||
{"name": "ZUBAT", "type": "0302", "exp": "125000", 'bst': [40, 45, 35, 55, 40 ], "Moveset": [109, 72, 92, 38]},
|
||||
{"name": "GOLBAT", "type": "0302", "exp": "125000", 'bst': [75, 80, 70, 90, 75 ], "Moveset": [109, 72, 63, 114]},
|
||||
{"name": "ODDISH", "type": "1603", "exp": "117360", 'bst': [45, 50, 55, 30, 75 ], "Moveset": [78, 80, 72, 38]},
|
||||
{"name": "GLOOM", "type": "1603", "exp": "117360", 'bst': [60, 65, 70, 40, 85 ], "Moveset": [78, 80, 51, 36]},
|
||||
{"name": "VILEPLUME", "type": "1603", "exp": "117360", 'bst': [75, 80, 85, 50, 100], "Moveset": [80, 51, 15, 78]},
|
||||
{"name": "PARAS", "type": "0716", "exp": "125000", 'bst': [35, 70, 55, 25, 55 ], "Moveset": [147, 163, 91, 72]},
|
||||
{"name": "PARASECT", "type": "0716", "exp": "125000", 'bst': [60, 95, 80, 30, 80 ], "Moveset": [147, 91, 74, 72]},
|
||||
{"name": "VENONAT", "type": "0703", "exp": "125000", 'bst': [60, 55, 50, 45, 40 ], "Moveset": [94, 72, 38, 92]},
|
||||
{"name": "VENOMOTH", "type": "0703", "exp": "125000", 'bst': [70, 65, 60, 90, 90 ], "Moveset": [94, 48, 129, 92]},
|
||||
{"name": "DIGLETT", "type": "0404", "exp": "125000", 'bst': [10, 55, 25, 95, 45 ], "Moveset": [89, 163, 90, 157]},
|
||||
{"name": "DUGTRIO", "type": "0404", "exp": "125000", 'bst': [35, 80, 50, 120, 70 ], "Moveset": [91, 28, 157, 164]},
|
||||
{"name": "MEOWTH", "type": "0000", "exp": "125000", 'bst': [40, 45, 35, 90, 40 ], "Moveset": [163, 85, 61, 104]},
|
||||
{"name": "PERSIAN", "type": "0000", "exp": "125000", 'bst': [65, 70, 60, 115, 65 ], "Moveset": [85, 38, 117, 103]},
|
||||
{"name": "PSYDUCK", "type": "1515", "exp": "125000", 'bst': [50, 52, 48, 55, 50 ], "Moveset": [57, 69, 91, 59]},
|
||||
{"name": "GOLDUCK", "type": "1515", "exp": "125000", 'bst': [80, 82, 78, 85, 80 ], "Moveset": [50, 57, 93, 25]},
|
||||
{"name": "MANKEY", "type": "0101", "exp": "125000", 'bst': [40, 80, 35, 70, 35 ], "Moveset": [66, 91, 69, 70]},
|
||||
{"name": "PRIMEAPE", "type": "0101", "exp": "125000", 'bst': [65, 105, 60, 95, 60 ], "Moveset": [69, 103, 5, 67]},
|
||||
{"name": "GROWLITHE", "type": "1414", "exp": "156250", 'bst': [55, 70, 45, 60, 50 ], "Moveset": [53, 34, 115, 91]},
|
||||
{"name": "ARCANINE", "type": "1414", "exp": "156250", 'bst': [90, 110, 80, 95, 80 ], "Moveset": [126, 36, 43, 97]},
|
||||
{"name": "POLIWAG", "type": "1515", "exp": "117360", 'bst': [40, 50, 40, 90, 40 ], "Moveset": [34, 59, 57, 133]},
|
||||
{"name": "POLIWHIRL", "type": "1515", "exp": "117360", 'bst': [65, 65, 65, 90, 50 ], "Moveset": [95, 56, 70, 89]},
|
||||
{"name": "POLIWRATH", "type": "1501", "exp": "117360", 'bst': [90, 85, 95, 70, 70 ], "Moveset": [95, 66, 102, 57]},
|
||||
{"name": "ABRA", "type": "1818", "exp": "117360", 'bst': [25, 20, 15, 90, 105], "Moveset": [94, 69, 115, 92]},
|
||||
{"name": "KADABRA", "type": "1818", "exp": "117360", 'bst': [40, 35, 30, 105, 120], "Moveset": [60, 86, 105, 69]},
|
||||
{"name": "ALAKAZAM", "type": "1818", "exp": "117360", 'bst': [55, 50, 45, 120, 135], "Moveset": [93, 115, 134, 91]},
|
||||
{"name": "MACHOP", "type": "0101", "exp": "117360", 'bst': [70, 80, 50, 35, 35 ], "Moveset": [66, 157, 89, 34]},
|
||||
{"name": "MACHOKE", "type": "0101", "exp": "117360", 'bst': [80, 100, 70, 45, 50 ], "Moveset": [89, 66, 70, 116]},
|
||||
{"name": "MACHAMP", "type": "0101", "exp": "117360", 'bst': [90, 130, 80, 55, 65 ], "Moveset": [2, 67, 126, 91]},
|
||||
{"name": "BELLSPROUT", "type": "1603", "exp": "117360", 'bst': [50, 75, 35, 40, 70 ], "Moveset": [51, 92, 74, 75]},
|
||||
{"name": "WEEPINBELL", "type": "1603", "exp": "117360", 'bst': [65, 90, 50, 55, 85 ], "Moveset": [75, 51, 21, 92]},
|
||||
{"name": "VICTREEBEL", "type": "1603", "exp": "117360", 'bst': [80, 105, 65, 70, 100], "Moveset": [72, 51, 35, 92]},
|
||||
{"name": "TENTACOOL", "type": "1503", "exp": "156250", 'bst': [40, 40, 35, 70, 100], "Moveset": [57, 72, 51, 92]},
|
||||
{"name": "TENTACRUEL", "type": "1503", "exp": "156250", 'bst': [80, 70, 65, 100, 120], "Moveset": [51, 103, 56, 15]},
|
||||
{"name": "GEODUDE", "type": "0504", "exp": "117360", 'bst': [40, 80, 100, 20, 30 ], "Moveset": [89, 34, 157, 153]},
|
||||
{"name": "GRAVELER", "type": "0504", "exp": "117360", 'bst': [55, 95, 115, 35, 45 ], "Moveset": [157, 89, 70, 120]},
|
||||
{"name": "GOLEM", "type": "0504", "exp": "117360", 'bst': [80, 110, 130, 45, 55 ], "Moveset": [88, 5, 91, 120]},
|
||||
{"name": "PONYTA", "type": "1414", "exp": "125000", 'bst': [50, 85, 55, 90, 65 ], "Moveset": [126, 115, 32, 34]},
|
||||
{"name": "RAPIDASH", "type": "1414", "exp": "125000", 'bst': [65, 100, 70, 105, 80 ], "Moveset": [23, 97, 92, 83]},
|
||||
{"name": "SLOWPOKE", "type": "1518", "exp": "125000", 'bst': [90, 65, 65, 15, 40 ], "Moveset": [57, 94, 86, 133]},
|
||||
{"name": "SLOWBRO", "type": "1518", "exp": "125000", 'bst': [95, 75, 110, 30, 80 ], "Moveset": [57, 29, 91, 50]},
|
||||
{"name": "MAGNEMITE", "type": "1717", "exp": "125000", 'bst': [25, 35, 70, 45, 95 ], "Moveset": [85, 86, 48, 38]},
|
||||
{"name": "MAGNETON", "type": "1717", "exp": "125000", 'bst': [50, 60, 95, 70, 120], "Moveset": [86, 48, 87, 103]},
|
||||
{"name": "FARFETCH'D", "type": "0002", "exp": "125000", 'bst': [52, 65, 55, 60, 58 ], "Moveset": [163, 28, 92, 19]},
|
||||
{"name": "DODUO", "type": "0002", "exp": "125000", 'bst': [35, 85, 45, 75, 35 ], "Moveset": [65, 161, 104, 115]},
|
||||
{"name": "DODRIO", "type": "0002", "exp": "125000", 'bst': [60, 110, 70, 100, 60 ], "Moveset": [19, 161, 115, 164]},
|
||||
{"name": "SEEL", "type": "1515", "exp": "125000", 'bst': [65, 45, 55, 45, 70 ], "Moveset": [58, 70, 104, 57]},
|
||||
{"name": "DEWGONG", "type": "1519", "exp": "125000", 'bst': [90, 70, 80, 70, 95 ], "Moveset": [36, 62, 156, 57]},
|
||||
{"name": "GRIMER", "type": "0303", "exp": "125000", 'bst': [80, 80, 50, 25, 40 ], "Moveset": [124, 34, 153, 72]},
|
||||
{"name": "MUK", "type": "0303", "exp": "125000", 'bst': [105, 105, 75, 50, 65 ], "Moveset": [124, 87, 72, 103]},
|
||||
{"name": "SHELLDER", "type": "1515", "exp": "156250", 'bst': [30, 65, 100, 40, 45 ], "Moveset": [58, 153, 57, 161]},
|
||||
{"name": "CLOYSTER", "type": "1519", "exp": "156250", 'bst': [50, 95, 180, 70, 85 ], "Moveset": [62, 120, 128, 131]},
|
||||
{"name": "GASTLY", "type": "0803", "exp": "117360", 'bst': [30, 35, 30, 80, 100], "Moveset": [94, 101, 153, 109]},
|
||||
{"name": "HAUNTER", "type": "0803", "exp": "117360", 'bst': [45, 50, 45, 95, 115], "Moveset": [94, 85, 120, 109]},
|
||||
{"name": "GENGAR", "type": "0803", "exp": "117360", 'bst': [60, 65, 60, 110, 130], "Moveset": [95, 138, 85, 109]},
|
||||
{"name": "ONIX", "type": "0504", "exp": "125000", 'bst': [35, 45, 160, 70, 30 ], "Moveset": [89, 157, 153, 103]},
|
||||
{"name": "DROWZEE", "type": "1818", "exp": "125000", 'bst': [60, 48, 45, 42, 90 ], "Moveset": [95, 69, 94, 115]},
|
||||
{"name": "HYPNO", "type": "1818", "exp": "125000", 'bst': [85, 73, 70, 67, 115], "Moveset": [95, 138, 68, 29]},
|
||||
{"name": "KRABBY", "type": "1515", "exp": "125000", 'bst': [30, 105, 90, 50, 25 ], "Moveset": [152, 92, 34, 59]},
|
||||
{"name": "KINGLER", "type": "1515", "exp": "125000", 'bst': [55, 130, 115, 75, 50 ], "Moveset": [152, 70, 117, 43]},
|
||||
{"name": "VOLTORB", "type": "1717", "exp": "125000", 'bst': [40, 30, 50, 100, 55 ], "Moveset": [85, 86, 115, 153]},
|
||||
{"name": "ELECTRODE", "type": "1717", "exp": "125000", 'bst': [60, 50, 70, 140, 80 ], "Moveset": [87, 92, 129, 120]},
|
||||
{"name": "EXEGGCUTE", "type": "1618", "exp": "156250", 'bst': [60, 40, 80, 40, 60 ], "Moveset": [73, 76, 121, 94]},
|
||||
{"name": "EXEGGUTOR", "type": "1618", "exp": "156250", 'bst': [95, 95, 85, 55, 125], "Moveset": [73, 95, 72, 121]},
|
||||
{"name": "CUBONE", "type": "0404", "exp": "125000", 'bst': [50, 50, 95, 35, 40 ], "Moveset": [155, 34, 58, 69]},
|
||||
{"name": "MAROWAK", "type": "0404", "exp": "125000", 'bst': [60, 80, 110, 45, 50 ], "Moveset": [155, 37, 126, 116]},
|
||||
{"name": "HITMONLEE", "type": "0101", "exp": "125000", 'bst': [50, 120, 53, 87, 35 ], "Moveset": [136, 70, 68, 116]},
|
||||
{"name": "HITMONCHAN", "type": "0101", "exp": "125000", 'bst': [50, 105, 79, 76, 35 ], "Moveset": [66, 70, 8, 9]},
|
||||
{"name": "LICKITUNG", "type": "0000", "exp": "125000", 'bst': [90, 55, 75, 30, 60 ], "Moveset": [89, 34, 103, 48]},
|
||||
{"name": "KOFFING", "type": "0303", "exp": "125000", 'bst': [40, 65, 95, 35, 60 ], "Moveset": [124, 92, 85, 126]},
|
||||
{"name": "WEEZING", "type": "0303", "exp": "125000", 'bst': [65, 90, 120, 60, 85 ], "Moveset": [124, 63, 114, 108]},
|
||||
{"name": "RHYHORN", "type": "0405", "exp": "156250", 'bst': [80, 85, 95, 25, 30 ], "Moveset": [34, 89, 87, 157]},
|
||||
{"name": "RHYDON", "type": "0405", "exp": "156250", 'bst': [105, 130, 120, 40, 45 ], "Moveset": [70, 91, 57, 164]},
|
||||
{"name": "CHANSEY", "type": "0000", "exp": "100000", 'bst': [250, 5, 5, 50, 105], "Moveset": [58, 87, 38, 115]},
|
||||
{"name": "TANGELA", "type": "1616", "exp": "125000", 'bst': [65, 55, 115, 60, 100], "Moveset": [77, 36, 72, 74]},
|
||||
{"name": "KANGASKHAN", "type": "0000", "exp": "125000", 'bst': [105, 95, 80, 90, 40 ], "Moveset": [146, 157, 43, 85]},
|
||||
{"name": "HORSEA", "type": "1515", "exp": "125000", 'bst': [30, 40, 70, 60, 70 ], "Moveset": [56, 92, 108, 58]},
|
||||
{"name": "SEADRA", "type": "1515", "exp": "125000", 'bst': [55, 65, 95, 85, 95 ], "Moveset": [108, 56, 129, 97]},
|
||||
{"name": "GOLDEEN", "type": "1515", "exp": "125000", 'bst': [45, 67, 60, 63, 50 ], "Moveset": [57, 92, 38, 58]},
|
||||
{"name": "SEAKING", "type": "1515", "exp": "125000", 'bst': [80, 92, 65, 68, 80 ], "Moveset": [127, 59, 48, 30]},
|
||||
{"name": "STARYU", "type": "1515", "exp": "156250", 'bst': [30, 45, 55, 85, 70 ], "Moveset": [85, 105, 57, 94]},
|
||||
{"name": "STARMIE", "type": "1518", "exp": "156250", 'bst': [60, 75, 85, 115, 100], "Moveset": [61, 87, 107, 161]},
|
||||
{"name": "MR. MIME", "type": "1818", "exp": "125000", 'bst': [40, 45, 65, 90, 100], "Moveset": [112, 94, 69, 68]},
|
||||
{"name": "SCYTHER", "type": "0702", "exp": "125000", 'bst': [70, 110, 80, 105, 55 ], "Moveset": [104, 17, 163, 92]},
|
||||
{"name": "JYNX", "type": "1918", "exp": "125000", 'bst': [65, 50, 35, 95, 95 ], "Moveset": [142, 8, 37, 94]},
|
||||
{"name": "ELECTABUZZ", "type": "1717", "exp": "125000", 'bst': [65, 83, 57, 105, 85 ], "Moveset": [9, 148, 86, 69]},
|
||||
{"name": "MAGMAR", "type": "1414", "exp": "125000", 'bst': [65, 95, 57, 93, 85 ], "Moveset": [109, 7, 108, 70]},
|
||||
{"name": "PINSIR", "type": "0707", "exp": "156250", 'bst': [65, 125, 100, 85, 55 ], "Moveset": [163, 102, 106, 12]},
|
||||
{"name": "TAUROS", "type": "0000", "exp": "156250", 'bst': [75, 100, 95, 110, 70 ], "Moveset": [70, 117, 126, 39]},
|
||||
{"name": "MAGIKARP", "type": "1515", "exp": "156250", 'bst': [20, 10, 55, 80, 20 ], "Moveset": [150, 33, 0, 0]},
|
||||
{"name": "GYARADOS", "type": "1502", "exp": "156250", 'bst': [95, 125, 79, 81, 100], "Moveset": [82, 56, 36, 43]},
|
||||
{"name": "LAPRAS", "type": "1519", "exp": "156250", 'bst': [130, 85, 80, 60, 95 ], "Moveset": [109, 47, 58, 61]},
|
||||
{"name": "DITTO", "type": "0000", "exp": "125000", 'bst': [48, 48, 48, 48, 48 ], "Moveset": [144, 0, 0, 0]},
|
||||
{"name": "EEVEE", "type": "0000", "exp": "125000", 'bst': [55, 55, 50, 55, 65 ], "Moveset": [92, 34, 28, 116]},
|
||||
{"name": "VAPOREON", "type": "1515", "exp": "125000", 'bst': [130, 65, 60, 65, 110], "Moveset": [151, 62, 57, 98]},
|
||||
{"name": "JOLTEON", "type": "1717", "exp": "125000", 'bst': [65, 65, 60, 130, 110], "Moveset": [87, 92, 42, 24]},
|
||||
{"name": "FLAREON", "type": "1414", "exp": "125000", 'bst': [65, 130, 60, 65, 110], "Moveset": [126, 28, 92, 38]},
|
||||
{"name": "PORYGON", "type": "0000", "exp": "125000", 'bst': [65, 60, 70, 40, 75 ], "Moveset": [160, 94, 105, 161]},
|
||||
{"name": "OMANYTE", "type": "0515", "exp": "125000", 'bst': [35, 40, 100, 35, 90 ], "Moveset": [59, 57, 38, 104]},
|
||||
{"name": "OMASTAR", "type": "0515", "exp": "125000", 'bst': [70, 60, 125, 55, 115], "Moveset": [56, 131, 43, 110]},
|
||||
{"name": "KABUTO", "type": "0515", "exp": "125000", 'bst': [30, 80, 90, 55, 45 ], "Moveset": [163, 56, 58, 92]},
|
||||
{"name": "KABUTOPS", "type": "0515", "exp": "125000", 'bst': [60, 115, 105, 80, 70 ], "Moveset": [56, 14, 66, 36]},
|
||||
{"name": "AERODACTYL", "type": "0502", "exp": "156250", 'bst': [80, 105, 65, 130, 60 ], "Moveset": [48, 36, 19, 115]},
|
||||
{"name": "SNORLAX", "type": "0000", "exp": "156250", 'bst': [160, 110, 65, 30, 65 ], "Moveset": [87, 29, 156, 117]},
|
||||
{"name": "ARTICUNO", "type": "1902", "exp": "156250", 'bst': [90, 85, 100, 85, 125], "Moveset": [58, 143, 99, 102]},
|
||||
{"name": "ZAPDOS", "type": "1702", "exp": "156250", 'bst': [90, 90, 85, 100, 125], "Moveset": [87, 143, 164, 148]},
|
||||
{"name": "MOLTRES", "type": "1402", "exp": "156250", 'bst': [90, 100, 90, 90, 125], "Moveset": [126, 143, 36, 117]},
|
||||
{"name": "DRATINI", "type": "1A1A", "exp": "156250", 'bst': [41, 64, 45, 50, 50 ], "Moveset": [34, 82, 59, 86]},
|
||||
{"name": "DRAGONAIR", "type": "1A1A", "exp": "156250", 'bst': [61, 84, 65, 70, 70 ], "Moveset": [63, 85, 126, 86]},
|
||||
{"name": "DRAGONITE", "type": "1A02", "exp": "156250", 'bst': [91, 134, 95, 80, 100], "Moveset": [21, 102, 57, 164]},
|
||||
]
|
||||
|
||||
poke_cup_list = [
|
||||
{"name": "BULBASAUR", "type": "1603", "exp": "117360", 'bst': [45, 49, 49, 45, 65 ], "Moveset": [73, 92, 34, 75]},
|
||||
{"name": "IVYSAUR", "type": "1603", "exp": "117360", 'bst': [60, 62, 63, 60, 80 ], "Moveset": [75, 79, 74, 38]},
|
||||
{"name": "VENUSAUR", "type": "1603", "exp": "117360", 'bst': [80, 82, 83, 80, 100], "Moveset": [73, 77, 76, 36]},
|
||||
{"name": "CHARMANDER", "type": "1414", "exp": "117360", 'bst': [39, 52, 43, 65, 50 ], "Moveset": [53, 163, 91, 83]},
|
||||
{"name": "CHARMELEON", "type": "1414", "exp": "117360", 'bst': [58, 64, 58, 80, 65 ], "Moveset": [53, 68, 69, 70]},
|
||||
{"name": "CHARIZARD", "type": "1402", "exp": "117360", 'bst': [78, 84, 78, 100, 85 ], "Moveset": [19, 14, 83, 126]},
|
||||
{"name": "SQUIRTLE", "type": "1515", "exp": "117360", 'bst': [44, 48, 65, 43, 50 ], "Moveset": [57, 59, 34, 91]},
|
||||
{"name": "WARTORTLE", "type": "1515", "exp": "117360", 'bst': [59, 63, 80, 58, 65 ], "Moveset": [57, 70, 156, 58]},
|
||||
{"name": "BLASTOISE", "type": "1515", "exp": "117360", 'bst': [79, 83, 100, 78, 85 ], "Moveset": [56, 130, 110, 69]},
|
||||
{"name": "CATERPIE", "type": "0707", "exp": "125000", 'bst': [45, 30, 35, 45, 20 ], "Moveset": [81, 33, 0, 0]},
|
||||
{"name": "METAPOD", "type": "0707", "exp": "125000", 'bst': [50, 20, 55, 30, 25 ], "Moveset": [81, 33, 0, 0]},
|
||||
{"name": "BUTTERFREE", "type": "0702", "exp": "125000", 'bst': [60, 45, 50, 70, 80 ], "Moveset": [94, 48, 72, 78]},
|
||||
{"name": "WEEDLE", "type": "0703", "exp": "125000", 'bst': [40, 35, 30, 50, 20 ], "Moveset": [81, 40, 0, 0]},
|
||||
{"name": "KAKUNA", "type": "0703", "exp": "125000", 'bst': [45, 25, 50, 35, 25 ], "Moveset": [81, 40, 0, 0]},
|
||||
{"name": "BEEDRILL", "type": "0703", "exp": "125000", 'bst': [65, 80, 40, 75, 45 ], "Moveset": [41, 63, 92, 116]},
|
||||
{"name": "PIDGEY", "type": "0002", "exp": "117360", 'bst': [40, 45, 40, 56, 35 ], "Moveset": [19, 92, 38, 104]},
|
||||
{"name": "PIDGEOTTO", "type": "0002", "exp": "117360", 'bst': [63, 60, 55, 71, 50 ], "Moveset": [19, 98, 28, 36]},
|
||||
{"name": "PIDGEOT", "type": "0002", "exp": "117360", 'bst': [83, 80, 75, 91, 70 ], "Moveset": [119, 19, 98, 28]},
|
||||
{"name": "RATTATA", "type": "0000", "exp": "125000", 'bst': [30, 56, 35, 72, 25 ], "Moveset": [162, 59, 98, 158]},
|
||||
{"name": "RATICATE", "type": "0000", "exp": "125000", 'bst': [55, 81, 60, 97, 50 ], "Moveset": [158, 63, 116, 87]},
|
||||
{"name": "SPEAROW", "type": "0002", "exp": "125000", 'bst': [40, 60, 30, 70, 31 ], "Moveset": [65, 119, 104, 38]},
|
||||
{"name": "FEAROW", "type": "0002", "exp": "125000", 'bst': [65, 90, 65, 100, 61 ], "Moveset": [65, 119, 31, 129]},
|
||||
{"name": "EKANS", "type": "0303", "exp": "125000", 'bst': [35, 60, 44, 55, 40 ], "Moveset": [89, 51, 103, 34]},
|
||||
{"name": "ARBOK", "type": "0303", "exp": "125000", 'bst': [60, 85, 69, 80, 65 ], "Moveset": [137, 35, 91, 70]},
|
||||
{"name": "PIKACHU", "type": "1717", "exp": "125000", 'bst': [35, 55, 30, 90, 50 ], "Moveset": [85, 21, 86, 69]},
|
||||
{"name": "RAICHU", "type": "1717", "exp": "125000", 'bst': [60, 90, 55, 100, 90 ], "Moveset": [87, 86, 148, 25]},
|
||||
{"name": "SANDSHREW", "type": "0404", "exp": "125000", 'bst': [50, 75, 85, 40, 30 ], "Moveset": [89, 163, 69, 28]},
|
||||
{"name": "SANDSLASH", "type": "0404", "exp": "125000", 'bst': [75, 100, 110, 65, 55 ], "Moveset": [91, 129, 69, 28]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "117360", 'bst': [55, 47, 52, 41, 40 ], "Moveset": [92, 85, 34, 59]},
|
||||
{"name": "NIDORINA", "type": "0303", "exp": "117360", 'bst': [70, 62, 67, 56, 55 ], "Moveset": [92, 87, 38, 58]},
|
||||
{"name": "NIDOQUEEN", "type": "0304", "exp": "117360", 'bst': [90, 82, 87, 76, 75 ], "Moveset": [92, 24, 44, 89]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "117360", 'bst': [46, 57, 40, 50, 40 ], "Moveset": [59, 34, 116, 85]},
|
||||
{"name": "NIDORINO", "type": "0303", "exp": "117360", 'bst': [61, 72, 57, 65, 55 ], "Moveset": [38, 32, 116, 87]},
|
||||
{"name": "NIDOKING", "type": "0304", "exp": "117360", 'bst': [81, 92, 77, 85, 75 ], "Moveset": [89, 32, 99, 164]},
|
||||
{"name": "CLEFAIRY", "type": "0000", "exp": "100000", 'bst': [70, 45, 48, 35, 60 ], "Moveset": [85, 94, 34, 59]},
|
||||
{"name": "CLEFABLE", "type": "0000", "exp": "100000", 'bst': [95, 70, 73, 60, 85 ], "Moveset": [47, 161, 107, 58]},
|
||||
{"name": "VULPIX", "type": "1414", "exp": "125000", 'bst': [38, 41, 40, 65, 65 ], "Moveset": [53, 91, 109, 38]},
|
||||
{"name": "NINETALES", "type": "1414", "exp": "125000", 'bst': [73, 76, 75, 100, 100], "Moveset": [126, 130, 109, 39]},
|
||||
{"name": "JIGGLYPUFF", "type": "0000", "exp": "100000", 'bst': [115, 45, 20, 20, 25 ], "Moveset": [47, 34, 69, 94]},
|
||||
{"name": "WIGGLYTUFF", "type": "0000", "exp": "100000", 'bst': [140, 70, 45, 45, 50 ], "Moveset": [47, 38, 66, 85]},
|
||||
{"name": "ZUBAT", "type": "0302", "exp": "125000", 'bst': [40, 45, 35, 55, 40 ], "Moveset": [109, 72, 92, 38]},
|
||||
{"name": "GOLBAT", "type": "0302", "exp": "125000", 'bst': [75, 80, 70, 90, 75 ], "Moveset": [109, 72, 44, 114]},
|
||||
{"name": "ODDISH", "type": "1603", "exp": "117360", 'bst': [45, 50, 55, 30, 75 ], "Moveset": [80, 92, 72, 38]},
|
||||
{"name": "GLOOM", "type": "1603", "exp": "117360", 'bst': [60, 65, 70, 40, 85 ], "Moveset": [80, 36, 72, 78]},
|
||||
{"name": "VILEPLUME", "type": "1603", "exp": "117360", 'bst': [75, 80, 85, 50, 100], "Moveset": [80, 79, 51, 15]},
|
||||
{"name": "PARAS", "type": "0716", "exp": "125000", 'bst': [35, 70, 55, 25, 55 ], "Moveset": [147, 163, 91, 72]},
|
||||
{"name": "PARASECT", "type": "0716", "exp": "125000", 'bst': [60, 95, 80, 30, 80 ], "Moveset": [147, 36, 91, 76]},
|
||||
{"name": "VENONAT", "type": "0703", "exp": "125000", 'bst': [60, 55, 50, 45, 40 ], "Moveset": [94, 72, 38, 78]},
|
||||
{"name": "VENOMOTH", "type": "0703", "exp": "125000", 'bst': [70, 65, 60, 90, 90 ], "Moveset": [94, 48, 76, 129]},
|
||||
{"name": "DIGLETT", "type": "0404", "exp": "125000", 'bst': [10, 55, 25, 95, 45 ], "Moveset": [89, 163, 28, 157]},
|
||||
{"name": "DUGTRIO", "type": "0404", "exp": "125000", 'bst': [35, 80, 50, 120, 70 ], "Moveset": [91, 28, 92, 63]},
|
||||
{"name": "MEOWTH", "type": "0000", "exp": "125000", 'bst': [40, 45, 35, 90, 40 ], "Moveset": [163, 85, 129, 104]},
|
||||
{"name": "PERSIAN", "type": "0000", "exp": "125000", 'bst': [65, 70, 60, 115, 65 ], "Moveset": [163, 61, 102, 45]},
|
||||
{"name": "PSYDUCK", "type": "1515", "exp": "125000", 'bst': [50, 52, 48, 55, 50 ], "Moveset": [57, 93, 91, 59]},
|
||||
{"name": "GOLDUCK", "type": "1515", "exp": "125000", 'bst': [80, 82, 78, 85, 80 ], "Moveset": [58, 57, 92, 50]},
|
||||
{"name": "MANKEY", "type": "0101", "exp": "125000", 'bst': [40, 80, 35, 70, 35 ], "Moveset": [66, 157, 69, 103]},
|
||||
{"name": "PRIMEAPE", "type": "0101", "exp": "125000", 'bst': [65, 105, 60, 95, 60 ], "Moveset": [154, 157, 67, 103]},
|
||||
{"name": "GROWLITHE", "type": "1414", "exp": "156250", 'bst': [55, 70, 45, 60, 50 ], "Moveset": [53, 34, 115, 91]},
|
||||
{"name": "ARCANINE", "type": "1414", "exp": "156250", 'bst': [90, 110, 80, 95, 80 ], "Moveset": [126, 36, 82, 164]},
|
||||
{"name": "POLIWAG", "type": "1515", "exp": "117360", 'bst': [40, 50, 40, 90, 40 ], "Moveset": [34, 59, 57, 133]},
|
||||
{"name": "POLIWHIRL", "type": "1515", "exp": "117360", 'bst': [65, 65, 65, 90, 50 ], "Moveset": [95, 57, 58, 89]},
|
||||
{"name": "POLIWRATH", "type": "1501", "exp": "117360", 'bst': [90, 85, 95, 70, 70 ], "Moveset": [95, 66, 68, 56]},
|
||||
{"name": "ABRA", "type": "1818", "exp": "117360", 'bst': [25, 20, 15, 90, 105], "Moveset": [94, 69, 115, 86]},
|
||||
{"name": "KADABRA", "type": "1818", "exp": "117360", 'bst': [40, 35, 30, 105, 120], "Moveset": [94, 68, 105, 91]},
|
||||
{"name": "ALAKAZAM", "type": "1818", "exp": "117360", 'bst': [55, 50, 45, 120, 135], "Moveset": [60, 118, 50, 161]},
|
||||
{"name": "MACHOP", "type": "0101", "exp": "117360", 'bst': [70, 80, 50, 35, 35 ], "Moveset": [66, 157, 89, 116]},
|
||||
{"name": "MACHOKE", "type": "0101", "exp": "117360", 'bst': [80, 100, 70, 45, 50 ], "Moveset": [66, 70, 157, 116]},
|
||||
{"name": "MACHAMP", "type": "0101", "exp": "117360", 'bst': [90, 130, 80, 55, 65 ], "Moveset": [67, 70, 68, 116]},
|
||||
{"name": "BELLSPROUT", "type": "1603", "exp": "117360", 'bst': [50, 75, 35, 40, 70 ], "Moveset": [75, 74, 72, 78]},
|
||||
{"name": "WEEPINBELL", "type": "1603", "exp": "117360", 'bst': [65, 90, 50, 55, 85 ], "Moveset": [75, 51, 35, 92]},
|
||||
{"name": "VICTREEBEL", "type": "1603", "exp": "117360", 'bst': [80, 105, 65, 70, 100], "Moveset": [76, 51, 115, 21]},
|
||||
{"name": "TENTACOOL", "type": "1503", "exp": "156250", 'bst': [40, 40, 35, 70, 100], "Moveset": [57, 48, 72, 59]},
|
||||
{"name": "TENTACRUEL", "type": "1503", "exp": "156250", 'bst': [80, 70, 65, 100, 120], "Moveset": [51, 48, 56, 15]},
|
||||
{"name": "GEODUDE", "type": "0504", "exp": "117360", 'bst': [40, 80, 100, 20, 30 ], "Moveset": [89, 69, 157, 153]},
|
||||
{"name": "GRAVELER", "type": "0504", "exp": "117360", 'bst': [55, 95, 115, 35, 45 ], "Moveset": [89, 69, 70, 120]},
|
||||
{"name": "GOLEM", "type": "0504", "exp": "117360", 'bst': [80, 110, 130, 45, 55 ], "Moveset": [91, 69, 126, 118]},
|
||||
{"name": "PONYTA", "type": "1414", "exp": "125000", 'bst': [50, 85, 55, 90, 65 ], "Moveset": [126, 97, 32, 34]},
|
||||
{"name": "RAPIDASH", "type": "1414", "exp": "125000", 'bst': [65, 100, 70, 105, 80 ], "Moveset": [126, 23, 92, 83]},
|
||||
{"name": "SLOWPOKE", "type": "1518", "exp": "125000", 'bst': [90, 65, 65, 15, 40 ], "Moveset": [57, 94, 86, 133]},
|
||||
{"name": "SLOWBRO", "type": "1518", "exp": "125000", 'bst': [95, 75, 110, 30, 80 ], "Moveset": [57, 94, 50, 110]},
|
||||
{"name": "MAGNEMITE", "type": "1717", "exp": "125000", 'bst': [25, 35, 70, 45, 95 ], "Moveset": [85, 86, 48, 38]},
|
||||
{"name": "MAGNETON", "type": "1717", "exp": "125000", 'bst': [50, 60, 95, 70, 120], "Moveset": [87, 103, 48, 129]},
|
||||
{"name": "FARFETCH'D", "type": "0002", "exp": "125000", 'bst': [52, 65, 55, 60, 58 ], "Moveset": [163, 28, 92, 19]},
|
||||
{"name": "DODUO", "type": "0002", "exp": "125000", 'bst': [35, 85, 45, 75, 35 ], "Moveset": [65, 161, 104, 115]},
|
||||
{"name": "DODRIO", "type": "0002", "exp": "125000", 'bst': [60, 110, 70, 100, 60 ], "Moveset": [19, 161, 97, 115]},
|
||||
{"name": "SEEL", "type": "1515", "exp": "125000", 'bst': [65, 45, 55, 45, 70 ], "Moveset": [58, 34, 32, 57]},
|
||||
{"name": "DEWGONG", "type": "1519", "exp": "125000", 'bst': [90, 70, 80, 70, 95 ], "Moveset": [62, 29, 156, 57]},
|
||||
{"name": "GRIMER", "type": "0303", "exp": "125000", 'bst': [80, 80, 50, 25, 40 ], "Moveset": [124, 34, 103, 153]},
|
||||
{"name": "MUK", "type": "0303", "exp": "125000", 'bst': [105, 105, 75, 50, 65 ], "Moveset": [124, 85, 63, 120]},
|
||||
{"name": "SHELLDER", "type": "1515", "exp": "156250", 'bst': [30, 65, 100, 40, 45 ], "Moveset": [57, 153, 59, 161]},
|
||||
{"name": "CLOYSTER", "type": "1519", "exp": "156250", 'bst': [50, 95, 180, 70, 85 ], "Moveset": [128, 131, 58, 48]},
|
||||
{"name": "GASTLY", "type": "0803", "exp": "117360", 'bst': [30, 35, 30, 80, 100], "Moveset": [95, 138, 94, 109]},
|
||||
{"name": "HAUNTER", "type": "0803", "exp": "117360", 'bst': [45, 50, 45, 95, 115], "Moveset": [72, 94, 153, 109]},
|
||||
{"name": "GENGAR", "type": "0803", "exp": "117360", 'bst': [60, 65, 60, 110, 130], "Moveset": [85, 101, 95, 109]},
|
||||
{"name": "ONIX", "type": "0504", "exp": "125000", 'bst': [35, 45, 160, 70, 30 ], "Moveset": [89, 157, 70, 153]},
|
||||
{"name": "DROWZEE", "type": "1818", "exp": "125000", 'bst': [60, 48, 45, 42, 90 ], "Moveset": [95, 138, 94, 161]},
|
||||
{"name": "HYPNO", "type": "1818", "exp": "125000", 'bst': [85, 73, 70, 67, 115], "Moveset": [95, 29, 138, 96]},
|
||||
{"name": "KRABBY", "type": "1515", "exp": "125000", 'bst': [30, 105, 90, 50, 25 ], "Moveset": [152, 12, 38, 59]},
|
||||
{"name": "KINGLER", "type": "1515", "exp": "125000", 'bst': [55, 130, 115, 75, 50 ], "Moveset": [152, 12, 23, 164]},
|
||||
{"name": "VOLTORB", "type": "1717", "exp": "125000", 'bst': [40, 30, 50, 100, 55 ], "Moveset": [85, 86, 129, 153]},
|
||||
{"name": "ELECTRODE", "type": "1717", "exp": "125000", 'bst': [60, 50, 70, 140, 80 ], "Moveset": [87, 86, 129, 120]},
|
||||
{"name": "EXEGGCUTE", "type": "1618", "exp": "156250", 'bst': [60, 40, 80, 40, 60 ], "Moveset": [94, 153, 73, 92]},
|
||||
{"name": "EXEGGUTOR", "type": "1618", "exp": "156250", 'bst': [95, 95, 85, 55, 125], "Moveset": [72, 78, 73, 121]},
|
||||
{"name": "CUBONE", "type": "0404", "exp": "125000", 'bst': [50, 50, 95, 35, 40 ], "Moveset": [89, 66, 59, 70]},
|
||||
{"name": "MAROWAK", "type": "0404", "exp": "125000", 'bst': [60, 80, 110, 45, 50 ], "Moveset": [155, 37, 126, 116]},
|
||||
{"name": "HITMONLEE", "type": "0101", "exp": "125000", 'bst': [50, 120, 53, 87, 35 ], "Moveset": [136, 25, 118, 69]},
|
||||
{"name": "HITMONCHAN", "type": "0101", "exp": "125000", 'bst': [50, 105, 79, 76, 35 ], "Moveset": [66, 9, 8, 70]},
|
||||
{"name": "LICKITUNG", "type": "0000", "exp": "125000", 'bst': [90, 55, 75, 30, 60 ], "Moveset": [70, 59, 87, 126]},
|
||||
{"name": "KOFFING", "type": "0303", "exp": "125000", 'bst': [40, 65, 95, 35, 60 ], "Moveset": [124, 92, 85, 153]},
|
||||
{"name": "WEEZING", "type": "0303", "exp": "125000", 'bst': [65, 90, 120, 60, 85 ], "Moveset": [124, 63, 126, 120]},
|
||||
{"name": "RHYHORN", "type": "0405", "exp": "156250", 'bst': [80, 85, 95, 25, 30 ], "Moveset": [89, 34, 157, 126]},
|
||||
{"name": "RHYDON", "type": "0405", "exp": "156250", 'bst': [105, 130, 120, 40, 45 ], "Moveset": [91, 70, 87, 57]},
|
||||
{"name": "CHANSEY", "type": "0000", "exp": "100000", 'bst': [250, 5, 5, 50, 105], "Moveset": [87, 126, 107, 156]},
|
||||
{"name": "TANGELA", "type": "1616", "exp": "125000", 'bst': [65, 55, 115, 60, 100], "Moveset": [72, 74, 92, 38]},
|
||||
{"name": "KANGASKHAN", "type": "0000", "exp": "125000", 'bst': [105, 95, 80, 90, 40 ], "Moveset": [146, 157, 57, 85]},
|
||||
{"name": "HORSEA", "type": "1515", "exp": "125000", 'bst': [30, 40, 70, 60, 70 ], "Moveset": [56, 92, 108, 58]},
|
||||
{"name": "SEADRA", "type": "1515", "exp": "125000", 'bst': [55, 65, 95, 85, 95 ], "Moveset": [57, 92, 108, 129]},
|
||||
{"name": "GOLDEEN", "type": "1515", "exp": "125000", 'bst': [45, 67, 60, 63, 50 ], "Moveset": [57, 48, 32, 59]},
|
||||
{"name": "SEAKING", "type": "1515", "exp": "125000", 'bst': [80, 92, 65, 68, 80 ], "Moveset": [127, 48, 30, 58]},
|
||||
{"name": "STARYU", "type": "1515", "exp": "156250", 'bst': [30, 45, 55, 85, 70 ], "Moveset": [56, 105, 85, 94]},
|
||||
{"name": "STARMIE", "type": "1518", "exp": "156250", 'bst': [60, 75, 85, 115, 100], "Moveset": [57, 87, 129, 106]},
|
||||
{"name": "MR. MIME", "type": "1818", "exp": "125000", 'bst': [40, 45, 65, 90, 100], "Moveset": [112, 94, 118, 69]},
|
||||
{"name": "SCYTHER", "type": "0702", "exp": "125000", 'bst': [70, 110, 80, 105, 55 ], "Moveset": [163, 17, 43, 104]},
|
||||
{"name": "JYNX", "type": "1918", "exp": "125000", 'bst': [65, 50, 35, 95, 95 ], "Moveset": [8, 5, 94, 142]},
|
||||
{"name": "ELECTABUZZ", "type": "1717", "exp": "125000", 'bst': [65, 83, 57, 105, 85 ], "Moveset": [9, 5, 94, 86]},
|
||||
{"name": "MAGMAR", "type": "1414", "exp": "125000", 'bst': [65, 95, 57, 93, 85 ], "Moveset": [7, 5, 94, 108]},
|
||||
{"name": "PINSIR", "type": "0707", "exp": "156250", 'bst': [65, 125, 100, 85, 55 ], "Moveset": [70, 106, 69, 12]},
|
||||
{"name": "TAUROS", "type": "0000", "exp": "156250", 'bst': [75, 100, 95, 110, 70 ], "Moveset": [38, 126, 39, 117]},
|
||||
{"name": "MAGIKARP", "type": "1515", "exp": "156250", 'bst': [20, 10, 55, 80, 20 ], "Moveset": [150, 33, 0, 0]},
|
||||
{"name": "GYARADOS", "type": "1502", "exp": "156250", 'bst': [95, 125, 79, 81, 100], "Moveset": [57, 82, 44, 126]},
|
||||
{"name": "LAPRAS", "type": "1519", "exp": "156250", 'bst': [130, 85, 80, 60, 95 ], "Moveset": [58, 76, 34, 47]},
|
||||
{"name": "DITTO", "type": "0000", "exp": "125000", 'bst': [48, 48, 48, 48, 48 ], "Moveset": [144, 0, 0, 0]},
|
||||
{"name": "EEVEE", "type": "0000", "exp": "125000", 'bst': [55, 55, 50, 55, 65 ], "Moveset": [34, 129, 28, 92]},
|
||||
{"name": "VAPOREON", "type": "1515", "exp": "125000", 'bst': [130, 65, 60, 65, 110], "Moveset": [57, 98, 28, 151]},
|
||||
{"name": "JOLTEON", "type": "1717", "exp": "125000", 'bst': [65, 65, 60, 130, 110], "Moveset": [85, 42, 92, 28]},
|
||||
{"name": "FLAREON", "type": "1414", "exp": "125000", 'bst': [65, 130, 60, 65, 110], "Moveset": [126, 36, 123, 28]},
|
||||
{"name": "PORYGON", "type": "0000", "exp": "125000", 'bst': [65, 60, 70, 40, 75 ], "Moveset": [161, 94, 159, 160]},
|
||||
{"name": "OMANYTE", "type": "0515", "exp": "125000", 'bst': [35, 40, 100, 35, 90 ], "Moveset": [57, 58, 38, 104]},
|
||||
{"name": "OMASTAR", "type": "0515", "exp": "125000", 'bst': [70, 60, 125, 55, 115], "Moveset": [56, 66, 131, 110]},
|
||||
{"name": "KABUTO", "type": "0515", "exp": "125000", 'bst': [30, 80, 90, 55, 45 ], "Moveset": [56, 59, 163, 104]},
|
||||
{"name": "KABUTOPS", "type": "0515", "exp": "125000", 'bst': [60, 115, 105, 80, 70 ], "Moveset": [57, 14, 25, 66]},
|
||||
{"name": "AERODACTYL", "type": "0502", "exp": "156250", 'bst': [80, 105, 65, 130, 60 ], "Moveset": [19, 63, 48, 82]},
|
||||
{"name": "SNORLAX", "type": "0000", "exp": "156250", 'bst': [160, 110, 65, 30, 65 ], "Moveset": [25, 157, 118, 156]},
|
||||
{"name": "ARTICUNO", "type": "1902", "exp": "156250", 'bst': [90, 85, 100, 85, 125], "Moveset": [58, 143, 13, 164]},
|
||||
{"name": "ZAPDOS", "type": "1702", "exp": "156250", 'bst': [90, 90, 85, 100, 125], "Moveset": [85, 143, 86, 148]},
|
||||
{"name": "MOLTRES", "type": "1402", "exp": "156250", 'bst': [90, 100, 90, 90, 125], "Moveset": [126, 19, 129, 164]},
|
||||
{"name": "DRATINI", "type": "1A1A", "exp": "156250", 'bst': [41, 64, 45, 50, 50 ], "Moveset": [63, 34, 85, 86]},
|
||||
{"name": "DRAGONAIR", "type": "1A1A", "exp": "156250", 'bst': [61, 84, 65, 70, 70 ], "Moveset": [63, 129, 58, 86]},
|
||||
{"name": "DRAGONITE", "type": "1A02", "exp": "156250", 'bst': [91, 134, 95, 80, 100], "Moveset": [21, 82, 87, 97]},
|
||||
]
|
||||
prime_cup_list = [
|
||||
{"name": "BULBASAUR", "type": "1603", "exp": "1059860", 'bst': [45, 49, 49, 45, 65 ], "Moveset": [73, 75, 74, 34]},
|
||||
{"name": "IVYSAUR", "type": "1603", "exp": "1059860", 'bst': [60, 62, 63, 60, 80 ], "Moveset": [73, 75, 74, 72]},
|
||||
{"name": "VENUSAUR", "type": "1603", "exp": "1059860", 'bst': [80, 82, 83, 80, 100], "Moveset": [73, 76, 74, 79]},
|
||||
{"name": "CHARMANDER", "type": "1414", "exp": "1059860", 'bst': [39, 52, 43, 65, 50 ], "Moveset": [53, 34, 69, 91]},
|
||||
{"name": "CHARMELEON", "type": "1414", "exp": "1059860", 'bst': [58, 64, 58, 80, 65 ], "Moveset": [53, 163, 91, 66]},
|
||||
{"name": "CHARIZARD", "type": "1402", "exp": "1059860", 'bst': [78, 84, 78, 100, 85 ], "Moveset": [126, 19, 83, 14]},
|
||||
{"name": "SQUIRTLE", "type": "1515", "exp": "1059860", 'bst': [44, 48, 65, 43, 50 ], "Moveset": [56, 59, 34, 91]},
|
||||
{"name": "WARTORTLE", "type": "1515", "exp": "1059860", 'bst': [59, 63, 80, 58, 65 ], "Moveset": [57, 69, 91, 92]},
|
||||
{"name": "BLASTOISE", "type": "1515", "exp": "1059860", 'bst': [79, 83, 100, 78, 85 ], "Moveset": [56, 130, 110, 39]},
|
||||
{"name": "CATERPIE", "type": "0707", "exp": "1000000", 'bst': [45, 30, 35, 45, 20 ], "Moveset": [33, 81, 0, 0]},
|
||||
{"name": "METAPOD", "type": "0707", "exp": "1000000", 'bst': [50, 20, 55, 30, 25 ], "Moveset": [33, 81, 0, 0]},
|
||||
{"name": "BUTTERFREE", "type": "0702", "exp": "1000000", 'bst': [60, 45, 50, 70, 80 ], "Moveset": [94, 72, 129, 78]},
|
||||
{"name": "WEEDLE", "type": "0703", "exp": "1000000", 'bst': [40, 35, 30, 50, 20 ], "Moveset": [40, 81, 0, 0]},
|
||||
{"name": "KAKUNA", "type": "0703", "exp": "1000000", 'bst': [45, 25, 50, 35, 25 ], "Moveset": [40, 81, 0, 0]},
|
||||
{"name": "BEEDRILL", "type": "0703", "exp": "1000000", 'bst': [65, 80, 40, 75, 45 ], "Moveset": [41, 63, 72, 116]},
|
||||
{"name": "PIDGEY", "type": "0002", "exp": "1059860", 'bst': [40, 45, 40, 56, 35 ], "Moveset": [19, 28, 119, 18]},
|
||||
{"name": "PIDGEOTTO", "type": "0002", "exp": "1059860", 'bst': [63, 60, 55, 71, 50 ], "Moveset": [19, 28, 129, 92]},
|
||||
{"name": "PIDGEOT", "type": "0002", "exp": "1059860", 'bst': [83, 80, 75, 91, 70 ], "Moveset": [98, 119, 28, 19]},
|
||||
{"name": "RATTATA", "type": "0000", "exp": "1000000", 'bst': [30, 56, 35, 72, 25 ], "Moveset": [162, 34, 91, 92]},
|
||||
{"name": "RATICATE", "type": "0000", "exp": "1000000", 'bst': [55, 81, 60, 97, 50 ], "Moveset": [162, 158, 98, 92]},
|
||||
{"name": "SPEAROW", "type": "0002", "exp": "1000000", 'bst': [40, 60, 30, 70, 31 ], "Moveset": [65, 129, 104, 19]},
|
||||
{"name": "FEAROW", "type": "0002", "exp": "1000000", 'bst': [65, 90, 65, 100, 61 ], "Moveset": [65, 119, 63, 45]},
|
||||
{"name": "EKANS", "type": "0303", "exp": "1000000", 'bst': [35, 60, 44, 55, 40 ], "Moveset": [38, 137, 89, 72]},
|
||||
{"name": "ARBOK", "type": "0303", "exp": "1000000", 'bst': [60, 85, 69, 80, 65 ], "Moveset": [91, 137, 70, 51]},
|
||||
{"name": "PIKACHU", "type": "1717", "exp": "1000000", 'bst': [35, 55, 30, 90, 50 ], "Moveset": [85, 86, 129, 115]},
|
||||
{"name": "RAICHU", "type": "1717", "exp": "1000000", 'bst': [60, 90, 55, 100, 90 ], "Moveset": [87, 86, 98, 25]},
|
||||
{"name": "SANDSHREW", "type": "0404", "exp": "1000000", 'bst': [50, 75, 85, 40, 30 ], "Moveset": [28, 89, 163, 157]},
|
||||
{"name": "SANDSLASH", "type": "0404", "exp": "1000000", 'bst': [75, 100, 110, 65, 55 ], "Moveset": [28, 91, 70, 157]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "1059860", 'bst': [55, 47, 52, 41, 40 ], "Moveset": [34, 59, 85, 92]},
|
||||
{"name": "NIDORINA", "type": "0303", "exp": "1059860", 'bst': [70, 62, 67, 56, 55 ], "Moveset": [34, 61, 87, 92]},
|
||||
{"name": "NIDOQUEEN", "type": "0304", "exp": "1059860", 'bst': [90, 82, 87, 76, 75 ], "Moveset": [89, 24, 157, 92]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "1059860", 'bst': [46, 57, 40, 50, 40 ], "Moveset": [34, 59, 87, 32]},
|
||||
{"name": "NIDORINO", "type": "0303", "exp": "1059860", 'bst': [61, 72, 57, 65, 55 ], "Moveset": [34, 85, 58, 32]},
|
||||
{"name": "NIDOKING", "type": "0304", "exp": "1059860", 'bst': [81, 92, 77, 85, 75 ], "Moveset": [30, 89, 117, 32]},
|
||||
{"name": "CLEFAIRY", "type": "0000", "exp": "800000", 'bst': [70, 45, 48, 35, 60 ], "Moveset": [118, 34, 86, 59]},
|
||||
{"name": "CLEFABLE", "type": "0000", "exp": "800000", 'bst': [95, 70, 73, 60, 85 ], "Moveset": [118, 70, 86, 87]},
|
||||
{"name": "VULPIX", "type": "1414", "exp": "1000000", 'bst': [38, 41, 40, 65, 65 ], "Moveset": [53, 91, 109, 92]},
|
||||
{"name": "NINETALES", "type": "1414", "exp": "1000000", 'bst': [73, 76, 75, 100, 100], "Moveset": [126, 98, 109, 39]},
|
||||
{"name": "JIGGLYPUFF", "type": "0000", "exp": "800000", 'bst': [115, 45, 20, 20, 25 ], "Moveset": [47, 148, 34, 69]},
|
||||
{"name": "WIGGLYTUFF", "type": "0000", "exp": "800000", 'bst': [140, 70, 45, 45, 50 ], "Moveset": [47, 50, 70, 63]},
|
||||
{"name": "ZUBAT", "type": "0302", "exp": "1000000", 'bst': [40, 45, 35, 55, 40 ], "Moveset": [109, 129, 72, 114]},
|
||||
{"name": "GOLBAT", "type": "0302", "exp": "1000000", 'bst': [75, 80, 70, 90, 75 ], "Moveset": [48, 63, 72, 114]},
|
||||
{"name": "ODDISH", "type": "1603", "exp": "1059860", 'bst': [45, 50, 55, 30, 75 ], "Moveset": [80, 72, 78, 38]},
|
||||
{"name": "GLOOM", "type": "1603", "exp": "1059860", 'bst': [60, 65, 70, 40, 85 ], "Moveset": [80, 72, 78, 51]},
|
||||
{"name": "VILEPLUME", "type": "1603", "exp": "1059860", 'bst': [75, 80, 85, 50, 100], "Moveset": [76, 72, 78, 51]},
|
||||
{"name": "PARAS", "type": "0716", "exp": "1000000", 'bst': [35, 70, 55, 25, 55 ], "Moveset": [163, 147, 91, 72]},
|
||||
{"name": "PARASECT", "type": "0716", "exp": "1000000", 'bst': [60, 95, 80, 30, 80 ], "Moveset": [163, 147, 74, 72]},
|
||||
{"name": "VENONAT", "type": "0703", "exp": "1000000", 'bst': [60, 55, 50, 45, 40 ], "Moveset": [94, 72, 38, 92]},
|
||||
{"name": "VENOMOTH", "type": "0703", "exp": "1000000", 'bst': [70, 65, 60, 90, 90 ], "Moveset": [94, 72, 79, 148]},
|
||||
{"name": "DIGLETT", "type": "0404", "exp": "1000000", 'bst': [10, 55, 25, 95, 45 ], "Moveset": [89, 90, 163, 28]},
|
||||
{"name": "DUGTRIO", "type": "0404", "exp": "1000000", 'bst': [35, 80, 50, 120, 70 ], "Moveset": [91, 157, 45, 28]},
|
||||
{"name": "MEOWTH", "type": "0000", "exp": "1000000", 'bst': [40, 45, 35, 90, 40 ], "Moveset": [61, 103, 163, 85]},
|
||||
{"name": "PERSIAN", "type": "0000", "exp": "1000000", 'bst': [65, 70, 60, 115, 65 ], "Moveset": [63, 103, 44, 87]},
|
||||
{"name": "PSYDUCK", "type": "1515", "exp": "1000000", 'bst': [50, 52, 48, 55, 50 ], "Moveset": [56, 59, 91, 50]},
|
||||
{"name": "GOLDUCK", "type": "1515", "exp": "1000000", 'bst': [80, 82, 78, 85, 80 ], "Moveset": [61, 58, 93, 50]},
|
||||
{"name": "MANKEY", "type": "0101", "exp": "1000000", 'bst': [40, 80, 35, 70, 35 ], "Moveset": [66, 37, 91, 68]},
|
||||
{"name": "PRIMEAPE", "type": "0101", "exp": "1000000", 'bst': [65, 105, 60, 95, 60 ], "Moveset": [67, 37, 69, 68]},
|
||||
{"name": "GROWLITHE", "type": "1414", "exp": "1250000", 'bst': [55, 70, 45, 60, 50 ], "Moveset": [53, 91, 34, 104]},
|
||||
{"name": "ARCANINE", "type": "1414", "exp": "1250000", 'bst': [90, 110, 80, 95, 80 ], "Moveset": [126, 91, 43, 97]},
|
||||
{"name": "POLIWAG", "type": "1515", "exp": "1059860", 'bst': [40, 50, 40, 90, 40 ], "Moveset": [56, 59, 94, 133]},
|
||||
{"name": "POLIWHIRL", "type": "1515", "exp": "1059860", 'bst': [65, 65, 65, 90, 50 ], "Moveset": [57, 58, 94, 133]},
|
||||
{"name": "POLIWRATH", "type": "1501", "exp": "1059860", 'bst': [90, 85, 95, 70, 70 ], "Moveset": [61, 66, 95, 133]},
|
||||
{"name": "ABRA", "type": "1818", "exp": "1059860", 'bst': [25, 20, 15, 90, 105], "Moveset": [94, 86, 104, 34]},
|
||||
{"name": "KADABRA", "type": "1818", "exp": "1059860", 'bst': [40, 35, 30, 105, 120], "Moveset": [94, 105, 115, 91]},
|
||||
{"name": "ALAKAZAM", "type": "1818", "exp": "1059860", 'bst': [55, 50, 45, 120, 135], "Moveset": [60, 134, 115, 63]},
|
||||
{"name": "MACHOP", "type": "0101", "exp": "1059860", 'bst': [70, 80, 50, 35, 35 ], "Moveset": [66, 34, 69, 116]},
|
||||
{"name": "MACHOKE", "type": "0101", "exp": "1059860", 'bst': [80, 100, 70, 45, 50 ], "Moveset": [66, 91, 69, 116]},
|
||||
{"name": "MACHAMP", "type": "0101", "exp": "1059860", 'bst': [90, 130, 80, 55, 65 ], "Moveset": [67, 5, 43, 116]},
|
||||
{"name": "BELLSPROUT", "type": "1603", "exp": "1059860", 'bst': [50, 75, 35, 40, 70 ], "Moveset": [75, 92, 35, 38]},
|
||||
{"name": "WEEPINBELL", "type": "1603", "exp": "1059860", 'bst': [65, 90, 50, 55, 85 ], "Moveset": [75, 72, 74, 78]},
|
||||
{"name": "VICTREEBEL", "type": "1603", "exp": "1059860", 'bst': [80, 105, 65, 70, 100], "Moveset": [75, 51, 35, 79]},
|
||||
{"name": "TENTACOOL", "type": "1503", "exp": "1250000", 'bst': [40, 40, 35, 70, 100], "Moveset": [57, 59, 72, 92]},
|
||||
{"name": "TENTACRUEL", "type": "1503", "exp": "1250000", 'bst': [80, 70, 65, 100, 120], "Moveset": [61, 35, 103, 92]},
|
||||
{"name": "GEODUDE", "type": "0504", "exp": "1059860", 'bst': [40, 80, 100, 20, 30 ], "Moveset": [157, 89, 69, 126]},
|
||||
{"name": "GRAVELER", "type": "0504", "exp": "1059860", 'bst': [55, 95, 115, 35, 45 ], "Moveset": [157, 89, 126, 118]},
|
||||
{"name": "GOLEM", "type": "0504", "exp": "1059860", 'bst': [80, 110, 130, 45, 55 ], "Moveset": [88, 91, 111, 126]},
|
||||
{"name": "PONYTA", "type": "1414", "exp": "1000000", 'bst': [50, 85, 55, 90, 65 ], "Moveset": [83, 97, 32, 92]},
|
||||
{"name": "RAPIDASH", "type": "1414", "exp": "1000000", 'bst': [65, 100, 70, 105, 80 ], "Moveset": [126, 23, 115, 39]},
|
||||
{"name": "SLOWPOKE", "type": "1518", "exp": "1000000", 'bst': [90, 65, 65, 15, 40 ], "Moveset": [57, 94, 133, 86]},
|
||||
{"name": "SLOWBRO", "type": "1518", "exp": "1000000", 'bst': [95, 75, 110, 30, 80 ], "Moveset": [57, 94, 50, 5]},
|
||||
{"name": "MAGNEMITE", "type": "1717", "exp": "1000000", 'bst': [25, 35, 70, 45, 95 ], "Moveset": [85, 86, 129, 148]},
|
||||
{"name": "MAGNETON", "type": "1717", "exp": "1000000", 'bst': [50, 60, 95, 70, 120], "Moveset": [87, 86, 48, 148]},
|
||||
{"name": "FARFETCH'D", "type": "0002", "exp": "1000000", 'bst': [52, 65, 55, 60, 58 ], "Moveset": [163, 28, 19, 92]},
|
||||
{"name": "DODUO", "type": "0002", "exp": "1000000", 'bst': [35, 85, 45, 75, 35 ], "Moveset": [65, 34, 115, 104]},
|
||||
{"name": "DODRIO", "type": "0002", "exp": "1000000", 'bst': [60, 110, 70, 100, 60 ], "Moveset": [161, 19, 45, 97]},
|
||||
{"name": "SEEL", "type": "1515", "exp": "1000000", 'bst': [65, 45, 55, 45, 70 ], "Moveset": [57, 59, 34, 104]},
|
||||
{"name": "DEWGONG", "type": "1519", "exp": "1000000", 'bst': [90, 70, 80, 70, 95 ], "Moveset": [62, 57, 29, 32]},
|
||||
{"name": "GRIMER", "type": "0303", "exp": "1000000", 'bst': [80, 80, 50, 25, 40 ], "Moveset": [124, 34, 85, 151]},
|
||||
{"name": "MUK", "type": "0303", "exp": "1000000", 'bst': [105, 105, 75, 50, 65 ], "Moveset": [124, 126, 103, 151]},
|
||||
{"name": "SHELLDER", "type": "1515", "exp": "1250000", 'bst': [30, 65, 100, 40, 45 ], "Moveset": [59, 57, 129, 48]},
|
||||
{"name": "CLOYSTER", "type": "1519", "exp": "1250000", 'bst': [50, 95, 180, 70, 85 ], "Moveset": [58, 61, 128, 48]},
|
||||
{"name": "GASTLY", "type": "0803", "exp": "1059860", 'bst': [30, 35, 30, 80, 100], "Moveset": [95, 94, 109, 101]},
|
||||
{"name": "HAUNTER", "type": "0803", "exp": "1059860", 'bst': [45, 50, 45, 95, 115], "Moveset": [95, 138, 109, 94]},
|
||||
{"name": "GENGAR", "type": "0803", "exp": "1059860", 'bst': [60, 65, 60, 110, 130], "Moveset": [95, 138, 118, 101]},
|
||||
{"name": "ONIX", "type": "0504", "exp": "1000000", 'bst': [35, 45, 160, 70, 30 ], "Moveset": [157, 89, 90, 120]},
|
||||
{"name": "DROWZEE", "type": "1818", "exp": "1000000", 'bst': [60, 48, 45, 42, 90 ], "Moveset": [95, 138, 69, 94]},
|
||||
{"name": "HYPNO", "type": "1818", "exp": "1000000", 'bst': [85, 73, 70, 67, 115], "Moveset": [95, 139, 29, 94]},
|
||||
{"name": "KRABBY", "type": "1515", "exp": "1000000", 'bst': [30, 105, 90, 50, 25 ], "Moveset": [57, 34, 12, 59]},
|
||||
{"name": "KINGLER", "type": "1515", "exp": "1000000", 'bst': [55, 130, 115, 75, 50 ], "Moveset": [152, 70, 12, 92]},
|
||||
{"name": "VOLTORB", "type": "1717", "exp": "1000000", 'bst': [40, 30, 50, 100, 55 ], "Moveset": [85, 86, 36, 115]},
|
||||
{"name": "ELECTRODE", "type": "1717", "exp": "1000000", 'bst': [60, 50, 70, 140, 80 ], "Moveset": [87, 86, 129, 148]},
|
||||
{"name": "EXEGGCUTE", "type": "1618", "exp": "1250000", 'bst': [60, 40, 80, 40, 60 ], "Moveset": [73, 92, 94, 120]},
|
||||
{"name": "EXEGGUTOR", "type": "1618", "exp": "1250000", 'bst': [95, 95, 85, 55, 125], "Moveset": [23, 79, 94, 76]},
|
||||
{"name": "CUBONE", "type": "0404", "exp": "1000000", 'bst': [50, 50, 95, 35, 40 ], "Moveset": [155, 59, 37, 116]},
|
||||
{"name": "MAROWAK", "type": "0404", "exp": "1000000", 'bst': [60, 80, 110, 45, 50 ], "Moveset": [125, 29, 37, 116]},
|
||||
{"name": "HITMONLEE", "type": "0101", "exp": "1000000", 'bst': [50, 120, 53, 87, 35 ], "Moveset": [27, 26, 136, 116]},
|
||||
{"name": "HITMONCHAN", "type": "0101", "exp": "1000000", 'bst': [50, 105, 79, 76, 35 ], "Moveset": [5, 7, 8, 9]},
|
||||
{"name": "LICKITUNG", "type": "0000", "exp": "1000000", 'bst': [90, 55, 75, 30, 60 ], "Moveset": [34, 87, 89, 59]},
|
||||
{"name": "KOFFING", "type": "0303", "exp": "1000000", 'bst': [40, 65, 95, 35, 60 ], "Moveset": [124, 87, 114, 92]},
|
||||
{"name": "WEEZING", "type": "0303", "exp": "1000000", 'bst': [65, 90, 120, 60, 85 ], "Moveset": [124, 87, 114, 102]},
|
||||
{"name": "RHYHORN", "type": "0405", "exp": "1250000", 'bst': [80, 85, 95, 25, 30 ], "Moveset": [34, 89, 157, 90]},
|
||||
{"name": "RHYDON", "type": "0405", "exp": "1250000", 'bst': [105, 130, 120, 40, 45 ], "Moveset": [30, 89, 87, 90]},
|
||||
{"name": "CHANSEY", "type": "0000", "exp": "800000", 'bst': [250, 5, 5, 50, 105], "Moveset": [121, 156, 118, 69]},
|
||||
{"name": "TANGELA", "type": "1616", "exp": "1000000", 'bst': [65, 55, 115, 60, 100], "Moveset": [72, 76, 74, 78]},
|
||||
{"name": "KANGASKHAN", "type": "0000", "exp": "1000000", 'bst': [105, 95, 80, 90, 40 ], "Moveset": [146, 157, 57, 164]},
|
||||
{"name": "HORSEA", "type": "1515", "exp": "1000000", 'bst': [30, 40, 70, 60, 70 ], "Moveset": [56, 58, 92, 108]},
|
||||
{"name": "SEADRA", "type": "1515", "exp": "1000000", 'bst': [55, 65, 95, 85, 95 ], "Moveset": [57, 38, 92, 108]},
|
||||
{"name": "GOLDEEN", "type": "1515", "exp": "1000000", 'bst': [45, 67, 60, 63, 50 ], "Moveset": [57, 32, 104, 97]},
|
||||
{"name": "SEAKING", "type": "1515", "exp": "1000000", 'bst': [80, 92, 65, 68, 80 ], "Moveset": [127, 32, 48, 31]},
|
||||
{"name": "STARYU", "type": "1515", "exp": "1250000", 'bst': [30, 45, 55, 85, 70 ], "Moveset": [57, 94, 107, 105]},
|
||||
{"name": "STARMIE", "type": "1518", "exp": "1250000", 'bst': [60, 75, 85, 115, 100], "Moveset": [61, 87, 107, 129]},
|
||||
{"name": "MR. MIME", "type": "1818", "exp": "1000000", 'bst': [40, 45, 65, 90, 100], "Moveset": [112, 113, 94, 63]},
|
||||
{"name": "SCYTHER", "type": "0702", "exp": "1000000", 'bst': [70, 110, 80, 105, 55 ], "Moveset": [116, 63, 129, 104]},
|
||||
{"name": "JYNX", "type": "1918", "exp": "1000000", 'bst': [65, 50, 35, 95, 95 ], "Moveset": [142, 34, 8, 94]},
|
||||
{"name": "ELECTABUZZ", "type": "1717", "exp": "1000000", 'bst': [65, 83, 57, 105, 85 ], "Moveset": [9, 86, 118, 115]},
|
||||
{"name": "MAGMAR", "type": "1414", "exp": "1000000", 'bst': [65, 95, 57, 93, 85 ], "Moveset": [7, 5, 109, 94]},
|
||||
{"name": "PINSIR", "type": "0707", "exp": "1250000", 'bst': [65, 125, 100, 85, 55 ], "Moveset": [163, 12, 69, 92]},
|
||||
{"name": "TAUROS", "type": "0000", "exp": "1250000", 'bst': [75, 100, 95, 110, 70 ], "Moveset": [23, 130, 117, 126]},
|
||||
{"name": "MAGIKARP", "type": "1515", "exp": "1250000", 'bst': [20, 10, 55, 80, 20 ], "Moveset": [150, 33, 0, 0]},
|
||||
{"name": "GYARADOS", "type": "1502", "exp": "1250000", 'bst': [95, 125, 79, 81, 100], "Moveset": [61, 44, 126, 43]},
|
||||
{"name": "LAPRAS", "type": "1519", "exp": "1250000", 'bst': [130, 85, 80, 60, 95 ], "Moveset": [61, 54, 47, 58]},
|
||||
{"name": "DITTO", "type": "0000", "exp": "1000000", 'bst': [48, 48, 48, 48, 48 ], "Moveset": [144, 0, 0, 0]},
|
||||
{"name": "EEVEE", "type": "0000", "exp": "1000000", 'bst': [55, 55, 50, 55, 65 ], "Moveset": [38, 116, 28, 98]},
|
||||
{"name": "VAPOREON", "type": "1515", "exp": "1000000", 'bst': [130, 65, 60, 65, 110], "Moveset": [56, 151, 114, 98]},
|
||||
{"name": "JOLTEON", "type": "1717", "exp": "1000000", 'bst': [65, 65, 60, 130, 110], "Moveset": [87, 42, 28, 98]},
|
||||
{"name": "FLAREON", "type": "1414", "exp": "1000000", 'bst': [65, 130, 60, 65, 110], "Moveset": [126, 123, 28, 98]},
|
||||
{"name": "PORYGON", "type": "0000", "exp": "1000000", 'bst': [65, 60, 70, 40, 75 ], "Moveset": [60, 161, 160, 105]},
|
||||
{"name": "OMANYTE", "type": "0515", "exp": "1000000", 'bst': [35, 40, 100, 35, 90 ], "Moveset": [56, 34, 58, 92]},
|
||||
{"name": "OMASTAR", "type": "0515", "exp": "1000000", 'bst': [70, 60, 125, 55, 115], "Moveset": [57, 131, 32, 92]},
|
||||
{"name": "KABUTO", "type": "0515", "exp": "1000000", 'bst': [30, 80, 90, 55, 45 ], "Moveset": [57, 59, 163, 104]},
|
||||
{"name": "KABUTOPS", "type": "0515", "exp": "1000000", 'bst': [60, 115, 105, 80, 70 ], "Moveset": [56, 25, 58, 14]},
|
||||
{"name": "AERODACTYL", "type": "0502", "exp": "1250000", 'bst': [80, 105, 65, 130, 60 ], "Moveset": [44, 48, 19, 126]},
|
||||
{"name": "SNORLAX", "type": "0000", "exp": "1250000", 'bst': [160, 110, 65, 30, 65 ], "Moveset": [36, 118, 156, 117]},
|
||||
{"name": "ARTICUNO", "type": "1902", "exp": "1250000", 'bst': [90, 85, 100, 85, 125], "Moveset": [58, 143, 54, 97]},
|
||||
{"name": "ZAPDOS", "type": "1702", "exp": "1250000", 'bst': [90, 90, 85, 100, 125], "Moveset": [87, 143, 117, 148]},
|
||||
{"name": "MOLTRES", "type": "1402", "exp": "1250000", 'bst': [90, 100, 90, 90, 125], "Moveset": [126, 143, 97, 115]},
|
||||
{"name": "DRATINI", "type": "1A1A", "exp": "1250000", 'bst': [41, 64, 45, 50, 50 ], "Moveset": [59, 85, 34, 126]},
|
||||
{"name": "DRAGONAIR", "type": "1A1A", "exp": "1250000", 'bst': [61, 84, 65, 70, 70 ], "Moveset": [85, 34, 58, 126]},
|
||||
{"name": "DRAGONITE", "type": "1A02", "exp": "1250000", 'bst': [91, 134, 95, 80, 100], "Moveset": [87, 35, 21, 126]},
|
||||
]
|
||||
petit_cup_list = [
|
||||
{"name": "BULBASAUR", "type": "1603", "exp": "11735", 'bst': [45, 49, 49, 45, 65 ], "DexNum": 1, "Moveset": [73, 72, 76, 15]},
|
||||
{"name": "CHARMANDER", "type": "1414", "exp": "11735", 'bst': [39, 52, 43, 65, 50 ], "DexNum": 4, "Moveset": [126, 99, 45, 5]},
|
||||
{"name": "SQUIRTLE", "type": "1515", "exp": "11735", 'bst': [44, 48, 65, 43, 50 ], "DexNum": 7, "Moveset": [44, 61, 92, 66]},
|
||||
{"name": "CATERPIE", "type": "0707", "exp": "15625", 'bst': [45, 30, 35, 45, 20 ], "DexNum": 10, "Moveset": [33, 81, 0, 0]},
|
||||
{"name": "WEEDLE", "type": "0703", "exp": "15625", 'bst': [40, 35, 30, 50, 20 ], "DexNum": 13, "Moveset": [40, 81, 0, 0]},
|
||||
{"name": "PIDGEY", "type": "0002", "exp": "11735", 'bst': [40, 45, 40, 56, 35 ], "DexNum": 16, "Moveset": [28, 98, 19, 38]},
|
||||
{"name": "RATTATA", "type": "0000", "exp": "15625", 'bst': [30, 56, 35, 72, 25 ], "DexNum": 19, "Moveset": [98, 158, 61, 91]},
|
||||
{"name": "SPEAROW", "type": "0002", "exp": "15625", 'bst': [40, 60, 30, 70, 31 ], "DexNum": 21, "Moveset": [38, 119, 19, 92]},
|
||||
{"name": "EKANS", "type": "0303", "exp": "15625", 'bst': [35, 60, 44, 55, 40 ], "DexNum": 23, "Moveset": [44, 137, 91, 72]},
|
||||
{"name": "PIKACHU", "type": "1717", "exp": "15625", 'bst': [35, 55, 30, 90, 50 ], "DexNum": 25, "Moveset": [86, 21, 87, 148]},
|
||||
{"name": "SANDSHREW", "type": "0404", "exp": "15625", 'bst': [50, 75, 85, 40, 30 ], "DexNum": 27, "Moveset": [163, 40, 91, 157]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "11735", 'bst': [55, 47, 52, 41, 40 ], "DexNum": 29, "Moveset": [24, 59, 36, 92]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "11735", 'bst': [46, 57, 40, 50, 40 ], "DexNum": 32, "Moveset": [24, 32, 34, 92]},
|
||||
{"name": "CLEFAIRY", "type": "0000", "exp": "12500", 'bst': [70, 45, 48, 35, 60 ], "DexNum": 35, "Moveset": [47, 126, 161, 118]},
|
||||
{"name": "VULPIX", "type": "1414", "exp": "15625", 'bst': [38, 41, 40, 65, 65 ], "DexNum": 37, "Moveset": [92, 38, 91, 52]},
|
||||
{"name": "JIGGLYPUFF", "type": "0000", "exp": "12500", 'bst': [115, 45, 20, 20, 25 ], "DexNum": 39,"Moveset": [47, 94, 36, 66] },
|
||||
{"name": "ZUBAT", "type": "0302", "exp": "15625", 'bst': [40, 45, 35, 55, 40 ], "DexNum": 41, "Moveset": [109, 38, 92, 72]},
|
||||
{"name": "ODDISH", "type": "1603", "exp": "11735", 'bst': [45, 50, 55, 30, 75 ], "DexNum": 43, "Moveset": [51, 79, 76, 15]},
|
||||
{"name": "PARAS", "type": "0716", "exp": "15625", 'bst': [35, 70, 55, 25, 55 ], "DexNum": 46, "Moveset": [78, 72, 141, 91]},
|
||||
{"name": "DIGLETT", "type": "0404", "exp": "15625", 'bst': [10, 55, 25, 95, 45 ], "DexNum": 50, "Moveset": [91, 28, 15, 157]},
|
||||
{"name": "MEOWTH", "type": "0000", "exp": "15625", 'bst': [40, 45, 35, 90, 40 ], "DexNum": 52, "Moveset": [44, 103, 61, 85]},
|
||||
{"name": "PSYDUCK", "type": "1515", "exp": "15625", 'bst': [50, 52, 48, 55, 50 ], "DexNum": 54, "Moveset": [61, 102, 5, 66]},
|
||||
{"name": "GROWLITHE", "type": "1414", "exp": "19531", 'bst': [55, 70, 45, 60, 50 ], "DexNum": 58, "Moveset": [126, 44, 102, 43]},
|
||||
{"name": "POLIWAG", "type": "1515", "exp": "11735", 'bst': [40, 50, 40, 90, 40 ], "DexNum": 60, "Moveset": [95, 130, 149, 57]},
|
||||
{"name": "ABRA", "type": "1818", "exp": "11735", 'bst': [25, 20, 15, 90, 105], "DexNum": 63, "Moveset": [118, 149, 34, 86]},
|
||||
{"name": "MACHOP", "type": "0101", "exp": "11735", 'bst': [70, 80, 50, 35, 35 ], "DexNum": 66, "Moveset": [2, 67, 69, 126]},
|
||||
{"name": "BELLSPROUT", "type": "1603", "exp": "11735", 'bst': [50, 75, 35, 40, 70 ], "DexNum": 69, "Moveset": [35, 72, 74, 77]},
|
||||
{"name": "GEODUDE", "type": "0504", "exp": "11735", 'bst': [40, 80, 100, 20, 30 ], "DexNum": 74, "Moveset": [88, 120, 91, 70]},
|
||||
{"name": "MAGNEMITE", "type": "1717", "exp": "15625", 'bst': [25, 35, 70, 45, 95 ], "DexNum": 81, "Moveset": [148, 129, 86, 87]},
|
||||
{"name": "FARFETCH'D", "type": "0002", "exp": "15625", 'bst': [52, 65, 55, 60, 58 ], "DexNum": 83, "Moveset": [31, 14, 28, 19]},
|
||||
{"name": "SHELLDER", "type": "1515", "exp": "19531", 'bst': [30, 65, 100, 40, 45 ], "DexNum": 90, "Moveset": [48, 128, 58, 120]},
|
||||
{"name": "GASTLY", "type": "0803", "exp": "11735", 'bst': [30, 35, 30, 80, 100], "DexNum": 92, "Moveset": [109, 101, 87, 72]},
|
||||
{"name": "KRABBY", "type": "1515", "exp": "15625", 'bst': [30, 105, 90, 50, 25 ], "DexNum": 98, "Moveset": [12, 57, 14, 70]},
|
||||
{"name": "VOLTORB", "type": "1717", "exp": "15625", 'bst': [40, 30, 50, 100, 55 ], "DexNum": 100, "Moveset": [103, 86, 87, 36]},
|
||||
{"name": "EXEGGCUTE", "type": "1618", "exp": "19531", 'bst': [60, 40, 80, 40, 60 ], "DexNum": 102, "Moveset": [95, 149, 121, 115]},
|
||||
{"name": "CUBONE", "type": "0404", "exp": "15625", 'bst': [50, 50, 95, 35, 40 ], "DexNum": 104, "Moveset": [125, 39, 126, 29]},
|
||||
{"name": "KOFFING", "type": "0303", "exp": "15625", 'bst': [40, 65, 95, 35, 60 ], "DexNum": 109, "Moveset": [123, 92, 126, 85]},
|
||||
{"name": "HORSEA", "type": "1515", "exp": "15625", 'bst': [30, 40, 70, 60, 70 ], "DexNum": 116, "Moveset": [108, 61, 129, 58]},
|
||||
{"name": "GOLDEEN", "type": "1515", "exp": "15625", 'bst': [45, 67, 60, 63, 50 ], "DexNum": 118, "Moveset": [48, 30, 57, 32]},
|
||||
{"name": "MAGIKARP", "type": "1515", "exp": "19531", 'bst': [20, 10, 55, 80, 20 ], "DexNum": 129, "Moveset": [150, 33, 0, 0]},
|
||||
{"name": "DITTO", "type": "0000", "exp": "15625", 'bst': [48, 48, 48, 48, 48 ], "DexNum": 132, "Moveset": [144, 0, 0, 0]},
|
||||
{"name": "EEVEE", "type": "0000", "exp": "15625", 'bst': [55, 55, 50, 55, 65 ], "DexNum": 133, "Moveset": [28, 98, 38, 164]},
|
||||
{"name": "OMANYTE", "type": "0515", "exp": "15625", 'bst': [35, 40, 100, 35, 90 ], "DexNum": 138, "Moveset": [110, 61, 38, 92]},
|
||||
{"name": "KABUTO", "type": "0515", "exp": "15625", 'bst': [30, 80, 90, 55, 45 ], "DexNum": 140, "Moveset": [58, 36, 57, 117]},
|
||||
{"name": "DRATINI", "type": "1A1A", "exp": "19531", 'bst': [41, 64, 45, 50, 50 ], "DexNum": 147, "Moveset": [86, 35, 87, 126]},
|
||||
]
|
||||
|
||||
pika_cup_list = [
|
||||
{"name": "BULBASAUR", "type": "1603", "exp": "2035", 'bst': [45, 49, 49, 45, 65 ], "DexNum": 1, "Moveset": [73, 92, 72, 38]},
|
||||
{"name": "IVYSAUR", "type": "1603", "exp": "2035", 'bst': [60, 62, 63, 60, 80 ], "DexNum": 2, "Moveset": [14, 34, 76, 73]},
|
||||
{"name": "CHARMANDER", "type": "1414", "exp": "2035", 'bst': [39, 52, 43, 65, 50 ], "DexNum": 4, "Moveset": [126, 69, 70, 45]},
|
||||
{"name": "CHARMELEON", "type": "1414", "exp": "2035", 'bst': [58, 64, 58, 80, 65 ], "DexNum": 5, "Moveset": [14, 25, 92, 52]},
|
||||
{"name": "SQUIRTLE", "type": "1515", "exp": "2035", 'bst': [44, 48, 65, 43, 50 ], "DexNum": 7, "Moveset": [33, 91, 57, 59]},
|
||||
{"name": "WARTORTLE", "type": "1515", "exp": "2035", 'bst': [59, 63, 80, 58, 65 ], "DexNum": 8, "Moveset": [34, 117, 57, 115]},
|
||||
{"name": "CATERPIE", "type": "0707", "exp": "3375", 'bst': [45, 30, 35, 45, 20 ], "DexNum": 10, "Moveset": [81, 33, 0, 0]},
|
||||
{"name": "METAPOD", "type": "0707", "exp": "3375", 'bst': [50, 20, 55, 30, 25 ], "DexNum": 11, "Moveset": [33, 81, 0, 0]},
|
||||
{"name": "BUTTERFREE", "type": "0702", "exp": "3375", 'bst': [60, 45, 50, 70, 80 ], "DexNum": 12, "Moveset": [77, 63, 149, 36]},
|
||||
{"name": "WEEDLE", "type": "0703", "exp": "3375", 'bst': [40, 35, 30, 50, 20 ], "DexNum": 13, "Moveset": [81, 40, 0, 0]},
|
||||
{"name": "KAKUNA", "type": "0703", "exp": "3375", 'bst': [45, 25, 50, 35, 25 ], "DexNum": 14, "Moveset": [81, 40, 0, 0]},
|
||||
{"name": "BEEDRILL", "type": "0703", "exp": "3375", 'bst': [65, 80, 40, 75, 45 ], "DexNum": 15, "Moveset": [31, 104, 14, 63]},
|
||||
{"name": "PIDGEY", "type": "0002", "exp": "2035", 'bst': [40, 45, 40, 56, 35 ], "DexNum": 16, "Moveset": [115, 19, 92, 38]},
|
||||
{"name": "PIDGEOTTO", "type": "0002", "exp": "2035", 'bst': [63, 60, 55, 71, 50 ], "DexNum": 17, "Moveset": [143, 36, 98, 28]},
|
||||
{"name": "RATTATA", "type": "0000", "exp": "3375", 'bst': [30, 56, 35, 72, 25 ], "DexNum": 19, "Moveset": [87, 98, 59, 91]},
|
||||
{"name": "RATICATE", "type": "0000", "exp": "3375", 'bst': [55, 81, 60, 97, 50 ], "DexNum": 20, "Moveset": [158, 92, 58, 129]},
|
||||
{"name": "SPEAROW", "type": "0002", "exp": "3375", 'bst': [40, 60, 30, 70, 31 ], "DexNum": 21, "Moveset": [38, 104, 19, 102]},
|
||||
{"name": "FEAROW", "type": "0002", "exp": "3375", 'bst': [65, 90, 65, 100, 61 ], "DexNum": 22, "Moveset": [19, 104, 64, 102]},
|
||||
{"name": "EKANS", "type": "0303", "exp": "3375", 'bst': [35, 60, 44, 55, 40 ], "DexNum": 23, "Moveset": [35, 40, 89, 43]},
|
||||
{"name": "PIKACHU", "type": "1717", "exp": "3375", 'bst': [35, 55, 30, 90, 50 ], "DexNum": 25, "Moveset": [98, 66, 85, 86]},
|
||||
{"name": "RAICHU", "type": "1717", "exp": "3375", 'bst': [60, 90, 55, 100, 90 ], "DexNum": 26, "Moveset": [87, 86, 69, 45]},
|
||||
{"name": "SANDSHREW", "type": "0404", "exp": "3375", 'bst': [50, 75, 85, 40, 30 ], "DexNum": 27, "Moveset": [28, 89, 66, 14]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "2035", 'bst': [55, 47, 52, 41, 40 ], "DexNum": 29, "Moveset": [92, 87, 59, 34]},
|
||||
{"name": "NIDORINA", "type": "0303", "exp": "2035", 'bst': [70, 62, 67, 56, 55 ], "DexNum": 30, "Moveset": [92, 58, 36, 32]},
|
||||
{"name": "NIDOQUEEN", "type": "0304", "exp": "2035", 'bst': [90, 82, 87, 76, 75 ], "DexNum": 31, "Moveset": [90, 24, 57, 115]},
|
||||
{"name": "NIDORAN", "type": "0303", "exp": "2035", 'bst': [46, 57, 40, 50, 40 ], "DexNum": 32, "Moveset": [59, 85, 34, 92]},
|
||||
{"name": "NIDORINO", "type": "0303", "exp": "2035", 'bst': [61, 72, 57, 65, 55 ], "DexNum": 33, "Moveset": [32, 58, 24, 30]},
|
||||
{"name": "NIDOKING", "type": "0304", "exp": "2035", 'bst': [81, 92, 77, 85, 75 ], "DexNum": 34, "Moveset": [40, 89, 61, 24]},
|
||||
{"name": "CLEFAIRY", "type": "0000", "exp": "2700", 'bst': [70, 45, 48, 35, 60 ], "DexNum": 35, "Moveset": [86, 161, 94, 118]},
|
||||
{"name": "CLEFABLE", "type": "0000", "exp": "2700", 'bst': [95, 70, 73, 60, 85 ], "DexNum": 36, "Moveset": [118, 161, 47, 104]},
|
||||
{"name": "VULPIX", "type": "1414", "exp": "3375", 'bst': [38, 41, 40, 65, 65 ], "DexNum": 37, "Moveset": [38, 126, 91, 104]},
|
||||
{"name": "NINETALES", "type": "1414", "exp": "3375", 'bst': [73, 76, 75, 100, 100], "DexNum": 38, "Moveset": [91, 52, 63, 115]},
|
||||
{"name": "JIGGLYPUFF", "type": "0000", "exp": "2700", 'bst': [115, 45, 20, 20, 25 ], "DexNum": 39, "Moveset": [47, 34, 86, 58]},
|
||||
{"name": "WIGGLYTUFF", "type": "0000", "exp": "2700", 'bst': [140, 70, 45, 45, 50 ], "DexNum": 40, "Moveset": [87, 5, 47, 104]},
|
||||
{"name": "ZUBAT", "type": "0302", "exp": "3375", 'bst': [40, 45, 35, 55, 40 ], "DexNum": 41, "Moveset": [48, 129, 72, 92]},
|
||||
{"name": "ODDISH", "type": "1603", "exp": "2035", 'bst': [45, 50, 55, 30, 75 ], "DexNum": 43, "Moveset": [92, 14, 72, 36]},
|
||||
{"name": "PARAS", "type": "0716", "exp": "3375", 'bst': [35, 70, 55, 25, 55 ], "DexNum": 46, "Moveset": [78, 91, 72, 36]},
|
||||
{"name": "VENONAT", "type": "0703", "exp": "3375", 'bst': [60, 55, 50, 45, 40 ], "DexNum": 48, "Moveset": [48, 94, 148, 38]},
|
||||
{"name": "DIGLETT", "type": "0404", "exp": "3375", 'bst': [10, 55, 25, 95, 45 ], "DexNum": 50, "Moveset": [89, 104, 36, 90]},
|
||||
{"name": "MEOWTH", "type": "0000", "exp": "3375", 'bst': [40, 45, 35, 90, 40 ], "DexNum": 52, "Moveset": [104, 85, 34, 156]},
|
||||
{"name": "PSYDUCK", "type": "1515", "exp": "3375", 'bst': [50, 52, 48, 55, 50 ], "DexNum": 54, "Moveset": [61, 59, 91, 102]},
|
||||
{"name": "MANKEY", "type": "0101", "exp": "3375", 'bst': [40, 80, 35, 70, 35 ], "DexNum": 56, "Moveset": [67, 2, 91, 68]},
|
||||
{"name": "GROWLITHE", "type": "1414", "exp": "4218", 'bst': [55, 70, 45, 60, 50 ], "DexNum": 58, "Moveset": [91, 126, 38, 115]},
|
||||
{"name": "ARCANINE", "type": "1414", "exp": "4218", 'bst': [90, 110, 80, 95, 80 ], "DexNum": 59, "Moveset": [91, 44, 52, 104]},
|
||||
{"name": "POLIWAG", "type": "1515", "exp": "2035", 'bst': [40, 50, 40, 90, 40 ], "DexNum": 60, "Moveset": [57, 34, 59, 92]},
|
||||
{"name": "POLIWHIRL", "type": "1515", "exp": "2035", 'bst': [65, 65, 65, 90, 50 ], "DexNum": 61, "Moveset": [57, 38, 118, 89]},
|
||||
{"name": "POLIWRATH", "type": "1501", "exp": "2035", 'bst': [90, 85, 95, 70, 70 ], "DexNum": 62, "Moveset": [57, 3, 118, 95]},
|
||||
{"name": "ABRA", "type": "1818", "exp": "2035", 'bst': [25, 20, 15, 90, 105], "DexNum": 63, "Moveset": [94, 86, 69, 115]},
|
||||
{"name": "KADABRA", "type": "1818", "exp": "2035", 'bst': [40, 35, 30, 105, 120], "DexNum": 64, "Moveset": [94, 118, 104, 69]},
|
||||
{"name": "ALAKAZAM", "type": "1818", "exp": "2035", 'bst': [55, 50, 45, 120, 135], "DexNum": 65, "Moveset": [149, 118, 86, 5]},
|
||||
{"name": "MACHOP", "type": "0101", "exp": "2035", 'bst': [70, 80, 50, 35, 35 ], "DexNum": 66, "Moveset": [2, 66, 126, 117]},
|
||||
{"name": "BELLSPROUT", "type": "1603", "exp": "2035", 'bst': [50, 75, 35, 40, 70 ], "DexNum": 69, "Moveset": [74, 36, 72, 115]},
|
||||
{"name": "TENTACOOL", "type": "1503", "exp": "4218", 'bst': [40, 40, 35, 70, 100], "DexNum": 72, "Moveset": [57, 51, 48, 92]},
|
||||
{"name": "TENTACRUEL", "type": "1503", "exp": "4218", 'bst': [80, 70, 65, 100, 120], "DexNum": 73, "Moveset": [48, 35, 92, 72]},
|
||||
{"name": "GEODUDE", "type": "0504", "exp": "2035", 'bst': [40, 80, 100, 20, 30 ], "DexNum": 74, "Moveset": [5, 89, 157, 111]},
|
||||
{"name": "PONYTA", "type": "1414", "exp": "3375", 'bst': [50, 85, 55, 90, 65 ], "DexNum": 77, "Moveset": [126, 32, 115, 129]},
|
||||
{"name": "SLOWPOKE", "type": "1518", "exp": "3375", 'bst': [90, 65, 65, 15, 40 ], "DexNum": 79, "Moveset": [94, 57, 148, 91]},
|
||||
{"name": "MAGNEMITE", "type": "1717", "exp": "3375", 'bst': [25, 35, 70, 45, 95 ], "DexNum": 81, "Moveset": [86, 85, 129, 164]},
|
||||
{"name": "FARFETCH'D", "type": "0002", "exp": "3375", 'bst': [52, 65, 55, 60, 58 ], "DexNum": 83, "Moveset": [28, 31, 19, 115]},
|
||||
{"name": "SEEL", "type": "1515", "exp": "3375", 'bst': [65, 45, 55, 45, 70 ], "DexNum": 86, "Moveset": [57, 29, 32, 59]},
|
||||
{"name": "SHELLDER", "type": "1515", "exp": "4218", 'bst': [30, 65, 100, 40, 45 ], "DexNum": 90, "Moveset": [59, 161, 153, 57]},
|
||||
{"name": "CLOYSTER", "type": "1519", "exp": "4218", 'bst': [50, 95, 180, 70, 85 ], "DexNum": 91, "Moveset": [48, 128, 63, 62]},
|
||||
{"name": "GASTLY", "type": "0803", "exp": "2035", 'bst': [30, 35, 30, 80, 100], "DexNum": 92, "Moveset": [109, 94, 101, 153]},
|
||||
{"name": "HAUNTER", "type": "0803", "exp": "2035", 'bst': [45, 50, 45, 95, 115], "DexNum": 93, "Moveset": [109, 85, 101, 120]},
|
||||
{"name": "GENGAR", "type": "0803", "exp": "2035", 'bst': [60, 65, 60, 110, 130], "DexNum": 94, "Moveset": [109, 101, 72, 118]},
|
||||
{"name": "ONIX", "type": "0504", "exp": "3375", 'bst': [35, 45, 160, 70, 30 ], "DexNum": 95, "Moveset": [157, 70, 89, 120]},
|
||||
{"name": "DROWZEE", "type": "1818", "exp": "3375", 'bst': [60, 48, 45, 42, 90 ], "DexNum": 96, "Moveset": [95, 94, 138, 161]},
|
||||
{"name": "KRABBY", "type": "1515", "exp": "3375", 'bst': [30, 105, 90, 50, 25 ], "DexNum": 98, "Moveset": [58, 34, 57, 92]},
|
||||
{"name": "KINGLER", "type": "1515", "exp": "3375", 'bst': [55, 130, 115, 75, 50 ], "DexNum": 99, "Moveset": [57, 70, 104, 102]},
|
||||
{"name": "VOLTORB", "type": "1717", "exp": "3375", 'bst': [40, 30, 50, 100, 55 ], "DexNum": 100, "Moveset": [153, 36, 85, 86]},
|
||||
{"name": "EXEGGCUTE", "type": "1618", "exp": "4218", 'bst': [60, 40, 80, 40, 60 ], "DexNum": 102, "Moveset": [94, 104, 121, 92]},
|
||||
{"name": "EXEGGUTOR", "type": "1618", "exp": "4218", 'bst': [95, 95, 85, 55, 125], "DexNum": 103, "Moveset": [92, 140, 72, 149]},
|
||||
{"name": "CUBONE", "type": "0404", "exp": "3375", 'bst': [50, 50, 95, 35, 40 ], "DexNum": 104, "Moveset": [70, 89, 39, 59]},
|
||||
{"name": "LICKITUNG", "type": "0000", "exp": "3375", 'bst': [90, 55, 75, 30, 60 ], "DexNum": 108, "Moveset": [38, 48, 126, 87]},
|
||||
{"name": "KOFFING", "type": "0303", "exp": "3375", 'bst': [40, 65, 95, 35, 60 ], "DexNum": 109, "Moveset": [126, 92, 85, 120]},
|
||||
{"name": "RHYHORN", "type": "0405", "exp": "4218", 'bst': [80, 85, 95, 25, 30 ], "DexNum": 111, "Moveset": [157, 89, 30, 164]},
|
||||
{"name": "CHANSEY", "type": "0000", "exp": "2700", 'bst': [250, 5, 5, 50, 105], "DexNum": 113, "Moveset": [161, 68, 61, 85]},
|
||||
{"name": "HORSEA", "type": "1515", "exp": "3375", 'bst': [30, 40, 70, 60, 70 ], "DexNum": 116, "Moveset": [57, 59, 92, 129]},
|
||||
{"name": "SEADRA", "type": "1515", "exp": "3375", 'bst': [55, 65, 95, 85, 95 ], "DexNum": 117, "Moveset": [108, 61, 58, 102]},
|
||||
{"name": "GOLDEEN", "type": "1515", "exp": "3375", 'bst': [45, 67, 60, 63, 50 ], "DexNum": 118, "Moveset": [57, 38, 58, 32]},
|
||||
{"name": "STARYU", "type": "1515", "exp": "4218", 'bst': [30, 45, 55, 85, 70 ], "DexNum": 120, "Moveset": [57, 94, 161, 86]},
|
||||
{"name": "STARMIE", "type": "1518", "exp": "4218", 'bst': [60, 75, 85, 115, 100], "DexNum": 121, "Moveset": [149, 61, 87, 164]},
|
||||
{"name": "MR. MIME", "type": "1818", "exp": "3375", 'bst': [40, 45, 65, 90, 100], "DexNum": 122, "Moveset": [25, 94, 112, 118]},
|
||||
{"name": "SCYTHER", "type": "0702", "exp": "3375", 'bst': [70, 110, 80, 105, 55 ], "DexNum": 123, "Moveset": [98, 14, 63, 104]},
|
||||
{"name": "PINSIR", "type": "0707", "exp": "4218", 'bst': [65, 125, 100, 85, 55 ], "DexNum": 127, "Moveset": [36, 66, 117, 102]},
|
||||
{"name": "MAGIKARP", "type": "1515", "exp": "4218", 'bst': [20, 10, 55, 80, 20 ], "DexNum": 129, "Moveset": [150, 33, 0, 0]},
|
||||
{"name": "GYARADOS", "type": "1502", "exp": "4218", 'bst': [95, 125, 79, 81, 100], "DexNum": 130, "Moveset": [56, 44, 156, 43]},
|
||||
{"name": "LAPRAS", "type": "1519", "exp": "4218", 'bst': [130, 85, 80, 60, 95 ], "DexNum": 131, "Moveset": [61, 58, 45, 130]},
|
||||
{"name": "DITTO", "type": "0000", "exp": "3375", 'bst': [48, 48, 48, 48, 48 ], "DexNum": 132, "Moveset": [144, 0, 0, 0]},
|
||||
{"name": "PORYGON", "type": "0000", "exp": "3375", 'bst': [65, 60, 70, 40, 75 ], "DexNum": 137, "Moveset": [160, 159, 161, 94]},
|
||||
{"name": "DRATINI", "type": "1A1A", "exp": "4218", 'bst': [41, 64, 45, 50, 50 ], "DexNum": 147, "Moveset": [126, 59, 34, 86]},
|
||||
]
|
||||
|
||||
kanto_attack_dict = {
|
||||
"PHY1": [34, 89, 163],
|
||||
"PHY2": [38, 63, 65, 70, 136, 155, 161],
|
||||
"PHY3": [23, 24, 25, 26, 29, 30, 36, 37, 44, 66, 120, 124, 146, 153, 157, 158],
|
||||
"PHY4": [2, 4, 5, 11, 15, 21, 27, 41, 67, 69, 91, 101, 121, 125, 129, 131, 143, 154, 162],
|
||||
"PHY5": [1, 3, 6, 10, 16, 17, 20, 31, 33, 35, 42, 51, 64, 88, 98, 117, 130, 140],
|
||||
"PHY6": [13, 19, 40, 49, 99, 122, 123, 132, 141, 68],
|
||||
"PHY7": [68],
|
||||
"SPE1": [53, 57, 58, 85, 94, 59],
|
||||
"SPE2": [87, 126, 7, 8, 9],
|
||||
"SPE3": [56, 127, 128, 152],
|
||||
"SPE4": [60, 61, 62, 75, 76, 80, 93],
|
||||
"SPE5": [138, 149, 55, 72, 83, 84],
|
||||
"SPE6": [52, 145],
|
||||
"SPE7": [22, 71, 82],
|
||||
"STA1": [86, 79, 142],
|
||||
"STA2": [95, 78, 109, 137],
|
||||
"STA3": [47, 97, 133, 156],
|
||||
"STA4": [14, 28, 48, 74, 77, 92, 103, 104, 105, 107, 108, 112, 113, 114, 115, 116, 134, 135, 139, 151, 164],
|
||||
"STA5": [12, 32, 39, 43, 45, 50, 54, 81, 90, 96, 106, 110, 111, 148, 159],
|
||||
"STA6": [73, 102, 118, 119, 144, 160],
|
||||
"STA7": [18, 46, 100, 150],
|
||||
"NORMAL": [1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 15, 16, 20, 21, 23, 25, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 44, 49, 63, 70, 98, 99, 117, 120, 121, 129, 130, 131, 132, 140, 146, 153, 154, 158, 161, 162, 163],
|
||||
"FIGHTING": [24, 26, 27, 66, 67, 68, 69, 136],
|
||||
"FLYING": [17, 19, 64, 65, 143],
|
||||
"POISON": [40, 51, 123, 124],
|
||||
"GROUND": [89, 90, 91, 125, 155],
|
||||
"ROCK": [88, 157],
|
||||
"BUG": [41, 42, 141],
|
||||
"GHOST": [101, 122],
|
||||
"FIRE": [7, 52, 53, 83, 126],
|
||||
"WATER": [55, 56, 57, 61, 127, 128, 145, 152],
|
||||
"GRASS": [22, 71, 72, 75, 76, 80],
|
||||
"ELECTRIC": [9, 84, 85, 87],
|
||||
"PSYCHIC": [60, 93, 94, 138, 149],
|
||||
"ICE": [8, 58, 59, 62],
|
||||
"DRAGON": [82]
|
||||
}
|
||||
|
||||
# random number rolling boundaries for picking a move bucket
|
||||
# lower BSTs are weighted towards the left, which should give weaker mons better moves on average
|
||||
# as you go up in BST teir, the weights shift to the right towards a tendancy for weaker moves
|
||||
stat_distribution_list = [
|
||||
[18.0, 41.4, 59.3, 74.0, 86.6, 98.0, 100.0],
|
||||
[14.9, 31.9, 52.9, 69.9, 84.8, 98.0, 100.0],
|
||||
[13.2, 27.6, 44.6, 65.6, 82.7, 97.0, 100.0],
|
||||
[12.3, 25.8, 40.9, 58.3, 79.6, 97.0, 100.0],
|
||||
[11.5, 23.6, 36.8, 52.3, 71.3, 96.0, 100.0],
|
||||
]
|
||||
|
||||
bst_weights = [
|
||||
[100, 100, 100, 100, 100], # uniform distribution
|
||||
[200, 150, 80, 50, 20], # weight one side
|
||||
[180, 80, 80, 80, 80], # heavily weight one stat
|
||||
[160, 50, 150, 90, 50] # random bursts
|
||||
]
|
||||
#We don't need lists for pokeball and greatball cup round 1 since they are all level 50 and level 51 respectively
|
||||
pokecupr1_ultra_levels = [
|
||||
[53, 51, 51, 53, 51, 51],
|
||||
[51, 50, 54, 51, 50, 50],
|
||||
[50, 51, 50, 54, 54, 51],
|
||||
[53, 50, 50, 52, 50, 55],
|
||||
[50, 51, 50, 54, 51, 54],
|
||||
[51, 51, 51, 51, 51, 53],
|
||||
[52, 51, 50, 54, 50, 52],
|
||||
[55, 50, 50, 50, 50, 50]
|
||||
]
|
||||
|
||||
pokecupr1_master_levels = [
|
||||
[51, 52, 51, 51, 52, 52],
|
||||
[50, 50, 53, 51, 54, 51],
|
||||
[51, 54, 51, 50, 50, 53],
|
||||
[53, 52, 50, 51, 51, 50],
|
||||
[50, 51, 52, 53, 54, 50],
|
||||
[51, 53, 50, 52, 52, 50],
|
||||
[50, 52, 53, 53, 50, 50],
|
||||
[55, 50, 50, 50, 50, 55]
|
||||
]
|
||||
|
||||
petitcupr1_levels = [
|
||||
[25, 25, 25, 25, 25, 25],
|
||||
[25, 26, 26, 26, 25, 25],
|
||||
[25, 25, 25, 30, 25, 30],
|
||||
[26, 26, 27, 26, 26, 27],
|
||||
[26, 26, 27, 26, 27, 27],
|
||||
[26, 27, 26, 27, 27, 27],
|
||||
[30, 25, 25, 25, 25, 30],
|
||||
[25, 25, 25, 25, 30, 30]
|
||||
]
|
||||
|
||||
pikacupr1_levels = [
|
||||
[16, 15, 15, 15, 15, 15],
|
||||
[15, 16, 15, 15, 15, 15],
|
||||
[16, 15, 16, 15, 15, 16],
|
||||
[16, 17, 16, 16, 16, 15],
|
||||
[16, 15, 15, 16, 18, 18],
|
||||
[20, 16, 15, 15, 16, 18],
|
||||
[20, 20, 15, 15, 15, 15],
|
||||
[18, 16, 16, 18, 16, 16]
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
import math
|
||||
|
||||
class levelExpCalculator:
|
||||
@classmethod
|
||||
def getExpValue(self, lvl, growthRate: str):
|
||||
|
||||
expValue = 0
|
||||
if(growthRate == "slow"):
|
||||
expValue = 5 * math.pow(lvl, 3) / 4
|
||||
return expValue
|
||||
if(growthRate == "mediumslow"):
|
||||
expValue = ((6/5) * math.pow(lvl, 3)) - (15*(math.pow(lvl, 2))) + (100*lvl) - 140
|
||||
return expValue
|
||||
if(growthRate == "mediumfast"):
|
||||
expValue = math.pow(lvl, 3)
|
||||
return expValue
|
||||
if(growthRate == "fast"):
|
||||
expValue = 4 * math.pow(lvl, 3) / 5
|
||||
return expValue
|
||||
else:
|
||||
print("Invalid growth rate.")
|
||||
return expValue
|
||||
@@ -0,0 +1,144 @@
|
||||
import random
|
||||
|
||||
from . import constants
|
||||
|
||||
|
||||
def get_random_move(attack_type, distribution):
|
||||
key_str = ""
|
||||
|
||||
if attack_type not in ['PHY', 'SPE', 'STA']:
|
||||
key_str = attack_type
|
||||
else:
|
||||
roll = random.randrange(1, 100)
|
||||
if roll <= distribution[0]:
|
||||
key_str = attack_type + "1"
|
||||
elif roll <= distribution[1]:
|
||||
key_str = attack_type + "2"
|
||||
elif roll <= distribution[2]:
|
||||
key_str = attack_type + "3"
|
||||
elif roll <= distribution[3]:
|
||||
key_str = attack_type + "4"
|
||||
elif roll > distribution[4]:
|
||||
key_str = attack_type + "5"
|
||||
elif roll <= distribution[5]:
|
||||
key_str = attack_type + "6"
|
||||
elif roll <= distribution[6]:
|
||||
key_str = attack_type + "7"
|
||||
|
||||
# Spore clause
|
||||
if attack_type == 'STA' and random.randint(1, 200) == 1:
|
||||
return 147
|
||||
|
||||
return random.choice(constants.kanto_attack_dict[key_str])
|
||||
|
||||
def get_type_name(type_num):
|
||||
if random.random() < 0.5:
|
||||
type_str = type_num.hex()[0:2].upper()
|
||||
else:
|
||||
type_str = type_num.hex()[2:].upper()
|
||||
|
||||
if type_str == '01':
|
||||
return 'FIGHTING'
|
||||
elif type_str == '02':
|
||||
return 'FLYING'
|
||||
elif type_str == '03':
|
||||
return 'POISON'
|
||||
elif type_str == '04':
|
||||
return 'GROUND'
|
||||
elif type_str == '05':
|
||||
return 'ROCK'
|
||||
elif type_str == '07':
|
||||
return 'BUG'
|
||||
elif type_str == '08':
|
||||
return 'GHOST'
|
||||
elif type_str == '14':
|
||||
return 'FIRE'
|
||||
elif type_str == '15':
|
||||
return 'WATER'
|
||||
elif type_str == '16':
|
||||
return 'GRASS'
|
||||
elif type_str == '17':
|
||||
return 'ELECTRIC'
|
||||
elif type_str == '18':
|
||||
return 'PSYCHIC'
|
||||
elif type_str == '19':
|
||||
return 'ICE'
|
||||
elif type_str == '1A':
|
||||
return 'DRAGON'
|
||||
else: # type == '00' or a bad value got in here
|
||||
return 'NORMAL'
|
||||
|
||||
class MovesetGenerator:
|
||||
@staticmethod
|
||||
def get_random_moveset(bst_list, rando_factor, pkm_type):
|
||||
bst = sum(bst_list)
|
||||
|
||||
# first type of move is always a damaging move that lines up with higher attacking stat
|
||||
first_type = "PHY" if bst_list[1] > bst_list[4] else "SPE"
|
||||
|
||||
# second move is a STAB damaging move if factor is at least 2
|
||||
if (rando_factor < 4):
|
||||
second_type = get_type_name(pkm_type)
|
||||
else:
|
||||
one_in_three = random.randrange(1, 99)
|
||||
if one_in_three <= 33:
|
||||
second_type = "PHY"
|
||||
elif one_in_three <= 66:
|
||||
second_type = "SPE"
|
||||
else:
|
||||
second_type = "STA"
|
||||
|
||||
# third move afflicts a status or affects stats if factor is at least 3
|
||||
if (rando_factor < 3):
|
||||
third_type = "STA"
|
||||
else:
|
||||
one_in_three = random.randrange(1, 99)
|
||||
if one_in_three <= 33:
|
||||
third_type = "PHY"
|
||||
elif one_in_three <= 66:
|
||||
third_type = "SPE"
|
||||
else:
|
||||
third_type = "STA"
|
||||
|
||||
# fourth move is random
|
||||
one_in_three = random.randrange(1, 99)
|
||||
if one_in_three <= 33:
|
||||
fourth_type = "PHY"
|
||||
elif one_in_three <= 66:
|
||||
fourth_type = "SPE"
|
||||
else:
|
||||
fourth_type = "STA"
|
||||
|
||||
attack_types = [first_type, second_type, third_type, fourth_type]
|
||||
moveset = []
|
||||
if (rando_factor == 2):
|
||||
if bst <= 225:
|
||||
distribution = constants.stat_distribution_list[0]
|
||||
elif bst <= 300:
|
||||
distribution = constants.stat_distribution_list[1]
|
||||
elif bst <= 375:
|
||||
distribution = constants.stat_distribution_list[2]
|
||||
elif bst <= 450:
|
||||
distribution = constants.stat_distribution_list[3]
|
||||
else:
|
||||
distribution = constants.stat_distribution_list[4]
|
||||
elif (rando_factor == 3):
|
||||
if bst <= 300:
|
||||
distribution = constants.stat_distribution_list[random.randrange(0, 1)]
|
||||
elif bst <= 450:
|
||||
distribution = constants.stat_distribution_list[random.randrange(2, 3)]
|
||||
else:
|
||||
distribution = constants.stat_distribution_list[4]
|
||||
else:
|
||||
distribution = constants.stat_distribution_list[random.randrange(0, 4)]
|
||||
|
||||
for atk_type in attack_types:
|
||||
random_move = get_random_move(atk_type, distribution)
|
||||
while True:
|
||||
if random_move in moveset:
|
||||
random_move = get_random_move(atk_type, distribution)
|
||||
else:
|
||||
break
|
||||
moveset.append(random_move)
|
||||
|
||||
return moveset
|
||||
@@ -0,0 +1,57 @@
|
||||
import random
|
||||
|
||||
from . import constants
|
||||
|
||||
class BaseValuesRandomizer:
|
||||
@classmethod
|
||||
def randomize_stats(cls, vanilla_stats, random_factor):
|
||||
min_val = 20
|
||||
max_val = 235
|
||||
|
||||
bst_list = []
|
||||
for stat in vanilla_stats:
|
||||
bst_list.append(stat)
|
||||
bst = sum(bst_list)
|
||||
new_stats_bytes = bytearray()
|
||||
|
||||
# Start with an array of 5 numbers, all at the minimum value
|
||||
new_stats = [min_val] * 5
|
||||
current_sum = sum(new_stats)
|
||||
|
||||
# Increment numbers until we reach BST
|
||||
while current_sum < bst:
|
||||
# Randomly select an index to increase
|
||||
idx = cls.select_index(random_factor)
|
||||
|
||||
# Only increase if it won't exceed max_val
|
||||
if new_stats[idx] < max_val:
|
||||
new_stats[idx] += 1
|
||||
current_sum += 1
|
||||
else:
|
||||
# Check if all numbers are maxed out (should never happen with correct BST input)
|
||||
if all(n == max_val for n in new_stats):
|
||||
raise RuntimeError("All stats reached max_val but BST is not yet met. Something went wrong!")
|
||||
|
||||
random.shuffle(new_stats)
|
||||
for stat in new_stats:
|
||||
try:
|
||||
new_stats_bytes.extend(stat.to_bytes(1, "big"))
|
||||
except OverflowError:
|
||||
print("ERROR: BST is too high.")
|
||||
print("BST_STR: " + str(vanilla_stats))
|
||||
print("BST: " + str(bst))
|
||||
print("STATS: " + str(new_stats))
|
||||
exit(1)
|
||||
|
||||
return new_stats_bytes
|
||||
|
||||
@classmethod
|
||||
def select_index(cls, random_factor):
|
||||
random_factor = random_factor - 1
|
||||
weight_map = {
|
||||
1: constants.bst_weights[0] if random.random() < 0.5 else constants.bst_weights[1],
|
||||
2: constants.bst_weights[2],
|
||||
3: constants.bst_weights[3]
|
||||
}
|
||||
|
||||
return random.choices([0, 1, 2, 3, 4], weights=weight_map.get(random_factor, constants.bst_weights[0]))[0]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
|
||||
class Util:
|
||||
|
||||
@staticmethod
|
||||
def calculate_stat(stat, ev, iv, level):
|
||||
return int((((stat + iv) * 2 + math.floor(math.ceil(math.sqrt(ev)) / 4)) * level)/100) + 5
|
||||
|
||||
@staticmethod
|
||||
def calculate_hp_stat(stat, ev, iv, level):
|
||||
return int((((stat + iv) * 2 + math.floor(math.ceil(math.sqrt(ev)) / 4)) * level)/100) + level + 10
|
||||
|
||||
@staticmethod
|
||||
def random_int_set(min_val, max_val, count):
|
||||
return random.sample(range(min_val, max_val), count)
|
||||
|
||||
@staticmethod
|
||||
def random_string_hex(length):
|
||||
int_set = random.sample(range(0, 15), length)
|
||||
return_hex = ""
|
||||
for integer in int_set:
|
||||
return_hex = return_hex + hex(integer)[2:]
|
||||
return return_hex
|
||||
@@ -0,0 +1,23 @@
|
||||
import math
|
||||
|
||||
from . import util
|
||||
|
||||
class DisplayDataWriter:
|
||||
@staticmethod
|
||||
def write_gym_tower_display(new_display_stats_set, evs, iv_str, lvl):
|
||||
ivs = [0, 0, 0, 0, 0]
|
||||
iv_binary = "{0:016b}".format(int(iv_str, 16))
|
||||
for i in range(0, 4):
|
||||
int_val = int(iv_binary[i * 4:(i * 4 + 4)], 2)
|
||||
ivs[i + 1] = int_val
|
||||
if int_val % 2 != 0:
|
||||
ivs[0] = int(ivs[0] + math.pow(2, 3 - i))
|
||||
|
||||
display_stats = bytearray()
|
||||
new_displays_int = [int(x) for x in new_display_stats_set]
|
||||
display = util.Util.calculate_hp_stat(new_displays_int[0], evs[0], ivs[0], lvl)
|
||||
display_stats.extend(display.to_bytes(2, "big"))
|
||||
for j in range(1, 5):
|
||||
display = util.Util.calculate_stat(new_displays_int[j], evs[j], ivs[j], lvl)
|
||||
display_stats.extend(display.to_bytes(2, "big"))
|
||||
return display_stats
|
||||
@@ -0,0 +1,11 @@
|
||||
# The first thing you should make for your world is an archipelago.json manifest file.
|
||||
# You can reference APQuest's, but you should change the "game" field (obviously),
|
||||
# and you should also change the "minimum_ap_version" - probably to the current value of Utils.__version__.
|
||||
|
||||
# Apart from the regular apworld code that allows generating multiworld seeds with your game,
|
||||
# your apworld might have other "components" that should be launchable from the Archipelago Launcher.
|
||||
# You can ignore this for now. If you are specifically interested in components, you can read components.py.
|
||||
|
||||
# The main thing we do in our __init__.py is importing our world class from our world.py to initialize it.
|
||||
# Obviously, this world class needs to exist first. For this, read world.py.
|
||||
from .world import Schedule1World as Schedule1World
|
||||
@@ -0,0 +1 @@
|
||||
{"game": "Schedule I", "minimum_ap_version": "0.6.6", "world_version": "3.5.4", "authors": ["MacH8s"], "compatible_version": 7, "version": 7}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
{
|
||||
"Overworld": {
|
||||
"connections": {
|
||||
"Welcome to Hyland Point": true,
|
||||
"Northtown" : true
|
||||
}
|
||||
},
|
||||
"Northtown": {
|
||||
"connections": {"Westville": {"randomize_level_unlocks&!randomize_customers" : {"has": "Westville Region Unlock"}},
|
||||
"Weed Recipe Checks": {"randomize_level_unlocks" : {"has": "Mixing Station Unlock"}}}
|
||||
},
|
||||
"Westville": {
|
||||
"connections": {"Downtown": {"randomize_cartel_influence&!randomize_customers" : {"has_all_counts": {"Cartel Influence, Westville": 2}}},
|
||||
"Meth Recipe Checks": {"randomize_level_unlocks" : {"has_any": [["Mixing Station Mk II Unlock", "Mixing Station Unlock"]],
|
||||
"has_all" : ["Acid Unlock",
|
||||
"Phosphorus Unlock",
|
||||
"Chemistry Station Unlock",
|
||||
"Lab Oven Unlock",
|
||||
"Warehouse Access"]},
|
||||
"randomize_suppliers" : {"has": "Shirley Watts Unlocked"},
|
||||
"randomize_customers&!randomize_suppliers" : {"has_any" : [["Meg Cooley Unlocked", "Jerry Montero Unlocked"]]}}}
|
||||
},
|
||||
"Downtown": {
|
||||
"connections": {"Docks": {"randomize_cartel_influence&!randomize_customers" : {"has_all_counts": {"Cartel Influence, Downtown": 7}},
|
||||
"randomize_level_unlocks" : {"has": "Fertilizer Unlock"}},
|
||||
"Vibin' on the 'Cybin": {"randomize_level_unlocks" : {"has" : "Warehouse Access"},
|
||||
"randomize_suppliers" : {"has": "Fungal Phil Unlocked"},
|
||||
"randomize_customers&!randomize_suppliers" : {"has_any" : [["Elizabeth Homley Unlocked", "Kevin Oakley Unlocked"]]}}}
|
||||
},
|
||||
"Docks": {
|
||||
"connections": {"Suburbia": {"randomize_cartel_influence&!randomize_customers" : {"has_all_counts": {"Cartel Influence, Docks": 7}}},
|
||||
"Cocaine Recipe Checks": {"randomize_level_unlocks" : {"has_any": [["Mixing Station Mk II Unlock", "Mixing Station Unlock"]],
|
||||
"has_all" : ["Cauldron Unlock", "Lab Oven Unlock", "Gasoline Unlock", "Warehouse Access"]},
|
||||
"randomize_suppliers" : {"has": "Salvador Moreno Unlocked"},
|
||||
"randomize_customers&!randomize_suppliers" : {"has_any" : [["Mac Cooper Unlocked", "Javier Pérez Unlocked"]]}}}
|
||||
},
|
||||
"Suburbia": {
|
||||
"connections": {"Uptown": {"randomize_cartel_influence&!randomize_customers" : {"has_all_counts": {"Cartel Influence, Suburbia": 7}},
|
||||
"randomize_level_unlocks" : {"has": "Drying Rack Unlock"}}}
|
||||
},
|
||||
"Uptown": {
|
||||
"connections": {}
|
||||
},
|
||||
"Weed Recipe Checks": {
|
||||
"connections": {}
|
||||
},
|
||||
"Meth Recipe Checks": {
|
||||
"connections": {}
|
||||
},
|
||||
"Shrooms Recipe Checks": {
|
||||
"connections": {}
|
||||
},
|
||||
"Cocaine Recipe Checks": {
|
||||
"connections": {}
|
||||
},
|
||||
"Welcome to Hyland Point": {
|
||||
"connections": {
|
||||
"Getting Started": true
|
||||
}
|
||||
},
|
||||
"Getting Started": {
|
||||
"connections": {
|
||||
"Money Management": true
|
||||
}
|
||||
},
|
||||
"Money Management": {
|
||||
"connections": {
|
||||
"Gearing Up|1": true,
|
||||
"Clean Cash": true
|
||||
}
|
||||
},
|
||||
"Clean Cash": {
|
||||
"connections": {
|
||||
|
||||
}
|
||||
},
|
||||
"Gearing Up|1": {
|
||||
"connections": {
|
||||
"Gearing Up|2": true,
|
||||
"Packin'": true,
|
||||
"Keeping it Fresh": true
|
||||
}
|
||||
},
|
||||
"Keeping it Fresh": {
|
||||
"connections": {
|
||||
|
||||
}
|
||||
},
|
||||
"Packin'": {
|
||||
"connections": {
|
||||
|
||||
}
|
||||
},
|
||||
"Gearing Up|2": {
|
||||
"connections": {
|
||||
"On the Grind|1": true
|
||||
}
|
||||
},
|
||||
"On the Grind|1": {
|
||||
"connections": {
|
||||
"Moving Up": true,
|
||||
"On the Grind|2": true
|
||||
}
|
||||
},
|
||||
"On the Grind|2": {
|
||||
"connections": {
|
||||
|
||||
}
|
||||
},
|
||||
"Moving Up": {
|
||||
"connections": {
|
||||
"Dodgy Dealing": {"randomize_customers": {"has_from_list":
|
||||
{"Austin Steiner Unlocked": 10,
|
||||
"Beth Penn Unlocked": 10,
|
||||
"Chloe Bowers Unlocked": 10,
|
||||
"Donna Martin Unlocked": 10,
|
||||
"Geraldine Poon Unlocked": 10,
|
||||
"Jessi Waters Unlocked": 10,
|
||||
"Kathy Henderson Unlocked": 10,
|
||||
"Kyle Cooley Unlocked": 10,
|
||||
"Ludwig Meyer Unlocked": 10,
|
||||
"Mick Lubbin Unlocked": 10,
|
||||
"Mrs. Ming Unlocked": 10,
|
||||
"Peggy Myers Unlocked": 10,
|
||||
"Peter File Unlocked": 10,
|
||||
"Sam Thompson Unlocked": 10,
|
||||
"Charles Rowland Unlocked": 10,
|
||||
"Dean Webster Unlocked": 10,
|
||||
"Doris Lubbin Unlocked": 10,
|
||||
"George Greene Unlocked": 10,
|
||||
"Jerry Montero Unlocked": 10,
|
||||
"Joyce Ball Unlocked": 10,
|
||||
"Keith Wagner Unlocked": 10,
|
||||
"Kim Delaney Unlocked": 10,
|
||||
"Meg Cooley Unlocked": 10,
|
||||
"Trent Sherman Unlocked": 10,
|
||||
"Bruce Norton Unlocked": 10,
|
||||
"Elizabeth Homley Unlocked": 10,
|
||||
"Eugene Buckley Unlocked": 10,
|
||||
"Greg Figgle Unlocked": 10,
|
||||
"Jeff Gilmore Unlocked": 10,
|
||||
"Jennifer Rivera Unlocked": 10,
|
||||
"Kevin Oakley Unlocked": 10,
|
||||
"Louis Fourier Unlocked": 10,
|
||||
"Philip Wentworth Unlocked": 10,
|
||||
"Randy Caulfield Unlocked": 10,
|
||||
"Lucy Pennington Unlocked": 10,
|
||||
"Anna Chesterfield Unlocked": 10,
|
||||
"Billy Kramer Unlocked": 10,
|
||||
"Cranky Frank Unlocked": 10,
|
||||
"Genghis Barn Unlocked": 10,
|
||||
"Javier Pérez Unlocked": 10,
|
||||
"Kelly Reynolds Unlocked": 10,
|
||||
"Lisa Gardener Unlocked": 10,
|
||||
"Mac Cooper Unlocked": 10,
|
||||
"Marco Barone Unlocked": 10,
|
||||
"Melissa Wood Unlocked": 10,
|
||||
"Sherman Giles Unlocked": 10,
|
||||
"Alison Knight Unlocked": 10,
|
||||
"Carl Bundy Unlocked": 10,
|
||||
"Chris Sullivan Unlocked": 10,
|
||||
"Dennis Kennedy Unlocked": 10,
|
||||
"Hank Stevenson Unlocked": 10,
|
||||
"Harold Colt Unlocked": 10,
|
||||
"Jack Knight Unlocked": 10,
|
||||
"Jackie Stevenson Unlocked": 10,
|
||||
"Jeremy Wilkinson Unlocked": 10,
|
||||
"Karen Kennedy Unlocked": 10,
|
||||
"Fiona Hancock Unlocked": 10,
|
||||
"Herbert Bleuball Unlocked": 10,
|
||||
"Irene Meadows Unlocked": 10,
|
||||
"Jen Heard Unlocked": 10,
|
||||
"Lily Turner Unlocked": 10,
|
||||
"Michael Boog Unlocked": 10,
|
||||
"Pearl Moore Unlocked": 10,
|
||||
"Ray Hoffman Unlocked": 10,
|
||||
"Tobias Wentworth Unlocked": 10,
|
||||
"Walter Cussler Unlocked": 10}}}
|
||||
}
|
||||
},
|
||||
"Dodgy Dealing": {
|
||||
"connections": {
|
||||
"Mixing Mania": {"randomize_customers": {"has_any" : [["Chloe Bowers Unlocked", "Ludwig Meyer Unlocked", "Beth Penn Unlocked"]]}}
|
||||
}
|
||||
},
|
||||
"Mixing Mania": {
|
||||
"connections": {
|
||||
"Making the Rounds": {"randomize_level_unlocks" : {"has": "Mixing Station Unlock"}}
|
||||
}
|
||||
},
|
||||
"Making the Rounds": {
|
||||
"connections": {
|
||||
"Needin' the Green": true
|
||||
}
|
||||
},
|
||||
"Needin' the Green": {
|
||||
"connections": {
|
||||
"Wretched Hive of Scum and Villainy": true
|
||||
}
|
||||
},
|
||||
"Vibin' on the 'Cybin": {
|
||||
"connections": {
|
||||
"Shrooms Recipe Checks": {"randomize_level_unlocks": {"has_any": [["Mixing Station Mk II Unlock", "Mixing Station Unlock"]]}}
|
||||
}
|
||||
},
|
||||
"Wretched Hive of Scum and Villainy": {
|
||||
"connections": {
|
||||
"We Need To Cook|1": {"randomize_level_unlocks": {"has": "Warehouse Access"}}
|
||||
}
|
||||
},
|
||||
"We Need To Cook|1": {
|
||||
"connections": {
|
||||
"We Need To Cook|2": {"randomize_customers": {"has_any": [["Meg Cooley Unlocked", "Jerry Montero Unlocked"]]},
|
||||
"randomize_suppliers": {"has": "Shirley Watts Unlocked"}}
|
||||
}
|
||||
},
|
||||
"We Need To Cook|2": {
|
||||
"connections": {
|
||||
"Unfavourable Agreements": {"randomize_level_unlocks": {"has_all" :["Chemistry Station Unlock",
|
||||
"Lab Oven Unlock",
|
||||
"Acid Unlock",
|
||||
"Phosphorus Unlock"]},
|
||||
"randomize_customers": {"has_from_list": {
|
||||
"Charles Rowland Unlocked": 5,
|
||||
"Dean Webster Unlocked": 5,
|
||||
"Doris Lubbin Unlocked": 5,
|
||||
"George Greene Unlocked": 5,
|
||||
"Jerry Montero Unlocked": 5,
|
||||
"Joyce Ball Unlocked": 5,
|
||||
"Kim Delaney Unlocked": 5,
|
||||
"Meg Cooley Unlocked": 5,
|
||||
"Trent Sherman Unlocked": 5,
|
||||
"Keith Wagner Unlocked": 5}}}
|
||||
}
|
||||
},
|
||||
"Unfavourable Agreements": {
|
||||
"connections": {
|
||||
"Finishing the Job": true,
|
||||
"Cartel Influence": true
|
||||
}
|
||||
},
|
||||
"Cartel Influence": {
|
||||
"connections": {}
|
||||
},
|
||||
"Finishing the Job": {
|
||||
"connections": {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"randomize_customers" : {"has_from_list" : [{"Charles Rowland Unlocked": 5,
|
||||
"Dean Webster Unlocked": 5,
|
||||
"Doris Lubbin Unlocked": 5,
|
||||
"George Greene Unlocked": 5,
|
||||
"Jerry Montero Unlocked": 5,
|
||||
"Joyce Ball Unlocked": 5,
|
||||
"Kim Delaney Unlocked": 5,
|
||||
"Meg Cooley Unlocked": 5,
|
||||
"Trent Sherman Unlocked": 5,
|
||||
"Keith Wagner Unlocked": 5},
|
||||
{"Austin Steiner Unlocked": 10,
|
||||
"Beth Penn Unlocked": 10,
|
||||
"Chloe Bowers Unlocked": 10,
|
||||
"Donna Martin Unlocked": 10,
|
||||
"Geraldine Poon Unlocked": 10,
|
||||
"Jessi Waters Unlocked": 10,
|
||||
"Kathy Henderson Unlocked": 10,
|
||||
"Kyle Cooley Unlocked": 10,
|
||||
"Ludwig Meyer Unlocked": 10,
|
||||
"Mick Lubbin Unlocked": 10,
|
||||
"Mrs. Ming Unlocked": 10,
|
||||
"Peggy Myers Unlocked": 10,
|
||||
"Peter File Unlocked": 10,
|
||||
"Sam Thompson Unlocked": 10,
|
||||
"Charles Rowland Unlocked": 10,
|
||||
"Dean Webster Unlocked": 10,
|
||||
"Doris Lubbin Unlocked": 10,
|
||||
"George Greene Unlocked": 10,
|
||||
"Jerry Montero Unlocked": 10,
|
||||
"Joyce Ball Unlocked": 10,
|
||||
"Keith Wagner Unlocked": 10,
|
||||
"Kim Delaney Unlocked": 10,
|
||||
"Meg Cooley Unlocked": 10,
|
||||
"Trent Sherman Unlocked": 10,
|
||||
"Bruce Norton Unlocked": 10,
|
||||
"Elizabeth Homley Unlocked": 10,
|
||||
"Eugene Buckley Unlocked": 10,
|
||||
"Greg Figgle Unlocked": 10,
|
||||
"Jeff Gilmore Unlocked": 10,
|
||||
"Jennifer Rivera Unlocked": 10,
|
||||
"Kevin Oakley Unlocked": 10,
|
||||
"Louis Fourier Unlocked": 10,
|
||||
"Philip Wentworth Unlocked": 10,
|
||||
"Randy Caulfield Unlocked": 10,
|
||||
"Lucy Pennington Unlocked": 10,
|
||||
"Anna Chesterfield Unlocked": 10,
|
||||
"Billy Kramer Unlocked": 10,
|
||||
"Cranky Frank Unlocked": 10,
|
||||
"Genghis Barn Unlocked": 10,
|
||||
"Javier Pérez Unlocked": 10,
|
||||
"Kelly Reynolds Unlocked": 10,
|
||||
"Lisa Gardener Unlocked": 10,
|
||||
"Mac Cooper Unlocked": 10,
|
||||
"Marco Barone Unlocked": 10,
|
||||
"Melissa Wood Unlocked": 10,
|
||||
"Sherman Giles Unlocked": 10,
|
||||
"Alison Knight Unlocked": 10,
|
||||
"Carl Bundy Unlocked": 10,
|
||||
"Chris Sullivan Unlocked": 10,
|
||||
"Dennis Kennedy Unlocked": 10,
|
||||
"Hank Stevenson Unlocked": 10,
|
||||
"Harold Colt Unlocked": 10,
|
||||
"Jack Knight Unlocked": 10,
|
||||
"Jackie Stevenson Unlocked": 10,
|
||||
"Jeremy Wilkinson Unlocked": 10,
|
||||
"Karen Kennedy Unlocked": 10,
|
||||
"Fiona Hancock Unlocked": 10,
|
||||
"Herbert Bleuball Unlocked": 10,
|
||||
"Irene Meadows Unlocked": 10,
|
||||
"Jen Heard Unlocked": 10,
|
||||
"Lily Turner Unlocked": 10,
|
||||
"Michael Boog Unlocked": 10,
|
||||
"Pearl Moore Unlocked": 10,
|
||||
"Ray Hoffman Unlocked": 10,
|
||||
"Tobias Wentworth Unlocked": 10,
|
||||
"Walter Cussler Unlocked": 10}],
|
||||
"has_any" : [["Chloe Bowers Unlocked", "Ludwig Meyer Unlocked", "Beth Penn Unlocked"],
|
||||
["Meg Cooley Unlocked", "Jerry Montero Unlocked"],
|
||||
["Mac Cooper Unlocked", "Javier Pérez Unlocked"]],
|
||||
"has_all" : ["Billy Kramer Unlocked", "Sam Thompson Unlocked"]},
|
||||
"randomize_level_unlocks" : {"has_any" : [["Mixing Station Mk II Unlock", "Mixing Station Unlock"]],
|
||||
"has_all" : [
|
||||
"Cauldron Unlock",
|
||||
"Gasoline Unlock",
|
||||
"Warehouse Access",
|
||||
"Chemistry Station Unlock",
|
||||
"Lab Oven Unlock",
|
||||
"Acid Unlock",
|
||||
"Phosphorus Unlock"]},
|
||||
"randomize_suppliers" : {"has_all": ["Salvador Moreno Unlocked", "Shirley Watts Unlocked"]},
|
||||
"randomize_cartel_influence" : {"has_all_counts": {"Cartel Influence, Suburbia" : 7}}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Schedule I
|
||||
|
||||
## Where is the options page?
|
||||
|
||||
The [player options page for this game](../player-options) contains all the options you need to configure and export a
|
||||
config file.
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
- Sends checks from all missions.
|
||||
- Sends and receives all customer checks and items.
|
||||
- Customers can be unlocked by receiving them through archipelago when randomize_customers is true in the YAML.
|
||||
- checks for samples will be sent no matter the settings and are functional.
|
||||
- Dealers will send checks when recruiting regardless of settings.
|
||||
- Dealer AP unlock will allow user to then recruit them in game. Check is still possible when having them as a possible contact.
|
||||
- Suppliers will not be unlockable if suppliers are randomized and only unlocked through ap items
|
||||
- Suppliers give checks for unlocking them when suppliers are not randomized
|
||||
- Every Action that would cause cartel influence in a region to drop is a check (x7 per region)
|
||||
- Unable to reduce cartel influence naturally and cartel influence items added to pool when randomize_cartel_influence is true
|
||||
- Level up rewards are suppressed when randomize_level_up_rewards is true
|
||||
- Level up rewards are added to the item pool when randomize_level_up_rewards is true
|
||||
- Whenever you'd nomrally get unlocks for leveling up, you get a check regardless of the option
|
||||
- Deathlink is sent when a player dies or when they arrested. Recieved deathlink causes player to get arrested
|
||||
- Property and busniesses give checks when purchased
|
||||
- Randomized properties or businesses will not be unlocked when purchased if randomization on, properties and/or businesses will be added to the item pool
|
||||
- Recipe checks are sent when recipes are learned
|
||||
- Cash for trash are sent every 10 trash burned
|
||||
- Filler items will be sent as deaddrop quests
|
||||
|
||||
## Once I'm inside Schedule1, how do I play Schedule1AP
|
||||
|
||||
Use In-Game UI to connect to server. Once connected, Create a new world and skip the prologue.
|
||||
Make sure to save as often as you can, and you are able to rejoin. Restart your game if you need to rejoin the world!
|
||||
If you want to play with friends (Untested): Invite them to your lobby. All of you connect as same archipelago Info, Load into world.
|
||||
|
||||
## A statement on the ownership over Schedule1AP
|
||||
|
||||
Schedule I apworld is MIT license. Created by MacH8s
|
||||
@@ -0,0 +1,33 @@
|
||||
# Schedule 1 Archipelago Randomizer Setup Guide
|
||||
|
||||
## Required Software
|
||||
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
|
||||
- [The Schedule I apworld](https://github.com/MacH8s/Narcopelago/releases/latest),
|
||||
- [Thunderstore Mod Manager](https://www.overwolf.com/app/thunderstore-thunderstore_mod_manager)
|
||||
- [Narcopelago Mod](https://thunderstore.io/c/schedule-i/p/Narcopelago/Narcopelago/)
|
||||
|
||||
## How to play
|
||||
|
||||
First, you need a room to connect to. For this, you or someone you know has to generate a game.
|
||||
This will not be explained here,
|
||||
but you can check the [Archipelago Setup Guide](/tutorial/Archipelago/setup_en#generating-a-game).
|
||||
|
||||
You also need to have [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest) installed
|
||||
and the [The Schedule I apworld](https://github.com/MacH8s/Narcopelago/releases/latest) installed into Archipelago.
|
||||
|
||||
### Install Mod
|
||||
|
||||
Install Thunderstore Mod Manager and open it.
|
||||
Choose Schedule I and make a profile for Archipelago, name it whatever you like.
|
||||
Search for 'Narcopelago' in the mod search and install it.
|
||||
From there you can launch the game as Modded on the top right and your install has been complete! You must launch the game this way every time you want to play Archipelago.
|
||||
|
||||
### Joining Game
|
||||
|
||||
Use In-Game UI to connect to server. Once connected, Create a new world and skip the prologue.
|
||||
Make sure to save as often as you can, and you are able to rejoin. Restart your game if you need to rejoin the world!
|
||||
If you want to play with friends (Untested): Invite them to your lobby. All of you connect as same archipelago Info, Load into world.
|
||||
|
||||
## Switching Rooms
|
||||
Restart your game to switch rooms. There may be some issues if you don't do so even if it shows things are working.
|
||||
@@ -0,0 +1,265 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import Schedule1World
|
||||
|
||||
ITEM_NAME_TO_ID = {}
|
||||
RAW_ITEM_CLASSIFICATIONS = {}
|
||||
|
||||
fillers = []
|
||||
traps = []
|
||||
|
||||
# Mapping from JSON classification strings to ItemClassification flags
|
||||
CLASSIFICATION_MAP = {
|
||||
"USEFUL": ItemClassification.useful,
|
||||
"PROGRESSION": ItemClassification.progression,
|
||||
"FILLER": ItemClassification.filler,
|
||||
"PROGRESSION_SKIP_BALANCING": ItemClassification.progression_skip_balancing,
|
||||
"TRAP": ItemClassification.trap
|
||||
}
|
||||
|
||||
def load_items_data(data):
|
||||
"""Load item data from JSON and populate ITEM_NAME_TO_ID and RAW_ITEM_CLASSIFICATIONS."""
|
||||
global ITEM_NAME_TO_ID, RAW_ITEM_CLASSIFICATIONS
|
||||
|
||||
ITEM_NAME_TO_ID = {item.name: item.modern_id for item in data.items.values()}
|
||||
RAW_ITEM_CLASSIFICATIONS = {item.name: item.classification for item in data.items.values()}
|
||||
|
||||
|
||||
# Each Item instance must correctly report the "game" it belongs to.
|
||||
# To make this simple, it is common practice to subclass the basic Item class and override the "game" field.
|
||||
class Schedule1Item(Item):
|
||||
game = "Schedule I"
|
||||
|
||||
# To do this, it must define a function called world.get_filler_item_name(), which we will define in world.py later.
|
||||
# For now, let's make a function that returns the name of a random filler item here in items.py.
|
||||
def get_random_filler_item_name(world: Schedule1World) -> str:
|
||||
# For this purpose, we need to use a random generator.
|
||||
|
||||
# IMPORTANT: Whenever you need to use a random generator, you must use world.random.
|
||||
# This ensures that generating with the same generator seed twice yields the same output.
|
||||
# DO NOT use a bare random object from Python's built-in random module.
|
||||
|
||||
# Check if we should generate a trap item based on the trap_chance option.
|
||||
if world.random.randint(0, 99) < world.options.trap_chance:
|
||||
return world.random.choice(traps)
|
||||
|
||||
# Otherwise, return a random filler item.
|
||||
return world.random.choice(fillers)
|
||||
|
||||
|
||||
def check_option_enabled(world: Schedule1World, option_name: str) -> bool:
|
||||
"""Check if an option is enabled based on option name string."""
|
||||
option_map = {
|
||||
"randomize_customers": world.options.randomize_customers,
|
||||
"randomize_dealers": world.options.randomize_dealers,
|
||||
"randomize_suppliers": world.options.randomize_suppliers,
|
||||
"randomize_level_unlocks": world.options.randomize_level_unlocks,
|
||||
"randomize_cartel_influence": world.options.randomize_cartel_influence,
|
||||
"randomize_business_properties": world.options.randomize_business_properties,
|
||||
"randomize_drug_making_properties": world.options.randomize_drug_making_properties,
|
||||
}
|
||||
return bool(option_map.get(option_name, False))
|
||||
|
||||
|
||||
def check_option_condition(world: Schedule1World, condition_key: str) -> bool:
|
||||
"""
|
||||
Parse and evaluate a compound option condition string.
|
||||
|
||||
Supports:
|
||||
- Simple: "randomize_level_unlocks" (option must be true)
|
||||
- Negation: "!randomize_level_unlocks" (option must be false)
|
||||
- Compound AND: "randomize_level_unlocks&!randomize_customers"
|
||||
(first must be true AND second must be false)
|
||||
|
||||
Returns True if the condition is satisfied, False otherwise.
|
||||
"""
|
||||
parts = condition_key.split('&')
|
||||
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
if part.startswith('!'):
|
||||
option_name = part[1:]
|
||||
expected_value = False
|
||||
else:
|
||||
option_name = part
|
||||
expected_value = True
|
||||
|
||||
actual_value = check_option_enabled(world, option_name)
|
||||
|
||||
if actual_value != expected_value:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def resolve_classification(world: Schedule1World, classification_data) -> ItemClassification:
|
||||
"""
|
||||
Resolve the classification from raw JSON data based on world options.
|
||||
|
||||
classification_data can be:
|
||||
- A string: "PROGRESSION"
|
||||
- A list: ["PROGRESSION", "USEFUL"]
|
||||
- A dict with conditions: {"!randomize_customers": ["PROGRESSION", "USEFUL"], "default": ["USEFUL"]}
|
||||
"""
|
||||
# Determine which classification strings to use
|
||||
if isinstance(classification_data, dict):
|
||||
# Conditional classification - find matching condition
|
||||
classification_strings = None
|
||||
for condition_key, value in classification_data.items():
|
||||
if condition_key == "default":
|
||||
continue # Handle default last
|
||||
if check_option_condition(world, condition_key):
|
||||
classification_strings = value
|
||||
break
|
||||
|
||||
# Fall back to default if no condition matched
|
||||
if classification_strings is None:
|
||||
classification_strings = classification_data.get("default", "FILLER")
|
||||
else:
|
||||
classification_strings = classification_data
|
||||
|
||||
# Convert to ItemClassification
|
||||
if isinstance(classification_strings, list):
|
||||
classification = CLASSIFICATION_MAP[classification_strings[0]]
|
||||
for class_name in classification_strings[1:]:
|
||||
classification |= CLASSIFICATION_MAP[class_name]
|
||||
else:
|
||||
classification = CLASSIFICATION_MAP[classification_strings]
|
||||
|
||||
return classification
|
||||
|
||||
|
||||
def create_item_with_correct_classification(world: Schedule1World, name: str) -> Schedule1Item:
|
||||
# Our world class must have a create_item() function that can create any of our items by name at any time.
|
||||
# So, we make this helper function that creates the item by name with the correct classification.
|
||||
# Note: This function's content could just be the contents of world.create_item in world.py directly,
|
||||
# but it seemed nicer to have it in its own function over here in items.py.
|
||||
classification = resolve_classification(world, RAW_ITEM_CLASSIFICATIONS[name])
|
||||
|
||||
return Schedule1Item(name, classification, ITEM_NAME_TO_ID[name], world.player)
|
||||
|
||||
|
||||
# With those two helper functions defined, let's now get to actually creating and submitting our itempool.
|
||||
def create_all_items(world: Schedule1World, data) -> None:
|
||||
# Creating items should generally be done via the world's create_item method.
|
||||
# First, we create a list containing all the items that always exist.
|
||||
|
||||
itempool: list[Item] = []
|
||||
|
||||
# Create bundles bundles
|
||||
# Hard coding the bundles here based on options is more efficient than adding them through the json data
|
||||
for _ in range(world.options.number_of_cash_bundles):
|
||||
itempool += [world.create_item("Cash Bundle")]
|
||||
|
||||
for _ in range(world.options.number_of_xp_bundles):
|
||||
itempool += [world.create_item("XP Bundle")]
|
||||
|
||||
if world.options.randomize_level_unlocks:
|
||||
# If the randomize_level_unlocks option is enabled, create all items tagged as "Level Up Reward".
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Level Up Reward" in item.tags]
|
||||
|
||||
# Add cartel influence items based on options
|
||||
if world.options.randomize_cartel_influence:
|
||||
if not world.options.randomize_customers:
|
||||
for _ in range(world.options.cartel_influence_items_per_region):
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Cartel Influence" in item.tags and "Westville" not in item.tags
|
||||
and "Suburbia" not in item.tags]
|
||||
|
||||
# Suburbia is required for Finishing the Job
|
||||
for _ in range(world.options.cartel_influence_items_per_region):
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Cartel Influence" in item.tags and "Suburbia" in item.tags]
|
||||
|
||||
# Westville starts at 500 less cartel influence. Will have 5 less cartel items as well to declutter
|
||||
# Westville is required for Vibin the Cybin
|
||||
for _ in range(world.options.cartel_influence_items_per_region - 5):
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Cartel Influence" in item.tags and "Westville" in item.tags]
|
||||
|
||||
if world.options.randomize_business_properties:
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Business Property" in item.tags]
|
||||
|
||||
if world.options.randomize_drug_making_properties:
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Drug Making Property" in item.tags]
|
||||
|
||||
if world.options.randomize_dealers:
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Dealer" in item.tags and "Default" not in item.tags]
|
||||
|
||||
if world.options.randomize_customers:
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Customer" in item.tags and "Default" not in item.tags]
|
||||
|
||||
if world.options.randomize_suppliers:
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Supplier" in item.tags and "Default" not in item.tags]
|
||||
|
||||
if world.options.randomize_sewer_key:
|
||||
itempool += [world.create_item(item.name) for item in data.items.values()
|
||||
if "Sewer" in item.tags]
|
||||
|
||||
# Removed these from checks
|
||||
if world.options.randomize_customers:
|
||||
starting_kyle_cooley = world.create_item("Kyle Cooley Unlocked")
|
||||
world.push_precollected(starting_kyle_cooley)
|
||||
starting_austin_steiner = world.create_item("Austin Steiner Unlocked")
|
||||
world.push_precollected(starting_austin_steiner)
|
||||
starting_kathy_henderson = world.create_item("Kathy Henderson Unlocked")
|
||||
world.push_precollected(starting_kathy_henderson)
|
||||
starting_jessi_waters = world.create_item("Jessi Waters Unlocked")
|
||||
world.push_precollected(starting_jessi_waters)
|
||||
starting_sam_thompson = world.create_item("Sam Thompson Unlocked")
|
||||
world.push_precollected(starting_sam_thompson)
|
||||
starting_mick_lubbin = world.create_item("Mick Lubbin Unlocked")
|
||||
world.push_precollected(starting_mick_lubbin)
|
||||
|
||||
# Set up traps
|
||||
for item in data.items.values():
|
||||
resolved_classification = resolve_classification(world, item.classification)
|
||||
if resolved_classification == ItemClassification.trap:
|
||||
# Create list of traps
|
||||
traps.append(item.name)
|
||||
|
||||
filler_conditions = {
|
||||
"Bad Filler" : world.options.ban_bad_filler_items,
|
||||
"Ban Progression Skip" : world.options.ban_progression_skip_items}
|
||||
|
||||
# set up fillers
|
||||
for item in data.items.values():
|
||||
resolved_classification = resolve_classification(world, item.classification)
|
||||
if resolved_classification == ItemClassification.filler:
|
||||
is_valid = True
|
||||
for tag, should_ban in filler_conditions.items():
|
||||
if should_ban and tag in item.tags:
|
||||
is_valid = False
|
||||
break
|
||||
if is_valid:
|
||||
fillers.append(item.name)
|
||||
|
||||
# The length of our itempool is easy to determine, since we have it as a list.
|
||||
number_of_items = len(itempool)
|
||||
|
||||
# What we actually want is the number of *unfilled* locations. Luckily, there is a helper method for this:
|
||||
number_of_unfilled_locations = len(world.multiworld.get_unfilled_locations(world.player))
|
||||
|
||||
# Now, we just subtract the number of items from the number of locations to get the number of empty item slots.
|
||||
needed_number_of_filler_items = number_of_unfilled_locations - number_of_items
|
||||
|
||||
# Finally, we create that many filler items and add them to the itempool.
|
||||
itempool += [world.create_filler() for _ in range(needed_number_of_filler_items)]
|
||||
|
||||
# This is how the generator actually knows about the existence of our items.
|
||||
world.multiworld.itempool += itempool
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Centralized data loader for Schedule1 world.
|
||||
Loads and parses items.json, locations.json, and regions.json into structured objects for easy access.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
import pkgutil
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
import orjson
|
||||
|
||||
def load_json_data(data_name: str) -> Union[List[Any], Dict[str, Any]]:
|
||||
return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode("utf-8-sig"))
|
||||
|
||||
@dataclass
|
||||
class ItemData:
|
||||
"""Represents item data from items.json"""
|
||||
name: str
|
||||
modern_id: int
|
||||
classification: Union[str, List[str], Dict[str, Union[str, List[str]]]]
|
||||
tags: List[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocationData:
|
||||
"""Represents location data from locations.json"""
|
||||
name: str
|
||||
region: str
|
||||
requirements: Union[bool, Dict[str, Any]]
|
||||
tags: List[str]
|
||||
modern_id: int
|
||||
|
||||
@dataclass
|
||||
class RegionData:
|
||||
"""Represents region data from regions.json"""
|
||||
name: str
|
||||
connections: Dict[str, Union[bool, Dict[str, Any]]]
|
||||
|
||||
class Schedule1ItemData:
|
||||
"""Container for all Schedule1 game data loaded from JSON files"""
|
||||
|
||||
def __init__(self):
|
||||
items_raw = load_json_data("items.json")
|
||||
|
||||
# Parse items into ItemData objects
|
||||
# Classification is stored raw - resolution happens in items.py based on world options
|
||||
self.items: Dict[str, ItemData] = {}
|
||||
for item_name, item_info in items_raw.items():
|
||||
self.items[item_name] = ItemData(
|
||||
name=item_name,
|
||||
modern_id=item_info["modern_id"],
|
||||
classification=item_info["classification"],
|
||||
tags=item_info["tags"]
|
||||
)
|
||||
|
||||
|
||||
class Schedule1LocationData:
|
||||
"""Container for all Schedule1 location data loaded from JSON files"""
|
||||
|
||||
def __init__(self):
|
||||
locations_raw = load_json_data("locations.json")
|
||||
|
||||
# Parse locations into LocationData objects
|
||||
self.locations: Dict[str, LocationData] = {}
|
||||
for location_name, location_info in locations_raw.items():
|
||||
self.locations[location_name] = LocationData(
|
||||
name=location_name,
|
||||
region=location_info["region"],
|
||||
requirements=location_info["requirements"],
|
||||
tags=location_info["tags"],
|
||||
modern_id=location_info["modern_id"]
|
||||
)
|
||||
|
||||
class Schedule1RegionData:
|
||||
"""Container for all Schedule1 region data loaded from JSON files"""
|
||||
|
||||
def __init__(self):
|
||||
regions_raw = load_json_data("regions.json")
|
||||
|
||||
# Parse regions into RegionData objects
|
||||
self.regions: Dict[str, RegionData] = {}
|
||||
for region_name, region_info in regions_raw.items():
|
||||
self.regions[region_name] = RegionData(
|
||||
name=region_name,
|
||||
connections=region_info["connections"]
|
||||
)
|
||||
|
||||
|
||||
class Schedule1VictoryData:
|
||||
"""Container for victory conditions loaded from victory.json"""
|
||||
|
||||
def __init__(self):
|
||||
# victory.json is structured as {option_name: {method: value, ...}, ...}
|
||||
# This is the same structure as requirements in locations/regions
|
||||
self.requirements: Dict[str, Any] = load_json_data("victory.json")
|
||||
|
||||
|
||||
# Create singleton instances for easy import
|
||||
schedule1_item_data = Schedule1ItemData()
|
||||
schedule1_location_data = Schedule1LocationData()
|
||||
schedule1_region_data = Schedule1RegionData()
|
||||
schedule1_victory_data = Schedule1VictoryData()
|
||||
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Dict
|
||||
|
||||
from BaseClasses import ItemClassification, Location
|
||||
|
||||
from . import items
|
||||
|
||||
from math import ceil
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import Schedule1World
|
||||
|
||||
# Every location must have a unique integer ID associated with it.
|
||||
LOCATION_NAME_TO_ID = {}
|
||||
def load_locations_data(data):
|
||||
"""Load location data from JSON and populate LOCATION_NAME_TO_ID."""
|
||||
global LOCATION_NAME_TO_ID
|
||||
LOCATION_NAME_TO_ID = {name: loc.modern_id for name, loc in data.locations.items()}
|
||||
# must include all locations, even those not created based on options
|
||||
drug_types = [
|
||||
("Weed", 195),
|
||||
("Meth", 210),
|
||||
("Shrooms", 225),
|
||||
("Cocaine", 240),
|
||||
]
|
||||
for drug_name, start_id in drug_types:
|
||||
for i in range(1, 16):
|
||||
LOCATION_NAME_TO_ID[f"{drug_name} Recipe Check, {i}"] = start_id + (i - 1)
|
||||
|
||||
start_id = 600 # Starting ID for Cash for Trash locations
|
||||
for i in range(1, 51):
|
||||
LOCATION_NAME_TO_ID[f"Cash for Trash {i}, Collect {i * 10} pieces of trash"] = start_id + (i - 1)
|
||||
# Each Location instance must correctly report the "game" it belongs to.
|
||||
# To make this simple, it is common practice to subclass the basic Location class and override the "game" field.
|
||||
class Schedule1Location(Location):
|
||||
game = "Schedule I"
|
||||
|
||||
|
||||
# Let's make one more helper method before we begin actually creating locations.
|
||||
# Later on in the code, we'll want specific subsections of LOCATION_NAME_TO_ID.
|
||||
# To reduce the chance of copy-paste errors writing something like {"Chest": LOCATION_NAME_TO_ID["Chest"]},
|
||||
# let's make a helper method that takes a list of location names and returns them as a dict with their IDs.
|
||||
# Note: There is a minor typing quirk here. Some functions want location addresses to be an "int | None",
|
||||
# so while our function here only ever returns dict[str, int], we annotate it as dict[str, int | None].
|
||||
def get_location_names_with_ids(location_names: list[str]) -> dict[str, int | None]:
|
||||
return {location_name: LOCATION_NAME_TO_ID[location_name] for location_name in location_names}
|
||||
|
||||
|
||||
def create_all_locations(world: Schedule1World, locationData) -> None:
|
||||
create_regular_locations(world, locationData)
|
||||
|
||||
|
||||
def create_regular_locations(world: Schedule1World, data) -> None:
|
||||
# Get all unique region names from location data
|
||||
region_names = set(loc.region for loc in data.locations.values())
|
||||
|
||||
# Load all regions into a dictionary once for efficient access
|
||||
regions_dict: Dict[str, any] = {}
|
||||
for region_name in sorted(region_names):
|
||||
regions_dict[region_name] = world.get_region(region_name)
|
||||
|
||||
# Group locations by region, excluding suppliers if randomized
|
||||
locations_by_region: Dict[str, list[str]] = {region: [] for region in sorted(region_names)}
|
||||
|
||||
for loc_name, loc_data in data.locations.items():
|
||||
# Skip supplier locations if randomize_suppliers is enabled
|
||||
if world.options.randomize_suppliers and "Supplier" in loc_data.tags:
|
||||
continue
|
||||
locations_by_region[loc_data.region].append(loc_name)
|
||||
|
||||
# Add all locations to their respective regions
|
||||
for region_name, location_names in locations_by_region.items():
|
||||
if location_names: # Only add if there are locations
|
||||
region = regions_dict[region_name]
|
||||
region.add_locations(get_location_names_with_ids(location_names), Schedule1Location)
|
||||
|
||||
# Recipe checks - Only include the number specified by the RecipeChecks option
|
||||
if world.options.recipe_checks > 0:
|
||||
# Get each recipe region
|
||||
weed_recipe_region = world.get_region("Weed Recipe Checks")
|
||||
meth_recipe_region = world.get_region("Meth Recipe Checks")
|
||||
shrooms_recipe_region = world.get_region("Shrooms Recipe Checks")
|
||||
cocaine_recipe_region = world.get_region("Cocaine Recipe Checks")
|
||||
|
||||
# Drug types with their regions and starting IDs in LOCATION_NAME_TO_ID
|
||||
# These magic numbers correspond to reserved IDs for recipe checks
|
||||
drug_types = [
|
||||
("Weed", weed_recipe_region),
|
||||
("Meth", meth_recipe_region),
|
||||
("Shrooms", shrooms_recipe_region),
|
||||
("Cocaine", cocaine_recipe_region),
|
||||
]
|
||||
# Add needed recipe locations to location name to id
|
||||
for drug_name, region in drug_types:
|
||||
recipe_locations = []
|
||||
for i in range(1, world.options.recipe_checks + 1):
|
||||
recipe_locations.append(f"{drug_name} Recipe Check, {i}")
|
||||
|
||||
recipe_locations_dict = get_location_names_with_ids(recipe_locations)
|
||||
region.add_locations(recipe_locations_dict, Schedule1Location)
|
||||
|
||||
# Cash for Trash checks - Only include the number specified by the CashForTrash option
|
||||
# Add to Overworld region
|
||||
cash_for_trash_count = world.options.cash_for_trash
|
||||
if cash_for_trash_count > 0:
|
||||
regions = {
|
||||
100 : regions_dict["Overworld"],
|
||||
200 : regions_dict["Dodgy Dealing"],
|
||||
300 : regions_dict["Mixing Mania"],
|
||||
400 : regions_dict["We Need To Cook|2"],
|
||||
500 : regions_dict["Finishing the Job"]
|
||||
}
|
||||
cash_for_trash_locations = []
|
||||
for i in range(1, cash_for_trash_count + 1):
|
||||
cash_for_trash_locations.append(f"Cash for Trash {i}, Collect {i * 10} pieces of trash")
|
||||
cash_for_trash_locations_dict = get_location_names_with_ids(cash_for_trash_locations)
|
||||
regions[ceil(201 / 100) * 100].add_locations(cash_for_trash_locations_dict, Schedule1Location)
|
||||
@@ -0,0 +1,322 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import (Choice, DefaultOnToggle, OptionGroup, PerGameCommonOptions,
|
||||
Range, DeathLink)
|
||||
|
||||
# In this file, we define the options the player can pick.
|
||||
# The most common types of options are Toggle, Range and Choice.
|
||||
|
||||
# Options will be in the game's template yaml.
|
||||
# They will be represented by checkboxes, sliders etc. on the game's options page on the website.
|
||||
# (Note: Options can also be made invisible from either of these places by overriding Option.visibility.
|
||||
# APQuest doesn't have an example of this, but this can be used for secret / hidden / advanced options.)
|
||||
|
||||
# For further reading on options, you can also read the Options API Document:
|
||||
# https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/options%20api.md
|
||||
|
||||
|
||||
# The first type of Option we'll discuss is the Toggle.
|
||||
# A toggle is an option that can either be on or off. This will be represented by a checkbox on the website.
|
||||
# The default for a toggle is "off".
|
||||
# If you want a toggle to be on by default, you can use the "DefaultOnToggle" class instead of the "Toggle" class.
|
||||
class Goal(Choice):
|
||||
"""
|
||||
The goal to win the game.
|
||||
missions_networth: Reach a specificed net worth and complete the main story.
|
||||
networth_only: Reach a specificed net worth.
|
||||
missions_only: Complete the main story.
|
||||
"""
|
||||
|
||||
display_name = "Goal"
|
||||
|
||||
option_networth_only = 0
|
||||
option_missions_networth = 1
|
||||
option_missions_only = 2
|
||||
|
||||
# Choice options must define an explicit default value.
|
||||
default = option_missions_networth
|
||||
|
||||
class RandomizeLevelUnlocks(DefaultOnToggle):
|
||||
"""
|
||||
Leveling up is always a check regardless of this option.
|
||||
Block Boss I, 30,350xp, will be the highest checks will go to.
|
||||
Things you unlock by leveling up will be shuffled into the item pool if this option is enabled.
|
||||
When this is enabled, you will no longer get level up rewards naturally.
|
||||
"""
|
||||
display_name = "Randomize Level Unlocks"
|
||||
|
||||
|
||||
class NumberOfXpBundles(Range):
|
||||
"""
|
||||
Number of XP bundles to include in the item pool.
|
||||
min to max xp bundles will be interpolated based on number of bundles.
|
||||
Meaning there will be 1 min bundle and 1 max bundle and the rest will be evenly distributed in between.
|
||||
If default min/max are used, 25 bundles give max level xp. 12 gives out about a quarter of that.
|
||||
16,425 xp is needed to complete the game, but this can be done naturally, bundles speed up process.
|
||||
If 1 bundle is chosen, only the minimum xp bundle will be in the item pool.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
default = 12
|
||||
|
||||
|
||||
class AmountOfXpPerBundleMin(Range):
|
||||
"""
|
||||
Min amount of XP per bundle included in the item pool.
|
||||
Each bundle is worth the specified amount of XP.
|
||||
This option is only relevant if NumberOfXpBundles > 0.
|
||||
"""
|
||||
range_start = 1
|
||||
range_end = 1000
|
||||
default = 100
|
||||
|
||||
|
||||
class AmountOfXpPerBundleMax(Range):
|
||||
"""
|
||||
Max amount of XP per bundle included in the item pool.
|
||||
Each bundle is worth the specified amount of XP.
|
||||
This option is only relevant if NumberOfXpBundles > 0.
|
||||
"""
|
||||
range_start = 1000
|
||||
range_end = 10000
|
||||
default = 5000
|
||||
|
||||
|
||||
class NumberOfCashBundles(Range):
|
||||
"""
|
||||
Number of cash bundles to include in the item pool.
|
||||
min to max cash bundles will be interpolated based on number of bundles.
|
||||
Meaning there will be 1 min bundle and 1 max bundle and the rest will be evenly distributed in between.
|
||||
If 1 bundle is chosen, only the minimum cash bundle will be in the item pool.
|
||||
Defaults will give out ~87k cash with 20 bundles. 46 bundles gives out ~200k cash.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
default = 12
|
||||
|
||||
|
||||
class AmountOfCashPerBundleMin(Range):
|
||||
"""
|
||||
Min amount of cash per bundle included in the item pool.
|
||||
Each bundle is worth the specified amount of cash.
|
||||
This option is only relevant if NumberOfCashBundles > 0.
|
||||
"""
|
||||
range_start = 1
|
||||
range_end = 1500
|
||||
default = 1500
|
||||
|
||||
|
||||
class AmountOfCashPerBundleMax(Range):
|
||||
"""
|
||||
Max amount of cash per bundle included in the item pool.
|
||||
Each bundle is worth the specified amount of cash.
|
||||
This option is only relevant if NumberOfCashBundles > 0.
|
||||
"""
|
||||
range_start = 1500
|
||||
range_end = 100000
|
||||
default = 10000
|
||||
|
||||
|
||||
class NetworthAmountRequired(Range):
|
||||
"""
|
||||
The net worth amount required to win the game.
|
||||
This option is only relevant if the goal includes net worth.
|
||||
"""
|
||||
range_start = 10000
|
||||
range_end = 10000000
|
||||
default = 100000
|
||||
|
||||
class BanBadFillerItems(DefaultOnToggle):
|
||||
"""
|
||||
If enabled, bad filler items will not be included in the item pool.
|
||||
"""
|
||||
display_name = "Ban Bad Filler Items"
|
||||
|
||||
|
||||
class BanProgressionSkipItems(DefaultOnToggle):
|
||||
"""
|
||||
If enabled, filler items that allow for progression skips will not be included in the item pool.
|
||||
This can add variety to the seed to allow for out of logic skips.
|
||||
"""
|
||||
display_name = "Ban Progression Skip Filler Items"
|
||||
|
||||
|
||||
class TrapChance(Range):
|
||||
"""
|
||||
Percentage chance that a filler item will be a trap.
|
||||
"""
|
||||
|
||||
display_name = "Trap Chance"
|
||||
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeCartelInfluence(DefaultOnToggle):
|
||||
"""
|
||||
Determines if cartel influence will be randomized into the item pool.
|
||||
7 Bundles of 100 cartel influence per in-game region that applies.
|
||||
Every 100 cartel influcence by the player counts as a check regardless of this option.
|
||||
This option removes the player's ability to earn cartel influence naturally
|
||||
"""
|
||||
display_name = "Randomize Cartel Influence"
|
||||
|
||||
|
||||
class CartelInfluenceItemsPerRegion(Range):
|
||||
"""
|
||||
Number of cartel influence Items to include in the item pool per region.
|
||||
Each item is worth 100 cartel influence.
|
||||
7 needed to unlock region, recommend to add extra of each region.
|
||||
Westville has 5 less checks and 5 less cartel influence items
|
||||
This option is only relevant if Randomize Cartel Influence is enabled.
|
||||
"""
|
||||
range_start = 7
|
||||
range_end = 12
|
||||
default = 10
|
||||
|
||||
|
||||
class RandomizeDrugMakingProperties(DefaultOnToggle):
|
||||
"""
|
||||
Determines if drug making properties will be added into the item pool.
|
||||
Purchasing drug making properties become checks, but you do not purchase them.
|
||||
Realtor will have AP items instead of drug making properties if this is enabled.
|
||||
This does not include ones you must purchase through missions.
|
||||
"""
|
||||
display_name = "Randomize drug making Properties"
|
||||
|
||||
|
||||
class RandomizeBusinessProperties(DefaultOnToggle):
|
||||
"""
|
||||
Determines if business properties will be added into the item pool.
|
||||
The Realtor will have AP items instead of business properties if this is enabled.
|
||||
"""
|
||||
display_name = "Randomize business making Properties"
|
||||
|
||||
|
||||
class RandomizeDealers(DefaultOnToggle):
|
||||
"""
|
||||
Determines if dealers will be added into the item pool.
|
||||
Recruiting dealers become checks, but you do not recruit them.
|
||||
This does not include Benji, who is required for story progression.
|
||||
"""
|
||||
display_name = "Randomize Dealers"
|
||||
|
||||
|
||||
class RandomizeCustomers(DefaultOnToggle):
|
||||
"""
|
||||
Determines if customers will be added into the item pool.
|
||||
Customers are checks regardless if this is toggled on or off
|
||||
Player can still get successful samples as checks
|
||||
"""
|
||||
display_name = "Randomize Customers"
|
||||
|
||||
class RandomizeSuppliers(DefaultOnToggle):
|
||||
"""
|
||||
Determines if suppliers will be added into the item pool.
|
||||
If enabled, befriending suppliers no longer become checks.
|
||||
Albert Hoover is unlocked by default
|
||||
"""
|
||||
display_name = "Randomize Suppliers"
|
||||
|
||||
|
||||
class RandomizeSewerKey(DefaultOnToggle):
|
||||
"""
|
||||
Determines if the Sewer Key will be added into the item pool.
|
||||
If enabled, Jen Herd will no longer sell sewer key.
|
||||
Buying the sewer key from Jen Herd is a check no matter if this option is toggled on or off.
|
||||
"""
|
||||
display_name = "Randomize Sewer Key"
|
||||
|
||||
class RecipeChecks(Range):
|
||||
"""
|
||||
Number of recipe checks to include in the item pool.
|
||||
These are recipes per drug type.
|
||||
10 means 10 weed recipes, 10 meth recipes, etc.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 15
|
||||
default = 5
|
||||
|
||||
class CashForTrash(Range):
|
||||
"""
|
||||
Number of checks for each 10 pieces of trash collected
|
||||
50 = 500 total pieces of trash which is equal to the achiemvement.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 5
|
||||
|
||||
# We must now define a dataclass inheriting from PerGameCommonOptions that we put all our options in.
|
||||
# This is in the format "option_name_in_snake_case: OptionClassName".
|
||||
@dataclass
|
||||
class Schedule1Options(PerGameCommonOptions):
|
||||
goal: Goal
|
||||
networth_amount_required: NetworthAmountRequired
|
||||
ban_bad_filler_items: BanBadFillerItems
|
||||
ban_progression_skip_items: BanProgressionSkipItems
|
||||
trap_chance: TrapChance
|
||||
number_of_xp_bundles: NumberOfXpBundles
|
||||
amount_of_xp_per_bundle_min: AmountOfXpPerBundleMin
|
||||
amount_of_xp_per_bundle_max: AmountOfXpPerBundleMax
|
||||
number_of_cash_bundles: NumberOfCashBundles
|
||||
amount_of_cash_per_bundle_min: AmountOfCashPerBundleMin
|
||||
amount_of_cash_per_bundle_max: AmountOfCashPerBundleMax
|
||||
randomize_level_unlocks: RandomizeLevelUnlocks
|
||||
randomize_cartel_influence: RandomizeCartelInfluence
|
||||
cartel_influence_items_per_region: CartelInfluenceItemsPerRegion
|
||||
randomize_drug_making_properties: RandomizeDrugMakingProperties
|
||||
randomize_business_properties: RandomizeBusinessProperties
|
||||
randomize_dealers: RandomizeDealers
|
||||
randomize_customers: RandomizeCustomers
|
||||
randomize_suppliers: RandomizeSuppliers
|
||||
randomize_sewer_key: RandomizeSewerKey
|
||||
recipe_checks: RecipeChecks
|
||||
cash_for_trash: CashForTrash
|
||||
death_link: DeathLink
|
||||
|
||||
|
||||
|
||||
# If we want to group our options by similar type, we can do so as well. This looks nice on the website.
|
||||
option_groups = [
|
||||
OptionGroup(
|
||||
"Gameplay Options",
|
||||
[Goal, NetworthAmountRequired, NumberOfXpBundles, AmountOfXpPerBundleMin, AmountOfXpPerBundleMax,
|
||||
NumberOfCashBundles, AmountOfCashPerBundleMin, AmountOfCashPerBundleMax,
|
||||
BanBadFillerItems, BanProgressionSkipItems, TrapChance,
|
||||
RandomizeLevelUnlocks, RandomizeCartelInfluence, CartelInfluenceItemsPerRegion,
|
||||
RandomizeCustomers, RandomizeDealers, RandomizeSuppliers, RandomizeSewerKey,
|
||||
RandomizeDrugMakingProperties, RandomizeBusinessProperties,
|
||||
RecipeChecks, CashForTrash,
|
||||
DeathLink],
|
||||
)
|
||||
]
|
||||
|
||||
# Finally, we can define some option presets if we want the player to be able to quickly choose a specific "mode".
|
||||
option_presets = {
|
||||
"Default": {
|
||||
"goal": Goal.default,
|
||||
"number_of_xp_bundles": NumberOfXpBundles.default,
|
||||
"amount_of_xp_per_bundle_min": AmountOfXpPerBundleMin.default,
|
||||
"amount_of_xp_per_bundle_max": AmountOfXpPerBundleMax.default,
|
||||
"number_of_cash_bundles": NumberOfCashBundles.default,
|
||||
"amount_of_cash_per_bundle_min": AmountOfCashPerBundleMin.default,
|
||||
"amount_of_cash_per_bundle_max": AmountOfCashPerBundleMax.default,
|
||||
"networth_amount_required": NetworthAmountRequired.default,
|
||||
"ban_bad_filler_items": BanBadFillerItems.default,
|
||||
"ban_progression_skip_items": BanProgressionSkipItems.default,
|
||||
"trap_chance": TrapChance.default,
|
||||
"randomize_cartel_influence": True,
|
||||
"randomize_drug_making_properties": True,
|
||||
"randomize_business_properties": True,
|
||||
"randomize_dealers": True,
|
||||
"randomize_customers": True,
|
||||
"cartel_influence_items_per_region": CartelInfluenceItemsPerRegion.default,
|
||||
"recipe_checks": RecipeChecks.default,
|
||||
"cash_for_trash": CashForTrash.default,
|
||||
"randomize_level_unlocks": True,
|
||||
"randomize_suppliers": True,
|
||||
"randomize_sewer_key": True,
|
||||
"death_link": DeathLink.default,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Dict
|
||||
|
||||
from BaseClasses import Region
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import Schedule1World as Schedule1World
|
||||
|
||||
# A region is a container for locations ("checks"), which connects to other regions via "Entrance" objects.
|
||||
# Many games will model their Regions after physical in-game places, but you can also have more abstract regions.
|
||||
# For a location to be in logic, its containing region must be reachable.
|
||||
# The Entrances connecting regions can have rules - more on that in rules.py.
|
||||
# This makes regions especially useful for traversal logic ("Can the player reach this part of the map?")
|
||||
|
||||
# Every location must be inside a region, and you must have at least one region.
|
||||
# This is why we create regions first, and then later we create the locations (in locations.py).
|
||||
|
||||
|
||||
def create_and_connect_regions(world: Schedule1World, region_data) -> None:
|
||||
create_all_regions(world, region_data)
|
||||
connect_regions(world, region_data)
|
||||
|
||||
|
||||
def create_all_regions(world: Schedule1World, region_data) -> None:
|
||||
# Create all regions from regions.json
|
||||
regions = []
|
||||
|
||||
for region_name in region_data.regions.keys():
|
||||
region = Region(region_name, world.player, world.multiworld)
|
||||
regions.append(region)
|
||||
|
||||
# Add all regions to multiworld.regions so that AP knows about their existence
|
||||
world.multiworld.regions += regions
|
||||
|
||||
|
||||
def connect_regions(world: Schedule1World, region_data) -> None:
|
||||
# Load all regions into a dictionary once to avoid repeated get_region calls
|
||||
regions_dict: Dict[str, Region] = {
|
||||
region_name: world.get_region(region_name)
|
||||
for region_name in region_data.regions.keys()
|
||||
}
|
||||
|
||||
# Connect all regions based on the connections defined in regions.json
|
||||
for region_name, region_info in region_data.regions.items():
|
||||
source_region = regions_dict[region_name]
|
||||
|
||||
# Iterate through all connections for this region
|
||||
for connected_region_name in region_info.connections.keys():
|
||||
target_region = regions_dict[connected_region_name]
|
||||
entrance_name = f"{region_name} to {connected_region_name}"
|
||||
source_region.connect(target_region, entrance_name)
|
||||
@@ -0,0 +1,251 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Any, Union
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import add_rule, set_rule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import Schedule1World
|
||||
|
||||
|
||||
def set_all_rules(world: Schedule1World, locationData, regionData, victoryData) -> None:
|
||||
# In order for AP to generate an item layout that is actually possible for the player to complete,
|
||||
# we need to define rules for our Entrances and Locations.
|
||||
# Note: Regions do not have rules, the Entrances connecting them do!
|
||||
# We'll do entrances first, then locations, and then finally we set our victory condition.
|
||||
|
||||
set_all_entrance_rules(world, regionData)
|
||||
set_all_location_rules(world, locationData)
|
||||
set_completion_condition(world, victoryData)
|
||||
|
||||
|
||||
def check_option_enabled(world: Schedule1World, option_name: str) -> bool:
|
||||
"""Check if an option is enabled based on option name string from JSON."""
|
||||
option_map = {
|
||||
"randomize_customers": world.options.randomize_customers,
|
||||
"randomize_dealers": world.options.randomize_dealers,
|
||||
"randomize_suppliers": world.options.randomize_suppliers,
|
||||
"randomize_level_unlocks": world.options.randomize_level_unlocks,
|
||||
"randomize_cartel_influence": world.options.randomize_cartel_influence,
|
||||
"randomize_business_properties": world.options.randomize_business_properties,
|
||||
"randomize_drug_making_properties": world.options.randomize_drug_making_properties,
|
||||
}
|
||||
return bool(option_map.get(option_name, False))
|
||||
|
||||
|
||||
def check_option_condition(world: Schedule1World, condition_key: str) -> bool:
|
||||
"""
|
||||
Parse and evaluate a compound option condition string.
|
||||
|
||||
Supports:
|
||||
- Simple: "randomize_level_unlocks" (option must be true)
|
||||
- Negation: "!randomize_level_unlocks" (option must be false)
|
||||
- Compound AND: "randomize_level_unlocks&!randomize_customers"
|
||||
(first must be true AND second must be false)
|
||||
|
||||
Returns True if the condition is satisfied, False otherwise.
|
||||
"""
|
||||
# Split by '&' to get individual conditions
|
||||
parts = condition_key.split('&')
|
||||
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
# Check for negation prefix
|
||||
if part.startswith('!'):
|
||||
option_name = part[1:]
|
||||
expected_value = False
|
||||
else:
|
||||
option_name = part
|
||||
expected_value = True
|
||||
|
||||
# Get the actual option value
|
||||
actual_value = check_option_enabled(world, option_name)
|
||||
|
||||
# If this part doesn't match expected, the whole condition fails
|
||||
if actual_value != expected_value:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def build_requirement_check(world: Schedule1World, method_name: str, value: Any) -> Callable[[CollectionState], bool]:
|
||||
"""Build a requirement check function based on the method name and value from JSON."""
|
||||
|
||||
if method_name == "has":
|
||||
# value is a single item name string
|
||||
return lambda state, v=value: state.has(v, world.player)
|
||||
|
||||
elif method_name == "has_any":
|
||||
# value is a list of lists, e.g. [["Item1", "Item2"]]
|
||||
# We take the first list as the items to check
|
||||
items = value[0] if isinstance(value[0], list) else value
|
||||
return lambda state, v=items: state.has_any(v, world.player)
|
||||
|
||||
elif method_name == "has_all":
|
||||
# value is a list of item names
|
||||
return lambda state, v=value: state.has_all(v, world.player)
|
||||
|
||||
elif method_name == "has_all_counts":
|
||||
# value is a dict of {item_name: count}
|
||||
return lambda state, v=value: state.has_all_counts(v, world.player)
|
||||
|
||||
elif method_name == "has_from_list":
|
||||
# value can be:
|
||||
# - A single dict: {item_name: count, ...} where all counts are the same
|
||||
# - A list of dicts: [{item_name: count, ...}, ...] for multiple tiers
|
||||
# For a list, we build checks for each dict and require all to pass
|
||||
if isinstance(value, list):
|
||||
# List of dicts - build a check for each dict
|
||||
checks = []
|
||||
for tier_dict in value:
|
||||
keys = list(tier_dict.keys())
|
||||
count = list(tier_dict.values())[0] # All values in a tier should be the same
|
||||
checks.append((keys, count))
|
||||
return lambda state, c=checks: all(
|
||||
state.has_from_list(keys, world.player, count) for keys, count in c
|
||||
)
|
||||
else:
|
||||
# Single dict
|
||||
keys = list(value.keys())
|
||||
count = list(value.values())[0] # All values should be the same count
|
||||
return lambda state, k=keys, c=count: state.has_from_list(k, world.player, c)
|
||||
|
||||
# Default: always true
|
||||
return lambda state: True
|
||||
|
||||
|
||||
def build_rule_from_requirements(world: Schedule1World, requirements: Union[bool, Dict[str, Any]], use_or_logic: bool = False) -> Callable[[CollectionState], bool]:
|
||||
"""
|
||||
Build a rule function from the requirements structure.
|
||||
|
||||
requirements can be:
|
||||
- True (always accessible)
|
||||
- A dict with option conditions as keys
|
||||
|
||||
use_or_logic: If True, only ONE condition needs to be satisfied (for Customer/Dealer/Supplier tags)
|
||||
If False, ALL applicable conditions must be satisfied
|
||||
"""
|
||||
if requirements is True:
|
||||
return lambda state: True
|
||||
|
||||
if not isinstance(requirements, dict):
|
||||
return lambda state: True
|
||||
|
||||
# Build list of (option_name, checks) pairs
|
||||
condition_checks: list[tuple[str, list[Callable[[CollectionState], bool]]]] = []
|
||||
|
||||
for option_name, checks in requirements.items():
|
||||
if not isinstance(checks, dict):
|
||||
continue
|
||||
|
||||
check_functions = []
|
||||
for method_name, value in checks.items():
|
||||
check_func = build_requirement_check(world, method_name, value)
|
||||
check_functions.append(check_func)
|
||||
|
||||
if check_functions:
|
||||
condition_checks.append((option_name, check_functions))
|
||||
|
||||
if not condition_checks:
|
||||
return lambda state: True
|
||||
|
||||
def rule_function(state: CollectionState) -> bool:
|
||||
results = []
|
||||
|
||||
for condition_key, check_functions in condition_checks:
|
||||
if check_option_condition(world, condition_key):
|
||||
# This option condition is satisfied, so its checks matter
|
||||
# All checks within this condition must pass
|
||||
option_result = all(check(state) for check in check_functions)
|
||||
results.append(option_result)
|
||||
|
||||
if not results:
|
||||
# No applicable options enabled - rule passes
|
||||
return True
|
||||
|
||||
if use_or_logic:
|
||||
# For Customer/Dealer/Supplier: only one needs to pass
|
||||
return any(results)
|
||||
else:
|
||||
# For all others: all must pass
|
||||
return all(results)
|
||||
|
||||
return rule_function
|
||||
|
||||
|
||||
def set_all_entrance_rules(world: Schedule1World, regionData) -> None:
|
||||
"""Set entrance rules based on region connection requirements from regions.json."""
|
||||
|
||||
# Load all entrances into a dictionary once
|
||||
entrances_dict: Dict[str, Any] = {}
|
||||
|
||||
for region_name, region_info in regionData.regions.items():
|
||||
for connected_region_name, requirements in region_info.connections.items():
|
||||
entrance_name = f"{region_name} to {connected_region_name}"
|
||||
try:
|
||||
entrances_dict[entrance_name] = world.get_entrance(entrance_name)
|
||||
except KeyError:
|
||||
# Entrance might not exist if region wasn't created
|
||||
continue
|
||||
|
||||
# Set rules for each entrance
|
||||
for region_name, region_info in regionData.regions.items():
|
||||
for connected_region_name, requirements in region_info.connections.items():
|
||||
entrance_name = f"{region_name} to {connected_region_name}"
|
||||
|
||||
if entrance_name not in entrances_dict:
|
||||
continue
|
||||
|
||||
entrance = entrances_dict[entrance_name]
|
||||
rule = build_rule_from_requirements(world, requirements, use_or_logic=False)
|
||||
set_rule(entrance, rule)
|
||||
|
||||
|
||||
def set_all_location_rules(world: Schedule1World, locationData) -> None:
|
||||
"""Set location rules based on requirements from locations.json."""
|
||||
|
||||
# Build a dict of location name -> location object for locations that exist
|
||||
locations_dict: Dict[str, Any] = {}
|
||||
|
||||
for loc_name, loc_data in locationData.locations.items():
|
||||
# Skip supplier locations if randomize_suppliers is enabled (they don't exist)
|
||||
if world.options.randomize_suppliers and "Supplier" in loc_data.tags:
|
||||
continue
|
||||
|
||||
try:
|
||||
locations_dict[loc_name] = world.get_location(loc_name)
|
||||
except KeyError:
|
||||
# Location might not exist
|
||||
continue
|
||||
|
||||
# Set rules for each location
|
||||
for loc_name, loc_data in locationData.locations.items():
|
||||
if loc_name not in locations_dict:
|
||||
continue
|
||||
|
||||
location = locations_dict[loc_name]
|
||||
requirements = loc_data.requirements
|
||||
|
||||
# Determine if this location uses OR logic (Customer, Dealer, or Supplier tags)
|
||||
tags = loc_data.tags
|
||||
use_or_logic = any(tag in tags for tag in ["Customer", "Dealer", "Supplier"])
|
||||
|
||||
rule = build_rule_from_requirements(world, requirements, use_or_logic=use_or_logic)
|
||||
set_rule(location, rule)
|
||||
|
||||
|
||||
def set_completion_condition(world: Schedule1World, victoryData) -> None:
|
||||
# Victory conditions are loaded from victory.json
|
||||
# > 0 means cartel is necessary, and we need to check all applicable conditions
|
||||
if world.options.goal > 0:
|
||||
# Build the victory rule from the requirements in victory.json
|
||||
# All applicable option conditions must pass (AND logic)
|
||||
rule = build_rule_from_requirements(world, victoryData.requirements, use_or_logic=False)
|
||||
world.multiworld.completion_condition[world.player] = rule
|
||||
else:
|
||||
# Otherwise, money is farmable no matter what.
|
||||
world.multiworld.completion_condition[world.player] = lambda state: True
|
||||
@@ -0,0 +1,37 @@
|
||||
from BaseClasses import Tutorial
|
||||
from worlds.AutoWorld import WebWorld
|
||||
|
||||
from .options import option_groups, option_presets
|
||||
|
||||
|
||||
# For our game to display correctly on the website, we need to define a WebWorld subclass.
|
||||
class APSchedule1(WebWorld):
|
||||
# We need to override the "game" field of the WebWorld superclass.
|
||||
# This must be the same string as the regular World class.
|
||||
game = "Schedule I"
|
||||
|
||||
# Your game pages will have a visual theme (affecting e.g. the background image).
|
||||
# You can choose between dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, and stone.
|
||||
theme = "partyTime"
|
||||
|
||||
# A WebWorld can have any number of tutorials, but should always have at least an English setup guide.
|
||||
# Many WebWorlds just have one setup guide, but some have multiple, e.g. for different languages.
|
||||
# We need to create a Tutorial object for every setup guide.
|
||||
# In order, we need to provide a title, a description, a language, a filepath, a link, and authors.
|
||||
# The filepath is relative to a "/docs/" directory in the root folder of your apworld.
|
||||
# The "link" parameter is unused, but we still need to provide it.
|
||||
setup_en = Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up Schedule 1 for MultiWorld.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["NewSoupVi"],
|
||||
)
|
||||
|
||||
# We add these tutorials to our WebWorld by overriding the "tutorials" field.
|
||||
tutorials = [setup_en]
|
||||
|
||||
# If we have option groups and/or option presets, we need to specify these here as well.
|
||||
option_groups = option_groups
|
||||
options_presets = option_presets
|
||||
@@ -0,0 +1,110 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
# Imports of base Archipelago modules must be absolute.
|
||||
from worlds.AutoWorld import World
|
||||
|
||||
# Imports of your world's files must be relative.
|
||||
from . import items, locations, options, regions, rules, web_world, json_data
|
||||
|
||||
# APQuest will go through all the parts of the world api one step at a time,
|
||||
# with many examples and comments across multiple files.
|
||||
# If you'd rather read one continuous document, or just like reading multiple sources,
|
||||
# we also have this document specifying the entire world api:
|
||||
# https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md
|
||||
|
||||
|
||||
# The world class is the heart and soul of an apworld implementation.
|
||||
# It holds all the data and functions required to build the world and submit it to the multiworld generator.
|
||||
# You could have all your world code in just this one class, but for readability and better structure,
|
||||
# it is common to split up world functionality into multiple files.
|
||||
# This implementation in particular has the following additional files, each covering one topic:
|
||||
# regions.py, locations.py, rules.py, items.py, options.py and web_world.py.
|
||||
# It is recommended that you read these in that specific order, then come back to the world class.
|
||||
class Schedule1World(World):
|
||||
"""
|
||||
Scheudle 1 is a game about manufacturing. Produce a range of drugs. Purchase properties and equipment.
|
||||
Distribute your products through a network of dealers. Avoid the law and rival manufacturers.
|
||||
Expand your empire and become the ultimate drug lord!
|
||||
"""
|
||||
|
||||
# The docstring should contain a description of the game, to be displayed on the WebHost.
|
||||
|
||||
# You must override the "game" field to say the name of the game.
|
||||
game = "Schedule I"
|
||||
|
||||
# The WebWorld is a definition class that governs how this world will be displayed on the website.
|
||||
web = web_world.APSchedule1()
|
||||
|
||||
# This is how we associate the options defined in our options.py with our world.
|
||||
options_dataclass = options.Schedule1Options
|
||||
options: options.Schedule1Options # Common mistake: This has to be a colon (:), not an equals sign (=).
|
||||
|
||||
# Our world class must have a static location_name_to_id and item_name_to_id defined.
|
||||
# We define these in regions.py and items.py respectively, so we just set them here.
|
||||
# Load items from json into needed dicts.
|
||||
items.load_items_data(json_data.schedule1_item_data)
|
||||
locations.load_locations_data(json_data.schedule1_location_data)
|
||||
location_name_to_id = locations.LOCATION_NAME_TO_ID
|
||||
item_name_to_id = items.ITEM_NAME_TO_ID
|
||||
|
||||
# There is always one region that the generator starts from & assumes you can always go back to.
|
||||
# This defaults to "Menu", but you can change it by overriding origin_region_name.
|
||||
origin_region_name = "Overworld"
|
||||
|
||||
# Our world class must have certain functions ("steps") that get called during generation.
|
||||
# The main ones are: create_regions, set_rules, create_items.
|
||||
# For better structure and readability, we put each of these in their own file.
|
||||
def create_regions(self) -> None:
|
||||
regions.create_and_connect_regions(self, json_data.schedule1_region_data)
|
||||
locations.create_all_locations(self, json_data.schedule1_location_data)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
rules.set_all_rules(self, json_data.schedule1_location_data, json_data.schedule1_region_data, json_data.schedule1_victory_data)
|
||||
|
||||
def create_items(self) -> None:
|
||||
items.create_all_items(self, json_data.schedule1_item_data)
|
||||
|
||||
# Our world class must also have a create_item function that can create any one of our items by name at any time.
|
||||
# We also put this in a different file, the same one that create_items is in.
|
||||
def create_item(self, name: str) -> items.Schedule1Item:
|
||||
return items.create_item_with_correct_classification(self, name)
|
||||
|
||||
# For features such as item links and panic-method start inventory, AP may ask your world to create extra filler.
|
||||
# The way it does this is by calling get_filler_item_name.
|
||||
# For this purpose, your world *must* have at least one infinitely repeatable item (usually filler).
|
||||
# You must override this function and return this infinitely repeatable item's name.
|
||||
# In our case, we defined a function called get_random_filler_item_name for this purpose in our items.py.
|
||||
def get_filler_item_name(self) -> str:
|
||||
return items.get_random_filler_item_name(self)
|
||||
|
||||
# There may be data that the game client will need to modify the behavior of the game.
|
||||
# This is what slot_data exists for. Upon every client connection, the slot's slot_data is sent to the client.
|
||||
# slot_data is just a dictionary using basic types, that will be converted to json when sent to the client.
|
||||
def fill_slot_data(self) -> Mapping[str, Any]:
|
||||
# If you need access to the player's chosen options on the client side, there is a helper for that.
|
||||
return self.options.as_dict(
|
||||
"goal",
|
||||
"number_of_xp_bundles",
|
||||
"amount_of_xp_per_bundle_min",
|
||||
"amount_of_xp_per_bundle_max",
|
||||
"number_of_cash_bundles",
|
||||
"amount_of_cash_per_bundle_min",
|
||||
"amount_of_cash_per_bundle_max",
|
||||
"networth_amount_required",
|
||||
"ban_bad_filler_items",
|
||||
"ban_progression_skip_items",
|
||||
"trap_chance",
|
||||
"randomize_cartel_influence",
|
||||
"randomize_drug_making_properties",
|
||||
"randomize_business_properties",
|
||||
"randomize_dealers",
|
||||
"randomize_customers",
|
||||
"randomize_suppliers",
|
||||
"cartel_influence_items_per_region",
|
||||
"recipe_checks",
|
||||
"cash_for_trash",
|
||||
"randomize_level_unlocks",
|
||||
"randomize_sewer_key",
|
||||
"death_link"
|
||||
)
|
||||
@@ -0,0 +1,265 @@
|
||||
import settings
|
||||
from typing import Dict, Any
|
||||
from BaseClasses import MultiWorld, Region, Item, Tutorial
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from Utils import visualize_regions
|
||||
from worlds.generic.Rules import set_rule
|
||||
from .items import (SonicFrontiersItem, SonicFrontiersItemData, item_list, kronos_amount, ares_amount, chaos_amount, ouranos_amount, fillers)
|
||||
from .locations import (kronosRegion, SonicFrontiersAdvancement, aresRegion, chaosRegion, ouranosRegion, kronosMemoryTokenSet,
|
||||
aresMemoryTokenSet, chaosMemoryTokenSet, ouranosMemoryTokenSet, kronosPurpleSet, kronosKocoSet, aresKocoSet,
|
||||
aresPurpleSet, chaosKocoSet, chaosPurpleSet, ouranosKocoSet, ouranosPurpleSet, kronosNewKocoSet,
|
||||
kronosMusicSet, aresMusicSet, aresNewKocoSet, chaosMusicSet, chaosNewKocoSet, ouranosMusicSet, ouranosNewKocoSet, all_items)
|
||||
from .options import SonicFrontiersOptions
|
||||
|
||||
class SonicFrontiersWebWorld(WebWorld):
|
||||
setup_en = Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to playing Sonic Frontiers with Archipelago.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["custom"]
|
||||
)
|
||||
|
||||
tutorials = [setup_en]
|
||||
|
||||
class SonicFrontiersWorld(World):
|
||||
game = "Sonic Frontiers"
|
||||
topology_present = False
|
||||
web = SonicFrontiersWebWorld()
|
||||
item_name_to_id = {name: data.id for name, data in item_list.items()}
|
||||
location_name_to_id = {name: data.id for name, data in all_items.items()}
|
||||
|
||||
options_dataclass = SonicFrontiersOptions
|
||||
options: SonicFrontiersOptions
|
||||
def create_kronos_island(self) -> Region:
|
||||
return
|
||||
def create_ares_island(self) -> Region:
|
||||
return
|
||||
|
||||
def create_regions(self) -> None:
|
||||
menu_region = Region("Menu", self.player, self.multiworld)
|
||||
|
||||
kronos_region = Region("Kronos", self.player, self.multiworld)
|
||||
kronos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, kronos_region)for loc_name, loc_data in kronosRegion.items()]
|
||||
|
||||
if(self.options.memory_token_sanity):
|
||||
kronos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, kronos_region)for loc_name, loc_data in kronosMemoryTokenSet.items()]
|
||||
if(self.options.challenge_kocos):
|
||||
kronos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, kronos_region)for loc_name, loc_data in kronosNewKocoSet.items()]
|
||||
if(self.options.music_notes):
|
||||
kronos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, kronos_region)for loc_name, loc_data in kronosMusicSet.items()]
|
||||
if(self.options.purple_coin_sanity):
|
||||
kronos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, kronos_region)for loc_name, loc_data in kronosPurpleSet.items()]
|
||||
if(self.options.koco_sanity):
|
||||
kronos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, kronos_region)for loc_name, loc_data in kronosKocoSet.items()]
|
||||
menu_region.connect(kronos_region)
|
||||
self.multiworld.regions += [menu_region, kronos_region]
|
||||
|
||||
if(self.options.goal > 0):
|
||||
ares_region = Region("Ares", self.player, self.multiworld)
|
||||
ares_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ares_region)for loc_name, loc_data in aresRegion.items()]
|
||||
|
||||
if(self.options.memory_token_sanity):
|
||||
ares_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ares_region)for loc_name, loc_data in aresMemoryTokenSet.items()]
|
||||
if(self.options.challenge_kocos):
|
||||
ares_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ares_region)for loc_name, loc_data in aresNewKocoSet.items()]
|
||||
if(self.options.music_notes):
|
||||
ares_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ares_region)for loc_name, loc_data in aresMusicSet.items()]
|
||||
if(self.options.purple_coin_sanity):
|
||||
ares_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ares_region)for loc_name, loc_data in aresPurpleSet.items()]
|
||||
if(self.options.koco_sanity):
|
||||
ares_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ares_region)for loc_name, loc_data in aresKocoSet.items()]
|
||||
self.multiworld.regions += [ares_region]
|
||||
kronos_region.add_exits({"Ares": "Ares Entrance"})
|
||||
|
||||
if(self.options.goal > 1):
|
||||
chaos_region = Region("Chaos", self.player, self.multiworld)
|
||||
chaos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, chaos_region)for loc_name, loc_data in chaosRegion.items()]
|
||||
if(self.options.memory_token_sanity):
|
||||
chaos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, chaos_region)for loc_name, loc_data in chaosMemoryTokenSet.items()]
|
||||
if(self.options.challenge_kocos):
|
||||
chaos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, chaos_region)for loc_name, loc_data in chaosNewKocoSet.items()]
|
||||
if(self.options.music_notes):
|
||||
chaos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, chaos_region)for loc_name, loc_data in chaosMusicSet.items()]
|
||||
if(self.options.purple_coin_sanity):
|
||||
chaos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, chaos_region)for loc_name, loc_data in chaosPurpleSet.items()]
|
||||
if(self.options.koco_sanity):
|
||||
chaos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, chaos_region)for loc_name, loc_data in chaosKocoSet.items()]
|
||||
self.multiworld.regions += [chaos_region]
|
||||
ares_region.add_exits({"Chaos": "Chaos Entrance"})
|
||||
|
||||
if(self.options.goal > 2):
|
||||
ouranos_region = Region("Ouranos", self.player, self.multiworld)
|
||||
ouranos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ouranos_region)for loc_name, loc_data in ouranosRegion.items()]
|
||||
if(self.options.memory_token_sanity):
|
||||
ouranos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ouranos_region)for loc_name, loc_data in ouranosMemoryTokenSet.items()]
|
||||
if(self.options.challenge_kocos):
|
||||
ouranos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ouranos_region)for loc_name, loc_data in ouranosNewKocoSet.items()]
|
||||
if(self.options.music_notes):
|
||||
ouranos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ouranos_region)for loc_name, loc_data in ouranosMusicSet.items()]
|
||||
if(self.options.purple_coin_sanity):
|
||||
ouranos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ouranos_region)for loc_name, loc_data in ouranosPurpleSet.items()]
|
||||
if(self.options.koco_sanity):
|
||||
ouranos_region.locations += [SonicFrontiersAdvancement(self.player, loc_name, loc_data.id, ouranos_region)for loc_name, loc_data in ouranosKocoSet.items()]
|
||||
self.multiworld.regions += [ouranos_region]
|
||||
chaos_region.add_exits({"Ouranos": "Ouranos Entrance"})
|
||||
|
||||
if(self.options.goal == 0):
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Defeat Giganto", self.player)
|
||||
if(self.options.goal == 1):
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Defeat Wyvern", self.player)
|
||||
if(self.options.goal == 2):
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Defeat Knight", self.player)
|
||||
if(self.options.goal == 3):
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Defeat Ouranos", self.player)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
|
||||
for i in range(7):
|
||||
set_rule(self.multiworld.get_location((f"1-2 All Missions ({i+1})"), self.player), lambda state: state.has("1-2 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"1-3 All Missions ({i+1})"), self.player), lambda state: state.has("1-3 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"1-4 All Missions ({i+1})"), self.player), lambda state: state.has("1-4 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"1-5 All Missions ({i+1})"), self.player), lambda state: state.has("1-5 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"1-6 All Missions ({i+1})"), self.player), lambda state: state.has("1-6 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"1-7 All Missions ({i+1})"), self.player), lambda state: state.has("1-7 Unlocked", self.player, 1))
|
||||
if(self.options.goal > 0):
|
||||
set_rule(self.multiworld.get_location((f"2-1 All Missions ({i+1})"), self.player), lambda state: state.has("2-1 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"2-2 All Missions ({i+1})"), self.player), lambda state: state.has("2-2 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"2-3 All Missions ({i+1})"), self.player), lambda state: state.has("2-3 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"2-4 All Missions ({i+1})"), self.player), lambda state: state.has("2-4 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"2-5 All Missions ({i+1})"), self.player), lambda state: state.has("2-5 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"2-6 All Missions ({i+1})"), self.player), lambda state: state.has("2-6 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"2-7 All Missions ({i+1})"), self.player), lambda state: state.has("2-7 Unlocked", self.player, 1))
|
||||
if(self.options.goal > 1):
|
||||
set_rule(self.multiworld.get_location((f"3-1 All Missions ({i+1})"), self.player), lambda state: state.has("3-1 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"3-2 All Missions ({i+1})"), self.player), lambda state: state.has("3-2 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"3-3 All Missions ({i+1})"), self.player), lambda state: state.has("3-3 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"3-4 All Missions ({i+1})"), self.player), lambda state: state.has("3-4 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"3-5 All Missions ({i+1})"), self.player), lambda state: state.has("3-5 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"3-6 All Missions ({i+1})"), self.player), lambda state: state.has("3-6 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"3-7 All Missions ({i+1})"), self.player), lambda state: state.has("3-7 Unlocked", self.player, 1)
|
||||
and state.can_reach_location("Chaos Cyan Emerald", self.player))
|
||||
if(self.options.goal > 2):
|
||||
set_rule(self.multiworld.get_location((f"4-1 All Missions ({i+1})"), self.player), lambda state: state.has("4-1 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-2 All Missions ({i+1})"), self.player), lambda state: state.has("4-2 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-3 All Missions ({i+1})"), self.player), lambda state: state.has("4-3 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-4 All Missions ({i+1})"), self.player), lambda state: state.has("4-4 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-5 All Missions ({i+1})"), self.player), lambda state: state.has("4-5 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-6 All Missions ({i+1})"), self.player), lambda state: state.has("4-6 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-7 All Missions ({i+1})"), self.player), lambda state: state.has("4-7 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-8 All Missions ({i+1})"), self.player), lambda state: state.has("4-8 Unlocked", self.player, 1))
|
||||
set_rule(self.multiworld.get_location((f"4-9 All Missions ({i+1})"), self.player), lambda state: state.has("4-9 Unlocked", self.player, 1))
|
||||
|
||||
set_rule(self.multiworld.get_location(("Kronos Blue Emerald"), self.player), lambda state: state.has("Kronos Vault Key", self.player, 2))
|
||||
set_rule(self.multiworld.get_location(("Kronos Red Emerald"), self.player), lambda state: state.has("Kronos Vault Key", self.player, 5))
|
||||
|
||||
set_rule(self.multiworld.get_location(("Kronos Green Emerald"), self.player), lambda state: state.has("Kronos Vault Key", self.player, 5)
|
||||
and state.has("Kronos Memory Treasure", self.player, 3) and state.has("Progressive Chaos Emerald", self.player, 2))
|
||||
|
||||
set_rule(self.multiworld.get_location(("Kronos Yellow Emerald"), self.player), lambda state: state.has("Kronos Vault Key", self.player, 13))
|
||||
|
||||
set_rule(self.multiworld.get_location(("Kronos Cyan Emerald"), self.player), lambda state: state.has("Kronos Vault Key", self.player, 13)
|
||||
and state.has("Kronos Memory Treasure", self.player, 6) and state.has("Progressive Chaos Emerald", self.player, 4))
|
||||
|
||||
set_rule(self.multiworld.get_location(("Kronos White Emerald"), self.player), lambda state: state.has("Kronos Vault Key", self.player, 20))
|
||||
if(self.options.goal > 0):
|
||||
set_rule(self.multiworld.get_location(("Ares Blue Emerald"), self.player), lambda state: state.has("Ares Vault Key", self.player, 7))
|
||||
set_rule(self.multiworld.get_location(("Ares Red Emerald"), self.player), lambda state: state.has("Ares Vault Key", self.player, 14))
|
||||
set_rule(self.multiworld.get_location(("Ares Green Emerald"), self.player), lambda state: state.has("Ares Vault Key", self.player, 14) and state.has("Progressive Chaos Emerald", self.player, 8))
|
||||
set_rule(self.multiworld.get_location(("Ares Yellow Emerald"), self.player), lambda state: state.has("Ares Vault Key", self.player, 20))
|
||||
set_rule(self.multiworld.get_location(("Ares Cyan Emerald"), self.player), lambda state: state.has("Ares Vault Key", self.player, 20) and state.has("Progressive Chaos Emerald", self.player, 10))
|
||||
set_rule(self.multiworld.get_location(("Ares White Emerald"), self.player), lambda state: state.has("Ares Vault Key", self.player, 24))
|
||||
|
||||
if(self.options.goal > 1):
|
||||
set_rule(self.multiworld.get_location(("Chaos Blue Emerald"), self.player), lambda state: state.has("Chaos Vault Key", self.player, 7))
|
||||
set_rule(self.multiworld.get_location(("Chaos Red Emerald"), self.player), lambda state: state.has("Chaos Vault Key", self.player, 14))
|
||||
set_rule(self.multiworld.get_location(("Chaos Green Emerald"), self.player), lambda state: state.has("Chaos Vault Key", self.player, 14) and state.has("Progressive Chaos Emerald", self.player, 14))
|
||||
set_rule(self.multiworld.get_location(("Chaos Yellow Emerald"), self.player), lambda state: state.has("Chaos Vault Key", self.player, 20))
|
||||
set_rule(self.multiworld.get_location(("Chaos Cyan Emerald"), self.player), lambda state: state.has("Chaos Vault Key", self.player, 20) and state.has("Progressive Chaos Emerald", self.player, 16))
|
||||
set_rule(self.multiworld.get_location(("Chaos White Emerald"), self.player), lambda state: state.has("Chaos Vault Key", self.player, 25))
|
||||
if(self.options.goal > 2):
|
||||
set_rule(self.multiworld.get_location(("Ouranos Blue Emerald"), self.player), lambda state: state.has("Ouranos Vault Key", self.player, 3))
|
||||
set_rule(self.multiworld.get_location(("Ouranos Red Emerald"), self.player), lambda state: state.has("Ouranos Vault Key", self.player, 9))
|
||||
set_rule(self.multiworld.get_location(("Ouranos Green Emerald"), self.player), lambda state: state.has("Ouranos Vault Key", self.player, 16))
|
||||
set_rule(self.multiworld.get_location(("Ouranos Yellow Emerald"), self.player), lambda state: state.has("Ouranos Vault Key", self.player, 23))
|
||||
set_rule(self.multiworld.get_location(("Ouranos Cyan Emerald"), self.player), lambda state: state.has("Ouranos Vault Key", self.player, 30))
|
||||
set_rule(self.multiworld.get_location(("Ouranos White Emerald"), self.player), lambda state: state.has("Ouranos Vault Key", self.player, 33))
|
||||
if(self.options.goal > 0):
|
||||
set_rule(self.multiworld.get_entrance("Ares Entrance", self.player), lambda state: state.has("Progressive Chaos Emerald", self.player, 6)
|
||||
and state.has("Kronos Memory Treasure", self.player, 9) and state.has("Kronos Vault Key", self.player, 20)
|
||||
and state.has("Stomp Attack", self.player) and state.has("Parry", self.player))
|
||||
|
||||
if(self.options.goal > 1):
|
||||
set_rule(self.multiworld.get_entrance("Chaos Entrance", self.player),
|
||||
lambda state: state.has("Progressive Chaos Emerald", self.player, 12) and state.has("Ares Memory Treasure", self.player, 32) and state.has("Ares Vault Key", self.player, 24))
|
||||
|
||||
if(self.options.goal > 2):
|
||||
set_rule(self.multiworld.get_entrance("Ouranos Entrance", self.player),
|
||||
lambda state: state.has("Progressive Chaos Emerald", self.player, 18) and state.has("Chaos Memory Treasure", self.player, 20) and state.has("Chaos Vault Key", self.player, 25))
|
||||
|
||||
if(self.options.goal == 0):
|
||||
set_rule(self.multiworld.get_location("Defeat Giganto", self.player), lambda state: state.has("Progressive Chaos Emerald", self.player, 6)
|
||||
and state.has("Kronos Memory Treasure", self.player, 9) and state.has("Kronos Vault Key", self.player, 20)
|
||||
and state.has("Stomp Attack", self.player) and state.has("Parry", self.player))
|
||||
if(self.options.goal == 1):
|
||||
set_rule(self.multiworld.get_location("Defeat Wyvern", self.player), lambda state: state.has("Progressive Chaos Emerald", self.player, 12)
|
||||
and state.has("Ares Memory Treasure", self.player, 32) and state.has("Ares Vault Key", self.player, 24))
|
||||
if(self.options.goal == 2):
|
||||
set_rule(self.multiworld.get_location("Defeat Knight", self.player),
|
||||
lambda state: state.has("Progressive Chaos Emerald", self.player, 18) and state.has("Chaos Memory Treasure", self.player, 20) and state.has("Chaos Vault Key", self.player, 25))
|
||||
if(self.options.goal == 3):
|
||||
set_rule(self.multiworld.get_location("Defeat Supreme", self.player),
|
||||
lambda state: state.has("Progressive Chaos Emerald", self.player, 24) and state.has("Ouranos Memory Treasure", self.player, 24) and state.has("Ouranos Vault Key", self.player, 33))
|
||||
|
||||
def create_items(self) -> None:
|
||||
numItems = 0
|
||||
for name, quantity in kronos_amount.items():
|
||||
for i in range(quantity):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
numItems += 1
|
||||
if self.options.goal > 0:
|
||||
for name, quantity in ares_amount.items():
|
||||
for i in range(quantity):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
numItems += 1
|
||||
if self.options.goal > 1:
|
||||
for name, quantity in chaos_amount.items():
|
||||
for i in range(quantity):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
numItems += 1
|
||||
if self.options.goal > 2:
|
||||
for name, quantity in ouranos_amount.items():
|
||||
for i in range(quantity):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
numItems += 1
|
||||
filler = len(self.multiworld.get_locations(self.player)) - numItems
|
||||
for _ in range(filler):
|
||||
name = self.random.choices(list(fillers.keys()), weights = list(fillers.values()))[0]
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_data = item_list[name]
|
||||
item = SonicFrontiersItem(name, item_data.item_class, item_data.id, self.player)
|
||||
return item
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"death_link": self.options.death_link.value,
|
||||
"goal": self.options.goal.value,
|
||||
"memory_token_sanity": self.options.memory_token_sanity.value,
|
||||
"cyberspace_times": self.options.cyberspace_times.value,
|
||||
"music_notes": self.options.music_notes.value,
|
||||
"challenge_kocos": self.options.challenge_kocos.value,
|
||||
"purple_coin_sanity": self.options.purple_coin_sanity.value,
|
||||
"koco_sanity": self.options.koco_sanity.value,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
stop making me do this
|
||||
@@ -0,0 +1,157 @@
|
||||
from typing import NamedTuple
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
class SonicFrontiersItem(Item):
|
||||
game: str = "Sonic Frontiers"
|
||||
|
||||
class SonicFrontiersItemData(NamedTuple):
|
||||
id: int
|
||||
item_class: ItemClassification = ItemClassification.progression
|
||||
|
||||
offset: int = 101000
|
||||
item_list = {
|
||||
"Kronos Memory Token": SonicFrontiersItemData(offset, ItemClassification.filler),
|
||||
"Kronos Portal Gear": SonicFrontiersItemData(offset + 1, ItemClassification.filler),
|
||||
"Kronos Vault Key": SonicFrontiersItemData(offset + 2, ItemClassification.progression),
|
||||
"Ares Memory Token": SonicFrontiersItemData(offset + 3, ItemClassification.filler),
|
||||
"Ares Portal Gear": SonicFrontiersItemData(offset + 4, ItemClassification.filler),
|
||||
"Ares Vault Key": SonicFrontiersItemData(offset + 5, ItemClassification.progression),
|
||||
"Chaos Memory Token": SonicFrontiersItemData(offset + 6, ItemClassification.filler),
|
||||
"Chaos Portal Gear": SonicFrontiersItemData(offset + 7, ItemClassification.filler),
|
||||
"Chaos Vault Key": SonicFrontiersItemData(offset + 8, ItemClassification.progression),
|
||||
"Ouranos Memory Token": SonicFrontiersItemData(offset + 9, ItemClassification.filler),
|
||||
"Ouranos Portal Gear": SonicFrontiersItemData(offset + 10, ItemClassification.filler),
|
||||
"Ouranos Vault Key": SonicFrontiersItemData(offset + 11, ItemClassification.progression),
|
||||
"Progressive Chaos Emerald": SonicFrontiersItemData(offset + 12, ItemClassification.progression),
|
||||
"Kronos Memory Treasure": SonicFrontiersItemData(offset + 36, ItemClassification.progression),
|
||||
"Ares Memory Treasure": SonicFrontiersItemData(offset + 37, ItemClassification.progression),
|
||||
"Chaos Memory Treasure": SonicFrontiersItemData(offset + 38, ItemClassification.progression),
|
||||
"Ouranos Memory Treasure": SonicFrontiersItemData(offset + 39, ItemClassification.progression),
|
||||
"Red Power Seed": SonicFrontiersItemData(offset + 40, ItemClassification.filler),
|
||||
"Blue Power Seed": SonicFrontiersItemData(offset + 41, ItemClassification.filler),
|
||||
"Nothing!": SonicFrontiersItemData(offset + 42, ItemClassification.filler),
|
||||
"Phantom Rush": SonicFrontiersItemData(offset + 43, ItemClassification.useful),
|
||||
"Air Trick":SonicFrontiersItemData(offset + 44, ItemClassification.useful),
|
||||
"Stomp Attack": SonicFrontiersItemData(offset + 45, ItemClassification.progression),
|
||||
"Quick Cyloop": SonicFrontiersItemData(offset + 46, ItemClassification.useful),
|
||||
"Spin Dash": SonicFrontiersItemData(offset + 47, ItemClassification.useful),
|
||||
"Sonic Boom": SonicFrontiersItemData(offset + 48, ItemClassification.useful),
|
||||
"Parry": SonicFrontiersItemData(offset + 49, ItemClassification.progression),
|
||||
"Homing Shot": SonicFrontiersItemData(offset + 50, ItemClassification.useful),
|
||||
"Spin Slash": SonicFrontiersItemData(offset + 51, ItemClassification.useful),
|
||||
"Recovery Smash": SonicFrontiersItemData(offset +52, ItemClassification.useful),
|
||||
"Cyclone Kick": SonicFrontiersItemData(offset + 53, ItemClassification.useful),
|
||||
"Cross Slash": SonicFrontiersItemData(offset + 54, ItemClassification.useful),
|
||||
"Grand Slam": SonicFrontiersItemData(offset + 55, ItemClassification.useful),
|
||||
"1-2 Unlocked": SonicFrontiersItemData(offset + 56,ItemClassification.progression),
|
||||
"1-3 Unlocked": SonicFrontiersItemData(offset + 57,ItemClassification.progression),
|
||||
"1-4 Unlocked": SonicFrontiersItemData(offset + 58,ItemClassification.progression),
|
||||
"1-5 Unlocked": SonicFrontiersItemData(offset + 59,ItemClassification.progression),
|
||||
"1-6 Unlocked": SonicFrontiersItemData(offset + 60,ItemClassification.progression),
|
||||
"1-7 Unlocked": SonicFrontiersItemData(offset + 61,ItemClassification.progression),
|
||||
"2-1 Unlocked": SonicFrontiersItemData(offset + 62,ItemClassification.progression),
|
||||
"2-2 Unlocked": SonicFrontiersItemData(offset + 63,ItemClassification.progression),
|
||||
"2-3 Unlocked": SonicFrontiersItemData(offset + 64,ItemClassification.progression),
|
||||
"2-4 Unlocked": SonicFrontiersItemData(offset + 65,ItemClassification.progression),
|
||||
"2-5 Unlocked": SonicFrontiersItemData(offset + 66,ItemClassification.progression),
|
||||
"2-6 Unlocked": SonicFrontiersItemData(offset + 67,ItemClassification.progression),
|
||||
"2-7 Unlocked": SonicFrontiersItemData(offset + 68,ItemClassification.progression),
|
||||
"3-1 Unlocked": SonicFrontiersItemData(offset + 69,ItemClassification.progression),
|
||||
"3-2 Unlocked": SonicFrontiersItemData(offset + 70,ItemClassification.progression),
|
||||
"3-3 Unlocked": SonicFrontiersItemData(offset + 71,ItemClassification.progression),
|
||||
"3-4 Unlocked": SonicFrontiersItemData(offset + 72,ItemClassification.progression),
|
||||
"3-5 Unlocked": SonicFrontiersItemData(offset + 73,ItemClassification.progression),
|
||||
"3-6 Unlocked": SonicFrontiersItemData(offset + 74,ItemClassification.progression),
|
||||
"3-7 Unlocked": SonicFrontiersItemData(offset + 75,ItemClassification.progression),
|
||||
"4-1 Unlocked": SonicFrontiersItemData(offset + 76,ItemClassification.progression),
|
||||
"4-2 Unlocked": SonicFrontiersItemData(offset + 78,ItemClassification.progression),
|
||||
"4-3 Unlocked": SonicFrontiersItemData(offset + 79,ItemClassification.progression),
|
||||
"4-4 Unlocked": SonicFrontiersItemData(offset + 80,ItemClassification.progression),
|
||||
"4-5 Unlocked": SonicFrontiersItemData(offset + 81,ItemClassification.progression),
|
||||
"4-6 Unlocked": SonicFrontiersItemData(offset + 82,ItemClassification.progression),
|
||||
"4-7 Unlocked": SonicFrontiersItemData(offset + 83,ItemClassification.progression),
|
||||
"4-8 Unlocked": SonicFrontiersItemData(offset + 84,ItemClassification.progression),
|
||||
"4-9 Unlocked": SonicFrontiersItemData(offset + 85,ItemClassification.progression),
|
||||
"Victory": SonicFrontiersItemData(offset + 86,ItemClassification.progression),
|
||||
}
|
||||
kronos_amount = {
|
||||
"Kronos Memory Treasure": 9,
|
||||
"Kronos Vault Key": 20,
|
||||
"Progressive Chaos Emerald": 6,
|
||||
"1-2 Unlocked" : 1,
|
||||
"1-3 Unlocked" : 1,
|
||||
"1-4 Unlocked" : 1,
|
||||
"1-5 Unlocked" : 1,
|
||||
"1-6 Unlocked" : 1,
|
||||
"1-7 Unlocked" : 1,
|
||||
"Phantom Rush": 1,
|
||||
"Air Trick": 1,
|
||||
"Stomp Attack": 1,
|
||||
"Quick Cyloop": 1,
|
||||
"Spin Dash": 1,
|
||||
"Sonic Boom": 1,
|
||||
"Parry": 1,
|
||||
"Homing Shot": 1,
|
||||
"Spin Slash": 1,
|
||||
"Recovery Smash": 1,
|
||||
"Cyclone Kick": 1,
|
||||
"Cross Slash": 1,
|
||||
"Grand Slam": 1,
|
||||
}
|
||||
ares_amount = {
|
||||
"Ares Memory Treasure": 32,
|
||||
"Ares Vault Key": 24,
|
||||
"Progressive Chaos Emerald": 6,
|
||||
"2-1 Unlocked" : 1,
|
||||
"2-2 Unlocked" : 1,
|
||||
"2-3 Unlocked" : 1,
|
||||
"2-4 Unlocked" : 1,
|
||||
"2-5 Unlocked" : 1,
|
||||
"2-6 Unlocked" : 1,
|
||||
"2-7 Unlocked" : 1,
|
||||
}
|
||||
chaos_amount = {
|
||||
"Chaos Memory Treasure": 20,
|
||||
"Chaos Vault Key": 25,
|
||||
"Progressive Chaos Emerald": 6,
|
||||
"3-1 Unlocked" : 1,
|
||||
"3-2 Unlocked" : 1,
|
||||
"3-3 Unlocked" : 1,
|
||||
"3-4 Unlocked" : 1,
|
||||
"3-5 Unlocked" : 1,
|
||||
"3-6 Unlocked" : 1,
|
||||
"3-7 Unlocked" : 1,
|
||||
|
||||
}
|
||||
|
||||
ouranos_amount = {
|
||||
"4-1 Unlocked" : 1,
|
||||
"4-2 Unlocked" : 1,
|
||||
"4-3 Unlocked" : 1,
|
||||
"4-4 Unlocked" : 1,
|
||||
"4-5 Unlocked" : 1,
|
||||
"4-6 Unlocked" : 1,
|
||||
"4-7 Unlocked" : 1,
|
||||
"4-8 Unlocked" : 1,
|
||||
"4-9 Unlocked" : 1,
|
||||
"Ouranos Memory Treasure": 26,
|
||||
"Ouranos Vault Key": 33,
|
||||
"Progressive Chaos Emerald": 6,
|
||||
}
|
||||
fillers = {
|
||||
"Red Power Seed": 15,
|
||||
"Blue Power Seed": 15,
|
||||
"Nothing!": 10,
|
||||
"Kronos Portal Gear": 5,
|
||||
"Kronos Vault Key": 5,
|
||||
"Ares Portal Gear": 4,
|
||||
"Ares Vault Key": 4,
|
||||
"Chaos Vault Key": 3,
|
||||
"Chaos Portal Gear": 3,
|
||||
"Ouranos Portal Gear": 2,
|
||||
"Ouranos Vault Key": 2,
|
||||
}
|
||||
#
|
||||
#traps = {
|
||||
# "Water",
|
||||
# "Timescale";
|
||||
@@ -0,0 +1,344 @@
|
||||
from BaseClasses import Location
|
||||
import typing
|
||||
class AdvData(typing.NamedTuple):
|
||||
id: typing.Optional[int]
|
||||
|
||||
class SonicFrontiersAdvancement(Location):
|
||||
game: str = "Sonic Frontiers"
|
||||
def create_locations():
|
||||
counter = 0
|
||||
kronosItems = [
|
||||
"Kronos Memory Token", "Kronos Memory Token Treasure", "Kronos Portal Gear", "Kronos Vault Key" "Kronos Map Challenge"
|
||||
]
|
||||
aresItems = [
|
||||
"Ares Memory Token", "Ares Memory Token Treasure", "Ares Portal Gear", "Ares Vault Key", "Ares Map Challenge"
|
||||
]
|
||||
chaosItems = [
|
||||
"Chaos Memory Token", "Chaos Memory Token Treasure", "Chaos Portal Gear", "Chaos Vault Key", "Chaos Map Challenge"
|
||||
]
|
||||
ouranosItems = [
|
||||
"Ouranos Memory Token", "Ouranos Portal Gear", "Ouranos Vault Key", "Ouranos Map Challenge"
|
||||
]
|
||||
kronosStages = [
|
||||
"1-1",
|
||||
"1-2",
|
||||
"1-3",
|
||||
"1-4",
|
||||
"1-5",
|
||||
"1-6",
|
||||
"1-7",
|
||||
]
|
||||
aresStages = [
|
||||
"2-1",
|
||||
"2-2",
|
||||
"2-3",
|
||||
"2-4",
|
||||
"2-5",
|
||||
"2-6",
|
||||
"2-7",
|
||||
]
|
||||
chaosStages = [
|
||||
"3-1",
|
||||
"3-2",
|
||||
"3-3",
|
||||
"3-4",
|
||||
"3-5",
|
||||
"3-6",
|
||||
"3-7",
|
||||
]
|
||||
ouranosStages = [
|
||||
"4-1",
|
||||
"4-2",
|
||||
"4-3",
|
||||
"4-4",
|
||||
"4-5",
|
||||
"4-6",
|
||||
"4-7",
|
||||
"4-8",
|
||||
"4-9",
|
||||
]
|
||||
skills = [
|
||||
"Phantom Rush", "Air Trick", "Stomp Attack",
|
||||
"Quick Cyloop", "Loop Kick", "Sonic Boom",
|
||||
"Wild Rush", "Homing Shot", "Spin Slash",
|
||||
"Recovery Smash", "Cyclone Kick","Cross Slash",
|
||||
"Grand Slam"
|
||||
]
|
||||
kronosEmeralds = [
|
||||
"Kronos Blue Emerald",
|
||||
"Kronos Red Emerald",
|
||||
"Kronos Green Emerald",
|
||||
"Kronos Yellow Emerald",
|
||||
"Kronos Cyan Emerald",
|
||||
"Kronos White Emerald"
|
||||
]
|
||||
aresEmeralds = [
|
||||
"Ares Blue Emerald",
|
||||
"Ares Red Emerald",
|
||||
"Ares Green Emerald",
|
||||
"Ares Yellow Emerald",
|
||||
"Ares Cyan Emerald",
|
||||
"Ares White Emerald"
|
||||
]
|
||||
chaosEmeralds = [
|
||||
"Chaos Blue Emerald",
|
||||
"Chaos Red Emerald",
|
||||
"Chaos Green Emerald",
|
||||
"Chaos Yellow Emerald",
|
||||
"Chaos Cyan Emerald",
|
||||
"Chaos White Emerald"
|
||||
]
|
||||
ouranosEmeralds = [
|
||||
"Ouranos Blue Emerald",
|
||||
"Ouranos Red Emerald",
|
||||
"Ouranos Green Emerald",
|
||||
"Ouranos Yellow Emerald",
|
||||
"Ouranos Cyan Emerald",
|
||||
"Ouranos White Emerald"
|
||||
]
|
||||
sonicItems = [
|
||||
"Skill Points (200)",
|
||||
"Red Power Seed",
|
||||
"Blue Power Seed",
|
||||
"Kocos (20)"
|
||||
]
|
||||
|
||||
##
|
||||
## Kronos
|
||||
##
|
||||
|
||||
#10000
|
||||
counter = 0
|
||||
for i in range(91):
|
||||
kronosMemoryTokenSet[f"Kronos Memory Token {counter+1}"] = AdvData(kronosOff + counter)
|
||||
counter += 1
|
||||
#10091
|
||||
counter = 0
|
||||
for i in range(9):
|
||||
kronosRegion[f"Kronos Memory Treasure {counter+1}"] = AdvData(kronosOff + tokenDigOffset + counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for i in range(17):
|
||||
kronosRegion[f"Kronos Portal Gear {i+1}"] = AdvData(kronosOff + gearOffset + counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for i in range(5):
|
||||
kronosRegion[f"Kronos Vault Key {1+i}"] = AdvData(kronosOff+keyOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
|
||||
for emerald in kronosEmeralds:
|
||||
kronosRegion[f"{emerald}"] = AdvData(kronosOff+counter+emeraldOffset)
|
||||
counter += 1
|
||||
counter = 0
|
||||
mapCounter = 1
|
||||
for i in range(25):
|
||||
if(mapCounter< 10):
|
||||
kronosRegion[f"Map Challenge M-00{mapCounter}"] = AdvData(kronosOff+counter+mapChallengeOffset)
|
||||
counter += 1
|
||||
mapCounter += 1
|
||||
else:
|
||||
kronosRegion[f"Map Challenge M-0{mapCounter}"] = AdvData(kronosOff+counter+mapChallengeOffset)
|
||||
counter += 1
|
||||
mapCounter += 1
|
||||
for i in range(8):
|
||||
kronosNewKocoSet[f"Kronos Challenge Koco {i+1}"] = AdvData(kronosOff+i+newKocoOffset)
|
||||
for i in range(13):
|
||||
kronosMusicSet[f"Kronos Music Note {i+1}"] = AdvData(kronosOff+i+musicOffset)
|
||||
for i in range(65):
|
||||
kronosPurpleSet[f"Kronos Purple Coin {i+1}"] = AdvData(kronosOff + i + purpleCoinOffset)
|
||||
for i in range(274):
|
||||
kronosKocoSet[f"Kronos Koco {i+1}"] = AdvData(kronosOff + i + kocoOffset)
|
||||
|
||||
kronosRegion["Defeat Giganto"] = AdvData(kronosOff+bossOffset)
|
||||
|
||||
##
|
||||
## Ares
|
||||
##
|
||||
counter = 0
|
||||
|
||||
for i in range(285):
|
||||
aresMemoryTokenSet[f"Ares Memory Token {i+1}"] = AdvData(aresOff + counter)
|
||||
counter += 1
|
||||
for i in range(26):
|
||||
aresRegion[f"Ares Memory Treasure {i+1}"] = AdvData(aresOff + tokenDigOffset + i)
|
||||
counter = 0
|
||||
for i in range(11):
|
||||
aresRegion[f"Ares Portal Gear {i+1}"] = AdvData(aresOff + gearOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for i in range(7):
|
||||
aresRegion[f"Ares Vault Key {1+i}"] = AdvData(aresOff+keyOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for emerald in aresEmeralds:
|
||||
aresRegion[f"{emerald}"] = AdvData(aresOff+emeraldOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for i in range(29):
|
||||
aresRegion[f"Map Challenge M-0{mapCounter}"] = AdvData(aresOff+mapChallengeOffset+counter)
|
||||
counter += 1
|
||||
mapCounter += 1
|
||||
for i in range(8):
|
||||
aresNewKocoSet[f"Ares Challenge Koco {i+1}"] = AdvData(aresOff+i+newKocoOffset)
|
||||
for i in range(13):
|
||||
aresMusicSet[f"Ares Music Note {i+1}"] = AdvData(aresOff+i+musicOffset)
|
||||
for i in range(148):
|
||||
aresPurpleSet[f"Ares Purple Coin {i+1}"] = AdvData(aresOff + i + purpleCoinOffset)
|
||||
for i in range(360):
|
||||
aresKocoSet[f"Ares Koco {i+1}"] = AdvData(aresOff + i + kocoOffset)
|
||||
aresRegion["Defeat Wyvern"] = AdvData(aresOff+bossOffset)
|
||||
|
||||
##
|
||||
## Chaos
|
||||
##
|
||||
|
||||
for i in range(252):
|
||||
chaosMemoryTokenSet[f"Chaos Memory Token {counter+1}"] = AdvData(chaosOff + i)
|
||||
for i in range(20):
|
||||
chaosRegion[f"Chaos Memory Treasure {i+1}"] = AdvData(chaosOff + tokenDigOffset+i)
|
||||
counter = 0
|
||||
for emerald in chaosEmeralds:
|
||||
chaosRegion[f"{emerald}"] = AdvData(chaosOff+emeraldOffset+counter)
|
||||
counter += 1
|
||||
for i in range(8):
|
||||
chaosRegion[f"Chaos Vault Key {1+i}"] = AdvData(chaosOff+keyOffset+i)
|
||||
for i in range(13):
|
||||
chaosRegion[f"Chaos Portal Gear {i+1}"] = AdvData(chaosOff +gearOffset+ i)
|
||||
for i in range(24):
|
||||
chaosRegion[f"Map Challenge M-0{mapCounter}"] = AdvData(chaosOff+mapChallengeOffset+i)
|
||||
mapCounter += 1
|
||||
counter = 0
|
||||
|
||||
for i in range(8):
|
||||
chaosNewKocoSet[f"Chaos Challenge Koco {i+1}"] = AdvData(chaosOff+i+newKocoOffset)
|
||||
for i in range(13):
|
||||
chaosMusicSet[f"Chaos Music Note {i+1}"] = AdvData(chaosOff+i+musicOffset)
|
||||
|
||||
for i in range(188):
|
||||
chaosPurpleSet[f"Chaos Purple Coin {i+1}"] = AdvData(chaosOff+purpleCoinOffset+i)
|
||||
for i in range(356):
|
||||
chaosKocoSet[f"Chaos Koco {i+1}"] = AdvData(chaosOff+kocoOffset+i)
|
||||
chaosRegion["Defeat Knight"] = AdvData(chaosOff+bossOffset)
|
||||
##
|
||||
## Ouranos
|
||||
##
|
||||
counter = 0
|
||||
|
||||
for i in range(200):
|
||||
ouranosMemoryTokenSet[f"Ouranos Memory Token {counter+1}"] = AdvData(ouranosOff + counter)
|
||||
counter += 1
|
||||
for i in range(16):
|
||||
ouranosRegion[f"Ouranos Memory Treasure {i+1}"] = AdvData(ouranosOff + tokenDigOffset+i)
|
||||
counter = 0
|
||||
for i in range(21):
|
||||
ouranosRegion[f"Ouranos Portal Gear {i+1}"] = AdvData(ouranosOff + gearOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for i in range(8):
|
||||
ouranosRegion[f"Ouranos Vault Key {1+i}"] = AdvData(ouranosOff+keyOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for emerald in ouranosEmeralds:
|
||||
ouranosRegion[f"{emerald}"] = AdvData(ouranosOff+emeraldOffset+counter)
|
||||
counter += 1
|
||||
counter = 0
|
||||
for i in range(27):
|
||||
if(mapCounter > 100):
|
||||
ouranosRegion[f"Map Challenge M-{mapCounter}"] = AdvData(ouranosOff+mapChallengeOffset+counter)
|
||||
counter += 1
|
||||
mapCounter += 1
|
||||
else:
|
||||
ouranosRegion[f"Map Challenge M-0{mapCounter}"] = AdvData(ouranosOff+mapChallengeOffset+counter)
|
||||
counter += 1
|
||||
mapCounter += 1
|
||||
counter = 0
|
||||
for stage in kronosStages:
|
||||
for i in range(7):
|
||||
kronosRegion[f"{stage} All Missions ({i+1})"] = AdvData(cyberspaceOffset + counter)
|
||||
counter += 1
|
||||
for stage in aresStages:
|
||||
for i in range(7):
|
||||
aresRegion[f"{stage} All Missions ({i+1})"] = AdvData(cyberspaceOffset + counter)
|
||||
counter += 1
|
||||
for stage in chaosStages:
|
||||
for i in range(7):
|
||||
chaosRegion[f"{stage} All Missions ({i+1})"] = AdvData(cyberspaceOffset+counter)
|
||||
counter += 1
|
||||
for stage in ouranosStages:
|
||||
for i in range(7):
|
||||
ouranosRegion[f"{stage} All Missions ({i+1})"] = AdvData(cyberspaceOffset+ counter)
|
||||
counter += 1
|
||||
|
||||
for i in range(8):
|
||||
ouranosNewKocoSet[f"Ouranos Challenge Koco {i+1}"] = AdvData(ouranosOff+i+newKocoOffset)
|
||||
for i in range(13):
|
||||
ouranosMusicSet[f"Ouranos Music Note {i+1}"] = AdvData(ouranosOff+i+musicOffset)
|
||||
for i in range(317):
|
||||
ouranosPurpleSet[f"Ouranos Purple Coin {i+1}"] = AdvData(ouranosOff+purpleCoinOffset+i)
|
||||
for i in range(358):
|
||||
ouranosKocoSet[f"Ouranos Koco {i+1}"] = AdvData(ouranosOff+kocoOffset+i)
|
||||
ouranosRegion["Defeat Supreme"] = AdvData(ouranosOff+bossOffset)
|
||||
|
||||
|
||||
|
||||
|
||||
##Essentially add all of these into a region. How do I add them? idfk
|
||||
kronosRegion = {}
|
||||
aresRegion = {}
|
||||
chaosRegion = {}
|
||||
ouranosRegion = {}
|
||||
victoryRegion = {}
|
||||
|
||||
kronosMemoryTokenSet = {}
|
||||
aresMemoryTokenSet = {}
|
||||
chaosMemoryTokenSet = {}
|
||||
ouranosMemoryTokenSet = {}
|
||||
|
||||
kronosPurpleSet = {}
|
||||
aresPurpleSet = {}
|
||||
chaosPurpleSet = {}
|
||||
ouranosPurpleSet = {}
|
||||
|
||||
kronosKocoSet = {}
|
||||
aresKocoSet = {}
|
||||
chaosKocoSet = {}
|
||||
ouranosKocoSet = {}
|
||||
|
||||
kronosNewKocoSet = {}
|
||||
aresNewKocoSet = {}
|
||||
chaosNewKocoSet = {}
|
||||
ouranosNewKocoSet = {}
|
||||
|
||||
kronosMusicSet = {}
|
||||
aresMusicSet = {}
|
||||
chaosMusicSet = {}
|
||||
ouranosMusicSet = {}
|
||||
|
||||
kronosOff: int = 10000
|
||||
aresOff: int = 20000
|
||||
chaosOff: int = 30000
|
||||
ouranosOff: int = 40000
|
||||
cyberspaceOffset: int = 50000
|
||||
|
||||
tokenDigOffset: int = 500
|
||||
emeraldOffset: int = 1000
|
||||
musicOffset: int = 1500
|
||||
gearOffset: int = 2000
|
||||
newKocoOffset: int = 2500
|
||||
keyOffset: int = 3000
|
||||
purpleCoinOffset: int = 4000 #10000 + i + 4000
|
||||
mapChallengeOffset: int = 5000
|
||||
kocoOffset: int = 6000
|
||||
bossOffset: int = 7000
|
||||
ringOffset: int = 100000
|
||||
|
||||
|
||||
create_locations()
|
||||
|
||||
kronosItems = kronosRegion | kronosMusicSet | kronosKocoSet | kronosMemoryTokenSet | kronosNewKocoSet | kronosPurpleSet
|
||||
aresItems = aresRegion | aresMusicSet | aresKocoSet | aresMemoryTokenSet | aresNewKocoSet | aresPurpleSet
|
||||
chaosItems = chaosRegion | chaosMusicSet | chaosKocoSet | chaosMemoryTokenSet | chaosNewKocoSet | chaosPurpleSet
|
||||
ouranosItems = ouranosRegion | ouranosMusicSet | ouranosKocoSet | ouranosMemoryTokenSet | ouranosNewKocoSet | ouranosPurpleSet
|
||||
|
||||
all_items = kronosItems | aresItems | chaosItems | ouranosItems
|
||||
@@ -0,0 +1,71 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCommonOptions, ExcludeLocations # , OptionGroup
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
"""
|
||||
Which Titan to defeat in order to complete the randomizer
|
||||
|
||||
Note: Only Giganto/Kronos Island for this release
|
||||
"""
|
||||
display_name = "Goal"
|
||||
option_defeat_giganto = 0
|
||||
#option_defeat_wyvern = 1
|
||||
#option_defeat_knight = 2
|
||||
#option_defeat_supreme = 3
|
||||
default = 0
|
||||
class MemoryTokenSanity(Toggle):
|
||||
"""
|
||||
Set whether All Memory Tokens should be locations
|
||||
"""
|
||||
display_name = "Memory Token Sanity"
|
||||
default = 0
|
||||
class MapChallenges(DefaultOnToggle):
|
||||
display_name = "Map Challenge Sanity"
|
||||
class HarderCyberspaceTimes(Toggle):
|
||||
"""
|
||||
This makes all Cyberspace stages have a harder S-Rank requirement.
|
||||
|
||||
Note: This is meant for speedrunners, do not enable this unless you're up for a challenge.
|
||||
"""
|
||||
display_name = "Harder Cyberspace Challenge Times"
|
||||
default = 0
|
||||
|
||||
class MusicNotes(Toggle):
|
||||
"""
|
||||
Set whether Music Notes should be locations
|
||||
"""
|
||||
display_name = "Music Notes"
|
||||
default = 0
|
||||
class ChallengeKocos(Toggle):
|
||||
"""
|
||||
Set whether Challenge Kocos should be locations
|
||||
"""
|
||||
display_name = "Challenge Kocos"
|
||||
default = 0
|
||||
class CyberspaceStages(Toggle):
|
||||
display_name = "Cyberspace Stages Missions"
|
||||
default = 0
|
||||
class PurpleCoinSanity(Toggle):
|
||||
"""
|
||||
Set whether All Purple Coins should be locations
|
||||
"""
|
||||
display_name = "Purple Coin Sanity"
|
||||
default = 0
|
||||
class KocoSanity(Toggle):
|
||||
"""
|
||||
Set whether All Kocos should be locations
|
||||
"""
|
||||
display_name = "Koco Sanity"
|
||||
default = 0
|
||||
|
||||
@dataclass
|
||||
class SonicFrontiersOptions(PerGameCommonOptions):
|
||||
goal: Goal
|
||||
death_link: DeathLink
|
||||
memory_token_sanity: MemoryTokenSanity
|
||||
cyberspace_times: HarderCyberspaceTimes
|
||||
music_notes: MusicNotes
|
||||
challenge_kocos: ChallengeKocos
|
||||
purple_coin_sanity: PurpleCoinSanity
|
||||
koco_sanity: KocoSanity
|
||||
+5
-16
@@ -11,7 +11,7 @@ import json
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import List, Sequence
|
||||
from zipfile import ZipFile, BadZipFile
|
||||
from zipfile import BadZipFile
|
||||
|
||||
from NetUtils import DataPackage
|
||||
from Utils import local_path, user_path, Version, version_tuple, tuplize_version, messagebox
|
||||
@@ -33,7 +33,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
failed_world_loads: dict[str, str] = {}
|
||||
failed_world_loads: List[str] = []
|
||||
|
||||
|
||||
@dataclasses.dataclass(order=True)
|
||||
@@ -68,9 +68,8 @@ class WorldSource:
|
||||
print(f"Could not load world {self}:", file=file_like)
|
||||
traceback.print_exc(file=file_like)
|
||||
file_like.seek(0)
|
||||
reason = file_like.read()
|
||||
logging.exception(reason)
|
||||
failed_world_loads[os.path.basename(self.path).rsplit(".", 1)[0]] = reason
|
||||
logging.exception(file_like.read())
|
||||
failed_world_loads.append(os.path.basename(self.path).rsplit(".", 1)[0])
|
||||
return False
|
||||
|
||||
|
||||
@@ -119,7 +118,6 @@ for world_source in world_sources:
|
||||
game = manifest.get("game")
|
||||
if game in AutoWorldRegister.world_types:
|
||||
AutoWorldRegister.world_types[game].world_version = tuplize_version(manifest.get("world_version", "0.0.0"))
|
||||
AutoWorldRegister.world_types[game].manifest = manifest
|
||||
|
||||
if apworlds:
|
||||
# encapsulation for namespace / gc purposes
|
||||
@@ -130,7 +128,7 @@ if apworlds:
|
||||
|
||||
def fail_world(game_name: str, reason: str, add_as_failed_to_load: bool = True) -> None:
|
||||
if add_as_failed_to_load:
|
||||
failed_world_loads[game_name] = reason
|
||||
failed_world_loads.append(game_name)
|
||||
logging.warning(reason)
|
||||
|
||||
for apworld_source in apworlds:
|
||||
@@ -201,15 +199,6 @@ if apworlds:
|
||||
# world could fail to load at this point
|
||||
if apworld.world_version:
|
||||
AutoWorldRegister.world_types[apworld.game].world_version = apworld.world_version
|
||||
|
||||
assert apworld.path
|
||||
with ZipFile(apworld.path, "r") as zf:
|
||||
manifest = apworld.read_contents(zf)
|
||||
# version/compatible_version shouldn't be needed by world, makes it consistent with folder world
|
||||
manifest.pop("version", None)
|
||||
manifest.pop("compatible_version", None)
|
||||
AutoWorldRegister.world_types[apworld.game].manifest = manifest
|
||||
|
||||
load_apworlds()
|
||||
del load_apworlds
|
||||
|
||||
|
||||
@@ -50,17 +50,16 @@ make sure ***Enable Developer Console*** is checked in Game Settings and press t
|
||||
|
||||
## FAQ/Common Issues
|
||||
|
||||
### The game is crashing on startup repeatedly!
|
||||
This is a common issue on older versions of the game, caused by the game failing to interface with the Steam Workshop.
|
||||
To fix it you can try the following (from least to most effort required)
|
||||
- Subscribe to any random workshop mod, then unsubscribe from it
|
||||
- Restart Steam
|
||||
- Restart your computer
|
||||
- Delete the game's config directory from the files `steamapps/common/HatinTime/HatinTimeGame/Config` then verify the game files
|
||||
- Reinstall the game
|
||||
### The game is not connecting when starting a new save!
|
||||
For unknown reasons, the mod will randomly disable itself in the mod menu. To fix this, go to the Mods menu
|
||||
(rocket icon) in-game, and re-enable the mod.
|
||||
|
||||
### Why do relics disappear from the stands in the Spaceship after they're completed?
|
||||
This is intentional behaviour. Because of how randomizer logic works, there is no way to predict the order that
|
||||
a player will place their relics. Since there are a limited amount of relic stands in the Spaceship, relics are removed
|
||||
after being completed to allow for the placement of more relics without being potentially locked out.
|
||||
The level that the relic set unlocked will stay unlocked.
|
||||
|
||||
### When I start a new save file, the intro cinematic doesn't get skipped, Hat Kid's body is missing and the mod doesn't work!
|
||||
There is a bug on older versions of A Hat in Time that causes save file creation to fail to work properly
|
||||
if you have too many save files. Delete them and it should fix the problem.
|
||||
|
||||
@@ -1,478 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from Utils import pc_to_snes, snes_to_pc
|
||||
from .enemizer_data.base_patch_data import ENEMIZER_BASE_PATCHES
|
||||
from .enemizer_data.symbols import ENEMIZER_SYMBOLS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ALTTPWorld
|
||||
from .Rom import LocalRom
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BossPatchData:
|
||||
pointer: tuple[int, int]
|
||||
graphics: int
|
||||
sprite_array: tuple[int, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DungeonBossPatchData:
|
||||
room_id: int
|
||||
sprite_pointer_address: int
|
||||
shell_x: int
|
||||
shell_y: int
|
||||
clear_layer2: bool = False
|
||||
extra_sprites: tuple[int, ...] = ()
|
||||
gt_sprite_write_address: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomObjectTable:
|
||||
header_byte_0: int
|
||||
header_byte_1: int
|
||||
layer_1_objects: list[bytes] = field(default_factory=list)
|
||||
layer_1_doors: list[bytes] = field(default_factory=list)
|
||||
layer_2_objects: list[bytes] = field(default_factory=list)
|
||||
layer_2_doors: list[bytes] = field(default_factory=list)
|
||||
layer_3_objects: list[bytes] = field(default_factory=list)
|
||||
layer_3_doors: list[bytes] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_rom(cls, rom: "LocalRom", start_address: int) -> "RoomObjectTable":
|
||||
table = cls(rom.read_byte(start_address), rom.read_byte(start_address + 1))
|
||||
layers = (
|
||||
(table.layer_1_objects, table.layer_1_doors),
|
||||
(table.layer_2_objects, table.layer_2_doors),
|
||||
(table.layer_3_objects, table.layer_3_doors),
|
||||
)
|
||||
index = start_address + 2
|
||||
|
||||
for objects, doors in layers:
|
||||
is_door = False
|
||||
while True:
|
||||
if rom.read_bytes(index, 2) == bytearray((0xF0, 0xFF)):
|
||||
is_door = True
|
||||
index += 2
|
||||
continue
|
||||
if rom.read_bytes(index, 2) == bytearray((0xFF, 0xFF)):
|
||||
index += 2
|
||||
break
|
||||
if is_door:
|
||||
doors.append(bytes(rom.read_bytes(index, 2)))
|
||||
index += 2
|
||||
else:
|
||||
objects.append(bytes(rom.read_bytes(index, 3)))
|
||||
index += 3
|
||||
|
||||
return table
|
||||
|
||||
def add_shell(self, x: int, y: int, clear_layer_2: bool, shell_id: int) -> None:
|
||||
self.header_byte_0 = 0xF0
|
||||
if clear_layer_2:
|
||||
self.layer_2_objects.clear()
|
||||
self.layer_2_objects.append(_build_subtype_3_object(x, y, shell_id))
|
||||
|
||||
def remove_shell(self, shell_id: int) -> None:
|
||||
self.layer_2_objects = [obj for obj in self.layer_2_objects if _object_id(obj) != shell_id]
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
output = bytearray((self.header_byte_0, self.header_byte_1))
|
||||
output.extend(self._serialize_layer(self.layer_1_objects, self.layer_1_doors, is_last_layer=False))
|
||||
output.extend(self._serialize_layer(self.layer_2_objects, self.layer_2_doors, is_last_layer=False))
|
||||
output.extend(self._serialize_layer(self.layer_3_objects, self.layer_3_doors, is_last_layer=True))
|
||||
return bytes(output)
|
||||
|
||||
@staticmethod
|
||||
def _serialize_layer(objects: list[bytes], doors: list[bytes], is_last_layer: bool) -> bytes:
|
||||
output = bytearray()
|
||||
for obj in objects:
|
||||
output.extend(obj)
|
||||
if is_last_layer or doors:
|
||||
output.extend((0xF0, 0xFF))
|
||||
for door in doors:
|
||||
output.extend(door)
|
||||
output.extend((0xFF, 0xFF))
|
||||
return bytes(output)
|
||||
|
||||
|
||||
BOSS_PATCH_DATA: dict[str, BossPatchData] = {
|
||||
"Armos": BossPatchData((0x87, 0xE8), 9, (0x05, 0x04, 0x53, 0x05, 0x07, 0x53, 0x05, 0x0A, 0x53,
|
||||
0x08, 0x0A, 0x53, 0x08, 0x07, 0x53, 0x08, 0x04, 0x53,
|
||||
0x08, 0xE7, 0x19)),
|
||||
"Arrghus": BossPatchData((0x97, 0xD9), 20, (0x07, 0x07, 0x8C, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D)),
|
||||
"Blind": BossPatchData((0x54, 0xE6), 32, (0x05, 0x09, 0xCE)),
|
||||
"Helmasaur": BossPatchData((0x49, 0xE0), 21, (0x06, 0x07, 0x92)),
|
||||
"Kholdstare": BossPatchData((0x01, 0xEA), 22, (0x05, 0x07, 0xA3, 0x05, 0x07, 0xA4, 0x05, 0x07, 0xA2)),
|
||||
"Lanmola": BossPatchData((0xCB, 0xDC), 11, (0x07, 0x06, 0x54, 0x07, 0x09, 0x54, 0x09, 0x07, 0x54)),
|
||||
"Moldorm": BossPatchData((0xC3, 0xD9), 12, (0x09, 0x09, 0x09)),
|
||||
"Mothula": BossPatchData((0x31, 0xDC), 26, (0x06, 0x08, 0x88)),
|
||||
"Trinexx": BossPatchData((0xBA, 0xE5), 23, (0x05, 0x07, 0xCB, 0x05, 0x07, 0xCC, 0x05, 0x07, 0xCD)),
|
||||
"Vitreous": BossPatchData((0x57, 0xE4), 22, (0x05, 0x07, 0xBD)),
|
||||
}
|
||||
|
||||
DUNGEON_BOSS_PATCH_DATA: dict[tuple[str, Optional[str]], DungeonBossPatchData] = {
|
||||
("Eastern Palace", None): DungeonBossPatchData(200, 0x04D7BE, 0x2B, 0x28),
|
||||
("Desert Palace", None): DungeonBossPatchData(51, 0x04D694, 0x0B, 0x28),
|
||||
("Tower of Hera", None): DungeonBossPatchData(7, 0x04D63C, 0x18, 0x16),
|
||||
("Palace of Darkness", None): DungeonBossPatchData(90, 0x04D6E2, 0x2B, 0x28),
|
||||
("Swamp Palace", None): DungeonBossPatchData(6, 0x04D63A, 0x0B, 0x28),
|
||||
("Skull Woods", None): DungeonBossPatchData(41, 0x04D680, 0x2B, 0x28),
|
||||
("Thieves Town", None): DungeonBossPatchData(172, 0x04D786, 0x2B, 0x28, clear_layer2=True),
|
||||
("Ice Palace", None): DungeonBossPatchData(222, 0x04D7EA, 0x2B, 0x08, clear_layer2=True),
|
||||
("Misery Mire", None): DungeonBossPatchData(144, 0x04D74E, 0x0B, 0x28, clear_layer2=True),
|
||||
("Turtle Rock", None): DungeonBossPatchData(164, 0x04D776, 0x0B, 0x28, clear_layer2=True),
|
||||
("Ganons Tower", "bottom"): DungeonBossPatchData(
|
||||
28, 0x04D666, 0x2B, 0x28, extra_sprites=(0x07, 0x07, 0xE3, 0x07, 0x08, 0xE3, 0x08, 0x07, 0xE3, 0x08, 0x08, 0xE3),
|
||||
gt_sprite_write_address=0x04D87E,
|
||||
),
|
||||
("Ganons Tower", "middle"): DungeonBossPatchData(
|
||||
108, 0x04D706, 0x0B, 0x28, extra_sprites=(0x18, 0x17, 0xD1, 0x1C, 0x03, 0xC5), gt_sprite_write_address=0x04D8B6,
|
||||
),
|
||||
("Ganons Tower", "top"): DungeonBossPatchData(77, 0x04D6C8, 0x18, 0x16),
|
||||
}
|
||||
|
||||
TRINEXX_SHELL_OBJECT_ID = 0xFF2
|
||||
KHOLDSTARE_SHELL_OBJECT_ID = 0xF95
|
||||
TRINEXX_VANILLA_ROOM_ID = 164
|
||||
KHOLDSTARE_VANILLA_ROOM_ID = 222
|
||||
ENEMY_HP_TABLE_ADDRESS = 0x6B173
|
||||
ENEMY_DAMAGE_TABLE_ADDRESS = 0x6B266
|
||||
HIDDEN_ENEMY_CHANCE_POOL_ADDRESS = 0xD7BBB
|
||||
DAMAGE_GROUP_TABLE_ADDRESS = 0x3742D
|
||||
RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS = 0x301FC
|
||||
RETRO_RUPEE_REPLACEMENT_SPRITE_ID = 0xDA
|
||||
ARROW_REFILL_5_SPRITE_ID = 0xE1
|
||||
THIEF_SPRITE_ID = 0xC4
|
||||
THIEF_DEFAULT_HP = 4
|
||||
VANILLA_HIDDEN_ENEMY_CHANCE_POOL = (
|
||||
0x01, 0x01, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x12,
|
||||
0x10, 0x01, 0x01, 0x01, 0x11, 0x01, 0x01, 0x03,
|
||||
)
|
||||
RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL = (
|
||||
0x01, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x12,
|
||||
0x0F, 0x01, 0x0F, 0x0F, 0x11, 0x0F, 0x0F, 0x03,
|
||||
)
|
||||
EXCLUDED_ENEMY_TABLE_SPRITE_IDS = frozenset({
|
||||
0x09, 0x53, 0x54, 0x70, 0x7A, 0x7B, 0x88, 0x89, 0x8C, 0x8D, 0x92,
|
||||
0xA2, 0xA3, 0xA4, 0xBD, 0xBE, 0xBF, 0xCB, 0xCC, 0xCD, 0xCE, 0xD6, 0xD7,
|
||||
})
|
||||
ENEMY_HEALTH_RANGE_BY_KEY = {
|
||||
"easy": (1, 4),
|
||||
"normal": (2, 15),
|
||||
"hard": (2, 25),
|
||||
"expert": (4, 50),
|
||||
}
|
||||
|
||||
_ENEMIZER_SYMBOLS: Optional[dict[str, int]] = None
|
||||
|
||||
BOSS_GFX_SHEET_INDEXES = {
|
||||
"Agahnim1": 0x8D,
|
||||
"Agahnim2": 0xB5,
|
||||
"Agahnim3": 0xC8,
|
||||
"Agahnim4": 0xB6,
|
||||
"ArmosKnight1": 0x90,
|
||||
"Ganon1": 0x94,
|
||||
"Ganon2": 0xA6,
|
||||
"Ganon3": 0xB4,
|
||||
"Ganon4": 0xB8,
|
||||
"Moldorm1": 0xA3,
|
||||
"Lanmola1": 0xA4,
|
||||
"Arrghus1": 0xAC,
|
||||
"Mothula1": 0xAB,
|
||||
"Helmasaure1": 0xAD,
|
||||
"Helmasaure2": 0xB1,
|
||||
"Blind1": 0xAE,
|
||||
"Kholdstare1": 0xAF,
|
||||
"Vitreous1": 0xB0,
|
||||
"Trinexx1": 0xB2,
|
||||
"Trinexx2": 0xB3,
|
||||
}
|
||||
|
||||
BOSS_GFX_TABLE = {
|
||||
"Agahnim1": (21, 190, 228),
|
||||
"Agahnim2": (22, 255, 135),
|
||||
"Agahnim3": (23, 220, 101),
|
||||
"Agahnim4": (23, 132, 92),
|
||||
"ArmosKnight1": (21, 206, 27),
|
||||
"Ganon1": (21, 227, 160),
|
||||
"Ganon2": (22, 186, 55),
|
||||
"Ganon3": (22, 250, 199),
|
||||
"Ganon4": (23, 142, 33),
|
||||
"Moldorm1": (22, 175, 152),
|
||||
"Lanmola1": (22, 180, 23),
|
||||
"Arrghus1": (22, 214, 147),
|
||||
"Mothula1": (22, 210, 84),
|
||||
"Helmasaure1": (22, 219, 114),
|
||||
"Helmasaure2": (22, 239, 177),
|
||||
"Blind1": (22, 224, 90),
|
||||
"Kholdstare1": (22, 230, 31),
|
||||
"Vitreous1": (22, 235, 9),
|
||||
"Trinexx1": (22, 243, 89),
|
||||
"Trinexx2": (22, 246, 35),
|
||||
}
|
||||
|
||||
TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS = 0x04B37E
|
||||
TRINEXX_ICE_PROJECTILE_TILE_ADDRESS = 0xE7A5
|
||||
TILE_TRAP_FLOOR_TILE_ADDRESS = 0xF3BED
|
||||
|
||||
|
||||
def apply_enemizer_base_patch(rom: "LocalRom") -> None:
|
||||
for address, patch_data in _load_enemizer_base_patches():
|
||||
rom.write_bytes(address, patch_data)
|
||||
_apply_trinexx_room_fixes(rom)
|
||||
|
||||
def patch_bosses(world: "ALTTPWorld", rom: "LocalRom") -> None:
|
||||
dungeon_header_base = _get_enemizer_symbol("room_header_table")
|
||||
moved_room_object_base = _get_enemizer_symbol("modified_room_object_table")
|
||||
gt_dungeon_name = "Ganons Tower" if world.options.mode != "inverted" else "Inverted Ganons Tower"
|
||||
gt_dungeon = world.dungeons[gt_dungeon_name]
|
||||
|
||||
placements = (
|
||||
(world.dungeons["Eastern Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Eastern Palace", None)]),
|
||||
(world.dungeons["Desert Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Desert Palace", None)]),
|
||||
(world.dungeons["Tower of Hera"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Tower of Hera", None)]),
|
||||
(world.dungeons["Palace of Darkness"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Palace of Darkness", None)]),
|
||||
(world.dungeons["Swamp Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Swamp Palace", None)]),
|
||||
(world.dungeons["Skull Woods"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Skull Woods", None)]),
|
||||
(world.dungeons["Thieves Town"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Thieves Town", None)]),
|
||||
(world.dungeons["Ice Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ice Palace", None)]),
|
||||
(world.dungeons["Misery Mire"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Misery Mire", None)]),
|
||||
(world.dungeons["Turtle Rock"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Turtle Rock", None)]),
|
||||
(gt_dungeon.bosses["bottom"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "bottom")]),
|
||||
(gt_dungeon.bosses["middle"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "middle")]),
|
||||
(gt_dungeon.bosses["top"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "top")]),
|
||||
)
|
||||
|
||||
modified_room_tables: dict[int, RoomObjectTable] = {}
|
||||
|
||||
for boss_name, dungeon_data in placements:
|
||||
boss_data = BOSS_PATCH_DATA[boss_name]
|
||||
rom.write_bytes(dungeon_data.sprite_pointer_address, boss_data.pointer)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 3, boss_data.graphics)
|
||||
|
||||
if boss_name == "Trinexx" and dungeon_data.room_id != TRINEXX_VANILLA_ROOM_ID:
|
||||
room_table = _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id)
|
||||
room_table.add_shell(
|
||||
dungeon_data.shell_x,
|
||||
dungeon_data.shell_y - 2,
|
||||
dungeon_data.clear_layer2,
|
||||
TRINEXX_SHELL_OBJECT_ID,
|
||||
)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14), 0x60)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 4, 0x04)
|
||||
|
||||
if boss_name == "Kholdstare" and dungeon_data.room_id != KHOLDSTARE_VANILLA_ROOM_ID:
|
||||
room_table = _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id)
|
||||
room_table.add_shell(
|
||||
dungeon_data.shell_x,
|
||||
dungeon_data.shell_y,
|
||||
dungeon_data.clear_layer2,
|
||||
KHOLDSTARE_SHELL_OBJECT_ID,
|
||||
)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14), 0xE0)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 4, 0x01)
|
||||
|
||||
if boss_name != "Trinexx" and dungeon_data.room_id == TRINEXX_VANILLA_ROOM_ID:
|
||||
_get_room_object_table(rom, modified_room_tables, dungeon_data.room_id).remove_shell(TRINEXX_SHELL_OBJECT_ID)
|
||||
|
||||
if boss_name != "Kholdstare" and dungeon_data.room_id == KHOLDSTARE_VANILLA_ROOM_ID:
|
||||
_get_room_object_table(rom, modified_room_tables, dungeon_data.room_id).remove_shell(KHOLDSTARE_SHELL_OBJECT_ID)
|
||||
|
||||
if dungeon_data.gt_sprite_write_address is not None:
|
||||
_write_gt_boss_sprite_block(rom, dungeon_data, boss_data)
|
||||
|
||||
write_address = moved_room_object_base
|
||||
for room_id in sorted(modified_room_tables):
|
||||
table_bytes = modified_room_tables[room_id].to_bytes()
|
||||
_write_room_object_pointer(rom, room_id, write_address)
|
||||
rom.write_bytes(write_address, table_bytes)
|
||||
write_address += len(table_bytes)
|
||||
|
||||
rom.write_byte(0x1B0101, 0x01)
|
||||
rom.write_byte(0x04DE81, 0x00)
|
||||
if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind":
|
||||
rom.write_byte(0x04DE81, 0x06)
|
||||
rom.write_byte(0x1B0101, 0x00)
|
||||
|
||||
|
||||
def _get_room_object_table(rom: "LocalRom", cache: dict[int, RoomObjectTable], room_id: int) -> RoomObjectTable:
|
||||
room_table = cache.get(room_id)
|
||||
if room_table is not None:
|
||||
return room_table
|
||||
|
||||
pointer_address = 0xF8000 + (room_id * 3)
|
||||
snes_address_bytes = rom.read_bytes(pointer_address, 3)
|
||||
snes_address = (snes_address_bytes[2] << 16) | (snes_address_bytes[1] << 8) | snes_address_bytes[0]
|
||||
room_table = RoomObjectTable.from_rom(rom, snes_to_pc(snes_address))
|
||||
cache[room_id] = room_table
|
||||
return room_table
|
||||
|
||||
|
||||
def _write_gt_boss_sprite_block(rom: "LocalRom", dungeon_data: DungeonBossPatchData, boss_data: BossPatchData) -> None:
|
||||
assert dungeon_data.gt_sprite_write_address is not None
|
||||
rom.write_int16(dungeon_data.sprite_pointer_address, dungeon_data.gt_sprite_write_address)
|
||||
|
||||
sprite_block = bytearray((0x00,))
|
||||
sprite_block.extend(boss_data.sprite_array)
|
||||
if dungeon_data.room_id == 28 and boss_data.pointer == BOSS_PATCH_DATA["Arrghus"].pointer:
|
||||
sprite_block.extend(dungeon_data.extra_sprites[:6])
|
||||
else:
|
||||
sprite_block.extend(dungeon_data.extra_sprites)
|
||||
sprite_block.append(0xFF)
|
||||
rom.write_bytes(dungeon_data.gt_sprite_write_address, sprite_block)
|
||||
|
||||
|
||||
def _write_room_object_pointer(rom: "LocalRom", room_id: int, pc_address: int) -> None:
|
||||
snes_address = pc_to_snes(pc_address)
|
||||
pointer_address = 0xF8000 + (room_id * 3)
|
||||
rom.write_bytes(pointer_address, (
|
||||
snes_address & 0xFF,
|
||||
(snes_address >> 8) & 0xFF,
|
||||
(snes_address >> 16) & 0xFF,
|
||||
))
|
||||
|
||||
|
||||
def _build_subtype_3_object(x: int, y: int, object_id: int) -> bytes:
|
||||
return bytes((
|
||||
((x << 2) & 0xFC) | (object_id & 0x03),
|
||||
((y << 2) & 0xFC) | ((object_id >> 2) & 0x03),
|
||||
0xF0 | ((object_id >> 4) & 0x0F),
|
||||
))
|
||||
|
||||
|
||||
def _object_id(object_bytes: bytes) -> Optional[int]:
|
||||
if len(object_bytes) != 3:
|
||||
return None
|
||||
if object_bytes[0] >= 0xFC:
|
||||
return (object_bytes[2] & 0x3F) + 0x100
|
||||
if object_bytes[2] >= 0xF8:
|
||||
return 0xF00 | ((object_bytes[2] & 0x0F) << 4) | ((object_bytes[1] & 0x03) << 2) | (object_bytes[0] & 0x03)
|
||||
return object_bytes[2]
|
||||
|
||||
|
||||
def _set_enemizer_flag(rom: "LocalRom", symbol_name: str, enabled: bool) -> None:
|
||||
rom.write_byte(_get_enemizer_symbol(symbol_name), 0x01 if enabled else 0x00)
|
||||
|
||||
|
||||
def _apply_killable_thief(rom: "LocalRom") -> None:
|
||||
rom.write_byte(_get_enemizer_symbol("notItemSprite_Mimic") + 4, THIEF_SPRITE_ID)
|
||||
thief_hp_address = ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID
|
||||
if rom.read_byte(thief_hp_address) != 0xFF:
|
||||
rom.write_byte(thief_hp_address, THIEF_DEFAULT_HP)
|
||||
|
||||
|
||||
def _randomize_enemy_health(rom: "LocalRom", rng: random.Random, enemy_health_key: str) -> None:
|
||||
min_hp, max_hp = ENEMY_HEALTH_RANGE_BY_KEY[enemy_health_key]
|
||||
for sprite_id in range(0xF3):
|
||||
hp_address = ENEMY_HP_TABLE_ADDRESS + sprite_id
|
||||
if rom.read_byte(hp_address) == 0xFF or sprite_id in EXCLUDED_ENEMY_TABLE_SPRITE_IDS:
|
||||
continue
|
||||
rom.write_byte(hp_address, rng.randrange(min_hp, max_hp))
|
||||
|
||||
|
||||
def _randomize_enemy_damage(rom: "LocalRom", rng: random.Random, allow_zero_damage: bool) -> None:
|
||||
for sprite_id in range(0xF3):
|
||||
if sprite_id in EXCLUDED_ENEMY_TABLE_SPRITE_IDS:
|
||||
continue
|
||||
new_damage = rng.randrange(8)
|
||||
if not allow_zero_damage and new_damage == 2:
|
||||
continue
|
||||
rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + sprite_id, new_damage)
|
||||
|
||||
|
||||
def _shuffle_damage_groups(
|
||||
rom: "LocalRom",
|
||||
rng: random.Random,
|
||||
*,
|
||||
chaos_mode: bool,
|
||||
allow_zero_damage: bool,
|
||||
) -> None:
|
||||
min_damage = 0 if allow_zero_damage else 4
|
||||
max_damage = 64 if chaos_mode else 32
|
||||
|
||||
for group_id in range(10):
|
||||
green_mail_damage = rng.randrange(min_damage, max_damage)
|
||||
if chaos_mode:
|
||||
blue_mail_damage = rng.randrange(min_damage, max_damage)
|
||||
red_mail_damage = rng.randrange(min_damage, max_damage)
|
||||
else:
|
||||
blue_mail_damage = green_mail_damage * 3 // 4
|
||||
red_mail_damage = green_mail_damage * 3 // 8
|
||||
group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3)
|
||||
rom.write_bytes(group_address, (green_mail_damage, blue_mail_damage, red_mail_damage))
|
||||
|
||||
|
||||
def _update_hidden_enemy_item_table_for_retro_mode(rom: "LocalRom") -> None:
|
||||
if rom.read_byte(RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS) != RETRO_RUPEE_REPLACEMENT_SPRITE_ID:
|
||||
return
|
||||
|
||||
item_table_address = _get_enemizer_symbol("sprite_bush_spawn_item_table")
|
||||
for index in range(22):
|
||||
if rom.read_byte(item_table_address + index) == ARROW_REFILL_5_SPRITE_ID:
|
||||
rom.write_byte(item_table_address + index, RETRO_RUPEE_REPLACEMENT_SPRITE_ID)
|
||||
|
||||
|
||||
def _apply_trinexx_room_fixes(rom: "LocalRom") -> None:
|
||||
# Match original Enemizer's unconditional Trinexx ice-floor removal so
|
||||
# blue-head projectiles do not create solid walls in non-vanilla rooms.
|
||||
rom.write_bytes(TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS, (0xEA, 0xEA, 0xEA, 0xEA))
|
||||
|
||||
|
||||
def _apply_randomized_tile_trap_floor_tile(rom: "LocalRom") -> None:
|
||||
# Original Enemizer's RandomizeTileTrapFloorTile option changes the tile
|
||||
# left behind by flying floor tile traps. AP does not currently expose or
|
||||
# call this option, so keep the implementation isolated and unused.
|
||||
rom.write_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, (0x88, 0x01))
|
||||
rom.write_byte(TILE_TRAP_FLOOR_TILE_ADDRESS, 0x12)
|
||||
|
||||
|
||||
def _make_native_enemizer_rng(world: "ALTTPWorld") -> random.Random:
|
||||
seed_material = "|".join((
|
||||
str(world.multiworld.seed),
|
||||
world.multiworld.seed_name,
|
||||
str(world.player),
|
||||
_option_key(world.options.enemy_health),
|
||||
_option_key(world.options.enemy_damage),
|
||||
str(int(bool(world.options.enemy_shuffle))),
|
||||
str(int(bool(world.options.bush_shuffle))),
|
||||
str(int(bool(world.options.killable_thieves))),
|
||||
))
|
||||
seed = int.from_bytes(hashlib.sha256(seed_material.encode("utf-8")).digest()[:8], "big")
|
||||
return random.Random(seed)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_enemizer_base_patches() -> tuple[tuple[int, bytes], ...]:
|
||||
return tuple(
|
||||
(entry.address, entry.patch_data)
|
||||
for entry in ENEMIZER_BASE_PATCHES
|
||||
)
|
||||
|
||||
|
||||
def _option_key(option: object) -> str:
|
||||
return str(getattr(option, "current_key", option))
|
||||
|
||||
|
||||
def _get_enemizer_symbol(symbol_name: str) -> int:
|
||||
global _ENEMIZER_SYMBOLS
|
||||
if _ENEMIZER_SYMBOLS is None:
|
||||
_ENEMIZER_SYMBOLS = _load_enemizer_symbols()
|
||||
return _ENEMIZER_SYMBOLS[symbol_name]
|
||||
|
||||
|
||||
def _load_enemizer_symbols() -> dict[str, int]:
|
||||
return {
|
||||
name: snes_to_pc(snes_address)
|
||||
for name, snes_address in ENEMIZER_SYMBOLS.items()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType
|
||||
from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType
|
||||
from .Bosses import place_bosses
|
||||
from .Dungeons import get_dungeon_item_pool_player
|
||||
from .EnemyShuffle import generate_enemy_shuffle_state
|
||||
from .EntranceShuffle import connect_entrance
|
||||
from .Items import item_factory, GetBeemizerItem, trap_replaceable, item_name_groups
|
||||
from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode, LTTPBosses
|
||||
@@ -512,8 +511,6 @@ def generate_itempool(world: "ALTTPWorld"):
|
||||
world.options.turtle_rock_medallion.current_key.title())
|
||||
|
||||
place_bosses(world)
|
||||
if world.options.enemy_shuffle:
|
||||
world.enemy_shuffle_state = generate_enemy_shuffle_state(world)
|
||||
|
||||
multiworld.itempool += items
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from Utils import snes_to_pc
|
||||
from .enemizer_data.pot_shuffle_data import POT_ROOMS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ALTTPWorld
|
||||
from .Rom import LocalRom
|
||||
|
||||
|
||||
POT_ITEM_POINTER_TABLE = 0xDB67
|
||||
POT_KEY = 0x08
|
||||
POT_ARROW = 0x09
|
||||
POT_BLUE_RUPEE = 0x07
|
||||
POT_SWITCH = 0x88
|
||||
POT_HOLE = 0x80
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PotData:
|
||||
x: int
|
||||
y: int
|
||||
reserved: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PotRoomData:
|
||||
room_id: int
|
||||
pots: tuple[PotData, ...]
|
||||
items: tuple[int, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FilledPot:
|
||||
x: int
|
||||
y: int
|
||||
item: int
|
||||
|
||||
|
||||
def generate_pot_shuffle(world: "ALTTPWorld") -> dict[int, tuple[FilledPot, ...]]:
|
||||
room_data = _load_pot_room_data()
|
||||
shuffled_pots: dict[int, tuple[FilledPot, ...]] = {}
|
||||
|
||||
for room in room_data:
|
||||
room_items = [item for item in room.items if item != POT_HOLE]
|
||||
if world.options.retro_bow:
|
||||
room_items = [POT_BLUE_RUPEE if item == POT_ARROW else item for item in room_items]
|
||||
|
||||
empty_pots: list[PotData] = []
|
||||
filled_pots: list[FilledPot] = []
|
||||
|
||||
for pot in room.pots:
|
||||
if pot.reserved == 3:
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, POT_HOLE))
|
||||
else:
|
||||
empty_pots.append(pot)
|
||||
|
||||
while POT_KEY in room_items:
|
||||
candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 1]
|
||||
if not candidate_indices:
|
||||
break
|
||||
pot_index = world.random.choice(candidate_indices)
|
||||
pot = empty_pots.pop(pot_index)
|
||||
room_items.remove(POT_KEY)
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, POT_KEY))
|
||||
|
||||
while POT_SWITCH in room_items:
|
||||
candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 2]
|
||||
if not candidate_indices:
|
||||
break
|
||||
pot_index = world.random.choice(candidate_indices)
|
||||
pot = empty_pots.pop(pot_index)
|
||||
room_items.remove(POT_SWITCH)
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, POT_SWITCH))
|
||||
|
||||
while room_items and empty_pots:
|
||||
pot_index = world.random.randrange(len(empty_pots))
|
||||
item_index = world.random.randrange(len(room_items))
|
||||
pot = empty_pots.pop(pot_index)
|
||||
item = room_items.pop(item_index)
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, item))
|
||||
|
||||
shuffled_pots[room.room_id] = tuple(filled_pots)
|
||||
|
||||
return shuffled_pots
|
||||
|
||||
|
||||
def apply_pot_shuffle(rom: "LocalRom", shuffled_pots: dict[int, tuple[FilledPot, ...]]) -> None:
|
||||
for room_id, pots in shuffled_pots.items():
|
||||
pointer_address = POT_ITEM_POINTER_TABLE + (room_id * 2)
|
||||
snes_address = rom.read_byte(pointer_address) | (rom.read_byte(pointer_address + 1) << 8) | (0x01 << 16)
|
||||
address = snes_to_pc(snes_address)
|
||||
for index, pot in enumerate(pots):
|
||||
rom.write_bytes(address + (index * 3), (pot.x, pot.y, pot.item))
|
||||
|
||||
|
||||
def get_unique_pot_item_position(
|
||||
shuffled_pots: dict[int, tuple[FilledPot, ...]],
|
||||
room_id: int,
|
||||
item: int,
|
||||
) -> tuple[int, int]:
|
||||
positions = [
|
||||
(pot.x, pot.y)
|
||||
for pot in shuffled_pots.get(room_id, ())
|
||||
if pot.item == item
|
||||
]
|
||||
if len(positions) != 1:
|
||||
raise ValueError(
|
||||
f"Expected exactly one pot item {hex(item)} in room {hex(room_id)}, found {len(positions)}"
|
||||
)
|
||||
return positions[0]
|
||||
|
||||
|
||||
def _load_pot_room_data() -> tuple[PotRoomData, ...]:
|
||||
return tuple(
|
||||
PotRoomData(
|
||||
room_id=room.room_id,
|
||||
pots=tuple(PotData(x=pot.x, y=pot.y, reserved=pot.reserved) for pot in room.pots),
|
||||
items=room.items,
|
||||
)
|
||||
for room in POT_ROOMS
|
||||
)
|
||||
+225
-132
@@ -15,6 +15,7 @@ import logging
|
||||
import os
|
||||
import random
|
||||
import struct
|
||||
import subprocess
|
||||
import threading
|
||||
import concurrent.futures
|
||||
import bsdiff4
|
||||
@@ -52,6 +53,9 @@ try:
|
||||
except:
|
||||
xxtea = None
|
||||
|
||||
enemizer_logger = logging.getLogger("Enemizer")
|
||||
|
||||
|
||||
class LocalRom:
|
||||
|
||||
def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None):
|
||||
@@ -175,6 +179,43 @@ class LocalRom:
|
||||
self.write_int32(startaddress + (i * 4), value)
|
||||
|
||||
|
||||
check_lock = threading.Lock()
|
||||
|
||||
|
||||
def check_enemizer(enemizercli):
|
||||
if getattr(check_enemizer, "done", None):
|
||||
return
|
||||
if not os.path.exists(enemizercli) and not os.path.exists(enemizercli + ".exe"):
|
||||
raise Exception(f"Enemizer not found at {enemizercli}, please install it. "
|
||||
f"Such as https://github.com/Ijwu/Enemizer/releases")
|
||||
|
||||
with check_lock:
|
||||
# some time may have passed since the lock was acquired, as such a quick re-check doesn't hurt
|
||||
if getattr(check_enemizer, "done", None):
|
||||
return
|
||||
wanted_version = (7, 1, 0)
|
||||
# version info is saved on the lib, for some reason
|
||||
library_info = os.path.join(os.path.dirname(enemizercli), "EnemizerCLI.Core.deps.json")
|
||||
with open(library_info) as f:
|
||||
info = json.load(f)
|
||||
|
||||
for lib in info["libraries"]:
|
||||
if lib.startswith("EnemizerLibrary/"):
|
||||
version = lib.split("/")[-1]
|
||||
version = tuple(int(element) for element in version.split("."))
|
||||
enemizer_logger.debug(f"Found Enemizer version {version}")
|
||||
if version < wanted_version:
|
||||
raise Exception(
|
||||
f"Enemizer found at {enemizercli} is outdated ({version}) < ({wanted_version}), "
|
||||
f"please update your Enemizer. "
|
||||
f"Such as from https://github.com/Ijwu/Enemizer/releases")
|
||||
break
|
||||
else:
|
||||
raise Exception(f"Could not find Enemizer library version information in {library_info}")
|
||||
|
||||
check_enemizer.done = True
|
||||
|
||||
|
||||
def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_random_on_event, sprite_pool):
|
||||
userandomsprites = False
|
||||
if sprite and not isinstance(sprite, Sprite):
|
||||
@@ -241,6 +282,174 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand
|
||||
rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette)
|
||||
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
||||
|
||||
|
||||
def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
|
||||
player = world.player
|
||||
check_enemizer(enemizercli)
|
||||
randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc'))
|
||||
options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json'))
|
||||
enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{player}.sfc'))
|
||||
|
||||
# write options file for enemizer
|
||||
options = {
|
||||
'RandomizeEnemies': world.options.enemy_shuffle.value,
|
||||
'RandomizeEnemiesType': 3,
|
||||
'RandomizeBushEnemyChance': world.options.bush_shuffle.value,
|
||||
'RandomizeEnemyHealthRange': world.options.enemy_health != 'default',
|
||||
'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[
|
||||
world.options.enemy_health.current_key],
|
||||
'OHKO': False,
|
||||
'RandomizeEnemyDamage': world.options.enemy_damage != 'default',
|
||||
'AllowEnemyZeroDamage': True,
|
||||
'ShuffleEnemyDamageGroups': world.options.enemy_damage != 'default',
|
||||
'EnemyDamageChaosMode': world.options.enemy_damage == 'chaos',
|
||||
'EasyModeEscape': world.options.mode == "standard",
|
||||
'EnemiesAbsorbable': False,
|
||||
'AbsorbableSpawnRate': 10,
|
||||
'AbsorbableTypes': {
|
||||
'FullMagic': True, 'SmallMagic': True, 'Bomb_1': True, 'BlueRupee': True, 'Heart': True, 'BigKey': True,
|
||||
'Key': True,
|
||||
'Fairy': True, 'Arrow_10': True, 'Arrow_5': True, 'Bomb_8': True, 'Bomb_4': True, 'GreenRupee': True,
|
||||
'RedRupee': True
|
||||
},
|
||||
'BossMadness': False,
|
||||
'RandomizeBosses': True,
|
||||
'RandomizeBossesType': 0,
|
||||
'RandomizeBossHealth': False,
|
||||
'RandomizeBossHealthMinAmount': 0,
|
||||
'RandomizeBossHealthMaxAmount': 300,
|
||||
'RandomizeBossDamage': False,
|
||||
'RandomizeBossDamageMinAmount': 0,
|
||||
'RandomizeBossDamageMaxAmount': 200,
|
||||
'RandomizeBossBehavior': False,
|
||||
'RandomizeDungeonPalettes': False,
|
||||
'SetBlackoutMode': False,
|
||||
'RandomizeOverworldPalettes': False,
|
||||
'RandomizeSpritePalettes': False,
|
||||
'SetAdvancedSpritePalettes': False,
|
||||
'PukeMode': False,
|
||||
'NegativeMode': False,
|
||||
'GrayscaleMode': False,
|
||||
'GenerateSpoilers': False,
|
||||
'RandomizeLinkSpritePalette': False,
|
||||
'RandomizePots': world.options.pot_shuffle.value,
|
||||
'ShuffleMusic': False,
|
||||
'BootlegMagic': True,
|
||||
'CustomBosses': False,
|
||||
'AndyMode': False,
|
||||
'HeartBeepSpeed': 0,
|
||||
'AlternateGfx': False,
|
||||
'ShieldGraphics': "shield_gfx/normal.gfx",
|
||||
'SwordGraphics': "sword_gfx/normal.gfx",
|
||||
'BeeMizer': False,
|
||||
'BeesLevel': 0,
|
||||
'RandomizeTileTrapPattern': False,
|
||||
'RandomizeTileTrapFloorTile': False,
|
||||
'AllowKillableThief': world.options.killable_thieves.value,
|
||||
'RandomizeSpriteOnHit': False,
|
||||
'DebugMode': False,
|
||||
'DebugForceEnemy': False,
|
||||
'DebugForceEnemyId': 0,
|
||||
'DebugForceBoss': False,
|
||||
'DebugForceBossId': 0,
|
||||
'DebugOpenShutterDoors': False,
|
||||
'DebugForceEnemyDamageZero': False,
|
||||
'DebugShowRoomIdInRupeeCounter': False,
|
||||
'UseManualBosses': True,
|
||||
'ManualBosses': {
|
||||
'EasternPalace': world.dungeons["Eastern Palace"].boss.enemizer_name,
|
||||
'DesertPalace': world.dungeons["Desert Palace"].boss.enemizer_name,
|
||||
'TowerOfHera': world.dungeons["Tower of Hera"].boss.enemizer_name,
|
||||
'AgahnimsTower': 'Agahnim',
|
||||
'PalaceOfDarkness': world.dungeons["Palace of Darkness"].boss.enemizer_name,
|
||||
'SwampPalace': world.dungeons["Swamp Palace"].boss.enemizer_name,
|
||||
'SkullWoods': world.dungeons["Skull Woods"].boss.enemizer_name,
|
||||
'ThievesTown': world.dungeons["Thieves Town"].boss.enemizer_name,
|
||||
'IcePalace': world.dungeons["Ice Palace"].boss.enemizer_name,
|
||||
'MiseryMire': world.dungeons["Misery Mire"].boss.enemizer_name,
|
||||
'TurtleRock': world.dungeons["Turtle Rock"].boss.enemizer_name,
|
||||
'GanonsTower1':
|
||||
world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
|
||||
"Inverted Ganons Tower"].bosses['bottom'].enemizer_name,
|
||||
'GanonsTower2':
|
||||
world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
|
||||
"Inverted Ganons Tower"].bosses['middle'].enemizer_name,
|
||||
'GanonsTower3':
|
||||
world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
|
||||
"Inverted Ganons Tower"].bosses['top'].enemizer_name,
|
||||
'GanonsTower4': 'Agahnim2',
|
||||
'Ganon': 'Ganon',
|
||||
}
|
||||
}
|
||||
|
||||
rom.write_to_file(randopatch_path)
|
||||
|
||||
with open(options_path, 'w') as f:
|
||||
json.dump(options, f)
|
||||
|
||||
max_enemizer_tries = 5
|
||||
for i in range(max_enemizer_tries):
|
||||
enemizer_seed = str(world.random.randint(0, 999999999))
|
||||
enemizer_command = [os.path.abspath(enemizercli),
|
||||
'--rom', randopatch_path,
|
||||
'--seed', enemizer_seed,
|
||||
'--binary',
|
||||
'--enemizer', options_path,
|
||||
'--output', enemizer_output_path]
|
||||
|
||||
p_open = subprocess.Popen(enemizer_command,
|
||||
cwd=os.path.dirname(enemizercli),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True)
|
||||
|
||||
enemizer_logger.debug(
|
||||
f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}")
|
||||
for stdout_line in iter(p_open.stdout.readline, ""):
|
||||
if i == max_enemizer_tries - 1:
|
||||
enemizer_logger.warning(stdout_line.rstrip())
|
||||
else:
|
||||
enemizer_logger.debug(stdout_line.rstrip())
|
||||
p_open.stdout.close()
|
||||
|
||||
return_code = p_open.wait()
|
||||
if return_code:
|
||||
if i == max_enemizer_tries - 1:
|
||||
raise subprocess.CalledProcessError(return_code, enemizer_command)
|
||||
continue
|
||||
|
||||
for j in range(i + 1, max_enemizer_tries):
|
||||
world.random.randint(0, 999999999)
|
||||
# Sacrifice all remaining random numbers that would have been used for unused enemizer tries.
|
||||
# This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness
|
||||
break
|
||||
|
||||
rom.read_from_file(enemizer_output_path)
|
||||
os.remove(enemizer_output_path)
|
||||
|
||||
if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind":
|
||||
rom.write_byte(0x04DE81, 6)
|
||||
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry.
|
||||
|
||||
# Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on.
|
||||
# Replace them with a Slime enemy if they are placed.
|
||||
if world.options.key_drop_shuffle:
|
||||
key_drop_enemies = {
|
||||
0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201,
|
||||
0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA
|
||||
}
|
||||
for enemy in key_drop_enemies:
|
||||
if rom.read_byte(enemy) == 0x12:
|
||||
logging.debug(f"Moblin found and replaced at {enemy} in world {player}")
|
||||
rom.write_byte(enemy, 0x8F)
|
||||
|
||||
for used in (randopatch_path, options_path):
|
||||
try:
|
||||
os.remove(used)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
tile_list_lock = threading.Lock()
|
||||
_tile_collection_table = []
|
||||
|
||||
@@ -586,13 +795,9 @@ def get_nonnative_item_sprite(code: int) -> int:
|
||||
# https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886
|
||||
|
||||
|
||||
def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool):
|
||||
local_random = multiworld.worlds[player].random
|
||||
local_world = multiworld.worlds[player]
|
||||
enemized = bool(local_world.options.boss_shuffle or local_world.options.enemy_shuffle
|
||||
or local_world.options.enemy_health != 'default' or local_world.options.enemy_damage != 'default'
|
||||
or local_world.options.pot_shuffle or local_world.options.bush_shuffle
|
||||
or local_world.options.killable_thieves)
|
||||
|
||||
# patch items
|
||||
|
||||
@@ -1126,13 +1331,6 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
starting_max_arrows = 30
|
||||
|
||||
startingstate = CollectionState(multiworld)
|
||||
has_blue_shield = False
|
||||
has_red_shield = False
|
||||
has_mirror_shield = False
|
||||
progressive_shields = 0
|
||||
has_blue_mail = False
|
||||
has_red_mail = False
|
||||
progressive_mail = 0
|
||||
|
||||
if startingstate.has('Silver Bow', player):
|
||||
equip[0x340] = 1
|
||||
@@ -1161,6 +1359,18 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
elif startingstate.has('Fighter Sword', player):
|
||||
equip[0x359] = 1
|
||||
|
||||
if startingstate.has('Mirror Shield', player):
|
||||
equip[0x35A] = 3
|
||||
elif startingstate.has('Red Shield', player):
|
||||
equip[0x35A] = 2
|
||||
elif startingstate.has('Blue Shield', player):
|
||||
equip[0x35A] = 1
|
||||
|
||||
if startingstate.has('Red Mail', player):
|
||||
equip[0x35B] = 2
|
||||
elif startingstate.has('Blue Mail', player):
|
||||
equip[0x35B] = 1
|
||||
|
||||
if startingstate.has('Magic Upgrade (1/4)', player):
|
||||
equip[0x37B] = 2
|
||||
equip[0x36E] = 0x80
|
||||
@@ -1173,6 +1383,8 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
if item.name in {'Bow', 'Silver Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
|
||||
'Titans Mitts', 'Power Glove', 'Progressive Glove',
|
||||
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
|
||||
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
|
||||
'Red Mail', 'Blue Mail', 'Progressive Mail',
|
||||
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', 'Triforce Piece'}:
|
||||
continue
|
||||
|
||||
@@ -1277,63 +1489,9 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
|
||||
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
|
||||
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
|
||||
elif item.name == 'Blue Shield':
|
||||
has_blue_shield = True
|
||||
continue
|
||||
elif item.name == 'Red Shield':
|
||||
has_red_shield = True
|
||||
continue
|
||||
elif item.name == 'Mirror Shield':
|
||||
has_mirror_shield = True
|
||||
continue
|
||||
elif item.name == 'Progressive Shield':
|
||||
progressive_shields += 1
|
||||
continue
|
||||
elif item.name == 'Blue Mail':
|
||||
has_blue_mail = True
|
||||
continue
|
||||
elif item.name == 'Red Mail':
|
||||
has_red_mail = True
|
||||
continue
|
||||
elif item.name == 'Progressive Mail':
|
||||
progressive_mail += 1
|
||||
continue
|
||||
else:
|
||||
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
|
||||
|
||||
for _ in range(progressive_shields):
|
||||
if has_mirror_shield:
|
||||
continue
|
||||
if has_red_shield and local_world.difficulty_requirements.progressive_shield_limit >= 3:
|
||||
has_mirror_shield = True
|
||||
continue
|
||||
if has_blue_shield and local_world.difficulty_requirements.progressive_shield_limit >= 2:
|
||||
has_red_shield = True
|
||||
continue
|
||||
if local_world.difficulty_requirements.progressive_shield_limit >= 1:
|
||||
has_blue_shield = True
|
||||
|
||||
for _ in range(progressive_mail):
|
||||
if has_red_mail:
|
||||
continue
|
||||
if has_blue_mail and local_world.difficulty_requirements.progressive_armor_limit >= 2:
|
||||
has_red_mail = True
|
||||
continue
|
||||
if local_world.difficulty_requirements.progressive_armor_limit >= 1:
|
||||
has_blue_mail = True
|
||||
|
||||
if has_mirror_shield:
|
||||
equip[0x35A] = 3
|
||||
elif has_red_shield:
|
||||
equip[0x35A] = 2
|
||||
elif has_blue_shield:
|
||||
equip[0x35A] = 1
|
||||
|
||||
if has_red_mail:
|
||||
equip[0x35B] = 2
|
||||
elif has_blue_mail:
|
||||
equip[0x35B] = 1
|
||||
|
||||
equip[0x343] = min(equip[0x343], starting_max_bombs)
|
||||
rom.write_byte(0x180034, starting_max_bombs)
|
||||
equip[0x377] = min(equip[0x377], starting_max_arrows)
|
||||
@@ -1552,71 +1710,6 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
if encoded_players > ROM_PLAYER_LIMIT:
|
||||
rom.write_bytes(0x195FFC + ((ROM_PLAYER_LIMIT - 1) * 32), hud_format_text("Archipelago"))
|
||||
|
||||
if enemized:
|
||||
from . import EnemizerPatches as enemizer_patches
|
||||
from .EnemyShuffle import apply_enemy_shuffle
|
||||
from .PotShuffle import apply_pot_shuffle
|
||||
|
||||
enemizer_patches.apply_enemizer_base_patch(rom)
|
||||
|
||||
enemy_shuffle_enabled = bool(local_world.options.enemy_shuffle)
|
||||
bush_shuffle_enabled = bool(local_world.options.bush_shuffle)
|
||||
enemy_health_key = enemizer_patches._option_key(local_world.options.enemy_health)
|
||||
enemy_damage_key = enemizer_patches._option_key(local_world.options.enemy_damage)
|
||||
|
||||
if enemy_shuffle_enabled or bush_shuffle_enabled:
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_randomize_bushes", True)
|
||||
hidden_enemy_chance_pool = (
|
||||
enemizer_patches.RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL
|
||||
if bush_shuffle_enabled
|
||||
else enemizer_patches.VANILLA_HIDDEN_ENEMY_CHANCE_POOL
|
||||
)
|
||||
rom.write_bytes(enemizer_patches.HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, hidden_enemy_chance_pool)
|
||||
enemizer_patches._update_hidden_enemy_item_table_for_retro_mode(rom)
|
||||
|
||||
if enemy_shuffle_enabled:
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_randomize_sprites", True)
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_enable_mimic_override", True)
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_enable_terrorpin_ai_fix", True)
|
||||
rom.write_bytes(0x1F2D5, (0x54, 0x9C))
|
||||
rom.write_byte(0x1F2E5, 0xB0)
|
||||
rom.write_byte(0x1F2EB, 0xD0)
|
||||
|
||||
if local_world.options.killable_thieves:
|
||||
enemizer_patches._apply_killable_thief(rom)
|
||||
|
||||
if enemy_health_key != "default" or enemy_damage_key != "default":
|
||||
rng = enemizer_patches._make_native_enemizer_rng(local_world)
|
||||
else:
|
||||
rng = None
|
||||
|
||||
if enemy_health_key != "default":
|
||||
assert rng is not None
|
||||
enemizer_patches._randomize_enemy_health(rom, rng, enemy_health_key)
|
||||
|
||||
if enemy_damage_key != "default":
|
||||
assert rng is not None
|
||||
enemizer_patches._randomize_enemy_damage(rom, rng, allow_zero_damage=True)
|
||||
enemizer_patches._shuffle_damage_groups(
|
||||
rom,
|
||||
rng,
|
||||
chaos_mode=enemy_damage_key == "chaos",
|
||||
allow_zero_damage=True,
|
||||
)
|
||||
|
||||
enemy_shuffle_state = getattr(local_world, "enemy_shuffle_state", None)
|
||||
if local_world.options.enemy_shuffle and enemy_shuffle_state is not None:
|
||||
apply_enemy_shuffle(rom, enemy_shuffle_state)
|
||||
|
||||
if local_world.options.boss_shuffle:
|
||||
# Boss shuffle must run after enemy shuffle so boss room sprite pointers
|
||||
# and graphics block IDs are not restored to the enemy-shuffled room values.
|
||||
enemizer_patches.patch_bosses(local_world, rom)
|
||||
|
||||
pot_shuffle_state = getattr(local_world, "pot_shuffle_state", None)
|
||||
if local_world.options.pot_shuffle and pot_shuffle_state is not None:
|
||||
apply_pot_shuffle(rom, pot_shuffle_state)
|
||||
|
||||
# Write title screen Code
|
||||
hashint = int(rom.get_hash(), 16)
|
||||
code = [
|
||||
@@ -1767,7 +1860,7 @@ def apply_oof_sfx(rom: LocalRom, oof: str):
|
||||
rom.write_bytes(0x12803A, oof_bytes)
|
||||
rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB])
|
||||
|
||||
# Preserve SPC $3188 instead of writing the unused "WHAT" sound effect there.
|
||||
# Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
|
||||
rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08])
|
||||
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@ from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from .ItemPool import generate_itempool, difficulties
|
||||
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
|
||||
from .Options import ALTTPOptions, small_key_shuffle
|
||||
from .PotShuffle import generate_pot_shuffle
|
||||
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
|
||||
is_main_entrance, key_drop_data
|
||||
from .Rom import LocalRom, patch_rom, patch_race_rom, apply_rom_settings, \
|
||||
from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
|
||||
get_hash_string, get_base_rom_path, LttPDeltaPatch
|
||||
from .Rules import set_rules
|
||||
from .Shops import create_shops, Shop, push_shop_inventories, ShopType, price_rate_display, price_type_display_name
|
||||
@@ -254,6 +253,17 @@ class ALTTPWorld(World):
|
||||
|
||||
create_items = generate_itempool
|
||||
|
||||
_enemizer_path: typing.ClassVar[typing.Optional[str]] = None
|
||||
|
||||
@property
|
||||
def enemizer_path(self) -> str:
|
||||
# TODO: directly use settings
|
||||
cls = self.__class__
|
||||
if cls._enemizer_path is None:
|
||||
cls._enemizer_path = settings.get_settings().generator.enemizer_path
|
||||
assert isinstance(cls._enemizer_path, str)
|
||||
return cls._enemizer_path
|
||||
|
||||
# custom instance vars
|
||||
dungeon_local_item_names: typing.Set[str]
|
||||
dungeon_specific_item_names: typing.Set[str]
|
||||
@@ -295,8 +305,6 @@ class ALTTPWorld(World):
|
||||
self.required_medallions = ["Ether", "Quake"]
|
||||
self.escape_assist = []
|
||||
self.shops = []
|
||||
self.enemy_shuffle_state = None
|
||||
self.pot_shuffle_state = None
|
||||
self.logical_heart_containers = 10
|
||||
self.logical_heart_pieces = 24
|
||||
super(ALTTPWorld, self).__init__(*args, **kwargs)
|
||||
@@ -308,6 +316,10 @@ class ALTTPWorld(World):
|
||||
raise FileNotFoundError(rom_file)
|
||||
if multiworld.is_race:
|
||||
import xxtea # noqa
|
||||
for player in multiworld.get_game_players(cls.game):
|
||||
if multiworld.worlds[player].use_enemizer:
|
||||
check_enemizer(multiworld.worlds[player].enemizer_path)
|
||||
break
|
||||
|
||||
def generate_early(self):
|
||||
multiworld = self.multiworld
|
||||
@@ -327,9 +339,6 @@ class ALTTPWorld(World):
|
||||
self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options)
|
||||
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
|
||||
|
||||
if self.options.pot_shuffle:
|
||||
self.pot_shuffle_state = generate_pot_shuffle(self)
|
||||
|
||||
if self.options.mode == 'standard':
|
||||
if self.options.small_key_shuffle:
|
||||
if (self.options.small_key_shuffle not in
|
||||
@@ -555,6 +564,13 @@ class ALTTPWorld(World):
|
||||
def stage_generate_output(cls, multiworld, output_directory):
|
||||
push_shop_inventories(multiworld)
|
||||
|
||||
@property
|
||||
def use_enemizer(self) -> bool:
|
||||
return bool(self.options.boss_shuffle or self.options.enemy_shuffle
|
||||
or self.options.enemy_health != 'default' or self.options.enemy_damage != 'default'
|
||||
or self.options.pot_shuffle or self.options.bush_shuffle
|
||||
or self.options.killable_thieves)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
multiworld = self.multiworld
|
||||
player = self.player
|
||||
@@ -562,9 +578,14 @@ class ALTTPWorld(World):
|
||||
self.pushed_shop_inventories.wait()
|
||||
|
||||
try:
|
||||
use_enemizer = self.use_enemizer
|
||||
|
||||
rom = LocalRom(get_base_rom_path())
|
||||
|
||||
patch_rom(multiworld, rom, player)
|
||||
patch_rom(multiworld, rom, player, use_enemizer)
|
||||
|
||||
if use_enemizer:
|
||||
patch_enemizer(self, rom, self.enemizer_path, output_directory)
|
||||
|
||||
if multiworld.is_race:
|
||||
patch_race_rom(rom, multiworld, player)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
These modules are vendored/generated from the upstream Enemizer compiled release and source that were already present
|
||||
locally in `/home/alchav/PycharmProjects/Archipelago/EnemizerCLI` and `/home/alchav/PycharmProjects/Archipelago/Enemizer`.
|
||||
|
||||
Source details:
|
||||
|
||||
- Upstream project: `Ijwu/Enemizer`
|
||||
- Release family: `7.1`
|
||||
- Library version from `EnemizerCLI/EnemizerCLI.Core.deps.json`: `EnemizerLibrary/7.1.0`
|
||||
|
||||
Vendored data modules:
|
||||
|
||||
- `base_patch_data.py`
|
||||
- `symbols.py`
|
||||
- `enemy_room_metadata.py`
|
||||
- `enemy_sprite_requirements.py`
|
||||
- `overworld_enemy_metadata.py`
|
||||
- `dungeon_sprite_addresses.py`
|
||||
- `pot_shuffle_data.py`
|
||||
|
||||
Purpose:
|
||||
|
||||
- `base_patch_data.py` contains the generated base patch Enemizer applies before feature-specific randomization.
|
||||
- `symbols.py` contains the assembled symbol map consumed by Enemizer's runtime code for ROM addresses.
|
||||
- `enemy_room_metadata.py` and `overworld_enemy_metadata.py` contain room and area grouping/randomization constraints.
|
||||
- `enemy_sprite_requirements.py` contains the sprite metadata used by the native enemy shuffle implementation.
|
||||
- `dungeon_sprite_addresses.py` contains dungeon sprite slot metadata derived from Enemizer's source tables and keyed-enemy address list.
|
||||
- `pot_shuffle_data.py` contains the native pot shuffle room/item source data.
|
||||
@@ -1 +0,0 @@
|
||||
"""Native ALTTP Enemizer data modules."""
|
||||
File diff suppressed because one or more lines are too long
@@ -1,202 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class DungeonSpriteAddressData(NamedTuple):
|
||||
room_id: int
|
||||
sprite_id_addresses: tuple[int, ...]
|
||||
|
||||
DUNGEON_SPRITE_ADDRESSES = (
|
||||
DungeonSpriteAddressData(room_id=2, sprite_id_addresses=(317750, 317753, 317756, 317759, 317762, 317792, 317795)),
|
||||
DungeonSpriteAddressData(room_id=4, sprite_id_addresses=(317803, 317806, 317809, 317812, 317827, 317839, 317842, 317845)),
|
||||
DungeonSpriteAddressData(room_id=9, sprite_id_addresses=(317904, 317907, 317910)),
|
||||
DungeonSpriteAddressData(room_id=10, sprite_id_addresses=(317915, 317918, 317921, 317924, 317930, 317933)),
|
||||
DungeonSpriteAddressData(room_id=11, sprite_id_addresses=(317941, 317944, 317947, 317950, 317953, 317956, 317959, 317962, 317965)),
|
||||
DungeonSpriteAddressData(room_id=14, sprite_id_addresses=(317978, 317981, 317984)),
|
||||
DungeonSpriteAddressData(room_id=17, sprite_id_addresses=(317992, 317995, 317998, 318001, 318004, 318007, 318010, 318013)),
|
||||
DungeonSpriteAddressData(room_id=19, sprite_id_addresses=(318029, 318032, 318035, 318038, 318044, 318056, 318053, 318041)),
|
||||
DungeonSpriteAddressData(room_id=21, sprite_id_addresses=(318105, 318108, 318111, 318114, 318117, 318120)),
|
||||
DungeonSpriteAddressData(room_id=22, sprite_id_addresses=(318125, 318128, 318131, 318134, 318137, 318140, 318143)),
|
||||
DungeonSpriteAddressData(room_id=23, sprite_id_addresses=(318157, 318160, 318163, 318166, 318169, 318172)),
|
||||
DungeonSpriteAddressData(room_id=25, sprite_id_addresses=(318177, 318180, 318183, 318186)),
|
||||
DungeonSpriteAddressData(room_id=26, sprite_id_addresses=(318191, 318194, 318197, 318200, 318203, 318206, 318209, 318212, 318218)),
|
||||
DungeonSpriteAddressData(room_id=27, sprite_id_addresses=(318232, 318235, 318238, 318241)),
|
||||
DungeonSpriteAddressData(room_id=30, sprite_id_addresses=(318284, 318287, 318290, 318293, 318296, 318299)),
|
||||
DungeonSpriteAddressData(room_id=31, sprite_id_addresses=(318304, 318307, 318310, 318313, 318316, 318319, 318322, 318325)),
|
||||
DungeonSpriteAddressData(room_id=33, sprite_id_addresses=(318335, 318341, 318344, 318347, 318350, 318353, 318356, 318359, 318362, 318365, 318368)),
|
||||
DungeonSpriteAddressData(room_id=34, sprite_id_addresses=(318373, 318376, 318379, 318382, 318385, 318388, 318391)),
|
||||
DungeonSpriteAddressData(room_id=36, sprite_id_addresses=(318413, 318416, 318419, 318422, 318425, 318428, 318431)),
|
||||
DungeonSpriteAddressData(room_id=38, sprite_id_addresses=(318471, 318438, 318441, 318444, 318447, 318450, 318453, 318459, 318462, 318465, 318468)),
|
||||
DungeonSpriteAddressData(room_id=39, sprite_id_addresses=(318476, 318479, 318482, 318485, 318488, 318491, 318494)),
|
||||
DungeonSpriteAddressData(room_id=40, sprite_id_addresses=(318511,)),
|
||||
DungeonSpriteAddressData(room_id=42, sprite_id_addresses=(318530, 318533, 318536, 318539, 318542, 318545)),
|
||||
DungeonSpriteAddressData(room_id=43, sprite_id_addresses=(318556, 318559, 318562, 318565, 318568, 318571)),
|
||||
DungeonSpriteAddressData(room_id=46, sprite_id_addresses=(318590, 318593, 318596, 318599, 318602, 318605)),
|
||||
DungeonSpriteAddressData(room_id=49, sprite_id_addresses=(318621, 318624, 318627, 318630, 318633, 318636, 318639, 318642, 318645, 318648)),
|
||||
DungeonSpriteAddressData(room_id=50, sprite_id_addresses=(318653, 318656, 318659, 318662, 318665)),
|
||||
DungeonSpriteAddressData(room_id=52, sprite_id_addresses=(318681, 318684, 318687, 318693, 318696, 318699, 318690)),
|
||||
DungeonSpriteAddressData(room_id=53, sprite_id_addresses=(318710, 318713, 318716, 318719, 318722, 318728, 318731, 318734, 318725)),
|
||||
DungeonSpriteAddressData(room_id=54, sprite_id_addresses=(318742, 318745, 318754, 318760, 318763)),
|
||||
DungeonSpriteAddressData(room_id=55, sprite_id_addresses=(318777, 318780, 318783, 318786, 318792, 318795, 318798, 318801, 318789)),
|
||||
DungeonSpriteAddressData(room_id=56, sprite_id_addresses=(318806, 318809, 318812, 318815, 318818, 318821, 318824)),
|
||||
DungeonSpriteAddressData(room_id=57, sprite_id_addresses=(318829, 318835, 318841, 318844, 318847, 318850)),
|
||||
DungeonSpriteAddressData(room_id=58, sprite_id_addresses=(318855, 318858, 318861, 318864, 318867, 318870)),
|
||||
DungeonSpriteAddressData(room_id=59, sprite_id_addresses=(318875, 318878, 318881, 318884, 318887, 318890, 318893)),
|
||||
DungeonSpriteAddressData(room_id=60, sprite_id_addresses=(318898, 318901, 318904)),
|
||||
DungeonSpriteAddressData(room_id=61, sprite_id_addresses=(318915, 318921, 318924, 318927, 318930, 318933, 318939, 318942, 318945, 318948, 318951)),
|
||||
DungeonSpriteAddressData(room_id=62, sprite_id_addresses=(318959, 318962, 318980, 318983, 318989, 318992)),
|
||||
DungeonSpriteAddressData(room_id=63, sprite_id_addresses=(319000, 319006, 319009)),
|
||||
DungeonSpriteAddressData(room_id=64, sprite_id_addresses=(319014, 319017, 319023, 319026, 319029)),
|
||||
DungeonSpriteAddressData(room_id=65, sprite_id_addresses=(319036, 319039, 319042, 319045)),
|
||||
DungeonSpriteAddressData(room_id=66, sprite_id_addresses=(319050, 319053, 319056, 319059, 319062, 319065)),
|
||||
DungeonSpriteAddressData(room_id=67, sprite_id_addresses=(319070, 319073)),
|
||||
DungeonSpriteAddressData(room_id=68, sprite_id_addresses=(319084, 319087, 319090, 319093, 319096, 319102)),
|
||||
DungeonSpriteAddressData(room_id=69, sprite_id_addresses=(319110, 319116, 319119, 319131, 319134, 319137, 319113, 319122, 319125, 319128)),
|
||||
DungeonSpriteAddressData(room_id=70, sprite_id_addresses=(319142, 319148, 319154)),
|
||||
DungeonSpriteAddressData(room_id=73, sprite_id_addresses=(319161, 319164, 319167, 319170, 319173, 319176, 319182, 319185, 319188, 319191, 319194, 319197)),
|
||||
DungeonSpriteAddressData(room_id=74, sprite_id_addresses=(319205, 319208)),
|
||||
DungeonSpriteAddressData(room_id=75, sprite_id_addresses=(319213, 319216, 319219, 319222, 319225, 319228, 319231, 319234)),
|
||||
DungeonSpriteAddressData(room_id=76, sprite_id_addresses=(319245, 319248, 319251, 319254, 319257, 319260)),
|
||||
DungeonSpriteAddressData(room_id=78, sprite_id_addresses=(319270, 319273, 319276, 319279)),
|
||||
DungeonSpriteAddressData(room_id=80, sprite_id_addresses=(319295, 319298, 319301)),
|
||||
DungeonSpriteAddressData(room_id=81, sprite_id_addresses=(319309, 319312)),
|
||||
DungeonSpriteAddressData(room_id=82, sprite_id_addresses=(319317, 319320, 319323)),
|
||||
DungeonSpriteAddressData(room_id=83, sprite_id_addresses=(319328, 319331, 319334, 319337, 319340, 319343, 319346, 319349, 319352, 319355, 319358, 319361, 319364)),
|
||||
DungeonSpriteAddressData(room_id=84, sprite_id_addresses=(319369, 319372, 319375, 319378, 319381, 319384, 319387, 319390)),
|
||||
DungeonSpriteAddressData(room_id=85, sprite_id_addresses=(319398, 319401)),
|
||||
DungeonSpriteAddressData(room_id=86, sprite_id_addresses=(319415, 319418, 319421, 319424, 319430, 319433, 319436, 319442, 319439)),
|
||||
DungeonSpriteAddressData(room_id=87, sprite_id_addresses=(319447, 319450, 319453, 319456, 319459, 319462, 319468, 319471, 319474, 319477, 319480, 319483, 319486, 319489)),
|
||||
DungeonSpriteAddressData(room_id=88, sprite_id_addresses=(319497, 319500, 319506, 319509, 319515, 319518, 319521)),
|
||||
DungeonSpriteAddressData(room_id=89, sprite_id_addresses=(319526, 319529, 319538, 319544, 319547, 319550, 319553, 319556, 319559, 319541)),
|
||||
DungeonSpriteAddressData(room_id=91, sprite_id_addresses=(319575, 319578, 319581, 319584)),
|
||||
DungeonSpriteAddressData(room_id=93, sprite_id_addresses=(319615, 319618, 319621, 319624, 319627, 319633, 319636, 319639, 319651, 319642, 319645, 319648, 319630)),
|
||||
DungeonSpriteAddressData(room_id=94, sprite_id_addresses=(319659, 319662, 319665, 319668)),
|
||||
DungeonSpriteAddressData(room_id=95, sprite_id_addresses=(319673, 319676, 319679)),
|
||||
DungeonSpriteAddressData(room_id=96, sprite_id_addresses=(319684,)),
|
||||
DungeonSpriteAddressData(room_id=97, sprite_id_addresses=(319689, 319692, 319695)),
|
||||
DungeonSpriteAddressData(room_id=98, sprite_id_addresses=(319700, 319703, 319706)),
|
||||
DungeonSpriteAddressData(room_id=99, sprite_id_addresses=(319714, 319711)),
|
||||
DungeonSpriteAddressData(room_id=100, sprite_id_addresses=(319719, 319725, 319728, 319731, 319734, 319737)),
|
||||
DungeonSpriteAddressData(room_id=101, sprite_id_addresses=(319760, 319763, 319766, 319769, 319772)),
|
||||
DungeonSpriteAddressData(room_id=102, sprite_id_addresses=(319777, 319783, 319786, 319795, 319798, 319801, 319804, 319810)),
|
||||
DungeonSpriteAddressData(room_id=103, sprite_id_addresses=(319818, 319821, 319824, 319827, 319830, 319833, 319836, 319839, 319842)),
|
||||
DungeonSpriteAddressData(room_id=104, sprite_id_addresses=(319859, 319865, 319868)),
|
||||
DungeonSpriteAddressData(room_id=106, sprite_id_addresses=(319873, 319876, 319879, 319882, 319885, 319888)),
|
||||
DungeonSpriteAddressData(room_id=107, sprite_id_addresses=(319899, 319902, 319905, 319911, 319914, 319917, 319920, 319923, 319926, 319929, 319932)),
|
||||
DungeonSpriteAddressData(room_id=109, sprite_id_addresses=(319954, 319957, 319960, 319963, 319966, 319969, 319972, 319975, 319978)),
|
||||
DungeonSpriteAddressData(room_id=110, sprite_id_addresses=(319983, 319986, 319989, 319992, 319995)),
|
||||
DungeonSpriteAddressData(room_id=113, sprite_id_addresses=(320000, 320003)),
|
||||
DungeonSpriteAddressData(room_id=114, sprite_id_addresses=(320011, 320017)),
|
||||
DungeonSpriteAddressData(room_id=115, sprite_id_addresses=(320022, 320025, 320028, 320031, 320034, 320037)),
|
||||
DungeonSpriteAddressData(room_id=116, sprite_id_addresses=(320045, 320048, 320051, 320054, 320057, 320060, 320063, 320066)),
|
||||
DungeonSpriteAddressData(room_id=117, sprite_id_addresses=(320071, 320074, 320077, 320080, 320083, 320086, 320095, 320098)),
|
||||
DungeonSpriteAddressData(room_id=118, sprite_id_addresses=(320106, 320109, 320112, 320115, 320121)),
|
||||
DungeonSpriteAddressData(room_id=119, sprite_id_addresses=(320126, 320138, 320141)),
|
||||
DungeonSpriteAddressData(room_id=123, sprite_id_addresses=(320146, 320149, 320152, 320155, 320158, 320161, 320167, 320170, 320173, 320176)),
|
||||
DungeonSpriteAddressData(room_id=124, sprite_id_addresses=(320181, 320184, 320187, 320190, 320193, 320196)),
|
||||
DungeonSpriteAddressData(room_id=125, sprite_id_addresses=(320216, 320219, 320225, 320228, 320234, 320222, 320231, 320204, 320207, 320210, 320213, 320222, 320231)),
|
||||
DungeonSpriteAddressData(room_id=126, sprite_id_addresses=(320242, 320245, 320254, 320257)),
|
||||
DungeonSpriteAddressData(room_id=128, sprite_id_addresses=(320291, 320294)),
|
||||
DungeonSpriteAddressData(room_id=129, sprite_id_addresses=(320302, 320305)),
|
||||
DungeonSpriteAddressData(room_id=130, sprite_id_addresses=(320310, 320313, 320316)),
|
||||
DungeonSpriteAddressData(room_id=131, sprite_id_addresses=(320321, 320324, 320327, 320330, 320333, 320336, 320339, 320342, 320345, 320348)),
|
||||
DungeonSpriteAddressData(room_id=132, sprite_id_addresses=(320353, 320356, 320359, 320362, 320365, 320368, 320371)),
|
||||
DungeonSpriteAddressData(room_id=133, sprite_id_addresses=(320376, 320379, 320382, 320385, 320388, 320391, 320394, 320397, 320400, 320403)),
|
||||
DungeonSpriteAddressData(room_id=135, sprite_id_addresses=(320410, 320413, 320416, 320419, 320434, 320437, 320440, 320446, 320422)),
|
||||
DungeonSpriteAddressData(room_id=139, sprite_id_addresses=(320468, 320471, 320474, 320477, 320480)),
|
||||
DungeonSpriteAddressData(room_id=140, sprite_id_addresses=(320503, 320506, 320509, 320512, 320518, 320521, 320527, 320524, 320515)),
|
||||
DungeonSpriteAddressData(room_id=141, sprite_id_addresses=(320538, 320541, 320544, 320547, 320550, 320556, 320559, 320562, 320565, 320568, 320571, 320535)),
|
||||
DungeonSpriteAddressData(room_id=142, sprite_id_addresses=(320579, 320582, 320585, 320588, 320591, 320594, 320597)),
|
||||
DungeonSpriteAddressData(room_id=145, sprite_id_addresses=(320610, 320616, 320619, 320622, 320625, 320613)),
|
||||
DungeonSpriteAddressData(room_id=146, sprite_id_addresses=(320636, 320639, 320642, 320645, 320648, 320654, 320657, 320660, 320663)),
|
||||
DungeonSpriteAddressData(room_id=147, sprite_id_addresses=(320668, 320671, 320674, 320677, 320680, 320683, 320686, 320689)),
|
||||
DungeonSpriteAddressData(room_id=149, sprite_id_addresses=(320694, 320697, 320700, 320703)),
|
||||
DungeonSpriteAddressData(room_id=151, sprite_id_addresses=(320728,)),
|
||||
DungeonSpriteAddressData(room_id=152, sprite_id_addresses=(320733, 320736, 320739, 320742, 320745)),
|
||||
DungeonSpriteAddressData(room_id=153, sprite_id_addresses=(320750, 320753, 320756, 320759, 320765, 320768, 320771, 320774, 320777, 320780)),
|
||||
DungeonSpriteAddressData(room_id=155, sprite_id_addresses=(320794, 320797, 320800, 320803, 320806, 320809, 320812, 320815, 320818, 320821)),
|
||||
DungeonSpriteAddressData(room_id=156, sprite_id_addresses=(320826, 320829, 320832, 320835, 320838, 320841)),
|
||||
DungeonSpriteAddressData(room_id=157, sprite_id_addresses=(320852, 320855, 320858, 320861, 320864, 320867, 320870, 320873)),
|
||||
DungeonSpriteAddressData(room_id=158, sprite_id_addresses=(320878, 320881, 320884, 320887)),
|
||||
DungeonSpriteAddressData(room_id=159, sprite_id_addresses=(320907, 320910)),
|
||||
DungeonSpriteAddressData(room_id=160, sprite_id_addresses=(320915, 320918, 320921)),
|
||||
DungeonSpriteAddressData(room_id=161, sprite_id_addresses=(320929, 320932, 320935, 320938, 320941, 320944, 320947, 320950)),
|
||||
DungeonSpriteAddressData(room_id=165, sprite_id_addresses=(320968, 320971, 320974, 320977, 320980, 320983, 320986, 320989, 320998, 321001)),
|
||||
DungeonSpriteAddressData(room_id=167, sprite_id_addresses=(321014, 321017)),
|
||||
DungeonSpriteAddressData(room_id=168, sprite_id_addresses=(321022, 321025, 321028, 321031, 321034)),
|
||||
DungeonSpriteAddressData(room_id=169, sprite_id_addresses=(321039, 321042, 321057, 321060, 321045, 321048, 321051, 321054)),
|
||||
DungeonSpriteAddressData(room_id=170, sprite_id_addresses=(321065, 321068, 321071, 321074, 321077, 321080)),
|
||||
DungeonSpriteAddressData(room_id=171, sprite_id_addresses=(321088, 321091, 321094, 321097, 321100, 321103, 321106)),
|
||||
DungeonSpriteAddressData(room_id=174, sprite_id_addresses=(321116, 321119)),
|
||||
DungeonSpriteAddressData(room_id=176, sprite_id_addresses=(321129, 321132, 321135, 321138, 321141, 321144, 321147, 321150, 321153, 321156, 321159, 321165, 321168)),
|
||||
DungeonSpriteAddressData(room_id=177, sprite_id_addresses=(321173, 321176, 321179, 321182, 321185, 321188, 321191, 321194, 321197, 321200)),
|
||||
DungeonSpriteAddressData(room_id=178, sprite_id_addresses=(321205, 321208, 321211, 321214, 321217, 321220, 321223, 321226, 321229, 321232, 321235, 321238, 321241, 321244)),
|
||||
DungeonSpriteAddressData(room_id=179, sprite_id_addresses=(321249, 321252, 321255, 321258, 321261)),
|
||||
DungeonSpriteAddressData(room_id=182, sprite_id_addresses=(321277, 321280, 321289, 321292, 321301, 321304)),
|
||||
DungeonSpriteAddressData(room_id=183, sprite_id_addresses=(321309, 321312)),
|
||||
DungeonSpriteAddressData(room_id=184, sprite_id_addresses=(321317, 321320, 321323, 321326, 321329, 321332)),
|
||||
DungeonSpriteAddressData(room_id=186, sprite_id_addresses=(321342, 321345, 321348, 321351, 321354, 321357, 321360)),
|
||||
DungeonSpriteAddressData(room_id=187, sprite_id_addresses=(321365, 321368, 321371, 321374, 321377, 321380, 321386, 321389, 321392, 321395, 321383)),
|
||||
DungeonSpriteAddressData(room_id=188, sprite_id_addresses=(321403, 321406, 321409, 321412, 321418, 321421, 321424, 321433, 321400, 321415, 321427, 321430)),
|
||||
DungeonSpriteAddressData(room_id=190, sprite_id_addresses=(321440, 321446, 321449, 321452, 321455, 321458)),
|
||||
DungeonSpriteAddressData(room_id=192, sprite_id_addresses=(321471, 321474, 321477, 321480, 321486, 321489, 321492, 321495)),
|
||||
DungeonSpriteAddressData(room_id=193, sprite_id_addresses=(321503, 321506, 321509, 321512, 321518, 321524, 321527, 321530, 321521, 321515, 321536)),
|
||||
DungeonSpriteAddressData(room_id=194, sprite_id_addresses=(321547, 321550, 321553, 321556, 321562, 321559, 321544, 321541)),
|
||||
DungeonSpriteAddressData(room_id=195, sprite_id_addresses=(321567, 321585, 321588)),
|
||||
DungeonSpriteAddressData(room_id=196, sprite_id_addresses=(321605, 321608, 321611, 321614, 321617, 321620)),
|
||||
DungeonSpriteAddressData(room_id=201, sprite_id_addresses=(321697, 321700, 321703)),
|
||||
DungeonSpriteAddressData(room_id=203, sprite_id_addresses=(321708, 321717, 321720, 321723, 321726, 321729, 321732, 321735, 321738, 321741, 321714, 321711)),
|
||||
DungeonSpriteAddressData(room_id=204, sprite_id_addresses=(321746, 321749, 321755, 321758, 321761, 321770, 321773, 321776, 321779, 321782, 321785, 321752, 321764, 321767)),
|
||||
DungeonSpriteAddressData(room_id=206, sprite_id_addresses=(321790, 321793, 321799, 321802, 321805, 321808, 321811)),
|
||||
DungeonSpriteAddressData(room_id=208, sprite_id_addresses=(321816, 321819, 321822, 321825, 321828, 321831, 321834, 321837, 321840, 321843, 321846)),
|
||||
DungeonSpriteAddressData(room_id=209, sprite_id_addresses=(321851, 321854, 321857, 321860, 321863, 321866, 321869, 321872)),
|
||||
DungeonSpriteAddressData(room_id=210, sprite_id_addresses=(321877, 321880, 321883, 321886, 321889, 321892, 321895, 321898, 321901, 321904)),
|
||||
DungeonSpriteAddressData(room_id=216, sprite_id_addresses=(321937, 321940, 321943, 321946, 321949, 321952, 321955, 321958, 321961, 321964, 321967)),
|
||||
DungeonSpriteAddressData(room_id=217, sprite_id_addresses=(321975, 321978, 321981, 321972)),
|
||||
DungeonSpriteAddressData(room_id=218, sprite_id_addresses=(321986, 321989)),
|
||||
DungeonSpriteAddressData(room_id=219, sprite_id_addresses=(321994, 321997, 322000, 322006, 322003, 322012, 322009)),
|
||||
DungeonSpriteAddressData(room_id=220, sprite_id_addresses=(322020, 322023, 322026, 322029, 322032, 322035, 322047, 322017, 322038, 322041, 322044)),
|
||||
DungeonSpriteAddressData(room_id=223, sprite_id_addresses=(322063, 322066)),
|
||||
DungeonSpriteAddressData(room_id=224, sprite_id_addresses=(322071, 322074, 322077, 322080)),
|
||||
DungeonSpriteAddressData(room_id=232, sprite_id_addresses=(322189, 322192, 322195, 322198)),
|
||||
DungeonSpriteAddressData(room_id=238, sprite_id_addresses=(322213, 322216, 322219, 322222, 322225)),
|
||||
DungeonSpriteAddressData(room_id=239, sprite_id_addresses=(322230, 322233, 322236)),
|
||||
DungeonSpriteAddressData(room_id=249, sprite_id_addresses=(322323, 322326, 322329, 322332)),
|
||||
DungeonSpriteAddressData(room_id=254, sprite_id_addresses=(322378, 322381, 322384, 322387, 322390)),
|
||||
DungeonSpriteAddressData(room_id=263, sprite_id_addresses=(322444, 322447)),
|
||||
DungeonSpriteAddressData(room_id=264, sprite_id_addresses=(322452, 322455, 322458, 322461)),
|
||||
DungeonSpriteAddressData(room_id=267, sprite_id_addresses=(322494,)),
|
||||
DungeonSpriteAddressData(room_id=269, sprite_id_addresses=(322525, 322528)),
|
||||
DungeonSpriteAddressData(room_id=291, sprite_id_addresses=(322671, 322674, 322677, 322680)),
|
||||
)
|
||||
|
||||
KEYED_SPRITE_ID_ADDRESSES = frozenset((317984,
|
||||
318044,
|
||||
318335,
|
||||
318835,
|
||||
318915,
|
||||
318983,
|
||||
320003,
|
||||
320011,
|
||||
320294,
|
||||
320759,
|
||||
321292,
|
||||
321480,
|
||||
321530,
|
||||
320000,
|
||||
321159,
|
||||
321937,
|
||||
321940,
|
||||
321943,
|
||||
321946,
|
||||
321949,
|
||||
321952,
|
||||
321955,
|
||||
321958,
|
||||
321961,
|
||||
321964,
|
||||
321967,
|
||||
321424,
|
||||
321421,
|
||||
321418))
|
||||
@@ -1,106 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class RoomGroupRequirementData(NamedTuple):
|
||||
group_id: Optional[int]
|
||||
subgroup_0: Optional[int]
|
||||
subgroup_1: Optional[int]
|
||||
subgroup_2: Optional[int]
|
||||
subgroup_3: Optional[int]
|
||||
rooms: tuple[int, ...]
|
||||
|
||||
SHUTTER_ROOM_IDS = frozenset((184,
|
||||
11,
|
||||
27,
|
||||
75,
|
||||
4,
|
||||
36,
|
||||
182,
|
||||
40,
|
||||
14,
|
||||
46,
|
||||
62,
|
||||
110,
|
||||
49,
|
||||
135,
|
||||
68,
|
||||
69,
|
||||
83,
|
||||
117,
|
||||
133,
|
||||
61,
|
||||
93,
|
||||
107,
|
||||
109,
|
||||
123,
|
||||
125,
|
||||
141,
|
||||
150,
|
||||
165,
|
||||
113,
|
||||
168,
|
||||
216,
|
||||
176,
|
||||
192,
|
||||
224,
|
||||
178,
|
||||
210,
|
||||
239,
|
||||
268,
|
||||
291))
|
||||
WATER_ROOM_IDS = frozenset((22, 40, 52, 54, 56, 70, 102))
|
||||
DONT_RANDOMIZE_ROOM_IDS = frozenset((0, 1, 3, 13, 20, 32, 48, 127))
|
||||
NO_SPECIAL_ENEMIES_STANDARD_ROOM_IDS = frozenset((1, 2, 17, 33, 34, 50, 65, 66, 80, 81, 82, 85, 96, 97, 98, 112, 113, 114, 128, 129, 130))
|
||||
BOSS_ROOM_IDS = frozenset((200, 51, 108, 7, 77, 90, 6, 41, 172, 222, 144, 164, 32, 13, 0))
|
||||
|
||||
ROOM_GROUP_REQUIREMENTS = (
|
||||
RoomGroupRequirementData(group_id=1, subgroup_0=70, subgroup_1=73, subgroup_2=28, subgroup_3=82, rooms=(228, 240)),
|
||||
RoomGroupRequirementData(group_id=5, subgroup_0=75, subgroup_1=77, subgroup_2=74, subgroup_3=90, rooms=(243, 265, 270, 271, 272, 273, 282, 284, 290)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=75, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(255, 274, 287)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=77, subgroup_2=None, subgroup_3=21, rooms=(289,)),
|
||||
RoomGroupRequirementData(group_id=7, subgroup_0=75, subgroup_1=77, subgroup_2=57, subgroup_3=54, rooms=(8, 44, 276, 277)),
|
||||
RoomGroupRequirementData(group_id=13, subgroup_0=81, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(85, 258, 260)),
|
||||
RoomGroupRequirementData(group_id=14, subgroup_0=71, subgroup_1=73, subgroup_2=76, subgroup_3=80, rooms=(18, 261, 266)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=80, rooms=(264,)),
|
||||
RoomGroupRequirementData(group_id=15, subgroup_0=79, subgroup_1=77, subgroup_2=74, subgroup_3=80, rooms=(244, 245, 257, 259, 262, 280, 281)),
|
||||
RoomGroupRequirementData(group_id=18, subgroup_0=85, subgroup_1=61, subgroup_2=66, subgroup_3=67, rooms=(32, 48)),
|
||||
RoomGroupRequirementData(group_id=24, subgroup_0=85, subgroup_1=26, subgroup_2=66, subgroup_3=67, rooms=(13,)),
|
||||
RoomGroupRequirementData(group_id=34, subgroup_0=33, subgroup_1=65, subgroup_2=69, subgroup_3=51, rooms=(0,)),
|
||||
RoomGroupRequirementData(group_id=40, subgroup_0=14, subgroup_1=None, subgroup_2=74, subgroup_3=80, rooms=(225, 256, 293, 292, 294)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=14, subgroup_1=30, subgroup_2=None, subgroup_3=None, rooms=(291,)),
|
||||
RoomGroupRequirementData(group_id=23, subgroup_0=64, subgroup_1=None, subgroup_2=None, subgroup_3=63, rooms=()),
|
||||
RoomGroupRequirementData(group_id=9, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=29, rooms=(227,)),
|
||||
RoomGroupRequirementData(group_id=11, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=61, rooms=()),
|
||||
RoomGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=49, rooms=()),
|
||||
RoomGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=60, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=21, subgroup_0=None, subgroup_1=None, subgroup_2=58, subgroup_3=62, rooms=()),
|
||||
RoomGroupRequirementData(group_id=28, subgroup_0=None, subgroup_1=None, subgroup_2=38, subgroup_3=82, rooms=(14, 126, 142, 158, 190)),
|
||||
RoomGroupRequirementData(group_id=12, subgroup_0=None, subgroup_1=None, subgroup_2=48, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=26, subgroup_0=None, subgroup_1=None, subgroup_2=56, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=20, subgroup_0=None, subgroup_1=None, subgroup_2=57, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=32, subgroup_0=None, subgroup_1=44, subgroup_2=59, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=3, subgroup_0=93, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(81,)),
|
||||
RoomGroupRequirementData(group_id=42, subgroup_0=21, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(286,)),
|
||||
RoomGroupRequirementData(group_id=10, subgroup_0=47, subgroup_1=None, subgroup_2=46, subgroup_3=None, rooms=(92, 117, 185, 217)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=34, subgroup_3=None, rooms=(54, 70, 102, 118)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=32, subgroup_2=None, subgroup_3=None, rooms=(62, 159)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=31, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(127,)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=35, subgroup_3=None, rooms=(57, 73, 86, 87, 104, 141)),
|
||||
RoomGroupRequirementData(group_id=37, subgroup_0=31, subgroup_1=None, subgroup_2=39, subgroup_3=82, rooms=(36, 180, 181, 198, 199, 214)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(23, 42, 68, 76, 86, 88, 89, 103, 104, 126, 139, 235, 251)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(23, 42, 76, 89, 103, 104, 126, 139, 235, 251)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(11, 19, 27, 30, 42, 43, 49, 61, 62, 91, 107, 119, 135, 139, 145, 146, 155, 157, 161, 171, 182, 191, 193, 196, 239)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(11, 19, 27, 30, 42, 43, 49, 53, 62, 91, 107, 119, 135, 139, 145, 146, 155, 157, 161, 171, 182, 191, 193, 196, 239)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(19, 35, 150, 165, 195, 197, 213)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(19, 35, 150, 165, 197, 213)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(26, 38, 43, 64, 74, 87, 107, 123)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(38, 43, 64, 74, 87, 107, 123, 206)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(2, 88, 100, 140, 267)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(26, 61, 68, 86, 94, 124, 149, 195)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(4, 63, 206)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(53, 55, 118)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=34, subgroup_3=None, rooms=(40,)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=37, subgroup_3=None, rooms=(151,)),
|
||||
)
|
||||
@@ -1,295 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class EnemySpriteRequirementData(NamedTuple):
|
||||
sprite_name: str
|
||||
sprite_id: int
|
||||
boss: bool
|
||||
overlord: bool
|
||||
do_not_randomize: bool
|
||||
killable: bool
|
||||
npc: bool
|
||||
never_use_dungeon: bool
|
||||
never_use_overworld: bool
|
||||
cannot_have_key: bool
|
||||
is_object: bool
|
||||
absorbable: bool
|
||||
is_water_sprite: bool
|
||||
is_enemy_sprite: bool
|
||||
group_ids: tuple[int, ...]
|
||||
subgroup_0: tuple[int, ...]
|
||||
subgroup_1: tuple[int, ...]
|
||||
subgroup_2: tuple[int, ...]
|
||||
subgroup_3: tuple[int, ...]
|
||||
parameters: Optional[int]
|
||||
special_glitched: bool
|
||||
excluded_rooms: tuple[int, ...]
|
||||
dont_randomize_rooms: tuple[int, ...]
|
||||
spawnable_rooms: tuple[int, ...]
|
||||
|
||||
ENEMY_SPRITE_REQUIREMENTS = (
|
||||
EnemySpriteRequirementData(sprite_name='RavenSprite', sprite_id=0, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17, 25), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VultureSprite', sprite_id=1, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EmptySprite', sprite_id=3, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PullSwitch_GoodSprite', sprite_id=4, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PullSwitch_TrapSprite', sprite_id=6, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Octorok_OneWaySprite', sprite_id=8, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12, 24), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MoldormSprite', sprite_id=9, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(48,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Octorok_FourWaySprite', sprite_id=10, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ChickenSprite', sprite_id=11, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(21, 80), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BuzzblobSprite', sprite_id=13, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SnapdragonSprite', sprite_id=14, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(23,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OctoballoonSprite', sprite_id=15, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OctoballoonHatchlingsSprite', sprite_id=16, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HinoxSprite', sprite_id=17, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MoblinSprite', sprite_id=18, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(23,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MiniHelmasaurSprite', sprite_id=19, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GargoylesDomainGateSprite', sprite_id=20, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AntifairySprite', sprite_id=21, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(64, 210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SahasrahlaAginahSprite', sprite_id=22, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(76,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BushHoarderSprite', sprite_id=23, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MiniMoldormSprite', sprite_id=24, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PoeSprite', sprite_id=25, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(14, 21), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DwarvesSprite', sprite_id=26, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(), subgroup_3=(21,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowInWall_MaybeSprite', sprite_id=27, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StatueSprite', sprite_id=28, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268, 63), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WeathervaneSprite', sprite_id=29, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CrystalSwitchSprite', sprite_id=30, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BugCatchingKidSprite', sprite_id=31, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(81,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SluggulaSprite', sprite_id=32, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PushSwitchSprite', sprite_id=33, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(83,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RopaSprite', sprite_id=34, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedBariSprite', sprite_id=35, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(127,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueBariSprite', sprite_id=36, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(127,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TalkingTreeSprite', sprite_id=37, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(21,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HardhatBeetleSprite', sprite_id=38, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DeadrockSprite', sprite_id=39, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(127, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StorytellersSprite', sprite_id=40, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlindHideoutAttendantSprite', sprite_id=41, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14, 79), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SweepingLadySprite', sprite_id=42, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MultipurposeSpriteSprite', sprite_id=43, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LumberjacksSprite', sprite_id=44, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(74,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TelepathicStones_NoIdeaWhatThisActuallyIsLikelyUnusedSprite', sprite_id=45, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FluteBoysNotesSprite', sprite_id=46, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RaceHPNPCsSprite', sprite_id=47, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Person_MaybeSprite', sprite_id=48, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FortuneTellerSprite', sprite_id=49, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AngryBrothersSprite', sprite_id=50, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PullForRupeesSpriteSprite', sprite_id=51, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ScaredGirl2Sprite', sprite_id=52, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='InnkeeperSprite', sprite_id=53, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WitchSprite', sprite_id=54, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(76,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WaterfallSprite', sprite_id=55, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowTargetSprite', sprite_id=56, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AverageMiddleAgedManSprite', sprite_id=57, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HalfMagicBatSprite', sprite_id=58, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(29,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DashItemSprite', sprite_id=59, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VillageKidSprite', sprite_id=60, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Signs_ChickenLadyAlsoShowedUp_ScaredLadiesOutsideHousesSprite', sprite_id=61, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RockHoarderSprite', sprite_id=62, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TutorialSoldierSprite', sprite_id=63, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LightningLockSprite', sprite_id=64, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueSwordSoldier_DetectPlayerSprite', sprite_id=65, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenSwordSoldierSprite', sprite_id=66, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedSpearSoldierSprite', sprite_id=67, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AssaultSwordSoldierSprite', sprite_id=68, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenSpearSoldierSprite', sprite_id=69, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueArcherSprite', sprite_id=70, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(72,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenArcherSprite', sprite_id=71, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(72,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedJavelinSoldierSprite', sprite_id=72, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedJavelinSoldier2Sprite', sprite_id=73, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedBombSoldiersSprite', sprite_id=74, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenSoldierRecruits_HMKnightSprite', sprite_id=75, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(73,), subgroup_2=(19,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GeldmanSprite', sprite_id=76, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RabbitSprite', sprite_id=77, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PopoSprite', sprite_id=78, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Popo2Sprite', sprite_id=79, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CannonBallsSprite', sprite_id=80, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArmosSprite', sprite_id=81, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GiantZoraSprite', sprite_id=82, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(68,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArmosKnightsSprite', sprite_id=83, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(29,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LanmolasSprite', sprite_id=84, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(49,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FireballZoraSprite', sprite_id=85, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12, 24), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WalkingZoraSprite', sprite_id=86, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(68,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DesertPalaceBarriersSprite', sprite_id=87, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CrabSprite', sprite_id=88, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BirdSprite', sprite_id=89, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SquirrelSprite', sprite_id=90, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Spark_LeftToRightSprite', sprite_id=91, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Spark_RightToLeftSprite', sprite_id=92, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Roller_VerticalMovingSprite', sprite_id=93, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Roller_VerticalMoving2Sprite', sprite_id=94, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RollerSprite', sprite_id=95, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Roller_HorizontalMovingSprite', sprite_id=96, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BeamosSprite', sprite_id=97, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MasterSwordSprite', sprite_id=98, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Devalant_NonShooterSprite', sprite_id=99, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Devalant_ShooterSprite', sprite_id=100, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ShootingGalleryProprietorSprite', sprite_id=101, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_RightSprite', sprite_id=102, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_LeftSprite', sprite_id=103, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_DownSprite', sprite_id=104, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_UpSprite', sprite_id=105, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BallNChainTrooperSprite', sprite_id=106, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CannonSoldierSprite', sprite_id=107, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MirrorPortalSprite', sprite_id=108, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RatSprite', sprite_id=109, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RopeSprite', sprite_id=110, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KeeseSprite', sprite_id=111, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LeeverSprite', sprite_id=113, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ActivatoForThePonds_WhereYouThrowInItemsSprite', sprite_id=114, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='UnclePriestSprite', sprite_id=115, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(71, 81), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RunningManSprite', sprite_id=116, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BottleSalesmanSprite', sprite_id=117, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PrincessZeldaSprite', sprite_id=118, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VillageElderSprite', sprite_id=120, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AgahnimSprite', sprite_id=122, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(85,), subgroup_1=(26, 61), subgroup_2=(66,), subgroup_3=(67,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AgahnimEnergyBallSprite', sprite_id=123, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FloatingStalfosHeadSprite', sprite_id=124, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BigSpikeTrapSprite', sprite_id=125, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GuruguruBar_ClockwiseSprite', sprite_id=126, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(181, 150), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GuruguruBar_CounterClockwiseSprite', sprite_id=127, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(181, 150), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WinderSprite', sprite_id=128, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WaterTektiteSprite', sprite_id=129, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(40,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AntifairyCircleSprite', sprite_id=130, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenEyegoreSprite', sprite_id=131, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedEyegoreSprite', sprite_id=132, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KodongosSprite', sprite_id=134, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(42,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MothulaSprite', sprite_id=136, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(56,), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MothulasBeamSprite', sprite_id=137, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(56,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SpikeTrapSprite', sprite_id=138, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(40, 11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GibdoSprite', sprite_id=139, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrghusSprite', sprite_id=140, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrghusSpawnSprite', sprite_id=141, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TerrorpinSprite', sprite_id=142, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(42,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SlimeSprite_JumpsOutOfTheFloor', sprite_id=143, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WallmasterSprite', sprite_id=144, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(1, 2, 17, 33, 34, 50, 65, 66, 80, 81, 82, 96, 97, 98, 112, 113, 114, 128, 129, 130, 137, 153, 168, 169, 170, 184, 185, 186, 200, 201, 216, 217, 218, 51, 67, 83, 99, 115, 116, 117, 131, 132, 133, 7, 23, 39, 49, 119, 135, 167, 32, 48, 64, 176, 192, 208, 224, 9, 10, 11, 25, 26, 27, 42, 43, 58, 59, 74, 75, 90, 106, 6, 22, 38, 40, 52, 53, 54, 55, 56, 70, 84, 102, 118, 41, 57, 73, 86, 87, 88, 89, 103, 104, 68, 69, 100, 101, 171, 172, 187, 188, 203, 204, 219, 220, 14, 30, 31, 46, 62, 63, 78, 79, 94, 95, 110, 126, 127, 142, 158, 159, 174, 175, 190, 191, 206, 222, 144, 145, 146, 147, 151, 152, 160, 161, 162, 163, 177, 178, 179, 193, 194, 195, 209, 210, 4, 19, 20, 21, 35, 36, 164, 180, 181, 182, 183, 196, 197, 198, 199, 213, 214, 12, 13, 28, 29, 61, 76, 77, 91, 92, 93, 107, 108, 109, 123, 124, 125, 139, 140, 141, 149, 150, 155, 156, 157, 165, 166)),
|
||||
EnemySpriteRequirementData(sprite_name='StalfosKnightSprite', sprite_id=145, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HelmasaurKingSprite', sprite_id=146, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(58,), subgroup_3=(62,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BumperSprite', sprite_id=147, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SwimmersEvilSprite', sprite_id=148, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_RightSprite', sprite_id=149, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_LeftSprite', sprite_id=150, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_DownSprite', sprite_id=151, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_UpSprite', sprite_id=152, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PengatorSprite', sprite_id=153, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(38,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KyameronWaterSplashSprite', sprite_id=154, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(40,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WizzrobeSprite', sprite_id=155, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37, 41), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VerminHorizontalSprite', sprite_id=156, boss=False, overlord=False, do_not_randomize=True, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VerminVerticalSprite', sprite_id=157, boss=False, overlord=False, do_not_randomize=True, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Ostrich_HauntedGroveSprite', sprite_id=158, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(78,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FluteSprite', sprite_id=159, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Birds_HauntedGroveSprite', sprite_id=160, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(78,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FreezorSprite', sprite_id=161, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(38,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KholdstareSprite', sprite_id=162, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(60,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KholdstaresShellSprite', sprite_id=163, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FallingIceSprite', sprite_id=164, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(60,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueZazakSprite', sprite_id=165, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedZazakSprite', sprite_id=166, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StalfosSprite', sprite_id=167, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BomberFlyingCreaturesFromDarkworldSprite', sprite_id=168, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BomberFlyingCreaturesFromDarkworld2Sprite', sprite_id=169, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PikitSprite', sprite_id=170, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MaidenSprite', sprite_id=171, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AppleSprite', sprite_id=172, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LostOldManSprite', sprite_id=173, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(28,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DownPipeSprite', sprite_id=174, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='UpPipeSprite', sprite_id=175, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RightPipeSprite', sprite_id=176, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LeftPipeSprite', sprite_id=177, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GoodBee_AgainMaybeSprite', sprite_id=178, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HylianInscriptionSprite', sprite_id=179, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ThiefsChestSprite', sprite_id=180, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(21,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombSalesmanSprite', sprite_id=181, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KikiSprite', sprite_id=182, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(25,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MaidenInBlindDungeonSprite', sprite_id=183, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MimicSprite', sprite_id=184, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FeudingFriendsOnDeathMountainSprite', sprite_id=185, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(20,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WhirlpoolSprite', sprite_id=186, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(255, 274, 287)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(271, 272)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(272,)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(280,)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(291, 292)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(291, 292)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(80,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(293,)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(21,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(286,)),
|
||||
EnemySpriteRequirementData(sprite_name='DrunkInTheInnSprite', sprite_id=188, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(80,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Vitreous_LargeEyeballSprite', sprite_id=189, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Vitreous_SmallEyeballSprite', sprite_id=190, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VitreousLightningSprite', sprite_id=191, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CatFish_QuakeMedallionSprite', sprite_id=192, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(24,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AgahnimTeleportingZeldaToDarkworldSprite', sprite_id=193, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(85,), subgroup_1=(61,), subgroup_2=(66,), subgroup_3=(67,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BouldersSprite', sprite_id=194, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Gibo_FloatingBlobSprite', sprite_id=195, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ThiefSprite', sprite_id=196, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(14, 21), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MedusaSprite', sprite_id=197, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FourWayFireballSpittersSprite', sprite_id=198, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HokkuBokkuSprite', sprite_id=199, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BigFairyWhoHealsYouSprite', sprite_id=200, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TektiteSprite', sprite_id=201, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ChainChompSprite', sprite_id=202, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TrinexxSprite', sprite_id=203, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AnotherPartOfTrinexxSprite', sprite_id=204, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='YetAnotherPartOfTrinexxSprite', sprite_id=205, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlindTheThiefSprite', sprite_id=206, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(59,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SwamolaSprite', sprite_id=207, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(25,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LynelSprite', sprite_id=208, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(20,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BunnyBeamSprite', sprite_id=209, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FloppingFishSprite', sprite_id=210, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StalSprite', sprite_id=211, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LandmineSprite', sprite_id=212, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DiggingGameProprietorSprite', sprite_id=213, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(42,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GanonSprite', sprite_id=214, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(33,), subgroup_1=(65,), subgroup_2=(69,), subgroup_3=(51,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CopyOfGanon_ExceptInvincibleSprite', sprite_id=215, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HeartSprite', sprite_id=216, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenRupeeSprite', sprite_id=217, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueRupeeSprite', sprite_id=218, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedRupeeSprite', sprite_id=219, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombRefill1Sprite', sprite_id=220, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombRefill4Sprite', sprite_id=221, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombRefill8Sprite', sprite_id=222, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SmallMagicRefillSprite', sprite_id=223, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FullMagicRefillSprite', sprite_id=224, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowRefill5Sprite', sprite_id=225, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowRefill10Sprite', sprite_id=226, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FairySprite', sprite_id=227, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KeySprite', sprite_id=228, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BigKeySprite', sprite_id=229, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ShieldEaterSprite', sprite_id=230, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MushroomSprite', sprite_id=231, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FakeMasterSwordSprite', sprite_id=232, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MagicShopDude_HisItemsIncludingTheMagicPowderSprite', sprite_id=233, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HeartContainerSprite', sprite_id=234, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HeartPieceSprite', sprite_id=235, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BushesSprite', sprite_id=236, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatformSprite', sprite_id=237, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MantleSprite', sprite_id=238, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(93,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused1Sprite', sprite_id=239, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused2Sprite', sprite_id=240, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused3Sprite', sprite_id=241, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MedallionTabletSprite', sprite_id=242, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OW_OL_FallingRocks', sprite_id=244, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_CanonBalls_EP4Walls', sprite_id=258, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_CanonBalls_EPEntrance', sprite_id=259, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_StalfosHeadTrap', sprite_id=261, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_BombDrop_RopeTrap', sprite_id=262, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_MovingFloor', sprite_id=263, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_SlimeDropper', sprite_id=264, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_Wallmaster', sprite_id=265, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_FloorDrop_Square', sprite_id=266, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_FloorDrop_Path', sprite_id=267, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_RightEvil_PirogusuSpawner', sprite_id=272, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_LeftEvil_PirogusuSpawner', sprite_id=273, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_DownEvil_PirogusuSpawner', sprite_id=274, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_UpEvil_PirogusuSpawner', sprite_id=275, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_FlyingFloorTileTrap', sprite_id=276, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_WizzrobeSpawner', sprite_id=277, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37, 41), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_BlackSpawn_Zoro_BombHole', sprite_id=278, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_4Skull_Trap_Pot', sprite_id=279, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_Stalfos_Spawn_Trap_EP', sprite_id=280, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_ArmosKnight_Trigger', sprite_id=281, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_BombDrop_BombTrap', sprite_id=282, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
)
|
||||
@@ -1,131 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class OverworldGroupRequirementData(NamedTuple):
|
||||
group_id: Optional[int]
|
||||
subgroup_0: Optional[int]
|
||||
subgroup_1: Optional[int]
|
||||
subgroup_2: Optional[int]
|
||||
subgroup_3: Optional[int]
|
||||
areas: tuple[int, ...]
|
||||
|
||||
AREA_IDS = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207)
|
||||
DO_NOT_RANDOMIZE_AREA_IDS = frozenset((1,
|
||||
4,
|
||||
6,
|
||||
8,
|
||||
9,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
25,
|
||||
28,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
35,
|
||||
36,
|
||||
38,
|
||||
39,
|
||||
49,
|
||||
54,
|
||||
56,
|
||||
57,
|
||||
61,
|
||||
62,
|
||||
65,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
78,
|
||||
89,
|
||||
92,
|
||||
95,
|
||||
96,
|
||||
97,
|
||||
99,
|
||||
100,
|
||||
102,
|
||||
103,
|
||||
113,
|
||||
118,
|
||||
120,
|
||||
121,
|
||||
125,
|
||||
126,
|
||||
42,
|
||||
106,
|
||||
130,
|
||||
131,
|
||||
132,
|
||||
133,
|
||||
134,
|
||||
135,
|
||||
136,
|
||||
137,
|
||||
138,
|
||||
139,
|
||||
140,
|
||||
141,
|
||||
142,
|
||||
143,
|
||||
186,
|
||||
250,
|
||||
145,
|
||||
148,
|
||||
150,
|
||||
152,
|
||||
153,
|
||||
155,
|
||||
156,
|
||||
158,
|
||||
169,
|
||||
172,
|
||||
175,
|
||||
176,
|
||||
177,
|
||||
179,
|
||||
180,
|
||||
182,
|
||||
183,
|
||||
193,
|
||||
198,
|
||||
200,
|
||||
274,
|
||||
275,
|
||||
276,
|
||||
277,
|
||||
278,
|
||||
279,
|
||||
281,
|
||||
288))
|
||||
|
||||
FORCED_GROUP_REQUIREMENTS = (
|
||||
OverworldGroupRequirementData(group_id=7, subgroup_0=None, subgroup_1=None, subgroup_2=74, subgroup_3=None, areas=(2,)),
|
||||
OverworldGroupRequirementData(group_id=16, subgroup_0=None, subgroup_1=None, subgroup_2=18, subgroup_3=16, areas=(3, 147)),
|
||||
OverworldGroupRequirementData(group_id=7, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=17, areas=(10, 154)),
|
||||
OverworldGroupRequirementData(group_id=4, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(15, 159)),
|
||||
OverworldGroupRequirementData(group_id=3, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=14, areas=(20, 164)),
|
||||
OverworldGroupRequirementData(group_id=1, subgroup_0=None, subgroup_1=None, subgroup_2=76, subgroup_3=63, areas=(27, 171)),
|
||||
OverworldGroupRequirementData(group_id=6, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(34, 40, 178, 184)),
|
||||
OverworldGroupRequirementData(group_id=8, subgroup_0=None, subgroup_1=None, subgroup_2=18, subgroup_3=None, areas=(48, 192)),
|
||||
OverworldGroupRequirementData(group_id=10, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(58, 202)),
|
||||
OverworldGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=24, subgroup_3=None, areas=(79, 223)),
|
||||
OverworldGroupRequirementData(group_id=21, subgroup_0=21, subgroup_1=None, subgroup_2=None, subgroup_3=21, areas=(98, 242)),
|
||||
OverworldGroupRequirementData(group_id=27, subgroup_0=None, subgroup_1=42, subgroup_2=None, subgroup_3=None, areas=(104, 248)),
|
||||
OverworldGroupRequirementData(group_id=13, subgroup_0=None, subgroup_1=None, subgroup_2=76, subgroup_3=None, areas=(22, 166)),
|
||||
OverworldGroupRequirementData(group_id=29, subgroup_0=None, subgroup_1=77, subgroup_2=None, subgroup_3=21, areas=(105, 249)),
|
||||
OverworldGroupRequirementData(group_id=15, subgroup_0=None, subgroup_1=None, subgroup_2=78, subgroup_3=None, areas=(42, 186)),
|
||||
OverworldGroupRequirementData(group_id=17, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=76, areas=(106, 250)),
|
||||
OverworldGroupRequirementData(group_id=12, subgroup_0=None, subgroup_1=None, subgroup_2=55, subgroup_3=54, areas=(128, 272)),
|
||||
OverworldGroupRequirementData(group_id=14, subgroup_0=None, subgroup_1=None, subgroup_2=12, subgroup_3=68, areas=(129, 273)),
|
||||
OverworldGroupRequirementData(group_id=26, subgroup_0=15, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(146,)),
|
||||
OverworldGroupRequirementData(group_id=23, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=25, areas=(94, 238)),
|
||||
)
|
||||
@@ -1,107 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class PotDataRecord(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
reserved: int
|
||||
|
||||
|
||||
class PotRoomDataRecord(NamedTuple):
|
||||
room_id: int
|
||||
pots: tuple[PotDataRecord, ...]
|
||||
items: tuple[int, ...]
|
||||
|
||||
POT_ROOMS = (
|
||||
PotRoomDataRecord(room_id=4, pots=(PotDataRecord(x=162, y=25, reserved=0), PotDataRecord(x=152, y=25, reserved=0), PotDataRecord(x=152, y=22, reserved=0), PotDataRecord(x=162, y=22, reserved=0), PotDataRecord(x=240, y=19, reserved=0), PotDataRecord(x=204, y=19, reserved=0),), items=(10, 10)),
|
||||
PotRoomDataRecord(room_id=9, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=12, reserved=0),), items=(1, 11, 136)),
|
||||
PotRoomDataRecord(room_id=10, pots=(PotDataRecord(x=204, y=11, reserved=0), PotDataRecord(x=156, y=17, reserved=0), PotDataRecord(x=96, y=8, reserved=0), PotDataRecord(x=100, y=7, reserved=0), PotDataRecord(x=160, y=17, reserved=0), PotDataRecord(x=104, y=8, reserved=0), PotDataRecord(x=100, y=9, reserved=0),), items=(11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=17, pots=(PotDataRecord(x=152, y=19, reserved=0), PotDataRecord(x=152, y=15, reserved=0), PotDataRecord(x=144, y=15, reserved=0), PotDataRecord(x=10, y=15, reserved=0), PotDataRecord(x=144, y=19, reserved=0), PotDataRecord(x=160, y=19, reserved=0),), items=(11, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=21, pots=(PotDataRecord(x=96, y=4, reserved=0), PotDataRecord(x=100, y=4, reserved=0), PotDataRecord(x=104, y=4, reserved=0), PotDataRecord(x=108, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=12, y=6, reserved=0), PotDataRecord(x=16, y=6, reserved=0), PotDataRecord(x=20, y=6, reserved=0), PotDataRecord(x=70, y=11, reserved=0),), items=(1, 7, 9, 9, 10, 11, 12, 12, 13)),
|
||||
PotRoomDataRecord(room_id=22, pots=(PotDataRecord(x=188, y=3, reserved=0), PotDataRecord(x=192, y=3, reserved=0), PotDataRecord(x=188, y=4, reserved=0), PotDataRecord(x=192, y=4, reserved=0), PotDataRecord(x=188, y=5, reserved=0), PotDataRecord(x=192, y=5, reserved=0), PotDataRecord(x=188, y=6, reserved=0), PotDataRecord(x=192, y=6, reserved=0), PotDataRecord(x=240, y=19, reserved=0),), items=(8, 9, 9, 10, 10, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=26, pots=(PotDataRecord(x=232, y=19, reserved=0), PotDataRecord(x=212, y=19, reserved=0), PotDataRecord(x=28, y=5, reserved=0), PotDataRecord(x=32, y=5, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0),), items=(10, 10, 10, 10)),
|
||||
PotRoomDataRecord(room_id=33, pots=(PotDataRecord(x=100, y=28, reserved=0), PotDataRecord(x=168, y=24, reserved=0), PotDataRecord(x=48, y=28, reserved=0), PotDataRecord(x=82, y=28, reserved=0), PotDataRecord(x=160, y=20, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=35, pots=(PotDataRecord(x=86, y=26, reserved=0), PotDataRecord(x=90, y=26, reserved=0), PotDataRecord(x=94, y=26, reserved=0), PotDataRecord(x=98, y=26, reserved=0), PotDataRecord(x=102, y=26, reserved=0),), items=(1, 10, 11)),
|
||||
PotRoomDataRecord(room_id=36, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=12, reserved=0), PotDataRecord(x=48, y=12, reserved=0),), items=(1, 11, 12, 7)),
|
||||
PotRoomDataRecord(room_id=38, pots=(PotDataRecord(x=28, y=4, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=150, y=19, reserved=2), PotDataRecord(x=22, y=26, reserved=2), PotDataRecord(x=220, y=26, reserved=0),), items=(7, 9, 10, 12, 136)),
|
||||
PotRoomDataRecord(room_id=39, pots=(PotDataRecord(x=214, y=19, reserved=0), PotDataRecord(x=214, y=20, reserved=0), PotDataRecord(x=166, y=20, reserved=0), PotDataRecord(x=214, y=21, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=80, y=28, reserved=0), PotDataRecord(x=84, y=28, reserved=0), PotDataRecord(x=102, y=17, reserved=0), PotDataRecord(x=98, y=17, reserved=0), PotDataRecord(x=106, y=17, reserved=0), PotDataRecord(x=166, y=21, reserved=0), PotDataRecord(x=166, y=19, reserved=0), PotDataRecord(x=92, y=12, reserved=0), PotDataRecord(x=160, y=12, reserved=0),), items=(1, 1, 10, 11, 7, 7)),
|
||||
PotRoomDataRecord(room_id=43, pots=(PotDataRecord(x=16, y=5, reserved=2), PotDataRecord(x=44, y=5, reserved=2), PotDataRecord(x=16, y=6, reserved=2), PotDataRecord(x=44, y=6, reserved=2), PotDataRecord(x=16, y=7, reserved=2), PotDataRecord(x=44, y=7, reserved=2), PotDataRecord(x=146, y=21, reserved=0), PotDataRecord(x=170, y=21, reserved=0), PotDataRecord(x=146, y=22, reserved=0), PotDataRecord(x=170, y=22, reserved=0),), items=(9, 9, 10, 10, 10, 10, 11, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=47, pots=(PotDataRecord(x=28, y=7, reserved=0), PotDataRecord(x=32, y=7, reserved=0), PotDataRecord(x=28, y=9, reserved=0), PotDataRecord(x=32, y=9, reserved=0), PotDataRecord(x=172, y=19, reserved=0), PotDataRecord(x=180, y=19, reserved=0), PotDataRecord(x=104, y=27, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(7, 7, 7, 7, 11, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=53, pots=(PotDataRecord(x=60, y=6, reserved=1), PotDataRecord(x=20, y=8, reserved=0), PotDataRecord(x=24, y=8, reserved=0), PotDataRecord(x=28, y=8, reserved=0), PotDataRecord(x=32, y=8, reserved=0), PotDataRecord(x=36, y=8, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=76, y=23, reserved=1), PotDataRecord(x=88, y=23, reserved=1), PotDataRecord(x=100, y=27, reserved=1), PotDataRecord(x=242, y=28, reserved=1), PotDataRecord(x=240, y=22, reserved=1), PotDataRecord(x=76, y=28, reserved=1),), items=(7, 7, 7, 7, 7, 8, 11)),
|
||||
PotRoomDataRecord(room_id=54, pots=(PotDataRecord(x=108, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=10, y=16, reserved=0), PotDataRecord(x=114, y=16, reserved=0),), items=(8, 10, 11)),
|
||||
PotRoomDataRecord(room_id=55, pots=(PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=60, y=6, reserved=0),), items=(8,)),
|
||||
PotRoomDataRecord(room_id=56, pots=(PotDataRecord(x=164, y=12, reserved=0), PotDataRecord(x=164, y=13, reserved=0), PotDataRecord(x=164, y=18, reserved=0), PotDataRecord(x=164, y=19, reserved=0),), items=(8, 7, 10, 10)),
|
||||
PotRoomDataRecord(room_id=57, pots=(PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=100, y=22, reserved=0), PotDataRecord(x=100, y=26, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(9, 9, 11, 12)),
|
||||
PotRoomDataRecord(room_id=60, pots=(PotDataRecord(x=24, y=8, reserved=0), PotDataRecord(x=64, y=12, reserved=0), PotDataRecord(x=20, y=14, reserved=0), PotDataRecord(x=68, y=18, reserved=0), PotDataRecord(x=96, y=19, reserved=0), PotDataRecord(x=64, y=20, reserved=0), PotDataRecord(x=64, y=26, reserved=0),), items=(1, 7, 7, 7, 7, 11, 12)),
|
||||
PotRoomDataRecord(room_id=61, pots=(PotDataRecord(x=76, y=12, reserved=0), PotDataRecord(x=112, y=12, reserved=0), PotDataRecord(x=24, y=22, reserved=0), PotDataRecord(x=40, y=22, reserved=0), PotDataRecord(x=32, y=24, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=36, y=26, reserved=0),), items=(9, 7, 10, 10, 11, 11, 13)),
|
||||
PotRoomDataRecord(room_id=62, pots=(PotDataRecord(x=96, y=6, reserved=0), PotDataRecord(x=100, y=6, reserved=0), PotDataRecord(x=88, y=10, reserved=0), PotDataRecord(x=92, y=10, reserved=0),), items=(10, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=63, pots=(PotDataRecord(x=12, y=25, reserved=0), PotDataRecord(x=20, y=25, reserved=0), PotDataRecord(x=12, y=26, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=12, y=27, reserved=0), PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=28, y=23, reserved=0),), items=(1, 1, 8, 10, 10, 11, 136)),
|
||||
PotRoomDataRecord(room_id=65, pots=(PotDataRecord(x=100, y=10, reserved=0), PotDataRecord(x=52, y=15, reserved=0), PotDataRecord(x=52, y=16, reserved=0), PotDataRecord(x=148, y=22, reserved=0),), items=(1, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=67, pots=(PotDataRecord(x=112, y=28, reserved=1), PotDataRecord(x=76, y=28, reserved=1), PotDataRecord(x=76, y=20, reserved=1), PotDataRecord(x=66, y=4, reserved=0), PotDataRecord(x=78, y=4, reserved=0), PotDataRecord(x=66, y=9, reserved=0), PotDataRecord(x=78, y=9, reserved=0), PotDataRecord(x=112, y=20, reserved=1),), items=(8, 9, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=69, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=108, y=11, reserved=0), PotDataRecord(x=48, y=12, reserved=0), PotDataRecord(x=220, y=16, reserved=0), PotDataRecord(x=236, y=16, reserved=0),), items=(9, 9, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=73, pots=(PotDataRecord(x=156, y=27, reserved=0), PotDataRecord(x=172, y=24, reserved=0), PotDataRecord(x=172, y=23, reserved=0), PotDataRecord(x=144, y=20, reserved=0), PotDataRecord(x=104, y=15, reserved=0), PotDataRecord(x=104, y=16, reserved=0), PotDataRecord(x=144, y=19, reserved=0), PotDataRecord(x=172, y=20, reserved=0), PotDataRecord(x=144, y=27, reserved=0), PotDataRecord(x=172, y=28, reserved=0), PotDataRecord(x=160, y=27, reserved=0),), items=(11, 11, 12, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=78, pots=(PotDataRecord(x=48, y=10, reserved=2), PotDataRecord(x=140, y=11, reserved=2), PotDataRecord(x=28, y=12, reserved=2), PotDataRecord(x=112, y=12, reserved=0),), items=(136, 11, 12)),
|
||||
PotRoomDataRecord(room_id=83, pots=(PotDataRecord(x=92, y=11, reserved=0), PotDataRecord(x=96, y=11, reserved=0), PotDataRecord(x=100, y=11, reserved=0), PotDataRecord(x=104, y=11, reserved=0),), items=(8, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=84, pots=(PotDataRecord(x=186, y=25, reserved=0), PotDataRecord(x=186, y=26, reserved=0), PotDataRecord(x=186, y=27, reserved=0), PotDataRecord(x=186, y=28, reserved=0),), items=(7, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=86, pots=(PotDataRecord(x=100, y=6, reserved=1), PotDataRecord(x=96, y=10, reserved=1), PotDataRecord(x=92, y=10, reserved=1), PotDataRecord(x=48, y=20, reserved=1), PotDataRecord(x=20, y=6, reserved=0), PotDataRecord(x=40, y=6, reserved=0), PotDataRecord(x=24, y=7, reserved=0), PotDataRecord(x=36, y=7, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=48, y=8, reserved=0), PotDataRecord(x=24, y=9, reserved=0), PotDataRecord(x=36, y=9, reserved=0), PotDataRecord(x=20, y=10, reserved=0), PotDataRecord(x=40, y=10, reserved=0), PotDataRecord(x=12, y=20, reserved=1),), items=(7, 7, 11, 11, 8, 12, 12, 12, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=87, pots=(PotDataRecord(x=92, y=7, reserved=0), PotDataRecord(x=12, y=20, reserved=2), PotDataRecord(x=92, y=23, reserved=0), PotDataRecord(x=100, y=23, reserved=0), PotDataRecord(x=84, y=25, reserved=0), PotDataRecord(x=76, y=27, reserved=0), PotDataRecord(x=48, y=20, reserved=2), PotDataRecord(x=30, y=22, reserved=2),), items=(7, 10, 11, 12, 12, 12, 13, 136)),
|
||||
PotRoomDataRecord(room_id=88, pots=(PotDataRecord(x=96, y=9, reserved=0), PotDataRecord(x=92, y=8, reserved=0), PotDataRecord(x=108, y=8, reserved=0), PotDataRecord(x=108, y=6, reserved=0), PotDataRecord(x=104, y=5, reserved=0), PotDataRecord(x=92, y=6, reserved=0), PotDataRecord(x=12, y=12, reserved=0), PotDataRecord(x=16, y=7, reserved=0), PotDataRecord(x=96, y=5, reserved=0), PotDataRecord(x=100, y=5, reserved=0), PotDataRecord(x=12, y=7, reserved=0), PotDataRecord(x=92, y=7, reserved=0), PotDataRecord(x=108, y=7, reserved=0), PotDataRecord(x=16, y=8, reserved=0), PotDataRecord(x=100, y=9, reserved=0), PotDataRecord(x=104, y=9, reserved=0),), items=(10, 10, 11, 11, 12, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=91, pots=(PotDataRecord(x=218, y=37, reserved=0), PotDataRecord(x=222, y=37, reserved=0), PotDataRecord(x=226, y=37, reserved=0),), items=(136,)),
|
||||
PotRoomDataRecord(room_id=92, pots=(PotDataRecord(x=228, y=25, reserved=0), PotDataRecord(x=104, y=24, reserved=0), PotDataRecord(x=228, y=22, reserved=0), PotDataRecord(x=216, y=25, reserved=0), PotDataRecord(x=84, y=24, reserved=0), PotDataRecord(x=216, y=22, reserved=0), PotDataRecord(x=94, y=22, reserved=0), PotDataRecord(x=94, y=26, reserved=0),), items=(10, 13)),
|
||||
PotRoomDataRecord(room_id=93, pots=(PotDataRecord(x=16, y=5, reserved=0), PotDataRecord(x=44, y=5, reserved=0), PotDataRecord(x=16, y=11, reserved=0), PotDataRecord(x=44, y=11, reserved=0), PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=12, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(1, 7, 9, 9, 10, 10, 10, 12)),
|
||||
PotRoomDataRecord(room_id=94, pots=(PotDataRecord(x=92, y=4, reserved=0), PotDataRecord(x=96, y=4, reserved=0), PotDataRecord(x=76, y=8, reserved=0), PotDataRecord(x=112, y=8, reserved=0),), items=(11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=99, pots=(PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=48, y=12, reserved=0), PotDataRecord(x=48, y=8, reserved=0), PotDataRecord(x=12, y=12, reserved=0),), items=(8, 11)),
|
||||
PotRoomDataRecord(room_id=100, pots=(PotDataRecord(x=12, y=22, reserved=0), PotDataRecord(x=16, y=22, reserved=0), PotDataRecord(x=20, y=22, reserved=0), PotDataRecord(x=36, y=28, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(10, 10, 10, 10, 12, 12, 136)),
|
||||
PotRoomDataRecord(room_id=102, pots=(PotDataRecord(x=48, y=37, reserved=0), PotDataRecord(x=52, y=37, reserved=0), PotDataRecord(x=56, y=37, reserved=0), PotDataRecord(x=84, y=5, reserved=0), PotDataRecord(x=104, y=5, reserved=0), PotDataRecord(x=48, y=38, reserved=0), PotDataRecord(x=52, y=38, reserved=0), PotDataRecord(x=56, y=38, reserved=0), PotDataRecord(x=84, y=6, reserved=0), PotDataRecord(x=104, y=6, reserved=0),), items=(7, 7, 9, 9, 9, 10, 10, 10, 11, 11)),
|
||||
PotRoomDataRecord(room_id=103, pots=(PotDataRecord(x=22, y=26, reserved=0), PotDataRecord(x=18, y=22, reserved=0), PotDataRecord(x=92, y=9, reserved=0), PotDataRecord(x=84, y=28, reserved=0), PotDataRecord(x=12, y=7, reserved=0), PotDataRecord(x=48, y=7, reserved=0), PotDataRecord(x=96, y=19, reserved=0), PotDataRecord(x=74, y=20, reserved=0), PotDataRecord(x=18, y=23, reserved=0), PotDataRecord(x=18, y=26, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(9, 11, 11, 11, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=104, pots=(PotDataRecord(x=84, y=14, reserved=0), PotDataRecord(x=84, y=13, reserved=0), PotDataRecord(x=88, y=12, reserved=0), PotDataRecord(x=88, y=6, reserved=0), PotDataRecord(x=88, y=5, reserved=0), PotDataRecord(x=88, y=4, reserved=0), PotDataRecord(x=64, y=17, reserved=0), PotDataRecord(x=64, y=15, reserved=0), PotDataRecord(x=64, y=7, reserved=0), PotDataRecord(x=88, y=7, reserved=0), PotDataRecord(x=64, y=16, reserved=0), PotDataRecord(x=64, y=24, reserved=0), PotDataRecord(x=64, y=25, reserved=0),), items=(11, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=115, pots=(PotDataRecord(x=154, y=21, reserved=0), PotDataRecord(x=158, y=21, reserved=0), PotDataRecord(x=20, y=23, reserved=0), PotDataRecord(x=36, y=23, reserved=0), PotDataRecord(x=144, y=24, reserved=0), PotDataRecord(x=168, y=24, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=36, y=26, reserved=0), PotDataRecord(x=154, y=27, reserved=0), PotDataRecord(x=158, y=27, reserved=0),), items=(1, 1, 11, 11, 7, 7, 9, 9, 12, 136)),
|
||||
PotRoomDataRecord(room_id=116, pots=(PotDataRecord(x=30, y=5, reserved=0), PotDataRecord(x=62, y=5, reserved=0), PotDataRecord(x=94, y=5, reserved=0), PotDataRecord(x=14, y=11, reserved=0), PotDataRecord(x=46, y=11, reserved=0), PotDataRecord(x=78, y=11, reserved=0), PotDataRecord(x=110, y=11, reserved=0),), items=(9, 9, 11, 11, 12, 12, 136)),
|
||||
PotRoomDataRecord(room_id=117, pots=(PotDataRecord(x=148, y=22, reserved=0), PotDataRecord(x=160, y=22, reserved=0), PotDataRecord(x=172, y=22, reserved=0),), items=(9, 11, 12)),
|
||||
PotRoomDataRecord(room_id=123, pots=(PotDataRecord(x=48, y=10, reserved=0), PotDataRecord(x=88, y=10, reserved=0), PotDataRecord(x=76, y=7, reserved=0), PotDataRecord(x=60, y=4, reserved=0), PotDataRecord(x=64, y=4, reserved=0),), items=(11, 8)),
|
||||
PotRoomDataRecord(room_id=124, pots=(PotDataRecord(x=36, y=21, reserved=0), PotDataRecord(x=24, y=11, reserved=0), PotDataRecord(x=28, y=4, reserved=0), PotDataRecord(x=32, y=4, reserved=0),), items=(11, 11)),
|
||||
PotRoomDataRecord(room_id=125, pots=(PotDataRecord(x=44, y=12, reserved=0), PotDataRecord(x=44, y=6, reserved=0), PotDataRecord(x=112, y=6, reserved=0), PotDataRecord(x=108, y=20, reserved=0), PotDataRecord(x=114, y=20, reserved=0), PotDataRecord(x=76, y=28, reserved=0),), items=(9, 10, 10, 11)),
|
||||
PotRoomDataRecord(room_id=126, pots=(PotDataRecord(x=86, y=15, reserved=0), PotDataRecord(x=82, y=26, reserved=0), PotDataRecord(x=100, y=26, reserved=0), PotDataRecord(x=104, y=26, reserved=0),), items=(11, 12, 136)),
|
||||
PotRoomDataRecord(room_id=130, pots=(PotDataRecord(x=50, y=5, reserved=0), PotDataRecord(x=50, y=10, reserved=0), PotDataRecord(x=76, y=50, reserved=0),), items=(11,)),
|
||||
PotRoomDataRecord(room_id=131, pots=(PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=80, y=4, reserved=0), PotDataRecord(x=76, y=28, reserved=0), PotDataRecord(x=80, y=28, reserved=0),), items=(1, 7, 9, 9)),
|
||||
PotRoomDataRecord(room_id=132, pots=(PotDataRecord(x=64, y=17, reserved=0), PotDataRecord(x=60, y=17, reserved=0), PotDataRecord(x=80, y=14, reserved=0), PotDataRecord(x=44, y=14, reserved=0), PotDataRecord(x=100, y=6, reserved=0), PotDataRecord(x=24, y=6, reserved=0), PotDataRecord(x=24, y=7, reserved=0), PotDataRecord(x=100, y=7, reserved=0),), items=(9, 9)),
|
||||
PotRoomDataRecord(room_id=135, pots=(PotDataRecord(x=12, y=11, reserved=0), PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=112, y=20, reserved=0), PotDataRecord(x=16, y=12, reserved=0), PotDataRecord(x=40, y=12, reserved=0), PotDataRecord(x=32, y=12, reserved=0), PotDataRecord(x=24, y=12, reserved=0), PotDataRecord(x=16, y=11, reserved=0),), items=(12, 13)),
|
||||
PotRoomDataRecord(room_id=139, pots=(PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=76, y=12, reserved=1), PotDataRecord(x=32, y=23, reserved=1), PotDataRecord(x=28, y=23, reserved=1), PotDataRecord(x=112, y=12, reserved=1), PotDataRecord(x=32, y=9, reserved=1), PotDataRecord(x=76, y=28, reserved=0),), items=(8, 11, 12)),
|
||||
PotRoomDataRecord(room_id=140, pots=(PotDataRecord(x=76, y=12, reserved=2), PotDataRecord(x=112, y=12, reserved=2), PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=92, y=20, reserved=0), PotDataRecord(x=100, y=21, reserved=0), PotDataRecord(x=104, y=26, reserved=0), PotDataRecord(x=88, y=27, reserved=0),), items=(9, 10, 10, 10, 10, 12, 136)),
|
||||
PotRoomDataRecord(room_id=145, pots=(PotDataRecord(x=84, y=4, reserved=0), PotDataRecord(x=104, y=4, reserved=0),), items=(11, 12)),
|
||||
PotRoomDataRecord(room_id=150, pots=(PotDataRecord(x=14, y=18, reserved=0), PotDataRecord(x=32, y=5, reserved=0), PotDataRecord(x=32, y=17, reserved=0), PotDataRecord(x=32, y=24, reserved=0), PotDataRecord(x=76, y=21, reserved=0), PotDataRecord(x=112, y=21, reserved=0), PotDataRecord(x=14, y=24, reserved=0),), items=(11, 12, 12, 13)),
|
||||
PotRoomDataRecord(room_id=155, pots=(PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=48, y=12, reserved=0),), items=(8, 12)),
|
||||
PotRoomDataRecord(room_id=157, pots=(PotDataRecord(x=32, y=7, reserved=0), PotDataRecord(x=40, y=9, reserved=0), PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=84, y=4, reserved=0),), items=(10, 12)),
|
||||
PotRoomDataRecord(room_id=159, pots=(PotDataRecord(x=138, y=20, reserved=0), PotDataRecord(x=138, y=19, reserved=0), PotDataRecord(x=178, y=19, reserved=0), PotDataRecord(x=40, y=21, reserved=0), PotDataRecord(x=138, y=21, reserved=0), PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=138, y=27, reserved=0), PotDataRecord(x=178, y=28, reserved=0), PotDataRecord(x=178, y=21, reserved=0), PotDataRecord(x=178, y=20, reserved=0), PotDataRecord(x=40, y=27, reserved=0), PotDataRecord(x=178, y=27, reserved=0), PotDataRecord(x=178, y=26, reserved=0), PotDataRecord(x=138, y=28, reserved=0), PotDataRecord(x=138, y=26, reserved=0), PotDataRecord(x=20, y=21, reserved=0),), items=(8, 11, 11, 11, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=161, pots=(PotDataRecord(x=96, y=27, reserved=0), PotDataRecord(x=92, y=21, reserved=0), PotDataRecord(x=150, y=6, reserved=0), PotDataRecord(x=100, y=11, reserved=0), PotDataRecord(x=104, y=12, reserved=0), PotDataRecord(x=108, y=13, reserved=0), PotDataRecord(x=112, y=14, reserved=0), PotDataRecord(x=96, y=23, reserved=0), PotDataRecord(x=76, y=28, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(8, 11, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=168, pots=(PotDataRecord(x=138, y=28, reserved=0), PotDataRecord(x=178, y=28, reserved=0), PotDataRecord(x=178, y=19, reserved=0), PotDataRecord(x=138, y=19, reserved=0), PotDataRecord(x=30, y=24, reserved=0),), items=(1, 11)),
|
||||
PotRoomDataRecord(room_id=169, pots=(PotDataRecord(x=12, y=19, reserved=0), PotDataRecord(x=112, y=19, reserved=0), PotDataRecord(x=144, y=43, reserved=0), PotDataRecord(x=236, y=43, reserved=0), PotDataRecord(x=144, y=44, reserved=0), PotDataRecord(x=236, y=44, reserved=0), PotDataRecord(x=16, y=20, reserved=0), PotDataRecord(x=108, y=20, reserved=0),), items=(11, 11, 11, 9, 9, 9)),
|
||||
PotRoomDataRecord(room_id=170, pots=(PotDataRecord(x=212, y=10, reserved=2), PotDataRecord(x=232, y=10, reserved=2), PotDataRecord(x=232, y=5, reserved=2), PotDataRecord(x=212, y=5, reserved=2), PotDataRecord(x=94, y=8, reserved=2), PotDataRecord(x=108, y=55, reserved=0), PotDataRecord(x=108, y=56, reserved=0), PotDataRecord(x=108, y=57, reserved=0),), items=(11, 11, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=176, pots=(PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=24, y=24, reserved=0), PotDataRecord(x=44, y=25, reserved=0), PotDataRecord(x=20, y=21, reserved=0), PotDataRecord(x=28, y=21, reserved=0), PotDataRecord(x=32, y=21, reserved=0), PotDataRecord(x=40, y=21, reserved=0), PotDataRecord(x=16, y=23, reserved=0), PotDataRecord(x=44, y=23, reserved=0), PotDataRecord(x=36, y=24, reserved=0), PotDataRecord(x=16, y=25, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=40, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0),), items=(1, 1, 7, 7, 9, 9, 10, 10, 11, 11)),
|
||||
PotRoomDataRecord(room_id=179, pots=(PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(8, 12, 136)),
|
||||
PotRoomDataRecord(room_id=180, pots=(PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(11, 13)),
|
||||
PotRoomDataRecord(room_id=181, pots=(PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=112, y=15, reserved=0), PotDataRecord(x=76, y=16, reserved=0), PotDataRecord(x=112, y=16, reserved=0), PotDataRecord(x=112, y=17, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(7, 10, 11, 11, 13, 136)),
|
||||
PotRoomDataRecord(room_id=184, pots=(PotDataRecord(x=96, y=13, reserved=0), PotDataRecord(x=88, y=16, reserved=0), PotDataRecord(x=104, y=16, reserved=0),), items=(11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=185, pots=(PotDataRecord(x=92, y=18, reserved=0), PotDataRecord(x=96, y=18, reserved=0), PotDataRecord(x=104, y=18, reserved=0), PotDataRecord(x=108, y=18, reserved=0),), items=(1, 1, 7, 7)),
|
||||
PotRoomDataRecord(room_id=186, pots=(PotDataRecord(x=100, y=8, reserved=0), PotDataRecord(x=88, y=8, reserved=0), PotDataRecord(x=94, y=4, reserved=0), PotDataRecord(x=76, y=6, reserved=0), PotDataRecord(x=112, y=6, reserved=0), PotDataRecord(x=76, y=10, reserved=0), PotDataRecord(x=112, y=10, reserved=0), PotDataRecord(x=94, y=12, reserved=0),), items=(1, 1, 8, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=188, pots=(PotDataRecord(x=138, y=3, reserved=2), PotDataRecord(x=178, y=3, reserved=2), PotDataRecord(x=86, y=4, reserved=1), PotDataRecord(x=102, y=4, reserved=1), PotDataRecord(x=138, y=12, reserved=2), PotDataRecord(x=178, y=12, reserved=2), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=28, y=21, reserved=0), PotDataRecord(x=32, y=21, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0), PotDataRecord(x=12, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(7, 7, 7, 7, 8, 10, 10, 10, 10, 10, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=191, pots=(PotDataRecord(x=40, y=20, reserved=0), PotDataRecord(x=44, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(9, 10, 11, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=192, pots=(PotDataRecord(x=48, y=10, reserved=0), PotDataRecord(x=12, y=14, reserved=0), PotDataRecord(x=12, y=26, reserved=0), PotDataRecord(x=28, y=27, reserved=0),), items=(1, 7, 10, 11)),
|
||||
PotRoomDataRecord(room_id=194, pots=(PotDataRecord(x=180, y=7, reserved=0), PotDataRecord(x=100, y=46, reserved=0), PotDataRecord(x=68, y=48, reserved=0), PotDataRecord(x=64, y=52, reserved=0),), items=(1, 9, 12, 136)),
|
||||
PotRoomDataRecord(room_id=196, pots=(PotDataRecord(x=84, y=9, reserved=0), PotDataRecord(x=24, y=14, reserved=0), PotDataRecord(x=56, y=17, reserved=0), PotDataRecord(x=84, y=17, reserved=0), PotDataRecord(x=12, y=21, reserved=0), PotDataRecord(x=76, y=23, reserved=0), PotDataRecord(x=48, y=25, reserved=0), PotDataRecord(x=12, y=26, reserved=0),), items=(1, 9, 12, 7, 10, 10, 11, 11)),
|
||||
PotRoomDataRecord(room_id=199, pots=(PotDataRecord(x=12, y=10, reserved=0), PotDataRecord(x=12, y=11, reserved=0), PotDataRecord(x=12, y=22, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(9, 12, 11, 13)),
|
||||
PotRoomDataRecord(room_id=201, pots=(PotDataRecord(x=30, y=22, reserved=0), PotDataRecord(x=94, y=22, reserved=0), PotDataRecord(x=60, y=22, reserved=0),), items=(1, 1, 136)),
|
||||
PotRoomDataRecord(room_id=203, pots=(PotDataRecord(x=88, y=16, reserved=0), PotDataRecord(x=88, y=28, reserved=0),), items=(7, 11)),
|
||||
PotRoomDataRecord(room_id=204, pots=(PotDataRecord(x=36, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=36, y=28, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(7, 11, 7, 10)),
|
||||
PotRoomDataRecord(room_id=206, pots=(PotDataRecord(x=76, y=8, reserved=0), PotDataRecord(x=80, y=8, reserved=0), PotDataRecord(x=108, y=12, reserved=0), PotDataRecord(x=112, y=12, reserved=0), PotDataRecord(x=204, y=11, reserved=3),), items=(9, 12, 12, 10, 128)),
|
||||
PotRoomDataRecord(room_id=208, pots=(PotDataRecord(x=158, y=5, reserved=0), PotDataRecord(x=140, y=11, reserved=0), PotDataRecord(x=42, y=13, reserved=0), PotDataRecord(x=48, y=16, reserved=0), PotDataRecord(x=176, y=20, reserved=0), PotDataRecord(x=146, y=23, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(1, 1, 7, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=209, pots=(PotDataRecord(x=76, y=12, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=168, y=7, reserved=0), PotDataRecord(x=112, y=12, reserved=0),), items=(9, 1, 1, 1, 13)),
|
||||
PotRoomDataRecord(room_id=214, pots=(PotDataRecord(x=92, y=22, reserved=0), PotDataRecord(x=96, y=22, reserved=0),), items=(10, 13)),
|
||||
PotRoomDataRecord(room_id=216, pots=(PotDataRecord(x=202, y=8, reserved=0), PotDataRecord(x=242, y=8, reserved=0), PotDataRecord(x=202, y=10, reserved=0), PotDataRecord(x=242, y=10, reserved=0), PotDataRecord(x=202, y=12, reserved=0), PotDataRecord(x=242, y=12, reserved=0), PotDataRecord(x=92, y=24, reserved=0), PotDataRecord(x=96, y=24, reserved=0),), items=(9, 9, 9, 9, 9, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=218, pots=(PotDataRecord(x=24, y=23, reserved=0), PotDataRecord(x=36, y=23, reserved=0), PotDataRecord(x=24, y=25, reserved=0), PotDataRecord(x=36, y=25, reserved=0),), items=(9, 9, 11, 136)),
|
||||
PotRoomDataRecord(room_id=219, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=62, y=19, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=88, y=16, reserved=0),), items=(7, 11)),
|
||||
PotRoomDataRecord(room_id=220, pots=(PotDataRecord(x=56, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=68, y=16, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(7, 9, 10, 11)),
|
||||
PotRoomDataRecord(room_id=235, pots=(PotDataRecord(x=206, y=8, reserved=0), PotDataRecord(x=210, y=8, reserved=0), PotDataRecord(x=88, y=14, reserved=0), PotDataRecord(x=92, y=14, reserved=0), PotDataRecord(x=96, y=14, reserved=0),), items=(7, 7, 11, 12, 12)),
|
||||
)
|
||||
@@ -1,171 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
ENEMIZER_SYMBOLS = {
|
||||
':pos_1_0': 0x36975E,
|
||||
':pos_1_1': 0x36976D,
|
||||
':pos_1_10': 0x369819,
|
||||
':pos_1_11': 0x369828,
|
||||
':pos_1_12': 0x369837,
|
||||
':pos_1_13': 0x369845,
|
||||
':pos_1_14': 0x36984B,
|
||||
':pos_1_15': 0x369851,
|
||||
':pos_1_16': 0x369873,
|
||||
':pos_1_17': 0x369898,
|
||||
':pos_1_18': 0x36989E,
|
||||
':pos_1_19': 0x3698A4,
|
||||
':pos_1_2': 0x369787,
|
||||
':pos_1_20': 0x3698C6,
|
||||
':pos_1_21': 0x3698EB,
|
||||
':pos_1_22': 0x3698F1,
|
||||
':pos_1_23': 0x3698F7,
|
||||
':pos_1_24': 0x369919,
|
||||
':pos_1_25': 0x36993E,
|
||||
':pos_1_26': 0x369944,
|
||||
':pos_1_27': 0x36994A,
|
||||
':pos_1_28': 0x36996C,
|
||||
':pos_1_29': 0x369AA5,
|
||||
':pos_1_3': 0x369796,
|
||||
':pos_1_30': 0x36B796,
|
||||
':pos_1_4': 0x3697A5,
|
||||
':pos_1_5': 0x3697B4,
|
||||
':pos_1_6': 0x3697D5,
|
||||
':pos_1_7': 0x3697E8,
|
||||
':pos_1_8': 0x3697F7,
|
||||
':pos_1_9': 0x369806,
|
||||
'CheckIfLinkShouldDie': 0x3699B2,
|
||||
'CheckIfLinkShouldDie_dead': 0x3699BB,
|
||||
'CheckIfLinkShouldDie_done': 0x3699BD,
|
||||
'Check_for_Blind_Fight': 0x1DA085,
|
||||
'CopyShield': 0x36B713,
|
||||
'CopyShield_loop_copy': 0x36B729,
|
||||
'CopyShield_shield_positon_gfx': 0x36B74B,
|
||||
'CopySword': 0x36B6D1,
|
||||
'CopySword_loop_copy': 0x36B6E7,
|
||||
'CopySword_sword_positon_gfx': 0x36B709,
|
||||
'DMAKholdstare': 0x3695A5,
|
||||
'DMATrinexx': 0x369617,
|
||||
'Dungeon_ResetSprites': 0x09C114,
|
||||
'EnemizerCodeStart': 0x3694F5,
|
||||
'EnemizerFlags': 0x368100,
|
||||
'EnemizerFlags_agahnim_fun_balls': 0x368104,
|
||||
'EnemizerFlags_close_blind_door': 0x368101,
|
||||
'EnemizerFlags_enable_mimic_override': 0x368105,
|
||||
'EnemizerFlags_enable_terrorpin_ai_fix': 0x368106,
|
||||
'EnemizerFlags_moldorm_eye_count': 0x368102,
|
||||
'EnemizerFlags_randomize_bushes': 0x368100,
|
||||
'EnemizerFlags_randomize_sprites': 0x368103,
|
||||
'EnemizerTablesStart': 0x368000,
|
||||
'Ext_OnBossDeath': 0x29803C,
|
||||
'Ext_OnDungeonCompleted': 0x29804B,
|
||||
'Ext_OnDungeonEnter': 0x298041,
|
||||
'Ext_OnDungeonExit': 0x298046,
|
||||
'Ext_OnFairyRevive': 0x298019,
|
||||
'Ext_OnFileCreate': 0x298000,
|
||||
'Ext_OnFileLoad': 0x298005,
|
||||
'Ext_OnFileSave': 0x29800A,
|
||||
'Ext_OnIemMenuOpen': 0x298023,
|
||||
'Ext_OnItemChange': 0x29802D,
|
||||
'Ext_OnItemMenuClose': 0x298028,
|
||||
'Ext_OnMapUse': 0x298014,
|
||||
'Ext_OnPlayerAttack': 0x298037,
|
||||
'Ext_OnPlayerDamaged': 0x298032,
|
||||
'Ext_OnPlayerDeath': 0x29800F,
|
||||
'Ext_OnYItemUse': 0x29801E,
|
||||
'Ext_OnZeldaRescued': 0x298050,
|
||||
'FixTerrorpin': 0x36971A,
|
||||
'FixTerrorpin_new': 0x369728,
|
||||
'GFX_Kholdstare_Shell': 0x36C79A,
|
||||
'GFX_Trinexx_Shell': 0x36D79A,
|
||||
'GFX_Trinexx_Shell2': 0x36DF9A,
|
||||
'GetRandomInt': 0x0DBA71,
|
||||
'Initialize_Blind_Fight': 0x1DA090,
|
||||
'Kholdstare_Draw': 0x0DD97F,
|
||||
'LoadFile': 0x369A87,
|
||||
'LoadNewSoundFx': 0x369A9E,
|
||||
'LoadOverworldSprites': 0x36B753,
|
||||
'Module_LoadFile_indoors': 0x028118,
|
||||
'Moldorm_UpdateOamPosition': 0x3699E7,
|
||||
'Moldorm_UpdateOamPosition_more_eyes': 0x3699ED,
|
||||
'NMIHookAction': 0x36956C,
|
||||
'NMIHookAction_loadKholdstare': 0x369584,
|
||||
'NMIHookAction_loadTrinexx': 0x369590,
|
||||
'NMIHookAction_return': 0x36959A,
|
||||
'NMIHookReturn': 0x0080D5,
|
||||
'NewLoadSoundBank': 0x369A98,
|
||||
'NewLoadSoundBank_Intro': 0x369A92,
|
||||
'OnInitFileSelect': 0x3696FA,
|
||||
'OnInitFileSelect_continue': 0x36970F,
|
||||
'Player_Main': 0x078000,
|
||||
'Sound_LoadSongBank': 0x008888,
|
||||
'Sound_SetSfx3PanLong': 0x0DBB8A,
|
||||
'Sound_SetSfxPanWithPlayerCoords': 0x0DBB67,
|
||||
'Spawn_Bees': 0x36B75D,
|
||||
'Spawn_Bees_done': 0x36B779,
|
||||
'SpritePrep_Eyegore': 0x1EC6FA,
|
||||
'SpritePrep_EyegoreNew': 0x369A1A,
|
||||
'SpritePrep_EyegoreNew_mimic': 0x369A31,
|
||||
'SpritePrep_EyegoreNew_new': 0x369A25,
|
||||
'Sprite_ResetAll': 0x09C44E,
|
||||
'Sprite_SpawnDynamically': 0x1DF65D,
|
||||
'VitreousKeyReset': 0x36B77A,
|
||||
'boss_move': 0x369743,
|
||||
'boss_move_loop_bottom_left': 0x369935,
|
||||
'boss_move_loop_bottom_left2': 0x369963,
|
||||
'boss_move_loop_bottom_right': 0x3698E2,
|
||||
'boss_move_loop_bottom_right2': 0x369910,
|
||||
'boss_move_loop_middle': 0x36983C,
|
||||
'boss_move_loop_middle2': 0x36986A,
|
||||
'boss_move_loop_top_right': 0x36988F,
|
||||
'boss_move_loop_top_right2': 0x3698BD,
|
||||
'boss_move_move_to_bottom_left': 0x369933,
|
||||
'boss_move_move_to_bottom_right': 0x3698E0,
|
||||
'boss_move_move_to_middle': 0x36983A,
|
||||
'boss_move_move_to_top_right': 0x36988D,
|
||||
'boss_move_no_blind_door': 0x3697D2,
|
||||
'boss_move_no_change': 0x369863,
|
||||
'boss_move_no_change2': 0x3698B6,
|
||||
'boss_move_no_change3': 0x369909,
|
||||
'boss_move_no_change4': 0x36995C,
|
||||
'boss_move_no_change_ov': 0x369885,
|
||||
'boss_move_no_change_ov2': 0x3698D8,
|
||||
'boss_move_no_change_ov3': 0x36992B,
|
||||
'boss_move_no_change_ov4': 0x36997E,
|
||||
'boss_move_return': 0x369986,
|
||||
'change_heartcontainer_position': 0x3699BE,
|
||||
'change_heartcontainer_position_not_moldorm_room': 0x3699E1,
|
||||
'check_blind_boss_room': 0x36B782,
|
||||
'check_special_action': 0x369731,
|
||||
'check_special_action_no_special_action': 0x36973E,
|
||||
'enemizer_info_table': 0x368000,
|
||||
'linkIsDead': 0x0780D5,
|
||||
'linkNotDead': 0x0780F7,
|
||||
'modified_room_object_table': 0x36B79A,
|
||||
'moved_room_header_bank_value_address': 0x368374,
|
||||
'newKodongoCollision': 0x369A02,
|
||||
'newKodongoCollision_continue': 0x369A19,
|
||||
'new_kholdstare_code': 0x369987,
|
||||
'new_kholdstare_code_already_iced': 0x369997,
|
||||
'new_trinexx_code': 0x36999C,
|
||||
'new_trinexx_code_already_rocked': 0x3699AC,
|
||||
'notItemSprite_Mimic': 0x369A66,
|
||||
'notItemSprite_Mimic_changeSpriteId': 0x369A7A,
|
||||
'notItemSprite_Mimic_continue': 0x369A82,
|
||||
'notItemSprite_Mimic_reloadSpriteIdAndSkipMimic': 0x369A7F,
|
||||
'resetSprite_Mimic': 0x369A4E,
|
||||
'resetSprite_Mimic_notMimic': 0x369A60,
|
||||
'room_header_table': 0x368375,
|
||||
'shieldgfx': 0x36AAD1,
|
||||
'sprite_bush_spawn': 0x3694F5,
|
||||
'sprite_bush_spawn_continue': 0x36950F,
|
||||
'sprite_bush_spawn_dontGoPhase2': 0x369565,
|
||||
'sprite_bush_spawn_item_table': 0x369525,
|
||||
'sprite_bush_spawn_newSpriteSpawn': 0x36954C,
|
||||
'sprite_bush_spawn_not_random': 0x36953B,
|
||||
'sprite_bush_spawn_not_random_old': 0x36950B,
|
||||
'sprite_bush_spawn_return': 0x369568,
|
||||
'sprite_bush_spawn_table': 0x368120,
|
||||
'sprite_bush_spawn_table_dungeons': 0x368248,
|
||||
'sprite_bush_spawn_table_overworld': 0x368120,
|
||||
'sprite_bush_spawn_table_random_sprites': 0x368370,
|
||||
'swordgfx': 0x369AD1,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user