diff --git a/.gitattributes b/.gitattributes
index 537a05f68b67..5ab537933405 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
worlds/blasphemous/region_data.py linguist-generated=true
+worlds/yachtdice/YachtWeights.py linguist-generated=true
diff --git a/.github/pyright-config.json b/.github/pyright-config.json
index 6ad7fa5f19b5..7d981778905f 100644
--- a/.github/pyright-config.json
+++ b/.github/pyright-config.json
@@ -16,7 +16,7 @@
"reportMissingImports": true,
"reportMissingTypeStubs": true,
- "pythonVersion": "3.8",
+ "pythonVersion": "3.10",
"pythonPlatform": "Windows",
"executionEnvironments": [
diff --git a/.github/workflows/analyze-modified-files.yml b/.github/workflows/analyze-modified-files.yml
index c9995fa2d043..b59336fafe9b 100644
--- a/.github/workflows/analyze-modified-files.yml
+++ b/.github/workflows/analyze-modified-files.yml
@@ -53,7 +53,7 @@ jobs:
- uses: actions/setup-python@v5
if: env.diff != ''
with:
- python-version: 3.8
+ python-version: '3.10'
- name: "Install dependencies"
if: env.diff != ''
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 23c463fb947a..ab94326d8188 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,14 +24,14 @@ env:
jobs:
# build-release-macos: # LF volunteer
- build-win-py38: # RCs will still be built and signed by hand
+ build-win: # RCs will still be built and signed by hand
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v5
with:
- python-version: '3.8'
+ python-version: '3.12'
- name: Download run-time dependencies
run: |
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
@@ -111,10 +111,10 @@ jobs:
- name: Get a recent python
uses: actions/setup-python@v5
with:
- python-version: '3.11'
+ python-version: '3.12'
- name: Install build-time dependencies
run: |
- echo "PYTHON=python3.11" >> $GITHUB_ENV
+ echo "PYTHON=python3.12" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3f8651d408e7..b28ec8733408 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -44,10 +44,10 @@ jobs:
- name: Get a recent python
uses: actions/setup-python@v5
with:
- python-version: '3.11'
+ python-version: '3.12'
- name: Install build-time dependencies
run: |
- echo "PYTHON=python3.11" >> $GITHUB_ENV
+ echo "PYTHON=python3.12" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml
index a38fef8fda08..88b5d12987ad 100644
--- a/.github/workflows/unittests.yml
+++ b/.github/workflows/unittests.yml
@@ -33,13 +33,11 @@ jobs:
matrix:
os: [ubuntu-latest]
python:
- - {version: '3.8'}
- - {version: '3.9'}
- {version: '3.10'}
- {version: '3.11'}
- {version: '3.12'}
include:
- - python: {version: '3.8'} # win7 compat
+ - python: {version: '3.10'} # old compat
os: windows-latest
- python: {version: '3.12'} # current
os: windows-latest
diff --git a/BaseClasses.py b/BaseClasses.py
index 46edeb5ea059..d84da5d212b4 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -1,18 +1,16 @@
from __future__ import annotations
import collections
-import itertools
import functools
import logging
import random
import secrets
-import typing # this can go away when Python 3.8 support is dropped
from argparse import Namespace
from collections import Counter, deque
from collections.abc import Collection, MutableSequence
from enum import IntEnum, IntFlag
from typing import (AbstractSet, Any, Callable, ClassVar, Dict, Iterable, Iterator, List, Mapping, NamedTuple,
- Optional, Protocol, Set, Tuple, Union, Type)
+ Optional, Protocol, Set, Tuple, Union, TYPE_CHECKING)
from typing_extensions import NotRequired, TypedDict
@@ -20,7 +18,7 @@
import Options
import Utils
-if typing.TYPE_CHECKING:
+if TYPE_CHECKING:
from worlds import AutoWorld
@@ -231,7 +229,7 @@ def set_options(self, args: Namespace) -> None:
for player in self.player_ids:
world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]]
self.worlds[player] = world_type(self, player)
- options_dataclass: typing.Type[Options.PerGameCommonOptions] = world_type.options_dataclass
+ options_dataclass: type[Options.PerGameCommonOptions] = world_type.options_dataclass
self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player]
for option_key in options_dataclass.type_hints})
@@ -975,7 +973,7 @@ class Region:
entrances: List[Entrance]
exits: List[Entrance]
locations: List[Location]
- entrance_type: ClassVar[Type[Entrance]] = Entrance
+ entrance_type: ClassVar[type[Entrance]] = Entrance
class Register(MutableSequence):
region_manager: MultiWorld.RegionManager
@@ -1075,7 +1073,7 @@ def get_connecting_entrance(self, is_main_entrance: Callable[[Entrance], bool])
return entrance.parent_region.get_connecting_entrance(is_main_entrance)
def add_locations(self, locations: Dict[str, Optional[int]],
- location_type: Optional[Type[Location]] = None) -> None:
+ location_type: Optional[type[Location]] = None) -> None:
"""
Adds locations to the Region object, where location_type is your Location class and locations is a dict of
location names to address.
@@ -1112,7 +1110,7 @@ def create_exit(self, name: str) -> Entrance:
return exit_
def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
- rules: Dict[str, Callable[[CollectionState], bool]] = None) -> None:
+ rules: Dict[str, Callable[[CollectionState], bool]] = None) -> List[Entrance]:
"""
Connects current region to regions in exit dictionary. Passed region names must exist first.
@@ -1122,10 +1120,14 @@ def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
"""
if not isinstance(exits, Dict):
exits = dict.fromkeys(exits)
- for connecting_region, name in exits.items():
- self.connect(self.multiworld.get_region(connecting_region, self.player),
- name,
- rules[connecting_region] if rules and connecting_region in rules else None)
+ return [
+ self.connect(
+ self.multiworld.get_region(connecting_region, self.player),
+ name,
+ rules[connecting_region] if rules and connecting_region in rules else None,
+ )
+ for connecting_region, name in exits.items()
+ ]
def __repr__(self):
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'
@@ -1264,6 +1266,10 @@ def useful(self) -> bool:
def trap(self) -> bool:
return ItemClassification.trap in self.classification
+ @property
+ def filler(self) -> bool:
+ return not (self.advancement or self.useful or self.trap)
+
@property
def excludable(self) -> bool:
return not (self.advancement or self.useful)
@@ -1386,14 +1392,21 @@ def create_playthrough(self, create_paths: bool = True) -> None:
# second phase, sphere 0
removed_precollected: List[Item] = []
- for item in (i for i in chain.from_iterable(multiworld.precollected_items.values()) if i.advancement):
- logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
- multiworld.precollected_items[item.player].remove(item)
- multiworld.state.remove(item)
- if not multiworld.can_beat_game():
- multiworld.push_precollected(item)
- else:
- removed_precollected.append(item)
+
+ for precollected_items in multiworld.precollected_items.values():
+ # The list of items is mutated by removing one item at a time to determine if each item is required to beat
+ # the game, and re-adding that item if it was required, so a copy needs to be made before iterating.
+ for item in precollected_items.copy():
+ if not item.advancement:
+ continue
+ logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
+ precollected_items.remove(item)
+ multiworld.state.remove(item)
+ if not multiworld.can_beat_game():
+ # Add the item back into `precollected_items` and collect it into `multiworld.state`.
+ multiworld.push_precollected(item)
+ else:
+ removed_precollected.append(item)
# we are now down to just the required progress items in collection_spheres. Unfortunately
# the previous pruning stage could potentially have made certain items dependant on others
@@ -1532,7 +1545,7 @@ def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
[f" {location}: {item}" for (location, item) in sphere.items()] if isinstance(sphere, dict) else
[f" {item}" for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
if self.unreachables:
- outfile.write('\n\nUnreachable Items:\n\n')
+ outfile.write('\n\nUnreachable Progression Items:\n\n')
outfile.write(
'\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))
diff --git a/CommonClient.py b/CommonClient.py
index 47100a7383ab..fc6ae6d9a5fa 100644
--- a/CommonClient.py
+++ b/CommonClient.py
@@ -23,7 +23,7 @@
from MultiServer import CommandProcessor
from NetUtils import (Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot,
- RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes, SlotType)
+ RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes, HintStatus, SlotType)
from Utils import Version, stream_input, async_start
from worlds import network_data_package, AutoWorldRegister
import os
@@ -412,6 +412,7 @@ async def disconnect(self, allow_autoreconnect: bool = False):
await self.server.socket.close()
if self.server_task is not None:
await self.server_task
+ self.ui.update_hints()
async def send_msgs(self, msgs: typing.List[typing.Any]) -> None:
""" `msgs` JSON serializable """
@@ -551,7 +552,14 @@ async def shutdown(self):
await self.ui_task
if self.input_task:
self.input_task.cancel()
-
+
+ # Hints
+ def update_hint(self, location: int, finding_player: int, status: typing.Optional[HintStatus]) -> None:
+ msg = {"cmd": "UpdateHint", "location": location, "player": finding_player}
+ if status is not None:
+ msg["status"] = status
+ async_start(self.send_msgs([msg]), name="update_hint")
+
# DataPackage
async def prepare_data_package(self, relevant_games: typing.Set[str],
remote_date_package_versions: typing.Dict[str, int],
diff --git a/Fill.py b/Fill.py
index 706cca657457..912b4d05bed9 100644
--- a/Fill.py
+++ b/Fill.py
@@ -978,15 +978,32 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
multiworld.random.shuffle(items)
count = 0
err: typing.List[str] = []
- successful_pairs: typing.List[typing.Tuple[Item, Location]] = []
+ successful_pairs: typing.List[typing.Tuple[int, Item, Location]] = []
+ claimed_indices: typing.Set[typing.Optional[int]] = set()
for item_name in items:
- item = multiworld.worlds[player].create_item(item_name)
+ index_to_delete: typing.Optional[int] = None
+ if from_pool:
+ try:
+ # If from_pool, try to find an existing item with this name & player in the itempool and use it
+ index_to_delete, item = next(
+ (i, item) for i, item in enumerate(multiworld.itempool)
+ if item.player == player and item.name == item_name and i not in claimed_indices
+ )
+ except StopIteration:
+ warn(
+ f"Could not remove {item_name} from pool for {multiworld.player_name[player]} as it's already missing from it.",
+ placement['force'])
+ item = multiworld.worlds[player].create_item(item_name)
+ else:
+ item = multiworld.worlds[player].create_item(item_name)
+
for location in reversed(candidates):
if (location.address is None) == (item.code is None): # either both None or both not None
if not location.item:
if location.item_rule(item):
if location.can_fill(multiworld.state, item, False):
- successful_pairs.append((item, location))
+ successful_pairs.append((index_to_delete, item, location))
+ claimed_indices.add(index_to_delete)
candidates.remove(location)
count = count + 1
break
@@ -998,6 +1015,7 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
err.append(f"Cannot place {item_name} into already filled location {location}.")
else:
err.append(f"Mismatch between {item_name} and {location}, only one is an event.")
+
if count == maxcount:
break
if count < placement['count']['min']:
@@ -1005,17 +1023,16 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
failed(
f"Plando block failed to place {m - count} of {m} item(s) for {multiworld.player_name[player]}, error(s): {' '.join(err)}",
placement['force'])
- for (item, location) in successful_pairs:
+
+ # Sort indices in reverse so we can remove them one by one
+ successful_pairs = sorted(successful_pairs, key=lambda successful_pair: successful_pair[0] or 0, reverse=True)
+
+ for (index, item, location) in successful_pairs:
multiworld.push_item(location, item, collect=False)
location.locked = True
logging.debug(f"Plando placed {item} at {location}")
- if from_pool:
- try:
- multiworld.itempool.remove(item)
- except ValueError:
- warn(
- f"Could not remove {item} from pool for {multiworld.player_name[player]} as it's already missing from it.",
- placement['force'])
+ if index is not None: # If this item is from_pool and was found in the pool, remove it.
+ multiworld.itempool.pop(index)
except Exception as e:
raise Exception(
diff --git a/Main.py b/Main.py
index 4008ca5e9017..b9612e9549ba 100644
--- a/Main.py
+++ b/Main.py
@@ -153,45 +153,38 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
# remove starting inventory from pool items.
# Because some worlds don't actually create items during create_items this has to be as late as possible.
- if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids):
- new_items: List[Item] = []
- old_items: List[Item] = []
- depletion_pool: Dict[int, Dict[str, int]] = {
- player: getattr(multiworld.worlds[player].options,
- "start_inventory_from_pool",
- StartInventoryPool({})).value.copy()
- for player in multiworld.player_ids
- }
- for player, items in depletion_pool.items():
- player_world: AutoWorld.World = multiworld.worlds[player]
- for count in items.values():
- for _ in range(count):
- new_items.append(player_world.create_filler())
- target: int = sum(sum(items.values()) for items in depletion_pool.values())
- for i, item in enumerate(multiworld.itempool):
+ fallback_inventory = StartInventoryPool({})
+ depletion_pool: Dict[int, Dict[str, int]] = {
+ player: getattr(multiworld.worlds[player].options, "start_inventory_from_pool", fallback_inventory).value.copy()
+ for player in multiworld.player_ids
+ }
+ target_per_player = {
+ player: sum(target_items.values()) for player, target_items in depletion_pool.items() if target_items
+ }
+
+ if target_per_player:
+ new_itempool: List[Item] = []
+
+ # Make new itempool with start_inventory_from_pool items removed
+ for item in multiworld.itempool:
if depletion_pool[item.player].get(item.name, 0):
- target -= 1
depletion_pool[item.player][item.name] -= 1
- # quick abort if we have found all items
- if not target:
- old_items.extend(multiworld.itempool[i+1:])
- break
else:
- old_items.append(item)
-
- # leftovers?
- if target:
- for player, remaining_items in depletion_pool.items():
- remaining_items = {name: count for name, count in remaining_items.items() if count}
- if remaining_items:
- logger.warning(f"{multiworld.get_player_name(player)}"
- f" is trying to remove items from their pool that don't exist: {remaining_items}")
- # find all filler we generated for the current player and remove until it matches
- removables = [item for item in new_items if item.player == player]
- for _ in range(sum(remaining_items.values())):
- new_items.remove(removables.pop())
- assert len(multiworld.itempool) == len(new_items + old_items), "Item Pool amounts should not change."
- multiworld.itempool[:] = new_items + old_items
+ new_itempool.append(item)
+
+ # Create filler in place of the removed items, warn if any items couldn't be found in the multiworld itempool
+ for player, target in target_per_player.items():
+ unfound_items = {item: count for item, count in depletion_pool[player].items() if count}
+
+ if unfound_items:
+ player_name = multiworld.get_player_name(player)
+ logger.warning(f"{player_name} tried to remove items from their pool that don't exist: {unfound_items}")
+
+ needed_items = target_per_player[player] - sum(unfound_items.values())
+ new_itempool += [multiworld.worlds[player].create_filler() for _ in range(needed_items)]
+
+ assert len(multiworld.itempool) == len(new_itempool), "Item Pool amounts should not change."
+ multiworld.itempool[:] = new_itempool
multiworld.link_items()
@@ -276,7 +269,7 @@ def write_multidata():
def precollect_hint(location):
entrance = er_hint_data.get(location.player, {}).get(location.address, "")
hint = NetUtils.Hint(location.item.player, location.player, location.address,
- location.item.code, False, entrance, location.item.flags)
+ location.item.code, False, entrance, location.item.flags, False)
precollected_hints[location.player].add(hint)
if location.item.player not in multiworld.groups:
precollected_hints[location.item.player].add(hint)
@@ -289,7 +282,7 @@ def precollect_hint(location):
if type(location.address) == int:
assert location.item.code is not None, "item code None should be event, " \
"location.address should then also be None. Location: " \
- f" {location}"
+ f" {location}, Item: {location.item}"
assert location.address not in locations_data[location.player], (
f"Locations with duplicate address. {location} and "
f"{locations_data[location.player][location.address]}")
diff --git a/ModuleUpdate.py b/ModuleUpdate.py
index f49182bb7863..dada16cefcaf 100644
--- a/ModuleUpdate.py
+++ b/ModuleUpdate.py
@@ -5,8 +5,8 @@
import warnings
-if sys.version_info < (3, 8, 6):
- raise RuntimeError("Incompatible Python Version. 3.8.7+ is supported.")
+if sys.version_info < (3, 10, 11):
+ raise RuntimeError(f"Incompatible Python Version found: {sys.version_info}. 3.10.11+ is supported.")
# don't run update if environment is frozen/compiled or if not the parent process (skip in subprocess)
_skip_update = bool(getattr(sys, "frozen", False) or multiprocessing.parent_process())
diff --git a/MultiServer.py b/MultiServer.py
index 847a0b281c40..0db8722b5cb6 100644
--- a/MultiServer.py
+++ b/MultiServer.py
@@ -41,7 +41,8 @@
import Utils
from Utils import version_tuple, restricted_loads, Version, async_start, get_intended_text
from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission, NetworkSlot, \
- SlotType, LocationStore
+ SlotType, LocationStore, Hint, HintStatus
+from BaseClasses import ItemClassification
min_client_version = Version(0, 1, 6)
colorama.init()
@@ -228,7 +229,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo
self.hint_cost = hint_cost
self.location_check_points = location_check_points
self.hints_used = collections.defaultdict(int)
- self.hints: typing.Dict[team_slot, typing.Set[NetUtils.Hint]] = collections.defaultdict(set)
+ self.hints: typing.Dict[team_slot, typing.Set[Hint]] = collections.defaultdict(set)
self.release_mode: str = release_mode
self.remaining_mode: str = remaining_mode
self.collect_mode: str = collect_mode
@@ -656,13 +657,29 @@ def get_hint_cost(self, slot):
return max(1, int(self.hint_cost * 0.01 * len(self.locations[slot])))
return 0
- def recheck_hints(self, team: typing.Optional[int] = None, slot: typing.Optional[int] = None):
+ def recheck_hints(self, team: typing.Optional[int] = None, slot: typing.Optional[int] = None,
+ changed: typing.Optional[typing.Set[team_slot]] = None) -> None:
+ """Refreshes the hints for the specified team/slot. Providing 'None' for either team or slot
+ will refresh all teams or all slots respectively. If a set is passed for 'changed', each (team,slot)
+ pair that has at least one hint modified will be added to the set.
+ """
for hint_team, hint_slot in self.hints:
- if (team is None or team == hint_team) and (slot is None or slot == hint_slot):
- self.hints[hint_team, hint_slot] = {
- hint.re_check(self, hint_team) for hint in
- self.hints[hint_team, hint_slot]
- }
+ if team != hint_team and team is not None:
+ continue # Check specified team only, all if team is None
+ if slot != hint_slot and slot is not None:
+ continue # Check specified slot only, all if slot is None
+ new_hints: typing.Set[Hint] = set()
+ for hint in self.hints[hint_team, hint_slot]:
+ new_hint = hint.re_check(self, hint_team)
+ new_hints.add(new_hint)
+ if hint == new_hint:
+ continue
+ for player in self.slot_set(hint.receiving_player) | {hint.finding_player}:
+ if changed is not None:
+ changed.add((hint_team,player))
+ if slot is not None and slot != player:
+ self.replace_hint(hint_team, player, hint, new_hint)
+ self.hints[hint_team, hint_slot] = new_hints
def get_rechecked_hints(self, team: int, slot: int):
self.recheck_hints(team, slot)
@@ -711,7 +728,7 @@ def get_aliased_name(self, team: int, slot: int):
else:
return self.player_names[team, slot]
- def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False,
+ def notify_hints(self, team: int, hints: typing.List[Hint], only_new: bool = False,
recipients: typing.Sequence[int] = None):
"""Send and remember hints."""
if only_new:
@@ -749,6 +766,17 @@ def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: b
for client in clients:
async_start(self.send_msgs(client, client_hints))
+ def get_hint(self, team: int, finding_player: int, seeked_location: int) -> typing.Optional[Hint]:
+ for hint in self.hints[team, finding_player]:
+ if hint.location == seeked_location:
+ return hint
+ return None
+
+ def replace_hint(self, team: int, slot: int, old_hint: Hint, new_hint: Hint) -> None:
+ if old_hint in self.hints[team, slot]:
+ self.hints[team, slot].remove(old_hint)
+ self.hints[team, slot].add(new_hint)
+
# "events"
def on_goal_achieved(self, client: Client):
@@ -1050,14 +1078,15 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
"hint_points": get_slot_points(ctx, team, slot),
"checked_locations": new_locations, # send back new checks only
}])
- old_hints = ctx.hints[team, slot].copy()
- ctx.recheck_hints(team, slot)
- if old_hints != ctx.hints[team, slot]:
- ctx.on_changed_hints(team, slot)
+ updated_slots: typing.Set[tuple[int, int]] = set()
+ ctx.recheck_hints(team, slot, updated_slots)
+ for hint_team, hint_slot in updated_slots:
+ ctx.on_changed_hints(hint_team, hint_slot)
ctx.save()
-def collect_hints(ctx: Context, team: int, slot: int, item: typing.Union[int, str]) -> typing.List[NetUtils.Hint]:
+def collect_hints(ctx: Context, team: int, slot: int, item: typing.Union[int, str], auto_status: HintStatus) \
+ -> typing.List[Hint]:
hints = []
slots: typing.Set[int] = {slot}
for group_id, group in ctx.groups.items():
@@ -1067,31 +1096,58 @@ def collect_hints(ctx: Context, team: int, slot: int, item: typing.Union[int, st
seeked_item_id = item if isinstance(item, int) else ctx.item_names_for_game(ctx.games[slot])[item]
for finding_player, location_id, item_id, receiving_player, item_flags \
in ctx.locations.find_item(slots, seeked_item_id):
- found = location_id in ctx.location_checks[team, finding_player]
- entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "")
- hints.append(NetUtils.Hint(receiving_player, finding_player, location_id, item_id, found, entrance,
- item_flags))
+ prev_hint = ctx.get_hint(team, slot, location_id)
+ if prev_hint:
+ hints.append(prev_hint)
+ else:
+ found = location_id in ctx.location_checks[team, finding_player]
+ entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "")
+ new_status = auto_status
+ if found:
+ new_status = HintStatus.HINT_FOUND
+ elif item_flags & ItemClassification.trap:
+ new_status = HintStatus.HINT_AVOID
+ hints.append(Hint(receiving_player, finding_player, location_id, item_id, found, entrance,
+ item_flags, new_status))
return hints
-def collect_hint_location_name(ctx: Context, team: int, slot: int, location: str) -> typing.List[NetUtils.Hint]:
+def collect_hint_location_name(ctx: Context, team: int, slot: int, location: str, auto_status: HintStatus) \
+ -> typing.List[Hint]:
seeked_location: int = ctx.location_names_for_game(ctx.games[slot])[location]
- return collect_hint_location_id(ctx, team, slot, seeked_location)
+ return collect_hint_location_id(ctx, team, slot, seeked_location, auto_status)
-def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location: int) -> typing.List[NetUtils.Hint]:
+def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location: int, auto_status: HintStatus) \
+ -> typing.List[Hint]:
+ prev_hint = ctx.get_hint(team, slot, seeked_location)
+ if prev_hint:
+ return [prev_hint]
result = ctx.locations[slot].get(seeked_location, (None, None, None))
if any(result):
item_id, receiving_player, item_flags = result
found = seeked_location in ctx.location_checks[team, slot]
entrance = ctx.er_hint_data.get(slot, {}).get(seeked_location, "")
- return [NetUtils.Hint(receiving_player, slot, seeked_location, item_id, found, entrance, item_flags)]
+ new_status = auto_status
+ if found:
+ new_status = HintStatus.HINT_FOUND
+ elif item_flags & ItemClassification.trap:
+ new_status = HintStatus.HINT_AVOID
+ return [Hint(receiving_player, slot, seeked_location, item_id, found, entrance, item_flags,
+ new_status)]
return []
-def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
+status_names: typing.Dict[HintStatus, str] = {
+ HintStatus.HINT_FOUND: "(found)",
+ HintStatus.HINT_UNSPECIFIED: "(unspecified)",
+ HintStatus.HINT_NO_PRIORITY: "(no priority)",
+ HintStatus.HINT_AVOID: "(avoid)",
+ HintStatus.HINT_PRIORITY: "(priority)",
+}
+def format_hint(ctx: Context, team: int, hint: Hint) -> str:
text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
f"{ctx.item_names[ctx.slot_info[hint.receiving_player].game][hint.item]} is " \
f"at {ctx.location_names[ctx.slot_info[hint.finding_player].game][hint.location]} " \
@@ -1099,7 +1155,8 @@ def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
if hint.entrance:
text += f" at {hint.entrance}"
- return text + (". (found)" if hint.found else ".")
+
+ return text + ". " + status_names.get(hint.status, "(unknown)")
def json_format_send_event(net_item: NetworkItem, receiving_player: int):
@@ -1503,7 +1560,7 @@ def _cmd_getitem(self, item_name: str) -> bool:
def get_hints(self, input_text: str, for_location: bool = False) -> bool:
points_available = get_client_points(self.ctx, self.client)
cost = self.ctx.get_hint_cost(self.client.slot)
-
+ auto_status = HintStatus.HINT_UNSPECIFIED if for_location else HintStatus.HINT_PRIORITY
if not input_text:
hints = {hint.re_check(self.ctx, self.client.team) for hint in
self.ctx.hints[self.client.team, self.client.slot]}
@@ -1529,9 +1586,9 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool:
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
hints = []
elif not for_location:
- hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_id)
+ hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_id, auto_status)
else:
- hints = collect_hint_location_id(self.ctx, self.client.team, self.client.slot, hint_id)
+ hints = collect_hint_location_id(self.ctx, self.client.team, self.client.slot, hint_id, auto_status)
else:
game = self.ctx.games[self.client.slot]
@@ -1551,16 +1608,16 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool:
hints = []
for item_name in self.ctx.item_name_groups[game][hint_name]:
if item_name in self.ctx.item_names_for_game(game): # ensure item has an ID
- hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item_name))
+ hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item_name, auto_status))
elif not for_location and hint_name in self.ctx.item_names_for_game(game): # item name
- hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name)
+ hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name, auto_status)
elif hint_name in self.ctx.location_name_groups[game]: # location group name
hints = []
for loc_name in self.ctx.location_name_groups[game][hint_name]:
if loc_name in self.ctx.location_names_for_game(game):
- hints.extend(collect_hint_location_name(self.ctx, self.client.team, self.client.slot, loc_name))
+ hints.extend(collect_hint_location_name(self.ctx, self.client.team, self.client.slot, loc_name, auto_status))
else: # location name
- hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name)
+ hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name, auto_status)
else:
self.output(response)
@@ -1832,13 +1889,51 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
target_item, target_player, flags = ctx.locations[client.slot][location]
if create_as_hint:
- hints.extend(collect_hint_location_id(ctx, client.team, client.slot, location))
+ hints.extend(collect_hint_location_id(ctx, client.team, client.slot, location,
+ HintStatus.HINT_UNSPECIFIED))
locs.append(NetworkItem(target_item, location, target_player, flags))
ctx.notify_hints(client.team, hints, only_new=create_as_hint == 2)
if locs and create_as_hint:
ctx.save()
await ctx.send_msgs(client, [{'cmd': 'LocationInfo', 'locations': locs}])
-
+
+ elif cmd == 'UpdateHint':
+ location = args["location"]
+ player = args["player"]
+ status = args["status"]
+ if not isinstance(player, int) or not isinstance(location, int) \
+ or (status is not None and not isinstance(status, int)):
+ await ctx.send_msgs(client,
+ [{'cmd': 'InvalidPacket', "type": "arguments", "text": 'UpdateHint',
+ "original_cmd": cmd}])
+ return
+ hint = ctx.get_hint(client.team, player, location)
+ if not hint:
+ return # Ignored safely
+ if hint.receiving_player != client.slot:
+ await ctx.send_msgs(client,
+ [{'cmd': 'InvalidPacket', "type": "arguments", "text": 'UpdateHint: No Permission',
+ "original_cmd": cmd}])
+ return
+ new_hint = hint
+ if status is None:
+ return
+ try:
+ status = HintStatus(status)
+ except ValueError:
+ await ctx.send_msgs(client,
+ [{'cmd': 'InvalidPacket', "type": "arguments",
+ "text": 'UpdateHint: Invalid Status', "original_cmd": cmd}])
+ return
+ new_hint = new_hint.re_prioritize(ctx, status)
+ if hint == new_hint:
+ return
+ ctx.replace_hint(client.team, hint.finding_player, hint, new_hint)
+ ctx.replace_hint(client.team, hint.receiving_player, hint, new_hint)
+ ctx.save()
+ ctx.on_changed_hints(client.team, hint.finding_player)
+ ctx.on_changed_hints(client.team, hint.receiving_player)
+
elif cmd == 'StatusUpdate':
update_client_status(ctx, client, args["status"])
@@ -2143,9 +2238,9 @@ def _cmd_hint(self, player_name: str, *item_name: str) -> bool:
hints = []
for item_name_from_group in self.ctx.item_name_groups[game][item]:
if item_name_from_group in self.ctx.item_names_for_game(game): # ensure item has an ID
- hints.extend(collect_hints(self.ctx, team, slot, item_name_from_group))
+ hints.extend(collect_hints(self.ctx, team, slot, item_name_from_group, HintStatus.HINT_PRIORITY))
else: # item name or id
- hints = collect_hints(self.ctx, team, slot, item)
+ hints = collect_hints(self.ctx, team, slot, item, HintStatus.HINT_PRIORITY)
if hints:
self.ctx.notify_hints(team, hints)
@@ -2179,14 +2274,17 @@ def _cmd_hint_location(self, player_name: str, *location_name: str) -> bool:
if usable:
if isinstance(location, int):
- hints = collect_hint_location_id(self.ctx, team, slot, location)
+ hints = collect_hint_location_id(self.ctx, team, slot, location,
+ HintStatus.HINT_UNSPECIFIED)
elif game in self.ctx.location_name_groups and location in self.ctx.location_name_groups[game]:
hints = []
for loc_name_from_group in self.ctx.location_name_groups[game][location]:
if loc_name_from_group in self.ctx.location_names_for_game(game):
- hints.extend(collect_hint_location_name(self.ctx, team, slot, loc_name_from_group))
+ hints.extend(collect_hint_location_name(self.ctx, team, slot, loc_name_from_group,
+ HintStatus.HINT_UNSPECIFIED))
else:
- hints = collect_hint_location_name(self.ctx, team, slot, location)
+ hints = collect_hint_location_name(self.ctx, team, slot, location,
+ HintStatus.HINT_UNSPECIFIED)
if hints:
self.ctx.notify_hints(team, hints)
else:
diff --git a/NetUtils.py b/NetUtils.py
index 4776b228db17..ec6ff3eb1d81 100644
--- a/NetUtils.py
+++ b/NetUtils.py
@@ -29,6 +29,14 @@ class ClientStatus(ByValue, enum.IntEnum):
CLIENT_GOAL = 30
+class HintStatus(enum.IntEnum):
+ HINT_FOUND = 0
+ HINT_UNSPECIFIED = 1
+ HINT_NO_PRIORITY = 10
+ HINT_AVOID = 20
+ HINT_PRIORITY = 30
+
+
class SlotType(ByValue, enum.IntFlag):
spectator = 0b00
player = 0b01
@@ -297,6 +305,20 @@ def add_json_location(parts: list, location_id: int, player: int = 0, **kwargs)
parts.append({"text": str(location_id), "player": player, "type": JSONTypes.location_id, **kwargs})
+status_names: typing.Dict[HintStatus, str] = {
+ HintStatus.HINT_FOUND: "(found)",
+ HintStatus.HINT_UNSPECIFIED: "(unspecified)",
+ HintStatus.HINT_NO_PRIORITY: "(no priority)",
+ HintStatus.HINT_AVOID: "(avoid)",
+ HintStatus.HINT_PRIORITY: "(priority)",
+}
+status_colors: typing.Dict[HintStatus, str] = {
+ HintStatus.HINT_FOUND: "green",
+ HintStatus.HINT_UNSPECIFIED: "white",
+ HintStatus.HINT_NO_PRIORITY: "slateblue",
+ HintStatus.HINT_AVOID: "salmon",
+ HintStatus.HINT_PRIORITY: "plum",
+}
class Hint(typing.NamedTuple):
receiving_player: int
finding_player: int
@@ -305,14 +327,21 @@ class Hint(typing.NamedTuple):
found: bool
entrance: str = ""
item_flags: int = 0
+ status: HintStatus = HintStatus.HINT_UNSPECIFIED
def re_check(self, ctx, team) -> Hint:
- if self.found:
+ if self.found and self.status == HintStatus.HINT_FOUND:
return self
found = self.location in ctx.location_checks[team, self.finding_player]
if found:
- return Hint(self.receiving_player, self.finding_player, self.location, self.item, found, self.entrance,
- self.item_flags)
+ return self._replace(found=found, status=HintStatus.HINT_FOUND)
+ return self
+
+ def re_prioritize(self, ctx, status: HintStatus) -> Hint:
+ if self.found and status != HintStatus.HINT_FOUND:
+ status = HintStatus.HINT_FOUND
+ if status != self.status:
+ return self._replace(status=status)
return self
def __hash__(self):
@@ -334,10 +363,8 @@ def as_network_message(self) -> dict:
else:
add_json_text(parts, "'s World")
add_json_text(parts, ". ")
- if self.found:
- add_json_text(parts, "(found)", type="color", color="green")
- else:
- add_json_text(parts, "(not found)", type="color", color="red")
+ add_json_text(parts, status_names.get(self.status, "(unknown)"), type="color",
+ color=status_colors.get(self.status, "red"))
return {"cmd": "PrintJSON", "data": parts, "type": "Hint",
"receiving": self.receiving_player,
diff --git a/Options.py b/Options.py
index 992348cb546d..0d0e6ae1cd95 100644
--- a/Options.py
+++ b/Options.py
@@ -828,7 +828,10 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P
f"is not a valid location name from {world.game}. "
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)")
+ def __iter__(self) -> typing.Iterator[typing.Any]:
+ return self.value.__iter__()
+
class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mapping[str, typing.Any]):
default = {}
supports_weighting = False
@@ -1460,22 +1463,26 @@ class OptionGroup(typing.NamedTuple):
def get_option_groups(world: typing.Type[World], visibility_level: Visibility = Visibility.template) -> typing.Dict[
str, typing.Dict[str, typing.Type[Option[typing.Any]]]]:
"""Generates and returns a dictionary for the option groups of a specified world."""
- option_groups = {option: option_group.name
- for option_group in world.web.option_groups
- for option in option_group.options}
+ option_to_name = {option: option_name for option_name, option in world.options_dataclass.type_hints.items()}
+
+ ordered_groups = {group.name: group.options for group in world.web.option_groups}
+
# add a default option group for uncategorized options to get thrown into
- ordered_groups = ["Game Options"]
- [ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups]
- grouped_options = {group: {} for group in ordered_groups}
- for option_name, option in world.options_dataclass.type_hints.items():
- if visibility_level & option.visibility:
- grouped_options[option_groups.get(option, "Game Options")][option_name] = option
-
- # if the world doesn't have any ungrouped options, this group will be empty so just remove it
- if not grouped_options["Game Options"]:
- del grouped_options["Game Options"]
-
- return grouped_options
+ if "Game Options" not in ordered_groups:
+ grouped_options = set(option for group in ordered_groups.values() for option in group)
+ ungrouped_options = [option for option in option_to_name if option not in grouped_options]
+ # only add the game options group if we have ungrouped options
+ if ungrouped_options:
+ ordered_groups = {**{"Game Options": ungrouped_options}, **ordered_groups}
+
+ return {
+ group: {
+ option_to_name[option]: option
+ for option in group_options
+ if (visibility_level in option.visibility and option in option_to_name)
+ }
+ for group, group_options in ordered_groups.items()
+ }
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True) -> None:
diff --git a/README.md b/README.md
index 0e57bce53b51..2cc3c18aa09d 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,7 @@ Currently, the following games are supported:
* Kingdom Hearts 1
* Mega Man 2
* Yacht Dice
+* Faxanadu
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
diff --git a/Utils.py b/Utils.py
index 2dfcd9d3e19a..f5c6ca6414f5 100644
--- a/Utils.py
+++ b/Utils.py
@@ -19,8 +19,7 @@
from argparse import Namespace
from settings import Settings, get_settings
from time import sleep
-from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union
-from typing_extensions import TypeGuard
+from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union, TypeGuard
from yaml import load, load_all, dump
try:
@@ -48,7 +47,7 @@ def as_simple_string(self) -> str:
return ".".join(str(item) for item in self)
-__version__ = "0.5.1"
+__version__ = "0.6.0"
version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux")
@@ -422,7 +421,8 @@ def find_class(self, module: str, name: str) -> type:
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# used by MultiServer -> savegame/multidata
- if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot"}:
+ if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint",
+ "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":
@@ -515,10 +515,13 @@ def filter(self, record: logging.LogRecord) -> bool:
return self.condition(record)
file_handler.addFilter(Filter("NoStream", lambda record: not getattr(record, "NoFile", False)))
+ file_handler.addFilter(Filter("NoCarriageReturn", lambda record: '\r' not in record.msg))
root_logger.addHandler(file_handler)
if sys.stdout:
+ formatter = logging.Formatter(fmt='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.addFilter(Filter("NoFile", lambda record: not getattr(record, "NoStream", False)))
+ stream_handler.setFormatter(formatter)
root_logger.addHandler(stream_handler)
# Relay unhandled exceptions to logger.
@@ -855,11 +858,10 @@ def async_start(co: Coroutine[None, None, typing.Any], name: Optional[str] = Non
task.add_done_callback(_faf_tasks.discard)
-def deprecate(message: str):
+def deprecate(message: str, add_stacklevels: int = 0):
if __debug__:
raise Exception(message)
- import warnings
- warnings.warn(message)
+ warnings.warn(message, stacklevel=2 + add_stacklevels)
class DeprecateDict(dict):
@@ -873,10 +875,9 @@ def __init__(self, message: str, error: bool = False) -> None:
def __getitem__(self, item: Any) -> Any:
if self.should_error:
- deprecate(self.log_message)
+ deprecate(self.log_message, add_stacklevels=1)
elif __debug__:
- import warnings
- warnings.warn(self.log_message)
+ warnings.warn(self.log_message, stacklevel=2)
return super().__getitem__(item)
diff --git a/WebHost.py b/WebHost.py
index 3bf75eb35ae0..3790a5f6f4d2 100644
--- a/WebHost.py
+++ b/WebHost.py
@@ -17,7 +17,7 @@
if typing.TYPE_CHECKING:
from flask import Flask
-Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
+Utils.local_path.cached_path = os.path.dirname(__file__)
settings.no_gui = True
configpath = os.path.abspath("config.yaml")
if not os.path.exists(configpath): # fall back to config.yaml in home
diff --git a/WebHostLib/requirements.txt b/WebHostLib/requirements.txt
index 5c79415312d4..b7b14dea1e6f 100644
--- a/WebHostLib/requirements.txt
+++ b/WebHostLib/requirements.txt
@@ -5,9 +5,7 @@ waitress>=3.0.0
Flask-Caching>=2.3.0
Flask-Compress>=1.15
Flask-Limiter>=3.8.0
-bokeh>=3.1.1; python_version <= '3.8'
-bokeh>=3.4.3; python_version == '3.9'
-bokeh>=3.5.2; python_version >= '3.10'
+bokeh>=3.5.2
markupsafe>=2.1.5
Markdown>=3.7
mdx-breakless-lists>=1.0.1
diff --git a/WebHostLib/templates/templates.html b/WebHostLib/templates/templates.html
index fb6ea7e9eab5..3b2418ae15b6 100644
--- a/WebHostLib/templates/templates.html
+++ b/WebHostLib/templates/templates.html
@@ -4,9 +4,6 @@
{% include 'header/grassHeader.html' %}
Option Templates (YAML)
-
{% endblock %}
{% block body %}
diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html
index 68d3968a178a..d18d0f0b8957 100644
--- a/WebHostLib/templates/weightedOptions/macros.html
+++ b/WebHostLib/templates/weightedOptions/macros.html
@@ -53,7 +53,7 @@
{{ RangeRow(option_name, option, option.range_start, option.range_start, True) }}
- {% if option.range_start < option.default < option.range_end %}
+ {% if option.default is number and option.range_start < option.default < option.range_end %}
{{ RangeRow(option_name, option, option.default, option.default, True) }}
{% endif %}
{{ RangeRow(option_name, option, option.range_end, option.range_end, True) }}
diff --git a/data/client.kv b/data/client.kv
index dc8a5c9c9d72..3455f2a23657 100644
--- a/data/client.kv
+++ b/data/client.kv
@@ -59,7 +59,7 @@
finding_text: "Finding Player"
location_text: "Location"
entrance_text: "Entrance"
- found_text: "Found?"
+ status_text: "Status"
TooltipLabel:
id: receiving
sort_key: 'receiving'
@@ -96,9 +96,9 @@
valign: 'center'
pos_hint: {"center_y": 0.5}
TooltipLabel:
- id: found
- sort_key: 'found'
- text: root.found_text
+ id: status
+ sort_key: 'status'
+ text: root.status_text
halign: 'center'
valign: 'center'
pos_hint: {"center_y": 0.5}
diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS
index a51cac37026b..64a1362bf380 100644
--- a/docs/CODEOWNERS
+++ b/docs/CODEOWNERS
@@ -55,19 +55,22 @@
/worlds/dlcquest/ @axe-y @agilbert1412
# DOOM 1993
-/worlds/doom_1993/ @Daivuk
+/worlds/doom_1993/ @Daivuk @KScl
# DOOM II
-/worlds/doom_ii/ @Daivuk
+/worlds/doom_ii/ @Daivuk @KScl
# Factorio
/worlds/factorio/ @Berserker66
+# Faxanadu
+/worlds/faxanadu/ @Daivuk
+
# Final Fantasy Mystic Quest
/worlds/ffmq/ @Alchav @wildham0
# Heretic
-/worlds/heretic/ @Daivuk
+/worlds/heretic/ @Daivuk @KScl
# Hollow Knight
/worlds/hk/ @BadMagic100 @qwint
diff --git a/docs/contributing.md b/docs/contributing.md
index 9fd21408eb7b..96fc316be82c 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -16,7 +16,7 @@ game contributions:
* **Do not introduce unit test failures/regressions.**
Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test
your changes. Currently, the oldest supported version
- is [Python 3.8](https://www.python.org/downloads/release/python-380/).
+ is [Python 3.10](https://www.python.org/downloads/release/python-31015/).
It is recommended that automated github actions are turned on in your fork to have github run unit tests after
pushing.
You can turn them on here:
diff --git a/docs/network protocol.md b/docs/network protocol.md
index 4a96a43f818f..1c5b2e002289 100644
--- a/docs/network protocol.md
+++ b/docs/network protocol.md
@@ -272,6 +272,7 @@ These packets are sent purely from client to server. They are not accepted by cl
* [Sync](#Sync)
* [LocationChecks](#LocationChecks)
* [LocationScouts](#LocationScouts)
+* [UpdateHint](#UpdateHint)
* [StatusUpdate](#StatusUpdate)
* [Say](#Say)
* [GetDataPackage](#GetDataPackage)
@@ -342,6 +343,29 @@ This is useful in cases where an item appears in the game world, such as 'ledge
| locations | list\[int\] | The ids of the locations seen by the client. May contain any number of locations, even ones sent before; duplicates do not cause issues with the Archipelago server. |
| create_as_hint | int | If non-zero, the scouted locations get created and broadcasted as a player-visible hint.
If 2 only new hints are broadcast, however this does not remove them from the LocationInfo reply. |
+### UpdateHint
+Sent to the server to update the status of a Hint. The client must be the 'receiving_player' of the Hint, or the update fails.
+
+### Arguments
+| Name | Type | Notes |
+| ---- | ---- | ----- |
+| player | int | The ID of the player whose location is being hinted for. |
+| location | int | The ID of the location to update the hint for. If no hint exists for this location, the packet is ignored. |
+| status | [HintStatus](#HintStatus) | Optional. If included, sets the status of the hint to this status. |
+
+#### HintStatus
+An enumeration containing the possible hint states.
+
+```python
+import enum
+class HintStatus(enum.IntEnum):
+ HINT_FOUND = 0
+ HINT_UNSPECIFIED = 1
+ HINT_NO_PRIORITY = 10
+ HINT_AVOID = 20
+ HINT_PRIORITY = 30
+```
+
### StatusUpdate
Sent to the server to update on the sender's status. Examples include readiness or goal completion. (Example: defeated Ganon in A Link to the Past)
diff --git a/docs/running from source.md b/docs/running from source.md
index ef1594da9588..66dd1925c897 100644
--- a/docs/running from source.md
+++ b/docs/running from source.md
@@ -7,7 +7,7 @@ use that version. These steps are for developers or platforms without compiled r
## General
What you'll need:
- * [Python 3.8.7 or newer](https://www.python.org/downloads/), not the Windows Store version
+ * [Python 3.10.15 or newer](https://www.python.org/downloads/), not the Windows Store version
* Python 3.12.x is currently the newest supported version
* pip: included in downloads from python.org, separate in many Linux distributions
* Matching C compiler
diff --git a/docs/world api.md b/docs/world api.md
index bf09d965f11d..20669d7ae7be 100644
--- a/docs/world api.md
+++ b/docs/world api.md
@@ -288,8 +288,8 @@ like entrance randomization in logic.
Regions have a list called `exits`, containing `Entrance` objects representing transitions to other regions.
-There must be one special region, "Menu", from which the logic unfolds. AP assumes that a player will always be able to
-return to the "Menu" region by resetting the game ("Save and quit").
+There must be one special region (Called "Menu" by default, but configurable using [origin_region_name](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/AutoWorld.py#L295-L296)),
+from which the logic unfolds. AP assumes that a player will always be able to return to this starting region by resetting the game ("Save and quit").
### Entrances
@@ -328,6 +328,9 @@ Even doing `state.can_reach_location` or `state.can_reach_entrance` is problemat
You can use `multiworld.register_indirect_condition(region, entrance)` to explicitly tell the generator that, when a given region becomes accessible, it is necessary to re-check a specific entrance.
You **must** use `multiworld.register_indirect_condition` if you perform this kind of `can_reach` from an entrance access rule, unless you have a **very** good technical understanding of the relevant code and can reason why it will never lead to problems in your case.
+Alternatively, you can set [world.explicit_indirect_conditions = False](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/AutoWorld.py#L298-L301),
+avoiding the need for indirect conditions at the expense of performance.
+
### Item Rules
An item rule is a function that returns `True` or `False` for a `Location` based on a single item. It can be used to
@@ -463,7 +466,7 @@ The world has to provide the following things for generation:
* the properties mentioned above
* additions to the item pool
-* additions to the regions list: at least one called "Menu"
+* additions to the regions list: at least one named after the world class's origin_region_name ("Menu" by default)
* locations placed inside those regions
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
* applying `self.multiworld.push_precollected` for world-defined start inventory
@@ -516,7 +519,7 @@ def generate_early(self) -> None:
```python
def create_regions(self) -> None:
- # Add regions to the multiworld. "Menu" is the required starting point.
+ # Add regions to the multiworld. One of them must use the origin_region_name as its name ("Menu" by default).
# Arguments to Region() are name, player, multiworld, and optionally hint_text
menu_region = Region("Menu", self.player, self.multiworld)
self.multiworld.regions.append(menu_region) # or use += [menu_region...]
diff --git a/kvui.py b/kvui.py
index 74d8ad06734a..dfe935930049 100644
--- a/kvui.py
+++ b/kvui.py
@@ -12,10 +12,7 @@
# kivy 2.2.0 introduced DPI awareness on Windows, but it makes the UI enter an infinitely recursive re-layout
# by setting the application to not DPI Aware, Windows handles scaling the entire window on its own, ignoring kivy's
- try:
- ctypes.windll.shcore.SetProcessDpiAwareness(0)
- except FileNotFoundError: # shcore may not be found on <= Windows 7
- pass # TODO: remove silent except when Python 3.8 is phased out.
+ ctypes.windll.shcore.SetProcessDpiAwareness(0)
os.environ["KIVY_NO_CONSOLELOG"] = "1"
os.environ["KIVY_NO_FILELOG"] = "1"
@@ -55,6 +52,7 @@
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
+from kivy.uix.dropdown import DropDown
from kivy.utils import escape_markup
from kivy.lang import Builder
from kivy.uix.recycleview.views import RecycleDataViewBehavior
@@ -66,7 +64,7 @@
fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25)
-from NetUtils import JSONtoTextParser, JSONMessagePart, SlotType
+from NetUtils import JSONtoTextParser, JSONMessagePart, SlotType, HintStatus
from Utils import async_start, get_input_text_from_response
if typing.TYPE_CHECKING:
@@ -303,11 +301,11 @@ def apply_selection(self, rv, index, is_selected):
""" Respond to the selection of items in the view. """
self.selected = is_selected
-
class HintLabel(RecycleDataViewBehavior, BoxLayout):
selected = BooleanProperty(False)
striped = BooleanProperty(False)
index = None
+ dropdown: DropDown
def __init__(self):
super(HintLabel, self).__init__()
@@ -316,10 +314,32 @@ def __init__(self):
self.finding_text = ""
self.location_text = ""
self.entrance_text = ""
- self.found_text = ""
+ self.status_text = ""
+ self.hint = {}
for child in self.children:
child.bind(texture_size=self.set_height)
+
+ ctx = App.get_running_app().ctx
+ self.dropdown = DropDown()
+
+ def set_value(button):
+ self.dropdown.select(button.status)
+
+ def select(instance, data):
+ ctx.update_hint(self.hint["location"],
+ self.hint["finding_player"],
+ data)
+
+ for status in (HintStatus.HINT_NO_PRIORITY, HintStatus.HINT_PRIORITY, HintStatus.HINT_AVOID):
+ name = status_names[status]
+ status_button = Button(text=name, size_hint_y=None, height=dp(50))
+ status_button.status = status
+ status_button.bind(on_release=set_value)
+ self.dropdown.add_widget(status_button)
+
+ self.dropdown.bind(on_select=select)
+
def set_height(self, instance, value):
self.height = max([child.texture_size[1] for child in self.children])
@@ -331,7 +351,8 @@ def refresh_view_attrs(self, rv, index, data):
self.finding_text = data["finding"]["text"]
self.location_text = data["location"]["text"]
self.entrance_text = data["entrance"]["text"]
- self.found_text = data["found"]["text"]
+ self.status_text = data["status"]["text"]
+ self.hint = data["status"]["hint"]
self.height = self.minimum_height
return super(HintLabel, self).refresh_view_attrs(rv, index, data)
@@ -341,13 +362,21 @@ def on_touch_down(self, touch):
return True
if self.index: # skip header
if self.collide_point(*touch.pos):
- if self.selected:
+ status_label = self.ids["status"]
+ if status_label.collide_point(*touch.pos):
+ if self.hint["status"] == HintStatus.HINT_FOUND:
+ return
+ ctx = App.get_running_app().ctx
+ if ctx.slot == self.hint["receiving_player"]: # If this player owns this hint
+ # open a dropdown
+ self.dropdown.open(self.ids["status"])
+ elif self.selected:
self.parent.clear_selection()
else:
text = "".join((self.receiving_text, "\'s ", self.item_text, " is at ", self.location_text, " in ",
self.finding_text, "\'s World", (" at " + self.entrance_text)
if self.entrance_text != "Vanilla"
- else "", ". (", self.found_text.lower(), ")"))
+ else "", ". (", self.status_text.lower(), ")"))
temp = MarkupLabel(text).markup
text = "".join(
part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]")))
@@ -361,18 +390,16 @@ def on_touch_down(self, touch):
for child in self.children:
if child.collide_point(*touch.pos):
key = child.sort_key
- parent.hint_sorter = lambda element: remove_between_brackets.sub("", element[key]["text"]).lower()
+ if key == "status":
+ parent.hint_sorter = lambda element: element["status"]["hint"]["status"]
+ else: parent.hint_sorter = lambda element: remove_between_brackets.sub("", element[key]["text"]).lower()
if key == parent.sort_key:
# second click reverses order
parent.reversed = not parent.reversed
else:
parent.sort_key = key
parent.reversed = False
- break
- else:
- logging.warning("Did not find clicked header for sorting.")
-
- App.get_running_app().update_hints()
+ App.get_running_app().update_hints()
def apply_selection(self, rv, index, is_selected):
""" Respond to the selection of items in the view. """
@@ -666,7 +693,7 @@ def set_new_energy_link_value(self):
self.energy_link_label.text = f"EL: {Utils.format_SI_prefix(self.ctx.current_energy_link_value)}J"
def update_hints(self):
- hints = self.ctx.stored_data[f"_read_hints_{self.ctx.team}_{self.ctx.slot}"]
+ hints = self.ctx.stored_data.get(f"_read_hints_{self.ctx.team}_{self.ctx.slot}", [])
self.log_panels["Hints"].refresh_hints(hints)
# default F1 keybind, opens a settings menu, that seems to break the layout engine once closed
@@ -722,6 +749,22 @@ def fix_heights(self):
element.height = element.texture_size[1]
+status_names: typing.Dict[HintStatus, str] = {
+ HintStatus.HINT_FOUND: "Found",
+ HintStatus.HINT_UNSPECIFIED: "Unspecified",
+ HintStatus.HINT_NO_PRIORITY: "No Priority",
+ HintStatus.HINT_AVOID: "Avoid",
+ HintStatus.HINT_PRIORITY: "Priority",
+}
+status_colors: typing.Dict[HintStatus, str] = {
+ HintStatus.HINT_FOUND: "green",
+ HintStatus.HINT_UNSPECIFIED: "white",
+ HintStatus.HINT_NO_PRIORITY: "cyan",
+ HintStatus.HINT_AVOID: "salmon",
+ HintStatus.HINT_PRIORITY: "plum",
+}
+
+
class HintLog(RecycleView):
header = {
"receiving": {"text": "[u]Receiving Player[/u]"},
@@ -729,12 +772,13 @@ class HintLog(RecycleView):
"finding": {"text": "[u]Finding Player[/u]"},
"location": {"text": "[u]Location[/u]"},
"entrance": {"text": "[u]Entrance[/u]"},
- "found": {"text": "[u]Status[/u]"},
+ "status": {"text": "[u]Status[/u]",
+ "hint": {"receiving_player": -1, "location": -1, "finding_player": -1, "status": ""}},
"striped": True,
}
sort_key: str = ""
- reversed: bool = False
+ reversed: bool = True
def __init__(self, parser):
super(HintLog, self).__init__()
@@ -742,8 +786,18 @@ def __init__(self, parser):
self.parser = parser
def refresh_hints(self, hints):
+ if not hints: # Fix the scrolling looking visually wrong in some edge cases
+ self.scroll_y = 1.0
data = []
+ ctx = App.get_running_app().ctx
for hint in hints:
+ if not hint.get("status"): # Allows connecting to old servers
+ hint["status"] = HintStatus.HINT_FOUND if hint["found"] else HintStatus.HINT_UNSPECIFIED
+ hint_status_node = self.parser.handle_node({"type": "color",
+ "color": status_colors.get(hint["status"], "red"),
+ "text": status_names.get(hint["status"], "Unknown")})
+ if hint["status"] != HintStatus.HINT_FOUND and hint["receiving_player"] == ctx.slot:
+ hint_status_node = f"[u]{hint_status_node}[/u]"
data.append({
"receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
"item": {"text": self.parser.handle_node({
@@ -761,9 +815,10 @@ def refresh_hints(self, hints):
"entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text",
"color": "blue", "text": hint["entrance"]
if hint["entrance"] else "Vanilla"})},
- "found": {
- "text": self.parser.handle_node({"type": "color", "color": "green" if hint["found"] else "red",
- "text": "Found" if hint["found"] else "Not Found"})},
+ "status": {
+ "text": hint_status_node,
+ "hint": hint,
+ },
})
data.sort(key=self.hint_sorter, reverse=self.reversed)
@@ -774,7 +829,7 @@ def refresh_hints(self, hints):
@staticmethod
def hint_sorter(element: dict) -> str:
- return ""
+ return element["status"]["hint"]["status"] # By status by default
def fix_heights(self):
"""Workaround fix for divergent texture and layout heights"""
diff --git a/settings.py b/settings.py
index 792770521459..ccd3458003c2 100644
--- a/settings.py
+++ b/settings.py
@@ -7,6 +7,7 @@
import os.path
import shutil
import sys
+import types
import typing
import warnings
from enum import IntEnum
@@ -162,8 +163,13 @@ def update(self, dct: Dict[str, Any]) -> None:
else:
# assign value, try to upcast to type hint
annotation = self.get_type_hints().get(k, None)
- candidates = [] if annotation is None else \
- typing.get_args(annotation) if typing.get_origin(annotation) is Union else [annotation]
+ candidates = (
+ [] if annotation is None else (
+ typing.get_args(annotation)
+ if typing.get_origin(annotation) in (Union, types.UnionType)
+ else [annotation]
+ )
+ )
none_type = type(None)
for cls in candidates:
assert isinstance(cls, type), f"{self.__class__.__name__}.{k}: type {cls} not supported in settings"
diff --git a/setup.py b/setup.py
index afbe17726df4..59c2d698d35b 100644
--- a/setup.py
+++ b/setup.py
@@ -321,7 +321,7 @@ def run(self) -> None:
f"{ex}\nPlease close all AP instances and delete manually.")
# regular cx build
- self.buildtime = datetime.datetime.utcnow()
+ self.buildtime = datetime.datetime.now(datetime.timezone.utc)
super().run()
# manually copy built modules to lib folder. cx_Freeze does not know they exist.
@@ -634,7 +634,7 @@ def find_lib(lib: str, arch: str, libc: str) -> Optional[str]:
"excludes": ["numpy", "Cython", "PySide2", "PIL",
"pandas", "zstandard"],
"zip_include_packages": ["*"],
- "zip_exclude_packages": ["worlds", "sc2", "orjson"], # TODO: remove orjson here once we drop py3.8 support
+ "zip_exclude_packages": ["worlds", "sc2"],
"include_files": [], # broken in cx 6.14.0, we use more special sauce now
"include_msvcr": False,
"replace_paths": ["*."],
diff --git a/test/general/test_items.py b/test/general/test_items.py
index 9cc91a1b00ef..64ce1b6997b7 100644
--- a/test/general/test_items.py
+++ b/test/general/test_items.py
@@ -80,3 +80,21 @@ def test_itempool_not_modified(self):
call_all(multiworld, step)
self.assertEqual(created_items, multiworld.itempool,
f"{game_name} modified the itempool during {step}")
+
+ def test_locality_not_modified(self):
+ """Test that worlds don't modify the locality of items after duplicates are resolved"""
+ gen_steps = ("generate_early", "create_regions", "create_items")
+ additional_steps = ("set_rules", "generate_basic", "pre_fill")
+ worlds_to_test = {game: world for game, world in AutoWorldRegister.world_types.items()}
+ for game_name, world_type in worlds_to_test.items():
+ with self.subTest("Game", game=game_name):
+ multiworld = setup_solo_multiworld(world_type, gen_steps)
+ local_items = multiworld.worlds[1].options.local_items.value.copy()
+ non_local_items = multiworld.worlds[1].options.non_local_items.value.copy()
+ for step in additional_steps:
+ with self.subTest("step", step=step):
+ call_all(multiworld, step)
+ self.assertEqual(local_items, multiworld.worlds[1].options.local_items.value,
+ f"{game_name} modified local_items during {step}")
+ self.assertEqual(non_local_items, multiworld.worlds[1].options.non_local_items.value,
+ f"{game_name} modified non_local_items during {step}")
diff --git a/test/general/test_settings.py b/test/general/test_settings.py
new file mode 100644
index 000000000000..165d7982b5fb
--- /dev/null
+++ b/test/general/test_settings.py
@@ -0,0 +1,16 @@
+from unittest import TestCase
+
+from settings import Group
+from worlds.AutoWorld import AutoWorldRegister
+
+
+class TestSettings(TestCase):
+ def test_settings_can_update(self) -> None:
+ """
+ Test that world settings can update.
+ """
+ for game_name, world_type in AutoWorldRegister.world_types.items():
+ with self.subTest(game=game_name):
+ if world_type.settings is not None:
+ assert isinstance(world_type.settings, Group)
+ world_type.settings.update({}) # a previous bug had a crash in this call to update
diff --git a/worlds/AutoSNIClient.py b/worlds/AutoSNIClient.py
index b3f40be2958d..f9444eee73c6 100644
--- a/worlds/AutoSNIClient.py
+++ b/worlds/AutoSNIClient.py
@@ -2,9 +2,7 @@
from __future__ import annotations
import abc
import logging
-from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union
-
-from typing_extensions import TypeGuard
+from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union, TypeGuard
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py
index 3c4edc1b0c3b..ded8701d3b61 100644
--- a/worlds/AutoWorld.py
+++ b/worlds/AutoWorld.py
@@ -33,7 +33,10 @@ def settings(cls) -> Any: # actual type is defined in World
# lazy loading + caching to minimize runtime cost
if cls.__settings is None:
from settings import get_settings
- cls.__settings = get_settings()[cls.settings_key]
+ try:
+ cls.__settings = get_settings()[cls.settings_key]
+ except AttributeError:
+ return None
return cls.__settings
def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister:
diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py
index 3c4c4477ef09..67806a7394c7 100644
--- a/worlds/LauncherComponents.py
+++ b/worlds/LauncherComponents.py
@@ -103,7 +103,7 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
try:
import zipfile
zip = zipfile.ZipFile(apworld_path)
- directories = [f.filename.strip('/') for f in zip.filelist if f.CRC == 0 and f.file_size == 0 and f.filename.count('/') == 1]
+ directories = [f.name for f in zipfile.Path(zip).iterdir() if f.is_dir()]
if len(directories) == 1 and directories[0] in apworld_path.stem:
module_name = directories[0]
apworld_name = module_name + ".apworld"
diff --git a/worlds/__init__.py b/worlds/__init__.py
index c277ac9ca1de..7db651bdd9e3 100644
--- a/worlds/__init__.py
+++ b/worlds/__init__.py
@@ -66,19 +66,12 @@ def load(self) -> bool:
start = time.perf_counter()
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
- if hasattr(importer, "find_spec"): # new in Python 3.10
- spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
- assert spec, f"{self.path} is not a loadable module"
- mod = importlib.util.module_from_spec(spec)
- else: # TODO: remove with 3.8 support
- mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])
-
- if mod.__package__ is not None:
- mod.__package__ = f"worlds.{mod.__package__}"
- else:
- # load_module does not populate package, we'll have to assume mod.__name__ is correct here
- # probably safe to remove with 3.8 support
- mod.__package__ = f"worlds.{mod.__name__}"
+ spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
+ assert spec, f"{self.path} is not a loadable module"
+ mod = importlib.util.module_from_spec(spec)
+
+ mod.__package__ = f"worlds.{mod.__package__}"
+
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py
index bd87cbf2c3ea..097458611734 100644
--- a/worlds/alttp/Options.py
+++ b/worlds/alttp/Options.py
@@ -1,7 +1,7 @@
-import typing
+from dataclasses import dataclass
from BaseClasses import MultiWorld
-from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, Option, \
+from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, PerGameCommonOptions, \
PlandoBosses, PlandoConnections, PlandoTexts, Removed, StartInventoryPool, Toggle
from .EntranceShuffle import default_connections, default_dungeon_connections, \
inverted_default_connections, inverted_default_dungeon_connections
@@ -742,86 +742,86 @@ class ALttPPlandoTexts(PlandoTexts):
valid_keys = TextTable.valid_keys
-alttp_options: typing.Dict[str, type(Option)] = {
- "accessibility": ItemsAccessibility,
- "plando_connections": ALttPPlandoConnections,
- "plando_texts": ALttPPlandoTexts,
- "start_inventory_from_pool": StartInventoryPool,
- "goal": Goal,
- "mode": Mode,
- "glitches_required": GlitchesRequired,
- "dark_room_logic": DarkRoomLogic,
- "open_pyramid": OpenPyramid,
- "crystals_needed_for_gt": CrystalsTower,
- "crystals_needed_for_ganon": CrystalsGanon,
- "triforce_pieces_mode": TriforcePiecesMode,
- "triforce_pieces_percentage": TriforcePiecesPercentage,
- "triforce_pieces_required": TriforcePiecesRequired,
- "triforce_pieces_available": TriforcePiecesAvailable,
- "triforce_pieces_extra": TriforcePiecesExtra,
- "entrance_shuffle": EntranceShuffle,
- "entrance_shuffle_seed": EntranceShuffleSeed,
- "big_key_shuffle": big_key_shuffle,
- "small_key_shuffle": small_key_shuffle,
- "key_drop_shuffle": key_drop_shuffle,
- "compass_shuffle": compass_shuffle,
- "map_shuffle": map_shuffle,
- "restrict_dungeon_item_on_boss": RestrictBossItem,
- "item_pool": ItemPool,
- "item_functionality": ItemFunctionality,
- "enemy_health": EnemyHealth,
- "enemy_damage": EnemyDamage,
- "progressive": Progressive,
- "swordless": Swordless,
- "dungeon_counters": DungeonCounters,
- "retro_bow": RetroBow,
- "retro_caves": RetroCaves,
- "hints": Hints,
- "scams": Scams,
- "boss_shuffle": LTTPBosses,
- "pot_shuffle": PotShuffle,
- "enemy_shuffle": EnemyShuffle,
- "killable_thieves": KillableThieves,
- "bush_shuffle": BushShuffle,
- "shop_item_slots": ShopItemSlots,
- "randomize_shop_inventories": RandomizeShopInventories,
- "shuffle_shop_inventories": ShuffleShopInventories,
- "include_witch_hut": IncludeWitchHut,
- "randomize_shop_prices": RandomizeShopPrices,
- "randomize_cost_types": RandomizeCostTypes,
- "shop_price_modifier": ShopPriceModifier,
- "shuffle_capacity_upgrades": ShuffleCapacityUpgrades,
- "bombless_start": BomblessStart,
- "shuffle_prizes": ShufflePrizes,
- "tile_shuffle": TileShuffle,
- "misery_mire_medallion": MiseryMireMedallion,
- "turtle_rock_medallion": TurtleRockMedallion,
- "glitch_boots": GlitchBoots,
- "beemizer_total_chance": BeemizerTotalChance,
- "beemizer_trap_chance": BeemizerTrapChance,
- "timer": Timer,
- "countdown_start_time": CountdownStartTime,
- "red_clock_time": RedClockTime,
- "blue_clock_time": BlueClockTime,
- "green_clock_time": GreenClockTime,
- "death_link": DeathLink,
- "allow_collect": AllowCollect,
- "ow_palettes": OWPalette,
- "uw_palettes": UWPalette,
- "hud_palettes": HUDPalette,
- "sword_palettes": SwordPalette,
- "shield_palettes": ShieldPalette,
- # "link_palettes": LinkPalette,
- "heartbeep": HeartBeep,
- "heartcolor": HeartColor,
- "quickswap": QuickSwap,
- "menuspeed": MenuSpeed,
- "music": Music,
- "reduceflashing": ReduceFlashing,
- "triforcehud": TriforceHud,
+@dataclass
+class ALTTPOptions(PerGameCommonOptions):
+ accessibility: ItemsAccessibility
+ plando_connections: ALttPPlandoConnections
+ plando_texts: ALttPPlandoTexts
+ start_inventory_from_pool: StartInventoryPool
+ goal: Goal
+ mode: Mode
+ glitches_required: GlitchesRequired
+ dark_room_logic: DarkRoomLogic
+ open_pyramid: OpenPyramid
+ crystals_needed_for_gt: CrystalsTower
+ crystals_needed_for_ganon: CrystalsGanon
+ triforce_pieces_mode: TriforcePiecesMode
+ triforce_pieces_percentage: TriforcePiecesPercentage
+ triforce_pieces_required: TriforcePiecesRequired
+ triforce_pieces_available: TriforcePiecesAvailable
+ triforce_pieces_extra: TriforcePiecesExtra
+ entrance_shuffle: EntranceShuffle
+ entrance_shuffle_seed: EntranceShuffleSeed
+ big_key_shuffle: big_key_shuffle
+ small_key_shuffle: small_key_shuffle
+ key_drop_shuffle: key_drop_shuffle
+ compass_shuffle: compass_shuffle
+ map_shuffle: map_shuffle
+ restrict_dungeon_item_on_boss: RestrictBossItem
+ item_pool: ItemPool
+ item_functionality: ItemFunctionality
+ enemy_health: EnemyHealth
+ enemy_damage: EnemyDamage
+ progressive: Progressive
+ swordless: Swordless
+ dungeon_counters: DungeonCounters
+ retro_bow: RetroBow
+ retro_caves: RetroCaves
+ hints: Hints
+ scams: Scams
+ boss_shuffle: LTTPBosses
+ pot_shuffle: PotShuffle
+ enemy_shuffle: EnemyShuffle
+ killable_thieves: KillableThieves
+ bush_shuffle: BushShuffle
+ shop_item_slots: ShopItemSlots
+ randomize_shop_inventories: RandomizeShopInventories
+ shuffle_shop_inventories: ShuffleShopInventories
+ include_witch_hut: IncludeWitchHut
+ randomize_shop_prices: RandomizeShopPrices
+ randomize_cost_types: RandomizeCostTypes
+ shop_price_modifier: ShopPriceModifier
+ shuffle_capacity_upgrades: ShuffleCapacityUpgrades
+ bombless_start: BomblessStart
+ shuffle_prizes: ShufflePrizes
+ tile_shuffle: TileShuffle
+ misery_mire_medallion: MiseryMireMedallion
+ turtle_rock_medallion: TurtleRockMedallion
+ glitch_boots: GlitchBoots
+ beemizer_total_chance: BeemizerTotalChance
+ beemizer_trap_chance: BeemizerTrapChance
+ timer: Timer
+ countdown_start_time: CountdownStartTime
+ red_clock_time: RedClockTime
+ blue_clock_time: BlueClockTime
+ green_clock_time: GreenClockTime
+ death_link: DeathLink
+ allow_collect: AllowCollect
+ ow_palettes: OWPalette
+ uw_palettes: UWPalette
+ hud_palettes: HUDPalette
+ sword_palettes: SwordPalette
+ shield_palettes: ShieldPalette
+ # link_palettes: LinkPalette
+ heartbeep: HeartBeep
+ heartcolor: HeartColor
+ quickswap: QuickSwap
+ menuspeed: MenuSpeed
+ music: Music
+ reduceflashing: ReduceFlashing
+ triforcehud: TriforceHud
# removed:
- "goals": Removed,
- "smallkey_shuffle": Removed,
- "bigkey_shuffle": Removed,
-}
+ goals: Removed
+ smallkey_shuffle: Removed
+ bigkey_shuffle: Removed
diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py
index 224de6aaf7f3..73a77b03f532 100644
--- a/worlds/alttp/Rom.py
+++ b/worlds/alttp/Rom.py
@@ -782,8 +782,8 @@ def get_nonnative_item_sprite(code: int) -> int:
def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
- local_random = world.per_slot_randoms[player]
local_world = world.worlds[player]
+ local_random = local_world.random
# patch items
@@ -1867,7 +1867,7 @@ def apply_oof_sfx(rom, oof: str):
def apply_rom_settings(rom, beep, color, quickswap, menuspeed, music: bool, sprite: str, oof: str, palettes_options,
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
triforcehud: str = None, deathlink: bool = False, allowcollect: bool = False):
- local_random = random if not world else world.per_slot_randoms[player]
+ local_random = random if not world else world.worlds[player].random
disable_music: bool = not music
# enable instant item menu
if menuspeed == 'instant':
@@ -2197,8 +2197,9 @@ def write_string_to_rom(rom, target, string):
def write_strings(rom, world, player):
from . import ALTTPWorld
- local_random = world.per_slot_randoms[player]
+
w: ALTTPWorld = world.worlds[player]
+ local_random = w.random
tt = TextTable()
tt.removeUnwantedText()
@@ -2425,7 +2426,7 @@ def hint_text(dest, ped_hint=False):
if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or (
world.swordless[player] or world.glitches_required[player] == 'no_glitches')):
prog_bow_locs = world.find_item_locations('Progressive Bow', player, True)
- world.per_slot_randoms[player].shuffle(prog_bow_locs)
+ local_random.shuffle(prog_bow_locs)
found_bow = False
found_bow_alt = False
while prog_bow_locs and not (found_bow and found_bow_alt):
diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py
index f897d3762929..b5489906889f 100644
--- a/worlds/alttp/__init__.py
+++ b/worlds/alttp/__init__.py
@@ -1,28 +1,27 @@
import logging
import os
import random
-import settings
import threading
import typing
-import Utils
+import settings
from BaseClasses import Item, CollectionState, Tutorial, MultiWorld
+from worlds.AutoWorld import World, WebWorld, LogicMixin
+from .Client import ALTTPSNIClient
from .Dungeons import create_dungeons, Dungeon
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
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 alttp_options, small_key_shuffle
+from .Options import ALTTPOptions, small_key_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 .Client import ALTTPSNIClient
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
-from .SubClasses import ALttPItem, LTTPRegionType
-from worlds.AutoWorld import World, WebWorld, LogicMixin
from .StateHelpers import can_buy_unlimited
+from .SubClasses import ALttPItem, LTTPRegionType
lttp_logger = logging.getLogger("A Link to the Past")
@@ -132,7 +131,8 @@ class ALTTPWorld(World):
Ganon!
"""
game = "A Link to the Past"
- option_definitions = alttp_options
+ options_dataclass = ALTTPOptions
+ options: ALTTPOptions
settings_key = "lttp_options"
settings: typing.ClassVar[ALTTPSettings]
topology_present = True
@@ -286,13 +286,22 @@ def stage_assert_generate(cls, multiworld: MultiWorld):
if not os.path.exists(rom_file):
raise FileNotFoundError(rom_file)
if multiworld.is_race:
- import xxtea
+ 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):
+ # write old options
+ import dataclasses
+ is_first = self.player == min(self.multiworld.get_game_players(self.game))
+
+ for field in dataclasses.fields(self.options_dataclass):
+ if is_first:
+ setattr(self.multiworld, field.name, {})
+ getattr(self.multiworld, field.name)[self.player] = getattr(self.options, field.name)
+ # end of old options re-establisher
player = self.player
multiworld = self.multiworld
@@ -536,12 +545,10 @@ def stage_generate_output(cls, multiworld, output_directory):
@property
def use_enemizer(self) -> bool:
- world = self.multiworld
- player = self.player
- return bool(world.boss_shuffle[player] or world.enemy_shuffle[player]
- or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
- or world.pot_shuffle[player] or world.bush_shuffle[player]
- or world.killable_thieves[player])
+ 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
diff --git a/worlds/alttp/docs/fr_A Link to the Past.md b/worlds/alttp/docs/fr_A Link to the Past.md
new file mode 100644
index 000000000000..a9ff8646b3f2
--- /dev/null
+++ b/worlds/alttp/docs/fr_A Link to the Past.md
@@ -0,0 +1,32 @@
+# A Link to the Past
+
+## Où se trouve la page des paramètres ?
+
+La [page des paramètres du joueur pour ce jeu](../player-options) contient tous les paramètres dont vous avez besoin
+pour configurer et exporter le fichier.
+
+## Quel est l'effet de la randomisation sur ce jeu ?
+
+Les objets que le joueur devrait normalement obtenir au cours du jeu ont été déplacés. Il y a tout de même une logique
+pour que le jeu puisse être terminé, mais dû au mélange des objets, le joueur peut avoir besoin d'accéder à certaines
+zones plus tôt que dans le jeu original.
+
+## Quels sont les objets et endroits mélangés ?
+
+Tous les objets principaux, les collectibles et munitions peuvent être mélangés, et tous les endroits qui
+pourraient contenir un de ces objets peuvent avoir leur contenu modifié.
+
+## Quels objets peuvent être dans le monde d'un autre joueur ?
+
+Un objet pouvant être mélangé peut être aussi placé dans le monde d'un autre joueur. Il est possible de limiter certains
+objets à votre propre monde.
+
+## À quoi ressemble un objet d'un autre monde dans LttP ?
+
+Les objets appartenant à d'autres mondes sont représentés par une Étoile de Super Mario World.
+
+## Quand le joueur reçoit un objet, que ce passe-t-il ?
+
+Quand le joueur reçoit un objet, Link montrera l'objet au monde en le mettant au-dessus de sa tête. C'est bon pour
+les affaires !
+
diff --git a/worlds/alttp/docs/multiworld_fr.md b/worlds/alttp/docs/multiworld_fr.md
index 310f3a4f96c4..0638d843e810 100644
--- a/worlds/alttp/docs/multiworld_fr.md
+++ b/worlds/alttp/docs/multiworld_fr.md
@@ -1,41 +1,28 @@
# Guide d'installation du MultiWorld de A Link to the Past Randomizer
-
-
-
-
## Logiciels requis
-- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
-- [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Inclus dans les utilitaires précédents)
+- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
+- [SNI](https://github.com/alttpo/sni/releases). Inclus avec l'installation d'Archipelago ci-dessus.
+ - SNI n'est pas compatible avec (Q)Usb2Snes.
- Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES
- - Un émulateur capable d'éxécuter des scripts Lua
- ([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
- [BizHawk](https://tasvideos.org/BizHawk))
- - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle
- compatible
-- Le fichier ROM de la v1.0 japonaise, sûrement nommé `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
+ - Un émulateur capable de se connecter à SNI
+ [snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), ([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
+ [BSNES-plus](https://github.com/black-sliver/bsnes-plus),
+ [BizHawk](https://tasvideos.org/BizHawk), ou
+ [RetroArch](https://retroarch.com?page=platforms) 1.10.1 ou plus récent). Ou,
+ - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle compatible. **À noter:
+ les SNES minis ne sont pas encore supportés par SNI. Certains utilisateurs rapportent avoir du succès avec QUsb2Snes pour ce système,
+ mais ce n'est pas supporté.**
+- Le fichier ROM de la v1.0 japonaise, habituellement nommé `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
## Procédure d'installation
-### Installation sur Windows
-
-1. Téléchargez et installez les utilitaires du MultiWorld à l'aide du lien au-dessus, faites attention à bien installer
- la version la plus récente.
- **Le fichier se situe dans la section "assets" en bas des informations de version**. Si vous voulez jouer des parties
- classiques de multiworld, téléchargez `Setup.BerserkerMultiWorld.exe`
- - Si vous voulez jouer à la version alternative avec le mélangeur de portes dans les donjons, vous téléchargez le
- fichier
- `Setup.BerserkerMultiWorld.Doors.exe`.
- - Durant le processus d'installation, il vous sera demandé de localiser votre ROM v1.0 japonaise. Si vous avez déjÃ
- installé le logiciel auparavant et qu'il s'agit simplement d'une mise à jour, la localisation de la ROM originale
- ne sera pas requise.
- - Il vous sera peut-être également demandé d'installer Microsoft Visual C++. Si vous le possédez déjà (possiblement
- parce qu'un jeu Steam l'a déjà installé), l'installateur ne reproposera pas de l'installer.
-
-2. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme
+1. Téléchargez et installez [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). **L'installateur se situe dans la section "assets" en bas des informations de version**.
+
+2. Si c'est la première fois que vous faites une génération locale ou un patch, il vous sera demandé votre fichier ROM de base. Il s'agit de votre fichier ROM Link to the Past japonais. Cet étape n'a besoin d'être faite qu'une seule fois.
+
+3. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme
programme par défaut pour ouvrir vos ROMs.
1. Extrayez votre dossier d'émulateur sur votre Bureau, ou à un endroit dont vous vous souviendrez.
2. Faites un clic droit sur un fichier ROM et sélectionnez **Ouvrir avec...**
@@ -44,58 +31,6 @@
5. Naviguez dans les dossiers jusqu'au fichier `.exe` de votre émulateur et choisissez **Ouvrir**. Ce fichier
devrait se trouver dans le dossier que vous avez extrait à la première étape.
-### Installation sur Mac
-
-- Des volontaires sont recherchés pour remplir cette section ! Contactez **Farrak Kilhn** sur Discord si vous voulez
- aider.
-
-## Configurer son fichier YAML
-
-### Qu'est-ce qu'un fichier YAML et pourquoi en ai-je besoin ?
-
-Votre fichier YAML contient un ensemble d'options de configuration qui fournissent au générateur des informations sur
-comment il devrait générer votre seed. Chaque joueur d'un multiwolrd devra fournir son propre fichier YAML. Cela permet
-à chaque joueur d'apprécier une expérience customisée selon ses goûts, et les différents joueurs d'un même multiworld
-peuvent avoir différentes options.
-
-### Où est-ce que j'obtiens un fichier YAML ?
-
-La page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options) vous permet de configurer vos
-paramètres personnels et de les exporter vers un fichier YAML.
-
-### Configuration avancée du fichier YAML
-
-Une version plus avancée du fichier YAML peut être créée en utilisant la page
-des [paramètres de pondération](/games/A Link to the Past/weighted-options), qui vous permet de configurer jusqu'Ã
-trois préréglages. Cette page a de nombreuses options qui sont essentiellement représentées avec des curseurs
-glissants. Cela vous permet de choisir quelles sont les chances qu'une certaine option apparaisse par rapport aux
-autres disponibles dans une même catégorie.
-
-Par exemple, imaginez que le générateur crée un seau étiqueté "Mélange des cartes", et qu'il place un morceau de papier
-pour chaque sous-option. Imaginez également que la valeur pour "On" est 20 et la valeur pour "Off" est 40.
-
-Dans cet exemple, il y a soixante morceaux de papier dans le seau : vingt pour "On" et quarante pour "Off". Quand le
-générateur décide s'il doit oui ou non activer le mélange des cartes pour votre partie, , il tire aléatoirement un
-papier dans le seau. Dans cet exemple, il y a de plus grandes chances d'avoir le mélange de cartes désactivé.
-
-S'il y a une option dont vous ne voulez jamais, mettez simplement sa valeur à zéro. N'oubliez pas qu'il faut que pour
-chaque paramètre il faut au moins une option qui soit paramétrée sur un nombre strictement positif.
-
-### Vérifier son fichier YAML
-
-Si vous voulez valider votre fichier YAML pour être sûr qu'il fonctionne, vous pouvez le vérifier sur la page du
-[Validateur de YAML](/check).
-
-## Générer une partie pour un joueur
-
-1. Aller sur la page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options), configurez vos options,
- et cliquez sur le bouton "Generate Game".
-2. Il vous sera alors présenté une page d'informations sur la seed, où vous pourrez télécharger votre patch.
-3. Double-cliquez sur le patch et l'émulateur devrait se lancer automatiquement avec la seed. Etant donné que le client
- n'est pas requis pour les parties à un joueur, vous pouvez le fermer ainsi que l'interface Web (WebUI).
-
-## Rejoindre un MultiWorld
-
### Obtenir son patch et créer sa ROM
Quand vous rejoignez un multiworld, il vous sera demandé de fournir votre fichier YAML à celui qui héberge la partie ou
@@ -109,35 +44,58 @@ automatiquement le client, et devrait créer la ROM dans le même dossier que vo
#### Avec un émulateur
-Quand le client se lance automatiquement, QUsb2Snes devrait se lancer automatiquement également en arrière-plan. Si
+Quand le client se lance automatiquement, SNI devrait se lancer automatiquement également en arrière-plan. Si
c'est la première fois qu'il démarre, il vous sera peut-être demandé de l'autoriser à communiquer à travers le pare-feu
Windows.
+#### snes9x-nwa
+
+1. Cliquez sur 'Network Menu' et cochez **Enable Emu Network Control**
+2. Chargez votre ROM si ce n'est pas déjà fait.
+
##### snes9x-rr
1. Chargez votre ROM si ce n'est pas déjà fait.
2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting**
3. Cliquez alors sur **New Lua Script Window...**
4. Dans la nouvelle fenêtre, sélectionnez **Browse...**
-5. Dirigez vous vers le dossier où vous avez extrait snes9x-rr, allez dans le dossier `lua`, puis
- choisissez `multibridge.lua`
-6. Remarquez qu'un nom vous a été assigné, et que l'interface Web affiche "SNES Device: Connected", avec ce même nom
- dans le coin en haut à gauche.
+5. Sélectionnez le fichier lua connecteur inclus avec votre client
+ - Recherchez `/SNI/lua/` dans votre fichier Archipelago.
+6. Si vous avez une erreur en chargeant le script indiquant `socket.dll missing` ou similaire, naviguez vers le fichier du
+lua que vous utilisez dans votre explorateur de fichiers et copiez le `socket.dll` Ã la base de votre installation snes9x.
+
+#### BSNES-Plus
+
+1. Chargez votre ROM si ce n'est pas déjà fait.
+2. L'émulateur devrait automatiquement se connecter lorsque SNI se lancera.
##### BizHawk
-1. Assurez vous d'avoir le coeur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant
+1. Assurez vous d'avoir le cœur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant
ces options de menu :
- `Config --> Cores --> SNES --> BSNES`
- Une fois le coeur changé, vous devez redémarrer BizHawk.
+ - (≤ 2.8) `Config` 〉 `Cores` 〉 `SNES` 〉 `BSNES`
+ - (≥ 2.9) `Config` 〉 `Preferred Cores` 〉 `SNES` 〉 `BSNESv115+`
+ Une fois le cœur changé, rechargez le avec Ctrl+R (par défaut).
2. Chargez votre ROM si ce n'est pas déjà fait.
-3. Cliquez sur le menu "Tools" et cliquez sur **Lua Console**
-4. Cliquez sur le bouton pour ouvrir un nouveau script Lua.
-5. Dirigez vous vers le dossier d'installation des utilitaires du MultiWorld, puis dans les dossiers suivants :
- `QUsb2Snes/Qusb2Snes/LuaBridge`
-6. Sélectionnez `luabridge.lua` et cliquez sur "Open".
-7. Remarquez qu'un nom vous a été assigné, et que l'interface Web affiche "SNES Device: Connected", avec ce même nom
- dans le coin en haut à gauche.
+3. Glissez et déposez le fichier `Connector.lua` que vous avez téléchargé ci-dessus sur la fenêtre principale EmuHawk.
+ - Recherchez `/SNI/lua/` dans votre fichier Archipelago.
+ - Vous pouvez aussi ouvrir la console Lua manuellement, cliquez sur `Script` 〉 `Open Script`, et naviguez sur `Connecteur.lua`
+ avec le sélecteur de fichiers.
+
+##### RetroArch 1.10.1 ou plus récent
+
+Vous n'avez qu'à faire ces étapes qu'une fois.
+
+1. Entrez dans le menu principal RetroArch
+2. Allez dans Réglages --> Interface utilisateur. Mettez "Afficher les réglages avancés" sur ON.
+3. Allez dans Réglages --> Réseau. Mettez "Commandes Réseau" sur ON. (trouvé sous Request Device 16.) Laissez le
+Port des commandes réseau à 555355.
+
+![Screenshot of Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-fr.png)
+4. Allez dans Menu Principal --> Mise à jour en ligne --> Téléchargement de cœurs. Descendez jusqu'a"Nintendo - SNES / SFC (bsnes-mercury Performance)" et
+ sélectionnez le.
+
+Quand vous chargez une ROM, veillez a sélectionner un cœur **bsnes-mercury**. Ce sont les seuls cœurs qui autorisent les outils externs à lire les données d'une ROM.
#### Avec une solution matérielle
@@ -147,10 +105,7 @@ le maintenant. Les utilisateurs de SD2SNES et de FXPak Pro peuvent télécharger
[sur cette page](http://usb2snes.com/#supported-platforms).
1. Fermez votre émulateur, qui s'est potentiellement lancé automatiquement.
-2. Fermez QUsb2Snes, qui s'est lancé automatiquement avec le client.
-3. Lancez la version appropriée de QUsb2Snes (v0.7.16).
-4. Lancer votre console et chargez la ROM.
-5. Remarquez que l'interface Web affiche "SNES Device: Connected", avec le nom de votre appareil.
+2. Lancez votre console et chargez la ROM.
### Se connecter au MultiServer
@@ -165,47 +120,6 @@ l'interface Web.
### Jouer au jeu
-Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations
-pour avoir rejoint un multiworld !
-
-## Héberger un MultiWorld
-
-La méthode recommandée pour héberger une partie est d'utiliser le service d'hébergement fourni par
-[le site](https://berserkermulti.world/generate). Le processus est relativement simple :
-
-1. Récupérez les fichiers YAML des joueurs.
-2. Créez une archive zip contenant ces fichiers YAML.
-3. Téléversez l'archive zip sur le lien ci-dessus.
-4. Attendez un moment que les seed soient générées.
-5. Lorsque les seeds sont générées, vous serez redirigé vers une page d'informations "Seed Info".
-6. Cliquez sur "Create New Room". Cela vous amènera à la page du serveur. Fournissez le lien de cette page aux autres
- joueurs afin qu'ils puissent récupérer leurs patchs.
- **Note:** Les patchs fournis sur cette page permettront aux joueurs de se connecteur automatiquement au serveur,
- tandis que ceux de la page "Seed Info" non.
-7. Remarquez qu'un lien vers le traqueur du MultiWorld est en haut de la page de la salle. Vous devriez également
- fournir ce lien aux joueurs pour qu'ils puissent suivre la progression de la partie. N'importe quel personne voulant
- observer devrait avoir accès à ce lien.
-8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer.
-
-## Auto-tracking
-
-Si vous voulez utiliser l'auto-tracking, plusieurs logiciels offrent cette possibilité.
-Le logiciel recommandé pour l'auto-tracking actuellement est
-[OpenTracker](https://github.com/trippsc2/OpenTracker/releases).
-
-### Installation
-
-1. Téléchargez le fichier d'installation approprié pour votre ordinateur (Les utilisateurs Windows voudront le
- fichier `.msi`).
-2. Durant le processus d'installation, il vous sera peut-être demandé d'installer les outils "Microsoft Visual Studio
- Build Tools". Un lien est fourni durant l'installation d'OpenTracker, et celle des outils doit se faire manuellement.
-
-### Activer l'auto-tracking
-
-1. Une fois OpenTracker démarré, cliquez sur le menu "Tracking" en haut de la fenêtre, puis choisissez **
- AutoTracker...**
-2. Appuyez sur le bouton **Get Devices**
-3. Sélectionnez votre appareil SNES dans la liste déroulante.
-4. Si vous voulez tracquer les petites clés ainsi que les objets des donjons, cochez la case **Race Illegal Tracking**
-5. Cliquez sur le bouton **Start Autotracking**
-6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire
+Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations,
+vous venez de rejoindre un multiworld ! Vous pouvez exécuter différentes commandes dans votre client. Pour plus d'informations
+sur ces commandes, vous pouvez utiliser `/help` pour les commandes locales et `!help` pour les commandes serveur.
diff --git a/worlds/alttp/docs/retroarch-network-commands-fr.png b/worlds/alttp/docs/retroarch-network-commands-fr.png
new file mode 100644
index 000000000000..60eba5b1b0fb
Binary files /dev/null and b/worlds/alttp/docs/retroarch-network-commands-fr.png differ
diff --git a/worlds/cv64/rom.py b/worlds/cv64/rom.py
index db621c7101d6..7af4e3807ac2 100644
--- a/worlds/cv64/rom.py
+++ b/worlds/cv64/rom.py
@@ -684,38 +684,37 @@ def apply_patches(caller: APProcedurePatch, rom: bytes, options_file: str) -> by
# Disable the 3HBs checking and setting flags when breaking them and enable their individual items checking and
# setting flags instead.
- if options["multi_hit_breakables"]:
- rom_data.write_int32(0xE87F8, 0x00000000) # NOP
- rom_data.write_int16(0xE836C, 0x1000)
- rom_data.write_int32(0xE8B40, 0x0C0FF3CD) # JAL 0x803FCF34
- rom_data.write_int32s(0xBFCF34, patches.three_hit_item_flags_setter)
- # Villa foyer chandelier-specific functions (yeah, IDK why KCEK made different functions for this one)
- rom_data.write_int32(0xE7D54, 0x00000000) # NOP
- rom_data.write_int16(0xE7908, 0x1000)
- rom_data.write_byte(0xE7A5C, 0x10)
- rom_data.write_int32(0xE7F08, 0x0C0FF3DF) # JAL 0x803FCF7C
- rom_data.write_int32s(0xBFCF7C, patches.chandelier_item_flags_setter)
-
- # New flag values to put in each 3HB vanilla flag's spot
- rom_data.write_int32(0x10C7C8, 0x8000FF48) # FoS dirge maiden rock
- rom_data.write_int32(0x10C7B0, 0x0200FF48) # FoS S1 bridge rock
- rom_data.write_int32(0x10C86C, 0x0010FF48) # CW upper rampart save nub
- rom_data.write_int32(0x10C878, 0x4000FF49) # CW Dracula switch slab
- rom_data.write_int32(0x10CAD8, 0x0100FF49) # Tunnel twin arrows slab
- rom_data.write_int32(0x10CAE4, 0x0004FF49) # Tunnel lonesome bucket pit rock
- rom_data.write_int32(0x10CB54, 0x4000FF4A) # UW poison parkour ledge
- rom_data.write_int32(0x10CB60, 0x0080FF4A) # UW skeleton crusher ledge
- rom_data.write_int32(0x10CBF0, 0x0008FF4A) # CC Behemoth crate
- rom_data.write_int32(0x10CC2C, 0x2000FF4B) # CC elevator pedestal
- rom_data.write_int32(0x10CC70, 0x0200FF4B) # CC lizard locker slab
- rom_data.write_int32(0x10CD88, 0x0010FF4B) # ToE pre-midsavepoint platforms ledge
- rom_data.write_int32(0x10CE6C, 0x4000FF4C) # ToSci invisible bridge crate
- rom_data.write_int32(0x10CF20, 0x0080FF4C) # CT inverted battery slab
- rom_data.write_int32(0x10CF2C, 0x0008FF4C) # CT inverted door slab
- rom_data.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab
- rom_data.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab
- rom_data.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier
- rom_data.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data
+ rom_data.write_int32(0xE87F8, 0x00000000) # NOP
+ rom_data.write_int16(0xE836C, 0x1000)
+ rom_data.write_int32(0xE8B40, 0x0C0FF3CD) # JAL 0x803FCF34
+ rom_data.write_int32s(0xBFCF34, patches.three_hit_item_flags_setter)
+ # Villa foyer chandelier-specific functions (yeah, IDK why KCEK made different functions for this one)
+ rom_data.write_int32(0xE7D54, 0x00000000) # NOP
+ rom_data.write_int16(0xE7908, 0x1000)
+ rom_data.write_byte(0xE7A5C, 0x10)
+ rom_data.write_int32(0xE7F08, 0x0C0FF3DF) # JAL 0x803FCF7C
+ rom_data.write_int32s(0xBFCF7C, patches.chandelier_item_flags_setter)
+
+ # New flag values to put in each 3HB vanilla flag's spot
+ rom_data.write_int32(0x10C7C8, 0x8000FF48) # FoS dirge maiden rock
+ rom_data.write_int32(0x10C7B0, 0x0200FF48) # FoS S1 bridge rock
+ rom_data.write_int32(0x10C86C, 0x0010FF48) # CW upper rampart save nub
+ rom_data.write_int32(0x10C878, 0x4000FF49) # CW Dracula switch slab
+ rom_data.write_int32(0x10CAD8, 0x0100FF49) # Tunnel twin arrows slab
+ rom_data.write_int32(0x10CAE4, 0x0004FF49) # Tunnel lonesome bucket pit rock
+ rom_data.write_int32(0x10CB54, 0x4000FF4A) # UW poison parkour ledge
+ rom_data.write_int32(0x10CB60, 0x0080FF4A) # UW skeleton crusher ledge
+ rom_data.write_int32(0x10CBF0, 0x0008FF4A) # CC Behemoth crate
+ rom_data.write_int32(0x10CC2C, 0x2000FF4B) # CC elevator pedestal
+ rom_data.write_int32(0x10CC70, 0x0200FF4B) # CC lizard locker slab
+ rom_data.write_int32(0x10CD88, 0x0010FF4B) # ToE pre-midsavepoint platforms ledge
+ rom_data.write_int32(0x10CE6C, 0x4000FF4C) # ToSci invisible bridge crate
+ rom_data.write_int32(0x10CF20, 0x0080FF4C) # CT inverted battery slab
+ rom_data.write_int32(0x10CF2C, 0x0008FF4C) # CT inverted door slab
+ rom_data.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab
+ rom_data.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab
+ rom_data.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier
+ rom_data.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data
# Once-per-frame gameplay checks
rom_data.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034
diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py
index b8f2aad6ff94..37eae9b447d1 100644
--- a/worlds/dlcquest/__init__.py
+++ b/worlds/dlcquest/__init__.py
@@ -72,8 +72,16 @@ def create_items(self):
self.multiworld.itempool += created_items
- if self.options.campaign == Options.Campaign.option_basic or self.options.campaign == Options.Campaign.option_both:
- self.multiworld.early_items[self.player]["Movement Pack"] = 1
+ campaign = self.options.campaign
+ has_both = campaign == Options.Campaign.option_both
+ has_base = campaign == Options.Campaign.option_basic or has_both
+ has_big_bundles = self.options.coinsanity and self.options.coinbundlequantity > 50
+ early_items = self.multiworld.early_items
+ if has_base:
+ if has_both and has_big_bundles:
+ early_items[self.player]["Incredibly Important Pack"] = 1
+ else:
+ early_items[self.player]["Movement Pack"] = 1
for item in items_to_exclude:
if item in self.multiworld.itempool:
@@ -82,7 +90,7 @@ def create_items(self):
def precollect_coinsanity(self):
if self.options.campaign == Options.Campaign.option_basic:
if self.options.coinsanity == Options.CoinSanity.option_coin and self.options.coinbundlequantity >= 5:
- self.multiworld.push_precollected(self.create_item("Movement Pack"))
+ self.multiworld.push_precollected(self.create_item("DLC Quest: Coin Bundle"))
def create_item(self, item: Union[str, ItemData], classification: ItemClassification = None) -> DLCQuestItem:
if isinstance(item, str):
diff --git a/worlds/doom_1993/Options.py b/worlds/doom_1993/Options.py
index f65952d3eb49..b4fc50aac674 100644
--- a/worlds/doom_1993/Options.py
+++ b/worlds/doom_1993/Options.py
@@ -112,7 +112,7 @@ class StartWithComputerAreaMaps(Toggle):
class ResetLevelOnDeath(DefaultOnToggle):
"""When dying, levels are reset and monsters respawned. But inventory and checks are kept.
Turning this setting off is considered easy mode. Good for new players that don't know the levels well."""
- display_name="Reset Level on Death"
+ display_name = "Reset Level on Death"
class Episode1(DefaultOnToggle):
diff --git a/worlds/doom_ii/Options.py b/worlds/doom_ii/Options.py
index cc39512a176e..6ecfb1aecd6c 100644
--- a/worlds/doom_ii/Options.py
+++ b/worlds/doom_ii/Options.py
@@ -102,7 +102,7 @@ class StartWithComputerAreaMaps(Toggle):
class ResetLevelOnDeath(DefaultOnToggle):
"""When dying, levels are reset and monsters respawned. But inventory and checks are kept.
Turning this setting off is considered easy mode. Good for new players that don't know the levels well."""
- display_message="Reset level on death"
+ display_name = "Reset Level on Death"
class Episode1(DefaultOnToggle):
diff --git a/worlds/faxanadu/Items.py b/worlds/faxanadu/Items.py
new file mode 100644
index 000000000000..4815fde9de66
--- /dev/null
+++ b/worlds/faxanadu/Items.py
@@ -0,0 +1,58 @@
+from BaseClasses import ItemClassification
+from typing import List, Optional
+
+
+class ItemDef:
+ def __init__(self,
+ id: Optional[int],
+ name: str,
+ classification: ItemClassification,
+ count: int,
+ progression_count: int,
+ prefill_location: Optional[str]):
+ self.id = id
+ self.name = name
+ self.classification = classification
+ self.count = count
+ self.progression_count = progression_count
+ self.prefill_location = prefill_location
+
+
+items: List[ItemDef] = [
+ ItemDef(400000, 'Progressive Sword', ItemClassification.progression, 4, 0, None),
+ ItemDef(400001, 'Progressive Armor', ItemClassification.progression, 3, 0, None),
+ ItemDef(400002, 'Progressive Shield', ItemClassification.useful, 4, 0, None),
+ ItemDef(400003, 'Spring Elixir', ItemClassification.progression, 1, 0, None),
+ ItemDef(400004, 'Mattock', ItemClassification.progression, 1, 0, None),
+ ItemDef(400005, 'Unlock Wingboots', ItemClassification.progression, 1, 0, None),
+ ItemDef(400006, 'Key Jack', ItemClassification.progression, 1, 0, None),
+ ItemDef(400007, 'Key Queen', ItemClassification.progression, 1, 0, None),
+ ItemDef(400008, 'Key King', ItemClassification.progression, 1, 0, None),
+ ItemDef(400009, 'Key Joker', ItemClassification.progression, 1, 0, None),
+ ItemDef(400010, 'Key Ace', ItemClassification.progression, 1, 0, None),
+ ItemDef(400011, 'Ring of Ruby', ItemClassification.progression, 1, 0, None),
+ ItemDef(400012, 'Ring of Dworf', ItemClassification.progression, 1, 0, None),
+ ItemDef(400013, 'Demons Ring', ItemClassification.progression, 1, 0, None),
+ ItemDef(400014, 'Black Onyx', ItemClassification.progression, 1, 0, None),
+ ItemDef(None, 'Sky Spring Flow', ItemClassification.progression, 1, 0, 'Sky Spring'),
+ ItemDef(None, 'Tower of Fortress Spring Flow', ItemClassification.progression, 1, 0, 'Tower of Fortress Spring'),
+ ItemDef(None, 'Joker Spring Flow', ItemClassification.progression, 1, 0, 'Joker Spring'),
+ ItemDef(400015, 'Deluge', ItemClassification.progression, 1, 0, None),
+ ItemDef(400016, 'Thunder', ItemClassification.useful, 1, 0, None),
+ ItemDef(400017, 'Fire', ItemClassification.useful, 1, 0, None),
+ ItemDef(400018, 'Death', ItemClassification.useful, 1, 0, None),
+ ItemDef(400019, 'Tilte', ItemClassification.useful, 1, 0, None),
+ ItemDef(400020, 'Ring of Elf', ItemClassification.useful, 1, 0, None),
+ ItemDef(400021, 'Magical Rod', ItemClassification.useful, 1, 0, None),
+ ItemDef(400022, 'Pendant', ItemClassification.useful, 1, 0, None),
+ ItemDef(400023, 'Hourglass', ItemClassification.filler, 6, 0, None),
+ # We need at least 4 red potions for the Tower of Red Potion. Up to the player to save them up!
+ ItemDef(400024, 'Red Potion', ItemClassification.filler, 15, 4, None),
+ ItemDef(400025, 'Elixir', ItemClassification.filler, 4, 0, None),
+ ItemDef(400026, 'Glove', ItemClassification.filler, 5, 0, None),
+ ItemDef(400027, 'Ointment', ItemClassification.filler, 8, 0, None),
+ ItemDef(400028, 'Poison', ItemClassification.trap, 13, 0, None),
+ ItemDef(None, 'Killed Evil One', ItemClassification.progression, 1, 0, 'Evil One'),
+ # Placeholder item so the game knows which shop slot to prefill wingboots
+ ItemDef(400029, 'Wingboots', ItemClassification.useful, 0, 0, None),
+]
diff --git a/worlds/faxanadu/Locations.py b/worlds/faxanadu/Locations.py
new file mode 100644
index 000000000000..ebb785f9391a
--- /dev/null
+++ b/worlds/faxanadu/Locations.py
@@ -0,0 +1,199 @@
+from typing import List, Optional
+
+
+class LocationType():
+ world = 1 # Just standing there in the world
+ hidden = 2 # Kill all monsters in the room to reveal, each "item room" counter tick.
+ boss_reward = 3 # Kill a boss to reveal the item
+ shop = 4 # Buy at a shop
+ give = 5 # Given by an NPC
+ spring = 6 # Activatable spring
+ boss = 7 # Entity to kill to trigger the check
+
+
+class ItemType():
+ unknown = 0 # Or don't care
+ red_potion = 1
+
+
+class LocationDef:
+ def __init__(self, id: Optional[int], name: str, region: str, type: int, original_item: int):
+ self.id = id
+ self.name = name
+ self.region = region
+ self.type = type
+ self.original_item = original_item
+
+
+locations: List[LocationDef] = [
+ # Eolis
+ LocationDef(400100, 'Eolis Guru', 'Eolis', LocationType.give, ItemType.unknown),
+ LocationDef(400101, 'Eolis Key Jack', 'Eolis', LocationType.shop, ItemType.unknown),
+ LocationDef(400102, 'Eolis Hand Dagger', 'Eolis', LocationType.shop, ItemType.unknown),
+ LocationDef(400103, 'Eolis Red Potion', 'Eolis', LocationType.shop, ItemType.red_potion),
+ LocationDef(400104, 'Eolis Elixir', 'Eolis', LocationType.shop, ItemType.unknown),
+ LocationDef(400105, 'Eolis Deluge', 'Eolis', LocationType.shop, ItemType.unknown),
+
+ # Path to Apolune
+ LocationDef(400106, 'Path to Apolune Magic Shield', 'Path to Apolune', LocationType.shop, ItemType.unknown),
+ LocationDef(400107, 'Path to Apolune Death', 'Path to Apolune', LocationType.shop, ItemType.unknown),
+
+ # Apolune
+ LocationDef(400108, 'Apolune Small Shield', 'Apolune', LocationType.shop, ItemType.unknown),
+ LocationDef(400109, 'Apolune Hand Dagger', 'Apolune', LocationType.shop, ItemType.unknown),
+ LocationDef(400110, 'Apolune Deluge', 'Apolune', LocationType.shop, ItemType.unknown),
+ LocationDef(400111, 'Apolune Red Potion', 'Apolune', LocationType.shop, ItemType.red_potion),
+ LocationDef(400112, 'Apolune Key Jack', 'Apolune', LocationType.shop, ItemType.unknown),
+
+ # Tower of Trunk
+ LocationDef(400113, 'Tower of Trunk Hidden Mattock', 'Tower of Trunk', LocationType.hidden, ItemType.unknown),
+ LocationDef(400114, 'Tower of Trunk Hidden Hourglass', 'Tower of Trunk', LocationType.hidden, ItemType.unknown),
+ LocationDef(400115, 'Tower of Trunk Boss Mattock', 'Tower of Trunk', LocationType.boss_reward, ItemType.unknown),
+
+ # Path to Forepaw
+ LocationDef(400116, 'Path to Forepaw Hidden Red Potion', 'Path to Forepaw', LocationType.hidden, ItemType.red_potion),
+ LocationDef(400117, 'Path to Forepaw Glove', 'Path to Forepaw', LocationType.world, ItemType.unknown),
+
+ # Forepaw
+ LocationDef(400118, 'Forepaw Long Sword', 'Forepaw', LocationType.shop, ItemType.unknown),
+ LocationDef(400119, 'Forepaw Studded Mail', 'Forepaw', LocationType.shop, ItemType.unknown),
+ LocationDef(400120, 'Forepaw Small Shield', 'Forepaw', LocationType.shop, ItemType.unknown),
+ LocationDef(400121, 'Forepaw Red Potion', 'Forepaw', LocationType.shop, ItemType.red_potion),
+ LocationDef(400122, 'Forepaw Wingboots', 'Forepaw', LocationType.shop, ItemType.unknown),
+ LocationDef(400123, 'Forepaw Key Jack', 'Forepaw', LocationType.shop, ItemType.unknown),
+ LocationDef(400124, 'Forepaw Key Queen', 'Forepaw', LocationType.shop, ItemType.unknown),
+
+ # Trunk
+ LocationDef(400125, 'Trunk Hidden Ointment', 'Trunk', LocationType.hidden, ItemType.unknown),
+ LocationDef(400126, 'Trunk Hidden Red Potion', 'Trunk', LocationType.hidden, ItemType.red_potion),
+ LocationDef(400127, 'Trunk Red Potion', 'Trunk', LocationType.world, ItemType.red_potion),
+ LocationDef(None, 'Sky Spring', 'Trunk', LocationType.spring, ItemType.unknown),
+
+ # Joker Spring
+ LocationDef(400128, 'Joker Spring Ruby Ring', 'Joker Spring', LocationType.give, ItemType.unknown),
+ LocationDef(None, 'Joker Spring', 'Joker Spring', LocationType.spring, ItemType.unknown),
+
+ # Tower of Fortress
+ LocationDef(400129, 'Tower of Fortress Poison 1', 'Tower of Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400130, 'Tower of Fortress Poison 2', 'Tower of Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400131, 'Tower of Fortress Hidden Wingboots', 'Tower of Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400132, 'Tower of Fortress Ointment', 'Tower of Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400133, 'Tower of Fortress Boss Wingboots', 'Tower of Fortress', LocationType.boss_reward, ItemType.unknown),
+ LocationDef(400134, 'Tower of Fortress Elixir', 'Tower of Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400135, 'Tower of Fortress Guru', 'Tower of Fortress', LocationType.give, ItemType.unknown),
+ LocationDef(None, 'Tower of Fortress Spring', 'Tower of Fortress', LocationType.spring, ItemType.unknown),
+
+ # Path to Mascon
+ LocationDef(400136, 'Path to Mascon Hidden Wingboots', 'Path to Mascon', LocationType.hidden, ItemType.unknown),
+
+ # Tower of Red Potion
+ LocationDef(400137, 'Tower of Red Potion', 'Tower of Red Potion', LocationType.world, ItemType.red_potion),
+
+ # Mascon
+ LocationDef(400138, 'Mascon Large Shield', 'Mascon', LocationType.shop, ItemType.unknown),
+ LocationDef(400139, 'Mascon Thunder', 'Mascon', LocationType.shop, ItemType.unknown),
+ LocationDef(400140, 'Mascon Mattock', 'Mascon', LocationType.shop, ItemType.unknown),
+ LocationDef(400141, 'Mascon Red Potion', 'Mascon', LocationType.shop, ItemType.red_potion),
+ LocationDef(400142, 'Mascon Key Jack', 'Mascon', LocationType.shop, ItemType.unknown),
+ LocationDef(400143, 'Mascon Key Queen', 'Mascon', LocationType.shop, ItemType.unknown),
+
+ # Path to Victim
+ LocationDef(400144, 'Misty Shop Death', 'Path to Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400145, 'Misty Shop Hourglass', 'Path to Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400146, 'Misty Shop Elixir', 'Path to Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400147, 'Misty Shop Red Potion', 'Path to Victim', LocationType.shop, ItemType.red_potion),
+ LocationDef(400148, 'Misty Doctor Office', 'Path to Victim', LocationType.hidden, ItemType.unknown),
+
+ # Tower of Suffer
+ LocationDef(400149, 'Tower of Suffer Hidden Wingboots', 'Tower of Suffer', LocationType.hidden, ItemType.unknown),
+ LocationDef(400150, 'Tower of Suffer Hidden Hourglass', 'Tower of Suffer', LocationType.hidden, ItemType.unknown),
+ LocationDef(400151, 'Tower of Suffer Pendant', 'Tower of Suffer', LocationType.boss_reward, ItemType.unknown),
+
+ # Victim
+ LocationDef(400152, 'Victim Full Plate', 'Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400153, 'Victim Mattock', 'Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400154, 'Victim Red Potion', 'Victim', LocationType.shop, ItemType.red_potion),
+ LocationDef(400155, 'Victim Key King', 'Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400156, 'Victim Key Queen', 'Victim', LocationType.shop, ItemType.unknown),
+ LocationDef(400157, 'Victim Tavern', 'Mist', LocationType.give, ItemType.unknown),
+
+ # Mist
+ LocationDef(400158, 'Mist Hidden Poison 1', 'Mist', LocationType.hidden, ItemType.unknown),
+ LocationDef(400159, 'Mist Hidden Poison 2', 'Mist', LocationType.hidden, ItemType.unknown),
+ LocationDef(400160, 'Mist Hidden Wingboots', 'Mist', LocationType.hidden, ItemType.unknown),
+ LocationDef(400161, 'Misty Magic Hall', 'Mist', LocationType.give, ItemType.unknown),
+ LocationDef(400162, 'Misty House', 'Mist', LocationType.give, ItemType.unknown),
+
+ # Useless Tower
+ LocationDef(400163, 'Useless Tower', 'Useless Tower', LocationType.hidden, ItemType.unknown),
+
+ # Tower of Mist
+ LocationDef(400164, 'Tower of Mist Hidden Ointment', 'Tower of Mist', LocationType.hidden, ItemType.unknown),
+ LocationDef(400165, 'Tower of Mist Elixir', 'Tower of Mist', LocationType.world, ItemType.unknown),
+ LocationDef(400166, 'Tower of Mist Black Onyx', 'Tower of Mist', LocationType.boss_reward, ItemType.unknown),
+
+ # Path to Conflate
+ LocationDef(400167, 'Path to Conflate Hidden Ointment', 'Path to Conflate', LocationType.hidden, ItemType.unknown),
+ LocationDef(400168, 'Path to Conflate Poison', 'Path to Conflate', LocationType.hidden, ItemType.unknown),
+
+ # Helm Branch
+ LocationDef(400169, 'Helm Branch Hidden Glove', 'Helm Branch', LocationType.hidden, ItemType.unknown),
+ LocationDef(400170, 'Helm Branch Battle Helmet', 'Helm Branch', LocationType.boss_reward, ItemType.unknown),
+
+ # Conflate
+ LocationDef(400171, 'Conflate Giant Blade', 'Conflate', LocationType.shop, ItemType.unknown),
+ LocationDef(400172, 'Conflate Magic Shield', 'Conflate', LocationType.shop, ItemType.unknown),
+ LocationDef(400173, 'Conflate Wingboots', 'Conflate', LocationType.shop, ItemType.unknown),
+ LocationDef(400174, 'Conflate Red Potion', 'Conflate', LocationType.shop, ItemType.red_potion),
+ LocationDef(400175, 'Conflate Guru', 'Conflate', LocationType.give, ItemType.unknown),
+
+ # Branches
+ LocationDef(400176, 'Branches Hidden Ointment', 'Branches', LocationType.hidden, ItemType.unknown),
+ LocationDef(400177, 'Branches Poison', 'Branches', LocationType.world, ItemType.unknown),
+ LocationDef(400178, 'Branches Hidden Mattock', 'Branches', LocationType.hidden, ItemType.unknown),
+ LocationDef(400179, 'Branches Hidden Hourglass', 'Branches', LocationType.hidden, ItemType.unknown),
+
+ # Path to Daybreak
+ LocationDef(400180, 'Path to Daybreak Hidden Wingboots 1', 'Path to Daybreak', LocationType.hidden, ItemType.unknown),
+ LocationDef(400181, 'Path to Daybreak Magical Rod', 'Path to Daybreak', LocationType.world, ItemType.unknown),
+ LocationDef(400182, 'Path to Daybreak Hidden Wingboots 2', 'Path to Daybreak', LocationType.hidden, ItemType.unknown),
+ LocationDef(400183, 'Path to Daybreak Poison', 'Path to Daybreak', LocationType.world, ItemType.unknown),
+ LocationDef(400184, 'Path to Daybreak Glove', 'Path to Daybreak', LocationType.world, ItemType.unknown),
+ LocationDef(400185, 'Path to Daybreak Battle Suit', 'Path to Daybreak', LocationType.boss_reward, ItemType.unknown),
+
+ # Daybreak
+ LocationDef(400186, 'Daybreak Tilte', 'Daybreak', LocationType.shop, ItemType.unknown),
+ LocationDef(400187, 'Daybreak Giant Blade', 'Daybreak', LocationType.shop, ItemType.unknown),
+ LocationDef(400188, 'Daybreak Red Potion', 'Daybreak', LocationType.shop, ItemType.red_potion),
+ LocationDef(400189, 'Daybreak Key King', 'Daybreak', LocationType.shop, ItemType.unknown),
+ LocationDef(400190, 'Daybreak Key Queen', 'Daybreak', LocationType.shop, ItemType.unknown),
+
+ # Dartmoor Castle
+ LocationDef(400191, 'Dartmoor Castle Hidden Hourglass', 'Dartmoor Castle', LocationType.hidden, ItemType.unknown),
+ LocationDef(400192, 'Dartmoor Castle Hidden Red Potion', 'Dartmoor Castle', LocationType.hidden, ItemType.red_potion),
+
+ # Dartmoor
+ LocationDef(400193, 'Dartmoor Giant Blade', 'Dartmoor', LocationType.shop, ItemType.unknown),
+ LocationDef(400194, 'Dartmoor Red Potion', 'Dartmoor', LocationType.shop, ItemType.red_potion),
+ LocationDef(400195, 'Dartmoor Key King', 'Dartmoor', LocationType.shop, ItemType.unknown),
+
+ # Fraternal Castle
+ LocationDef(400196, 'Fraternal Castle Hidden Ointment', 'Fraternal Castle', LocationType.hidden, ItemType.unknown),
+ LocationDef(400197, 'Fraternal Castle Shop Hidden Ointment', 'Fraternal Castle', LocationType.hidden, ItemType.unknown),
+ LocationDef(400198, 'Fraternal Castle Poison 1', 'Fraternal Castle', LocationType.world, ItemType.unknown),
+ LocationDef(400199, 'Fraternal Castle Poison 2', 'Fraternal Castle', LocationType.world, ItemType.unknown),
+ LocationDef(400200, 'Fraternal Castle Poison 3', 'Fraternal Castle', LocationType.world, ItemType.unknown),
+ # LocationDef(400201, 'Fraternal Castle Red Potion', 'Fraternal Castle', LocationType.world, ItemType.red_potion), # This location is inaccessible. Keeping commented for context.
+ LocationDef(400202, 'Fraternal Castle Hidden Hourglass', 'Fraternal Castle', LocationType.hidden, ItemType.unknown),
+ LocationDef(400203, 'Fraternal Castle Dragon Slayer', 'Fraternal Castle', LocationType.boss_reward, ItemType.unknown),
+ LocationDef(400204, 'Fraternal Castle Guru', 'Fraternal Castle', LocationType.give, ItemType.unknown),
+
+ # Evil Fortress
+ LocationDef(400205, 'Evil Fortress Ointment', 'Evil Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400206, 'Evil Fortress Poison 1', 'Evil Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400207, 'Evil Fortress Glove', 'Evil Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400208, 'Evil Fortress Poison 2', 'Evil Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400209, 'Evil Fortress Poison 3', 'Evil Fortress', LocationType.world, ItemType.unknown),
+ LocationDef(400210, 'Evil Fortress Hidden Glove', 'Evil Fortress', LocationType.hidden, ItemType.unknown),
+ LocationDef(None, 'Evil One', 'Evil Fortress', LocationType.boss, ItemType.unknown),
+]
diff --git a/worlds/faxanadu/Options.py b/worlds/faxanadu/Options.py
new file mode 100644
index 000000000000..dbcb5789944f
--- /dev/null
+++ b/worlds/faxanadu/Options.py
@@ -0,0 +1,107 @@
+from Options import PerGameCommonOptions, Toggle, DefaultOnToggle, StartInventoryPool, Choice
+from dataclasses import dataclass
+
+
+class KeepShopRedPotions(Toggle):
+ """
+ Prevents the Shop's Red Potions from being shuffled. Those locations
+ will have purchasable Red Potion as usual for their usual price.
+ """
+ display_name = "Keep Shop Red Potions"
+
+
+class IncludePendant(Toggle):
+ """
+ Pendant is an item that boosts your attack power permanently when picked up.
+ However, due to a programming error in the original game, it has the reverse
+ effect. You start with the Pendant power, and lose it when picking
+ it up. So this item is essentially a trap.
+ There is a setting in the client to reverse the effect back to its original intend.
+ This could be used in conjunction with this option to increase or lower difficulty.
+ """
+ display_name = "Include Pendant"
+
+
+class IncludePoisons(DefaultOnToggle):
+ """
+ Whether or not to include Poison Potions in the pool of items. Including them
+ effectively turn them into traps in multiplayer.
+ """
+ display_name = "Include Poisons"
+
+
+class RequireDragonSlayer(Toggle):
+ """
+ Requires the Dragon Slayer to be available before fighting the final boss is required.
+ Turning this on will turn Progressive Shields into progression items.
+
+ This setting does not force you to use Dragon Slayer to kill the final boss.
+ Instead, it ensures that you will have the Dragon Slayer and be able to equip
+ it before you are expected to beat the final boss.
+ """
+ display_name = "Require Dragon Slayer"
+
+
+class RandomMusic(Toggle):
+ """
+ All levels' music is shuffled. Except the title screen because it's finite.
+ This is an aesthetic option and doesn't affect gameplay.
+ """
+ display_name = "Random Musics"
+
+
+class RandomSound(Toggle):
+ """
+ All sounds are shuffled.
+ This is an aesthetic option and doesn't affect gameplay.
+ """
+ display_name = "Random Sounds"
+
+
+class RandomNPC(Toggle):
+ """
+ NPCs and their portraits are shuffled.
+ This is an aesthetic option and doesn't affect gameplay.
+ """
+ display_name = "Random NPCs"
+
+
+class RandomMonsters(Choice):
+ """
+ Choose how monsters are randomized.
+ "Vanilla": No randomization
+ "Level Shuffle": Monsters are shuffled within a level
+ "Level Random": Monsters are picked randomly, balanced based on the ratio of the current level
+ "World Shuffle": Monsters are shuffled across the entire world
+ "World Random": Monsters are picked randomly, balanced based on the ratio of the entire world
+ "Chaotic": Completely random, except big vs small ratio is kept. Big are mini-bosses.
+ """
+ display_name = "Random Monsters"
+ option_vanilla = 0
+ option_level_shuffle = 1
+ option_level_random = 2
+ option_world_shuffle = 3
+ option_world_random = 4
+ option_chaotic = 5
+ default = 0
+
+
+class RandomRewards(Toggle):
+ """
+ Monsters drops are shuffled.
+ """
+ display_name = "Random Rewards"
+
+
+@dataclass
+class FaxanaduOptions(PerGameCommonOptions):
+ start_inventory_from_pool: StartInventoryPool
+ keep_shop_red_potions: KeepShopRedPotions
+ include_pendant: IncludePendant
+ include_poisons: IncludePoisons
+ require_dragon_slayer: RequireDragonSlayer
+ random_musics: RandomMusic
+ random_sounds: RandomSound
+ random_npcs: RandomNPC
+ random_monsters: RandomMonsters
+ random_rewards: RandomRewards
diff --git a/worlds/faxanadu/Regions.py b/worlds/faxanadu/Regions.py
new file mode 100644
index 000000000000..9db11d8ef114
--- /dev/null
+++ b/worlds/faxanadu/Regions.py
@@ -0,0 +1,66 @@
+from BaseClasses import Region
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from . import FaxanaduWorld
+
+
+def create_region(name, player, multiworld):
+ region = Region(name, player, multiworld)
+ multiworld.regions.append(region)
+ return region
+
+
+def create_regions(faxanadu_world: "FaxanaduWorld"):
+ player = faxanadu_world.player
+ multiworld = faxanadu_world.multiworld
+
+ # Create regions
+ menu = create_region("Menu", player, multiworld)
+ eolis = create_region("Eolis", player, multiworld)
+ path_to_apolune = create_region("Path to Apolune", player, multiworld)
+ apolune = create_region("Apolune", player, multiworld)
+ create_region("Tower of Trunk", player, multiworld)
+ path_to_forepaw = create_region("Path to Forepaw", player, multiworld)
+ forepaw = create_region("Forepaw", player, multiworld)
+ trunk = create_region("Trunk", player, multiworld)
+ create_region("Joker Spring", player, multiworld)
+ create_region("Tower of Fortress", player, multiworld)
+ path_to_mascon = create_region("Path to Mascon", player, multiworld)
+ create_region("Tower of Red Potion", player, multiworld)
+ mascon = create_region("Mascon", player, multiworld)
+ path_to_victim = create_region("Path to Victim", player, multiworld)
+ create_region("Tower of Suffer", player, multiworld)
+ victim = create_region("Victim", player, multiworld)
+ mist = create_region("Mist", player, multiworld)
+ create_region("Useless Tower", player, multiworld)
+ create_region("Tower of Mist", player, multiworld)
+ path_to_conflate = create_region("Path to Conflate", player, multiworld)
+ create_region("Helm Branch", player, multiworld)
+ create_region("Conflate", player, multiworld)
+ branches = create_region("Branches", player, multiworld)
+ path_to_daybreak = create_region("Path to Daybreak", player, multiworld)
+ daybreak = create_region("Daybreak", player, multiworld)
+ dartmoor_castle = create_region("Dartmoor Castle", player, multiworld)
+ create_region("Dartmoor", player, multiworld)
+ create_region("Fraternal Castle", player, multiworld)
+ create_region("Evil Fortress", player, multiworld)
+
+ # Create connections
+ menu.add_exits(["Eolis"])
+ eolis.add_exits(["Path to Apolune"])
+ path_to_apolune.add_exits(["Apolune"])
+ apolune.add_exits(["Tower of Trunk", "Path to Forepaw"])
+ path_to_forepaw.add_exits(["Forepaw"])
+ forepaw.add_exits(["Trunk"])
+ trunk.add_exits(["Joker Spring", "Tower of Fortress", "Path to Mascon"])
+ path_to_mascon.add_exits(["Tower of Red Potion", "Mascon"])
+ mascon.add_exits(["Path to Victim"])
+ path_to_victim.add_exits(["Tower of Suffer", "Victim"])
+ victim.add_exits(["Mist"])
+ mist.add_exits(["Useless Tower", "Tower of Mist", "Path to Conflate"])
+ path_to_conflate.add_exits(["Helm Branch", "Conflate", "Branches"])
+ branches.add_exits(["Path to Daybreak"])
+ path_to_daybreak.add_exits(["Daybreak"])
+ daybreak.add_exits(["Dartmoor Castle"])
+ dartmoor_castle.add_exits(["Dartmoor", "Fraternal Castle", "Evil Fortress"])
diff --git a/worlds/faxanadu/Rules.py b/worlds/faxanadu/Rules.py
new file mode 100644
index 000000000000..a48b442c107a
--- /dev/null
+++ b/worlds/faxanadu/Rules.py
@@ -0,0 +1,79 @@
+from typing import TYPE_CHECKING
+from worlds.generic.Rules import set_rule
+
+if TYPE_CHECKING:
+ from . import FaxanaduWorld
+
+
+def can_buy_in_eolis(state, player):
+ # Sword or Deluge so we can farm for gold.
+ # Ring of Elf so we can get 1500 from the King.
+ return state.has_any(["Progressive Sword", "Deluge", "Ring of Elf"], player)
+
+
+def has_any_magic(state, player):
+ return state.has_any(["Deluge", "Thunder", "Fire", "Death", "Tilte"], player)
+
+
+def set_rules(faxanadu_world: "FaxanaduWorld"):
+ player = faxanadu_world.player
+ multiworld = faxanadu_world.multiworld
+
+ # Region rules
+ set_rule(multiworld.get_entrance("Eolis -> Path to Apolune", player), lambda state:
+ state.has_all(["Key Jack", "Progressive Sword"], player)) # You can't go far with magic only
+ set_rule(multiworld.get_entrance("Apolune -> Tower of Trunk", player), lambda state: state.has("Key Jack", player))
+ set_rule(multiworld.get_entrance("Apolune -> Path to Forepaw", player), lambda state: state.has("Mattock", player))
+ set_rule(multiworld.get_entrance("Trunk -> Joker Spring", player), lambda state: state.has("Key Joker", player))
+ set_rule(multiworld.get_entrance("Trunk -> Tower of Fortress", player), lambda state: state.has("Key Jack", player))
+ set_rule(multiworld.get_entrance("Trunk -> Path to Mascon", player), lambda state:
+ state.has_all(["Key Queen", "Ring of Ruby", "Sky Spring Flow", "Tower of Fortress Spring Flow", "Joker Spring Flow"], player) and
+ state.has("Progressive Sword", player, 2))
+ set_rule(multiworld.get_entrance("Path to Mascon -> Tower of Red Potion", player), lambda state:
+ state.has("Key Queen", player) and
+ state.has("Red Potion", player, 4)) # It's impossible to go through the tower of Red Potion without at least 1-2 potions. Give them 4 for good measure.
+ set_rule(multiworld.get_entrance("Path to Victim -> Tower of Suffer", player), lambda state: state.has("Key Queen", player))
+ set_rule(multiworld.get_entrance("Path to Victim -> Victim", player), lambda state: state.has("Unlock Wingboots", player))
+ set_rule(multiworld.get_entrance("Mist -> Useless Tower", player), lambda state:
+ state.has_all(["Key King", "Unlock Wingboots"], player))
+ set_rule(multiworld.get_entrance("Mist -> Tower of Mist", player), lambda state: state.has("Key King", player))
+ set_rule(multiworld.get_entrance("Mist -> Path to Conflate", player), lambda state: state.has("Key Ace", player))
+ set_rule(multiworld.get_entrance("Path to Conflate -> Helm Branch", player), lambda state: state.has("Key King", player))
+ set_rule(multiworld.get_entrance("Path to Conflate -> Branches", player), lambda state: state.has("Key King", player))
+ set_rule(multiworld.get_entrance("Daybreak -> Dartmoor Castle", player), lambda state: state.has("Ring of Dworf", player))
+ set_rule(multiworld.get_entrance("Dartmoor Castle -> Evil Fortress", player), lambda state: state.has("Demons Ring", player))
+
+ # Location rules
+ set_rule(multiworld.get_location("Eolis Key Jack", player), lambda state: can_buy_in_eolis(state, player))
+ set_rule(multiworld.get_location("Eolis Hand Dagger", player), lambda state: can_buy_in_eolis(state, player))
+ set_rule(multiworld.get_location("Eolis Elixir", player), lambda state: can_buy_in_eolis(state, player))
+ set_rule(multiworld.get_location("Eolis Deluge", player), lambda state: can_buy_in_eolis(state, player))
+ set_rule(multiworld.get_location("Eolis Red Potion", player), lambda state: can_buy_in_eolis(state, player))
+ set_rule(multiworld.get_location("Path to Apolune Magic Shield", player), lambda state: state.has("Key King", player)) # Mid-late cost, make sure we've progressed
+ set_rule(multiworld.get_location("Path to Apolune Death", player), lambda state: state.has("Key Ace", player)) # Mid-late cost, make sure we've progressed
+ set_rule(multiworld.get_location("Tower of Trunk Hidden Mattock", player), lambda state:
+ # This is actually possible if the monster drop into the stairs and kill it with dagger. But it's a "pro move"
+ state.has("Deluge", player, 1) or
+ state.has("Progressive Sword", player, 2))
+ set_rule(multiworld.get_location("Path to Forepaw Glove", player), lambda state:
+ state.has_all(["Deluge", "Unlock Wingboots"], player))
+ set_rule(multiworld.get_location("Trunk Red Potion", player), lambda state: state.has("Unlock Wingboots", player))
+ set_rule(multiworld.get_location("Sky Spring", player), lambda state: state.has("Unlock Wingboots", player))
+ set_rule(multiworld.get_location("Tower of Fortress Spring", player), lambda state: state.has("Spring Elixir", player))
+ set_rule(multiworld.get_location("Tower of Fortress Guru", player), lambda state: state.has("Sky Spring Flow", player))
+ set_rule(multiworld.get_location("Tower of Suffer Hidden Wingboots", player), lambda state:
+ state.has("Deluge", player) or
+ state.has("Progressive Sword", player, 2))
+ set_rule(multiworld.get_location("Misty House", player), lambda state: state.has("Black Onyx", player))
+ set_rule(multiworld.get_location("Misty Doctor Office", player), lambda state: has_any_magic(state, player))
+ set_rule(multiworld.get_location("Conflate Guru", player), lambda state: state.has("Progressive Armor", player, 3))
+ set_rule(multiworld.get_location("Branches Hidden Mattock", player), lambda state: state.has("Unlock Wingboots", player))
+ set_rule(multiworld.get_location("Path to Daybreak Glove", player), lambda state: state.has("Unlock Wingboots", player))
+ set_rule(multiworld.get_location("Dartmoor Castle Hidden Hourglass", player), lambda state: state.has("Unlock Wingboots", player))
+ set_rule(multiworld.get_location("Dartmoor Castle Hidden Red Potion", player), lambda state: has_any_magic(state, player))
+ set_rule(multiworld.get_location("Fraternal Castle Guru", player), lambda state: state.has("Progressive Sword", player, 4))
+ set_rule(multiworld.get_location("Fraternal Castle Shop Hidden Ointment", player), lambda state: has_any_magic(state, player))
+
+ if faxanadu_world.options.require_dragon_slayer.value:
+ set_rule(multiworld.get_location("Evil One", player), lambda state:
+ state.has_all_counts({"Progressive Sword": 4, "Progressive Armor": 3, "Progressive Shield": 4}, player))
diff --git a/worlds/faxanadu/__init__.py b/worlds/faxanadu/__init__.py
new file mode 100644
index 000000000000..c4ae1ccaa198
--- /dev/null
+++ b/worlds/faxanadu/__init__.py
@@ -0,0 +1,190 @@
+from typing import Any, Dict, List
+
+from BaseClasses import Item, Location, Tutorial, ItemClassification, MultiWorld
+from worlds.AutoWorld import WebWorld, World
+from . import Items, Locations, Regions, Rules
+from .Options import FaxanaduOptions
+from worlds.generic.Rules import set_rule
+
+
+DAXANADU_VERSION = "0.3.0"
+
+
+class FaxanaduLocation(Location):
+ game: str = "Faxanadu"
+
+
+class FaxanaduItem(Item):
+ game: str = "Faxanadu"
+
+
+class FaxanaduWeb(WebWorld):
+ tutorials = [Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up the Faxanadu randomizer connected to an Archipelago Multiworld",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["Daivuk"]
+ )]
+ theme = "dirt"
+
+
+class FaxanaduWorld(World):
+ """
+ Faxanadu is an action role-playing platform video game developed by Hudson Soft for the Nintendo Entertainment System
+ """
+ options_dataclass = FaxanaduOptions
+ options: FaxanaduOptions
+ game = "Faxanadu"
+ web = FaxanaduWeb()
+
+ item_name_to_id = {item.name: item.id for item in Items.items if item.id is not None}
+ item_name_to_item = {item.name: item for item in Items.items}
+ location_name_to_id = {loc.name: loc.id for loc in Locations.locations if loc.id is not None}
+
+ def __init__(self, world: MultiWorld, player: int):
+ self.filler_ratios: Dict[str, int] = {}
+
+ super().__init__(world, player)
+
+ def create_regions(self):
+ Regions.create_regions(self)
+
+ # Add locations into regions
+ for region in self.multiworld.get_regions(self.player):
+ for loc in [location for location in Locations.locations if location.region == region.name]:
+ location = FaxanaduLocation(self.player, loc.name, loc.id, region)
+
+ # In Faxanadu, Poison hurts you when picked up. It makes no sense to sell them in shops
+ if loc.type == Locations.LocationType.shop:
+ location.item_rule = lambda item, player=self.player: not (player == item.player and item.name == "Poison")
+
+ region.locations.append(location)
+
+ def set_rules(self):
+ Rules.set_rules(self)
+ self.multiworld.completion_condition[self.player] = lambda state: state.has("Killed Evil One", self.player)
+
+ def create_item(self, name: str) -> FaxanaduItem:
+ item: Items.ItemDef = self.item_name_to_item[name]
+ return FaxanaduItem(name, item.classification, item.id, self.player)
+
+ # Returns how many red potions were prefilled into shops
+ def prefill_shop_red_potions(self) -> int:
+ red_potion_in_shop_count = 0
+ if self.options.keep_shop_red_potions:
+ red_potion_item = self.item_name_to_item["Red Potion"]
+ red_potion_shop_locations = [
+ loc
+ for loc in Locations.locations
+ if loc.type == Locations.LocationType.shop and loc.original_item == Locations.ItemType.red_potion
+ ]
+ for loc in red_potion_shop_locations:
+ location = self.get_location(loc.name)
+ location.place_locked_item(FaxanaduItem(red_potion_item.name, red_potion_item.classification, red_potion_item.id, self.player))
+ red_potion_in_shop_count += 1
+ return red_potion_in_shop_count
+
+ def put_wingboot_in_shop(self, shops, region_name):
+ item = self.item_name_to_item["Wingboots"]
+ shop = shops.pop(region_name)
+ slot = self.random.randint(0, len(shop) - 1)
+ loc = shop[slot]
+ location = self.get_location(loc.name)
+ location.place_locked_item(FaxanaduItem(item.name, item.classification, item.id, self.player))
+
+ # Put a rule right away that we need to have to unlocked.
+ set_rule(location, lambda state: state.has("Unlock Wingboots", self.player))
+
+ # Returns how many wingboots were prefilled into shops
+ def prefill_shop_wingboots(self) -> int:
+ # Collect shops
+ shops: Dict[str, List[Locations.LocationDef]] = {}
+ for loc in Locations.locations:
+ if loc.type == Locations.LocationType.shop:
+ if self.options.keep_shop_red_potions and loc.original_item == Locations.ItemType.red_potion:
+ continue # Don't override our red potions
+ shops.setdefault(loc.region, []).append(loc)
+
+ shop_count = len(shops)
+ wingboots_count = round(shop_count / 2.5) # On 10 shops, we should have about 4 shops with wingboots
+
+ # At least one should be in the first 4 shops. Because we require wingboots to progress past that point.
+ must_have_regions = [region for i, region in enumerate(shops) if i < 4]
+ self.put_wingboot_in_shop(shops, self.random.choice(must_have_regions))
+
+ # Fill in the rest randomly in remaining shops
+ for i in range(wingboots_count - 1): # -1 because we added one already
+ region = self.random.choice(list(shops.keys()))
+ self.put_wingboot_in_shop(shops, region)
+
+ return wingboots_count
+
+ def create_items(self) -> None:
+ itempool: List[FaxanaduItem] = []
+
+ # Prefill red potions in shops if option is set
+ red_potion_in_shop_count = self.prefill_shop_red_potions()
+
+ # Prefill wingboots in shops
+ wingboots_in_shop_count = self.prefill_shop_wingboots()
+
+ # Create the item pool, excluding fillers.
+ prefilled_count = red_potion_in_shop_count + wingboots_in_shop_count
+ for item in Items.items:
+ # Ignore pendant if turned off
+ if item.name == "Pendant" and not self.options.include_pendant:
+ continue
+
+ # ignore fillers for now, we will fill them later
+ if item.classification in [ItemClassification.filler, ItemClassification.trap] and \
+ item.progression_count == 0:
+ continue
+
+ prefill_loc = None
+ if item.prefill_location:
+ prefill_loc = self.get_location(item.prefill_location)
+
+ # if require dragon slayer is turned on, we need progressive shields to be progression
+ item_classification = item.classification
+ if self.options.require_dragon_slayer and item.name == "Progressive Shield":
+ item_classification = ItemClassification.progression
+
+ if prefill_loc:
+ prefill_loc.place_locked_item(FaxanaduItem(item.name, item_classification, item.id, self.player))
+ prefilled_count += 1
+ else:
+ for i in range(item.count - item.progression_count):
+ itempool.append(FaxanaduItem(item.name, item_classification, item.id, self.player))
+ for i in range(item.progression_count):
+ itempool.append(FaxanaduItem(item.name, ItemClassification.progression, item.id, self.player))
+
+ # Set up filler ratios
+ self.filler_ratios = {
+ item.name: item.count
+ for item in Items.items
+ if item.classification in [ItemClassification.filler, ItemClassification.trap]
+ }
+
+ # If red potions are locked in shops, remove the count from the ratio.
+ self.filler_ratios["Red Potion"] -= red_potion_in_shop_count
+
+ # Remove poisons if not desired
+ if not self.options.include_poisons:
+ self.filler_ratios["Poison"] = 0
+
+ # Randomly add fillers to the pool with ratios based on og game occurrence counts.
+ filler_count = len(Locations.locations) - len(itempool) - prefilled_count
+ for i in range(filler_count):
+ itempool.append(self.create_item(self.get_filler_item_name()))
+
+ self.multiworld.itempool += itempool
+
+ def get_filler_item_name(self) -> str:
+ return self.random.choices(list(self.filler_ratios.keys()), weights=list(self.filler_ratios.values()))[0]
+
+ def fill_slot_data(self) -> Dict[str, Any]:
+ slot_data = self.options.as_dict("keep_shop_red_potions", "random_musics", "random_sounds", "random_npcs", "random_monsters", "random_rewards")
+ slot_data["daxanadu_version"] = DAXANADU_VERSION
+ return slot_data
diff --git a/worlds/faxanadu/docs/en_Faxanadu.md b/worlds/faxanadu/docs/en_Faxanadu.md
new file mode 100644
index 000000000000..7f5c4ab293ce
--- /dev/null
+++ b/worlds/faxanadu/docs/en_Faxanadu.md
@@ -0,0 +1,27 @@
+# Faxanadu
+
+## Where is the settings page?
+
+The [player options page](../player-options) contains the options needed to configure your game session.
+
+## What does randomization do to this game?
+
+All game items collected in the map, shops, and boss drops are randomized.
+
+Keys are unique. Once you get the Jack Key, you can open all Jack doors; the key stays in your inventory.
+
+Wingboots are randomized across shops only. They are LOCKED and cannot be bought until you get the item that unlocks them.
+
+Normal Elixirs don't revive the tower spring. A new item, Spring Elixir, is necessary. This new item is unique.
+
+## What is the goal?
+
+The goal is to kill the Evil One.
+
+## What is a "check" in The Faxanadu?
+
+Shop items, item locations in the world, boss drops, and secret items.
+
+## What "items" can you unlock in Faxanadu?
+
+Keys, Armors, Weapons, Potions, Shields, Magics, Poisons, Gloves, etc.
diff --git a/worlds/faxanadu/docs/setup_en.md b/worlds/faxanadu/docs/setup_en.md
new file mode 100644
index 000000000000..4ff714c61393
--- /dev/null
+++ b/worlds/faxanadu/docs/setup_en.md
@@ -0,0 +1,32 @@
+# Faxanadu Randomizer Setup
+
+## Required Software
+
+- [Daxanadu](https://github.com/Daivuk/Daxanadu/releases/)
+- Faxanadu ROM, English version
+
+## Optional Software
+
+- [ArchipelagoTextClient](https://github.com/ArchipelagoMW/Archipelago/releases)
+
+## Installing Daxanadu
+1. Download [Daxanadu.zip](https://github.com/Daivuk/Daxanadu/releases/) and extract it.
+2. Copy your rom `Faxanadu (U).nes` into the newly extracted folder.
+
+## Joining a MultiWorld Game
+
+1. Launch Daxanadu.exe
+2. From the Main menu, go to the `ARCHIPELAGO` menu. Enter the server's address, slot name, and password. Then select `PLAY`.
+3. Enjoy!
+
+To continue a game, follow the same connection steps.
+Connecting with a different seed won't erase your progress in other seeds.
+
+## Archipelago Text Client
+
+We recommend having Archipelago's Text Client open on the side to keep track of what items you receive and send.
+Daxanadu doesn't display messages. You'll only get popups when picking them up.
+
+## Auto-Tracking
+
+Daxanadu has an integrated tracker that can be toggled in the options.
diff --git a/worlds/ffmq/Client.py b/worlds/ffmq/Client.py
index 93688a6116f6..401c240a46ba 100644
--- a/worlds/ffmq/Client.py
+++ b/worlds/ffmq/Client.py
@@ -47,6 +47,17 @@ def get_flag(data, flag):
bit = int(0x80 / (2 ** (flag % 8)))
return (data[byte] & bit) > 0
+def validate_read_state(data1, data2):
+ validation_array = bytes([0x01, 0x46, 0x46, 0x4D, 0x51, 0x52])
+
+ if data1 is None or data2 is None:
+ return False
+ for i in range(6):
+ if data1[i] != validation_array[i] or data2[i] != validation_array[i]:
+ return False;
+ return True
+
+
class FFMQClient(SNIClient):
game = "Final Fantasy Mystic Quest"
@@ -67,11 +78,11 @@ async def validate_rom(self, ctx):
async def game_watcher(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
- check_1 = await snes_read(ctx, 0xF53749, 1)
+ check_1 = await snes_read(ctx, 0xF53749, 6)
received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1])
data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START)
- check_2 = await snes_read(ctx, 0xF53749, 1)
- if check_1 != b'\x01' or check_2 != b'\x01':
+ check_2 = await snes_read(ctx, 0xF53749, 6)
+ if not validate_read_state(check_1, check_2):
return
def get_range(data_range):
diff --git a/worlds/generic/docs/mac_en.md b/worlds/generic/docs/mac_en.md
index 2904781862da..76b1ee4a3827 100644
--- a/worlds/generic/docs/mac_en.md
+++ b/worlds/generic/docs/mac_en.md
@@ -2,8 +2,8 @@
Archipelago does not have a compiled release on macOS. However, it is possible to run from source code on macOS. This guide expects you to have some experience with running software from the terminal.
## Prerequisite Software
Here is a list of software to install and source code to download.
-1. Python 3.9 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/).
- **Python 3.11 is not supported yet.**
+1. Python 3.10 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/).
+ **Python 3.13 is not supported yet.**
2. Xcode from the [macOS App Store](https://apps.apple.com/us/app/xcode/id497799835).
3. The source code from the [Archipelago releases page](https://github.com/ArchipelagoMW/Archipelago/releases).
4. The asset with darwin in the name from the [SNI Github releases page](https://github.com/alttpo/sni/releases).
diff --git a/worlds/heretic/Options.py b/worlds/heretic/Options.py
index 75e2257a7336..14f4198a55f0 100644
--- a/worlds/heretic/Options.py
+++ b/worlds/heretic/Options.py
@@ -104,7 +104,7 @@ class StartWithMapScrolls(Toggle):
class ResetLevelOnDeath(DefaultOnToggle):
"""When dying, levels are reset and monsters respawned. But inventory and checks are kept.
Turning this setting off is considered easy mode. Good for new players that don't know the levels well."""
- display_message="Reset level on death"
+ display_name = "Reset Level on Death"
class CheckSanity(Toggle):
diff --git a/worlds/hk/Extractor.py b/worlds/hk/Extractor.py
index 61fabc4da0d9..866608489ec2 100644
--- a/worlds/hk/Extractor.py
+++ b/worlds/hk/Extractor.py
@@ -9,11 +9,7 @@
import jinja2
-try:
- from ast import unparse
-except ImportError:
- # Py 3.8 and earlier compatibility module
- from astunparse import unparse
+from ast import unparse
from Utils import get_text_between
diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py
index fc8eae1c0aa3..e17abbb7ae47 100644
--- a/worlds/hk/Options.py
+++ b/worlds/hk/Options.py
@@ -300,7 +300,7 @@ class PlandoCharmCosts(OptionDict):
display_name = "Charm Notch Cost Plando"
valid_keys = frozenset(charm_names)
schema = Schema({
- Optional(name): And(int, lambda n: 6 >= n >= 0) for name in charm_names
+ Optional(name): And(int, lambda n: 6 >= n >= 0, error="Charm costs must be integers in the range 0-6.") for name in charm_names
})
def get_costs(self, charm_costs: typing.List[int]) -> typing.List[int]:
diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py
index 486aa164cd5d..aede8e59cca5 100644
--- a/worlds/hk/__init__.py
+++ b/worlds/hk/__init__.py
@@ -231,7 +231,7 @@ def create_regions(self):
all_event_names.update(set(godhome_event_names))
# Link regions
- for event_name in all_event_names:
+ for event_name in sorted(all_event_names):
#if event_name in wp_exclusions:
# continue
loc = HKLocation(self.player, event_name, None, menu_region)
diff --git a/worlds/hk/requirements.txt b/worlds/hk/requirements.txt
deleted file mode 100644
index 1b410ffb2aed..000000000000
--- a/worlds/hk/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-astunparse>=1.6.3; python_version <= '3.8'
\ No newline at end of file
diff --git a/worlds/kh1/Rules.py b/worlds/kh1/Rules.py
index e1f72f5b3e54..130238e5048e 100644
--- a/worlds/kh1/Rules.py
+++ b/worlds/kh1/Rules.py
@@ -235,6 +235,11 @@ def set_rules(kh1world):
lambda state: (
state.has("Progressive Glide", player)
or
+ (
+ state.has("High Jump", player, 2)
+ and state.has("Footprints", player)
+ )
+ or
(
options.advanced_logic
and state.has_all({
@@ -246,6 +251,11 @@ def set_rules(kh1world):
lambda state: (
state.has("Progressive Glide", player)
or
+ (
+ state.has("High Jump", player, 2)
+ and state.has("Footprints", player)
+ )
+ or
(
options.advanced_logic
and state.has_all({
@@ -258,7 +268,6 @@ def set_rules(kh1world):
state.has("Footprints", player)
or (options.advanced_logic and state.has("Progressive Glide", player))
- or state.has("High Jump", player, 2)
))
add_rule(kh1world.get_location("Wonderland Tea Party Garden Across From Bizarre Room Entrance Chest"),
lambda state: (
@@ -376,7 +385,7 @@ def set_rules(kh1world):
lambda state: state.has("White Trinity", player))
add_rule(kh1world.get_location("Monstro Chamber 6 Other Platform Chest"),
lambda state: (
- state.has("High Jump", player)
+ state.has_all(("High Jump", "Progressive Glide"), player)
or (options.advanced_logic and state.has("Combo Master", player))
))
add_rule(kh1world.get_location("Monstro Chamber 6 Platform Near Chamber 5 Entrance Chest"),
@@ -386,7 +395,7 @@ def set_rules(kh1world):
))
add_rule(kh1world.get_location("Monstro Chamber 6 Raised Area Near Chamber 1 Entrance Chest"),
lambda state: (
- state.has("High Jump", player)
+ state.has_all(("High Jump", "Progressive Glide"), player)
or (options.advanced_logic and state.has("Combo Master", player))
))
add_rule(kh1world.get_location("Halloween Town Moonlight Hill White Trinity Chest"),
@@ -595,6 +604,7 @@ def set_rules(kh1world):
lambda state: (
state.has("Green Trinity", player)
and has_all_magic_lvx(state, player, 2)
+ and has_defensive_tools(state, player)
))
add_rule(kh1world.get_location("Neverland Hold Flight 2nd Chest"),
lambda state: (
@@ -710,8 +720,7 @@ def set_rules(kh1world):
lambda state: state.has("White Trinity", player))
add_rule(kh1world.get_location("End of the World Giant Crevasse 5th Chest"),
lambda state: (
- state.has("High Jump", player)
- or state.has("Progressive Glide", player)
+ state.has("Progressive Glide", player)
))
add_rule(kh1world.get_location("End of the World Giant Crevasse 1st Chest"),
lambda state: (
@@ -1441,10 +1450,11 @@ def set_rules(kh1world):
has_emblems(state, player, options.keyblades_unlock_chests)
and has_x_worlds(state, player, 7, options.keyblades_unlock_chests)
and has_defensive_tools(state, player)
+ and state.has("Progressive Blizzard", player, 3)
))
add_rule(kh1world.get_location("Agrabah Defeat Kurt Zisa Zantetsuken Event"),
lambda state: (
- has_emblems(state, player, options.keyblades_unlock_chests) and has_x_worlds(state, player, 7, options.keyblades_unlock_chests) and has_defensive_tools(state, player)
+ has_emblems(state, player, options.keyblades_unlock_chests) and has_x_worlds(state, player, 7, options.keyblades_unlock_chests) and has_defensive_tools(state, player) and state.has("Progressive Blizzard", player, 3)
))
if options.super_bosses or options.goal.current_key == "sephiroth":
add_rule(kh1world.get_location("Olympus Coliseum Defeat Sephiroth Ansem's Report 12"),
diff --git a/worlds/landstalker/Locations.py b/worlds/landstalker/Locations.py
index b0148269eab3..0fe63526c63b 100644
--- a/worlds/landstalker/Locations.py
+++ b/worlds/landstalker/Locations.py
@@ -34,7 +34,7 @@ def create_locations(player: int, regions_table: Dict[str, LandstalkerRegion], n
for data in WORLD_PATHS_JSON:
if "requiredNodes" in data:
regions_with_entrance_checks.extend([region_id for region_id in data["requiredNodes"]])
- regions_with_entrance_checks = list(set(regions_with_entrance_checks))
+ regions_with_entrance_checks = sorted(set(regions_with_entrance_checks))
for region_id in regions_with_entrance_checks:
region = regions_table[region_id]
location = LandstalkerLocation(player, 'event_visited_' + region_id, None, region, "event")
diff --git a/worlds/lufia2ac/__init__.py b/worlds/lufia2ac/__init__.py
index 6433452cefea..96de24a4b6a0 100644
--- a/worlds/lufia2ac/__init__.py
+++ b/worlds/lufia2ac/__init__.py
@@ -118,7 +118,7 @@ def create_regions(self) -> None:
L2ACItem("Progressive chest access", ItemClassification.progression, None, self.player))
chest_access.show_in_spoiler = False
ancient_dungeon.locations.append(chest_access)
- for iris in self.item_name_groups["Iris treasures"]:
+ for iris in sorted(self.item_name_groups["Iris treasures"]):
treasure_name: str = f"Iris treasure {self.item_name_to_id[iris] - self.item_name_to_id['Iris sword'] + 1}"
iris_treasure: Location = \
L2ACLocation(self.player, treasure_name, self.location_name_to_id[treasure_name], ancient_dungeon)
diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py
index 9a38953ffbdf..59e724d3fb7f 100644
--- a/worlds/messenger/__init__.py
+++ b/worlds/messenger/__init__.py
@@ -1,5 +1,5 @@
import logging
-from typing import Any, ClassVar, Dict, List, Optional, Set, TextIO
+from typing import Any, ClassVar, TextIO
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, MultiWorld, Tutorial
from Options import Accessibility
@@ -120,16 +120,16 @@ class MessengerWorld(World):
required_seals: int = 0
created_seals: int = 0
total_shards: int = 0
- shop_prices: Dict[str, int]
- figurine_prices: Dict[str, int]
- _filler_items: List[str]
- starting_portals: List[str]
- plando_portals: List[str]
- spoiler_portal_mapping: Dict[str, str]
- portal_mapping: List[int]
- transitions: List[Entrance]
+ shop_prices: dict[str, int]
+ figurine_prices: dict[str, int]
+ _filler_items: list[str]
+ starting_portals: list[str]
+ plando_portals: list[str]
+ spoiler_portal_mapping: dict[str, str]
+ portal_mapping: list[int]
+ transitions: list[Entrance]
reachable_locs: int = 0
- filler: Dict[str, int]
+ filler: dict[str, int]
def generate_early(self) -> None:
if self.options.goal == Goal.option_power_seal_hunt:
@@ -178,7 +178,7 @@ def create_regions(self) -> None:
for reg_name in sub_region]
for region in complex_regions:
- region_name = region.name.replace(f"{region.parent} - ", "")
+ region_name = region.name.removeprefix(f"{region.parent} - ")
connection_data = CONNECTIONS[region.parent][region_name]
for exit_region in connection_data:
region.connect(self.multiworld.get_region(exit_region, self.player))
@@ -191,7 +191,7 @@ def create_items(self) -> None:
# create items that are always in the item pool
main_movement_items = ["Rope Dart", "Wingsuit"]
precollected_names = [item.name for item in self.multiworld.precollected_items[self.player]]
- itempool: List[MessengerItem] = [
+ itempool: list[MessengerItem] = [
self.create_item(item)
for item in self.item_name_to_id
if item not in {
@@ -290,7 +290,7 @@ def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
for portal, output in portal_info:
spoiler.set_entrance(f"{portal} Portal", output, "I can write anything I want here lmao", self.player)
- def fill_slot_data(self) -> Dict[str, Any]:
+ def fill_slot_data(self) -> dict[str, Any]:
slot_data = {
"shop": {SHOP_ITEMS[item].internal_name: price for item, price in self.shop_prices.items()},
"figures": {FIGURINES[item].internal_name: price for item, price in self.figurine_prices.items()},
@@ -316,7 +316,7 @@ def get_filler_item_name(self) -> str:
return self._filler_items.pop(0)
def create_item(self, name: str) -> MessengerItem:
- item_id: Optional[int] = self.item_name_to_id.get(name, None)
+ item_id: int | None = self.item_name_to_id.get(name, None)
return MessengerItem(
name,
ItemClassification.progression if item_id is None else self.get_item_classification(name),
@@ -351,7 +351,7 @@ def get_item_classification(self, name: str) -> ItemClassification:
return ItemClassification.filler
@classmethod
- def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World:
+ def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: set[int]) -> World:
group = super().create_group(multiworld, new_player_id, players)
assert isinstance(group, MessengerWorld)
diff --git a/worlds/messenger/client_setup.py b/worlds/messenger/client_setup.py
index 77a0f634326c..6b98a1b44013 100644
--- a/worlds/messenger/client_setup.py
+++ b/worlds/messenger/client_setup.py
@@ -5,7 +5,7 @@
import subprocess
import urllib.request
from shutil import which
-from typing import Any, Optional
+from typing import Any
from zipfile import ZipFile
from Utils import open_file
@@ -17,7 +17,7 @@
MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest"
-def ask_yes_no_cancel(title: str, text: str) -> Optional[bool]:
+def ask_yes_no_cancel(title: str, text: str) -> bool | None:
"""
Wrapper for tkinter.messagebox.askyesnocancel, that creates a popup dialog box with yes, no, and cancel buttons.
@@ -33,7 +33,6 @@ def ask_yes_no_cancel(title: str, text: str) -> Optional[bool]:
return ret
-
def launch_game(*args) -> None:
"""Check the game installation, then launch it"""
def courier_installed() -> bool:
diff --git a/worlds/messenger/connections.py b/worlds/messenger/connections.py
index 69dd7aa7f286..79912a5688c2 100644
--- a/worlds/messenger/connections.py
+++ b/worlds/messenger/connections.py
@@ -1,6 +1,4 @@
-from typing import Dict, List
-
-CONNECTIONS: Dict[str, Dict[str, List[str]]] = {
+CONNECTIONS: dict[str, dict[str, list[str]]] = {
"Ninja Village": {
"Right": [
"Autumn Hills - Left",
@@ -640,7 +638,7 @@
},
}
-RANDOMIZED_CONNECTIONS: Dict[str, str] = {
+RANDOMIZED_CONNECTIONS: dict[str, str] = {
"Ninja Village - Right": "Autumn Hills - Left",
"Autumn Hills - Left": "Ninja Village - Right",
"Autumn Hills - Right": "Forlorn Temple - Left",
@@ -680,7 +678,7 @@
"Sunken Shrine - Left": "Howling Grotto - Bottom",
}
-TRANSITIONS: List[str] = [
+TRANSITIONS: list[str] = [
"Ninja Village - Right",
"Autumn Hills - Left",
"Autumn Hills - Right",
diff --git a/worlds/messenger/constants.py b/worlds/messenger/constants.py
index ea15c71068db..47b5a1a85cff 100644
--- a/worlds/messenger/constants.py
+++ b/worlds/messenger/constants.py
@@ -2,7 +2,7 @@
# items
# listing individual groups first for easy lookup
-NOTES = [
+NOTES: list[str] = [
"Key of Hope",
"Key of Chaos",
"Key of Courage",
@@ -11,7 +11,7 @@
"Key of Symbiosis",
]
-PROG_ITEMS = [
+PROG_ITEMS: list[str] = [
"Wingsuit",
"Rope Dart",
"Lightfoot Tabi",
@@ -28,18 +28,18 @@
"Seashell",
]
-PHOBEKINS = [
+PHOBEKINS: list[str] = [
"Necro",
"Pyro",
"Claustro",
"Acro",
]
-USEFUL_ITEMS = [
+USEFUL_ITEMS: list[str] = [
"Windmill Shuriken",
]
-FILLER = {
+FILLER: dict[str, int] = {
"Time Shard": 5,
"Time Shard (10)": 10,
"Time Shard (50)": 20,
@@ -48,13 +48,13 @@
"Time Shard (500)": 5,
}
-TRAPS = {
+TRAPS: dict[str, int] = {
"Teleport Trap": 5,
"Prophecy Trap": 10,
}
# item_name_to_id needs to be deterministic and match upstream
-ALL_ITEMS = [
+ALL_ITEMS: list[str] = [
*NOTES,
"Windmill Shuriken",
"Wingsuit",
@@ -83,7 +83,7 @@
# locations
# the names of these don't actually matter, but using the upstream's names for now
# order must be exactly the same as upstream
-ALWAYS_LOCATIONS = [
+ALWAYS_LOCATIONS: list[str] = [
# notes
"Sunken Shrine - Key of Love",
"Corrupted Future - Key of Courage",
@@ -160,7 +160,7 @@
"Elemental Skylands Seal - Fire",
]
-BOSS_LOCATIONS = [
+BOSS_LOCATIONS: list[str] = [
"Autumn Hills - Leaf Golem",
"Catacombs - Ruxxtin",
"Howling Grotto - Emerald Golem",
diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py
index 59e694cd3963..8b61a9435422 100644
--- a/worlds/messenger/options.py
+++ b/worlds/messenger/options.py
@@ -1,5 +1,4 @@
from dataclasses import dataclass
-from typing import Dict
from schema import And, Optional, Or, Schema
@@ -167,7 +166,7 @@ class ShopPrices(Range):
default = 100
-def planned_price(location: str) -> Dict[Optional, Or]:
+def planned_price(location: str) -> dict[Optional, Or]:
return {
Optional(location): Or(
And(int, lambda n: n >= 0),
diff --git a/worlds/messenger/portals.py b/worlds/messenger/portals.py
index 17152a1a1538..896fefa686f1 100644
--- a/worlds/messenger/portals.py
+++ b/worlds/messenger/portals.py
@@ -1,5 +1,5 @@
from copy import deepcopy
-from typing import List, TYPE_CHECKING
+from typing import TYPE_CHECKING
from BaseClasses import CollectionState, PlandoOptions
from Options import PlandoConnection
@@ -8,7 +8,7 @@
from . import MessengerWorld
-PORTALS = [
+PORTALS: list[str] = [
"Autumn Hills",
"Riviere Turquoise",
"Howling Grotto",
@@ -18,7 +18,7 @@
]
-SHOP_POINTS = {
+SHOP_POINTS: dict[str, list[str]] = {
"Autumn Hills": [
"Climbing Claws",
"Hope Path",
@@ -113,7 +113,7 @@
}
-CHECKPOINTS = {
+CHECKPOINTS: dict[str, list[str]] = {
"Autumn Hills": [
"Hope Latch",
"Key of Hope",
@@ -186,7 +186,7 @@
}
-REGION_ORDER = [
+REGION_ORDER: list[str] = [
"Autumn Hills",
"Forlorn Temple",
"Catacombs",
@@ -228,7 +228,7 @@ def create_mapping(in_portal: str, warp: str) -> str:
return parent
- def handle_planned_portals(plando_connections: List[PlandoConnection]) -> None:
+ def handle_planned_portals(plando_connections: list[PlandoConnection]) -> None:
"""checks the provided plando connections for portals and connects them"""
nonlocal available_portals
diff --git a/worlds/messenger/regions.py b/worlds/messenger/regions.py
index 153f8510f1bd..d53b84fe3401 100644
--- a/worlds/messenger/regions.py
+++ b/worlds/messenger/regions.py
@@ -1,7 +1,4 @@
-from typing import Dict, List
-
-
-LOCATIONS: Dict[str, List[str]] = {
+LOCATIONS: dict[str, list[str]] = {
"Ninja Village - Nest": [
"Ninja Village - Candle",
"Ninja Village - Astral Seed",
@@ -201,7 +198,7 @@
}
-SUB_REGIONS: Dict[str, List[str]] = {
+SUB_REGIONS: dict[str, list[str]] = {
"Ninja Village": [
"Right",
],
@@ -385,7 +382,7 @@
# order is slightly funky here for back compat
-MEGA_SHARDS: Dict[str, List[str]] = {
+MEGA_SHARDS: dict[str, list[str]] = {
"Autumn Hills - Lakeside Checkpoint": ["Autumn Hills Mega Shard"],
"Forlorn Temple - Outside Shop": ["Hidden Entrance Mega Shard"],
"Catacombs - Top Left": ["Catacombs Mega Shard"],
@@ -414,7 +411,7 @@
}
-REGION_CONNECTIONS: Dict[str, Dict[str, str]] = {
+REGION_CONNECTIONS: dict[str, dict[str, str]] = {
"Menu": {"Tower HQ": "Start Game"},
"Tower HQ": {
"Autumn Hills - Portal": "ToTHQ Autumn Hills Portal",
@@ -436,7 +433,7 @@
# regions that don't have sub-regions
-LEVELS: List[str] = [
+LEVELS: list[str] = [
"Menu",
"Tower HQ",
"The Shop",
diff --git a/worlds/messenger/rules.py b/worlds/messenger/rules.py
index c354ad70aba6..f09025c7edce 100644
--- a/worlds/messenger/rules.py
+++ b/worlds/messenger/rules.py
@@ -1,4 +1,4 @@
-from typing import Dict, TYPE_CHECKING
+from typing import TYPE_CHECKING
from BaseClasses import CollectionState
from worlds.generic.Rules import CollectionRule, add_rule, allow_self_locking_items
@@ -12,9 +12,9 @@
class MessengerRules:
player: int
world: "MessengerWorld"
- connection_rules: Dict[str, CollectionRule]
- region_rules: Dict[str, CollectionRule]
- location_rules: Dict[str, CollectionRule]
+ connection_rules: dict[str, CollectionRule]
+ region_rules: dict[str, CollectionRule]
+ location_rules: dict[str, CollectionRule]
maximum_price: int
required_seals: int
diff --git a/worlds/messenger/shop.py b/worlds/messenger/shop.py
index 3c8c7bf6f21e..6ab72f9765f3 100644
--- a/worlds/messenger/shop.py
+++ b/worlds/messenger/shop.py
@@ -1,11 +1,11 @@
-from typing import Dict, List, NamedTuple, Optional, Set, TYPE_CHECKING, Tuple, Union
+from typing import NamedTuple, TYPE_CHECKING
if TYPE_CHECKING:
from . import MessengerWorld
else:
MessengerWorld = object
-PROG_SHOP_ITEMS: List[str] = [
+PROG_SHOP_ITEMS: list[str] = [
"Path of Resilience",
"Meditation",
"Strike of the Ninja",
@@ -14,7 +14,7 @@
"Aerobatics Warrior",
]
-USEFUL_SHOP_ITEMS: List[str] = [
+USEFUL_SHOP_ITEMS: list[str] = [
"Karuta Plates",
"Serendipitous Bodies",
"Kusari Jacket",
@@ -29,10 +29,10 @@ class ShopData(NamedTuple):
internal_name: str
min_price: int
max_price: int
- prerequisite: Optional[Union[str, Set[str]]] = None
+ prerequisite: str | set[str] | None = None
-SHOP_ITEMS: Dict[str, ShopData] = {
+SHOP_ITEMS: dict[str, ShopData] = {
"Karuta Plates": ShopData("HP_UPGRADE_1", 20, 200),
"Serendipitous Bodies": ShopData("ENEMY_DROP_HP", 20, 300, "The Shop - Karuta Plates"),
"Path of Resilience": ShopData("DAMAGE_REDUCTION", 100, 500, "The Shop - Serendipitous Bodies"),
@@ -56,7 +56,7 @@ class ShopData(NamedTuple):
"Focused Power Sense": ShopData("POWER_SEAL_WORLD_MAP", 300, 600, "The Shop - Power Sense"),
}
-FIGURINES: Dict[str, ShopData] = {
+FIGURINES: dict[str, ShopData] = {
"Green Kappa Figurine": ShopData("GREEN_KAPPA", 100, 500),
"Blue Kappa Figurine": ShopData("BLUE_KAPPA", 100, 500),
"Ountarde Figurine": ShopData("OUNTARDE", 100, 500),
@@ -73,12 +73,12 @@ class ShopData(NamedTuple):
}
-def shuffle_shop_prices(world: MessengerWorld) -> Tuple[Dict[str, int], Dict[str, int]]:
+def shuffle_shop_prices(world: MessengerWorld) -> tuple[dict[str, int], dict[str, int]]:
shop_price_mod = world.options.shop_price.value
shop_price_planned = world.options.shop_price_plan
- shop_prices: Dict[str, int] = {}
- figurine_prices: Dict[str, int] = {}
+ shop_prices: dict[str, int] = {}
+ figurine_prices: dict[str, int] = {}
for item, price in shop_price_planned.value.items():
if not isinstance(price, int):
price = world.random.choices(list(price.keys()), weights=list(price.values()))[0]
diff --git a/worlds/messenger/subclasses.py b/worlds/messenger/subclasses.py
index b60aeb179feb..29e3ea8953ec 100644
--- a/worlds/messenger/subclasses.py
+++ b/worlds/messenger/subclasses.py
@@ -1,5 +1,5 @@
from functools import cached_property
-from typing import Optional, TYPE_CHECKING
+from typing import TYPE_CHECKING
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Region
from .regions import LOCATIONS, MEGA_SHARDS
@@ -10,14 +10,14 @@
class MessengerEntrance(Entrance):
- world: Optional["MessengerWorld"] = None
+ world: "MessengerWorld | None" = None
class MessengerRegion(Region):
parent: str
entrance_type = MessengerEntrance
- def __init__(self, name: str, world: "MessengerWorld", parent: Optional[str] = None) -> None:
+ def __init__(self, name: str, world: "MessengerWorld", parent: str | None = None) -> None:
super().__init__(name, world.player, world.multiworld)
self.parent = parent
locations = []
@@ -48,7 +48,7 @@ def __init__(self, name: str, world: "MessengerWorld", parent: Optional[str] = N
class MessengerLocation(Location):
game = "The Messenger"
- def __init__(self, player: int, name: str, loc_id: Optional[int], parent: MessengerRegion) -> None:
+ def __init__(self, player: int, name: str, loc_id: int | None, parent: MessengerRegion) -> None:
super().__init__(player, name, loc_id, parent)
if loc_id is None:
if name == "Rescue Phantom":
@@ -59,7 +59,7 @@ def __init__(self, player: int, name: str, loc_id: Optional[int], parent: Messen
class MessengerShopLocation(MessengerLocation):
@cached_property
def cost(self) -> int:
- name = self.name.replace("The Shop - ", "") # TODO use `remove_prefix` when 3.8 finally gets dropped
+ name = self.name.removeprefix("The Shop - ")
world = self.parent_region.multiworld.worlds[self.player]
shop_data = SHOP_ITEMS[name]
if shop_data.prerequisite:
diff --git a/worlds/messenger/test/test_shop.py b/worlds/messenger/test/test_shop.py
index ce6fd19e33c8..21a0c352bff4 100644
--- a/worlds/messenger/test/test_shop.py
+++ b/worlds/messenger/test/test_shop.py
@@ -77,7 +77,7 @@ def test_costs(self) -> None:
loc = f"The Shop - {loc}"
self.assertLessEqual(price, self.multiworld.get_location(loc, self.player).cost)
- self.assertTrue(loc.replace("The Shop - ", "") in SHOP_ITEMS)
+ self.assertTrue(loc.removeprefix("The Shop - ") in SHOP_ITEMS)
self.assertEqual(len(prices), len(SHOP_ITEMS))
figures = self.world.figurine_prices
diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py
index ab3a4819fc48..be2eec2f87b8 100644
--- a/worlds/musedash/__init__.py
+++ b/worlds/musedash/__init__.py
@@ -183,7 +183,7 @@ def create_item(self, name: str) -> Item:
if album:
return MuseDashSongItem(name, self.player, album)
- song = self.md_collection.song_items.get(name)
+ song = self.md_collection.song_items[name]
return MuseDashSongItem(name, self.player, song)
def get_filler_item_name(self) -> str:
diff --git a/worlds/oot/Options.py b/worlds/oot/Options.py
index 613c5d01b381..797b276b766c 100644
--- a/worlds/oot/Options.py
+++ b/worlds/oot/Options.py
@@ -1,7 +1,7 @@
import typing
import random
from dataclasses import dataclass
-from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections, \
+from Options import Option, DefaultOnToggle, Toggle, Range, OptionSet, DeathLink, PlandoConnections, \
PerGameCommonOptions, OptionGroup
from .EntranceShuffle import entrance_shuffle_table
from .LogicTricks import normalized_name_tricks
@@ -1272,7 +1272,7 @@ class SfxOcarina(Choice):
}
-class LogicTricks(OptionList):
+class LogicTricks(OptionSet):
"""Set various tricks for logic in Ocarina of Time.
Format as a comma-separated list of "nice" names: ["Fewer Tunic Requirements", "Hidden Grottos without Stone of Agony"].
A full list of supported tricks can be found at:
diff --git a/worlds/pokemon_emerald/CHANGELOG.md b/worlds/pokemon_emerald/CHANGELOG.md
index 0dd874b25029..72005d6f9d3f 100644
--- a/worlds/pokemon_emerald/CHANGELOG.md
+++ b/worlds/pokemon_emerald/CHANGELOG.md
@@ -2,6 +2,7 @@
### Features
+- Added many new item and location groups.
- Added a Swedish translation of the setup guide.
- The client communicates map transitions to any trackers connected to the slot.
- Added the player's Normalize Encounter Rates option to slot data for trackers.
diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py
index a87f93ece56b..c99a0c11cdfb 100644
--- a/worlds/pokemon_emerald/__init__.py
+++ b/worlds/pokemon_emerald/__init__.py
@@ -15,11 +15,11 @@
from worlds.AutoWorld import WebWorld, World
from .client import PokemonEmeraldClient # Unused, but required to register with BizHawkClient
-from .data import LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, data as emerald_data
-from .items import (ITEM_GROUPS, PokemonEmeraldItem, create_item_label_to_code_map, get_item_classification,
- offset_item_value)
-from .locations import (LOCATION_GROUPS, PokemonEmeraldLocation, create_location_label_to_id_map,
- create_locations_with_tags, set_free_fly, set_legendary_cave_entrances)
+from .data import LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, LocationCategory, data as emerald_data
+from .groups import ITEM_GROUPS, LOCATION_GROUPS
+from .items import PokemonEmeraldItem, create_item_label_to_code_map, get_item_classification, offset_item_value
+from .locations import (PokemonEmeraldLocation, create_location_label_to_id_map, create_locations_by_category,
+ set_free_fly, set_legendary_cave_entrances)
from .opponents import randomize_opponent_parties
from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType, PokemonEmeraldOptions,
RandomizeWildPokemon, RandomizeBadges, RandomizeHms, NormanRequirement)
@@ -133,9 +133,10 @@ def __init__(self, multiworld, player):
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
- from .sanity_check import validate_regions
+ from .sanity_check import validate_regions, validate_group_maps
assert validate_regions()
+ assert validate_group_maps()
def get_filler_item_name(self) -> str:
return "Great Ball"
@@ -237,24 +238,32 @@ def generate_early(self) -> None:
def create_regions(self) -> None:
from .regions import create_regions
- regions = create_regions(self)
-
- tags = {"Badge", "HM", "KeyItem", "Rod", "Bike", "EventTicket"} # Tags with progression items always included
+ all_regions = create_regions(self)
+
+ # Categories with progression items always included
+ categories = {
+ LocationCategory.BADGE,
+ LocationCategory.HM,
+ LocationCategory.KEY,
+ LocationCategory.ROD,
+ LocationCategory.BIKE,
+ LocationCategory.TICKET
+ }
if self.options.overworld_items:
- tags.add("OverworldItem")
+ categories.add(LocationCategory.OVERWORLD_ITEM)
if self.options.hidden_items:
- tags.add("HiddenItem")
+ categories.add(LocationCategory.HIDDEN_ITEM)
if self.options.npc_gifts:
- tags.add("NpcGift")
+ categories.add(LocationCategory.GIFT)
if self.options.berry_trees:
- tags.add("BerryTree")
+ categories.add(LocationCategory.BERRY_TREE)
if self.options.dexsanity:
- tags.add("Pokedex")
+ categories.add(LocationCategory.POKEDEX)
if self.options.trainersanity:
- tags.add("Trainer")
- create_locations_with_tags(self, regions, tags)
+ categories.add(LocationCategory.TRAINER)
+ create_locations_by_category(self, all_regions, categories)
- self.multiworld.regions.extend(regions.values())
+ self.multiworld.regions.extend(all_regions.values())
# Exclude locations which are always locked behind the player's goal
def exclude_locations(location_names: List[str]):
@@ -325,21 +334,21 @@ def create_items(self) -> None:
# Filter progression items which shouldn't be shuffled into the itempool.
# Their locations will still exist, but event items will be placed and
# locked at their vanilla locations instead.
- filter_tags = set()
+ filter_categories = set()
if not self.options.key_items:
- filter_tags.add("KeyItem")
+ filter_categories.add(LocationCategory.KEY)
if not self.options.rods:
- filter_tags.add("Rod")
+ filter_categories.add(LocationCategory.ROD)
if not self.options.bikes:
- filter_tags.add("Bike")
+ filter_categories.add(LocationCategory.BIKE)
if not self.options.event_tickets:
- filter_tags.add("EventTicket")
+ filter_categories.add(LocationCategory.TICKET)
if self.options.badges in {RandomizeBadges.option_vanilla, RandomizeBadges.option_shuffle}:
- filter_tags.add("Badge")
+ filter_categories.add(LocationCategory.BADGE)
if self.options.hms in {RandomizeHms.option_vanilla, RandomizeHms.option_shuffle}:
- filter_tags.add("HM")
+ filter_categories.add(LocationCategory.HM)
# If Badges and HMs are set to the `shuffle` option, don't add them to
# the normal item pool, but do create their items and save them and
@@ -347,17 +356,17 @@ def create_items(self) -> None:
if self.options.badges == RandomizeBadges.option_shuffle:
self.badge_shuffle_info = [
(location, self.create_item_by_code(location.default_item_code))
- for location in [l for l in item_locations if "Badge" in l.tags]
+ for location in [l for l in item_locations if emerald_data.locations[l.key].category == LocationCategory.BADGE]
]
if self.options.hms == RandomizeHms.option_shuffle:
self.hm_shuffle_info = [
(location, self.create_item_by_code(location.default_item_code))
- for location in [l for l in item_locations if "HM" in l.tags]
+ for location in [l for l in item_locations if emerald_data.locations[l.key].category == LocationCategory.HM]
]
# Filter down locations to actual items that will be filled and create
# the itempool.
- item_locations = [location for location in item_locations if len(filter_tags & location.tags) == 0]
+ item_locations = [location for location in item_locations if emerald_data.locations[location.key].category not in filter_categories]
default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations]
# Take the itempool as-is
@@ -366,7 +375,8 @@ def create_items(self) -> None:
# Recreate the itempool from random items
elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced):
- item_categories = ["Ball", "Heal", "Candy", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc", "Berry"]
+ item_categories = ["Ball", "Healing", "Rare Candy", "Vitamin", "Evolution Stone",
+ "Money", "TM", "Held", "Misc", "Berry"]
# Count occurrences of types of vanilla items in pool
item_category_counter = Counter()
@@ -436,25 +446,26 @@ def generate_basic(self) -> None:
# Key items which are considered in access rules but not randomized are converted to events and placed
# in their vanilla locations so that the player can have them in their inventory for logic.
- def convert_unrandomized_items_to_events(tag: str) -> None:
+ def convert_unrandomized_items_to_events(category: LocationCategory) -> None:
for location in self.multiworld.get_locations(self.player):
- if location.tags is not None and tag in location.tags:
+ assert isinstance(location, PokemonEmeraldLocation)
+ if location.key is not None and emerald_data.locations[location.key].category == category:
location.place_locked_item(self.create_event(self.item_id_to_name[location.default_item_code]))
location.progress_type = LocationProgressType.DEFAULT
location.address = None
if self.options.badges == RandomizeBadges.option_vanilla:
- convert_unrandomized_items_to_events("Badge")
+ convert_unrandomized_items_to_events(LocationCategory.BADGE)
if self.options.hms == RandomizeHms.option_vanilla:
- convert_unrandomized_items_to_events("HM")
+ convert_unrandomized_items_to_events(LocationCategory.HM)
if not self.options.rods:
- convert_unrandomized_items_to_events("Rod")
+ convert_unrandomized_items_to_events(LocationCategory.ROD)
if not self.options.bikes:
- convert_unrandomized_items_to_events("Bike")
+ convert_unrandomized_items_to_events(LocationCategory.BIKE)
if not self.options.event_tickets:
- convert_unrandomized_items_to_events("EventTicket")
+ convert_unrandomized_items_to_events(LocationCategory.TICKET)
if not self.options.key_items:
- convert_unrandomized_items_to_events("KeyItem")
+ convert_unrandomized_items_to_events(LocationCategory.KEY)
def pre_fill(self) -> None:
# Badges and HMs that are set to shuffle need to be placed at
diff --git a/worlds/pokemon_emerald/data.py b/worlds/pokemon_emerald/data.py
index 432d59387391..c7af5ef2284a 100644
--- a/worlds/pokemon_emerald/data.py
+++ b/worlds/pokemon_emerald/data.py
@@ -117,6 +117,21 @@ class ItemData(NamedTuple):
tags: FrozenSet[str]
+class LocationCategory(IntEnum):
+ BADGE = 0
+ HM = 1
+ KEY = 2
+ ROD = 3
+ BIKE = 4
+ TICKET = 5
+ OVERWORLD_ITEM = 6
+ HIDDEN_ITEM = 7
+ GIFT = 8
+ BERRY_TREE = 9
+ TRAINER = 10
+ POKEDEX = 11
+
+
class LocationData(NamedTuple):
name: str
label: str
@@ -124,6 +139,7 @@ class LocationData(NamedTuple):
default_item: int
address: Union[int, List[int]]
flag: int
+ category: LocationCategory
tags: FrozenSet[str]
@@ -431,6 +447,7 @@ def _init() -> None:
location_json["default_item"],
[location_json["address"]] + [j["address"] for j in alternate_rival_jsons],
location_json["flag"],
+ LocationCategory[location_attributes_json[location_name]["category"]],
frozenset(location_attributes_json[location_name]["tags"])
)
else:
@@ -441,6 +458,7 @@ def _init() -> None:
location_json["default_item"],
location_json["address"],
location_json["flag"],
+ LocationCategory[location_attributes_json[location_name]["category"]],
frozenset(location_attributes_json[location_name]["tags"])
)
new_region.locations.append(location_name)
@@ -948,6 +966,7 @@ def _init() -> None:
evo_stage_to_ball_map[evo_stage],
data.locations[dex_location_name].address,
data.locations[dex_location_name].flag,
+ data.locations[dex_location_name].category,
data.locations[dex_location_name].tags
)
diff --git a/worlds/pokemon_emerald/data/items.json b/worlds/pokemon_emerald/data/items.json
index 139d75aad0ab..4c09d215cf3c 100644
--- a/worlds/pokemon_emerald/data/items.json
+++ b/worlds/pokemon_emerald/data/items.json
@@ -52,49 +52,49 @@
"ITEM_HM_CUT": {
"label": "HM01 Cut",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM01", "Unique"],
"modern_id": 420
},
"ITEM_HM_FLY": {
"label": "HM02 Fly",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM02", "Unique"],
"modern_id": 421
},
"ITEM_HM_SURF": {
"label": "HM03 Surf",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM03", "Unique"],
"modern_id": 422
},
"ITEM_HM_STRENGTH": {
"label": "HM04 Strength",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM04", "Unique"],
"modern_id": 423
},
"ITEM_HM_FLASH": {
"label": "HM05 Flash",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM05", "Unique"],
"modern_id": 424
},
"ITEM_HM_ROCK_SMASH": {
"label": "HM06 Rock Smash",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM06", "Unique"],
"modern_id": 425
},
"ITEM_HM_WATERFALL": {
"label": "HM07 Waterfall",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM07", "Unique"],
"modern_id": 737
},
"ITEM_HM_DIVE": {
"label": "HM08 Dive",
"classification": "PROGRESSION",
- "tags": ["HM", "Unique"],
+ "tags": ["HM", "HM08", "Unique"],
"modern_id": null
},
@@ -375,169 +375,169 @@
"ITEM_POTION": {
"label": "Potion",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 17
},
"ITEM_ANTIDOTE": {
"label": "Antidote",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 18
},
"ITEM_BURN_HEAL": {
"label": "Burn Heal",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 19
},
"ITEM_ICE_HEAL": {
"label": "Ice Heal",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 20
},
"ITEM_AWAKENING": {
"label": "Awakening",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 21
},
"ITEM_PARALYZE_HEAL": {
"label": "Paralyze Heal",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 22
},
"ITEM_FULL_RESTORE": {
"label": "Full Restore",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 23
},
"ITEM_MAX_POTION": {
"label": "Max Potion",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 24
},
"ITEM_HYPER_POTION": {
"label": "Hyper Potion",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 25
},
"ITEM_SUPER_POTION": {
"label": "Super Potion",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 26
},
"ITEM_FULL_HEAL": {
"label": "Full Heal",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 27
},
"ITEM_REVIVE": {
"label": "Revive",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 28
},
"ITEM_MAX_REVIVE": {
"label": "Max Revive",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 29
},
"ITEM_FRESH_WATER": {
"label": "Fresh Water",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 30
},
"ITEM_SODA_POP": {
"label": "Soda Pop",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 31
},
"ITEM_LEMONADE": {
"label": "Lemonade",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 32
},
"ITEM_MOOMOO_MILK": {
"label": "Moomoo Milk",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 33
},
"ITEM_ENERGY_POWDER": {
"label": "Energy Powder",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 34
},
"ITEM_ENERGY_ROOT": {
"label": "Energy Root",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 35
},
"ITEM_HEAL_POWDER": {
"label": "Heal Powder",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 36
},
"ITEM_REVIVAL_HERB": {
"label": "Revival Herb",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 37
},
"ITEM_ETHER": {
"label": "Ether",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 38
},
"ITEM_MAX_ETHER": {
"label": "Max Ether",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 39
},
"ITEM_ELIXIR": {
"label": "Elixir",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 40
},
"ITEM_MAX_ELIXIR": {
"label": "Max Elixir",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 41
},
"ITEM_LAVA_COOKIE": {
"label": "Lava Cookie",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 42
},
"ITEM_BERRY_JUICE": {
"label": "Berry Juice",
"classification": "FILLER",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 43
},
"ITEM_SACRED_ASH": {
"label": "Sacred Ash",
"classification": "USEFUL",
- "tags": ["Heal"],
+ "tags": ["Healing"],
"modern_id": 44
},
@@ -736,19 +736,19 @@
},
"ITEM_BLACK_FLUTE": {
"label": "Black Flute",
- "classification": "FILLER",
+ "classification": "USEFUL",
"tags": ["Misc"],
"modern_id": 68
},
"ITEM_WHITE_FLUTE": {
"label": "White Flute",
- "classification": "FILLER",
+ "classification": "USEFUL",
"tags": ["Misc"],
"modern_id": 69
},
"ITEM_HEART_SCALE": {
"label": "Heart Scale",
- "classification": "FILLER",
+ "classification": "USEFUL",
"tags": ["Misc"],
"modern_id": 93
},
@@ -757,37 +757,37 @@
"ITEM_SUN_STONE": {
"label": "Sun Stone",
"classification": "USEFUL",
- "tags": ["EvoStone"],
+ "tags": ["Evolution Stone"],
"modern_id": 80
},
"ITEM_MOON_STONE": {
"label": "Moon Stone",
"classification": "USEFUL",
- "tags": ["EvoStone"],
+ "tags": ["Evolution Stone"],
"modern_id": 81
},
"ITEM_FIRE_STONE": {
"label": "Fire Stone",
"classification": "USEFUL",
- "tags": ["EvoStone"],
+ "tags": ["Evolution Stone"],
"modern_id": 82
},
"ITEM_THUNDER_STONE": {
"label": "Thunder Stone",
"classification": "USEFUL",
- "tags": ["EvoStone"],
+ "tags": ["Evolution Stone"],
"modern_id": 83
},
"ITEM_WATER_STONE": {
"label": "Water Stone",
"classification": "USEFUL",
- "tags": ["EvoStone"],
+ "tags": ["Evolution Stone"],
"modern_id": 84
},
"ITEM_LEAF_STONE": {
"label": "Leaf Stone",
"classification": "USEFUL",
- "tags": ["EvoStone"],
+ "tags": ["Evolution Stone"],
"modern_id": 85
},
@@ -1215,7 +1215,7 @@
"ITEM_KINGS_ROCK": {
"label": "King's Rock",
"classification": "USEFUL",
- "tags": ["Held"],
+ "tags": ["Held", "Evolution Stone"],
"modern_id": 221
},
"ITEM_SILVER_POWDER": {
@@ -1245,13 +1245,13 @@
"ITEM_DEEP_SEA_TOOTH": {
"label": "Deep Sea Tooth",
"classification": "USEFUL",
- "tags": ["Held"],
+ "tags": ["Held", "Evolution Stone"],
"modern_id": 226
},
"ITEM_DEEP_SEA_SCALE": {
"label": "Deep Sea Scale",
"classification": "USEFUL",
- "tags": ["Held"],
+ "tags": ["Held", "Evolution Stone"],
"modern_id": 227
},
"ITEM_SMOKE_BALL": {
@@ -1287,7 +1287,7 @@
"ITEM_METAL_COAT": {
"label": "Metal Coat",
"classification": "USEFUL",
- "tags": ["Held"],
+ "tags": ["Held", "Evolution Stone"],
"modern_id": 233
},
"ITEM_LEFTOVERS": {
@@ -1299,7 +1299,7 @@
"ITEM_DRAGON_SCALE": {
"label": "Dragon Scale",
"classification": "USEFUL",
- "tags": ["Held"],
+ "tags": ["Held", "Evolution Stone"],
"modern_id": 235
},
"ITEM_LIGHT_BALL": {
@@ -1401,7 +1401,7 @@
"ITEM_UP_GRADE": {
"label": "Up-Grade",
"classification": "USEFUL",
- "tags": ["Held"],
+ "tags": ["Held", "Evolution Stone"],
"modern_id": 252
},
"ITEM_SHELL_BELL": {
diff --git a/worlds/pokemon_emerald/data/locations.json b/worlds/pokemon_emerald/data/locations.json
index 55ef15d871bb..63f42340cce4 100644
--- a/worlds/pokemon_emerald/data/locations.json
+++ b/worlds/pokemon_emerald/data/locations.json
@@ -1,5364 +1,6702 @@
{
"BADGE_1": {
"label": "Rustboro Gym - Stone Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_2": {
"label": "Dewford Gym - Knuckle Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_3": {
"label": "Mauville Gym - Dynamo Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_4": {
"label": "Lavaridge Gym - Heat Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_5": {
"label": "Petalburg Gym - Balance Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_6": {
"label": "Fortree Gym - Feather Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_7": {
"label": "Mossdeep Gym - Mind Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"BADGE_8": {
"label": "Sootopolis Gym - Rain Badge",
- "tags": ["Badge"]
+ "tags": [],
+ "category": "BADGE"
},
"NPC_GIFT_RECEIVED_HM_CUT": {
"label": "Rustboro City - HM01 from Cutter's House",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_FLY": {
"label": "Route 119 - HM02 from Rival Battle",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_SURF": {
"label": "Petalburg City - HM03 from Wally's Uncle",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_STRENGTH": {
"label": "Rusturf Tunnel - HM04 from Tunneler",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_FLASH": {
"label": "Granite Cave 1F - HM05 from Hiker",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_ROCK_SMASH": {
"label": "Mauville City - HM06 from Rock Smash Guy",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_WATERFALL": {
"label": "Sootopolis City - HM07 from Wallace",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_HM_DIVE": {
"label": "Mossdeep City - HM08 from Steven's House",
- "tags": ["HM"]
+ "tags": [],
+ "category": "HM"
},
"NPC_GIFT_RECEIVED_ACRO_BIKE": {
"label": "Mauville City - Acro Bike",
- "tags": ["Bike"]
+ "tags": [],
+ "category": "BIKE"
},
"NPC_GIFT_RECEIVED_MACH_BIKE": {
"label": "Mauville City - Mach Bike",
- "tags": ["Bike"]
+ "tags": [],
+ "category": "BIKE"
},
"NPC_GIFT_RECEIVED_WAILMER_PAIL": {
"label": "Route 104 - Wailmer Pail from Flower Shop Lady",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_DEVON_GOODS_RUSTURF_TUNNEL": {
"label": "Rusturf Tunnel - Recover Devon Goods",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_LETTER": {
"label": "Devon Corp 3F - Letter from Mr. Stone",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_COIN_CASE": {
"label": "Mauville City - Coin Case from Lady in House",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_METEORITE": {
"label": "Mt Chimney - Meteorite from Machine",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_GO_GOGGLES": {
"label": "Lavaridge Town - Go Goggles from Rival",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_GOT_BASEMENT_KEY_FROM_WATTSON": {
"label": "Mauville City - Basement Key from Wattson",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_ITEMFINDER": {
"label": "Route 110 - Itemfinder from Rival",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_DEVON_SCOPE": {
"label": "Route 120 - Devon Scope from Steven",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_MAGMA_EMBLEM": {
"label": "Mt Pyre Summit - Magma Emblem from Old Lady",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"ITEM_ABANDONED_SHIP_CAPTAINS_OFFICE_STORAGE_KEY": {
"label": "Abandoned Ship - Captain's Office Key",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"HIDDEN_ITEM_ABANDONED_SHIP_RM_4_KEY": {
"label": "Abandoned Ship HF - Room 4 Key",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"HIDDEN_ITEM_ABANDONED_SHIP_RM_1_KEY": {
"label": "Abandoned Ship HF - Room 1 Key",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"HIDDEN_ITEM_ABANDONED_SHIP_RM_6_KEY": {
"label": "Abandoned Ship HF - Room 6 Key",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"HIDDEN_ITEM_ABANDONED_SHIP_RM_2_KEY": {
"label": "Abandoned Ship HF - Room 2 Key",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_2_SCANNER": {
"label": "Abandoned Ship HF - Scanner",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_POKEBLOCK_CASE": {
"label": "Lilycove City - Pokeblock Case from Contest Hall",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_SS_TICKET": {
"label": "Littleroot Town - S.S. Ticket from Norman",
- "tags": ["KeyItem"]
+ "tags": [],
+ "category": "KEY"
},
"NPC_GIFT_RECEIVED_AURORA_TICKET": {
"label": "Littleroot Town - Aurora Ticket from Norman",
- "tags": ["EventTicket"]
+ "tags": [],
+ "category": "TICKET"
},
"NPC_GIFT_RECEIVED_EON_TICKET": {
"label": "Littleroot Town - Eon Ticket from Norman",
- "tags": ["EventTicket"]
+ "tags": [],
+ "category": "TICKET"
},
"NPC_GIFT_RECEIVED_MYSTIC_TICKET": {
"label": "Littleroot Town - Mystic Ticket from Norman",
- "tags": ["EventTicket"]
+ "tags": [],
+ "category": "TICKET"
},
"NPC_GIFT_RECEIVED_OLD_SEA_MAP": {
"label": "Littleroot Town - Old Sea Map from Norman",
- "tags": ["EventTicket"]
+ "tags": [],
+ "category": "TICKET"
},
"NPC_GIFT_RECEIVED_OLD_ROD": {
"label": "Dewford Town - Old Rod from Fisherman",
- "tags": ["Rod"]
+ "tags": [],
+ "category": "ROD"
},
"NPC_GIFT_RECEIVED_GOOD_ROD": {
"label": "Route 118 - Good Rod from Fisherman",
- "tags": ["Rod"]
+ "tags": [],
+ "category": "ROD"
},
"NPC_GIFT_RECEIVED_SUPER_ROD": {
"label": "Mossdeep City - Super Rod from Fisherman in House",
- "tags": ["Rod"]
+ "tags": [],
+ "category": "ROD"
},
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_CALCIUM": {
"label": "Artisan Cave B1F - Hidden Item 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_IRON": {
"label": "Artisan Cave B1F - Hidden Item 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_PROTEIN": {
"label": "Artisan Cave B1F - Hidden Item 3",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_ZINC": {
"label": "Artisan Cave B1F - Hidden Item 4",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_FALLARBOR_TOWN_NUGGET": {
"label": "Fallarbor Town - Hidden Item in Crater",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_GRANITE_CAVE_B2F_EVERSTONE_1": {
"label": "Granite Cave B2F - Hidden Item After Crumbling Floor",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_GRANITE_CAVE_B2F_EVERSTONE_2": {
"label": "Granite Cave B2F - Hidden Item on Platform",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_JAGGED_PASS_FULL_HEAL": {
"label": "Jagged Pass - Hidden Item in Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_JAGGED_PASS_GREAT_BALL": {
"label": "Jagged Pass - Hidden Item in Corner",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_LAVARIDGE_TOWN_ICE_HEAL": {
"label": "Lavaridge Town - Hidden Item in Springs",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_LILYCOVE_CITY_HEART_SCALE": {
"label": "Lilycove City - Hidden Item on Beach West",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_LILYCOVE_CITY_POKE_BALL": {
"label": "Lilycove City - Hidden Item on Beach East",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_LILYCOVE_CITY_PP_UP": {
"label": "Lilycove City - Hidden Item on Beach North",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_MT_PYRE_EXTERIOR_MAX_ETHER": {
"label": "Mt Pyre Exterior - Hidden Item First Grave",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_MT_PYRE_EXTERIOR_ULTRA_BALL": {
"label": "Mt Pyre Exterior - Hidden Item Second Grave",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_MT_PYRE_SUMMIT_RARE_CANDY": {
"label": "Mt Pyre Summit - Hidden Item in Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_MT_PYRE_SUMMIT_ZINC": {
"label": "Mt Pyre Summit - Hidden Item Grave",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_PETALBURG_CITY_RARE_CANDY": {
"label": "Petalburg City - Hidden Item Past Pond South",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_PETALBURG_WOODS_POKE_BALL": {
"label": "Petalburg Woods - Hidden Item After Grunt",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_PETALBURG_WOODS_POTION": {
"label": "Petalburg Woods - Hidden Item Southeast",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_PETALBURG_WOODS_TINY_MUSHROOM_1": {
"label": "Petalburg Woods - Hidden Item Past Tree North",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_PETALBURG_WOODS_TINY_MUSHROOM_2": {
"label": "Petalburg Woods - Hidden Item Past Tree South",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_104_ANTIDOTE": {
"label": "Route 104 - Hidden Item on Beach 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_104_HEART_SCALE": {
"label": "Route 104 - Hidden Item on Beach 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_104_POTION": {
"label": "Route 104 - Hidden Item on Beach 3",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_104_POKE_BALL": {
"label": "Route 104 - Hidden Item Behind Flower Shop 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_104_SUPER_POTION": {
"label": "Route 104 - Hidden Item Behind Flower Shop 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_105_BIG_PEARL": {
"label": "Route 105 - Hidden Item Between Trainers",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_105_HEART_SCALE": {
"label": "Route 105 - Hidden Item on Small Island",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_106_HEART_SCALE": {
"label": "Route 106 - Hidden Item on Beach 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_106_STARDUST": {
"label": "Route 106 - Hidden Item on Beach 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_106_POKE_BALL": {
"label": "Route 106 - Hidden Item on Beach 3",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_108_RARE_CANDY": {
"label": "Route 108 - Hidden Item on Rock",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_109_REVIVE": {
"label": "Route 109 - Hidden Item on Beach Southwest",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_109_ETHER": {
"label": "Route 109 - Hidden Item on Beach Southeast",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_109_HEART_SCALE_2": {
"label": "Route 109 - Hidden Item on Beach Under Umbrella",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_109_GREAT_BALL": {
"label": "Route 109 - Hidden Item on Beach West",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_109_HEART_SCALE_1": {
"label": "Route 109 - Hidden Item on Beach Behind Old Man",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_109_HEART_SCALE_3": {
"label": "Route 109 - Hidden Item in Front of Couple",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_110_FULL_HEAL": {
"label": "Route 110 - Hidden Item South of Rival",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_110_GREAT_BALL": {
"label": "Route 110 - Hidden Item North of Rival",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_110_REVIVE": {
"label": "Route 110 - Hidden Item Behind Two Trainers",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_110_POKE_BALL": {
"label": "Route 110 - Hidden Item South of Berries",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_111_PROTEIN": {
"label": "Route 111 - Hidden Item Desert Behind Tower",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_111_RARE_CANDY": {
"label": "Route 111 - Hidden Item Desert on Rock 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_111_STARDUST": {
"label": "Route 111 - Hidden Item Desert on Rock 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_113_ETHER": {
"label": "Route 113 - Hidden Item Mound West of Three Trainers",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_113_NUGGET": {
"label": "Route 113 - Hidden Item Mound Between Trainers",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_113_TM_DOUBLE_TEAM": {
"label": "Route 113 - Hidden Item Mound West of Workshop",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_114_CARBOS": {
"label": "Route 114 - Hidden Item Rock in Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_114_REVIVE": {
"label": "Route 114 - Hidden Item West of Bridge",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_115_HEART_SCALE": {
"label": "Route 115 - Hidden Item Behind Trainer on Beach",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_116_BLACK_GLASSES": {
"label": "Route 116 - Hidden Item in East",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_116_SUPER_POTION": {
"label": "Route 116 - Hidden Item in Tree Maze",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_117_REPEL": {
"label": "Route 117 - Hidden Item Behind Flower Patch",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_118_HEART_SCALE": {
"label": "Route 118 - Hidden Item West on Rock",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_118_IRON": {
"label": "Route 118 - Hidden Item East on Rock",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_119_FULL_HEAL": {
"label": "Route 119 - Hidden Item in South Tall Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_119_CALCIUM": {
"label": "Route 119 - Hidden Item Across South Rail",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_119_ULTRA_BALL": {
"label": "Route 119 - Hidden Item in East Tall Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_119_MAX_ETHER": {
"label": "Route 119 - Hidden Item Next to Waterfall",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_120_RARE_CANDY_1": {
"label": "Route 120 - Hidden Item Behind Trees",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_120_REVIVE": {
"label": "Route 120 - Hidden Item in North Tall Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_120_ZINC": {
"label": "Route 120 - Hidden Item in Tall Grass Maze",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2": {
"label": "Route 120 - Hidden Item Behind Southwest Pool",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_121_HP_UP": {
"label": "Route 121 - Hidden Item West of Grunts",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_121_FULL_HEAL": {
"label": "Route 121 - Hidden Item in Maze 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_121_MAX_REVIVE": {
"label": "Route 121 - Hidden Item in Maze 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_121_NUGGET": {
"label": "Route 121 - Hidden Item Behind Tree",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_123_PP_UP": {
"label": "Route 123 - Hidden Item East Behind Tree 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_123_RARE_CANDY": {
"label": "Route 123 - Hidden Item East Behind Tree 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_123_HYPER_POTION": {
"label": "Route 123 - Hidden Item on Rock Before Ledges",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_123_SUPER_REPEL": {
"label": "Route 123 - Hidden Item in North Path Grass",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_123_REVIVE": {
"label": "Route 123 - Hidden Item Behind House",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_128_HEART_SCALE_1": {
"label": "Route 128 - Hidden Item North Island",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_128_HEART_SCALE_2": {
"label": "Route 128 - Hidden Item Center Island",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_ROUTE_128_HEART_SCALE_3": {
"label": "Route 128 - Hidden Item Southwest Island",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_SAFARI_ZONE_NORTH_EAST_ZINC": {
"label": "Safari Zone NE - Hidden Item North",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_SAFARI_ZONE_NORTH_EAST_RARE_CANDY": {
"label": "Safari Zone NE - Hidden Item East",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_SAFARI_ZONE_SOUTH_EAST_FULL_RESTORE": {
"label": "Safari Zone SE - Hidden Item in South Grass 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_SAFARI_ZONE_SOUTH_EAST_PP_UP": {
"label": "Safari Zone SE - Hidden Item in South Grass 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_SS_TIDAL_LOWER_DECK_LEFTOVERS": {
"label": "SS Tidal - Hidden Item in Lower Deck Trash Can",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_GREEN_SHARD": {
"label": "Route 124 UW - Hidden Item in Big Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_CARBOS": {
"label": "Route 124 UW - Hidden Item in Tunnel Alcove",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_CALCIUM": {
"label": "Route 124 UW - Hidden Item in North Tunnel 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_HEART_SCALE_2": {
"label": "Route 124 UW - Hidden Item in North Tunnel 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_PEARL": {
"label": "Route 124 UW - Hidden Item in Small Area North",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_BIG_PEARL": {
"label": "Route 124 UW - Hidden Item in Small Area Middle",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_124_HEART_SCALE_1": {
"label": "Route 124 UW - Hidden Item in Small Area South",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_STARDUST": {
"label": "Route 126 UW - Hidden Item Northeast",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_ULTRA_BALL": {
"label": "Route 126 UW - Hidden Item in North Alcove",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_BIG_PEARL": {
"label": "Route 126 UW - Hidden Item in Southeast",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_HEART_SCALE": {
"label": "Route 126 UW - Hidden Item in Northwest Alcove",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_BLUE_SHARD": {
"label": "Route 126 UW - Hidden Item in Southwest Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_IRON": {
"label": "Route 126 UW - Hidden Item in West Area 1",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_PEARL": {
"label": "Route 126 UW - Hidden Item in West Area 2",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_126_YELLOW_SHARD": {
"label": "Route 126 UW - Hidden Item in West Area 3",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_127_STAR_PIECE": {
"label": "Route 127 UW - Hidden Item in West Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_127_HEART_SCALE": {
"label": "Route 127 UW - Hidden Item in Center Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_127_HP_UP": {
"label": "Route 127 UW - Hidden Item in East Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_127_RED_SHARD": {
"label": "Route 127 UW - Hidden Item in Northeast Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_128_PEARL": {
"label": "Route 128 UW - Hidden Item in East Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_UNDERWATER_128_PROTEIN": {
"label": "Route 128 UW - Hidden Item in Small Area",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_VICTORY_ROAD_1F_ULTRA_BALL": {
"label": "Victory Road 1F - Hidden Item on Southeast Ledge",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_VICTORY_ROAD_B2F_ELIXIR": {
"label": "Victory Road B2F - Hidden Item Above Waterfall",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_VICTORY_ROAD_B2F_MAX_REPEL": {
"label": "Victory Road B2F - Hidden Item in Northeast Corner",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"HIDDEN_ITEM_NAVEL_ROCK_TOP_SACRED_ASH": {
"label": "Navel Rock Top - Hidden Item Sacred Ash",
- "tags": ["HiddenItem"]
+ "tags": [],
+ "category": "HIDDEN_ITEM"
},
"ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_1_TM_RAIN_DANCE": {
"label": "Abandoned Ship HF - Item in Room 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_3_WATER_STONE": {
"label": "Abandoned Ship HF - Item in Room 3",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_6_LUXURY_BALL": {
"label": "Abandoned Ship HF - Item in Room 6",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_ROOMS_1F_HARBOR_MAIL": {
"label": "Abandoned Ship 1F - Item in East Side Northwest Room",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_ROOMS_2_1F_REVIVE": {
"label": "Abandoned Ship 1F - Item in West Side North Room",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_ROOMS_B1F_ESCAPE_ROPE": {
"label": "Abandoned Ship B1F - Item in South Rooms",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_ROOMS_B1F_TM_ICE_BEAM": {
"label": "Abandoned Ship B1F - Item in Storage Room",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ABANDONED_SHIP_ROOMS_2_B1F_DIVE_BALL": {
"label": "Abandoned Ship B1F - Item in North Rooms",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_AQUA_HIDEOUT_B1F_MASTER_BALL": {
"label": "Aqua Hideout B1F - Item in Center Room 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_AQUA_HIDEOUT_B1F_NUGGET": {
"label": "Aqua Hideout B1F - Item in Center Room 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_AQUA_HIDEOUT_B1F_MAX_ELIXIR": {
"label": "Aqua Hideout B1F - Item in East Room",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_AQUA_HIDEOUT_B2F_NEST_BALL": {
"label": "Aqua Hideout B2F - Item in Long Hallway",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ARTISAN_CAVE_1F_CARBOS": {
"label": "Artisan Cave 1F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ARTISAN_CAVE_B1F_HP_UP": {
"label": "Artisan Cave B1F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_FIERY_PATH_FIRE_STONE": {
"label": "Fiery Path - Item Behind Boulders 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_FIERY_PATH_TM_TOXIC": {
"label": "Fiery Path - Item Behind Boulders 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_GRANITE_CAVE_1F_ESCAPE_ROPE": {
"label": "Granite Cave 1F - Item Before Ladder",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_GRANITE_CAVE_B1F_POKE_BALL": {
"label": "Granite Cave B1F - Item in Alcove",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_GRANITE_CAVE_B2F_RARE_CANDY": {
"label": "Granite Cave B2F - Item After Crumbling Floor",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_GRANITE_CAVE_B2F_REPEL": {
"label": "Granite Cave B2F - Item After Mud Slope",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_JAGGED_PASS_BURN_HEAL": {
"label": "Jagged Pass - Item Below Hideout",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_LILYCOVE_CITY_MAX_REPEL": {
"label": "Lilycove City - Item on Peninsula",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_1F_RARE_CANDY": {
"label": "Magma Hideout 1F - Item on Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_2F_2R_FULL_RESTORE": {
"label": "Magma Hideout 2F - Item on West Platform",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_2F_2R_MAX_ELIXIR": {
"label": "Magma Hideout 2F - Item on East Platform",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_3F_1R_NUGGET": {
"label": "Magma Hideout 3F - Item Before Last Floor",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_3F_2R_PP_MAX": {
"label": "Magma Hideout 3F - Item in Drill Room",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_3F_3R_ECAPE_ROPE": {
"label": "Magma Hideout 3F - Item After Groudon",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAGMA_HIDEOUT_4F_MAX_REVIVE": {
"label": "Magma Hideout 4F - Item Before Groudon",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MAUVILLE_CITY_X_SPEED": {
"label": "Mauville City - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_METEOR_FALLS_1F_1R_FULL_HEAL": {
"label": "Meteor Falls 1F - Item Northeast",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_METEOR_FALLS_1F_1R_MOON_STONE": {
"label": "Meteor Falls 1F - Item West",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_METEOR_FALLS_1F_1R_PP_UP": {
"label": "Meteor Falls 1F - Item Below Waterfall",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_METEOR_FALLS_1F_1R_TM_IRON_TAIL": {
"label": "Meteor Falls 1F - Item Before Steven's Cave",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_METEOR_FALLS_B1F_2R_TM_DRAGON_CLAW": {
"label": "Meteor Falls B1F - Item in North Cave",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MOSSDEEP_CITY_NET_BALL": {
"label": "Mossdeep City - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_2F_ULTRA_BALL": {
"label": "Mt Pyre 2F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_3F_SUPER_REPEL": {
"label": "Mt Pyre 3F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_4F_SEA_INCENSE": {
"label": "Mt Pyre 4F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_5F_LAX_INCENSE": {
"label": "Mt Pyre 5F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_6F_TM_SHADOW_BALL": {
"label": "Mt Pyre 6F - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_EXTERIOR_TM_SKILL_SWAP": {
"label": "Mt Pyre Exterior - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_MT_PYRE_EXTERIOR_MAX_POTION": {
"label": "Mt Pyre Exterior - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_NEW_MAUVILLE_ESCAPE_ROPE": {
"label": "New Mauville - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_NEW_MAUVILLE_PARALYZE_HEAL": {
"label": "New Mauville - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_NEW_MAUVILLE_FULL_HEAL": {
"label": "New Mauville - Item 3",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_NEW_MAUVILLE_THUNDER_STONE": {
"label": "New Mauville - Item 4",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_NEW_MAUVILLE_ULTRA_BALL": {
"label": "New Mauville - Item 5",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_PETALBURG_CITY_ETHER": {
"label": "Petalburg City - Item Past Pond South",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_PETALBURG_CITY_MAX_REVIVE": {
"label": "Petalburg City - Item Past Pond North",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_PETALBURG_WOODS_ETHER": {
"label": "Petalburg Woods - Item Northwest",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_PETALBURG_WOODS_PARALYZE_HEAL": {
"label": "Petalburg Woods - Item Southwest",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_PETALBURG_WOODS_GREAT_BALL": {
"label": "Petalburg Woods - Item Past Tree Northeast",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_PETALBURG_WOODS_X_ATTACK": {
"label": "Petalburg Woods - Item Past Tree South",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_102_POTION": {
"label": "Route 102 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_103_GUARD_SPEC": {
"label": "Route 103 - Item Near Berries",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_103_PP_UP": {
"label": "Route 103 - Item in Tree Maze",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_104_POKE_BALL": {
"label": "Route 104 - Item Near Briney on Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_104_POTION": {
"label": "Route 104 - Item Behind Flower Shop",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_104_X_ACCURACY": {
"label": "Route 104 - Item Behind Tree",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_104_PP_UP": {
"label": "Route 104 - Item East Past Pond",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_105_IRON": {
"label": "Route 105 - Item on Island",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_106_PROTEIN": {
"label": "Route 106 - Item on West Beach",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_108_STAR_PIECE": {
"label": "Route 108 - Item Between Trainers",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_109_POTION": {
"label": "Route 109 - Item on Beach",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_109_PP_UP": {
"label": "Route 109 - Item on Island",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_110_DIRE_HIT": {
"label": "Route 110 - Item South of Rival",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_110_ELIXIR": {
"label": "Route 110 - Item South of Berries",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_110_RARE_CANDY": {
"label": "Route 110 - Item on Island",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_111_ELIXIR": {
"label": "Route 111 - Item Near Winstrates",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_111_HP_UP": {
"label": "Route 111 - Item West of Pond Near Winstrates",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_111_STARDUST": {
"label": "Route 111 - Item Desert Near Tower",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_111_TM_SANDSTORM": {
"label": "Route 111 - Item Desert South",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_112_NUGGET": {
"label": "Route 112 - Item on Ledges",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_113_SUPER_REPEL": {
"label": "Route 113 - Item Past Three Trainers",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_113_MAX_ETHER": {
"label": "Route 113 - Item on Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_113_HYPER_POTION": {
"label": "Route 113 - Item Near Fallarbor South",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_114_ENERGY_POWDER": {
"label": "Route 114 - Item Between Trainers",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_114_PROTEIN": {
"label": "Route 114 - Item Behind Smashable Rock",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_114_RARE_CANDY": {
"label": "Route 114 - Item Above Waterfall",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_115_SUPER_POTION": {
"label": "Route 115 - Item on Beach",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_115_PP_UP": {
"label": "Route 115 - Item on Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_115_GREAT_BALL": {
"label": "Route 115 - Item Behind Smashable Rock",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_115_HEAL_POWDER": {
"label": "Route 115 - Item North Near Trainers",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_115_TM_FOCUS_PUNCH": {
"label": "Route 115 - Item Near Mud Slope",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_115_IRON": {
"label": "Route 115 - Item Past Mud Slope",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_116_REPEL": {
"label": "Route 116 - Item in Grass",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_116_X_SPECIAL": {
"label": "Route 116 - Item Near Tunnel",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_116_POTION": {
"label": "Route 116 - Item in Tree Maze 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_116_ETHER": {
"label": "Route 116 - Item in Tree Maze 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_116_HP_UP": {
"label": "Route 116 - Item in East",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_117_GREAT_BALL": {
"label": "Route 117 - Item Behind Flower Patch",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_117_REVIVE": {
"label": "Route 117 - Item Behind Tree",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_118_HYPER_POTION": {
"label": "Route 118 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_SUPER_REPEL": {
"label": "Route 119 - Item in South Tall Grass 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_HYPER_POTION_1": {
"label": "Route 119 - Item in South Tall Grass 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_ZINC": {
"label": "Route 119 - Item Across River South",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_HYPER_POTION_2": {
"label": "Route 119 - Item Near Mud Slope",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_ELIXIR_1": {
"label": "Route 119 - Item East of Mud Slope",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_ELIXIR_2": {
"label": "Route 119 - Item on River Bank",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_LEAF_STONE": {
"label": "Route 119 - Item Near South Waterfall",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_NUGGET": {
"label": "Route 119 - Item Above North Waterfall 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_119_RARE_CANDY": {
"label": "Route 119 - Item Above North Waterfall 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_120_NEST_BALL": {
"label": "Route 120 - Item Near North Pond",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_120_REVIVE": {
"label": "Route 120 - Item in North Puddles",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_120_NUGGET": {
"label": "Route 120 - Item in Tall Grass Maze",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_120_HYPER_POTION": {
"label": "Route 120 - Item in Tall Grass South",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_120_FULL_HEAL": {
"label": "Route 120 - Item Behind Southwest Pool",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_121_ZINC": {
"label": "Route 121 - Item Near Safari Zone",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_121_REVIVE": {
"label": "Route 121 - Item in Maze 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_121_CARBOS": {
"label": "Route 121 - Item in Maze 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_123_ULTRA_BALL": {
"label": "Route 123 - Item Below Ledges",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_123_ELIXIR": {
"label": "Route 123 - Item on Ledges 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_123_REVIVAL_HERB": {
"label": "Route 123 - Item on Ledges 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_123_PP_UP": {
"label": "Route 123 - Item on Ledges 3",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_123_CALCIUM": {
"label": "Route 123 - Item on Ledges 4",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_124_RED_SHARD": {
"label": "Route 124 - Item in Northwest Area",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_124_YELLOW_SHARD": {
"label": "Route 124 - Item in Northeast Area",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_124_BLUE_SHARD": {
"label": "Route 124 - Item in Southwest Area",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_125_BIG_PEARL": {
"label": "Route 125 - Item Between Trainers",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_126_GREEN_SHARD": {
"label": "Route 126 - Item in Separated Area",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_127_ZINC": {
"label": "Route 127 - Item North",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_127_CARBOS": {
"label": "Route 127 - Item East",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_127_RARE_CANDY": {
"label": "Route 127 - Item Between Trainers",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_132_PROTEIN": {
"label": "Route 132 - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_132_RARE_CANDY": {
"label": "Route 132 - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_133_BIG_PEARL": {
"label": "Route 133 - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_133_MAX_REVIVE": {
"label": "Route 133 - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_133_STAR_PIECE": {
"label": "Route 133 - Item 3",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_134_CARBOS": {
"label": "Route 134 - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_ROUTE_134_STAR_PIECE": {
"label": "Route 134 - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_RUSTBORO_CITY_X_DEFEND": {
"label": "Rustboro City - Item Behind Fences",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_RUSTURF_TUNNEL_POKE_BALL": {
"label": "Rusturf Tunnel - Item West",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_RUSTURF_TUNNEL_MAX_ETHER": {
"label": "Rusturf Tunnel - Item East",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SAFARI_ZONE_NORTH_CALCIUM": {
"label": "Safari Zone N - Item in Grass",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SAFARI_ZONE_NORTH_EAST_NUGGET": {
"label": "Safari Zone NE - Item on Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SAFARI_ZONE_NORTH_WEST_TM_SOLAR_BEAM": {
"label": "Safari Zone NW - Item Behind Pond",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SAFARI_ZONE_SOUTH_EAST_BIG_PEARL": {
"label": "Safari Zone SE - Item in Grass",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SAFARI_ZONE_SOUTH_WEST_MAX_REVIVE": {
"label": "Safari Zone SW - Item Behind Pond",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SCORCHED_SLAB_TM_SUNNY_DAY": {
"label": "Scorched Slab - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SEAFLOOR_CAVERN_ROOM_9_TM_EARTHQUAKE": {
"label": "Seafloor Cavern Room 9 - Item Before Kyogre",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SHOAL_CAVE_ENTRANCE_BIG_PEARL": {
"label": "Shoal Cave Entrance - Item on Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SHOAL_CAVE_ICE_ROOM_NEVER_MELT_ICE": {
"label": "Shoal Cave Ice Room - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SHOAL_CAVE_ICE_ROOM_TM_HAIL": {
"label": "Shoal Cave Ice Room - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SHOAL_CAVE_INNER_ROOM_RARE_CANDY": {
"label": "Shoal Cave Inner Room - Item in Center",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_SHOAL_CAVE_STAIRS_ROOM_ICE_HEAL": {
"label": "Shoal Cave Stairs Room - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_1_ORANGE_MAIL": {
"label": "Trick House Puzzle 1 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_2_HARBOR_MAIL": {
"label": "Trick House Puzzle 2 - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_2_WAVE_MAIL": {
"label": "Trick House Puzzle 2 - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_3_SHADOW_MAIL": {
"label": "Trick House Puzzle 3 - Item 1",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_3_WOOD_MAIL": {
"label": "Trick House Puzzle 3 - Item 2",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_4_MECH_MAIL": {
"label": "Trick House Puzzle 4 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_6_GLITTER_MAIL": {
"label": "Trick House Puzzle 6 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_7_TROPIC_MAIL": {
"label": "Trick House Puzzle 7 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_TRICK_HOUSE_PUZZLE_8_BEAD_MAIL": {
"label": "Trick House Puzzle 8 - Item",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_VICTORY_ROAD_1F_MAX_ELIXIR": {
"label": "Victory Road 1F - Item East",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_VICTORY_ROAD_1F_PP_UP": {
"label": "Victory Road 1F - Item on Southeast Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_VICTORY_ROAD_B1F_FULL_RESTORE": {
"label": "Victory Road B1F - Item Behind Boulders",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_VICTORY_ROAD_B1F_TM_PSYCHIC": {
"label": "Victory Road B1F - Item on Northeast Ledge",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"ITEM_VICTORY_ROAD_B2F_FULL_HEAL": {
"label": "Victory Road B2F - Item Above Waterfall",
- "tags": ["OverworldItem"]
+ "tags": [],
+ "category": "OVERWORLD_ITEM"
},
"NPC_GIFT_GOT_TM_THUNDERBOLT_FROM_WATTSON": {
"label": "Mauville City - TM24 from Wattson",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_6_SODA_POP": {
"label": "Route 109 - Seashore House Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_AMULET_COIN": {
"label": "Littleroot Town - Amulet Coin from Mom",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_CHARCOAL": {
"label": "Lavaridge Town Herb Shop - Charcoal from Man",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_CHESTO_BERRY_ROUTE_104": {
"label": "Route 104 - Gift from Woman Near Berries",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_CLEANSE_TAG": {
"label": "Mt Pyre 1F - Cleanse Tag from Woman in NE Corner",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_EXP_SHARE": {
"label": "Devon Corp 3F - Exp. Share from Mr. Stone",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_FOCUS_BAND": {
"label": "Shoal Cave Lower Room - Focus Band from Black Belt",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_GREAT_BALL_PETALBURG_WOODS": {
"label": "Petalburg Woods - Gift from Devon Employee",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_GREAT_BALL_RUSTBORO_CITY": {
"label": "Rustboro City - Gift from Devon Employee",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_KINGS_ROCK": {
"label": "Mossdeep City - King's Rock from Boy",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_MACHO_BRACE": {
"label": "Route 111 - Winstrate Family Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_MENTAL_HERB": {
"label": "Fortree City - Wingull Delivery Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_MIRACLE_SEED": {
"label": "Petalburg Woods - Miracle Seed from Lady",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_POTION_OLDALE": {
"label": "Oldale Town - Gift from Shop Tutorial",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_POWDER_JAR": {
"label": "Slateport City - Powder Jar from Lady in Market",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_PREMIER_BALL_RUSTBORO": {
"label": "Rustboro City - Gift from Boy in Apartments",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_QUICK_CLAW": {
"label": "Rustboro City - Quick Claw from School Teacher",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_REPEAT_BALL": {
"label": "Route 116 - Gift from Devon Researcher",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_SECRET_POWER": {
"label": "Route 111 - Secret Power from Man Near Tree",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_SILK_SCARF": {
"label": "Dewford Town - Silk Scarf from Man in House",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_SOFT_SAND": {
"label": "Route 109 - Soft Sand from Tuber",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_SOOT_SACK": {
"label": "Route 113 - Soot Sack from Glass Blower",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_SOOTHE_BELL": {
"label": "Slateport City - Soothe Bell from Woman in Fan Club",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_SUN_STONE_MOSSDEEP": {
"label": "Space Center - Gift from Man",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_WATER_PULSE": {
"label": "Sootopolis Gym - TM03 from Juan",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_CALM_MIND": {
"label": "Mossdeep Gym - TM04 from Tate and Liza",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_ROAR": {
"label": "Route 114 - TM05 from Roaring Man",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_BULK_UP": {
"label": "Dewford Gym - TM08 from Brawly",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_BULLET_SEED": {
"label": "Route 104 - TM09 from Boy",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_HIDDEN_POWER": {
"label": "Fortree City - TM10 from Hidden Power Lady",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_GIGA_DRAIN": {
"label": "Route 123 - TM19 from Girl near Berries",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_FRUSTRATION": {
"label": "Pacifidlog Town - TM21 from Man in House",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_RETURN": {
"label": "Fallarbor Town - TM27 from Cozmo",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_RETURN_2": {
"label": "Pacifidlog Town - TM27 from Man in House",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_DIG": {
"label": "Route 114 - TM28 from Fossil Maniac's Brother",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_BRICK_BREAK": {
"label": "Sootopolis City - TM31 from Black Belt in House",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_SHOCK_WAVE": {
"label": "Mauville Gym - TM34 from Wattson",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_SLUDGE_BOMB": {
"label": "Dewford Town - TM36 from Sludge Bomb Man",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_ROCK_TOMB": {
"label": "Rustboro Gym - TM39 from Roxanne",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_AERIAL_ACE": {
"label": "Fortree Gym - TM40 from Winona",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_TORMENT": {
"label": "Slateport City - TM41 from Sailor in Battle Tent",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_FACADE": {
"label": "Petalburg Gym - TM42 from Norman",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_REST": {
"label": "Lilycove City - TM44 from Man in House",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_ATTRACT": {
"label": "Verdanturf Town - TM45 from Woman in Battle Tent",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_THIEF": {
"label": "Oceanic Museum - TM46 from Aqua Grunt in Museum",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_STEEL_WING": {
"label": "Granite Cave 1F - TM47 from Steven",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_SNATCH": {
"label": "SS Tidal - TM49 from Thief",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TM_OVERHEAT": {
"label": "Lavaridge Gym - TM50 from Flannery",
- "tags": ["NpcGift"]
+ "tags": ["Gym TMs"],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_WHITE_HERB": {
"label": "Route 104 - White Herb from Lady Near Flower Shop",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_FLOWER_SHOP_RECEIVED_BERRY": {
"label": "Route 104 - Berry from Girl in Flower Shop",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_DEEP_SEA_SCALE": {
"label": "Slateport City - Deep Sea Scale from Capt. Stern",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_DEEP_SEA_TOOTH": {
"label": "Slateport City - Deep Sea Tooth from Capt. Stern",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_1": {
"label": "Trick House Puzzle 1 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_2": {
"label": "Trick House Puzzle 2 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_3": {
"label": "Trick House Puzzle 3 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_4": {
"label": "Trick House Puzzle 4 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_5": {
"label": "Trick House Puzzle 5 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_6": {
"label": "Trick House Puzzle 6 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_TRICK_HOUSE_REWARD_7": {
"label": "Trick House Puzzle 7 - Reward",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_RECEIVED_FIRST_POKEBALLS": {
"label": "Littleroot Town - Pokeballs from Rival",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_SOOTOPOLIS_RECEIVED_BERRY_1": {
"label": "Sootopolis City - Berry from Girl on Grass 1",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_SOOTOPOLIS_RECEIVED_BERRY_2": {
"label": "Sootopolis City - Berry from Girl on Grass 2",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_ROUTE_111_RECEIVED_BERRY": {
"label": "Route 111 - Berry from Girl Near Berry Trees",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_ROUTE_114_RECEIVED_BERRY": {
"label": "Route 114 - Berry from Man Near House",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_ROUTE_120_RECEIVED_BERRY": {
"label": "Route 120 - Berry from Lady Near Berry Trees",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_BERRY_MASTER_RECEIVED_BERRY_1": {
"label": "Route 123 - Berry from Berry Master 1",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_BERRY_MASTER_RECEIVED_BERRY_2": {
"label": "Route 123 - Berry from Berry Master 2",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_BERRY_MASTERS_WIFE": {
"label": "Route 123 - Berry from Berry Master's Wife",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"NPC_GIFT_LILYCOVE_RECEIVED_BERRY": {
"label": "Lilycove City - Berry from Gentleman Above Ledges",
- "tags": ["NpcGift"]
+ "tags": [],
+ "category": "GIFT"
},
"BERRY_TREE_01": {
"label": "Route 102 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_02": {
"label": "Route 102 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_03": {
"label": "Route 104 - Berry Tree Flower Shop 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_04": {
"label": "Route 104 - Berry Tree Flower Shop 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_05": {
"label": "Route 103 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_06": {
"label": "Route 103 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_07": {
"label": "Route 103 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_08": {
"label": "Route 104 - Berry Tree North 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_09": {
"label": "Route 104 - Berry Tree North 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_10": {
"label": "Route 104 - Berry Tree North 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_11": {
"label": "Route 104 - Berry Tree South 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_12": {
"label": "Route 104 - Berry Tree South 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_13": {
"label": "Route 104 - Berry Tree South 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_14": {
"label": "Route 123 - Berry Tree Berry Master 6",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_15": {
"label": "Route 123 - Berry Tree Berry Master 7",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_16": {
"label": "Route 110 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_17": {
"label": "Route 110 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_18": {
"label": "Route 110 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_19": {
"label": "Route 111 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_20": {
"label": "Route 111 - Berry Tree 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_21": {
"label": "Route 112 - Berry Tree 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_22": {
"label": "Route 112 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_23": {
"label": "Route 112 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_24": {
"label": "Route 112 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_25": {
"label": "Route 116 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_26": {
"label": "Route 116 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_27": {
"label": "Route 117 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_28": {
"label": "Route 117 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_29": {
"label": "Route 117 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_30": {
"label": "Route 123 - Berry Tree Berry Master 8",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_31": {
"label": "Route 118 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_32": {
"label": "Route 118 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_33": {
"label": "Route 118 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_34": {
"label": "Route 119 - Berry Tree North 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_35": {
"label": "Route 119 - Berry Tree North 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_36": {
"label": "Route 119 - Berry Tree North 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_37": {
"label": "Route 120 - Berry Tree in Side Area 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_38": {
"label": "Route 120 - Berry Tree in Side Area 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_39": {
"label": "Route 120 - Berry Tree in Side Area 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_40": {
"label": "Route 120 - Berry Tree South 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_41": {
"label": "Route 120 - Berry Tree South 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_42": {
"label": "Route 120 - Berry Tree South 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_43": {
"label": "Route 120 - Berry Tree Pond 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_44": {
"label": "Route 120 - Berry Tree Pond 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_45": {
"label": "Route 120 - Berry Tree Pond 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_46": {
"label": "Route 120 - Berry Tree Pond 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_47": {
"label": "Route 121 - Berry Tree West 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_48": {
"label": "Route 121 - Berry Tree West 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_49": {
"label": "Route 121 - Berry Tree West 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_50": {
"label": "Route 121 - Berry Tree West 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_51": {
"label": "Route 121 - Berry Tree East 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_52": {
"label": "Route 121 - Berry Tree East 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_53": {
"label": "Route 121 - Berry Tree East 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_54": {
"label": "Route 121 - Berry Tree East 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_55": {
"label": "Route 115 - Berry Tree Behind Smashable Rock 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_56": {
"label": "Route 115 - Berry Tree Behind Smashable Rock 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_57": {
"label": "Route 123 - Berry Tree East 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_58": {
"label": "Route 123 - Berry Tree Berry Master 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_59": {
"label": "Route 123 - Berry Tree Berry Master 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_60": {
"label": "Route 123 - Berry Tree Berry Master 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_61": {
"label": "Route 123 - Berry Tree Berry Master 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_62": {
"label": "Route 123 - Berry Tree East 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_63": {
"label": "Route 123 - Berry Tree East 5",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_64": {
"label": "Route 123 - Berry Tree East 6",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_65": {
"label": "Route 123 - Berry Tree Berry Master 9",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_66": {
"label": "Route 116 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_67": {
"label": "Route 116 - Berry Tree 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_68": {
"label": "Route 114 - Berry Tree 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_69": {
"label": "Route 115 - Berry Tree North 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_70": {
"label": "Route 115 - Berry Tree North 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_71": {
"label": "Route 115 - Berry Tree North 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_72": {
"label": "Route 123 - Berry Tree Berry Master 10",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_73": {
"label": "Route 123 - Berry Tree Berry Master 11",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_74": {
"label": "Route 123 - Berry Tree Berry Master 12",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_75": {
"label": "Route 104 - Berry Tree Flower Shop 3",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_76": {
"label": "Route 104 - Berry Tree Flower Shop 4",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_77": {
"label": "Route 114 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_78": {
"label": "Route 114 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_79": {
"label": "Route 123 - Berry Tree Berry Master 5",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_80": {
"label": "Route 111 - Berry Tree 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_81": {
"label": "Route 111 - Berry Tree 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_82": {
"label": "Route 130 - Berry Tree on Mirage Island",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_83": {
"label": "Route 119 - Berry Tree Above Waterfall 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_84": {
"label": "Route 119 - Berry Tree Above Waterfall 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_85": {
"label": "Route 119 - Berry Tree South 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_86": {
"label": "Route 119 - Berry Tree South 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_87": {
"label": "Route 123 - Berry Tree East 1",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"BERRY_TREE_88": {
"label": "Route 123 - Berry Tree East 2",
- "tags": ["BerryTree"]
+ "tags": [],
+ "category": "BERRY_TREE"
},
"POKEDEX_REWARD_001": {
"label": "Pokedex - Bulbasaur",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_002": {
"label": "Pokedex - Ivysaur",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_003": {
"label": "Pokedex - Venusaur",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_004": {
"label": "Pokedex - Charmander",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_005": {
"label": "Pokedex - Charmeleon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_006": {
"label": "Pokedex - Charizard",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_007": {
"label": "Pokedex - Squirtle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_008": {
"label": "Pokedex - Wartortle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_009": {
"label": "Pokedex - Blastoise",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_010": {
"label": "Pokedex - Caterpie",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_011": {
"label": "Pokedex - Metapod",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_012": {
"label": "Pokedex - Butterfree",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_013": {
"label": "Pokedex - Weedle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_014": {
"label": "Pokedex - Kakuna",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_015": {
"label": "Pokedex - Beedrill",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_016": {
"label": "Pokedex - Pidgey",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_017": {
"label": "Pokedex - Pidgeotto",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_018": {
"label": "Pokedex - Pidgeot",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_019": {
"label": "Pokedex - Rattata",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_020": {
"label": "Pokedex - Raticate",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_021": {
"label": "Pokedex - Spearow",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_022": {
"label": "Pokedex - Fearow",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_023": {
"label": "Pokedex - Ekans",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_024": {
"label": "Pokedex - Arbok",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_025": {
"label": "Pokedex - Pikachu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_026": {
"label": "Pokedex - Raichu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_027": {
"label": "Pokedex - Sandshrew",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_028": {
"label": "Pokedex - Sandslash",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_029": {
"label": "Pokedex - Nidoran Female",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_030": {
"label": "Pokedex - Nidorina",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_031": {
"label": "Pokedex - Nidoqueen",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_032": {
"label": "Pokedex - Nidoran Male",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_033": {
"label": "Pokedex - Nidorino",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_034": {
"label": "Pokedex - Nidoking",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_035": {
"label": "Pokedex - Clefairy",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_036": {
"label": "Pokedex - Clefable",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_037": {
"label": "Pokedex - Vulpix",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_038": {
"label": "Pokedex - Ninetales",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_039": {
"label": "Pokedex - Jigglypuff",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_040": {
"label": "Pokedex - Wigglytuff",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_041": {
"label": "Pokedex - Zubat",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_042": {
"label": "Pokedex - Golbat",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_043": {
"label": "Pokedex - Oddish",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_044": {
"label": "Pokedex - Gloom",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_045": {
"label": "Pokedex - Vileplume",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_046": {
"label": "Pokedex - Paras",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_047": {
"label": "Pokedex - Parasect",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_048": {
"label": "Pokedex - Venonat",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_049": {
"label": "Pokedex - Venomoth",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_050": {
"label": "Pokedex - Diglett",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_051": {
"label": "Pokedex - Dugtrio",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_052": {
"label": "Pokedex - Meowth",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_053": {
"label": "Pokedex - Persian",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_054": {
"label": "Pokedex - Psyduck",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_055": {
"label": "Pokedex - Golduck",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_056": {
"label": "Pokedex - Mankey",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_057": {
"label": "Pokedex - Primeape",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_058": {
"label": "Pokedex - Growlithe",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_059": {
"label": "Pokedex - Arcanine",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_060": {
"label": "Pokedex - Poliwag",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_061": {
"label": "Pokedex - Poliwhirl",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_062": {
"label": "Pokedex - Poliwrath",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_063": {
"label": "Pokedex - Abra",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_064": {
"label": "Pokedex - Kadabra",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_065": {
"label": "Pokedex - Alakazam",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_066": {
"label": "Pokedex - Machop",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_067": {
"label": "Pokedex - Machoke",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_068": {
"label": "Pokedex - Machamp",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_069": {
"label": "Pokedex - Bellsprout",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_070": {
"label": "Pokedex - Weepinbell",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_071": {
"label": "Pokedex - Victreebel",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_072": {
"label": "Pokedex - Tentacool",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_073": {
"label": "Pokedex - Tentacruel",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_074": {
"label": "Pokedex - Geodude",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_075": {
"label": "Pokedex - Graveler",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_076": {
"label": "Pokedex - Golem",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_077": {
"label": "Pokedex - Ponyta",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_078": {
"label": "Pokedex - Rapidash",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_079": {
"label": "Pokedex - Slowpoke",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_080": {
"label": "Pokedex - Slowbro",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_081": {
"label": "Pokedex - Magnemite",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_082": {
"label": "Pokedex - Magneton",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_083": {
"label": "Pokedex - Farfetch'd",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_084": {
"label": "Pokedex - Doduo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_085": {
"label": "Pokedex - Dodrio",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_086": {
"label": "Pokedex - Seel",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_087": {
"label": "Pokedex - Dewgong",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_088": {
"label": "Pokedex - Grimer",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_089": {
"label": "Pokedex - Muk",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_090": {
"label": "Pokedex - Shellder",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_091": {
"label": "Pokedex - Cloyster",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_092": {
"label": "Pokedex - Gastly",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_093": {
"label": "Pokedex - Haunter",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_094": {
"label": "Pokedex - Gengar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_095": {
"label": "Pokedex - Onix",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_096": {
"label": "Pokedex - Drowzee",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_097": {
"label": "Pokedex - Hypno",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_098": {
"label": "Pokedex - Krabby",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_099": {
"label": "Pokedex - Kingler",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_100": {
"label": "Pokedex - Voltorb",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_101": {
"label": "Pokedex - Electrode",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_102": {
"label": "Pokedex - Exeggcute",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_103": {
"label": "Pokedex - Exeggutor",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_104": {
"label": "Pokedex - Cubone",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_105": {
"label": "Pokedex - Marowak",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_106": {
"label": "Pokedex - Hitmonlee",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_107": {
"label": "Pokedex - Hitmonchan",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_108": {
"label": "Pokedex - Lickitung",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_109": {
"label": "Pokedex - Koffing",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_110": {
"label": "Pokedex - Weezing",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_111": {
"label": "Pokedex - Rhyhorn",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_112": {
"label": "Pokedex - Rhydon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_113": {
"label": "Pokedex - Chansey",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_114": {
"label": "Pokedex - Tangela",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_115": {
"label": "Pokedex - Kangaskhan",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_116": {
"label": "Pokedex - Horsea",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_117": {
"label": "Pokedex - Seadra",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_118": {
"label": "Pokedex - Goldeen",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_119": {
"label": "Pokedex - Seaking",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_120": {
"label": "Pokedex - Staryu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_121": {
"label": "Pokedex - Starmie",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_122": {
"label": "Pokedex - Mr. Mime",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_123": {
"label": "Pokedex - Scyther",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_124": {
"label": "Pokedex - Jynx",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_125": {
"label": "Pokedex - Electabuzz",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_126": {
"label": "Pokedex - Magmar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_127": {
"label": "Pokedex - Pinsir",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_128": {
"label": "Pokedex - Tauros",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_129": {
"label": "Pokedex - Magikarp",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_130": {
"label": "Pokedex - Gyarados",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_131": {
"label": "Pokedex - Lapras",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_132": {
"label": "Pokedex - Ditto",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_133": {
"label": "Pokedex - Eevee",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_134": {
"label": "Pokedex - Vaporeon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_135": {
"label": "Pokedex - Jolteon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_136": {
"label": "Pokedex - Flareon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_137": {
"label": "Pokedex - Porygon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_138": {
"label": "Pokedex - Omanyte",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_139": {
"label": "Pokedex - Omastar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_140": {
"label": "Pokedex - Kabuto",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_141": {
"label": "Pokedex - Kabutops",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_142": {
"label": "Pokedex - Aerodactyl",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_143": {
"label": "Pokedex - Snorlax",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_144": {
"label": "Pokedex - Articuno",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_145": {
"label": "Pokedex - Zapdos",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_146": {
"label": "Pokedex - Moltres",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_147": {
"label": "Pokedex - Dratini",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_148": {
"label": "Pokedex - Dragonair",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_149": {
"label": "Pokedex - Dragonite",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_150": {
"label": "Pokedex - Mewtwo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_151": {
"label": "Pokedex - Mew",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_152": {
"label": "Pokedex - Chikorita",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_153": {
"label": "Pokedex - Bayleef",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_154": {
"label": "Pokedex - Meganium",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_155": {
"label": "Pokedex - Cyndaquil",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_156": {
"label": "Pokedex - Quilava",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_157": {
"label": "Pokedex - Typhlosion",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_158": {
"label": "Pokedex - Totodile",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_159": {
"label": "Pokedex - Croconaw",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_160": {
"label": "Pokedex - Feraligatr",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_161": {
"label": "Pokedex - Sentret",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_162": {
"label": "Pokedex - Furret",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_163": {
"label": "Pokedex - Hoothoot",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_164": {
"label": "Pokedex - Noctowl",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_165": {
"label": "Pokedex - Ledyba",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_166": {
"label": "Pokedex - Ledian",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_167": {
"label": "Pokedex - Spinarak",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_168": {
"label": "Pokedex - Ariados",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_169": {
"label": "Pokedex - Crobat",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_170": {
"label": "Pokedex - Chinchou",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_171": {
"label": "Pokedex - Lanturn",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_172": {
"label": "Pokedex - Pichu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_173": {
"label": "Pokedex - Cleffa",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_174": {
"label": "Pokedex - Igglybuff",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_175": {
"label": "Pokedex - Togepi",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_176": {
"label": "Pokedex - Togetic",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_177": {
"label": "Pokedex - Natu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_178": {
"label": "Pokedex - Xatu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_179": {
"label": "Pokedex - Mareep",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_180": {
"label": "Pokedex - Flaaffy",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_181": {
"label": "Pokedex - Ampharos",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_182": {
"label": "Pokedex - Bellossom",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_183": {
"label": "Pokedex - Marill",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_184": {
"label": "Pokedex - Azumarill",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_185": {
"label": "Pokedex - Sudowoodo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_186": {
"label": "Pokedex - Politoed",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_187": {
"label": "Pokedex - Hoppip",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_188": {
"label": "Pokedex - Skiploom",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_189": {
"label": "Pokedex - Jumpluff",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_190": {
"label": "Pokedex - Aipom",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_191": {
"label": "Pokedex - Sunkern",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_192": {
"label": "Pokedex - Sunflora",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_193": {
"label": "Pokedex - Yanma",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_194": {
"label": "Pokedex - Wooper",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_195": {
"label": "Pokedex - Quagsire",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_196": {
"label": "Pokedex - Espeon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_197": {
"label": "Pokedex - Umbreon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_198": {
"label": "Pokedex - Murkrow",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_199": {
"label": "Pokedex - Slowking",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_200": {
"label": "Pokedex - Misdreavus",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_201": {
"label": "Pokedex - Unown",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_202": {
"label": "Pokedex - Wobbuffet",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_203": {
"label": "Pokedex - Girafarig",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_204": {
"label": "Pokedex - Pineco",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_205": {
"label": "Pokedex - Forretress",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_206": {
"label": "Pokedex - Dunsparce",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_207": {
"label": "Pokedex - Gligar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_208": {
"label": "Pokedex - Steelix",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_209": {
"label": "Pokedex - Snubbull",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_210": {
"label": "Pokedex - Granbull",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_211": {
"label": "Pokedex - Qwilfish",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_212": {
"label": "Pokedex - Scizor",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_213": {
"label": "Pokedex - Shuckle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_214": {
"label": "Pokedex - Heracross",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_215": {
"label": "Pokedex - Sneasel",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_216": {
"label": "Pokedex - Teddiursa",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_217": {
"label": "Pokedex - Ursaring",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_218": {
"label": "Pokedex - Slugma",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_219": {
"label": "Pokedex - Magcargo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_220": {
"label": "Pokedex - Swinub",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_221": {
"label": "Pokedex - Piloswine",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_222": {
"label": "Pokedex - Corsola",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_223": {
"label": "Pokedex - Remoraid",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_224": {
"label": "Pokedex - Octillery",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_225": {
"label": "Pokedex - Delibird",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_226": {
"label": "Pokedex - Mantine",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_227": {
"label": "Pokedex - Skarmory",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_228": {
"label": "Pokedex - Houndour",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_229": {
"label": "Pokedex - Houndoom",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_230": {
"label": "Pokedex - Kingdra",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_231": {
"label": "Pokedex - Phanpy",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_232": {
"label": "Pokedex - Donphan",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_233": {
"label": "Pokedex - Porygon2",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_234": {
"label": "Pokedex - Stantler",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_235": {
"label": "Pokedex - Smeargle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_236": {
"label": "Pokedex - Tyrogue",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_237": {
"label": "Pokedex - Hitmontop",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_238": {
"label": "Pokedex - Smoochum",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_239": {
"label": "Pokedex - Elekid",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_240": {
"label": "Pokedex - Magby",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_241": {
"label": "Pokedex - Miltank",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_242": {
"label": "Pokedex - Blissey",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_243": {
"label": "Pokedex - Raikou",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_244": {
"label": "Pokedex - Entei",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_245": {
"label": "Pokedex - Suicune",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_246": {
"label": "Pokedex - Larvitar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_247": {
"label": "Pokedex - Pupitar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_248": {
"label": "Pokedex - Tyranitar",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_249": {
"label": "Pokedex - Lugia",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_250": {
"label": "Pokedex - Ho-Oh",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_251": {
"label": "Pokedex - Celebi",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_252": {
"label": "Pokedex - Treecko",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_253": {
"label": "Pokedex - Grovyle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_254": {
"label": "Pokedex - Sceptile",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_255": {
"label": "Pokedex - Torchic",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_256": {
"label": "Pokedex - Combusken",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_257": {
"label": "Pokedex - Blaziken",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_258": {
"label": "Pokedex - Mudkip",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_259": {
"label": "Pokedex - Marshtomp",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_260": {
"label": "Pokedex - Swampert",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_261": {
"label": "Pokedex - Poochyena",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_262": {
"label": "Pokedex - Mightyena",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_263": {
"label": "Pokedex - Zigzagoon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_264": {
"label": "Pokedex - Linoone",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_265": {
"label": "Pokedex - Wurmple",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_266": {
"label": "Pokedex - Silcoon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_267": {
"label": "Pokedex - Beautifly",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_268": {
"label": "Pokedex - Cascoon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_269": {
"label": "Pokedex - Dustox",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_270": {
"label": "Pokedex - Lotad",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_271": {
"label": "Pokedex - Lombre",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_272": {
"label": "Pokedex - Ludicolo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_273": {
"label": "Pokedex - Seedot",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_274": {
"label": "Pokedex - Nuzleaf",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_275": {
"label": "Pokedex - Shiftry",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_276": {
"label": "Pokedex - Taillow",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_277": {
"label": "Pokedex - Swellow",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_278": {
"label": "Pokedex - Wingull",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_279": {
"label": "Pokedex - Pelipper",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_280": {
"label": "Pokedex - Ralts",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_281": {
"label": "Pokedex - Kirlia",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_282": {
"label": "Pokedex - Gardevoir",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_283": {
"label": "Pokedex - Surskit",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_284": {
"label": "Pokedex - Masquerain",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_285": {
"label": "Pokedex - Shroomish",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_286": {
"label": "Pokedex - Breloom",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_287": {
"label": "Pokedex - Slakoth",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_288": {
"label": "Pokedex - Vigoroth",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_289": {
"label": "Pokedex - Slaking",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_290": {
"label": "Pokedex - Nincada",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_291": {
"label": "Pokedex - Ninjask",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_292": {
"label": "Pokedex - Shedinja",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_293": {
"label": "Pokedex - Whismur",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_294": {
"label": "Pokedex - Loudred",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_295": {
"label": "Pokedex - Exploud",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_296": {
"label": "Pokedex - Makuhita",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_297": {
"label": "Pokedex - Hariyama",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_298": {
"label": "Pokedex - Azurill",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_299": {
"label": "Pokedex - Nosepass",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_300": {
"label": "Pokedex - Skitty",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_301": {
"label": "Pokedex - Delcatty",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_302": {
"label": "Pokedex - Sableye",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_303": {
"label": "Pokedex - Mawile",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_304": {
"label": "Pokedex - Aron",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_305": {
"label": "Pokedex - Lairon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_306": {
"label": "Pokedex - Aggron",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_307": {
"label": "Pokedex - Meditite",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_308": {
"label": "Pokedex - Medicham",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_309": {
"label": "Pokedex - Electrike",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_310": {
"label": "Pokedex - Manectric",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_311": {
"label": "Pokedex - Plusle",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_312": {
"label": "Pokedex - Minun",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_313": {
"label": "Pokedex - Volbeat",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_314": {
"label": "Pokedex - Illumise",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_315": {
"label": "Pokedex - Roselia",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_316": {
"label": "Pokedex - Gulpin",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_317": {
"label": "Pokedex - Swalot",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_318": {
"label": "Pokedex - Carvanha",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_319": {
"label": "Pokedex - Sharpedo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_320": {
"label": "Pokedex - Wailmer",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_321": {
"label": "Pokedex - Wailord",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_322": {
"label": "Pokedex - Numel",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_323": {
"label": "Pokedex - Camerupt",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_324": {
"label": "Pokedex - Torkoal",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_325": {
"label": "Pokedex - Spoink",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_326": {
"label": "Pokedex - Grumpig",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_327": {
"label": "Pokedex - Spinda",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_328": {
"label": "Pokedex - Trapinch",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_329": {
"label": "Pokedex - Vibrava",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_330": {
"label": "Pokedex - Flygon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_331": {
"label": "Pokedex - Cacnea",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_332": {
"label": "Pokedex - Cacturne",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_333": {
"label": "Pokedex - Swablu",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_334": {
"label": "Pokedex - Altaria",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_335": {
"label": "Pokedex - Zangoose",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_336": {
"label": "Pokedex - Seviper",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_337": {
"label": "Pokedex - Lunatone",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_338": {
"label": "Pokedex - Solrock",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_339": {
"label": "Pokedex - Barboach",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_340": {
"label": "Pokedex - Whiscash",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_341": {
"label": "Pokedex - Corphish",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_342": {
"label": "Pokedex - Crawdaunt",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_343": {
"label": "Pokedex - Baltoy",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_344": {
"label": "Pokedex - Claydol",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_345": {
"label": "Pokedex - Lileep",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_346": {
"label": "Pokedex - Cradily",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_347": {
"label": "Pokedex - Anorith",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_348": {
"label": "Pokedex - Armaldo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_349": {
"label": "Pokedex - Feebas",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_350": {
"label": "Pokedex - Milotic",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_351": {
"label": "Pokedex - Castform",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_352": {
"label": "Pokedex - Kecleon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_353": {
"label": "Pokedex - Shuppet",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_354": {
"label": "Pokedex - Banette",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_355": {
"label": "Pokedex - Duskull",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_356": {
"label": "Pokedex - Dusclops",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_357": {
"label": "Pokedex - Tropius",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_358": {
"label": "Pokedex - Chimecho",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_359": {
"label": "Pokedex - Absol",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_360": {
"label": "Pokedex - Wynaut",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_361": {
"label": "Pokedex - Snorunt",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_362": {
"label": "Pokedex - Glalie",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_363": {
"label": "Pokedex - Spheal",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_364": {
"label": "Pokedex - Sealeo",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_365": {
"label": "Pokedex - Walrein",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_366": {
"label": "Pokedex - Clamperl",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_367": {
"label": "Pokedex - Huntail",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_368": {
"label": "Pokedex - Gorebyss",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_369": {
"label": "Pokedex - Relicanth",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_370": {
"label": "Pokedex - Luvdisc",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_371": {
"label": "Pokedex - Bagon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_372": {
"label": "Pokedex - Shelgon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_373": {
"label": "Pokedex - Salamence",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_374": {
"label": "Pokedex - Beldum",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_375": {
"label": "Pokedex - Metang",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_376": {
"label": "Pokedex - Metagross",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_377": {
"label": "Pokedex - Regirock",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_378": {
"label": "Pokedex - Regice",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_379": {
"label": "Pokedex - Registeel",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_380": {
"label": "Pokedex - Latias",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_381": {
"label": "Pokedex - Latios",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_382": {
"label": "Pokedex - Kyogre",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_383": {
"label": "Pokedex - Groudon",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_384": {
"label": "Pokedex - Rayquaza",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_385": {
"label": "Pokedex - Jirachi",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"POKEDEX_REWARD_386": {
"label": "Pokedex - Deoxys",
- "tags": ["Pokedex"]
+ "tags": [],
+ "category": "POKEDEX"
},
"TRAINER_AARON_REWARD": {
"label": "Route 134 - Dragon Tamer Aaron",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ABIGAIL_1_REWARD": {
"label": "Route 110 - Triathlete Abigail",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AIDAN_REWARD": {
"label": "Route 127 - Bird Keeper Aidan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AISHA_REWARD": {
"label": "Route 117 - Battle Girl Aisha",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALBERTO_REWARD": {
"label": "Route 123 - Bird Keeper Alberto",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALBERT_REWARD": {
"label": "Victory Road 1F - Cooltrainer Albert",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALEXA_REWARD": {
"label": "Route 128 - Cooltrainer Alexa",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALEXIA_REWARD": {
"label": "Petalburg Gym - Cooltrainer Alexia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALEX_REWARD": {
"label": "Route 134 - Bird Keeper Alex",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALICE_REWARD": {
"label": "Route 109 - Swimmer Alice",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALIX_REWARD": {
"label": "Route 115 - Psychic Alix",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALLEN_REWARD": {
"label": "Route 102 - Youngster Allen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALLISON_REWARD": {
"label": "Route 129 - Triathlete Allison",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ALYSSA_REWARD": {
"label": "Route 110 - Triathlete Alyssa",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AMY_AND_LIV_1_REWARD": {
"label": "Route 103 - Twins Amy and Liv",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANNA_AND_MEG_1_REWARD": {
"label": "Route 117 - Sr. and Jr. Anna and Meg",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANDREA_REWARD": {
"label": "Sootopolis Gym - Lass Andrea",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANDRES_1_REWARD": {
"label": "Route 105 - Ruin Maniac Andres",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANDREW_REWARD": {
"label": "Route 103 - Fisherman Andrew",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANGELICA_REWARD": {
"label": "Route 120 - Parasol Lady Angelica",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANGELINA_REWARD": {
"label": "Route 114 - Picnicker Angelina",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANGELO_REWARD": {
"label": "Mauville Gym - Bug Maniac Angelo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANNIKA_REWARD": {
"label": "Sootopolis Gym - Pokefan Annika",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ANTHONY_REWARD": {
"label": "Route 110 - Triathlete Anthony",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ARCHIE_REWARD": {
"label": "Seafloor Cavern Room 9 - Aqua Leader Archie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ASHLEY_REWARD": {
"label": "Fortree Gym - Picnicker Ashley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ATHENA_REWARD": {
"label": "Route 127 - Cooltrainer Athena",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ATSUSHI_REWARD": {
"label": "Mt Pyre 5F - Black Belt Atsushi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AURON_REWARD": {
"label": "Route 125 - Expert Auron",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AUSTINA_REWARD": {
"label": "Route 109 - Tuber Austina",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AUTUMN_REWARD": {
"label": "Jagged Pass - Picnicker Autumn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_AXLE_REWARD": {
"label": "Lavaridge Gym - Kindler Axle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BARNY_REWARD": {
"label": "Route 118 - Fisherman Barny",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BARRY_REWARD": {
"label": "Route 126 - Swimmer Barry",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BEAU_REWARD": {
"label": "Route 111 - Camper Beau",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BECKY_REWARD": {
"label": "Route 111 - Picnicker Becky",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BECK_REWARD": {
"label": "Route 133 - Bird Keeper Beck",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BENJAMIN_1_REWARD": {
"label": "Route 110 - Triathlete Benjamin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BEN_REWARD": {
"label": "Mauville Gym - Youngster Ben",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BERKE_REWARD": {
"label": "Petalburg Gym - Cooltrainer Berke",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BERNIE_1_REWARD": {
"label": "Route 114 - Kindler Bernie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BETHANY_REWARD": {
"label": "Sootopolis Gym - Pokefan Bethany",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BETH_REWARD": {
"label": "Route 107 - Swimmer Beth",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BEVERLY_REWARD": {
"label": "Route 105 - Swimmer Beverly",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BIANCA_REWARD": {
"label": "Route 111 - Picnicker Bianca",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BILLY_REWARD": {
"label": "Route 104 - Youngster Billy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BLAKE_REWARD": {
"label": "Mossdeep Gym - Psychic Blake",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRANDEN_REWARD": {
"label": "Route 111 - Camper Branden",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRANDI_REWARD": {
"label": "Route 117 - Psychic Brandi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRAWLY_1_REWARD": {
"label": "Dewford Gym - Leader Brawly",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRAXTON_REWARD": {
"label": "Route 123 - Cooltrainer Braxton",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDAN_LILYCOVE_MUDKIP_REWARD": {
"label": "Lilycove City - Rival Brendan/May",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDAN_ROUTE_103_MUDKIP_REWARD": {
"label": "Route 103 - Rival Brendan/May",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDAN_ROUTE_110_MUDKIP_REWARD": {
"label": "Route 110 - Rival Brendan/May",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDAN_ROUTE_119_MUDKIP_REWARD": {
"label": "Route 119 - Rival Brendan/May",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDAN_RUSTBORO_MUDKIP_REWARD": {
"label": "Rustboro City - Rival Brendan/May",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDA_REWARD": {
"label": "Route 126 - Swimmer Brenda",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENDEN_REWARD": {
"label": "Dewford Gym - Sailor Brenden",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRENT_REWARD": {
"label": "Route 119 - Bug Maniac Brent",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRIANNA_REWARD": {
"label": "Sootopolis Gym - Lady Brianna",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRICE_REWARD": {
"label": "Route 112 - Hiker Brice",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRIDGET_REWARD": {
"label": "Sootopolis Gym - Beauty Bridget",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BROOKE_1_REWARD": {
"label": "Route 111 - Cooltrainer Brooke",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRYANT_REWARD": {
"label": "Route 112 - Kindler Bryant",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_BRYAN_REWARD": {
"label": "Route 111 - Ruin Maniac Bryan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CALE_REWARD": {
"label": "Route 121 - Bug Maniac Cale",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CALLIE_REWARD": {
"label": "Route 120 - Battle Girl Callie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CALVIN_1_REWARD": {
"label": "Route 102 - Youngster Calvin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CAMDEN_REWARD": {
"label": "Route 127 - Triathlete Camden",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CAMERON_1_REWARD": {
"label": "Route 123 - Psychic Cameron",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CAMRON_REWARD": {
"label": "Route 107 - Triathlete Camron",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CARLEE_REWARD": {
"label": "Route 128 - Swimmer Carlee",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CAROLINA_REWARD": {
"label": "Route 108 - Cooltrainer Carolina",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CAROLINE_REWARD": {
"label": "Victory Road B2F - Cooltrainer Caroline",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CAROL_REWARD": {
"label": "Route 112 - Picnicker Carol",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CARTER_REWARD": {
"label": "Route 109 - Fisherman Carter",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CATHERINE_1_REWARD": {
"label": "Route 119 - Pokemon Ranger Catherine",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CEDRIC_REWARD": {
"label": "Mt Pyre 6F - Psychic Cedric",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CELIA_REWARD": {
"label": "Route 111 - Picnicker Celia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CELINA_REWARD": {
"label": "Route 111 - Aroma Lady Celina",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHAD_REWARD": {
"label": "Route 124 - Swimmer Chad",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHANDLER_REWARD": {
"label": "Route 109 - Tuber Chandler",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHARLIE_REWARD": {
"label": "Abandoned Ship 1F - Tuber Charlie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHARLOTTE_REWARD": {
"label": "Route 114 - Picnicker Charlotte",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHASE_REWARD": {
"label": "Route 129 - Triathlete Chase",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHESTER_REWARD": {
"label": "Route 118 - Bird Keeper Chester",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHIP_REWARD": {
"label": "Route 120 - Ruin Maniac Chip",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CHRIS_REWARD": {
"label": "Route 119 - Fisherman Chris",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CINDY_1_REWARD": {
"label": "Route 104 - Lady Cindy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CLARENCE_REWARD": {
"label": "Route 129 - Swimmer Clarence",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CLARISSA_REWARD": {
"label": "Route 120 - Parasol Lady Clarissa",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CLARK_REWARD": {
"label": "Route 116 - Hiker Clark",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CLAUDE_REWARD": {
"label": "Route 114 - Fisherman Claude",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CLIFFORD_REWARD": {
"label": "Mossdeep Gym - Gentleman Clifford",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_COBY_REWARD": {
"label": "Route 113 - Bird Keeper Coby",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_COLE_REWARD": {
"label": "Lavaridge Gym - Kindler Cole",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_COLIN_REWARD": {
"label": "Route 120 - Bird Keeper Colin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_COLTON_REWARD": {
"label": "SS Tidal - Pokefan Colton",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CONNIE_REWARD": {
"label": "Sootopolis Gym - Beauty Connie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CONOR_REWARD": {
"label": "Route 133 - Expert Conor",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CORY_1_REWARD": {
"label": "Route 108 - Sailor Cory",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CRISSY_REWARD": {
"label": "Sootopolis Gym - Lass Crissy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CRISTIAN_REWARD": {
"label": "Dewford Gym - Black Belt Cristian",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CRISTIN_1_REWARD": {
"label": "Route 121 - Cooltrainer Cristin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_CYNDY_1_REWARD": {
"label": "Route 115 - Battle Girl Cyndy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAISUKE_REWARD": {
"label": "Route 111 - Black Belt Daisuke",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAISY_REWARD": {
"label": "Route 103 - Aroma Lady Daisy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DALE_REWARD": {
"label": "Route 110 - Fisherman Dale",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DALTON_1_REWARD": {
"label": "Route 118 - Guitarist Dalton",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DANA_REWARD": {
"label": "Route 132 - Swimmer Dana",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DANIELLE_REWARD": {
"label": "Lavaridge Gym - Battle Girl Danielle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAPHNE_REWARD": {
"label": "Sootopolis Gym - Lady Daphne",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DARCY_REWARD": {
"label": "Route 132 - Cooltrainer Darcy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DARIAN_REWARD": {
"label": "Route 104 - Fisherman Darian",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DARIUS_REWARD": {
"label": "Fortree Gym - Bird Keeper Darius",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DARRIN_REWARD": {
"label": "Route 107 - Swimmer Darrin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAVID_REWARD": {
"label": "Route 109 - Swimmer David",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAVIS_REWARD": {
"label": "Route 123 - Bug Catcher Davis",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAWSON_REWARD": {
"label": "Route 116 - Rich Boy Dawson",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DAYTON_REWARD": {
"label": "Route 119 - Kindler Dayton",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEANDRE_REWARD": {
"label": "Route 118 - Youngster Deandre",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEAN_REWARD": {
"label": "Route 126 - Swimmer Dean",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEBRA_REWARD": {
"label": "Route 133 - Swimmer Debra",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DECLAN_REWARD": {
"label": "Route 124 - Swimmer Declan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEMETRIUS_REWARD": {
"label": "Abandoned Ship 1F - Youngster Demetrius",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DENISE_REWARD": {
"label": "Route 107 - Swimmer Denise",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEREK_REWARD": {
"label": "Route 117 - Bug Maniac Derek",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEVAN_REWARD": {
"label": "Route 116 - Hiker Devan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DIANA_1_REWARD": {
"label": "Jagged Pass - Picnicker Diana",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DIANNE_REWARD": {
"label": "Victory Road B2F - Cooltrainer Dianne",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DILLON_REWARD": {
"label": "Route 113 - Youngster Dillon",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DOMINIK_REWARD": {
"label": "Route 105 - Swimmer Dominik",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DONALD_REWARD": {
"label": "Route 119 - Bug Maniac Donald",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DONNY_REWARD": {
"label": "Route 127 - Triathlete Donny",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DOUGLAS_REWARD": {
"label": "Route 106 - Swimmer Douglas",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DOUG_REWARD": {
"label": "Route 119 - Bug Catcher Doug",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DRAKE_REWARD": {
"label": "Ever Grande City - Elite Four Drake",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DREW_REWARD": {
"label": "Route 111 - Camper Drew",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DUNCAN_REWARD": {
"label": "Abandoned Ship B1F - Sailor Duncan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DUSTY_1_REWARD": {
"label": "Route 111 - Ruin Maniac Dusty",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DWAYNE_REWARD": {
"label": "Route 109 - Sailor Dwayne",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DYLAN_1_REWARD": {
"label": "Route 117 - Triathlete Dylan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_DEZ_AND_LUKE_REWARD": {
"label": "Mt Pyre 2F - Young Couple Dez and Luke",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_EDGAR_REWARD": {
"label": "Victory Road 1F - Cooltrainer Edgar",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_EDMOND_REWARD": {
"label": "Route 109 - Sailor Edmond",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_EDWARDO_REWARD": {
"label": "Fortree Gym - Bird Keeper Edwardo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_EDWARD_REWARD": {
"label": "Route 110 - Psychic Edward",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_EDWIN_1_REWARD": {
"label": "Route 110 - Collector Edwin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ED_REWARD": {
"label": "Route 123 - Collector Ed",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ELIJAH_REWARD": {
"label": "Route 109 - Bird Keeper Elijah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ELI_REWARD": {
"label": "Lavaridge Gym - Hiker Eli",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ELLIOT_1_REWARD": {
"label": "Route 106 - Fisherman Elliot",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ERIC_REWARD": {
"label": "Jagged Pass - Hiker Eric",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ERNEST_1_REWARD": {
"label": "Route 125 - Sailor Ernest",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ETHAN_1_REWARD": {
"label": "Jagged Pass - Camper Ethan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FABIAN_REWARD": {
"label": "Route 119 - Guitarist Fabian",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FELIX_REWARD": {
"label": "Victory Road B2F - Cooltrainer Felix",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FERNANDO_1_REWARD": {
"label": "Route 123 - Guitarist Fernando",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FLANNERY_1_REWARD": {
"label": "Lavaridge Gym - Leader Flannery",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FLINT_REWARD": {
"label": "Fortree Gym - Camper Flint",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FOSTER_REWARD": {
"label": "Route 105 - Ruin Maniac Foster",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FRANKLIN_REWARD": {
"label": "Route 133 - Swimmer Franklin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_FREDRICK_REWARD": {
"label": "Route 123 - Expert Fredrick",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GABRIELLE_1_REWARD": {
"label": "Mt Pyre 3F - Pokemon Breeder Gabrielle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GARRET_REWARD": {
"label": "SS Tidal - Rich Boy Garret",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GARRISON_REWARD": {
"label": "Abandoned Ship 1F - Ruin Maniac Garrison",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GEORGE_REWARD": {
"label": "Petalburg Gym - Cooltrainer George",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GERALD_REWARD": {
"label": "Lavaridge Gym - Cooltrainer Gerald",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GILBERT_REWARD": {
"label": "Route 132 - Swimmer Gilbert",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GINA_AND_MIA_1_REWARD": {
"label": "Route 104 - Twins Gina and Mia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GLACIA_REWARD": {
"label": "Ever Grande City - Elite Four Glacia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRACE_REWARD": {
"label": "Route 124 - Swimmer Grace",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GREG_REWARD": {
"label": "Route 119 - Bug Catcher Greg",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_1_REWARD": {
"label": "Aqua Hideout 1F - Team Aqua Grunt",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_2_REWARD": {
"label": "Aqua Hideout B1F - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_3_REWARD": {
"label": "Aqua Hideout B1F - Team Aqua Grunt 4",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_4_REWARD": {
"label": "Aqua Hideout B2F - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_5_REWARD": {
"label": "Aqua Hideout B1F - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_6_REWARD": {
"label": "Aqua Hideout B2F - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_7_REWARD": {
"label": "Aqua Hideout B1F - Team Aqua Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_AQUA_HIDEOUT_8_REWARD": {
"label": "Aqua Hideout B2F - Team Aqua Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_10_REWARD": {
"label": "Magma Hideout 3F - Team Magma Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_11_REWARD": {
"label": "Magma Hideout 4F - Team Magma Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_12_REWARD": {
"label": "Magma Hideout 4F - Team Magma Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_13_REWARD": {
"label": "Magma Hideout 4F - Team Magma Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_14_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_15_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 5",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_16_REWARD": {
"label": "Magma Hideout 3F - Team Magma Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_1_REWARD": {
"label": "Magma Hideout 1F - Team Magma Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_2_REWARD": {
"label": "Magma Hideout 1F - Team Magma Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_3_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_4_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 4",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_5_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_6_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 6",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_7_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 7",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_8_REWARD": {
"label": "Magma Hideout 2F - Team Magma Grunt 8",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MAGMA_HIDEOUT_9_REWARD": {
"label": "Magma Hideout 3F - Team Magma Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MT_CHIMNEY_1_REWARD": {
"label": "Mt Chimney - Team Magma Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MT_CHIMNEY_2_REWARD": {
"label": "Mt Chimney - Team Magma Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MT_PYRE_1_REWARD": {
"label": "Mt Pyre Summit - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MT_PYRE_2_REWARD": {
"label": "Mt Pyre Summit - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MT_PYRE_3_REWARD": {
"label": "Mt Pyre Summit - Team Aqua Grunt 4",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MT_PYRE_4_REWARD": {
"label": "Mt Pyre Summit - Team Aqua Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MUSEUM_1_REWARD": {
"label": "Oceanic Museum - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_MUSEUM_2_REWARD": {
"label": "Oceanic Museum - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_PETALBURG_WOODS_REWARD": {
"label": "Petalburg Woods - Team Aqua Grunt",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_RUSTURF_TUNNEL_REWARD": {
"label": "Rusturf Tunnel - Team Aqua Grunt",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SEAFLOOR_CAVERN_1_REWARD": {
"label": "Seafloor Cavern Room 1 - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SEAFLOOR_CAVERN_2_REWARD": {
"label": "Seafloor Cavern Room 1 - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SEAFLOOR_CAVERN_3_REWARD": {
"label": "Seafloor Cavern Room 4 - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SEAFLOOR_CAVERN_4_REWARD": {
"label": "Seafloor Cavern Room 4 - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SEAFLOOR_CAVERN_5_REWARD": {
"label": "Seafloor Cavern Room 3 - Team Aqua Grunt",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_1_REWARD": {
"label": "Space Center - Team Magma Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_2_REWARD": {
"label": "Space Center - Team Magma Grunt 4",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_3_REWARD": {
"label": "Space Center - Team Magma Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_4_REWARD": {
"label": "Space Center - Team Magma Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_5_REWARD": {
"label": "Space Center - Team Magma Grunt 5",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_6_REWARD": {
"label": "Space Center - Team Magma Grunt 6",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_SPACE_CENTER_7_REWARD": {
"label": "Space Center - Team Magma Grunt 7",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_WEATHER_INST_1_REWARD": {
"label": "Weather Institute 1F - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_WEATHER_INST_2_REWARD": {
"label": "Weather Institute 2F - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_WEATHER_INST_3_REWARD": {
"label": "Weather Institute 2F - Team Aqua Grunt 3",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_WEATHER_INST_4_REWARD": {
"label": "Weather Institute 1F - Team Aqua Grunt 1",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GRUNT_WEATHER_INST_5_REWARD": {
"label": "Weather Institute 2F - Team Aqua Grunt 2",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_GWEN_REWARD": {
"label": "Route 109 - Tuber Gwen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HAILEY_REWARD": {
"label": "Route 109 - Tuber Hailey",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HALEY_1_REWARD": {
"label": "Route 104 - Lass Haley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HALLE_REWARD": {
"label": "Victory Road B1F - Cooltrainer Halle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HANNAH_REWARD": {
"label": "Mossdeep Gym - Psychic Hannah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HARRISON_REWARD": {
"label": "Route 128 - Swimmer Harrison",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HAYDEN_REWARD": {
"label": "Route 111 - Kindler Hayden",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HECTOR_REWARD": {
"label": "Route 115 - Collector Hector",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HEIDI_REWARD": {
"label": "Route 111 - Picnicker Heidi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HELENE_REWARD": {
"label": "Route 115 - Battle Girl Helene",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HENRY_REWARD": {
"label": "Route 127 - Fisherman Henry",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HERMAN_REWARD": {
"label": "Route 131 - Swimmer Herman",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HIDEO_REWARD": {
"label": "Route 119 - Ninja Boy Hideo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HITOSHI_REWARD": {
"label": "Route 134 - Black Belt Hitoshi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HOPE_REWARD": {
"label": "Victory Road 1F - Cooltrainer Hope",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HUDSON_REWARD": {
"label": "Route 134 - Sailor Hudson",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HUEY_REWARD": {
"label": "Route 109 - Sailor Huey",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HUGH_REWARD": {
"label": "Route 119 - Bird Keeper Hugh",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_HUMBERTO_REWARD": {
"label": "Fortree Gym - Bird Keeper Humberto",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_IMANI_REWARD": {
"label": "Route 105 - Swimmer Imani",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_IRENE_REWARD": {
"label": "Route 111 - Picnicker Irene",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ISAAC_1_REWARD": {
"label": "Route 117 - Pokemon Breeder Isaac",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ISABELLA_REWARD": {
"label": "Route 124 - Triathlete Isabella",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ISABELLE_REWARD": {
"label": "Route 103 - Swimmer Isabelle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ISABEL_1_REWARD": {
"label": "Route 110 - Pokefan Isabel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ISAIAH_1_REWARD": {
"label": "Route 128 - Triathlete Isaiah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ISOBEL_REWARD": {
"label": "Route 126 - Triathlete Isobel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_IVAN_REWARD": {
"label": "Route 104 - Fisherman Ivan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JACE_REWARD": {
"label": "Lavaridge Gym - Kindler Jace",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JACKI_1_REWARD": {
"label": "Route 123 - Psychic Jacki",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JACKSON_1_REWARD": {
"label": "Route 119 - Pokemon Ranger Jackson",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JACK_REWARD": {
"label": "Route 134 - Swimmer Jack",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JACLYN_REWARD": {
"label": "Route 110 - Psychic Jaclyn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JACOB_REWARD": {
"label": "Route 110 - Triathlete Jacob",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JAIDEN_REWARD": {
"label": "Route 115 - Ninja Boy Jaiden",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JAMES_1_REWARD": {
"label": "Petalburg Woods - Bug Catcher James",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JANICE_REWARD": {
"label": "Route 116 - Lass Janice",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JANI_REWARD": {
"label": "Abandoned Ship 1F - Tuber Jani",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JARED_REWARD": {
"label": "Fortree Gym - Bird Keeper Jared",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JASMINE_REWARD": {
"label": "Route 110 - Triathlete Jasmine",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JAYLEN_REWARD": {
"label": "Route 113 - Youngster Jaylen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JAZMYN_REWARD": {
"label": "Route 123 - Cooltrainer Jazmyn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JEFFREY_1_REWARD": {
"label": "Route 120 - Bug Maniac Jeffrey",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JEFF_REWARD": {
"label": "Lavaridge Gym - Kindler Jeff",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JENNA_REWARD": {
"label": "Route 120 - Pokemon Ranger Jenna",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JENNIFER_REWARD": {
"label": "Route 120 - Cooltrainer Jennifer",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JENNY_1_REWARD": {
"label": "Route 124 - Swimmer Jenny",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JEROME_REWARD": {
"label": "Route 108 - Swimmer Jerome",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JERRY_1_REWARD": {
"label": "Route 116 - School Kid Jerry",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JESSICA_1_REWARD": {
"label": "Route 121 - Beauty Jessica",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOCELYN_REWARD": {
"label": "Dewford Gym - Battle Girl Jocelyn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JODY_REWARD": {
"label": "Petalburg Gym - Cooltrainer Jody",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOEY_REWARD": {
"label": "Route 116 - Youngster Joey",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOHANNA_REWARD": {
"label": "Route 109 - Beauty Johanna",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOHN_AND_JAY_1_REWARD": {
"label": "Meteor Falls 1F - Old Couple John and Jay",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOHNSON_REWARD": {
"label": "Route 116 - Youngster Johnson",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JONAH_REWARD": {
"label": "Route 127 - Fisherman Jonah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JONAS_REWARD": {
"label": "Route 123 - Ninja Boy Jonas",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JONATHAN_REWARD": {
"label": "Route 132 - Cooltrainer Jonathan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOSEPH_REWARD": {
"label": "Route 110 - Guitarist Joseph",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOSE_REWARD": {
"label": "Route 116 - Bug Catcher Jose",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOSH_REWARD": {
"label": "Rustboro Gym - Youngster Josh",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JOSUE_REWARD": {
"label": "Route 105 - Bird Keeper Josue",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JUAN_1_REWARD": {
"label": "Sootopolis Gym - Leader Juan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JULIE_REWARD": {
"label": "Victory Road B2F - Cooltrainer Julie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_JULIO_REWARD": {
"label": "Jagged Pass - Triathlete Julio",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KAI_REWARD": {
"label": "Route 114 - Fisherman Kai",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KALEB_REWARD": {
"label": "Route 110 - Pokefan Kaleb",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KARA_REWARD": {
"label": "Route 131 - Swimmer Kara",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KAREN_1_REWARD": {
"label": "Route 116 - School Kid Karen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KATE_AND_JOY_REWARD": {
"label": "Route 121 - Sr. and Jr. Kate and Joy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KATELYNN_REWARD": {
"label": "Victory Road 1F - Cooltrainer Katelynn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KATELYN_1_REWARD": {
"label": "Route 128 - Triathlete Katelyn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KATHLEEN_REWARD": {
"label": "Mossdeep Gym - Hex Maniac Kathleen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KATIE_REWARD": {
"label": "Route 130 - Swimmer Katie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KAYLA_REWARD": {
"label": "Mt Pyre 3F - Psychic Kayla",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KAYLEY_REWARD": {
"label": "Route 123 - Parasol Lady Kayley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KEEGAN_REWARD": {
"label": "Lavaridge Gym - Kindler Keegan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KEIGO_REWARD": {
"label": "Route 120 - Ninja Boy Keigo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KELVIN_REWARD": {
"label": "Route 134 - Sailor Kelvin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KENT_REWARD": {
"label": "Route 119 - Bug Catcher Kent",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KEVIN_REWARD": {
"label": "Route 131 - Swimmer Kevin",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KIM_AND_IRIS_REWARD": {
"label": "Route 125 - Sr. and Jr. Kim and Iris",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KINDRA_REWARD": {
"label": "Route 123 - Hex Maniac Kindra",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KIRA_AND_DAN_1_REWARD": {
"label": "Abandoned Ship 1F - Young Couple Kira and Dan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KIRK_REWARD": {
"label": "Mauville Gym - Guitarist Kirk",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KIYO_REWARD": {
"label": "Route 132 - Black Belt Kiyo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KOICHI_REWARD": {
"label": "Route 115 - Black Belt Koichi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KOJI_1_REWARD": {
"label": "Route 127 - Black Belt Koji",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KYLA_REWARD": {
"label": "Route 106 - Swimmer Kyla",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_KYRA_REWARD": {
"label": "Route 115 - Triathlete Kyra",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LAO_1_REWARD": {
"label": "Route 113 - Ninja Boy Lao",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LARRY_REWARD": {
"label": "Route 112 - Camper Larry",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LAURA_REWARD": {
"label": "Dewford Gym - Battle Girl Laura",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LAUREL_REWARD": {
"label": "Route 134 - Swimmer Laurel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LAWRENCE_REWARD": {
"label": "Route 113 - Camper Lawrence",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LEA_AND_JED_REWARD": {
"label": "SS Tidal - Young Couple Lea and Jed",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LEAH_REWARD": {
"label": "Mt Pyre 2F - Hex Maniac Leah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LENNY_REWARD": {
"label": "Route 114 - Hiker Lenny",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LEONARDO_REWARD": {
"label": "Route 126 - Swimmer Leonardo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LEONARD_REWARD": {
"label": "SS Tidal - Sailor Leonard",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LEONEL_REWARD": {
"label": "Route 120 - Cooltrainer Leonel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LILA_AND_ROY_1_REWARD": {
"label": "Route 124 - Sis and Bro Lila and Roy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LILITH_REWARD": {
"label": "Dewford Gym - Battle Girl Lilith",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LINDA_REWARD": {
"label": "Route 133 - Swimmer Linda",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LISA_AND_RAY_REWARD": {
"label": "Route 107 - Sis and Bro Lisa and Ray",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LOLA_1_REWARD": {
"label": "Route 109 - Tuber Lola",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LORENZO_REWARD": {
"label": "Route 120 - Pokemon Ranger Lorenzo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LUCAS_1_REWARD": {
"label": "Route 114 - Hiker Lucas",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LUIS_REWARD": {
"label": "Route 105 - Swimmer Luis",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LUNG_REWARD": {
"label": "Route 113 - Ninja Boy Lung",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LYDIA_1_REWARD": {
"label": "Route 117 - Pokemon Breeder Lydia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_LYLE_REWARD": {
"label": "Petalburg Woods - Bug Catcher Lyle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MACEY_REWARD": {
"label": "Mossdeep Gym - Psychic Macey",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MADELINE_1_REWARD": {
"label": "Route 113 - Parasol Lady Madeline",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MAKAYLA_REWARD": {
"label": "Route 132 - Expert Makayla",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARCEL_REWARD": {
"label": "Route 121 - Cooltrainer Marcel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARCOS_REWARD": {
"label": "Route 103 - Guitarist Marcos",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARC_REWARD": {
"label": "Rustboro Gym - Hiker Marc",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARIA_1_REWARD": {
"label": "Route 117 - Triathlete Maria",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARK_REWARD": {
"label": "Mt Pyre 2F - Pokemaniac Mark",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARLENE_REWARD": {
"label": "Route 115 - Psychic Marlene",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARLEY_REWARD": {
"label": "Route 134 - Cooltrainer Marley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MARY_REWARD": {
"label": "Petalburg Gym - Cooltrainer Mary",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MATTHEW_REWARD": {
"label": "Route 108 - Swimmer Matthew",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MATT_REWARD": {
"label": "Aqua Hideout B2F - Aqua Admin Matt",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MAURA_REWARD": {
"label": "Mossdeep Gym - Psychic Maura",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MAXIE_MAGMA_HIDEOUT_REWARD": {
"label": "Magma Hideout 4F - Magma Leader Maxie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MAXIE_MT_CHIMNEY_REWARD": {
"label": "Mt Chimney - Magma Leader Maxie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MEL_AND_PAUL_REWARD": {
"label": "Route 109 - Young Couple Mel and Paul",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MELINA_REWARD": {
"label": "Route 117 - Triathlete Melina",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MELISSA_REWARD": {
"label": "Mt Chimney - Beauty Melissa",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MICAH_REWARD": {
"label": "SS Tidal - Gentleman Micah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MICHELLE_REWARD": {
"label": "Victory Road B1F - Cooltrainer Michelle",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MIGUEL_1_REWARD": {
"label": "Route 103 - Pokefan Miguel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MIKE_2_REWARD": {
"label": "Rusturf Tunnel - Hiker Mike",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MISSY_REWARD": {
"label": "Route 108 - Swimmer Missy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MITCHELL_REWARD": {
"label": "Victory Road B1F - Cooltrainer Mitchell",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MIU_AND_YUKI_REWARD": {
"label": "Route 123 - Twins Miu and Yuki",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MOLLIE_REWARD": {
"label": "Route 133 - Expert Mollie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_MYLES_REWARD": {
"label": "Route 121 - Pokemon Breeder Myles",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NANCY_REWARD": {
"label": "Route 114 - Picnicker Nancy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NAOMI_REWARD": {
"label": "SS Tidal - Lady Naomi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NATE_REWARD": {
"label": "Mossdeep Gym - Gentleman Nate",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NED_REWARD": {
"label": "Route 106 - Fisherman Ned",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NICHOLAS_REWARD": {
"label": "Mossdeep Gym - Psychic Nicholas",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NICOLAS_1_REWARD": {
"label": "Meteor Falls 1F - Dragon Tamer Nicolas",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NIKKI_REWARD": {
"label": "Route 126 - Swimmer Nikki",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NOB_1_REWARD": {
"label": "Route 115 - Black Belt Nob",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NOLAN_REWARD": {
"label": "Route 114 - Fisherman Nolan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NOLEN_REWARD": {
"label": "Route 125 - Swimmer Nolen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_NORMAN_1_REWARD": {
"label": "Petalburg Gym - Leader Norman",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_OLIVIA_REWARD": {
"label": "Sootopolis Gym - Beauty Olivia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_OWEN_REWARD": {
"label": "Victory Road B2F - Cooltrainer Owen",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PABLO_1_REWARD": {
"label": "Route 126 - Triathlete Pablo",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PARKER_REWARD": {
"label": "Petalburg Gym - Cooltrainer Parker",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PAT_REWARD": {
"label": "Route 121 - Pokemon Breeder Pat",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PAXTON_REWARD": {
"label": "Route 132 - Expert Paxton",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PERRY_REWARD": {
"label": "Route 118 - Bird Keeper Perry",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PETE_REWARD": {
"label": "Route 103 - Swimmer Pete",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PHILLIP_REWARD": {
"label": "SS Tidal - Sailor Phillip",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PHIL_REWARD": {
"label": "Route 119 - Bird Keeper Phil",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PHOEBE_REWARD": {
"label": "Ever Grande City - Elite Four Phoebe",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PRESLEY_REWARD": {
"label": "Route 125 - Bird Keeper Presley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_PRESTON_REWARD": {
"label": "Mossdeep Gym - Psychic Preston",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_QUINCY_REWARD": {
"label": "Victory Road 1F - Cooltrainer Quincy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RACHEL_REWARD": {
"label": "Route 119 - Parasol Lady Rachel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RANDALL_REWARD": {
"label": "Petalburg Gym - Cooltrainer Randall",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_REED_REWARD": {
"label": "Route 129 - Swimmer Reed",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RELI_AND_IAN_REWARD": {
"label": "Route 131 - Sis and Bro Reli and Ian",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_REYNA_REWARD": {
"label": "Route 134 - Battle Girl Reyna",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RHETT_REWARD": {
"label": "Route 103 - Black Belt Rhett",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RICHARD_REWARD": {
"label": "Route 131 - Swimmer Richard",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RICKY_1_REWARD": {
"label": "Route 109 - Tuber Ricky",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RICK_REWARD": {
"label": "Route 102 - Bug Catcher Rick",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RILEY_REWARD": {
"label": "Route 120 - Ninja Boy Riley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ROBERT_1_REWARD": {
"label": "Route 120 - Bird Keeper Robert",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RODNEY_REWARD": {
"label": "Route 130 - Swimmer Rodney",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ROGER_REWARD": {
"label": "Route 127 - Fisherman Roger",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ROLAND_REWARD": {
"label": "Route 124 - Swimmer Roland",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RONALD_REWARD": {
"label": "Route 132 - Fisherman Ronald",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ROSE_1_REWARD": {
"label": "Route 118 - Aroma Lady Rose",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ROXANNE_1_REWARD": {
"label": "Rustboro Gym - Leader Roxanne",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_RUBEN_REWARD": {
"label": "Route 128 - Cooltrainer Ruben",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SAMANTHA_REWARD": {
"label": "Mossdeep Gym - Psychic Samantha",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SAMUEL_REWARD": {
"label": "Victory Road B1F - Cooltrainer Samuel",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SANTIAGO_REWARD": {
"label": "Route 130 - Swimmer Santiago",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SARAH_REWARD": {
"label": "Route 116 - Lady Sarah",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SAWYER_1_REWARD": {
"label": "Mt Chimney - Hiker Sawyer",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHANE_REWARD": {
"label": "Route 114 - Camper Shane",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHANNON_REWARD": {
"label": "Victory Road B1F - Cooltrainer Shannon",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHARON_REWARD": {
"label": "Route 125 - Swimmer Sharon",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHAWN_REWARD": {
"label": "Mauville Gym - Guitarist Shawn",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHAYLA_REWARD": {
"label": "Route 112 - Aroma Lady Shayla",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHEILA_REWARD": {
"label": "Mt Chimney - Beauty Sheila",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHELBY_1_REWARD": {
"label": "Mt Chimney - Expert Shelby",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHELLY_SEAFLOOR_CAVERN_REWARD": {
"label": "Seafloor Cavern Room 3 - Aqua Admin Shelly",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHELLY_WEATHER_INSTITUTE_REWARD": {
"label": "Weather Institute 2F - Aqua Admin Shelly",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SHIRLEY_REWARD": {
"label": "Mt Chimney - Beauty Shirley",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SIDNEY_REWARD": {
"label": "Ever Grande City - Elite Four Sidney",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SIENNA_REWARD": {
"label": "Route 126 - Swimmer Sienna",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SIMON_REWARD": {
"label": "Route 109 - Tuber Simon",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SOPHIE_REWARD": {
"label": "Route 113 - Picnicker Sophie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SPENCER_REWARD": {
"label": "Route 124 - Swimmer Spencer",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_STAN_REWARD": {
"label": "Route 125 - Swimmer Stan",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_STEVEN_REWARD": {
"label": "Meteor Falls 1F - Rival Steven",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_STEVE_1_REWARD": {
"label": "Route 114 - Pokemaniac Steve",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SUSIE_REWARD": {
"label": "Route 131 - Swimmer Susie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_SYLVIA_REWARD": {
"label": "Mossdeep Gym - Hex Maniac Sylvia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TABITHA_MAGMA_HIDEOUT_REWARD": {
"label": "Magma Hideout 4F - Magma Admin Tabitha",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TABITHA_MT_CHIMNEY_REWARD": {
"label": "Mt Chimney - Magma Admin Tabitha",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TAKAO_REWARD": {
"label": "Dewford Gym - Black Belt Takao",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TAKASHI_REWARD": {
"label": "Route 119 - Ninja Boy Takashi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TALIA_REWARD": {
"label": "Route 131 - Triathlete Talia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TAMMY_REWARD": {
"label": "Route 121 - Hex Maniac Tammy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TANYA_REWARD": {
"label": "Route 125 - Swimmer Tanya",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TARA_REWARD": {
"label": "Route 108 - Swimmer Tara",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TASHA_REWARD": {
"label": "Mt Pyre 4F - Hex Maniac Tasha",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TATE_AND_LIZA_1_REWARD": {
"label": "Mossdeep Gym - Leader Tate and Liza",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TAYLOR_REWARD": {
"label": "Route 119 - Bug Maniac Taylor",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TYRA_AND_IVY_REWARD": {
"label": "Route 114 - Sr. and Jr. Tyra and Ivy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_THALIA_1_REWARD": {
"label": "Abandoned Ship 1F - Beauty Thalia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_THOMAS_REWARD": {
"label": "SS Tidal - Gentleman Thomas",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TIANA_REWARD": {
"label": "Route 102 - Lass Tiana",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TIFFANY_REWARD": {
"label": "Sootopolis Gym - Beauty Tiffany",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TIMMY_REWARD": {
"label": "Route 110 - Youngster Timmy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TIMOTHY_1_REWARD": {
"label": "Route 115 - Expert Timothy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TISHA_REWARD": {
"label": "Route 129 - Swimmer Tisha",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TOMMY_REWARD": {
"label": "Rustboro Gym - Youngster Tommy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TONY_1_REWARD": {
"label": "Route 107 - Swimmer Tony",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TORI_AND_TIA_REWARD": {
"label": "Route 113 - Twins Tori and Tia",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TRAVIS_REWARD": {
"label": "Route 111 - Camper Travis",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TRENT_1_REWARD": {
"label": "Route 112 - Hiker Trent",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_TYRON_REWARD": {
"label": "Route 111 - Camper Tyron",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VALERIE_1_REWARD": {
"label": "Mt Pyre 6F - Hex Maniac Valerie",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VANESSA_REWARD": {
"label": "Route 121 - Pokefan Vanessa",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VICKY_REWARD": {
"label": "Route 111 - Winstrate Vicky",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VICTORIA_REWARD": {
"label": "Route 111 - Winstrate Victoria",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VICTOR_REWARD": {
"label": "Route 111 - Winstrate Victor",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VIOLET_REWARD": {
"label": "Route 123 - Aroma Lady Violet",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VIRGIL_REWARD": {
"label": "Mossdeep Gym - Psychic Virgil",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VITO_REWARD": {
"label": "Victory Road B2F - Cooltrainer Vito",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VIVIAN_REWARD": {
"label": "Mauville Gym - Battle Girl Vivian",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_VIVI_REWARD": {
"label": "Route 111 - Winstrate Vivi",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WADE_REWARD": {
"label": "Route 118 - Fisherman Wade",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WALLACE_REWARD": {
"label": "Ever Grande City - Champion Wallace",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WALTER_1_REWARD": {
"label": "Route 121 - Gentleman Walter",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WALLY_MAUVILLE_REWARD": {
"label": "Mauville City - Rival Wally",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WALLY_VR_1_REWARD": {
"label": "Victory Road 1F - Rival Wally",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WATTSON_1_REWARD": {
"label": "Mauville Gym - Leader Wattson",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WARREN_REWARD": {
"label": "Route 133 - Cooltrainer Warren",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WAYNE_REWARD": {
"label": "Route 128 - Fisherman Wayne",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WENDY_REWARD": {
"label": "Route 123 - Cooltrainer Wendy",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WILLIAM_REWARD": {
"label": "Mt Pyre 3F - Psychic William",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WILTON_1_REWARD": {
"label": "Route 111 - Cooltrainer Wilton",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WINONA_1_REWARD": {
"label": "Fortree Gym - Leader Winona",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WINSTON_1_REWARD": {
"label": "Route 104 - Rich Boy Winston",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_WYATT_REWARD": {
"label": "Route 113 - Pokemaniac Wyatt",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_YASU_REWARD": {
"label": "Route 119 - Ninja Boy Yasu",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
},
"TRAINER_ZANDER_REWARD": {
"label": "Mt Pyre 2F - Black Belt Zander",
- "tags": ["Trainer"]
+ "tags": [],
+ "category": "TRAINER"
}
}
diff --git a/worlds/pokemon_emerald/groups.py b/worlds/pokemon_emerald/groups.py
new file mode 100644
index 000000000000..d358da18350f
--- /dev/null
+++ b/worlds/pokemon_emerald/groups.py
@@ -0,0 +1,721 @@
+from typing import Dict, Set
+
+from .data import LocationCategory, data
+
+
+# Item Groups
+ITEM_GROUPS: Dict[str, Set[str]] = {}
+
+for item in data.items.values():
+ for tag in item.tags:
+ if tag not in ITEM_GROUPS:
+ ITEM_GROUPS[tag] = set()
+ ITEM_GROUPS[tag].add(item.label)
+
+# Location Groups
+_LOCATION_GROUP_MAPS = {
+ "Abandoned Ship": {
+ "MAP_ABANDONED_SHIP_CAPTAINS_OFFICE",
+ "MAP_ABANDONED_SHIP_CORRIDORS_1F",
+ "MAP_ABANDONED_SHIP_CORRIDORS_B1F",
+ "MAP_ABANDONED_SHIP_DECK",
+ "MAP_ABANDONED_SHIP_HIDDEN_FLOOR_CORRIDORS",
+ "MAP_ABANDONED_SHIP_HIDDEN_FLOOR_ROOMS",
+ "MAP_ABANDONED_SHIP_ROOMS2_1F",
+ "MAP_ABANDONED_SHIP_ROOMS2_B1F",
+ "MAP_ABANDONED_SHIP_ROOMS_1F",
+ "MAP_ABANDONED_SHIP_ROOMS_B1F",
+ "MAP_ABANDONED_SHIP_ROOM_B1F",
+ "MAP_ABANDONED_SHIP_UNDERWATER1",
+ "MAP_ABANDONED_SHIP_UNDERWATER2",
+ },
+ "Aqua Hideout": {
+ "MAP_AQUA_HIDEOUT_1F",
+ "MAP_AQUA_HIDEOUT_B1F",
+ "MAP_AQUA_HIDEOUT_B2F",
+ },
+ "Battle Frontier": {
+ "MAP_ARTISAN_CAVE_1F",
+ "MAP_ARTISAN_CAVE_B1F",
+ "MAP_BATTLE_FRONTIER_BATTLE_ARENA_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_ARENA_CORRIDOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_DOME_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_DOME_CORRIDOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_DOME_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_DOME_PRE_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_FACTORY_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_FACTORY_PRE_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_PIKE_CORRIDOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_FINAL",
+ "MAP_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_NORMAL",
+ "MAP_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS",
+ "MAP_BATTLE_FRONTIER_BATTLE_PIKE_THREE_PATH_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_TOP",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_CORRIDOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_ELEVATOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_MULTI_BATTLE_ROOM",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_MULTI_CORRIDOR",
+ "MAP_BATTLE_FRONTIER_BATTLE_TOWER_MULTI_PARTNER_ROOM",
+ "MAP_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER",
+ "MAP_BATTLE_FRONTIER_LOUNGE1",
+ "MAP_BATTLE_FRONTIER_LOUNGE2",
+ "MAP_BATTLE_FRONTIER_LOUNGE3",
+ "MAP_BATTLE_FRONTIER_LOUNGE4",
+ "MAP_BATTLE_FRONTIER_LOUNGE5",
+ "MAP_BATTLE_FRONTIER_LOUNGE6",
+ "MAP_BATTLE_FRONTIER_LOUNGE7",
+ "MAP_BATTLE_FRONTIER_LOUNGE8",
+ "MAP_BATTLE_FRONTIER_LOUNGE9",
+ "MAP_BATTLE_FRONTIER_MART",
+ "MAP_BATTLE_FRONTIER_OUTSIDE_EAST",
+ "MAP_BATTLE_FRONTIER_OUTSIDE_WEST",
+ "MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F",
+ "MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F",
+ "MAP_BATTLE_FRONTIER_RANKING_HALL",
+ "MAP_BATTLE_FRONTIER_RECEPTION_GATE",
+ "MAP_BATTLE_FRONTIER_SCOTTS_HOUSE",
+ "MAP_BATTLE_PYRAMID_SQUARE01",
+ "MAP_BATTLE_PYRAMID_SQUARE02",
+ "MAP_BATTLE_PYRAMID_SQUARE03",
+ "MAP_BATTLE_PYRAMID_SQUARE04",
+ "MAP_BATTLE_PYRAMID_SQUARE05",
+ "MAP_BATTLE_PYRAMID_SQUARE06",
+ "MAP_BATTLE_PYRAMID_SQUARE07",
+ "MAP_BATTLE_PYRAMID_SQUARE08",
+ "MAP_BATTLE_PYRAMID_SQUARE09",
+ "MAP_BATTLE_PYRAMID_SQUARE10",
+ "MAP_BATTLE_PYRAMID_SQUARE11",
+ "MAP_BATTLE_PYRAMID_SQUARE12",
+ "MAP_BATTLE_PYRAMID_SQUARE13",
+ "MAP_BATTLE_PYRAMID_SQUARE14",
+ "MAP_BATTLE_PYRAMID_SQUARE15",
+ "MAP_BATTLE_PYRAMID_SQUARE16",
+ },
+ "Birth Island": {
+ "MAP_BIRTH_ISLAND_EXTERIOR",
+ "MAP_BIRTH_ISLAND_HARBOR",
+ },
+ "Contest Hall": {
+ "MAP_CONTEST_HALL",
+ "MAP_CONTEST_HALL_BEAUTY",
+ "MAP_CONTEST_HALL_COOL",
+ "MAP_CONTEST_HALL_CUTE",
+ "MAP_CONTEST_HALL_SMART",
+ "MAP_CONTEST_HALL_TOUGH",
+ },
+ "Dewford Town": {
+ "MAP_DEWFORD_TOWN",
+ "MAP_DEWFORD_TOWN_GYM",
+ "MAP_DEWFORD_TOWN_HALL",
+ "MAP_DEWFORD_TOWN_HOUSE1",
+ "MAP_DEWFORD_TOWN_HOUSE2",
+ "MAP_DEWFORD_TOWN_POKEMON_CENTER_1F",
+ "MAP_DEWFORD_TOWN_POKEMON_CENTER_2F",
+ },
+ "Ever Grande City": {
+ "MAP_EVER_GRANDE_CITY",
+ "MAP_EVER_GRANDE_CITY_CHAMPIONS_ROOM",
+ "MAP_EVER_GRANDE_CITY_DRAKES_ROOM",
+ "MAP_EVER_GRANDE_CITY_GLACIAS_ROOM",
+ "MAP_EVER_GRANDE_CITY_HALL1",
+ "MAP_EVER_GRANDE_CITY_HALL2",
+ "MAP_EVER_GRANDE_CITY_HALL3",
+ "MAP_EVER_GRANDE_CITY_HALL4",
+ "MAP_EVER_GRANDE_CITY_HALL5",
+ "MAP_EVER_GRANDE_CITY_HALL_OF_FAME",
+ "MAP_EVER_GRANDE_CITY_PHOEBES_ROOM",
+ "MAP_EVER_GRANDE_CITY_POKEMON_CENTER_1F",
+ "MAP_EVER_GRANDE_CITY_POKEMON_CENTER_2F",
+ "MAP_EVER_GRANDE_CITY_POKEMON_LEAGUE_1F",
+ "MAP_EVER_GRANDE_CITY_POKEMON_LEAGUE_2F",
+ "MAP_EVER_GRANDE_CITY_SIDNEYS_ROOM",
+ },
+ "Fallarbor Town": {
+ "MAP_FALLARBOR_TOWN",
+ "MAP_FALLARBOR_TOWN_BATTLE_TENT_BATTLE_ROOM",
+ "MAP_FALLARBOR_TOWN_BATTLE_TENT_CORRIDOR",
+ "MAP_FALLARBOR_TOWN_BATTLE_TENT_LOBBY",
+ "MAP_FALLARBOR_TOWN_COZMOS_HOUSE",
+ "MAP_FALLARBOR_TOWN_MART",
+ "MAP_FALLARBOR_TOWN_MOVE_RELEARNERS_HOUSE",
+ "MAP_FALLARBOR_TOWN_POKEMON_CENTER_1F",
+ "MAP_FALLARBOR_TOWN_POKEMON_CENTER_2F",
+ },
+ "Faraway Island": {
+ "MAP_FARAWAY_ISLAND_ENTRANCE",
+ "MAP_FARAWAY_ISLAND_INTERIOR",
+ },
+ "Fiery Path": {"MAP_FIERY_PATH"},
+ "Fortree City": {
+ "MAP_FORTREE_CITY",
+ "MAP_FORTREE_CITY_DECORATION_SHOP",
+ "MAP_FORTREE_CITY_GYM",
+ "MAP_FORTREE_CITY_HOUSE1",
+ "MAP_FORTREE_CITY_HOUSE2",
+ "MAP_FORTREE_CITY_HOUSE3",
+ "MAP_FORTREE_CITY_HOUSE4",
+ "MAP_FORTREE_CITY_HOUSE5",
+ "MAP_FORTREE_CITY_MART",
+ "MAP_FORTREE_CITY_POKEMON_CENTER_1F",
+ "MAP_FORTREE_CITY_POKEMON_CENTER_2F",
+ },
+ "Granite Cave": {
+ "MAP_GRANITE_CAVE_1F",
+ "MAP_GRANITE_CAVE_B1F",
+ "MAP_GRANITE_CAVE_B2F",
+ "MAP_GRANITE_CAVE_STEVENS_ROOM",
+ },
+ "Jagged Pass": {"MAP_JAGGED_PASS"},
+ "Lavaridge Town": {
+ "MAP_LAVARIDGE_TOWN",
+ "MAP_LAVARIDGE_TOWN_GYM_1F",
+ "MAP_LAVARIDGE_TOWN_GYM_B1F",
+ "MAP_LAVARIDGE_TOWN_HERB_SHOP",
+ "MAP_LAVARIDGE_TOWN_HOUSE",
+ "MAP_LAVARIDGE_TOWN_MART",
+ "MAP_LAVARIDGE_TOWN_POKEMON_CENTER_1F",
+ "MAP_LAVARIDGE_TOWN_POKEMON_CENTER_2F",
+ },
+ "Lilycove City": {
+ "MAP_LILYCOVE_CITY",
+ "MAP_LILYCOVE_CITY_CONTEST_HALL",
+ "MAP_LILYCOVE_CITY_CONTEST_LOBBY",
+ "MAP_LILYCOVE_CITY_COVE_LILY_MOTEL_1F",
+ "MAP_LILYCOVE_CITY_COVE_LILY_MOTEL_2F",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_1F",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_2F",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_3F",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_4F",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_5F",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR",
+ "MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ROOFTOP",
+ "MAP_LILYCOVE_CITY_HARBOR",
+ "MAP_LILYCOVE_CITY_HOUSE1",
+ "MAP_LILYCOVE_CITY_HOUSE2",
+ "MAP_LILYCOVE_CITY_HOUSE3",
+ "MAP_LILYCOVE_CITY_HOUSE4",
+ "MAP_LILYCOVE_CITY_LILYCOVE_MUSEUM_1F",
+ "MAP_LILYCOVE_CITY_LILYCOVE_MUSEUM_2F",
+ "MAP_LILYCOVE_CITY_MOVE_DELETERS_HOUSE",
+ "MAP_LILYCOVE_CITY_POKEMON_CENTER_1F",
+ "MAP_LILYCOVE_CITY_POKEMON_CENTER_2F",
+ "MAP_LILYCOVE_CITY_POKEMON_TRAINER_FAN_CLUB",
+ },
+ "Littleroot Town": {
+ "MAP_INSIDE_OF_TRUCK",
+ "MAP_LITTLEROOT_TOWN",
+ "MAP_LITTLEROOT_TOWN_BRENDANS_HOUSE_1F",
+ "MAP_LITTLEROOT_TOWN_BRENDANS_HOUSE_2F",
+ "MAP_LITTLEROOT_TOWN_MAYS_HOUSE_1F",
+ "MAP_LITTLEROOT_TOWN_MAYS_HOUSE_2F",
+ "MAP_LITTLEROOT_TOWN_PROFESSOR_BIRCHS_LAB",
+ },
+ "Magma Hideout": {
+ "MAP_MAGMA_HIDEOUT_1F",
+ "MAP_MAGMA_HIDEOUT_2F_1R",
+ "MAP_MAGMA_HIDEOUT_2F_2R",
+ "MAP_MAGMA_HIDEOUT_2F_3R",
+ "MAP_MAGMA_HIDEOUT_3F_1R",
+ "MAP_MAGMA_HIDEOUT_3F_2R",
+ "MAP_MAGMA_HIDEOUT_3F_3R",
+ "MAP_MAGMA_HIDEOUT_4F",
+ },
+ "Marine Cave": {
+ "MAP_MARINE_CAVE_END",
+ "MAP_MARINE_CAVE_ENTRANCE",
+ "MAP_UNDERWATER_MARINE_CAVE",
+ },
+ "Mauville City": {
+ "MAP_MAUVILLE_CITY",
+ "MAP_MAUVILLE_CITY_BIKE_SHOP",
+ "MAP_MAUVILLE_CITY_GAME_CORNER",
+ "MAP_MAUVILLE_CITY_GYM",
+ "MAP_MAUVILLE_CITY_HOUSE1",
+ "MAP_MAUVILLE_CITY_HOUSE2",
+ "MAP_MAUVILLE_CITY_MART",
+ "MAP_MAUVILLE_CITY_POKEMON_CENTER_1F",
+ "MAP_MAUVILLE_CITY_POKEMON_CENTER_2F",
+ },
+ "Meteor Falls": {
+ "MAP_METEOR_FALLS_1F_1R",
+ "MAP_METEOR_FALLS_1F_2R",
+ "MAP_METEOR_FALLS_B1F_1R",
+ "MAP_METEOR_FALLS_B1F_2R",
+ "MAP_METEOR_FALLS_STEVENS_CAVE",
+ },
+ "Mirage Tower": {
+ "MAP_MIRAGE_TOWER_1F",
+ "MAP_MIRAGE_TOWER_2F",
+ "MAP_MIRAGE_TOWER_3F",
+ "MAP_MIRAGE_TOWER_4F",
+ },
+ "Mossdeep City": {
+ "MAP_MOSSDEEP_CITY",
+ "MAP_MOSSDEEP_CITY_GAME_CORNER_1F",
+ "MAP_MOSSDEEP_CITY_GAME_CORNER_B1F",
+ "MAP_MOSSDEEP_CITY_GYM",
+ "MAP_MOSSDEEP_CITY_HOUSE1",
+ "MAP_MOSSDEEP_CITY_HOUSE2",
+ "MAP_MOSSDEEP_CITY_HOUSE3",
+ "MAP_MOSSDEEP_CITY_HOUSE4",
+ "MAP_MOSSDEEP_CITY_MART",
+ "MAP_MOSSDEEP_CITY_POKEMON_CENTER_1F",
+ "MAP_MOSSDEEP_CITY_POKEMON_CENTER_2F",
+ "MAP_MOSSDEEP_CITY_SPACE_CENTER_1F",
+ "MAP_MOSSDEEP_CITY_SPACE_CENTER_2F",
+ "MAP_MOSSDEEP_CITY_STEVENS_HOUSE",
+ },
+ "Mt. Chimney": {
+ "MAP_MT_CHIMNEY",
+ "MAP_MT_CHIMNEY_CABLE_CAR_STATION",
+ },
+ "Mt. Pyre": {
+ "MAP_MT_PYRE_1F",
+ "MAP_MT_PYRE_2F",
+ "MAP_MT_PYRE_3F",
+ "MAP_MT_PYRE_4F",
+ "MAP_MT_PYRE_5F",
+ "MAP_MT_PYRE_6F",
+ "MAP_MT_PYRE_EXTERIOR",
+ "MAP_MT_PYRE_SUMMIT",
+ },
+ "Navel Rock": {
+ "MAP_NAVEL_ROCK_B1F",
+ "MAP_NAVEL_ROCK_BOTTOM",
+ "MAP_NAVEL_ROCK_DOWN01",
+ "MAP_NAVEL_ROCK_DOWN02",
+ "MAP_NAVEL_ROCK_DOWN03",
+ "MAP_NAVEL_ROCK_DOWN04",
+ "MAP_NAVEL_ROCK_DOWN05",
+ "MAP_NAVEL_ROCK_DOWN06",
+ "MAP_NAVEL_ROCK_DOWN07",
+ "MAP_NAVEL_ROCK_DOWN08",
+ "MAP_NAVEL_ROCK_DOWN09",
+ "MAP_NAVEL_ROCK_DOWN10",
+ "MAP_NAVEL_ROCK_DOWN11",
+ "MAP_NAVEL_ROCK_ENTRANCE",
+ "MAP_NAVEL_ROCK_EXTERIOR",
+ "MAP_NAVEL_ROCK_FORK",
+ "MAP_NAVEL_ROCK_HARBOR",
+ "MAP_NAVEL_ROCK_TOP",
+ "MAP_NAVEL_ROCK_UP1",
+ "MAP_NAVEL_ROCK_UP2",
+ "MAP_NAVEL_ROCK_UP3",
+ "MAP_NAVEL_ROCK_UP4",
+ },
+ "New Mauville": {
+ "MAP_NEW_MAUVILLE_ENTRANCE",
+ "MAP_NEW_MAUVILLE_INSIDE",
+ },
+ "Oldale Town": {
+ "MAP_OLDALE_TOWN",
+ "MAP_OLDALE_TOWN_HOUSE1",
+ "MAP_OLDALE_TOWN_HOUSE2",
+ "MAP_OLDALE_TOWN_MART",
+ "MAP_OLDALE_TOWN_POKEMON_CENTER_1F",
+ "MAP_OLDALE_TOWN_POKEMON_CENTER_2F",
+ },
+ "Pacifidlog Town": {
+ "MAP_PACIFIDLOG_TOWN",
+ "MAP_PACIFIDLOG_TOWN_HOUSE1",
+ "MAP_PACIFIDLOG_TOWN_HOUSE2",
+ "MAP_PACIFIDLOG_TOWN_HOUSE3",
+ "MAP_PACIFIDLOG_TOWN_HOUSE4",
+ "MAP_PACIFIDLOG_TOWN_HOUSE5",
+ "MAP_PACIFIDLOG_TOWN_POKEMON_CENTER_1F",
+ "MAP_PACIFIDLOG_TOWN_POKEMON_CENTER_2F",
+ },
+ "Petalburg City": {
+ "MAP_PETALBURG_CITY",
+ "MAP_PETALBURG_CITY_GYM",
+ "MAP_PETALBURG_CITY_HOUSE1",
+ "MAP_PETALBURG_CITY_HOUSE2",
+ "MAP_PETALBURG_CITY_MART",
+ "MAP_PETALBURG_CITY_POKEMON_CENTER_1F",
+ "MAP_PETALBURG_CITY_POKEMON_CENTER_2F",
+ "MAP_PETALBURG_CITY_WALLYS_HOUSE",
+ },
+ "Petalburg Woods": {"MAP_PETALBURG_WOODS"},
+ "Route 101": {"MAP_ROUTE101"},
+ "Route 102": {"MAP_ROUTE102"},
+ "Route 103": {"MAP_ROUTE103"},
+ "Route 104": {
+ "MAP_ROUTE104",
+ "MAP_ROUTE104_MR_BRINEYS_HOUSE",
+ "MAP_ROUTE104_PRETTY_PETAL_FLOWER_SHOP",
+ },
+ "Route 105": {
+ "MAP_ISLAND_CAVE",
+ "MAP_ROUTE105",
+ "MAP_UNDERWATER_ROUTE105",
+ },
+ "Route 106": {"MAP_ROUTE106"},
+ "Route 107": {"MAP_ROUTE107"},
+ "Route 108": {"MAP_ROUTE108"},
+ "Route 109": {
+ "MAP_ROUTE109",
+ "MAP_ROUTE109_SEASHORE_HOUSE",
+ },
+ "Route 110": {
+ "MAP_ROUTE110",
+ "MAP_ROUTE110_SEASIDE_CYCLING_ROAD_NORTH_ENTRANCE",
+ "MAP_ROUTE110_SEASIDE_CYCLING_ROAD_SOUTH_ENTRANCE",
+ },
+ "Trick House": {
+ "MAP_ROUTE110_TRICK_HOUSE_CORRIDOR",
+ "MAP_ROUTE110_TRICK_HOUSE_END",
+ "MAP_ROUTE110_TRICK_HOUSE_ENTRANCE",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE1",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE2",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE3",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE4",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE5",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE6",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE7",
+ "MAP_ROUTE110_TRICK_HOUSE_PUZZLE8",
+ },
+ "Route 111": {
+ "MAP_DESERT_RUINS",
+ "MAP_ROUTE111",
+ "MAP_ROUTE111_OLD_LADYS_REST_STOP",
+ "MAP_ROUTE111_WINSTRATE_FAMILYS_HOUSE",
+ },
+ "Route 112": {
+ "MAP_ROUTE112",
+ "MAP_ROUTE112_CABLE_CAR_STATION",
+ },
+ "Route 113": {
+ "MAP_ROUTE113",
+ "MAP_ROUTE113_GLASS_WORKSHOP",
+ },
+ "Route 114": {
+ "MAP_DESERT_UNDERPASS",
+ "MAP_ROUTE114",
+ "MAP_ROUTE114_FOSSIL_MANIACS_HOUSE",
+ "MAP_ROUTE114_FOSSIL_MANIACS_TUNNEL",
+ "MAP_ROUTE114_LANETTES_HOUSE",
+ },
+ "Route 115": {"MAP_ROUTE115"},
+ "Route 116": {
+ "MAP_ROUTE116",
+ "MAP_ROUTE116_TUNNELERS_REST_HOUSE",
+ },
+ "Route 117": {
+ "MAP_ROUTE117",
+ "MAP_ROUTE117_POKEMON_DAY_CARE",
+ },
+ "Route 118": {"MAP_ROUTE118"},
+ "Route 119": {
+ "MAP_ROUTE119",
+ "MAP_ROUTE119_HOUSE",
+ "MAP_ROUTE119_WEATHER_INSTITUTE_1F",
+ "MAP_ROUTE119_WEATHER_INSTITUTE_2F",
+ },
+ "Route 120": {
+ "MAP_ANCIENT_TOMB",
+ "MAP_ROUTE120",
+ "MAP_SCORCHED_SLAB",
+ },
+ "Route 121": {
+ "MAP_ROUTE121",
+ },
+ "Route 122": {"MAP_ROUTE122"},
+ "Route 123": {
+ "MAP_ROUTE123",
+ "MAP_ROUTE123_BERRY_MASTERS_HOUSE",
+ },
+ "Route 124": {
+ "MAP_ROUTE124",
+ "MAP_ROUTE124_DIVING_TREASURE_HUNTERS_HOUSE",
+ "MAP_UNDERWATER_ROUTE124",
+ },
+ "Route 125": {
+ "MAP_ROUTE125",
+ "MAP_UNDERWATER_ROUTE125",
+ },
+ "Route 126": {
+ "MAP_ROUTE126",
+ "MAP_UNDERWATER_ROUTE126",
+ },
+ "Route 127": {
+ "MAP_ROUTE127",
+ "MAP_UNDERWATER_ROUTE127",
+ },
+ "Route 128": {
+ "MAP_ROUTE128",
+ "MAP_UNDERWATER_ROUTE128",
+ },
+ "Route 129": {
+ "MAP_ROUTE129",
+ "MAP_UNDERWATER_ROUTE129",
+ },
+ "Route 130": {"MAP_ROUTE130"},
+ "Route 131": {"MAP_ROUTE131"},
+ "Route 132": {"MAP_ROUTE132"},
+ "Route 133": {"MAP_ROUTE133"},
+ "Route 134": {
+ "MAP_ROUTE134",
+ "MAP_UNDERWATER_ROUTE134",
+ "MAP_SEALED_CHAMBER_INNER_ROOM",
+ "MAP_SEALED_CHAMBER_OUTER_ROOM",
+ "MAP_UNDERWATER_SEALED_CHAMBER",
+ },
+ "Rustboro City": {
+ "MAP_RUSTBORO_CITY",
+ "MAP_RUSTBORO_CITY_CUTTERS_HOUSE",
+ "MAP_RUSTBORO_CITY_DEVON_CORP_1F",
+ "MAP_RUSTBORO_CITY_DEVON_CORP_2F",
+ "MAP_RUSTBORO_CITY_DEVON_CORP_3F",
+ "MAP_RUSTBORO_CITY_FLAT1_1F",
+ "MAP_RUSTBORO_CITY_FLAT1_2F",
+ "MAP_RUSTBORO_CITY_FLAT2_1F",
+ "MAP_RUSTBORO_CITY_FLAT2_2F",
+ "MAP_RUSTBORO_CITY_FLAT2_3F",
+ "MAP_RUSTBORO_CITY_GYM",
+ "MAP_RUSTBORO_CITY_HOUSE1",
+ "MAP_RUSTBORO_CITY_HOUSE2",
+ "MAP_RUSTBORO_CITY_HOUSE3",
+ "MAP_RUSTBORO_CITY_MART",
+ "MAP_RUSTBORO_CITY_POKEMON_CENTER_1F",
+ "MAP_RUSTBORO_CITY_POKEMON_CENTER_2F",
+ "MAP_RUSTBORO_CITY_POKEMON_SCHOOL",
+ },
+ "Rusturf Tunnel": {"MAP_RUSTURF_TUNNEL"},
+ "Safari Zone": {
+ "MAP_ROUTE121_SAFARI_ZONE_ENTRANCE",
+ "MAP_SAFARI_ZONE_NORTH",
+ "MAP_SAFARI_ZONE_NORTHEAST",
+ "MAP_SAFARI_ZONE_NORTHWEST",
+ "MAP_SAFARI_ZONE_REST_HOUSE",
+ "MAP_SAFARI_ZONE_SOUTH",
+ "MAP_SAFARI_ZONE_SOUTHEAST",
+ "MAP_SAFARI_ZONE_SOUTHWEST",
+ },
+ "Seafloor Cavern": {
+ "MAP_SEAFLOOR_CAVERN_ENTRANCE",
+ "MAP_SEAFLOOR_CAVERN_ROOM1",
+ "MAP_SEAFLOOR_CAVERN_ROOM2",
+ "MAP_SEAFLOOR_CAVERN_ROOM3",
+ "MAP_SEAFLOOR_CAVERN_ROOM4",
+ "MAP_SEAFLOOR_CAVERN_ROOM5",
+ "MAP_SEAFLOOR_CAVERN_ROOM6",
+ "MAP_SEAFLOOR_CAVERN_ROOM7",
+ "MAP_SEAFLOOR_CAVERN_ROOM8",
+ "MAP_SEAFLOOR_CAVERN_ROOM9",
+ "MAP_UNDERWATER_SEAFLOOR_CAVERN",
+ },
+ "Shoal Cave": {
+ "MAP_SHOAL_CAVE_HIGH_TIDE_ENTRANCE_ROOM",
+ "MAP_SHOAL_CAVE_HIGH_TIDE_INNER_ROOM",
+ "MAP_SHOAL_CAVE_LOW_TIDE_ENTRANCE_ROOM",
+ "MAP_SHOAL_CAVE_LOW_TIDE_ICE_ROOM",
+ "MAP_SHOAL_CAVE_LOW_TIDE_INNER_ROOM",
+ "MAP_SHOAL_CAVE_LOW_TIDE_LOWER_ROOM",
+ "MAP_SHOAL_CAVE_LOW_TIDE_STAIRS_ROOM",
+ },
+ "Sky Pillar": {
+ "MAP_SKY_PILLAR_1F",
+ "MAP_SKY_PILLAR_2F",
+ "MAP_SKY_PILLAR_3F",
+ "MAP_SKY_PILLAR_4F",
+ "MAP_SKY_PILLAR_5F",
+ "MAP_SKY_PILLAR_ENTRANCE",
+ "MAP_SKY_PILLAR_OUTSIDE",
+ "MAP_SKY_PILLAR_TOP",
+ },
+ "Slateport City": {
+ "MAP_SLATEPORT_CITY",
+ "MAP_SLATEPORT_CITY_BATTLE_TENT_BATTLE_ROOM",
+ "MAP_SLATEPORT_CITY_BATTLE_TENT_CORRIDOR",
+ "MAP_SLATEPORT_CITY_BATTLE_TENT_LOBBY",
+ "MAP_SLATEPORT_CITY_HARBOR",
+ "MAP_SLATEPORT_CITY_HOUSE",
+ "MAP_SLATEPORT_CITY_MART",
+ "MAP_SLATEPORT_CITY_NAME_RATERS_HOUSE",
+ "MAP_SLATEPORT_CITY_OCEANIC_MUSEUM_1F",
+ "MAP_SLATEPORT_CITY_OCEANIC_MUSEUM_2F",
+ "MAP_SLATEPORT_CITY_POKEMON_CENTER_1F",
+ "MAP_SLATEPORT_CITY_POKEMON_CENTER_2F",
+ "MAP_SLATEPORT_CITY_POKEMON_FAN_CLUB",
+ "MAP_SLATEPORT_CITY_STERNS_SHIPYARD_1F",
+ "MAP_SLATEPORT_CITY_STERNS_SHIPYARD_2F",
+ },
+ "Sootopolis City": {
+ "MAP_CAVE_OF_ORIGIN_1F",
+ "MAP_CAVE_OF_ORIGIN_B1F",
+ "MAP_CAVE_OF_ORIGIN_ENTRANCE",
+ "MAP_SOOTOPOLIS_CITY",
+ "MAP_SOOTOPOLIS_CITY_GYM_1F",
+ "MAP_SOOTOPOLIS_CITY_GYM_B1F",
+ "MAP_SOOTOPOLIS_CITY_HOUSE1",
+ "MAP_SOOTOPOLIS_CITY_HOUSE2",
+ "MAP_SOOTOPOLIS_CITY_HOUSE3",
+ "MAP_SOOTOPOLIS_CITY_HOUSE4",
+ "MAP_SOOTOPOLIS_CITY_HOUSE5",
+ "MAP_SOOTOPOLIS_CITY_HOUSE6",
+ "MAP_SOOTOPOLIS_CITY_HOUSE7",
+ "MAP_SOOTOPOLIS_CITY_LOTAD_AND_SEEDOT_HOUSE",
+ "MAP_SOOTOPOLIS_CITY_MART",
+ "MAP_SOOTOPOLIS_CITY_MYSTERY_EVENTS_HOUSE_1F",
+ "MAP_SOOTOPOLIS_CITY_MYSTERY_EVENTS_HOUSE_B1F",
+ "MAP_SOOTOPOLIS_CITY_POKEMON_CENTER_1F",
+ "MAP_SOOTOPOLIS_CITY_POKEMON_CENTER_2F",
+ "MAP_UNDERWATER_SOOTOPOLIS_CITY",
+ },
+ "Southern Island": {
+ "MAP_SOUTHERN_ISLAND_EXTERIOR",
+ "MAP_SOUTHERN_ISLAND_INTERIOR",
+ },
+ "S.S. Tidal": {
+ "MAP_SS_TIDAL_CORRIDOR",
+ "MAP_SS_TIDAL_LOWER_DECK",
+ "MAP_SS_TIDAL_ROOMS",
+ },
+ "Terra Cave": {
+ "MAP_TERRA_CAVE_END",
+ "MAP_TERRA_CAVE_ENTRANCE",
+ },
+ "Trainer Hill": {
+ "MAP_TRAINER_HILL_2F",
+ "MAP_TRAINER_HILL_3F",
+ "MAP_TRAINER_HILL_4F",
+ "MAP_TRAINER_HILL_ELEVATOR",
+ "MAP_TRAINER_HILL_ENTRANCE",
+ "MAP_TRAINER_HILL_ROOF",
+ },
+ "Verdanturf Town": {
+ "MAP_VERDANTURF_TOWN",
+ "MAP_VERDANTURF_TOWN_BATTLE_TENT_BATTLE_ROOM",
+ "MAP_VERDANTURF_TOWN_BATTLE_TENT_CORRIDOR",
+ "MAP_VERDANTURF_TOWN_BATTLE_TENT_LOBBY",
+ "MAP_VERDANTURF_TOWN_FRIENDSHIP_RATERS_HOUSE",
+ "MAP_VERDANTURF_TOWN_HOUSE",
+ "MAP_VERDANTURF_TOWN_MART",
+ "MAP_VERDANTURF_TOWN_POKEMON_CENTER_1F",
+ "MAP_VERDANTURF_TOWN_POKEMON_CENTER_2F",
+ "MAP_VERDANTURF_TOWN_WANDAS_HOUSE",
+ },
+ "Victory Road": {
+ "MAP_VICTORY_ROAD_1F",
+ "MAP_VICTORY_ROAD_B1F",
+ "MAP_VICTORY_ROAD_B2F",
+ },
+}
+
+_LOCATION_CATEGORY_TO_GROUP_NAME = {
+ LocationCategory.BADGE: "Badges",
+ LocationCategory.HM: "HMs",
+ LocationCategory.KEY: "Key Items",
+ LocationCategory.ROD: "Fishing Rods",
+ LocationCategory.BIKE: "Bikes",
+ LocationCategory.TICKET: "Tickets",
+ LocationCategory.OVERWORLD_ITEM: "Overworld Items",
+ LocationCategory.HIDDEN_ITEM: "Hidden Items",
+ LocationCategory.GIFT: "NPC Gifts",
+ LocationCategory.BERRY_TREE: "Berry Trees",
+ LocationCategory.TRAINER: "Trainers",
+ LocationCategory.POKEDEX: "Pokedex",
+}
+
+LOCATION_GROUPS: Dict[str, Set[str]] = {group_name: set() for group_name in _LOCATION_CATEGORY_TO_GROUP_NAME.values()}
+for location in data.locations.values():
+ # Category groups
+ LOCATION_GROUPS[_LOCATION_CATEGORY_TO_GROUP_NAME[location.category]].add(location.label)
+
+ # Tag groups
+ for tag in location.tags:
+ if tag not in LOCATION_GROUPS:
+ LOCATION_GROUPS[tag] = set()
+ LOCATION_GROUPS[tag].add(location.label)
+
+ # Geographic groups
+ if location.parent_region != "REGION_POKEDEX":
+ map_name = data.regions[location.parent_region].parent_map.name
+ for group, maps in _LOCATION_GROUP_MAPS.items():
+ if map_name in maps:
+ if group not in LOCATION_GROUPS:
+ LOCATION_GROUPS[group] = set()
+ LOCATION_GROUPS[group].add(location.label)
+ break
+
+# Meta-groups
+LOCATION_GROUPS["Cities"] = {
+ *LOCATION_GROUPS.get("Littleroot Town", set()),
+ *LOCATION_GROUPS.get("Oldale Town", set()),
+ *LOCATION_GROUPS.get("Petalburg City", set()),
+ *LOCATION_GROUPS.get("Rustboro City", set()),
+ *LOCATION_GROUPS.get("Dewford Town", set()),
+ *LOCATION_GROUPS.get("Slateport City", set()),
+ *LOCATION_GROUPS.get("Mauville City", set()),
+ *LOCATION_GROUPS.get("Verdanturf Town", set()),
+ *LOCATION_GROUPS.get("Fallarbor Town", set()),
+ *LOCATION_GROUPS.get("Lavaridge Town", set()),
+ *LOCATION_GROUPS.get("Fortree City", set()),
+ *LOCATION_GROUPS.get("Mossdeep City", set()),
+ *LOCATION_GROUPS.get("Sootopolis City", set()),
+ *LOCATION_GROUPS.get("Pacifidlog Town", set()),
+ *LOCATION_GROUPS.get("Ever Grande City", set()),
+}
+
+LOCATION_GROUPS["Dungeons"] = {
+ *LOCATION_GROUPS.get("Petalburg Woods", set()),
+ *LOCATION_GROUPS.get("Rusturf Tunnel", set()),
+ *LOCATION_GROUPS.get("Granite Cave", set()),
+ *LOCATION_GROUPS.get("Fiery Path", set()),
+ *LOCATION_GROUPS.get("Meteor Falls", set()),
+ *LOCATION_GROUPS.get("Jagged Pass", set()),
+ *LOCATION_GROUPS.get("Mt. Chimney", set()),
+ *LOCATION_GROUPS.get("Abandoned Ship", set()),
+ *LOCATION_GROUPS.get("New Mauville", set()),
+ *LOCATION_GROUPS.get("Mt. Pyre", set()),
+ *LOCATION_GROUPS.get("Seafloor Cavern", set()),
+ *LOCATION_GROUPS.get("Sky Pillar", set()),
+ *LOCATION_GROUPS.get("Victory Road", set()),
+}
+
+LOCATION_GROUPS["Routes"] = {
+ *LOCATION_GROUPS.get("Route 101", set()),
+ *LOCATION_GROUPS.get("Route 102", set()),
+ *LOCATION_GROUPS.get("Route 103", set()),
+ *LOCATION_GROUPS.get("Route 104", set()),
+ *LOCATION_GROUPS.get("Route 105", set()),
+ *LOCATION_GROUPS.get("Route 106", set()),
+ *LOCATION_GROUPS.get("Route 107", set()),
+ *LOCATION_GROUPS.get("Route 108", set()),
+ *LOCATION_GROUPS.get("Route 109", set()),
+ *LOCATION_GROUPS.get("Route 110", set()),
+ *LOCATION_GROUPS.get("Route 111", set()),
+ *LOCATION_GROUPS.get("Route 112", set()),
+ *LOCATION_GROUPS.get("Route 113", set()),
+ *LOCATION_GROUPS.get("Route 114", set()),
+ *LOCATION_GROUPS.get("Route 115", set()),
+ *LOCATION_GROUPS.get("Route 116", set()),
+ *LOCATION_GROUPS.get("Route 117", set()),
+ *LOCATION_GROUPS.get("Route 118", set()),
+ *LOCATION_GROUPS.get("Route 119", set()),
+ *LOCATION_GROUPS.get("Route 120", set()),
+ *LOCATION_GROUPS.get("Route 121", set()),
+ *LOCATION_GROUPS.get("Route 122", set()),
+ *LOCATION_GROUPS.get("Route 123", set()),
+ *LOCATION_GROUPS.get("Route 124", set()),
+ *LOCATION_GROUPS.get("Route 125", set()),
+ *LOCATION_GROUPS.get("Route 126", set()),
+ *LOCATION_GROUPS.get("Route 127", set()),
+ *LOCATION_GROUPS.get("Route 128", set()),
+ *LOCATION_GROUPS.get("Route 129", set()),
+ *LOCATION_GROUPS.get("Route 130", set()),
+ *LOCATION_GROUPS.get("Route 131", set()),
+ *LOCATION_GROUPS.get("Route 132", set()),
+ *LOCATION_GROUPS.get("Route 133", set()),
+ *LOCATION_GROUPS.get("Route 134", set()),
+}
diff --git a/worlds/pokemon_emerald/items.py b/worlds/pokemon_emerald/items.py
index 436db771d396..922bbbc0dbfb 100644
--- a/worlds/pokemon_emerald/items.py
+++ b/worlds/pokemon_emerald/items.py
@@ -1,7 +1,7 @@
"""
Classes and functions related to AP items for Pokemon Emerald
"""
-from typing import Dict, FrozenSet, Optional
+from typing import Dict, FrozenSet, Set, Optional
from BaseClasses import Item, ItemClassification
@@ -46,30 +46,6 @@ def create_item_label_to_code_map() -> Dict[str, int]:
return label_to_code_map
-ITEM_GROUPS = {
- "Badges": {
- "Stone Badge", "Knuckle Badge",
- "Dynamo Badge", "Heat Badge",
- "Balance Badge", "Feather Badge",
- "Mind Badge", "Rain Badge",
- },
- "HMs": {
- "HM01 Cut", "HM02 Fly",
- "HM03 Surf", "HM04 Strength",
- "HM05 Flash", "HM06 Rock Smash",
- "HM07 Waterfall", "HM08 Dive",
- },
- "HM01": {"HM01 Cut"},
- "HM02": {"HM02 Fly"},
- "HM03": {"HM03 Surf"},
- "HM04": {"HM04 Strength"},
- "HM05": {"HM05 Flash"},
- "HM06": {"HM06 Rock Smash"},
- "HM07": {"HM07 Waterfall"},
- "HM08": {"HM08 Dive"},
-}
-
-
def get_item_classification(item_code: int) -> ItemClassification:
"""
Returns the item classification for a given AP item id (code)
diff --git a/worlds/pokemon_emerald/locations.py b/worlds/pokemon_emerald/locations.py
index 9123690bead7..473c189166be 100644
--- a/worlds/pokemon_emerald/locations.py
+++ b/worlds/pokemon_emerald/locations.py
@@ -1,59 +1,17 @@
"""
Classes and functions related to AP locations for Pokemon Emerald
"""
-from typing import TYPE_CHECKING, Dict, Optional, FrozenSet, Iterable
+from typing import TYPE_CHECKING, Dict, Optional, Set
from BaseClasses import Location, Region
-from .data import BASE_OFFSET, NATIONAL_ID_TO_SPECIES_ID, POKEDEX_OFFSET, data
+from .data import BASE_OFFSET, NATIONAL_ID_TO_SPECIES_ID, POKEDEX_OFFSET, LocationCategory, data
from .items import offset_item_value
if TYPE_CHECKING:
from . import PokemonEmeraldWorld
-LOCATION_GROUPS = {
- "Badges": {
- "Rustboro Gym - Stone Badge",
- "Dewford Gym - Knuckle Badge",
- "Mauville Gym - Dynamo Badge",
- "Lavaridge Gym - Heat Badge",
- "Petalburg Gym - Balance Badge",
- "Fortree Gym - Feather Badge",
- "Mossdeep Gym - Mind Badge",
- "Sootopolis Gym - Rain Badge",
- },
- "Gym TMs": {
- "Rustboro Gym - TM39 from Roxanne",
- "Dewford Gym - TM08 from Brawly",
- "Mauville Gym - TM34 from Wattson",
- "Lavaridge Gym - TM50 from Flannery",
- "Petalburg Gym - TM42 from Norman",
- "Fortree Gym - TM40 from Winona",
- "Mossdeep Gym - TM04 from Tate and Liza",
- "Sootopolis Gym - TM03 from Juan",
- },
- "Trick House": {
- "Trick House Puzzle 1 - Item",
- "Trick House Puzzle 2 - Item 1",
- "Trick House Puzzle 2 - Item 2",
- "Trick House Puzzle 3 - Item 1",
- "Trick House Puzzle 3 - Item 2",
- "Trick House Puzzle 4 - Item",
- "Trick House Puzzle 6 - Item",
- "Trick House Puzzle 7 - Item",
- "Trick House Puzzle 8 - Item",
- "Trick House Puzzle 1 - Reward",
- "Trick House Puzzle 2 - Reward",
- "Trick House Puzzle 3 - Reward",
- "Trick House Puzzle 4 - Reward",
- "Trick House Puzzle 5 - Reward",
- "Trick House Puzzle 6 - Reward",
- "Trick House Puzzle 7 - Reward",
- }
-}
-
-
VISITED_EVENT_NAME_TO_ID = {
"EVENT_VISITED_LITTLEROOT_TOWN": 0,
"EVENT_VISITED_OLDALE_TOWN": 1,
@@ -80,7 +38,7 @@ class PokemonEmeraldLocation(Location):
game: str = "Pokemon Emerald"
item_address: Optional[int]
default_item_code: Optional[int]
- tags: FrozenSet[str]
+ key: Optional[str]
def __init__(
self,
@@ -88,13 +46,13 @@ def __init__(
name: str,
address: Optional[int],
parent: Optional[Region] = None,
+ key: Optional[str] = None,
item_address: Optional[int] = None,
- default_item_value: Optional[int] = None,
- tags: FrozenSet[str] = frozenset()) -> None:
+ default_item_value: Optional[int] = None) -> None:
super().__init__(player, name, address, parent)
self.default_item_code = None if default_item_value is None else offset_item_value(default_item_value)
self.item_address = item_address
- self.tags = tags
+ self.key = key
def offset_flag(flag: int) -> int:
@@ -115,16 +73,14 @@ def reverse_offset_flag(location_id: int) -> int:
return location_id - BASE_OFFSET
-def create_locations_with_tags(world: "PokemonEmeraldWorld", regions: Dict[str, Region], tags: Iterable[str]) -> None:
+def create_locations_by_category(world: "PokemonEmeraldWorld", regions: Dict[str, Region], categories: Set[LocationCategory]) -> None:
"""
Iterates through region data and adds locations to the multiworld if
those locations include any of the provided tags.
"""
- tags = set(tags)
-
for region_name, region_data in data.regions.items():
region = regions[region_name]
- filtered_locations = [loc for loc in region_data.locations if len(tags & data.locations[loc].tags) > 0]
+ filtered_locations = [loc for loc in region_data.locations if data.locations[loc].category in categories]
for location_name in filtered_locations:
location_data = data.locations[location_name]
@@ -144,9 +100,9 @@ def create_locations_with_tags(world: "PokemonEmeraldWorld", regions: Dict[str,
location_data.label,
location_id,
region,
+ location_name,
location_data.address,
- location_data.default_item,
- location_data.tags
+ location_data.default_item
)
region.locations.append(location)
diff --git a/worlds/pokemon_emerald/rules.py b/worlds/pokemon_emerald/rules.py
index 5f83686ebeec..b8d1efb1a98d 100644
--- a/worlds/pokemon_emerald/rules.py
+++ b/worlds/pokemon_emerald/rules.py
@@ -6,7 +6,8 @@
from BaseClasses import CollectionState
from worlds.generic.Rules import add_rule, set_rule
-from .data import NATIONAL_ID_TO_SPECIES_ID, NUM_REAL_SPECIES, data
+from .data import LocationCategory, NATIONAL_ID_TO_SPECIES_ID, NUM_REAL_SPECIES, data
+from .locations import PokemonEmeraldLocation
from .options import DarkCavesRequireFlash, EliteFourRequirement, NormanRequirement, Goal
if TYPE_CHECKING:
@@ -23,7 +24,7 @@ def set_rules(world: "PokemonEmeraldWorld") -> None:
state.has(hm, world.player) and state.has_all(badges, world.player)
else:
hm_rules[hm] = lambda state, hm=hm, badges=badges: \
- state.has(hm, world.player) and state.has_group_unique("Badges", world.player, badges)
+ state.has(hm, world.player) and state.has_group_unique("Badge", world.player, badges)
def has_acro_bike(state: CollectionState):
return state.has("Acro Bike", world.player)
@@ -236,11 +237,11 @@ def get_location(location: str):
if world.options.norman_requirement == NormanRequirement.option_badges:
set_rule(
get_entrance("MAP_PETALBURG_CITY_GYM:2/MAP_PETALBURG_CITY_GYM:3"),
- lambda state: state.has_group_unique("Badges", world.player, world.options.norman_count.value)
+ lambda state: state.has_group_unique("Badge", world.player, world.options.norman_count.value)
)
set_rule(
get_entrance("MAP_PETALBURG_CITY_GYM:5/MAP_PETALBURG_CITY_GYM:6"),
- lambda state: state.has_group_unique("Badges", world.player, world.options.norman_count.value)
+ lambda state: state.has_group_unique("Badge", world.player, world.options.norman_count.value)
)
else:
set_rule(
@@ -1506,7 +1507,7 @@ def get_location(location: str):
if world.options.elite_four_requirement == EliteFourRequirement.option_badges:
set_rule(
get_entrance("REGION_EVER_GRANDE_CITY_POKEMON_LEAGUE_1F/MAIN -> REGION_EVER_GRANDE_CITY_POKEMON_LEAGUE_1F/BEHIND_BADGE_CHECKERS"),
- lambda state: state.has_group_unique("Badges", world.player, world.options.elite_four_count.value)
+ lambda state: state.has_group_unique("Badge", world.player, world.options.elite_four_count.value)
)
else:
set_rule(
@@ -1659,7 +1660,8 @@ def get_location(location: str):
# Add Itemfinder requirement to hidden items
if world.options.require_itemfinder:
for location in world.multiworld.get_locations(world.player):
- if location.tags is not None and "HiddenItem" in location.tags:
+ assert isinstance(location, PokemonEmeraldLocation)
+ if location.key is not None and data.locations[location.key].category == LocationCategory.HIDDEN_ITEM:
add_rule(
location,
lambda state: state.has("Itemfinder", world.player)
diff --git a/worlds/pokemon_emerald/sanity_check.py b/worlds/pokemon_emerald/sanity_check.py
index 24eb768bfbc5..048b19b46919 100644
--- a/worlds/pokemon_emerald/sanity_check.py
+++ b/worlds/pokemon_emerald/sanity_check.py
@@ -5,8 +5,6 @@
import logging
from typing import List
-from .data import load_json_data, data
-
_IGNORABLE_LOCATIONS = frozenset({
"HIDDEN_ITEM_TRICK_HOUSE_NUGGET", # Is permanently mssiable and has special behavior that sets the flag early
@@ -247,12 +245,29 @@
})
+def validate_group_maps() -> bool:
+ from .data import data
+ from .groups import _LOCATION_GROUP_MAPS
+
+ failed = False
+
+ for group_name, map_set in _LOCATION_GROUP_MAPS.items():
+ for map_name in map_set:
+ if map_name not in data.maps:
+ failed = True
+ logging.error("Pokemon Emerald: Map named %s in location group %s does not exist", map_name, group_name)
+
+ return not failed
+
+
def validate_regions() -> bool:
"""
Verifies that Emerald's data doesn't have duplicate or missing
regions/warps/locations. Meant to catch problems during development like
forgetting to add a new location or incorrectly splitting a region.
"""
+ from .data import load_json_data, data
+
extracted_data_json = load_json_data("extracted_data.json")
error_messages: List[str] = []
warn_messages: List[str] = []
diff --git a/worlds/pokemon_rb/docs/setup_es.md b/worlds/pokemon_rb/docs/setup_es.md
index 6499c9501263..67024c5b52ec 100644
--- a/worlds/pokemon_rb/docs/setup_es.md
+++ b/worlds/pokemon_rb/docs/setup_es.md
@@ -11,8 +11,7 @@ Al usar BizHawk, esta guÃa solo es aplicable en los sistemas de Windows y Linux
- Instrucciones de instalación detalladas para BizHawk se pueden encontrar en el enlace de arriba.
- Los usuarios de Windows deben ejecutar el instalador de prerrequisitos (prereq installer) primero, que también se
encuentra en el enlace de arriba.
-- El cliente incorporado de Archipelago, que se puede encontrar [aquÃ](https://github.com/ArchipelagoMW/Archipelago/releases)
- (selecciona `Pokemon Client` durante la instalación).
+- El cliente incorporado de Archipelago, que se puede encontrar [aquÃ](https://github.com/ArchipelagoMW/Archipelago/releases).
- Los ROMs originales de Pokémon Red y/o Blue. La comunidad de Archipelago no puede proveerlos.
## Software Opcional
@@ -75,27 +74,41 @@ Y los siguientes caracteres especiales (cada uno ocupa un carácter):
## Unirse a un juego MultiWorld
-### Obtener tu parche de Pokémon
+### Generar y parchar un juego
-Cuando te unes a un juego multiworld, se te pedirá que entregues tu archivo YAML a quien lo esté organizando.
-Una vez que la generación acabe, el anfitrión te dará un enlace a tu archivo, o un .zip con los archivos de
-todos. Tu archivo tiene una extensión `.apred` o `.apblue`.
+1. Crea tu archivo de opciones (YAML).
+2. Sigue las instrucciones generales de Archipelago para [generar un juego](../../Archipelago/setup/en#generating-a-game).
+Haciendo esto se generará un archivo de salida. Tu parche tendrá la extensión de archivo `.apred` o `.apblue`.
+3. Abre `ArchipelagoLauncher.exe`
+4. Selecciona "Open Patch" en el lado izquierdo y selecciona tu parche.
+5. Si es tu primera vez parchando, se te pedirá que selecciones tu ROM original.
+6. Un archivo `.gb` parchado será creado en el mismo lugar donde está el parche.
+7. La primera vez que abras un parche con BizHawk Client, también se te pedira ubicar `EmuHawk.exe` en tu
+instalación de BizHawk.
-Haz doble clic en tu archivo `.apred` o `.apblue` para que se ejecute el cliente y realice el parcheado del ROM.
-Una vez acabe ese proceso (esto puede tardar un poco), el cliente y el emulador se abrirán automáticamente (si es que se
-ha asociado la extensión al emulador tal como fue recomendado)
+Si estás jugando una semilla single-player y no te importa tener seguimiento ni pistas, puedes terminar aqui, cerrar el
+cliente, y cargar el ROM parchado en cualquier emulador. Sin embargo, para multiworlds y otras funciones de Archipelago,
+continúa con los pasos abajo, usando el emulador BizHawk.
### Conectarse al multiserver
-Una vez ejecutado tanto el cliente como el emulador, hay que conectarlos. Abre la carpeta de instalación de Archipelago,
-luego abre `data/lua`, y simplemente arrastra el archivo `connector_pkmn_rb.lua` a la ventana principal de Emuhawk.
-(Alternativamente, puedes abrir la consola de Lua manualmente. En Emuhawk ir a Tools > Lua Console, luego ir al menú
-`Script` 〉 `Open Script`, navegar a la ubicación de `connector_pkmn_rb.lua` y seleccionarlo.)
-
-Para conectar el cliente con el servidor, simplemente pon `:` en la caja de texto superior y presiona
-enter (si el servidor tiene contraseña, en la caja de texto inferior escribir `/connect : [contraseña]`)
-
-Ahora ya estás listo para tu aventura en Kanto.
+Por defecto, abrir un parche hará los pasos del 1 al 5 automáticamente. Incluso asi, es bueno memorizarlos en caso de
+que tengas que cerrar y volver a abrir el juego por alguna razón.
+
+1. Pokémon Red/Blue usa el BizHawk Client de Archipelago. Si el cliente no está abierto desde cuando parchaste tu juego,
+puedes volverlo a abrir desde el Launcher.
+2. Asegúrate que EmuHawk esta cargando el ROM parchado.
+3. En EmuHawk, ir a `Tools > Lua Console`. Esta ventana debe quedarse abierta mientras se juega.
+4. En la ventana de Lua Console, ir a `Script > Open Script…`.
+5. Navegar a tu carpeta de instalación de Archipelago y abrir `data/lua/connector_bizhawk_generic.lua`.
+6. El emulador se puede congelar por unos segundos hasta que logre conectarse al cliente. Esto es normal. La ventana del
+BizHawk Client deberÃa indicar que se logro conectar y reconocer Pokémon Red/Blue.
+7. Para conectar el cliente al servidor, ingresa la dirección y el puerto (por ejemplo, `archipelago.gg:38281`) en el
+campo de texto superior del cliente y y haz clic en Connect.
+
+Para conectar el cliente al multiserver simplemente escribe `:` en el campo de texto superior y
+presiona enter (si el servidor usa contraseña, escribe en el campo de texto inferior
+`/connect :[contraseña]`)
## Auto-Tracking
diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py
index 5885183baa9c..943204ceaf75 100644
--- a/worlds/pokemon_rb/locations.py
+++ b/worlds/pokemon_rb/locations.py
@@ -223,7 +223,7 @@ def __init__(self, flag):
Missable(92)),
LocationData("Victory Road 2F-C", "East Item", "Full Heal", rom_addresses["Missable_Victory_Road_2F_Item_2"],
Missable(93)),
- LocationData("Victory Road 2F-W", "West Item", "TM05 Mega Kick", rom_addresses["Missable_Victory_Road_2F_Item_3"],
+ LocationData("Victory Road 2F-C", "West Item", "TM05 Mega Kick", rom_addresses["Missable_Victory_Road_2F_Item_3"],
Missable(94)),
LocationData("Victory Road 2F-NW", "North Item Near Moltres", "Guard Spec", rom_addresses["Missable_Victory_Road_2F_Item_4"],
Missable(95)),
diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py
index 53f41f4e4c3d..b9c30bb70106 100644
--- a/worlds/sc2/Locations.py
+++ b/worlds/sc2/Locations.py
@@ -1387,7 +1387,7 @@ def get_locations(world: Optional[World]) -> Tuple[LocationData, ...]:
lambda state: logic.templars_return_requirement(state)),
LocationData("The Host", "The Host: Victory", SC2LOTV_LOC_ID_OFFSET + 2100, LocationType.VICTORY,
lambda state: logic.the_host_requirement(state)),
- LocationData("The Host", "The Host: Southeast Void Shard", SC2LOTV_LOC_ID_OFFSET + 2101, LocationType.VICTORY,
+ LocationData("The Host", "The Host: Southeast Void Shard", SC2LOTV_LOC_ID_OFFSET + 2101, LocationType.EXTRA,
lambda state: logic.the_host_requirement(state)),
LocationData("The Host", "The Host: South Void Shard", SC2LOTV_LOC_ID_OFFSET + 2102, LocationType.EXTRA,
lambda state: logic.the_host_requirement(state)),
diff --git a/worlds/sc2/MissionTables.py b/worlds/sc2/MissionTables.py
index 4dece46411bf..08e1f133deda 100644
--- a/worlds/sc2/MissionTables.py
+++ b/worlds/sc2/MissionTables.py
@@ -43,6 +43,9 @@ def __init__(self, campaign_id: int, name: str, goal_priority: SC2CampaignGoalPr
self.goal_priority = goal_priority
self.race = race
+ def __lt__(self, other: "SC2Campaign"):
+ return self.id < other.id
+
GLOBAL = 0, "Global", SC2CampaignGoalPriority.NONE, SC2Race.ANY
WOL = 1, "Wings of Liberty", SC2CampaignGoalPriority.VERY_HARD, SC2Race.TERRAN
PROPHECY = 2, "Prophecy", SC2CampaignGoalPriority.MINI_CAMPAIGN, SC2Race.PROTOSS
diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py
index 84830a9a32bd..273bc4a5e87c 100644
--- a/worlds/sc2/Regions.py
+++ b/worlds/sc2/Regions.py
@@ -50,7 +50,7 @@ def create_vanilla_regions(
names: Dict[str, int] = {}
# Generating all regions and locations for each enabled campaign
- for campaign in enabled_campaigns:
+ for campaign in sorted(enabled_campaigns):
for region_name in vanilla_mission_req_table[campaign].keys():
regions.append(create_region(world, locations_per_region, location_cache, region_name))
world.multiworld.regions += regions
diff --git a/worlds/sm64ex/Options.py b/worlds/sm64ex/Options.py
index 8269d3a262cd..6cf233558ce2 100644
--- a/worlds/sm64ex/Options.py
+++ b/worlds/sm64ex/Options.py
@@ -1,6 +1,6 @@
import typing
from dataclasses import dataclass
-from Options import DefaultOnToggle, Range, Toggle, DeathLink, Choice, PerGameCommonOptions, OptionSet
+from Options import DefaultOnToggle, Range, Toggle, DeathLink, Choice, PerGameCommonOptions, OptionSet, OptionGroup
from .Items import action_item_table
class EnableCoinStars(DefaultOnToggle):
@@ -127,6 +127,32 @@ class MoveRandomizerActions(OptionSet):
valid_keys = [action for action in action_item_table if action != 'Double Jump']
default = valid_keys
+sm64_options_groups = [
+ OptionGroup("Logic Options", [
+ AreaRandomizer,
+ BuddyChecks,
+ ExclamationBoxes,
+ ProgressiveKeys,
+ EnableCoinStars,
+ StrictCapRequirements,
+ StrictCannonRequirements,
+ ]),
+ OptionGroup("Ability Options", [
+ EnableMoveRandomizer,
+ MoveRandomizerActions,
+ StrictMoveRequirements,
+ ]),
+ OptionGroup("Star Options", [
+ AmountOfStars,
+ FirstBowserStarDoorCost,
+ BasementStarDoorCost,
+ SecondFloorStarDoorCost,
+ MIPS1Cost,
+ MIPS2Cost,
+ StarsToFinish,
+ ]),
+]
+
@dataclass
class SM64Options(PerGameCommonOptions):
area_rando: AreaRandomizer
diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py
index d4bafbafcc57..40c778ebe66c 100644
--- a/worlds/sm64ex/__init__.py
+++ b/worlds/sm64ex/__init__.py
@@ -3,7 +3,7 @@
import json
from .Items import item_table, action_item_table, cannon_item_table, SM64Item
from .Locations import location_table, SM64Location
-from .Options import SM64Options
+from .Options import sm64_options_groups, SM64Options
from .Rules import set_rules
from .Regions import create_regions, sm64_level_to_entrances, SM64Levels
from BaseClasses import Item, Tutorial, ItemClassification, Region
@@ -20,6 +20,8 @@ class SM64Web(WebWorld):
["N00byKing"]
)]
+ option_groups = sm64_options_groups
+
class SM64World(World):
"""
diff --git a/worlds/smz3/Options.py b/worlds/smz3/Options.py
index 7df01f8710e1..02521d695a7a 100644
--- a/worlds/smz3/Options.py
+++ b/worlds/smz3/Options.py
@@ -1,5 +1,6 @@
import typing
-from Options import Choice, Option, PerGameCommonOptions, Toggle, DefaultOnToggle, Range, ItemsAccessibility
+
+from Options import Choice, Option, PerGameCommonOptions, Toggle, DefaultOnToggle, Range, ItemsAccessibility, StartInventoryPool
from dataclasses import dataclass
class SMLogic(Choice):
@@ -129,6 +130,7 @@ class EnergyBeep(DefaultOnToggle):
@dataclass
class SMZ3Options(PerGameCommonOptions):
+ start_inventory_from_pool: StartInventoryPool
accessibility: ItemsAccessibility
sm_logic: SMLogic
sword_location: SwordLocation
diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py
index 292ee69fdf9a..f35804707a1a 100644
--- a/worlds/stardew_valley/__init__.py
+++ b/worlds/stardew_valley/__init__.py
@@ -3,7 +3,7 @@
from typing import Dict, Any, Iterable, Optional, Union, List, TextIO
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState
-from Options import PerGameCommonOptions
+from Options import PerGameCommonOptions, Accessibility
from worlds.AutoWorld import World, WebWorld
from . import rules
from .bundles.bundle_room import BundleRoom
@@ -91,15 +91,14 @@ class StardewValleyWorld(World):
web = StardewWebWorld()
modified_bundles: List[BundleRoom]
randomized_entrances: Dict[str, str]
- total_progression_items: int
- # all_progression_items: Dict[str, int] # If you need to debug total_progression_items, uncommenting this will help tremendously
+ total_progression_items: int
+ excluded_from_total_progression_items: List[str] = [Event.received_walnuts]
def __init__(self, multiworld: MultiWorld, player: int):
super().__init__(multiworld, player)
self.filler_item_pool_names = []
self.total_progression_items = 0
- # self.all_progression_items = dict()
# Taking the seed specified in slot data for UT, otherwise just generating the seed.
self.seed = getattr(multiworld, "re_gen_passthrough", {}).get(STARDEW_VALLEY, self.random.getrandbits(64))
@@ -121,17 +120,27 @@ def force_change_options_if_incompatible(self):
goal_is_perfection = self.options.goal == Goal.option_perfection
goal_is_island_related = goal_is_walnut_hunter or goal_is_perfection
exclude_ginger_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
+
if goal_is_island_related and exclude_ginger_island:
self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false
goal_name = self.options.goal.current_key
- player_name = self.multiworld.player_name[self.player]
logger.warning(
- f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
+ f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({self.player_name})")
+
if exclude_ginger_island and self.options.walnutsanity != Walnutsanity.preset_none:
self.options.walnutsanity.value = Walnutsanity.preset_none
- player_name = self.multiworld.player_name[self.player]
logger.warning(
- f"Walnutsanity requires Ginger Island. Ginger Island was excluded from {self.player} ({player_name})'s world, so walnutsanity was force disabled")
+ f"Walnutsanity requires Ginger Island. Ginger Island was excluded from {self.player} ({self.player_name})'s world, so walnutsanity was force disabled")
+
+ if goal_is_perfection and self.options.accessibility == Accessibility.option_minimal:
+ self.options.accessibility.value = Accessibility.option_full
+ logger.warning(
+ f"Goal 'Perfection' requires full accessibility. Accessibility setting forced to 'Full' for player {self.player} ({self.player_name})")
+
+ elif self.options.goal == Goal.option_allsanity and self.options.accessibility == Accessibility.option_minimal:
+ self.options.accessibility.value = Accessibility.option_full
+ logger.warning(
+ f"Goal 'Allsanity' requires full accessibility. Accessibility setting forced to 'Full' for player {self.player} ({self.player_name})")
def create_regions(self):
def create_region(name: str, exits: Iterable[str]) -> Region:
@@ -171,8 +180,7 @@ def create_items(self):
for location in self.multiworld.get_locations(self.player)
if location.address is not None])
- created_items = create_items(self.create_item, self.delete_item, locations_count, items_to_exclude, self.options, self.content,
- self.random)
+ created_items = create_items(self.create_item, locations_count, items_to_exclude, self.options, self.content, self.random)
self.multiworld.itempool += created_items
@@ -180,6 +188,18 @@ def create_items(self):
self.setup_player_events()
self.setup_victory()
+ # This is really a best-effort to get the total progression items count. It is mostly used to spread grinds across spheres are push back locations that
+ # only become available after months or years in game. In most cases, not having the exact count will not impact the logic.
+ #
+ # The actual total can be impacted by the start_inventory_from_pool, when items are removed from the pool but not from the total. The is also a bug
+ # with plando where additional progression items can be created without being accounted for, which impact the real amount of progression items. This can
+ # ultimately create unwinnable seeds where some items (like Blueberry seeds) are locked in Shipsanity: Blueberry, but world is deemed winnable as the
+ # winning rule only check the count of collected progression items.
+ self.total_progression_items += sum(1 for i in self.multiworld.precollected_items[self.player] if i.advancement)
+ self.total_progression_items += sum(1 for i in self.multiworld.get_filled_locations(self.player) if i.advancement)
+ self.total_progression_items += sum(1 for i in created_items if i.advancement)
+ self.total_progression_items -= 1 # -1 for the victory event
+
def precollect_starting_season(self):
if self.options.season_randomization == SeasonRandomization.option_progressive:
return
@@ -206,25 +226,10 @@ def precollect_farm_type_items(self):
self.multiworld.push_precollected(self.create_starting_item("Progressive Coop"))
def setup_player_events(self):
- self.setup_construction_events()
- self.setup_quest_events()
self.setup_action_events()
self.setup_logic_events()
- def setup_construction_events(self):
- can_construct_buildings = LocationData(None, RegionName.carpenter, Event.can_construct_buildings)
- self.create_event_location(can_construct_buildings, True_(), Event.can_construct_buildings)
-
- def setup_quest_events(self):
- start_dark_talisman_quest = LocationData(None, RegionName.railroad, Event.start_dark_talisman_quest)
- self.create_event_location(start_dark_talisman_quest, self.logic.wallet.has_rusty_key(), Event.start_dark_talisman_quest)
-
def setup_action_events(self):
- can_ship_event = LocationData(None, LogicRegion.shipping, Event.can_ship_items)
- self.create_event_location(can_ship_event, true_, Event.can_ship_items)
- can_shop_pierre_event = LocationData(None, RegionName.pierre_store, Event.can_shop_at_pierre)
- self.create_event_location(can_shop_pierre_event, true_, Event.can_shop_at_pierre)
-
spring_farming = LocationData(None, LogicRegion.spring_farming, Event.spring_farming)
self.create_event_location(spring_farming, true_, Event.spring_farming)
summer_farming = LocationData(None, LogicRegion.summer_farming, Event.summer_farming)
@@ -319,14 +324,8 @@ def create_item(self, item: Union[str, ItemData], override_classification: ItemC
if override_classification is None:
override_classification = item.classification
- if override_classification == ItemClassification.progression:
- self.total_progression_items += 1
return StardewItem(item.name, override_classification, item.code, self.player)
- def delete_item(self, item: Item):
- if item.classification & ItemClassification.progression:
- self.total_progression_items -= 1
-
def create_starting_item(self, item: Union[str, ItemData]) -> StardewItem:
if isinstance(item, str):
item = item_table[item]
@@ -345,10 +344,6 @@ def create_event_location(self, location_data: LocationData, rule: StardewRule =
region.locations.append(location)
location.place_locked_item(StardewItem(item, ItemClassification.progression, None, self.player))
- # This is not ideal, but the rule count them so...
- if item != Event.victory:
- self.total_progression_items += 1
-
def set_rules(self):
set_rules(self)
diff --git a/worlds/stardew_valley/content/unpacking.py b/worlds/stardew_valley/content/unpacking.py
index f069866d56cd..3c57f91afe3a 100644
--- a/worlds/stardew_valley/content/unpacking.py
+++ b/worlds/stardew_valley/content/unpacking.py
@@ -1,16 +1,12 @@
from __future__ import annotations
+from graphlib import TopologicalSorter
from typing import Iterable, Mapping, Callable
from .game_content import StardewContent, ContentPack, StardewFeatures
from .vanilla.base import base_game as base_game_content_pack
from ..data.game_item import GameItem, ItemSource
-try:
- from graphlib import TopologicalSorter
-except ImportError:
- from graphlib_backport import TopologicalSorter # noqa
-
def unpack_content(features: StardewFeatures, packs: Iterable[ContentPack]) -> StardewContent:
# Base game is always registered first.
diff --git a/worlds/stardew_valley/data/artisan.py b/worlds/stardew_valley/data/artisan.py
index 593ab6a3ddf0..90be5b1684f0 100644
--- a/worlds/stardew_valley/data/artisan.py
+++ b/worlds/stardew_valley/data/artisan.py
@@ -1,9 +1,9 @@
from dataclasses import dataclass
-from .game_item import kw_only, ItemSource
+from .game_item import ItemSource
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class MachineSource(ItemSource):
item: str # this should be optional (worm bin)
machine: str
diff --git a/worlds/stardew_valley/data/game_item.py b/worlds/stardew_valley/data/game_item.py
index 6c8d30ed8e6f..c6e4717cd1e0 100644
--- a/worlds/stardew_valley/data/game_item.py
+++ b/worlds/stardew_valley/data/game_item.py
@@ -1,5 +1,4 @@
import enum
-import sys
from abc import ABC
from dataclasses import dataclass, field
from types import MappingProxyType
@@ -7,11 +6,6 @@
from ..stardew_rule.protocol import StardewRule
-if sys.version_info >= (3, 10):
- kw_only = {"kw_only": True}
-else:
- kw_only = {}
-
DEFAULT_REQUIREMENT_TAGS = MappingProxyType({})
@@ -36,21 +30,17 @@ class ItemTag(enum.Enum):
class ItemSource(ABC):
add_tags: ClassVar[Tuple[ItemTag]] = ()
+ other_requirements: Tuple[Requirement, ...] = field(kw_only=True, default_factory=tuple)
+
@property
def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]:
return DEFAULT_REQUIREMENT_TAGS
- # FIXME this should just be an optional field, but kw_only requires python 3.10...
- @property
- def other_requirements(self) -> Iterable[Requirement]:
- return ()
-
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class GenericSource(ItemSource):
regions: Tuple[str, ...] = ()
"""No region means it's available everywhere."""
- other_requirements: Tuple[Requirement, ...] = ()
@dataclass(frozen=True)
@@ -59,7 +49,7 @@ class CustomRuleSource(ItemSource):
create_rule: Callable[[Any], StardewRule]
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class CompoundSource(ItemSource):
sources: Tuple[ItemSource, ...] = ()
diff --git a/worlds/stardew_valley/data/harvest.py b/worlds/stardew_valley/data/harvest.py
index 087d7c3fa86b..0fdae9549587 100644
--- a/worlds/stardew_valley/data/harvest.py
+++ b/worlds/stardew_valley/data/harvest.py
@@ -1,18 +1,17 @@
from dataclasses import dataclass
from typing import Tuple, Sequence, Mapping
-from .game_item import ItemSource, kw_only, ItemTag, Requirement
+from .game_item import ItemSource, ItemTag
from ..strings.season_names import Season
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class ForagingSource(ItemSource):
regions: Tuple[str, ...]
seasons: Tuple[str, ...] = Season.all
- other_requirements: Tuple[Requirement, ...] = ()
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class SeasonalForagingSource(ItemSource):
season: str
days: Sequence[int]
@@ -22,17 +21,17 @@ def as_foraging_source(self) -> ForagingSource:
return ForagingSource(seasons=(self.season,), regions=self.regions)
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class FruitBatsSource(ItemSource):
...
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class MushroomCaveSource(ItemSource):
...
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class HarvestFruitTreeSource(ItemSource):
add_tags = (ItemTag.CROPSANITY,)
@@ -46,7 +45,7 @@ def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]:
}
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class HarvestCropSource(ItemSource):
add_tags = (ItemTag.CROPSANITY,)
@@ -61,6 +60,6 @@ def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]:
}
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class ArtifactSpotSource(ItemSource):
amount: int
diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv
index ffcae223e251..05af275ba472 100644
--- a/worlds/stardew_valley/data/items.csv
+++ b/worlds/stardew_valley/data/items.csv
@@ -7,7 +7,7 @@ id,name,classification,groups,mod_name
19,Glittering Boulder Removed,progression,COMMUNITY_REWARD,
20,Minecarts Repair,useful,COMMUNITY_REWARD,
21,Bus Repair,progression,COMMUNITY_REWARD,
-22,Progressive Movie Theater,progression,COMMUNITY_REWARD,
+22,Progressive Movie Theater,"progression,trap",COMMUNITY_REWARD,
23,Stardrop,progression,,
24,Progressive Backpack,progression,,
25,Rusty Sword,filler,"WEAPON,DEPRECATED",
diff --git a/worlds/stardew_valley/data/shop.py b/worlds/stardew_valley/data/shop.py
index f14dbac82131..cc9506023f19 100644
--- a/worlds/stardew_valley/data/shop.py
+++ b/worlds/stardew_valley/data/shop.py
@@ -1,40 +1,39 @@
from dataclasses import dataclass
from typing import Tuple, Optional
-from .game_item import ItemSource, kw_only, Requirement
+from .game_item import ItemSource
from ..strings.season_names import Season
ItemPrice = Tuple[int, str]
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class ShopSource(ItemSource):
shop_region: str
money_price: Optional[int] = None
items_price: Optional[Tuple[ItemPrice, ...]] = None
seasons: Tuple[str, ...] = Season.all
- other_requirements: Tuple[Requirement, ...] = ()
def __post_init__(self):
assert self.money_price is not None or self.items_price is not None, "At least money price or items price need to be defined."
assert self.items_price is None or all(isinstance(p, tuple) for p in self.items_price), "Items price should be a tuple."
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class MysteryBoxSource(ItemSource):
amount: int
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class ArtifactTroveSource(ItemSource):
amount: int
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class PrizeMachineSource(ItemSource):
amount: int
-@dataclass(frozen=True, **kw_only)
+@dataclass(frozen=True, kw_only=True)
class FishingTreasureChestSource(ItemSource):
amount: int
diff --git a/worlds/stardew_valley/data/skill.py b/worlds/stardew_valley/data/skill.py
index d0674f34c0e1..4c754ddd8716 100644
--- a/worlds/stardew_valley/data/skill.py
+++ b/worlds/stardew_valley/data/skill.py
@@ -1,9 +1,7 @@
from dataclasses import dataclass, field
-from ..data.game_item import kw_only
-
@dataclass(frozen=True)
class Skill:
name: str
- has_mastery: bool = field(**kw_only)
+ has_mastery: bool = field(kw_only=True)
diff --git a/worlds/stardew_valley/docs/en_Stardew Valley.md b/worlds/stardew_valley/docs/en_Stardew Valley.md
index 0ed693031b82..62755dad798d 100644
--- a/worlds/stardew_valley/docs/en_Stardew Valley.md
+++ b/worlds/stardew_valley/docs/en_Stardew Valley.md
@@ -138,7 +138,7 @@ This means that, for these specific mods, if you decide to include them in your
with the assumption that you will install and play with these mods. The multiworld will contain related items and locations
for these mods, the specifics will vary from mod to mod
-[Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md)
+[Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md)
List of supported mods:
diff --git a/worlds/stardew_valley/docs/setup_en.md b/worlds/stardew_valley/docs/setup_en.md
index c672152543cf..801bf345e916 100644
--- a/worlds/stardew_valley/docs/setup_en.md
+++ b/worlds/stardew_valley/docs/setup_en.md
@@ -12,7 +12,7 @@
- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
* (Only for the TextClient)
- Other Stardew Valley Mods [Nexus Mods](https://www.nexusmods.com/stardewvalley)
- * There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md)
+ * There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md)
that you can add to your yaml to include them with the Archipelago randomization
* It is **not** recommended to further mod Stardew Valley with unsupported mods, although it is possible to do so.
diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py
index 31c7da5e3ade..5bf59a5e8397 100644
--- a/worlds/stardew_valley/items.py
+++ b/worlds/stardew_valley/items.py
@@ -2,6 +2,7 @@
import enum
import logging
from dataclasses import dataclass, field
+from functools import reduce
from pathlib import Path
from random import Random
from typing import Dict, List, Protocol, Union, Set, Optional
@@ -124,17 +125,14 @@ def __call__(self, item: Item):
def load_item_csv():
- try:
- from importlib.resources import files
- except ImportError:
- from importlib_resources import files # noqa
+ from importlib.resources import files
items = []
with files(data).joinpath("items.csv").open() as file:
item_reader = csv.DictReader(file)
for item in item_reader:
id = int(item["id"]) if item["id"] else None
- classification = ItemClassification[item["classification"]]
+ classification = reduce((lambda a, b: a | b), {ItemClassification[str_classification] for str_classification in item["classification"].split(",")})
groups = {Group[group] for group in item["groups"].split(",") if group}
mod_name = str(item["mod_name"]) if item["mod_name"] else None
items.append(ItemData(id, item["name"], classification, mod_name, groups))
@@ -171,14 +169,14 @@ def get_too_many_items_error_message(locations_count: int, items_count: int) ->
return f"There should be at least as many locations [{locations_count}] as there are mandatory items [{items_count}]"
-def create_items(item_factory: StardewItemFactory, item_deleter: StardewItemDeleter, locations_count: int, items_to_exclude: List[Item],
+def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item],
options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
items = []
unique_items = create_unique_items(item_factory, options, content, random)
- remove_items(item_deleter, items_to_exclude, unique_items)
+ remove_items(items_to_exclude, unique_items)
- remove_items_if_no_room_for_them(item_deleter, unique_items, locations_count, random)
+ remove_items_if_no_room_for_them(unique_items, locations_count, random)
items += unique_items
logger.debug(f"Created {len(unique_items)} unique items")
@@ -194,14 +192,13 @@ def create_items(item_factory: StardewItemFactory, item_deleter: StardewItemDele
return items
-def remove_items(item_deleter: StardewItemDeleter, items_to_remove, items):
+def remove_items(items_to_remove, items):
for item in items_to_remove:
if item in items:
items.remove(item)
- item_deleter(item)
-def remove_items_if_no_room_for_them(item_deleter: StardewItemDeleter, unique_items: List[Item], locations_count: int, random: Random):
+def remove_items_if_no_room_for_them(unique_items: List[Item], locations_count: int, random: Random):
if len(unique_items) <= locations_count:
return
@@ -214,7 +211,7 @@ def remove_items_if_no_room_for_them(item_deleter: StardewItemDeleter, unique_it
logger.debug(f"Player has more items than locations, trying to remove {number_of_items_to_remove} random filler items")
assert len(removable_items) >= number_of_items_to_remove, get_too_many_items_error_message(locations_count, len(unique_items))
items_to_remove = random.sample(removable_items, number_of_items_to_remove)
- remove_items(item_deleter, items_to_remove, unique_items)
+ remove_items(items_to_remove, unique_items)
def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
diff --git a/worlds/stardew_valley/locations.py b/worlds/stardew_valley/locations.py
index 43246a94a356..1d67d535ccee 100644
--- a/worlds/stardew_valley/locations.py
+++ b/worlds/stardew_valley/locations.py
@@ -130,10 +130,7 @@ def __call__(self, name: str, code: Optional[int], region: str) -> None:
def load_location_csv() -> List[LocationData]:
- try:
- from importlib.resources import files
- except ImportError:
- from importlib_resources import files
+ from importlib.resources import files
with files(data).joinpath("locations.csv").open() as file:
reader = csv.DictReader(file)
diff --git a/worlds/stardew_valley/logic/building_logic.py b/worlds/stardew_valley/logic/building_logic.py
index 4611eba37d64..b4eff4399385 100644
--- a/worlds/stardew_valley/logic/building_logic.py
+++ b/worlds/stardew_valley/logic/building_logic.py
@@ -1,3 +1,4 @@
+from functools import cached_property
from typing import Dict, Union
from Utils import cache_self1
@@ -8,12 +9,12 @@
from .region_logic import RegionLogicMixin
from ..options import BuildingProgression
from ..stardew_rule import StardewRule, True_, False_, Has
-from ..strings.ap_names.event_names import Event
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.fish_names import WaterItem
from ..strings.material_names import Material
from ..strings.metal_names import MetalBar
+from ..strings.region_names import Region
has_group = "building"
@@ -60,7 +61,7 @@ def has_building(self, building: str) -> StardewRule:
return True_()
return self.logic.received(building)
- carpenter_rule = self.logic.received(Event.can_construct_buildings)
+ carpenter_rule = self.logic.building.can_construct_buildings
if not self.options.building_progression & BuildingProgression.option_progressive:
return Has(building, self.registry.building_rules, has_group) & carpenter_rule
@@ -75,6 +76,10 @@ def has_building(self, building: str) -> StardewRule:
building = " ".join(["Progressive", *building.split(" ")[1:]])
return self.logic.received(building, count) & carpenter_rule
+ @cached_property
+ def can_construct_buildings(self) -> StardewRule:
+ return self.logic.region.can_reach(Region.carpenter)
+
@cache_self1
def has_house(self, upgrade_level: int) -> StardewRule:
if upgrade_level < 1:
@@ -83,7 +88,7 @@ def has_house(self, upgrade_level: int) -> StardewRule:
if upgrade_level > 3:
return False_()
- carpenter_rule = self.logic.received(Event.can_construct_buildings)
+ carpenter_rule = self.logic.building.can_construct_buildings
if self.options.building_progression & BuildingProgression.option_progressive:
return carpenter_rule & self.logic.received(f"Progressive House", upgrade_level)
diff --git a/worlds/stardew_valley/logic/money_logic.py b/worlds/stardew_valley/logic/money_logic.py
index 73c5291af082..85370273c987 100644
--- a/worlds/stardew_valley/logic/money_logic.py
+++ b/worlds/stardew_valley/logic/money_logic.py
@@ -1,3 +1,4 @@
+import typing
from typing import Union
from Utils import cache_self1
@@ -11,10 +12,14 @@
from ..data.shop import ShopSource
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_, true_
-from ..strings.ap_names.event_names import Event
from ..strings.currency_names import Currency
from ..strings.region_names import Region, LogicRegion
+if typing.TYPE_CHECKING:
+ from .shipping_logic import ShippingLogicMixin
+
+ assert ShippingLogicMixin
+
qi_gem_rewards = ("100 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems", "25 Qi Gems",
"20 Qi Gems", "15 Qi Gems", "10 Qi Gems")
@@ -26,7 +31,7 @@ def __init__(self, *args, **kwargs):
class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SeasonLogicMixin,
-GrindLogicMixin]]):
+GrindLogicMixin, 'ShippingLogicMixin']]):
@cache_self1
def can_have_earned_total(self, amount: int) -> StardewRule:
@@ -37,7 +42,7 @@ def can_have_earned_total(self, amount: int) -> StardewRule:
willy_rule = self.logic.region.can_reach_all((Region.fish_shop, LogicRegion.fishing))
clint_rule = self.logic.region.can_reach_all((Region.blacksmith, Region.mines_floor_5))
robin_rule = self.logic.region.can_reach_all((Region.carpenter, Region.secret_woods))
- shipping_rule = self.logic.received(Event.can_ship_items)
+ shipping_rule = self.logic.shipping.can_use_shipping_bin
if amount < 2000:
selling_any_rule = pierre_rule | willy_rule | clint_rule | robin_rule | shipping_rule
@@ -50,7 +55,7 @@ def can_have_earned_total(self, amount: int) -> StardewRule:
if amount < 10000:
return shipping_rule
- seed_rules = self.logic.received(Event.can_shop_at_pierre)
+ seed_rules = self.logic.region.can_reach(Region.pierre_store)
if amount < 40000:
return shipping_rule & seed_rules
diff --git a/worlds/stardew_valley/logic/shipping_logic.py b/worlds/stardew_valley/logic/shipping_logic.py
index 8d545e219627..e9f2258172e6 100644
--- a/worlds/stardew_valley/logic/shipping_logic.py
+++ b/worlds/stardew_valley/logic/shipping_logic.py
@@ -11,7 +11,6 @@
from ..options import ExcludeGingerIsland, Shipsanity
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule
-from ..strings.ap_names.event_names import Event
from ..strings.building_names import Building
@@ -29,7 +28,7 @@ def can_use_shipping_bin(self) -> StardewRule:
@cache_self1
def can_ship(self, item: str) -> StardewRule:
- return self.logic.received(Event.can_ship_items) & self.logic.has(item)
+ return self.logic.shipping.can_use_shipping_bin & self.logic.has(item)
def can_ship_everything(self) -> StardewRule:
shipsanity_prefix = "Shipsanity: "
@@ -49,7 +48,7 @@ def can_ship_everything(self) -> StardewRule:
def can_ship_everything_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
if self.options.shipsanity == Shipsanity.option_none:
- return self.can_ship_everything()
+ return self.logic.shipping.can_ship_everything()
rules = [self.logic.building.has_building(Building.shipping_bin)]
diff --git a/worlds/stardew_valley/logic/special_order_logic.py b/worlds/stardew_valley/logic/special_order_logic.py
index 65497df477b8..8bcd78d7d26e 100644
--- a/worlds/stardew_valley/logic/special_order_logic.py
+++ b/worlds/stardew_valley/logic/special_order_logic.py
@@ -21,7 +21,6 @@
from ..content.vanilla.qi_board import qi_board_content_pack
from ..stardew_rule import StardewRule, Has, false_
from ..strings.animal_product_names import AnimalProduct
-from ..strings.ap_names.event_names import Event
from ..strings.ap_names.transport_names import Transportation
from ..strings.artisan_good_names import ArtisanGood
from ..strings.crop_names import Vegetable, Fruit
@@ -61,7 +60,7 @@ def initialize_rules(self):
SpecialOrder.gifts_for_george: self.logic.season.has(Season.spring) & self.logic.has(Forageable.leek),
SpecialOrder.fragments_of_the_past: self.logic.monster.can_kill(Monster.skeleton),
SpecialOrder.gus_famous_omelet: self.logic.has(AnimalProduct.any_egg),
- SpecialOrder.crop_order: self.logic.ability.can_farm_perfectly() & self.logic.received(Event.can_ship_items),
+ SpecialOrder.crop_order: self.logic.ability.can_farm_perfectly() & self.logic.shipping.can_use_shipping_bin,
SpecialOrder.community_cleanup: self.logic.skill.can_crab_pot,
SpecialOrder.the_strong_stuff: self.logic.has(ArtisanGood.specific_juice(Vegetable.potato)),
SpecialOrder.pierres_prime_produce: self.logic.ability.can_farm_perfectly(),
@@ -94,12 +93,12 @@ def initialize_rules(self):
self.update_rules({
SpecialOrder.qis_crop: self.logic.ability.can_farm_perfectly() & self.logic.region.can_reach(Region.greenhouse) &
self.logic.region.can_reach(Region.island_west) & self.logic.skill.has_total_level(50) &
- self.logic.has(Machine.seed_maker) & self.logic.received(Event.can_ship_items),
+ self.logic.has(Machine.seed_maker) & self.logic.shipping.can_use_shipping_bin,
SpecialOrder.lets_play_a_game: self.logic.arcade.has_junimo_kart_max_level(),
SpecialOrder.four_precious_stones: self.logic.time.has_lived_max_months & self.logic.has("Prismatic Shard") &
self.logic.ability.can_mine_perfectly_in_the_skull_cavern(),
SpecialOrder.qis_hungry_challenge: self.logic.ability.can_mine_perfectly_in_the_skull_cavern(),
- SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.received(Event.can_ship_items) &
+ SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.shipping.can_use_shipping_bin &
(self.logic.money.can_spend_at(Region.saloon, 205000) | self.logic.money.can_spend_at(Region.pierre_store, 170000)),
SpecialOrder.qis_kindness: self.logic.relationship.can_give_loved_gifts_to_everyone(),
SpecialOrder.extended_family: self.logic.ability.can_fish_perfectly() & self.logic.has(Fish.angler) & self.logic.has(Fish.glacierfish) &
diff --git a/worlds/stardew_valley/requirements.txt b/worlds/stardew_valley/requirements.txt
deleted file mode 100644
index 65e922a64483..000000000000
--- a/worlds/stardew_valley/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-importlib_resources; python_version <= '3.8'
-graphlib_backport; python_version <= '3.8'
diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py
index eda2d4377e09..e7107e89f948 100644
--- a/worlds/stardew_valley/rules.py
+++ b/worlds/stardew_valley/rules.py
@@ -27,7 +27,6 @@
from .stardew_rule.rule_explain import explain
from .strings.ap_names.ap_option_names import OptionName
from .strings.ap_names.community_upgrade_names import CommunityUpgrade
-from .strings.ap_names.event_names import Event
from .strings.ap_names.mods.mod_items import SVEQuestItem, SVERunes
from .strings.ap_names.transport_names import Transportation
from .strings.artisan_good_names import ArtisanGood
@@ -251,7 +250,8 @@ def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: S
set_entrance_rule(multiworld, player, Entrance.enter_witch_warp_cave, logic.quest.has_dark_talisman() | (logic.mod.magic.can_blink()))
set_entrance_rule(multiworld, player, Entrance.enter_witch_hut, (logic.has(ArtisanGood.void_mayonnaise) | logic.mod.magic.can_blink()))
set_entrance_rule(multiworld, player, Entrance.enter_mutant_bug_lair,
- (logic.received(Event.start_dark_talisman_quest) & logic.relationship.can_meet(NPC.krobus)) | logic.mod.magic.can_blink())
+ (logic.wallet.has_rusty_key() & logic.region.can_reach(Region.railroad) & logic.relationship.can_meet(
+ NPC.krobus)) | logic.mod.magic.can_blink())
set_entrance_rule(multiworld, player, Entrance.enter_casino, logic.quest.has_club_card())
set_bedroom_entrance_rules(logic, multiworld, player, world_options)
@@ -307,8 +307,7 @@ def set_mines_floor_entrance_rules(logic, multiworld, player):
rule = logic.mine.has_mine_elevator_to_floor(floor - 10)
if floor == 5 or floor == 45 or floor == 85:
rule = rule & logic.mine.can_progress_in_the_mines_from_floor(floor)
- entrance = multiworld.get_entrance(dig_to_mines_floor(floor), player)
- MultiWorldRules.set_rule(entrance, rule)
+ set_entrance_rule(multiworld, player, dig_to_mines_floor(floor), rule)
def set_skull_cavern_floor_entrance_rules(logic, multiworld, player):
@@ -316,8 +315,7 @@ def set_skull_cavern_floor_entrance_rules(logic, multiworld, player):
rule = logic.mod.elevator.has_skull_cavern_elevator_to_floor(floor - 25)
if floor == 25 or floor == 75 or floor == 125:
rule = rule & logic.mine.can_progress_in_the_skull_cavern_from_floor(floor)
- entrance = multiworld.get_entrance(dig_to_skull_floor(floor), player)
- MultiWorldRules.set_rule(entrance, rule)
+ set_entrance_rule(multiworld, player, dig_to_skull_floor(floor), rule)
def set_blacksmith_entrance_rules(logic, multiworld, player):
@@ -346,9 +344,8 @@ def set_skill_entrance_rules(logic, multiworld, player, world_options: StardewVa
def set_blacksmith_upgrade_rule(logic, multiworld, player, entrance_name: str, item_name: str, tool_material: str):
- material_entrance = multiworld.get_entrance(entrance_name, player)
upgrade_rule = logic.has(item_name) & logic.money.can_spend(tool_upgrade_prices[tool_material])
- MultiWorldRules.set_rule(material_entrance, upgrade_rule)
+ set_entrance_rule(multiworld, player, entrance_name, upgrade_rule)
def set_festival_entrance_rules(logic, multiworld, player):
@@ -880,25 +877,19 @@ def set_traveling_merchant_day_rules(logic: StardewLogic, multiworld: MultiWorld
def set_arcade_machine_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions):
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_junimo_kart, player),
- logic.received(Wallet.skull_key))
+ play_junimo_kart_rule = logic.received(Wallet.skull_key)
+
if world_options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling:
+ set_entrance_rule(multiworld, player, Entrance.play_junimo_kart, play_junimo_kart_rule)
return
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_junimo_kart, player),
- logic.has("Junimo Kart Small Buff"))
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_2, player),
- logic.has("Junimo Kart Medium Buff"))
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_3, player),
- logic.has("Junimo Kart Big Buff"))
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_4, player),
- logic.has("Junimo Kart Max Buff"))
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_journey_of_the_prairie_king, player),
- logic.has("JotPK Small Buff"))
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_jotpk_world_2, player),
- logic.has("JotPK Medium Buff"))
- MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_jotpk_world_3, player),
- logic.has("JotPK Big Buff"))
+ set_entrance_rule(multiworld, player, Entrance.play_junimo_kart, play_junimo_kart_rule & logic.has("Junimo Kart Small Buff"))
+ set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_2, logic.has("Junimo Kart Medium Buff"))
+ set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_3, logic.has("Junimo Kart Big Buff"))
+ set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_4, logic.has("Junimo Kart Max Buff"))
+ set_entrance_rule(multiworld, player, Entrance.play_journey_of_the_prairie_king, logic.has("JotPK Small Buff"))
+ set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_2, logic.has("JotPK Medium Buff"))
+ set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_3, logic.has("JotPK Big Buff"))
MultiWorldRules.add_rule(multiworld.get_location("Journey of the Prairie King Victory", player),
logic.has("JotPK Max Buff"))
@@ -1049,6 +1040,7 @@ def set_entrance_rule(multiworld, player, entrance: str, rule: StardewRule):
potentially_required_regions = look_for_indirect_connection(rule)
if potentially_required_regions:
for region in potentially_required_regions:
+ logger.debug(f"Registering indirect condition for {region} -> {entrance}")
multiworld.register_indirect_condition(multiworld.get_region(region, player), multiworld.get_entrance(entrance, player))
MultiWorldRules.set_rule(multiworld.get_entrance(entrance, player), rule)
diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py
index a9767c7b72d5..2e2b9c959d7f 100644
--- a/worlds/stardew_valley/stardew_rule/rule_explain.py
+++ b/worlds/stardew_valley/stardew_rule/rule_explain.py
@@ -4,7 +4,7 @@
from functools import cached_property, singledispatch
from typing import Iterable, Set, Tuple, List, Optional
-from BaseClasses import CollectionState
+from BaseClasses import CollectionState, Location, Entrance
from worlds.generic.Rules import CollectionRule
from . import StardewRule, AggregatingStardewRule, Count, Has, TotalReceived, Received, Reach, true_
@@ -12,10 +12,10 @@
@dataclass
class RuleExplanation:
rule: StardewRule
- state: CollectionState
+ state: CollectionState = field(repr=False, hash=False)
expected: bool
sub_rules: Iterable[StardewRule] = field(default_factory=list)
- explored_rules_key: Set[Tuple[str, str]] = field(default_factory=set)
+ explored_rules_key: Set[Tuple[str, str]] = field(default_factory=set, repr=False, hash=False)
current_rule_explored: bool = False
def __post_init__(self):
@@ -38,13 +38,6 @@ def __str__(self, depth=0):
if i.result is not self.expected else i.summary(depth + 1)
for i in sorted(self.explained_sub_rules, key=lambda x: x.result))
- def __repr__(self, depth=0):
- if not self.sub_rules:
- return self.summary(depth)
-
- return self.summary(depth) + "\n" + "\n".join(i.__repr__(depth + 1)
- for i in sorted(self.explained_sub_rules, key=lambda x: x.result))
-
@cached_property
def result(self) -> bool:
try:
@@ -134,6 +127,10 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T
access_rules = [Reach(spot.parent_region.name, "Region", rule.player)]
else:
access_rules = [spot.access_rule, Reach(spot.parent_region.name, "Region", rule.player)]
+ elif spot.access_rule == Location.access_rule:
+ # Sometime locations just don't have an access rule and all the relevant logic is in the parent region.
+ access_rules = [Reach(spot.parent_region.name, "Region", rule.player)]
+
elif rule.resolution_hint == 'Entrance':
spot = state.multiworld.get_entrance(rule.spot, rule.player)
@@ -143,6 +140,9 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T
access_rules = [Reach(spot.parent_region.name, "Region", rule.player)]
else:
access_rules = [spot.access_rule, Reach(spot.parent_region.name, "Region", rule.player)]
+ elif spot.access_rule == Entrance.access_rule:
+ # Sometime entrances just don't have an access rule and all the relevant logic is in the parent region.
+ access_rules = [Reach(spot.parent_region.name, "Region", rule.player)]
else:
spot = state.multiworld.get_region(rule.spot, rule.player)
diff --git a/worlds/stardew_valley/stardew_rule/state.py b/worlds/stardew_valley/stardew_rule/state.py
index a5d936d57db8..d60f08ac4c94 100644
--- a/worlds/stardew_valley/stardew_rule/state.py
+++ b/worlds/stardew_valley/stardew_rule/state.py
@@ -1,11 +1,14 @@
from dataclasses import dataclass
-from typing import Iterable, Union, List, Tuple, Hashable
+from typing import Iterable, Union, List, Tuple, Hashable, TYPE_CHECKING
from BaseClasses import CollectionState
from .base import BaseStardewRule, CombinableStardewRule
from .protocol import StardewRule
from ..strings.ap_names.event_names import Event
+if TYPE_CHECKING:
+ from .. import StardewValleyWorld
+
class TotalReceived(BaseStardewRule):
count: int
diff --git a/worlds/stardew_valley/strings/ap_names/event_names.py b/worlds/stardew_valley/strings/ap_names/event_names.py
index 28d9a76d54ad..8ee69e178a4f 100644
--- a/worlds/stardew_valley/strings/ap_names/event_names.py
+++ b/worlds/stardew_valley/strings/ap_names/event_names.py
@@ -8,10 +8,6 @@ def event(name: str):
class Event:
victory = event("Victory")
- can_construct_buildings = event("Can Construct Buildings")
- start_dark_talisman_quest = event("Start Dark Talisman Quest")
- can_ship_items = event("Can Ship Items")
- can_shop_at_pierre = event("Can Shop At Pierre's")
spring_farming = event("Spring Farming")
summer_farming = event("Summer Farming")
fall_farming = event("Fall Farming")
diff --git a/worlds/stardew_valley/test/TestCrops.py b/worlds/stardew_valley/test/TestCrops.py
index 362e6bf27e7c..4fa836a97d14 100644
--- a/worlds/stardew_valley/test/TestCrops.py
+++ b/worlds/stardew_valley/test/TestCrops.py
@@ -11,10 +11,10 @@ def test_need_greenhouse_for_cactus(self):
harvest_cactus = self.world.logic.region.can_reach_location("Harvest Cactus Fruit")
self.assert_rule_false(harvest_cactus, self.multiworld.state)
- self.multiworld.state.collect(self.world.create_item("Cactus Seeds"), prevent_sweep=False)
- self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False)
- self.multiworld.state.collect(self.world.create_item("Desert Obelisk"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Cactus Seeds"))
+ self.multiworld.state.collect(self.create_item("Shipping Bin"))
+ self.multiworld.state.collect(self.create_item("Desert Obelisk"))
self.assert_rule_false(harvest_cactus, self.multiworld.state)
- self.multiworld.state.collect(self.world.create_item("Greenhouse"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Greenhouse"))
self.assert_rule_true(harvest_cactus, self.multiworld.state)
diff --git a/worlds/stardew_valley/test/TestGeneration.py b/worlds/stardew_valley/test/TestGeneration.py
index 8431e6857eaf..56f338fe8e11 100644
--- a/worlds/stardew_valley/test/TestGeneration.py
+++ b/worlds/stardew_valley/test/TestGeneration.py
@@ -35,7 +35,7 @@ def test_all_progression_items_are_added_to_the_pool(self):
items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY])
items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK])
items_to_ignore.append("The Gateway Gazette")
- progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression and item.name not in items_to_ignore]
+ progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression and item.name not in items_to_ignore]
for progression_item in progression_items:
with self.subTest(f"{progression_item.name}"):
self.assertIn(progression_item.name, all_created_items)
@@ -86,7 +86,7 @@ def test_all_progression_items_except_island_are_added_to_the_pool(self):
items_to_ignore.extend(season.name for season in items.items_by_group[Group.WEAPON])
items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY])
items_to_ignore.append("The Gateway Gazette")
- progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression and item.name not in items_to_ignore]
+ progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression and item.name not in items_to_ignore]
for progression_item in progression_items:
with self.subTest(f"{progression_item.name}"):
if Group.GINGER_ISLAND in progression_item.groups:
diff --git a/worlds/stardew_valley/test/TestOptions.py b/worlds/stardew_valley/test/TestOptions.py
index 2824a10c38af..9db7f06ff5a5 100644
--- a/worlds/stardew_valley/test/TestOptions.py
+++ b/worlds/stardew_valley/test/TestOptions.py
@@ -1,6 +1,6 @@
import itertools
-from Options import NamedRange
+from Options import NamedRange, Accessibility
from . import SVTestCase, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x, solo_multiworld
from .assertion import WorldAssertMixin
from .long.option_names import all_option_choices
@@ -54,6 +54,23 @@ def test_given_goal_when_generate_then_victory_is_in_correct_location(self):
victory = multi_world.find_item("Victory", 1)
self.assertEqual(victory.name, location)
+ def test_given_perfection_goal_when_generate_then_accessibility_is_forced_to_full(self):
+ """There is a bug with the current victory condition of the perfection goal that can create unwinnable seeds if the accessibility is set to minimal and
+ the world gets flooded with progression items through plando. This will increase the amount of collected progression items pass the total amount
+ calculated for the world when creating the item pool. This will cause the victory condition to be met before all locations are collected, so some could
+ be left inaccessible, which in practice will make the seed unwinnable.
+ """
+ for accessibility in Accessibility.options.keys():
+ world_options = {Goal.internal_name: Goal.option_perfection, "accessibility": accessibility}
+ with self.solo_world_sub_test(f"Accessibility: {accessibility}", world_options) as (_, world):
+ self.assertEqual(world.options.accessibility, Accessibility.option_full)
+
+ def test_given_allsanity_goal_when_generate_then_accessibility_is_forced_to_full(self):
+ for accessibility in Accessibility.options.keys():
+ world_options = {Goal.internal_name: Goal.option_allsanity, "accessibility": accessibility}
+ with self.solo_world_sub_test(f"Accessibility: {accessibility}", world_options) as (_, world):
+ self.assertEqual(world.options.accessibility, Accessibility.option_full)
+
class TestSeasonRandomization(SVTestCase):
def test_given_disabled_when_generate_then_all_seasons_are_precollected(self):
diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py
index 3fe05d205ce0..1a312e569d11 100644
--- a/worlds/stardew_valley/test/__init__.py
+++ b/worlds/stardew_valley/test/__init__.py
@@ -6,7 +6,7 @@
from contextlib import contextmanager
from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any
-from BaseClasses import MultiWorld, CollectionState, PlandoOptions, get_seed, Location, Item, ItemClassification
+from BaseClasses import MultiWorld, CollectionState, PlandoOptions, get_seed, Location, Item
from Options import VerifyKeys
from test.bases import WorldTestBase
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
@@ -236,7 +236,6 @@ def world_setup(self, *args, **kwargs):
self.original_state = self.multiworld.state.copy()
self.original_itempool = self.multiworld.itempool.copy()
- self.original_prog_item_count = world.total_progression_items
self.unfilled_locations = self.multiworld.get_unfilled_locations(1)
if self.constructed:
self.world = world # noqa
@@ -246,7 +245,6 @@ def tearDown(self) -> None:
self.multiworld.itempool = self.original_itempool
for location in self.unfilled_locations:
location.item = None
- self.world.total_progression_items = self.original_prog_item_count
self.multiworld.lock.release()
@@ -257,20 +255,13 @@ def run_default_tests(self) -> bool:
return super().run_default_tests
def collect_lots_of_money(self, percent: float = 0.25):
- self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False)
- real_total_prog_items = self.multiworld.worlds[self.player].total_progression_items
+ self.collect("Shipping Bin")
+ real_total_prog_items = self.world.total_progression_items
required_prog_items = int(round(real_total_prog_items * percent))
- for i in range(required_prog_items):
- self.multiworld.state.collect(self.world.create_item("Stardrop"), prevent_sweep=False)
- self.multiworld.worlds[self.player].total_progression_items = real_total_prog_items
+ self.collect("Stardrop", required_prog_items)
def collect_all_the_money(self):
- self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False)
- real_total_prog_items = self.multiworld.worlds[self.player].total_progression_items
- required_prog_items = int(round(real_total_prog_items * 0.95))
- for i in range(required_prog_items):
- self.multiworld.state.collect(self.world.create_item("Stardrop"), prevent_sweep=False)
- self.multiworld.worlds[self.player].total_progression_items = real_total_prog_items
+ self.collect_lots_of_money(0.95)
def collect_everything(self):
non_event_items = [item for item in self.multiworld.get_items() if item.code]
@@ -278,7 +269,8 @@ def collect_everything(self):
self.multiworld.state.collect(item)
def collect_all_except(self, item_to_not_collect: str):
- for item in self.multiworld.get_items():
+ non_event_items = [item for item in self.multiworld.get_items() if item.code]
+ for item in non_event_items:
if item.name != item_to_not_collect:
self.multiworld.state.collect(item)
@@ -290,25 +282,26 @@ def get_real_location_names(self) -> List[str]:
def collect(self, item: Union[str, Item, Iterable[Item]], count: int = 1) -> Union[None, Item, List[Item]]:
assert count > 0
+
if not isinstance(item, str):
super().collect(item)
return
+
if count == 1:
item = self.create_item(item)
self.multiworld.state.collect(item)
return item
+
items = []
for i in range(count):
item = self.create_item(item)
self.multiworld.state.collect(item)
items.append(item)
+
return items
def create_item(self, item: str) -> StardewItem:
- created_item = self.world.create_item(item)
- if created_item.classification == ItemClassification.progression:
- self.multiworld.worlds[self.player].total_progression_items -= 1
- return created_item
+ return self.world.create_item(item)
def remove_one_by_name(self, item: str) -> None:
self.remove(self.create_item(item))
@@ -336,7 +329,6 @@ def solo_multiworld(world_options: Optional[Dict[Union[str, StardewValleyOption]
original_state = multiworld.state.copy()
original_itempool = multiworld.itempool.copy()
unfilled_locations = multiworld.get_unfilled_locations(1)
- original_prog_item_count = world.total_progression_items
yield multiworld, world
@@ -344,7 +336,6 @@ def solo_multiworld(world_options: Optional[Dict[Union[str, StardewValleyOption]
multiworld.itempool = original_itempool
for location in unfilled_locations:
location.item = None
- multiworld.total_progression_items = original_prog_item_count
multiworld.lock.release()
diff --git a/worlds/stardew_valley/test/mods/TestMods.py b/worlds/stardew_valley/test/mods/TestMods.py
index 97184b1338b8..07a75f21b1de 100644
--- a/worlds/stardew_valley/test/mods/TestMods.py
+++ b/worlds/stardew_valley/test/mods/TestMods.py
@@ -75,7 +75,7 @@ def test_all_progression_items_are_added_to_the_pool(self):
items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY])
items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK])
items_to_ignore.append("The Gateway Gazette")
- progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression
+ progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression
and item.name not in items_to_ignore]
for progression_item in progression_items:
with self.subTest(f"{progression_item.name}"):
@@ -105,7 +105,7 @@ def test_all_progression_items_except_island_are_added_to_the_pool(self):
items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY])
items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK])
items_to_ignore.append("The Gateway Gazette")
- progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression
+ progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression
and item.name not in items_to_ignore]
for progression_item in progression_items:
with self.subTest(f"{progression_item.name}"):
diff --git a/worlds/stardew_valley/test/rules/TestArcades.py b/worlds/stardew_valley/test/rules/TestArcades.py
index 2922ecfb5d9e..69e5b22cc01b 100644
--- a/worlds/stardew_valley/test/rules/TestArcades.py
+++ b/worlds/stardew_valley/test/rules/TestArcades.py
@@ -19,8 +19,8 @@ def test_prairie_king(self):
life = self.create_item("JotPK: Extra Life")
drop = self.create_item("JotPK: Increased Drop Rate")
- self.multiworld.state.collect(boots, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
+ self.multiworld.state.collect(boots)
+ self.multiworld.state.collect(gun)
self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state))
self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state))
self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state))
@@ -28,8 +28,8 @@ def test_prairie_king(self):
self.remove(boots)
self.remove(gun)
- self.multiworld.state.collect(boots, prevent_sweep=True)
- self.multiworld.state.collect(boots, prevent_sweep=True)
+ self.multiworld.state.collect(boots)
+ self.multiworld.state.collect(boots)
self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state))
self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state))
self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state))
@@ -37,10 +37,10 @@ def test_prairie_king(self):
self.remove(boots)
self.remove(boots)
- self.multiworld.state.collect(boots, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(ammo, prevent_sweep=True)
- self.multiworld.state.collect(life, prevent_sweep=True)
+ self.multiworld.state.collect(boots)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(ammo)
+ self.multiworld.state.collect(life)
self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state))
self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state))
self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state))
@@ -50,13 +50,13 @@ def test_prairie_king(self):
self.remove(ammo)
self.remove(life)
- self.multiworld.state.collect(boots, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(ammo, prevent_sweep=True)
- self.multiworld.state.collect(ammo, prevent_sweep=True)
- self.multiworld.state.collect(life, prevent_sweep=True)
- self.multiworld.state.collect(drop, prevent_sweep=True)
+ self.multiworld.state.collect(boots)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(ammo)
+ self.multiworld.state.collect(ammo)
+ self.multiworld.state.collect(life)
+ self.multiworld.state.collect(drop)
self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state))
self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state))
self.assertTrue(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state))
@@ -69,17 +69,17 @@ def test_prairie_king(self):
self.remove(life)
self.remove(drop)
- self.multiworld.state.collect(boots, prevent_sweep=True)
- self.multiworld.state.collect(boots, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(gun, prevent_sweep=True)
- self.multiworld.state.collect(ammo, prevent_sweep=True)
- self.multiworld.state.collect(ammo, prevent_sweep=True)
- self.multiworld.state.collect(ammo, prevent_sweep=True)
- self.multiworld.state.collect(life, prevent_sweep=True)
- self.multiworld.state.collect(drop, prevent_sweep=True)
+ self.multiworld.state.collect(boots)
+ self.multiworld.state.collect(boots)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(gun)
+ self.multiworld.state.collect(ammo)
+ self.multiworld.state.collect(ammo)
+ self.multiworld.state.collect(ammo)
+ self.multiworld.state.collect(life)
+ self.multiworld.state.collect(drop)
self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state))
self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state))
self.assertTrue(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state))
diff --git a/worlds/stardew_valley/test/rules/TestBuildings.py b/worlds/stardew_valley/test/rules/TestBuildings.py
index 2c276d8b5cbe..d1f60b20e0db 100644
--- a/worlds/stardew_valley/test/rules/TestBuildings.py
+++ b/worlds/stardew_valley/test/rules/TestBuildings.py
@@ -23,11 +23,7 @@ def test_big_coop_blueprint(self):
self.assertFalse(big_coop_blueprint_rule(self.multiworld.state),
f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}")
- self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True)
- self.assertFalse(big_coop_blueprint_rule(self.multiworld.state),
- f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}")
-
- self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Progressive Coop"))
self.assertTrue(big_coop_blueprint_rule(self.multiworld.state),
f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}")
@@ -35,13 +31,12 @@ def test_deluxe_coop_blueprint(self):
self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state))
self.collect_lots_of_money()
- self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True)
self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state))
- self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=True)
+ self.multiworld.state.collect(self.create_item("Progressive Coop"))
self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state))
- self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=True)
+ self.multiworld.state.collect(self.create_item("Progressive Coop"))
self.assertTrue(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state))
def test_big_shed_blueprint(self):
@@ -53,10 +48,6 @@ def test_big_shed_blueprint(self):
self.assertFalse(big_shed_rule(self.multiworld.state),
f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}")
- self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True)
- self.assertFalse(big_shed_rule(self.multiworld.state),
- f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}")
-
- self.multiworld.state.collect(self.create_item("Progressive Shed"), prevent_sweep=True)
+ self.multiworld.state.collect(self.create_item("Progressive Shed"))
self.assertTrue(big_shed_rule(self.multiworld.state),
f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}")
diff --git a/worlds/stardew_valley/test/rules/TestCookingRecipes.py b/worlds/stardew_valley/test/rules/TestCookingRecipes.py
index 7ab9d61cb942..d5f9da73c9d7 100644
--- a/worlds/stardew_valley/test/rules/TestCookingRecipes.py
+++ b/worlds/stardew_valley/test/rules/TestCookingRecipes.py
@@ -17,14 +17,14 @@ def test_can_learn_qos_recipe(self):
rule = self.world.logic.region.can_reach_location(location)
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Radish Seeds"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Spring"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Summer"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Progressive House"))
+ self.multiworld.state.collect(self.create_item("Radish Seeds"))
+ self.multiworld.state.collect(self.create_item("Spring"))
+ self.multiworld.state.collect(self.create_item("Summer"))
self.collect_lots_of_money()
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("The Queen of Sauce"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("The Queen of Sauce"))
self.assert_rule_true(rule, self.multiworld.state)
@@ -42,21 +42,21 @@ def test_can_learn_qos_recipe(self):
rule = self.world.logic.region.can_reach_location(location)
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Radish Seeds"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Summer"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Progressive House"))
+ self.multiworld.state.collect(self.create_item("Radish Seeds"))
+ self.multiworld.state.collect(self.create_item("Summer"))
self.collect_lots_of_money()
self.assert_rule_false(rule, self.multiworld.state)
spring = self.create_item("Spring")
qos = self.create_item("The Queen of Sauce")
- self.multiworld.state.collect(spring, prevent_sweep=False)
- self.multiworld.state.collect(qos, prevent_sweep=False)
+ self.multiworld.state.collect(spring)
+ self.multiworld.state.collect(qos)
self.assert_rule_false(rule, self.multiworld.state)
self.multiworld.state.remove(spring)
self.multiworld.state.remove(qos)
- self.multiworld.state.collect(self.create_item("Radish Salad Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Radish Salad Recipe"))
self.assert_rule_true(rule, self.multiworld.state)
def test_get_chefsanity_check_recipe(self):
@@ -64,20 +64,20 @@ def test_get_chefsanity_check_recipe(self):
rule = self.world.logic.region.can_reach_location(location)
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Spring"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Spring"))
self.collect_lots_of_money()
self.assert_rule_false(rule, self.multiworld.state)
seeds = self.create_item("Radish Seeds")
summer = self.create_item("Summer")
house = self.create_item("Progressive House")
- self.multiworld.state.collect(seeds, prevent_sweep=False)
- self.multiworld.state.collect(summer, prevent_sweep=False)
- self.multiworld.state.collect(house, prevent_sweep=False)
+ self.multiworld.state.collect(seeds)
+ self.multiworld.state.collect(summer)
+ self.multiworld.state.collect(house)
self.assert_rule_false(rule, self.multiworld.state)
self.multiworld.state.remove(seeds)
self.multiworld.state.remove(summer)
self.multiworld.state.remove(house)
- self.multiworld.state.collect(self.create_item("The Queen of Sauce"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("The Queen of Sauce"))
self.assert_rule_true(rule, self.multiworld.state)
diff --git a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py
index 4719edea1d59..46a1b73d0b7a 100644
--- a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py
+++ b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py
@@ -25,7 +25,7 @@ def test_can_craft_recipe(self):
self.collect_all_the_money()
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Marble Brazier Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Marble Brazier Recipe"))
self.assert_rule_true(rule, self.multiworld.state)
def test_can_learn_crafting_recipe(self):
@@ -38,16 +38,16 @@ def test_can_learn_crafting_recipe(self):
def test_can_craft_festival_recipe(self):
recipe = all_crafting_recipes_by_name["Jack-O-Lantern"]
- self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Torch Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Pumpkin Seeds"))
+ self.multiworld.state.collect(self.create_item("Torch Recipe"))
self.collect_lots_of_money()
rule = self.world.logic.crafting.can_craft(recipe)
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Fall"))
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"))
self.assert_rule_true(rule, self.multiworld.state)
def test_require_furnace_recipe_for_smelting_checks(self):
@@ -64,7 +64,7 @@ def test_require_furnace_recipe_for_smelting_checks(self):
self.collect_all_the_money()
self.assert_rules_false(rules, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Furnace Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Furnace Recipe"))
self.assert_rules_true(rules, self.multiworld.state)
@@ -79,16 +79,16 @@ class TestCraftsanityWithFestivalsLogic(SVTestBase):
def test_can_craft_festival_recipe(self):
recipe = all_crafting_recipes_by_name["Jack-O-Lantern"]
- self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Pumpkin Seeds"))
+ self.multiworld.state.collect(self.create_item("Fall"))
self.collect_lots_of_money()
rule = self.world.logic.crafting.can_craft(recipe)
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"))
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Torch Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Torch Recipe"))
self.assert_rule_true(rule, self.multiworld.state)
@@ -109,7 +109,7 @@ def test_can_craft_recipe(self):
def test_can_craft_festival_recipe(self):
recipe = all_crafting_recipes_by_name["Jack-O-Lantern"]
- self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Pumpkin Seeds"))
self.collect_lots_of_money()
rule = self.world.logic.crafting.can_craft(recipe)
result = rule(self.multiworld.state)
@@ -126,7 +126,7 @@ def test_requires_mining_levels_for_smelting_checks(self):
self.collect([self.create_item("Progressive Sword")] * 4)
self.collect([self.create_item("Progressive Mine Elevator")] * 24)
self.collect([self.create_item("Progressive Trash Can")] * 2)
- self.multiworld.state.collect(self.create_item("Furnace Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Furnace Recipe"))
self.collect([self.create_item("Combat Level")] * 10)
self.collect([self.create_item("Fishing Level")] * 10)
self.collect_all_the_money()
@@ -147,11 +147,11 @@ class TestNoCraftsanityWithFestivalsLogic(SVTestBase):
def test_can_craft_festival_recipe(self):
recipe = all_crafting_recipes_by_name["Jack-O-Lantern"]
- self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Pumpkin Seeds"))
+ self.multiworld.state.collect(self.create_item("Fall"))
self.collect_lots_of_money()
rule = self.world.logic.crafting.can_craft(recipe)
self.assert_rule_false(rule, self.multiworld.state)
- self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"))
self.assert_rule_true(rule, self.multiworld.state)
diff --git a/worlds/stardew_valley/test/rules/TestDonations.py b/worlds/stardew_valley/test/rules/TestDonations.py
index 984a3ebc38b4..3927bd09a48b 100644
--- a/worlds/stardew_valley/test/rules/TestDonations.py
+++ b/worlds/stardew_valley/test/rules/TestDonations.py
@@ -18,7 +18,7 @@ def test_cannot_make_any_donation_without_museum_access(self):
for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]:
self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state))
- self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(railroad_item))
for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]:
self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state))
@@ -39,7 +39,7 @@ def test_cannot_make_any_donation_without_museum_access(self):
for donation in donation_locations:
self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state))
- self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(railroad_item))
for donation in donation_locations:
self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state))
@@ -58,7 +58,7 @@ def test_cannot_make_any_donation_without_museum_access(self):
for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]:
self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state))
- self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(railroad_item))
for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]:
self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state))
diff --git a/worlds/stardew_valley/test/rules/TestFriendship.py b/worlds/stardew_valley/test/rules/TestFriendship.py
index fb186ca99480..3e9109ed5010 100644
--- a/worlds/stardew_valley/test/rules/TestFriendship.py
+++ b/worlds/stardew_valley/test/rules/TestFriendship.py
@@ -11,34 +11,34 @@ class TestFriendsanityDatingRules(SVTestBase):
def test_earning_dating_heart_requires_dating(self):
self.collect_all_the_money()
- self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Beach Bridge"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Fall"))
+ self.multiworld.state.collect(self.create_item("Beach Bridge"))
+ self.multiworld.state.collect(self.create_item("Progressive House"))
for i in range(3):
- self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive Weapon"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive Axe"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive Barn"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Progressive Pickaxe"))
+ self.multiworld.state.collect(self.create_item("Progressive Weapon"))
+ self.multiworld.state.collect(self.create_item("Progressive Axe"))
+ self.multiworld.state.collect(self.create_item("Progressive Barn"))
for i in range(10):
- self.multiworld.state.collect(self.create_item("Foraging Level"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Farming Level"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Mining Level"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Combat Level"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Foraging Level"))
+ self.multiworld.state.collect(self.create_item("Farming Level"))
+ self.multiworld.state.collect(self.create_item("Mining Level"))
+ self.multiworld.state.collect(self.create_item("Combat Level"))
+ self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"))
+ self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"))
npc = "Abigail"
heart_name = f"{npc} <3"
step = 3
self.assert_can_reach_heart_up_to(npc, 3, step)
- self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(heart_name))
self.assert_can_reach_heart_up_to(npc, 6, step)
- self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(heart_name))
self.assert_can_reach_heart_up_to(npc, 8, step)
- self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(heart_name))
self.assert_can_reach_heart_up_to(npc, 10, step)
- self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(heart_name))
self.assert_can_reach_heart_up_to(npc, 14, step)
def assert_can_reach_heart_up_to(self, npc: str, max_reachable: int, step: int):
diff --git a/worlds/stardew_valley/test/rules/TestStateRules.py b/worlds/stardew_valley/test/rules/TestStateRules.py
index 4f53b9a7f536..49577d2223e0 100644
--- a/worlds/stardew_valley/test/rules/TestStateRules.py
+++ b/worlds/stardew_valley/test/rules/TestStateRules.py
@@ -1,12 +1,22 @@
-import unittest
+from .. import SVTestBase, allsanity_mods_6_x_x
+from ...stardew_rule import HasProgressionPercent
-from BaseClasses import ItemClassification
-from ...test import solo_multiworld
+class TestHasProgressionPercentWithVictory(SVTestBase):
+ options = allsanity_mods_6_x_x()
-class TestHasProgressionPercent(unittest.TestCase):
- def test_max_item_amount_is_full_collection(self):
- # Not caching because it fails too often for some reason
- with solo_multiworld(world_caching=False) as (multiworld, world):
- progression_item_count = sum(1 for i in multiworld.get_items() if ItemClassification.progression in i.classification)
- self.assertEqual(world.total_progression_items, progression_item_count - 1) # -1 to skip Victory
+ def test_has_100_progression_percent_is_false_while_items_are_missing(self):
+ has_100_progression_percent = HasProgressionPercent(1, 100)
+
+ for i, item in enumerate([i for i in self.multiworld.get_items() if i.advancement and i.code][1:]):
+ if item.name != "Victory":
+ self.collect(item)
+ self.assertFalse(has_100_progression_percent(self.multiworld.state),
+ f"Rule became true after {i} items, total_progression_items is {self.world.total_progression_items}")
+
+ def test_has_100_progression_percent_account_for_victory_not_being_collected(self):
+ has_100_progression_percent = HasProgressionPercent(1, 100)
+
+ self.collect_all_except("Victory")
+
+ self.assert_rule_true(has_100_progression_percent, self.multiworld.state)
diff --git a/worlds/stardew_valley/test/rules/TestTools.py b/worlds/stardew_valley/test/rules/TestTools.py
index 5f0fe8ef3ffb..5b8975f4e707 100644
--- a/worlds/stardew_valley/test/rules/TestTools.py
+++ b/worlds/stardew_valley/test/rules/TestTools.py
@@ -21,30 +21,30 @@ def test_sturgeon(self):
self.assert_rule_false(sturgeon_rule, self.multiworld.state)
summer = self.create_item("Summer")
- self.multiworld.state.collect(summer, prevent_sweep=False)
+ self.multiworld.state.collect(summer)
self.assert_rule_false(sturgeon_rule, self.multiworld.state)
fishing_rod = self.create_item("Progressive Fishing Rod")
- self.multiworld.state.collect(fishing_rod, prevent_sweep=False)
- self.multiworld.state.collect(fishing_rod, prevent_sweep=False)
+ self.multiworld.state.collect(fishing_rod)
+ self.multiworld.state.collect(fishing_rod)
self.assert_rule_false(sturgeon_rule, self.multiworld.state)
fishing_level = self.create_item("Fishing Level")
- self.multiworld.state.collect(fishing_level, prevent_sweep=False)
+ self.multiworld.state.collect(fishing_level)
self.assert_rule_false(sturgeon_rule, self.multiworld.state)
- self.multiworld.state.collect(fishing_level, prevent_sweep=False)
- self.multiworld.state.collect(fishing_level, prevent_sweep=False)
- self.multiworld.state.collect(fishing_level, prevent_sweep=False)
- self.multiworld.state.collect(fishing_level, prevent_sweep=False)
- self.multiworld.state.collect(fishing_level, prevent_sweep=False)
+ self.multiworld.state.collect(fishing_level)
+ self.multiworld.state.collect(fishing_level)
+ self.multiworld.state.collect(fishing_level)
+ self.multiworld.state.collect(fishing_level)
+ self.multiworld.state.collect(fishing_level)
self.assert_rule_true(sturgeon_rule, self.multiworld.state)
self.remove(summer)
self.assert_rule_false(sturgeon_rule, self.multiworld.state)
winter = self.create_item("Winter")
- self.multiworld.state.collect(winter, prevent_sweep=False)
+ self.multiworld.state.collect(winter)
self.assert_rule_true(sturgeon_rule, self.multiworld.state)
self.remove(fishing_rod)
@@ -53,24 +53,24 @@ def test_sturgeon(self):
def test_old_master_cannoli(self):
self.multiworld.state.prog_items = {1: Counter()}
- self.multiworld.state.collect(self.create_item("Progressive Axe"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Progressive Axe"), prevent_sweep=False)
- self.multiworld.state.collect(self.create_item("Summer"), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item("Progressive Axe"))
+ self.multiworld.state.collect(self.create_item("Progressive Axe"))
+ self.multiworld.state.collect(self.create_item("Summer"))
self.collect_lots_of_money()
rule = self.world.logic.region.can_reach_location("Old Master Cannoli")
self.assert_rule_false(rule, self.multiworld.state)
fall = self.create_item("Fall")
- self.multiworld.state.collect(fall, prevent_sweep=False)
+ self.multiworld.state.collect(fall)
self.assert_rule_false(rule, self.multiworld.state)
tuesday = self.create_item("Traveling Merchant: Tuesday")
- self.multiworld.state.collect(tuesday, prevent_sweep=False)
+ self.multiworld.state.collect(tuesday)
self.assert_rule_false(rule, self.multiworld.state)
rare_seed = self.create_item("Rare Seed")
- self.multiworld.state.collect(rare_seed, prevent_sweep=False)
+ self.multiworld.state.collect(rare_seed)
self.assert_rule_true(rule, self.multiworld.state)
self.remove(fall)
@@ -80,11 +80,11 @@ def test_old_master_cannoli(self):
green_house = self.create_item("Greenhouse")
self.collect(self.create_item(Event.fall_farming))
- self.multiworld.state.collect(green_house, prevent_sweep=False)
+ self.multiworld.state.collect(green_house)
self.assert_rule_false(rule, self.multiworld.state)
friday = self.create_item("Traveling Merchant: Friday")
- self.multiworld.state.collect(friday, prevent_sweep=False)
+ self.multiworld.state.collect(friday)
self.assertTrue(self.multiworld.get_location("Old Master Cannoli", 1).access_rule(self.multiworld.state))
self.remove(green_house)
@@ -111,7 +111,7 @@ def test_cannot_get_any_tool_without_blacksmith_access(self):
for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]:
self.assert_rule_false(self.world.logic.tool.has_tool(tool, material), self.multiworld.state)
- self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(railroad_item))
for tool in [Tool.pickaxe, Tool.axe, Tool.hoe, Tool.trash_can, Tool.watering_can]:
for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]:
@@ -125,7 +125,7 @@ def test_cannot_get_fishing_rod_without_willy_access(self):
for fishing_rod_level in [3, 4]:
self.assert_rule_false(self.world.logic.tool.has_fishing_rod(fishing_rod_level), self.multiworld.state)
- self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False)
+ self.multiworld.state.collect(self.create_item(railroad_item))
for fishing_rod_level in [3, 4]:
self.assert_rule_true(self.world.logic.tool.has_fishing_rod(fishing_rod_level), self.multiworld.state)
diff --git a/worlds/stardew_valley/test/rules/TestWeapons.py b/worlds/stardew_valley/test/rules/TestWeapons.py
index 972170b93c75..383f26e841d2 100644
--- a/worlds/stardew_valley/test/rules/TestWeapons.py
+++ b/worlds/stardew_valley/test/rules/TestWeapons.py
@@ -10,40 +10,40 @@ class TestWeaponsLogic(SVTestBase):
}
def test_mine(self):
- self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True)
- self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True)
- self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True)
- self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True)
- self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=True)
+ self.multiworld.state.collect(self.create_item("Progressive Pickaxe"))
+ self.multiworld.state.collect(self.create_item("Progressive Pickaxe"))
+ self.multiworld.state.collect(self.create_item("Progressive Pickaxe"))
+ self.multiworld.state.collect(self.create_item("Progressive Pickaxe"))
+ self.multiworld.state.collect(self.create_item("Progressive House"))
self.collect([self.create_item("Combat Level")] * 10)
self.collect([self.create_item("Mining Level")] * 10)
self.collect([self.create_item("Progressive Mine Elevator")] * 24)
- self.multiworld.state.collect(self.create_item("Bus Repair"), prevent_sweep=True)
- self.multiworld.state.collect(self.create_item("Skull Key"), prevent_sweep=True)
+ self.multiworld.state.collect(self.create_item("Bus Repair"))
+ self.multiworld.state.collect(self.create_item("Skull Key"))
- self.GiveItemAndCheckReachableMine("Progressive Sword", 1)
- self.GiveItemAndCheckReachableMine("Progressive Dagger", 1)
- self.GiveItemAndCheckReachableMine("Progressive Club", 1)
+ self.give_item_and_check_reachable_mine("Progressive Sword", 1)
+ self.give_item_and_check_reachable_mine("Progressive Dagger", 1)
+ self.give_item_and_check_reachable_mine("Progressive Club", 1)
- self.GiveItemAndCheckReachableMine("Progressive Sword", 2)
- self.GiveItemAndCheckReachableMine("Progressive Dagger", 2)
- self.GiveItemAndCheckReachableMine("Progressive Club", 2)
+ self.give_item_and_check_reachable_mine("Progressive Sword", 2)
+ self.give_item_and_check_reachable_mine("Progressive Dagger", 2)
+ self.give_item_and_check_reachable_mine("Progressive Club", 2)
- self.GiveItemAndCheckReachableMine("Progressive Sword", 3)
- self.GiveItemAndCheckReachableMine("Progressive Dagger", 3)
- self.GiveItemAndCheckReachableMine("Progressive Club", 3)
+ self.give_item_and_check_reachable_mine("Progressive Sword", 3)
+ self.give_item_and_check_reachable_mine("Progressive Dagger", 3)
+ self.give_item_and_check_reachable_mine("Progressive Club", 3)
- self.GiveItemAndCheckReachableMine("Progressive Sword", 4)
- self.GiveItemAndCheckReachableMine("Progressive Dagger", 4)
- self.GiveItemAndCheckReachableMine("Progressive Club", 4)
+ self.give_item_and_check_reachable_mine("Progressive Sword", 4)
+ self.give_item_and_check_reachable_mine("Progressive Dagger", 4)
+ self.give_item_and_check_reachable_mine("Progressive Club", 4)
- self.GiveItemAndCheckReachableMine("Progressive Sword", 5)
- self.GiveItemAndCheckReachableMine("Progressive Dagger", 5)
- self.GiveItemAndCheckReachableMine("Progressive Club", 5)
+ self.give_item_and_check_reachable_mine("Progressive Sword", 5)
+ self.give_item_and_check_reachable_mine("Progressive Dagger", 5)
+ self.give_item_and_check_reachable_mine("Progressive Club", 5)
- def GiveItemAndCheckReachableMine(self, item_name: str, reachable_level: int):
+ def give_item_and_check_reachable_mine(self, item_name: str, reachable_level: int):
item = self.multiworld.create_item(item_name, self.player)
- self.multiworld.state.collect(item, prevent_sweep=True)
+ self.multiworld.state.collect(item)
rule = self.world.logic.mine.can_mine_in_the_mines_floor_1_40()
if reachable_level > 0:
self.assert_rule_true(rule, self.multiworld.state)
diff --git a/worlds/stardew_valley/test/stability/TestStability.py b/worlds/stardew_valley/test/stability/TestStability.py
index 2beb3102cc02..b4d0f30ea51f 100644
--- a/worlds/stardew_valley/test/stability/TestStability.py
+++ b/worlds/stardew_valley/test/stability/TestStability.py
@@ -9,8 +9,6 @@
# at 0x102ca98a0>
lambda_regex = re.compile(r"^ at (.*)>$")
-# Python 3.10.2\r\n
-python_version_regex = re.compile(r"^Python (\d+)\.(\d+)\.(\d+)\s*$")
class TestGenerationIsStable(SVTestCase):
diff --git a/worlds/timespinner/Items.py b/worlds/timespinner/Items.py
index 45c67c254736..3beead95153b 100644
--- a/worlds/timespinner/Items.py
+++ b/worlds/timespinner/Items.py
@@ -138,7 +138,7 @@ class ItemData(NamedTuple):
'Elevator Keycard': ItemData('Relic', 1337125, progression=True),
'Jewelry Box': ItemData('Relic', 1337126, useful=True),
'Goddess Brooch': ItemData('Relic', 1337127),
- 'Wyrm Brooch': ItemData('Relic', 1337128),
+ 'Wyrm Brooch': ItemData('Relic', 1337128),
'Greed Brooch': ItemData('Relic', 1337129),
'Eternal Brooch': ItemData('Relic', 1337130),
'Blue Orb': ItemData('Orb Melee', 1337131),
@@ -199,7 +199,11 @@ class ItemData(NamedTuple):
'Chaos Trap': ItemData('Trap', 1337186, 0, trap=True),
'Neurotoxin Trap': ItemData('Trap', 1337187, 0, trap=True),
'Bee Trap': ItemData('Trap', 1337188, 0, trap=True),
- # 1337189 - 1337248 Reserved
+ 'Laser Access A': ItemData('Relic', 1337189, progression=True),
+ 'Laser Access I': ItemData('Relic', 1337191, progression=True),
+ 'Laser Access M': ItemData('Relic', 1337192, progression=True),
+ 'Throw Stun Trap': ItemData('Trap', 1337193, 0, trap=True),
+ # 1337194 - 1337248 Reserved
'Max Sand': ItemData('Stat', 1337249, 14)
}
diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py
index 2423e06bb010..93ac6ccb98c7 100644
--- a/worlds/timespinner/Locations.py
+++ b/worlds/timespinner/Locations.py
@@ -71,8 +71,8 @@ def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptio
LocationData('Skeleton Shaft', 'Sealed Caves (Xarion): Skeleton', 1337044),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Shroom jump room', 1337045, logic.has_timestop),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Double shroom room', 1337046),
- LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Mini jackpot room', 1337047, logic.has_forwarddash_doublejump),
- LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Below mini jackpot room', 1337048),
+ LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Jacksquat room', 1337047, logic.has_forwarddash_doublejump),
+ LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Below Jacksquat room', 1337048),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Secret room', 1337049, logic.can_break_walls),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Bottom left room', 1337050),
LocationData('Sealed Caves (Xarion)', 'Sealed Caves (Xarion): Last chance before Xarion', 1337051, logic.has_doublejump),
diff --git a/worlds/timespinner/LogicExtensions.py b/worlds/timespinner/LogicExtensions.py
index 6c9cb3f684a0..2a0a358737f7 100644
--- a/worlds/timespinner/LogicExtensions.py
+++ b/worlds/timespinner/LogicExtensions.py
@@ -22,6 +22,7 @@ def __init__(self, player: int, options: Optional[TimespinnerOptions],
self.flag_specific_keycards = bool(options and options.specific_keycards)
self.flag_eye_spy = bool(options and options.eye_spy)
self.flag_unchained_keys = bool(options and options.unchained_keys)
+ self.flag_prism_break = bool(options and options.prism_break)
if precalculated_weights:
if self.flag_unchained_keys:
@@ -92,6 +93,8 @@ def can_break_walls(self, state: CollectionState) -> bool:
return True
def can_kill_all_3_bosses(self, state: CollectionState) -> bool:
+ if self.flag_prism_break:
+ return state.has_all({'Laser Access M', 'Laser Access I', 'Laser Access A'}, self.player)
return state.has_all({'Killed Maw', 'Killed Twins', 'Killed Aelana'}, self.player)
def has_teleport(self, state: CollectionState) -> bool:
diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py
index f6a3dba3e311..72f2d8b35abf 100644
--- a/worlds/timespinner/Options.py
+++ b/worlds/timespinner/Options.py
@@ -180,12 +180,19 @@ class DamageRandoOverrides(OptionDict):
}
class HpCap(Range):
- "Sets the number that Lunais's HP maxes out at."
+ """Sets the number that Lunais's HP maxes out at."""
display_name = "HP Cap"
range_start = 1
range_end = 999
default = 999
+class AuraCap(Range):
+ """Sets the maximum Aura Lunais is allowed to have. Level 1 is 80. Djinn Inferno costs 45."""
+ display_name = "Aura Cap"
+ range_start = 45
+ range_end = 999
+ default = 999
+
class LevelCap(Range):
"""Sets the max level Lunais can achieve."""
display_name = "Level Cap"
@@ -359,13 +366,18 @@ class TrapChance(Range):
class Traps(OptionList):
"""List of traps that may be in the item pool to find"""
display_name = "Traps Types"
- valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" }
- default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" ]
+ valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap" }
+ default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap" ]
class PresentAccessWithWheelAndSpindle(Toggle):
"""When inverted, allows using the refugee camp warp when both the Timespinner Wheel and Spindle is acquired."""
display_name = "Back to the future"
+class PrismBreak(Toggle):
+ """Adds 3 Laser Access items to the item pool to remove the lasers blocking the military hangar area
+ instead of needing to beat the Golden Idol, Aelana, and The Maw."""
+ display_name = "Prism Break"
+
@dataclass
class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
start_with_jewelry_box: StartWithJewelryBox
@@ -379,9 +391,11 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
cantoran: Cantoran
lore_checks: LoreChecks
boss_rando: BossRando
+ enemy_rando: EnemyRando
damage_rando: DamageRando
damage_rando_overrides: DamageRandoOverrides
hp_cap: HpCap
+ aura_cap: AuraCap
level_cap: LevelCap
extra_earrings_xp: ExtraEarringsXP
boss_healing: BossHealing
@@ -400,6 +414,7 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
rising_tides_overrides: RisingTidesOverrides
unchained_keys: UnchainedKeys
back_to_the_future: PresentAccessWithWheelAndSpindle
+ prism_break: PrismBreak
trap_chance: TrapChance
traps: Traps
@@ -445,6 +460,7 @@ class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions):
Cantoran: hidden(Cantoran) # type: ignore
LoreChecks: hidden(LoreChecks) # type: ignore
BossRando: hidden(BossRando) # type: ignore
+ EnemyRando: hidden(EnemyRando) # type: ignore
DamageRando: hidden(DamageRando) # type: ignore
DamageRandoOverrides: HiddenDamageRandoOverrides
HpCap: hidden(HpCap) # type: ignore
@@ -516,6 +532,10 @@ def handle_backward_compatibility(self) -> None:
self.boss_rando == BossRando.default:
self.boss_rando.value = self.BossRando.value
self.has_replaced_options.value = Toggle.option_true
+ if self.EnemyRando != EnemyRando.default and \
+ self.enemy_rando == EnemyRando.default:
+ self.enemy_rando.value = self.EnemyRando.value
+ self.has_replaced_options.value = Toggle.option_true
if self.DamageRando != DamageRando.default and \
self.damage_rando == DamageRando.default:
self.damage_rando.value = self.DamageRando.value
diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py
index f241d4468162..a2a5c7ce9c78 100644
--- a/worlds/timespinner/__init__.py
+++ b/worlds/timespinner/__init__.py
@@ -98,9 +98,11 @@ def fill_slot_data(self) -> Dict[str, object]:
"Cantoran": self.options.cantoran.value,
"LoreChecks": self.options.lore_checks.value,
"BossRando": self.options.boss_rando.value,
+ "EnemyRando": self.options.enemy_rando.value,
"DamageRando": self.options.damage_rando.value,
"DamageRandoOverrides": self.options.damage_rando_overrides.value,
"HpCap": self.options.hp_cap.value,
+ "AuraCap": self.options.aura_cap.value,
"LevelCap": self.options.level_cap.value,
"ExtraEarringsXP": self.options.extra_earrings_xp.value,
"BossHealing": self.options.boss_healing.value,
@@ -118,6 +120,7 @@ def fill_slot_data(self) -> Dict[str, object]:
"RisingTides": self.options.rising_tides.value,
"UnchainedKeys": self.options.unchained_keys.value,
"PresentAccessWithWheelAndSpindle": self.options.back_to_the_future.value,
+ "PrismBreak": self.options.prism_break.value,
"Traps": self.options.traps.value,
"DeathLink": self.options.death_link.value,
"StinkyMaw": True,
@@ -223,6 +226,9 @@ def create_item(self, name: str) -> Item:
elif name in {"Timeworn Warp Beacon", "Modern Warp Beacon", "Mysterious Warp Beacon"} \
and not self.options.unchained_keys:
item.classification = ItemClassification.filler
+ elif name in {"Laser Access A", "Laser Access I", "Laser Access M"} \
+ and not self.options.prism_break:
+ item.classification = ItemClassification.filler
return item
@@ -255,6 +261,11 @@ def get_excluded_items(self) -> Set[str]:
excluded_items.add('Modern Warp Beacon')
excluded_items.add('Mysterious Warp Beacon')
+ if not self.options.prism_break:
+ excluded_items.add('Laser Access A')
+ excluded_items.add('Laser Access I')
+ excluded_items.add('Laser Access M')
+
for item in self.multiworld.precollected_items[self.player]:
if item.name not in self.item_name_groups['UseItem']:
excluded_items.add(item.name)
diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py
index 5a2847278bd5..d1430aac1895 100644
--- a/worlds/tunic/__init__.py
+++ b/worlds/tunic/__init__.py
@@ -432,6 +432,7 @@ def fill_slot_data(self) -> Dict[str, Any]:
# this would be in a stage if there was an appropriate stage for it
self.player_item_link_locations = {}
groups = self.multiworld.get_player_groups(self.player)
+ # checking if groups so that this doesn't run if the player isn't in a group
if groups:
if not self.item_link_locations:
tunic_worlds: Tuple[TunicWorld] = self.multiworld.get_game_worlds("TUNIC")
diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py
index 3b111ad83488..d5d6f16c57ec 100644
--- a/worlds/tunic/er_rules.py
+++ b/worlds/tunic/er_rules.py
@@ -1446,7 +1446,7 @@ def set_er_location_rules(world: "TunicWorld") -> None:
set_rule(world.get_location("West Garden Fuse"),
lambda state: has_ability(prayer, state, world))
set_rule(world.get_location("Library Fuse"),
- lambda state: has_ability(prayer, state, world))
+ lambda state: has_ability(prayer, state, world) and has_ladder("Ladders in Library", state, world))
# Bombable Walls
for location_name in bomb_walls:
diff --git a/worlds/witness/data/WitnessLogic.txt b/worlds/witness/data/WitnessLogic.txt
index 8fadf68c3131..0dbb88a107b1 100644
--- a/worlds/witness/data/WitnessLogic.txt
+++ b/worlds/witness/data/WitnessLogic.txt
@@ -752,12 +752,12 @@ Swamp Entry Area (Swamp) - Swamp Sliding Bridge - TrueOneWay:
158300 - 0x00987 (Intro Back 7) - 0x00985 - Shapers
158301 - 0x181A9 (Intro Back 8) - 0x00987 - Shapers
-Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Near Platform - 0x00609:
+Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Platform - 0x00609:
158302 - 0x00609 (Sliding Bridge) - True - Shapers
159342 - 0x0105D (Sliding Bridge Left EP) - 0x00609 - True
159343 - 0x0A304 (Sliding Bridge Right EP) - 0x00609 - True
-Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
+Swamp Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
158313 - 0x00982 (Platform Row 1) - True - Shapers
158314 - 0x0097F (Platform Row 2) - 0x00982 - Shapers
158315 - 0x0098F (Platform Row 3) - 0x0097F - Shapers
diff --git a/worlds/witness/data/WitnessLogicExpert.txt b/worlds/witness/data/WitnessLogicExpert.txt
index c6d6efa96485..0f601724acbe 100644
--- a/worlds/witness/data/WitnessLogicExpert.txt
+++ b/worlds/witness/data/WitnessLogicExpert.txt
@@ -752,12 +752,12 @@ Swamp Entry Area (Swamp) - Swamp Sliding Bridge - TrueOneWay:
158300 - 0x00987 (Intro Back 7) - 0x00985 - Shapers & Rotated Shapers & Triangles & Black/White Squares
158301 - 0x181A9 (Intro Back 8) - 0x00987 - Rotated Shapers & Triangles & Black/White Squares
-Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Near Platform - 0x00609:
+Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Platform - 0x00609:
158302 - 0x00609 (Sliding Bridge) - True - Shapers & Black/White Squares
159342 - 0x0105D (Sliding Bridge Left EP) - 0x00609 - True
159343 - 0x0A304 (Sliding Bridge Right EP) - 0x00609 - True
-Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
+Swamp Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
158313 - 0x00982 (Platform Row 1) - True - Rotated Shapers
158314 - 0x0097F (Platform Row 2) - 0x00982 - Rotated Shapers
158315 - 0x0098F (Platform Row 3) - 0x0097F - Rotated Shapers
diff --git a/worlds/witness/data/WitnessLogicVanilla.txt b/worlds/witness/data/WitnessLogicVanilla.txt
index 1186c470233e..f0c6a8690ed3 100644
--- a/worlds/witness/data/WitnessLogicVanilla.txt
+++ b/worlds/witness/data/WitnessLogicVanilla.txt
@@ -752,12 +752,12 @@ Swamp Entry Area (Swamp) - Swamp Sliding Bridge - TrueOneWay:
158300 - 0x00987 (Intro Back 7) - 0x00985 - Shapers
158301 - 0x181A9 (Intro Back 8) - 0x00987 - Shapers
-Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Near Platform - 0x00609:
+Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Platform - 0x00609:
158302 - 0x00609 (Sliding Bridge) - True - Shapers
159342 - 0x0105D (Sliding Bridge Left EP) - 0x00609 - True
159343 - 0x0A304 (Sliding Bridge Right EP) - 0x00609 - True
-Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
+Swamp Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
158313 - 0x00982 (Platform Row 1) - True - Shapers
158314 - 0x0097F (Platform Row 2) - 0x00982 - Shapers
158315 - 0x0098F (Platform Row 3) - 0x0097F - Shapers
diff --git a/worlds/witness/data/WitnessLogicVariety.txt b/worlds/witness/data/WitnessLogicVariety.txt
index 31263aa33790..b7b705a6db9f 100644
--- a/worlds/witness/data/WitnessLogicVariety.txt
+++ b/worlds/witness/data/WitnessLogicVariety.txt
@@ -752,12 +752,12 @@ Swamp Entry Area (Swamp) - Swamp Sliding Bridge - TrueOneWay:
158300 - 0x00987 (Intro Back 7) - 0x00985 - Rotated Shapers & Triangles
158301 - 0x181A9 (Intro Back 8) - 0x00987 - Shapers & Triangles
-Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Near Platform - 0x00609:
+Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Platform - 0x00609:
158302 - 0x00609 (Sliding Bridge) - True - Shapers
159342 - 0x0105D (Sliding Bridge Left EP) - 0x00609 - True
159343 - 0x0A304 (Sliding Bridge Right EP) - 0x00609 - True
-Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
+Swamp Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
158313 - 0x00982 (Platform Row 1) - True - Shapers
158314 - 0x0097F (Platform Row 2) - 0x00982 - Shapers
158315 - 0x0098F (Platform Row 3) - 0x0097F - Shapers
diff --git a/worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt b/worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt
deleted file mode 100644
index 78d245f9f0b5..000000000000
--- a/worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-New Connections:
-Quarry - Quarry Elevator - TrueOneWay
-Outside Quarry - Quarry Elevator - TrueOneWay
-Outside Bunker - Bunker Elevator - TrueOneWay
-Outside Swamp - Swamp Long Bridge - TrueOneWay
-Swamp Near Boat - Swamp Long Bridge - TrueOneWay
-Town Red Rooftop - Town Maze Rooftop - TrueOneWay
-
-
-Requirement Changes:
-0x035DE - 0x17E2B - True
\ No newline at end of file
diff --git a/worlds/witness/data/utils.py b/worlds/witness/data/utils.py
index 737daff70fae..190c00dc283b 100644
--- a/worlds/witness/data/utils.py
+++ b/worlds/witness/data/utils.py
@@ -204,10 +204,6 @@ def get_caves_except_path_to_challenge_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Exclusions/Caves_Except_Path_To_Challenge.txt")
-def get_elevators_come_to_you() -> List[str]:
- return get_adjustment_file("settings/Door_Shuffle/Elevators_Come_To_You.txt")
-
-
def get_entity_hunt() -> List[str]:
return get_adjustment_file("settings/Entity_Hunt.txt")
diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py
index dac7e3fb4d05..82837aed0686 100644
--- a/worlds/witness/hints.py
+++ b/worlds/witness/hints.py
@@ -301,11 +301,11 @@ def hint_from_location(world: "WitnessWorld", location: str) -> Optional[Witness
def get_item_and_location_names_in_random_order(world: "WitnessWorld",
own_itempool: List["WitnessItem"]) -> Tuple[List[str], List[str]]:
- prog_item_names_in_this_world = [
+ progression_item_names_in_this_world = [
item.name for item in own_itempool
if item.advancement and item.code and item.location
]
- world.random.shuffle(prog_item_names_in_this_world)
+ world.random.shuffle(progression_item_names_in_this_world)
locations_in_this_world = [
location for location in world.multiworld.get_locations(world.player)
@@ -318,22 +318,24 @@ def get_item_and_location_names_in_random_order(world: "WitnessWorld",
location_names_in_this_world = [location.name for location in locations_in_this_world]
- return prog_item_names_in_this_world, location_names_in_this_world
+ return progression_item_names_in_this_world, location_names_in_this_world
def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List["WitnessItem"],
already_hinted_locations: Set[Location]
) -> Tuple[List[WitnessLocationHint], List[WitnessLocationHint]]:
- prog_items_in_this_world, loc_in_this_world = get_item_and_location_names_in_random_order(world, own_itempool)
+ progression_items_in_this_world, locations_in_this_world = get_item_and_location_names_in_random_order(
+ world, own_itempool
+ )
always_items = [
item for item in get_always_hint_items(world)
- if item in prog_items_in_this_world
+ if item in progression_items_in_this_world
]
priority_items = [
item for item in get_priority_hint_items(world)
- if item in prog_items_in_this_world
+ if item in progression_items_in_this_world
]
if world.options.vague_hints:
@@ -341,11 +343,11 @@ def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List["Wi
else:
always_locations = [
location for location in get_always_hint_locations(world)
- if location in loc_in_this_world
+ if location in locations_in_this_world
]
priority_locations = [
location for location in get_priority_hint_locations(world)
- if location in loc_in_this_world
+ if location in locations_in_this_world
]
# Get always and priority location/item hints
@@ -376,7 +378,9 @@ def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List["Wi
def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List["WitnessItem"],
already_hinted_locations: Set[Location], hints_to_use_first: List[WitnessLocationHint],
unhinted_locations_for_hinted_areas: Dict[str, Set[Location]]) -> List[WitnessWordedHint]:
- prog_items_in_this_world, locations_in_this_world = get_item_and_location_names_in_random_order(world, own_itempool)
+ progression_items_in_this_world, locations_in_this_world = get_item_and_location_names_in_random_order(
+ world, own_itempool
+ )
next_random_hint_is_location = world.random.randrange(0, 2)
@@ -390,7 +394,7 @@ def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itemp
}
while len(hints) < hint_amount:
- if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first:
+ if not progression_items_in_this_world and not locations_in_this_world and not hints_to_use_first:
logging.warning(f"Ran out of items/locations to hint for player {world.player_name}.")
break
@@ -399,8 +403,8 @@ def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itemp
location_hint = hints_to_use_first.pop()
elif next_random_hint_is_location and locations_in_this_world:
location_hint = hint_from_location(world, locations_in_this_world.pop())
- elif not next_random_hint_is_location and prog_items_in_this_world:
- location_hint = hint_from_item(world, prog_items_in_this_world.pop(), own_itempool)
+ elif not next_random_hint_is_location and progression_items_in_this_world:
+ location_hint = hint_from_item(world, progression_items_in_this_world.pop(), own_itempool)
# The list that the hint was supposed to be taken from was empty.
# Try the other list, which has to still have something, as otherwise, all lists would be empty,
# which would have triggered the guard condition above.
@@ -587,9 +591,11 @@ def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations
hints = []
for hinted_area in hinted_areas:
- hint_string, prog_amount, hunt_panels = word_area_hint(world, hinted_area, items_per_area[hinted_area])
+ hint_string, progression_amount, hunt_panels = word_area_hint(world, hinted_area, items_per_area[hinted_area])
- hints.append(WitnessWordedHint(hint_string, None, f"hinted_area:{hinted_area}", prog_amount, hunt_panels))
+ hints.append(
+ WitnessWordedHint(hint_string, None, f"hinted_area:{hinted_area}", progression_amount, hunt_panels)
+ )
if len(hinted_areas) < amount:
logging.warning(f"Was not able to make {amount} area hints for player {world.player_name}. "
diff --git a/worlds/witness/options.py b/worlds/witness/options.py
index e1462cc37508..b5c15e242f10 100644
--- a/worlds/witness/options.py
+++ b/worlds/witness/options.py
@@ -2,7 +2,18 @@
from schema import And, Schema
-from Options import Choice, DefaultOnToggle, OptionDict, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility
+from Options import (
+ Choice,
+ DefaultOnToggle,
+ OptionDict,
+ OptionError,
+ OptionGroup,
+ OptionSet,
+ PerGameCommonOptions,
+ Range,
+ Toggle,
+ Visibility,
+)
from .data import static_logic as static_witness_logic
from .data.item_definition_classes import ItemCategory, WeightedItemDefinition
@@ -164,6 +175,16 @@ class ObeliskKeys(DefaultOnToggle):
display_name = "Obelisk Keys"
+class UnlockableWarps(Toggle):
+ """
+ Adds unlockable fast travel points to the game.
+ These warp points are represented by spheres in game. You walk up to one, you unlock it for warping.
+
+ The warp points are: Entry, Symmetry Island, Desert, Quarry, Keep, Shipwreck, Town, Jungle, Bunker, Treehouse, Mountaintop, Caves.
+ """
+ display_name = "Unlockable Fast Travel Points"
+
+
class ShufflePostgame(Toggle):
"""
Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal.
@@ -284,12 +305,33 @@ class ChallengeLasers(Range):
default = 11
-class ElevatorsComeToYou(Toggle):
+class ElevatorsComeToYou(OptionSet):
"""
- If on, the Quarry Elevator, Bunker Elevator and Swamp Long Bridge will "come to you" if you approach them.
- This does actually affect logic as it allows unintended backwards / early access into these areas.
+ In vanilla, some bridges/elevators come to you if you walk up to them when they are not currently there.
+ However, there are some that don't. Notably, this prevents Quarry Elevator from being a logical access method into Quarry, because you can send it away without riding ot and then permanently be locked out of using it.
+
+ This option allows you to change specific elevators/bridges to "come to you" as well.
+
+ - Quarry Elevator: Makes the Quarry Elevator come down when you approach it from lower Quarry and back up when you approach it from above
+ - Swamp Long Bridge: Rotates the side you approach it from towards you, but also rotates the other side away
+ - Bunker Elevator: Makes the Bunker Elevator come to any floor that you approach it from, meaning it can be accessed from the roof immediately
"""
- display_name = "All Bridges & Elevators come to you"
+
+ # Used to be a toggle
+ @classmethod
+ def from_text(cls, text: str):
+ if text.lower() in {"off", "0", "false", "none", "null", "no"}:
+ raise OptionError('elevators_come_to_you is an OptionSet now. The equivalent of "false" is {}')
+ if text.lower() in {"on", "1", "true", "yes"}:
+ raise OptionError(
+ f'elevators_come_to_you is an OptionSet now. The equivalent of "true" is {set(cls.valid_keys)}'
+ )
+ return super().from_text(text)
+
+ display_name = "Elevators come to you"
+
+ valid_keys = frozenset({"Quarry Elevator", "Swamp Long Bridge", "Bunker Elevator"})
+ default = frozenset({"Quarry Elevator"})
class TrapPercentage(Range):
@@ -406,6 +448,7 @@ class PuzzleRandomizationSeed(Range):
Sigma Rando, which is the basis for all puzzle randomization in this randomizer, uses a seed from 1 to 9999999 for the puzzle randomization.
This option lets you set this seed yourself.
"""
+ display_name = "Puzzle Randomization Seed"
range_start = 1
range_end = 9999999
default = "random"
@@ -423,6 +466,7 @@ class TheWitnessOptions(PerGameCommonOptions):
shuffle_discarded_panels: ShuffleDiscardedPanels
shuffle_vault_boxes: ShuffleVaultBoxes
obelisk_keys: ObeliskKeys
+ unlockable_warps: UnlockableWarps
shuffle_EPs: ShuffleEnvironmentalPuzzles # noqa: N815
EP_difficulty: EnvironmentalPuzzlesDifficulty
shuffle_postgame: ShufflePostgame
@@ -456,7 +500,7 @@ class TheWitnessOptions(PerGameCommonOptions):
MountainLasers,
ChallengeLasers,
]),
- OptionGroup("Panel Hunt Settings", [
+ OptionGroup("Panel Hunt Options", [
PanelHuntRequiredPercentage,
PanelHuntTotal,
PanelHuntPostgame,
@@ -478,6 +522,9 @@ class TheWitnessOptions(PerGameCommonOptions):
ShuffleBoat,
ObeliskKeys,
]),
+ OptionGroup("Warps", [
+ UnlockableWarps,
+ ]),
OptionGroup("Filler Items", [
PuzzleSkipAmount,
TrapPercentage,
diff --git a/worlds/witness/player_items.py b/worlds/witness/player_items.py
index 3be298ebccae..831e614f21c4 100644
--- a/worlds/witness/player_items.py
+++ b/worlds/witness/player_items.py
@@ -55,7 +55,7 @@ def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic,
name: data for (name, data) in self.item_data.items()
if data.classification not in
{ItemClassification.progression, ItemClassification.progression_skip_balancing}
- or name in player_logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME
+ or name in player_logic.PROGRESSION_ITEMS_ACTUALLY_IN_THE_GAME
}
# Downgrade door items
@@ -76,7 +76,7 @@ def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic,
}
for item_name, item_data in progression_dict.items():
if isinstance(item_data.definition, ProgressiveItemDefinition):
- num_progression = len(self._logic.MULTI_LISTS[item_name])
+ num_progression = len(self._logic.PROGRESSIVE_LISTS[item_name])
self._mandatory_items[item_name] = num_progression
else:
self._mandatory_items[item_name] = 1
diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py
index f8b7db3570a9..58f15532f58c 100644
--- a/worlds/witness/player_logic.py
+++ b/worlds/witness/player_logic.py
@@ -34,7 +34,6 @@
get_discard_exclusion_list,
get_early_caves_list,
get_early_caves_start_list,
- get_elevators_come_to_you,
get_entity_hunt,
get_ep_all_individual,
get_ep_easy,
@@ -75,13 +74,15 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in
self.UNREACHABLE_REGIONS: Set[str] = set()
+ self.THEORETICAL_BASE_ITEMS: Set[str] = set()
self.THEORETICAL_ITEMS: Set[str] = set()
- self.THEORETICAL_ITEMS_NO_MULTI: Set[str] = set()
- self.MULTI_AMOUNTS: Dict[str, int] = defaultdict(lambda: 1)
- self.MULTI_LISTS: Dict[str, List[str]] = {}
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI: Set[str] = set()
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME: Set[str] = set()
+ self.BASE_PROGESSION_ITEMS_ACTUALLY_IN_THE_GAME: Set[str] = set()
+ self.PROGRESSION_ITEMS_ACTUALLY_IN_THE_GAME: Set[str] = set()
+
+ self.PARENT_ITEM_COUNT_PER_BASE_ITEM: Dict[str, int] = defaultdict(lambda: 1)
+ self.PROGRESSIVE_LISTS: Dict[str, List[str]] = {}
self.DOOR_ITEMS_BY_ID: Dict[str, List[str]] = {}
+
self.STARTING_INVENTORY: Set[str] = set()
self.DIFFICULTY = world.options.puzzle_randomization
@@ -183,13 +184,13 @@ def reduce_req_within_region(self, entity_hex: str) -> WitnessRule:
# Remove any items that don't actually exist in the settings (e.g. Symbol Shuffle turned off)
these_items = frozenset({
- subset.intersection(self.THEORETICAL_ITEMS_NO_MULTI)
+ subset.intersection(self.THEORETICAL_BASE_ITEMS)
for subset in these_items
})
# Update the list of "items that are actually being used by any entity"
for subset in these_items:
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(subset)
+ self.BASE_PROGESSION_ITEMS_ACTUALLY_IN_THE_GAME.update(subset)
# Handle door entities (door shuffle)
if entity_hex in self.DOOR_ITEMS_BY_ID:
@@ -197,7 +198,7 @@ def reduce_req_within_region(self, entity_hex: str) -> WitnessRule:
door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[entity_hex]})
for dependent_item in door_items:
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependent_item)
+ self.BASE_PROGESSION_ITEMS_ACTUALLY_IN_THE_GAME.update(dependent_item)
these_items = logical_and_witness_rules([door_items, these_items])
@@ -299,10 +300,10 @@ def make_single_adjustment(self, adj_type: str, line: str) -> None:
self.THEORETICAL_ITEMS.add(item_name)
if isinstance(static_witness_logic.ALL_ITEMS[item_name], ProgressiveItemDefinition):
- self.THEORETICAL_ITEMS_NO_MULTI.update(cast(ProgressiveItemDefinition,
- static_witness_logic.ALL_ITEMS[item_name]).child_item_names)
+ self.THEORETICAL_BASE_ITEMS.update(cast(ProgressiveItemDefinition,
+ static_witness_logic.ALL_ITEMS[item_name]).child_item_names)
else:
- self.THEORETICAL_ITEMS_NO_MULTI.add(item_name)
+ self.THEORETICAL_BASE_ITEMS.add(item_name)
if static_witness_logic.ALL_ITEMS[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]:
entity_hexes = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).panel_id_hexes
@@ -316,11 +317,11 @@ def make_single_adjustment(self, adj_type: str, line: str) -> None:
self.THEORETICAL_ITEMS.discard(item_name)
if isinstance(static_witness_logic.ALL_ITEMS[item_name], ProgressiveItemDefinition):
- self.THEORETICAL_ITEMS_NO_MULTI.difference_update(
+ self.THEORETICAL_BASE_ITEMS.difference_update(
cast(ProgressiveItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).child_item_names
)
else:
- self.THEORETICAL_ITEMS_NO_MULTI.discard(item_name)
+ self.THEORETICAL_BASE_ITEMS.discard(item_name)
if static_witness_logic.ALL_ITEMS[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]:
entity_hexes = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).panel_id_hexes
@@ -624,8 +625,29 @@ def make_options_adjustments(self, world: "WitnessWorld") -> None:
if world.options.early_caves == "add_to_pool" and not remote_doors:
adjustment_linesets_in_order.append(get_early_caves_list())
- if world.options.elevators_come_to_you:
- adjustment_linesets_in_order.append(get_elevators_come_to_you())
+ if "Quarry Elevator" in world.options.elevators_come_to_you:
+ adjustment_linesets_in_order.append([
+ "New Connections:",
+ "Quarry - Quarry Elevator - TrueOneWay",
+ "Outside Quarry - Quarry Elevator - TrueOneWay",
+ ])
+ if "Bunker Elevator" in world.options.elevators_come_to_you:
+ adjustment_linesets_in_order.append([
+ "New Connections:",
+ "Outside Bunker - Bunker Elevator - TrueOneWay",
+ ])
+ if "Swamp Long Bridge" in world.options.elevators_come_to_you:
+ adjustment_linesets_in_order.append([
+ "New Connections:",
+ "Outside Swamp - Swamp Long Bridge - TrueOneWay",
+ "Swamp Near Boat - Swamp Long Bridge - TrueOneWay",
+ "Requirement Changes:",
+ "0x035DE - 0x17E2B - True", # Swamp Purple Sand Bottom EP
+ ])
+ # if "Town Maze Rooftop Bridge" in world.options.elevators_come_to_you:
+ # adjustment_linesets_in_order.append([
+ # "New Connections:"
+ # "Town Red Rooftop - Town Maze Rooftop - TrueOneWay"
if world.options.victory_condition == "panel_hunt":
adjustment_linesets_in_order.append(get_entity_hunt())
@@ -843,7 +865,7 @@ def make_dependency_reduced_checklist(self) -> None:
self.REQUIREMENTS_BY_HEX = {}
self.USED_EVENT_NAMES_BY_HEX = defaultdict(list)
self.CONNECTIONS_BY_REGION_NAME = {}
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI = set()
+ self.BASE_PROGESSION_ITEMS_ACTUALLY_IN_THE_GAME = set()
# Make independent requirements for entities
for entity_hex in self.DEPENDENT_REQUIREMENTS_BY_HEX.keys():
@@ -868,18 +890,18 @@ def finalize_items(self) -> None:
"""
Finalise which items are used in the world, and handle their progressive versions.
"""
- for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI:
+ for item in self.BASE_PROGESSION_ITEMS_ACTUALLY_IN_THE_GAME:
if item not in self.THEORETICAL_ITEMS:
progressive_item_name = static_witness_logic.get_parent_progressive_item(item)
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(progressive_item_name)
+ self.PROGRESSION_ITEMS_ACTUALLY_IN_THE_GAME.add(progressive_item_name)
child_items = cast(ProgressiveItemDefinition,
static_witness_logic.ALL_ITEMS[progressive_item_name]).child_item_names
- multi_list = [child_item for child_item in child_items
- if child_item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI]
- self.MULTI_AMOUNTS[item] = multi_list.index(item) + 1
- self.MULTI_LISTS[progressive_item_name] = multi_list
+ progressive_list = [child_item for child_item in child_items
+ if child_item in self.BASE_PROGESSION_ITEMS_ACTUALLY_IN_THE_GAME]
+ self.PARENT_ITEM_COUNT_PER_BASE_ITEM[item] = progressive_list.index(item) + 1
+ self.PROGRESSIVE_LISTS[progressive_item_name] = progressive_list
else:
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(item)
+ self.PROGRESSION_ITEMS_ACTUALLY_IN_THE_GAME.add(item)
def solvability_guaranteed(self, entity_hex: str) -> bool:
return not (
@@ -962,7 +984,7 @@ def make_event_panel_lists(self) -> None:
Makes event-item pairs for entities with associated events, unless these entities are disabled.
"""
- self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"
+ self.USED_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION].append("Victory")
for event_hex, event_name in self.ALWAYS_EVENT_NAMES_BY_HEX.items():
self.USED_EVENT_NAMES_BY_HEX[event_hex].append(event_name)
diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py
index 8993048065f4..687d74f771cb 100644
--- a/worlds/witness/presets.py
+++ b/worlds/witness/presets.py
@@ -35,7 +35,8 @@
"challenge_lasers": 11,
"early_caves": EarlyCaves.option_off,
- "elevators_come_to_you": False,
+
+ "elevators_come_to_you": ElevatorsComeToYou.default,
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": PuzzleSkipAmount.default,
@@ -73,7 +74,8 @@
"challenge_lasers": 9,
"early_caves": EarlyCaves.option_off,
- "elevators_come_to_you": False,
+
+ "elevators_come_to_you": ElevatorsComeToYou.default,
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": 15,
@@ -111,7 +113,8 @@
"challenge_lasers": 9,
"early_caves": EarlyCaves.option_off,
- "elevators_come_to_you": True,
+
+ "elevators_come_to_you": ElevatorsComeToYou.valid_keys,
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": 15,
diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py
index 74ea2aef5740..323d5943c853 100644
--- a/worlds/witness/rules.py
+++ b/worlds/witness/rules.py
@@ -201,10 +201,10 @@ def _has_item(item: str, world: "WitnessWorld",
if item == "Theater to Tunnels":
return lambda state: _can_do_theater_to_tunnels(state, world)
- prog_item = static_witness_logic.get_parent_progressive_item(item)
- needed_amount = player_logic.MULTI_AMOUNTS[item]
+ actual_item = static_witness_logic.get_parent_progressive_item(item)
+ needed_amount = player_logic.PARENT_ITEM_COUNT_PER_BASE_ITEM[item]
- simple_rule: SimpleItemRepresentation = SimpleItemRepresentation(prog_item, needed_amount)
+ simple_rule: SimpleItemRepresentation = SimpleItemRepresentation(actual_item, needed_amount)
return simple_rule
diff --git a/worlds/witness/test/test_auto_elevators.py b/worlds/witness/test/test_auto_elevators.py
index 16b1b5a56d37..f91943e85577 100644
--- a/worlds/witness/test/test_auto_elevators.py
+++ b/worlds/witness/test/test_auto_elevators.py
@@ -1,49 +1,25 @@
-from ..test import WitnessMultiworldTestBase, WitnessTestBase
-
-
-class TestElevatorsComeToYou(WitnessTestBase):
- options = {
- "elevators_come_to_you": True,
- "shuffle_doors": "mixed",
- "shuffle_symbols": False,
- }
-
- def test_bunker_laser(self) -> None:
- """
- In elevators_come_to_you, Bunker can be entered from the back.
- This means that you can access the laser with just Bunker Elevator Control (Panel).
- It also means that you can, for example, access UV Room with the Control and the Elevator Room Entry Door.
- """
-
- self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", self.player))
-
- self.collect_by_name("Bunker Elevator Control (Panel)")
-
- self.assertTrue(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", self.player))
- self.assertFalse(self.multiworld.state.can_reach("Bunker UV Room 2", "Location", self.player))
-
- self.collect_by_name("Bunker Elevator Room Entry (Door)")
- self.collect_by_name("Bunker Drop-Down Door Controls (Panel)")
-
- self.assertTrue(self.multiworld.state.can_reach("Bunker UV Room 2", "Location", self.player))
+from ..test import WitnessMultiworldTestBase
class TestElevatorsComeToYouBleed(WitnessMultiworldTestBase):
options_per_world = [
{
- "elevators_come_to_you": False,
+ "elevators_come_to_you": {},
},
{
- "elevators_come_to_you": True,
+ "elevators_come_to_you": {"Quarry Elevator", "Swamp Long Bridge", "Bunker Elevator"},
},
{
- "elevators_come_to_you": False,
+ "elevators_come_to_you": {}
},
]
common_options = {
"shuffle_symbols": False,
"shuffle_doors": "panels",
+ "shuffle_boat": True,
+ "shuffle_EPs": "individual",
+ "obelisk_keys": False,
}
def test_correct_access_per_player(self) -> None:
@@ -53,14 +29,22 @@ def test_correct_access_per_player(self) -> None:
(This is essentially a "does connection info bleed over" test).
"""
- self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 1))
- self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 2))
- self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 3))
-
- self.collect_by_name(["Bunker Elevator Control (Panel)"], 1)
- self.collect_by_name(["Bunker Elevator Control (Panel)"], 2)
- self.collect_by_name(["Bunker Elevator Control (Panel)"], 3)
-
- self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 1))
- self.assertTrue(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 2))
- self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 3))
+ combinations = [
+ ("Quarry Elevator Control (Panel)", "Quarry Boathouse Intro Left"),
+ ("Swamp Long Bridge (Panel)", "Swamp Long Bridge Side EP"),
+ ("Bunker Elevator Control (Panel)", "Bunker Laser Panel"),
+ ]
+
+ for item, location in combinations:
+ with self.subTest(f"Test that {item} only locks {location} for player 2"):
+ self.assertFalse(self.multiworld.state.can_reach_location(location, 1))
+ self.assertFalse(self.multiworld.state.can_reach_location(location, 2))
+ self.assertFalse(self.multiworld.state.can_reach_location(location, 3))
+
+ self.collect_by_name(item, 1)
+ self.collect_by_name(item, 2)
+ self.collect_by_name(item, 3)
+
+ self.assertFalse(self.multiworld.state.can_reach_location(location, 1))
+ self.assertTrue(self.multiworld.state.can_reach_location(location, 2))
+ self.assertFalse(self.multiworld.state.can_reach_location(location, 3))
diff --git a/worlds/witness/test/test_roll_other_options.py b/worlds/witness/test/test_roll_other_options.py
index 7473716e06e6..05f3235a1f4d 100644
--- a/worlds/witness/test/test_roll_other_options.py
+++ b/worlds/witness/test/test_roll_other_options.py
@@ -1,3 +1,4 @@
+from ..options import ElevatorsComeToYou
from ..test import WitnessTestBase
# These are just some random options combinations, just to catch whether I broke anything obvious
@@ -19,7 +20,7 @@ class TestExpertNonRandomizedEPs(WitnessTestBase):
class TestVanillaAutoElevatorsPanels(WitnessTestBase):
options = {
"puzzle_randomization": "none",
- "elevators_come_to_you": True,
+ "elevators_come_to_you": ElevatorsComeToYou.valid_keys - ElevatorsComeToYou.default, # Opposite of default
"shuffle_doors": "panels",
"victory_condition": "mountain_box_short",
"early_caves": True,
@@ -61,3 +62,10 @@ class TestPostgameGroupedDoors(WitnessTestBase):
"door_groupings": "regional",
"victory_condition": "elevator",
}
+
+
+class TestPostgamePanels(WitnessTestBase):
+ options = {
+ "victory_condition": "mountain_box_long",
+ "shuffle_postgame": True
+ }
diff --git a/worlds/yugioh06/__init__.py b/worlds/yugioh06/__init__.py
index a39b52cd09d5..90bbed1a2174 100644
--- a/worlds/yugioh06/__init__.py
+++ b/worlds/yugioh06/__init__.py
@@ -50,7 +50,7 @@
class Yugioh06Web(WebWorld):
theme = "stone"
setup = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up Yu-Gi-Oh! - Ultimate Masters Edition - World Championship Tournament 2006 "
"for Archipelago on your computer.",
"English",
diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py
index d5e86bb33292..5a4e2bb48f18 100644
--- a/worlds/zillion/__init__.py
+++ b/worlds/zillion/__init__.py
@@ -3,11 +3,12 @@
import functools
import settings
import threading
-import typing
-from typing import Any, Dict, List, Set, Tuple, Optional, Union
+from typing import Any, ClassVar
import os
import logging
+from typing_extensions import override
+
from BaseClasses import ItemClassification, LocationProgressType, \
MultiWorld, Item, CollectionState, Entrance, Tutorial
@@ -47,7 +48,7 @@ class RomStart(str):
"""
rom_file: RomFile = RomFile(RomFile.copy_to)
- rom_start: typing.Union[RomStart, bool] = RomStart("retroarch")
+ rom_start: RomStart | bool = RomStart("retroarch")
class ZillionWebWorld(WebWorld):
@@ -76,7 +77,7 @@ class ZillionWorld(World):
options_dataclass = ZillionOptions
options: ZillionOptions # type: ignore
- settings: typing.ClassVar[ZillionSettings] # type: ignore
+ settings: ClassVar[ZillionSettings] # type: ignore
# these type: ignore are because of this issue: https://github.com/python/typing/discussions/1486
topology_present = True # indicate if world type has any meaningful layout/pathing
@@ -89,14 +90,14 @@ class ZillionWorld(World):
class LogStreamInterface:
logger: logging.Logger
- buffer: List[str]
+ buffer: list[str]
def __init__(self, logger: logging.Logger) -> None:
self.logger = logger
self.buffer = []
def write(self, msg: str) -> None:
- if msg.endswith('\n'):
+ if msg.endswith("\n"):
self.buffer.append(msg[:-1])
self.logger.debug("".join(self.buffer))
self.buffer = []
@@ -108,21 +109,21 @@ def flush(self) -> None:
lsi: LogStreamInterface
- id_to_zz_item: Optional[Dict[int, ZzItem]] = None
+ id_to_zz_item: dict[int, ZzItem] | None = None
zz_system: System
- _item_counts: "Counter[str]" = Counter()
+ _item_counts: Counter[str] = Counter()
"""
These are the items counts that will be in the game,
which might be different from the item counts the player asked for in options
(if the player asked for something invalid).
"""
- my_locations: List[ZillionLocation] = []
+ my_locations: list[ZillionLocation] = []
""" This is kind of a cache to avoid iterating through all the multiworld locations in logic. """
slot_data_ready: threading.Event
""" This event is set in `generate_output` when the data is ready for `fill_slot_data` """
- logic_cache: Union[ZillionLogicCache, None] = None
+ logic_cache: ZillionLogicCache | None = None
- def __init__(self, world: MultiWorld, player: int):
+ def __init__(self, world: MultiWorld, player: int) -> None:
super().__init__(world, player)
self.logger = logging.getLogger("Zillion")
self.lsi = ZillionWorld.LogStreamInterface(self.logger)
@@ -133,6 +134,7 @@ def _make_item_maps(self, start_char: Chars) -> None:
_id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char)
self.id_to_zz_item = id_to_zz_item
+ @override
def generate_early(self) -> None:
zz_op, item_counts = validate(self.options)
@@ -150,12 +152,13 @@ def generate_early(self) -> None:
# just in case the options changed anything (I don't think they do)
assert self.zz_system.randomizer, "init failed"
for zz_name in self.zz_system.randomizer.locations:
- if zz_name != 'main':
+ if zz_name != "main":
assert self.zz_system.randomizer.loc_name_2_pretty[zz_name] in self.location_name_to_id, \
f"{self.zz_system.randomizer.loc_name_2_pretty[zz_name]} not in location map"
self._make_item_maps(zz_op.start_char)
+ @override
def create_regions(self) -> None:
assert self.zz_system.randomizer, "generate_early hasn't been called"
assert self.id_to_zz_item, "generate_early hasn't been called"
@@ -177,23 +180,23 @@ def create_regions(self) -> None:
zz_loc.req.gun = 1
assert len(self.zz_system.randomizer.get_locations(Req(gun=1, jump=1))) != 0
- start = self.zz_system.randomizer.regions['start']
+ start = self.zz_system.randomizer.regions["start"]
- all: Dict[str, ZillionRegion] = {}
+ all_regions: dict[str, ZillionRegion] = {}
for here_zz_name, zz_r in self.zz_system.randomizer.regions.items():
here_name = "Menu" if here_zz_name == "start" else zz_reg_name_to_reg_name(here_zz_name)
- all[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w)
- self.multiworld.regions.append(all[here_name])
+ all_regions[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w)
+ self.multiworld.regions.append(all_regions[here_name])
limited_skill = Req(gun=3, jump=3, skill=self.zz_system.randomizer.options.skill, hp=940, red=1, floppy=126)
queue = deque([start])
- done: Set[str] = set()
+ done: set[str] = set()
while len(queue):
zz_here = queue.popleft()
here_name = "Menu" if zz_here.name == "start" else zz_reg_name_to_reg_name(zz_here.name)
if here_name in done:
continue
- here = all[here_name]
+ here = all_regions[here_name]
for zz_loc in zz_here.locations:
# if local gun reqs didn't place "keyword" item
@@ -217,15 +220,16 @@ def access_rule_wrapped(zz_loc_local: ZzLocation,
self.my_locations.append(loc)
for zz_dest in zz_here.connections.keys():
- dest_name = "Menu" if zz_dest.name == 'start' else zz_reg_name_to_reg_name(zz_dest.name)
- dest = all[dest_name]
- exit = Entrance(p, f"{here_name} to {dest_name}", here)
- here.exits.append(exit)
- exit.connect(dest)
+ dest_name = "Menu" if zz_dest.name == "start" else zz_reg_name_to_reg_name(zz_dest.name)
+ dest = all_regions[dest_name]
+ exit_ = Entrance(p, f"{here_name} to {dest_name}", here)
+ here.exits.append(exit_)
+ exit_.connect(dest)
queue.append(zz_dest)
done.add(here.name)
+ @override
def create_items(self) -> None:
if not self.id_to_zz_item:
self._make_item_maps("JJ")
@@ -249,14 +253,11 @@ def create_items(self) -> None:
self.logger.debug(f"Zillion Items: {item_name} 1")
self.multiworld.itempool.append(self.create_item(item_name))
- def set_rules(self) -> None:
- # logic for this game is in create_regions
- pass
-
+ @override
def generate_basic(self) -> None:
assert self.zz_system.randomizer, "generate_early hasn't been called"
# main location name is an alias
- main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations['main'].name]
+ main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations["main"].name]
self.multiworld.get_location(main_loc_name, self.player)\
.place_locked_item(self.create_item("Win"))
@@ -264,22 +265,18 @@ def generate_basic(self) -> None:
lambda state: state.has("Win", self.player)
@staticmethod
- def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None:
+ def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: # noqa: ANN401
# item link pools are about to be created in main
# JJ can't be an item link unless all the players share the same start_char
# (The reason for this is that the JJ ZillionItem will have a different ZzItem depending
# on whether the start char is Apple or Champ, and the logic depends on that ZzItem.)
for group in multiworld.groups.values():
- # TODO: remove asserts on group when we can specify which members of TypedDict are optional
- assert "game" in group
- if group["game"] == "Zillion":
- assert "item_pool" in group
+ if group["game"] == "Zillion" and "item_pool" in group:
item_pool = group["item_pool"]
to_stay: Chars = "JJ"
if "JJ" in item_pool:
- assert "players" in group
- group_players = group["players"]
- players_start_chars: List[Tuple[int, Chars]] = []
+ group["players"] = group_players = set(group["players"])
+ players_start_chars: list[tuple[int, Chars]] = []
for player in group_players:
z_world = multiworld.worlds[player]
assert isinstance(z_world, ZillionWorld)
@@ -291,17 +288,17 @@ def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None:
elif start_char_counts["Champ"] > start_char_counts["Apple"]:
to_stay = "Champ"
else: # equal
- choices: Tuple[Chars, ...] = ("Apple", "Champ")
+ choices: tuple[Chars, ...] = ("Apple", "Champ")
to_stay = multiworld.random.choice(choices)
for p, sc in players_start_chars:
if sc != to_stay:
group_players.remove(p)
- assert "world" in group
group_world = group["world"]
assert isinstance(group_world, ZillionWorld)
group_world._make_item_maps(to_stay)
+ @override
def post_fill(self) -> None:
"""Optional Method that is called after regular fill. Can be used to do adjustments before output generation.
This happens before progression balancing, so the items may not be in their final locations yet."""
@@ -317,10 +314,10 @@ def finalize_item_locations(self) -> GenData:
assert self.zz_system.randomizer, "generate_early hasn't been called"
- # debug_zz_loc_ids: Dict[str, int] = {}
+ # debug_zz_loc_ids: dict[str, int] = {}
empty = zz_items[4]
multi_item = empty # a different patcher method differentiates empty from ap multi item
- multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name)
+ multi_items: dict[str, tuple[str, str]] = {} # zz_loc_name to (item_name, player_name)
for z_loc in self.multiworld.get_locations(self.player):
assert isinstance(z_loc, ZillionLocation)
# debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc)
@@ -343,7 +340,7 @@ def finalize_item_locations(self) -> GenData:
# print(id_)
# print("size:", len(debug_zz_loc_ids))
- # debug_loc_to_id: Dict[str, int] = {}
+ # debug_loc_to_id: dict[str, int] = {}
# regions = self.zz_randomizer.regions
# for region in regions.values():
# for loc in region.locations:
@@ -358,10 +355,11 @@ def finalize_item_locations(self) -> GenData:
f"in world {self.player} didn't get an item"
)
- game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode()
+ game_id = self.multiworld.player_name[self.player].encode() + b"\x00" + self.multiworld.seed_name[-6:].encode()
return GenData(multi_items, self.zz_system.get_game(), game_id)
+ @override
def generate_output(self, output_directory: str) -> None:
"""This method gets called from a threadpool, do not use multiworld.random here.
If you need any last-second randomization, use self.random instead."""
@@ -383,6 +381,7 @@ def generate_output(self, output_directory: str) -> None:
self.logger.debug(f"Zillion player {self.player} finished generate_output")
+ @override
def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot
"""Fill in the `slot_data` field in the `Connected` network package.
This is a way the generator can give custom data to the client.
@@ -400,6 +399,7 @@ def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot
# end of ordered Main.py calls
+ @override
def create_item(self, name: str) -> Item:
"""Create an item for this world type and player.
Warning: this may be called with self.multiworld = None, for example by MultiServer"""
@@ -420,6 +420,7 @@ def create_item(self, name: str) -> Item:
z_item = ZillionItem(name, classification, item_id, self.player, zz_item)
return z_item
+ @override
def get_filler_item_name(self) -> str:
"""Called when the item pool needs to be filled with additional items to match location count."""
return "Empty"
diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py
index 09d0565e1c5e..d629df583a81 100644
--- a/worlds/zillion/client.py
+++ b/worlds/zillion/client.py
@@ -3,7 +3,7 @@
import io
import pkgutil
import platform
-from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, cast
+from typing import Any, ClassVar, Coroutine, Protocol, cast
from CommonClient import CommonContext, server_loop, gui_enabled, \
ClientCommandProcessor, logger, get_base_parser
@@ -11,6 +11,7 @@
from Utils import async_start
import colorama
+from typing_extensions import override
from zilliandomizer.zri.memory import Memory, RescueInfo
from zilliandomizer.zri import events
@@ -35,11 +36,11 @@ def _cmd_map(self) -> None:
class ToggleCallback(Protocol):
- def __call__(self) -> None: ...
+ def __call__(self) -> object: ...
class SetRoomCallback(Protocol):
- def __call__(self, rooms: List[List[int]]) -> None: ...
+ def __call__(self, rooms: list[list[int]]) -> object: ...
class ZillionContext(CommonContext):
@@ -47,7 +48,7 @@ class ZillionContext(CommonContext):
command_processor = ZillionCommandProcessor
items_handling = 1 # receive items from other players
- known_name: Optional[str]
+ known_name: str | None
""" This is almost the same as `auth` except `auth` is reset to `None` when server disconnects, and this isn't. """
from_game: "asyncio.Queue[events.EventFromGame]"
@@ -56,11 +57,11 @@ class ZillionContext(CommonContext):
""" local checks watched by server """
next_item: int
""" index in `items_received` """
- ap_id_to_name: Dict[int, str]
- ap_id_to_zz_id: Dict[int, int]
+ ap_id_to_name: dict[int, str]
+ ap_id_to_zz_id: dict[int, int]
start_char: Chars = "JJ"
- rescues: Dict[int, RescueInfo] = {}
- loc_mem_to_id: Dict[int, int] = {}
+ rescues: dict[int, RescueInfo] = {}
+ loc_mem_to_id: dict[int, int] = {}
got_room_info: asyncio.Event
""" flag for connected to server """
got_slot_data: asyncio.Event
@@ -119,22 +120,22 @@ def reset_game_state(self) -> None:
self.finished_game = False
self.items_received.clear()
- # override
- def on_deathlink(self, data: Dict[str, Any]) -> None:
+ @override
+ def on_deathlink(self, data: dict[str, Any]) -> None:
self.to_game.put_nowait(events.DeathEventToGame())
return super().on_deathlink(data)
- # override
+ @override
async def server_auth(self, password_requested: bool = False) -> None:
if password_requested and not self.password:
await super().server_auth(password_requested)
if not self.auth:
- logger.info('waiting for connection to game...')
+ logger.info("waiting for connection to game...")
return
logger.info("logging in to server...")
await self.send_connect()
- # override
+ @override
def run_gui(self) -> None:
from kvui import GameManager
from kivy.core.text import Label as CoreLabel
@@ -154,10 +155,10 @@ class MapPanel(Widget):
MAP_WIDTH: ClassVar[int] = 281
map_background: CoreImage
- _number_textures: List[Texture] = []
- rooms: List[List[int]] = []
+ _number_textures: list[Texture] = []
+ rooms: list[list[int]] = []
- def __init__(self, **kwargs: Any) -> None:
+ def __init__(self, **kwargs: Any) -> None: # noqa: ANN401
super().__init__(**kwargs)
FILE_NAME = "empty-zillion-map-row-col-labels-281.png"
@@ -183,7 +184,7 @@ def _make_numbers(self) -> None:
label.refresh()
self._number_textures.append(label.texture)
- def update_map(self, *args: Any) -> None:
+ def update_map(self, *args: Any) -> None: # noqa: ANN401
self.canvas.clear()
with self.canvas:
@@ -203,6 +204,7 @@ def update_map(self, *args: Any) -> None:
num_texture = self._number_textures[num]
Rectangle(texture=num_texture, size=num_texture.size, pos=pos)
+ @override
def build(self) -> Layout:
container = super().build()
self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=ZillionManager.MapPanel.MAP_WIDTH)
@@ -216,17 +218,18 @@ def toggle_map_width(self) -> None:
self.map_widget.width = 0
self.container.do_layout()
- def set_rooms(self, rooms: List[List[int]]) -> None:
+ def set_rooms(self, rooms: list[list[int]]) -> None:
self.map_widget.rooms = rooms
self.map_widget.update_map()
self.ui = ZillionManager(self)
- self.ui_toggle_map = lambda: self.ui.toggle_map_width()
- self.ui_set_rooms = lambda rooms: self.ui.set_rooms(rooms)
+ self.ui_toggle_map = lambda: isinstance(self.ui, ZillionManager) and self.ui.toggle_map_width()
+ self.ui_set_rooms = lambda rooms: isinstance(self.ui, ZillionManager) and self.ui.set_rooms(rooms)
run_co: Coroutine[Any, Any, None] = self.ui.async_run()
self.ui_task = asyncio.create_task(run_co, name="UI")
- def on_package(self, cmd: str, args: Dict[str, Any]) -> None:
+ @override
+ def on_package(self, cmd: str, args: dict[str, Any]) -> None:
self.room_item_numbers_to_ui()
if cmd == "Connected":
logger.info("logged in to Archipelago server")
@@ -238,7 +241,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None:
if "start_char" not in slot_data:
logger.warning("invalid Zillion `Connected` packet, `slot_data` missing `start_char`")
return
- self.start_char = slot_data['start_char']
+ self.start_char = slot_data["start_char"]
if self.start_char not in {"Apple", "Champ", "JJ"}:
logger.warning("invalid Zillion `Connected` packet, "
f"`slot_data` `start_char` has invalid value: {self.start_char}")
@@ -259,7 +262,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None:
self.rescues[0 if rescue_id == "0" else 1] = ri
if "loc_mem_to_id" not in slot_data:
- logger.warn("invalid Zillion `Connected` packet, `slot_data` missing `loc_mem_to_id`")
+ logger.warning("invalid Zillion `Connected` packet, `slot_data` missing `loc_mem_to_id`")
return
loc_mem_to_id = slot_data["loc_mem_to_id"]
self.loc_mem_to_id = {}
@@ -286,7 +289,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None:
if "keys" not in args:
logger.warning(f"invalid Retrieved packet to ZillionClient: {args}")
return
- keys = cast(Dict[str, Optional[str]], args["keys"])
+ keys = cast(dict[str, str | None], args["keys"])
doors_b64 = keys.get(f"zillion-{self.auth}-doors", None)
if doors_b64:
logger.info("received door data from server")
@@ -321,9 +324,9 @@ def process_from_game_queue(self) -> None:
if server_id in self.missing_locations:
self.ap_local_count += 1
n_locations = len(self.missing_locations) + len(self.checked_locations) - 1 # -1 to ignore win
- logger.info(f'New Check: {loc_name} ({self.ap_local_count}/{n_locations})')
+ logger.info(f"New Check: {loc_name} ({self.ap_local_count}/{n_locations})")
async_start(self.send_msgs([
- {"cmd": 'LocationChecks', "locations": [server_id]}
+ {"cmd": "LocationChecks", "locations": [server_id]}
]))
else:
# This will happen a lot in Zillion,
@@ -334,7 +337,7 @@ def process_from_game_queue(self) -> None:
elif isinstance(event_from_game, events.WinEventFromGame):
if not self.finished_game:
async_start(self.send_msgs([
- {"cmd": 'LocationChecks', "locations": [loc_name_to_id["J-6 bottom far left"]]},
+ {"cmd": "LocationChecks", "locations": [loc_name_to_id["J-6 bottom far left"]]},
{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}
]))
self.finished_game = True
@@ -362,24 +365,24 @@ def process_items_received(self) -> None:
ap_id = self.items_received[index].item
from_name = self.player_names[self.items_received[index].player]
# TODO: colors in this text, like sni client?
- logger.info(f'received {self.ap_id_to_name[ap_id]} from {from_name}')
+ logger.info(f"received {self.ap_id_to_name[ap_id]} from {from_name}")
self.to_game.put_nowait(
events.ItemEventToGame(zz_item_ids)
)
self.next_item = len(self.items_received)
-def name_seed_from_ram(data: bytes) -> Tuple[str, str]:
+def name_seed_from_ram(data: bytes) -> tuple[str, str]:
""" returns player name, and end of seed string """
if len(data) == 0:
# no connection to game
return "", "xxx"
- null_index = data.find(b'\x00')
+ null_index = data.find(b"\x00")
if null_index == -1:
logger.warning(f"invalid game id in rom {repr(data)}")
null_index = len(data)
name = data[:null_index].decode()
- null_index_2 = data.find(b'\x00', null_index + 1)
+ null_index_2 = data.find(b"\x00", null_index + 1)
if null_index_2 == -1:
null_index_2 = len(data)
seed_name = data[null_index + 1:null_index_2].decode()
@@ -479,8 +482,8 @@ def log_no_spam(msg: str) -> None:
async def main() -> None:
parser = get_base_parser()
- parser.add_argument('diff_file', default="", type=str, nargs="?",
- help='Path to a .apzl Archipelago Binary Patch file')
+ parser.add_argument("diff_file", default="", type=str, nargs="?",
+ help="Path to a .apzl Archipelago Binary Patch file")
# SNI parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
args = parser.parse_args()
print(args)
diff --git a/worlds/zillion/gen_data.py b/worlds/zillion/gen_data.py
index 13cbee9ced20..214073396153 100644
--- a/worlds/zillion/gen_data.py
+++ b/worlds/zillion/gen_data.py
@@ -1,6 +1,5 @@
from dataclasses import dataclass
import json
-from typing import Dict, Tuple
from zilliandomizer.game import Game as ZzGame
@@ -9,7 +8,7 @@
class GenData:
""" data passed from generation to patcher """
- multi_items: Dict[str, Tuple[str, str]]
+ multi_items: dict[str, tuple[str, str]]
""" zz_loc_name to (item_name, player_name) """
zz_game: ZzGame
game_id: bytes
diff --git a/worlds/zillion/id_maps.py b/worlds/zillion/id_maps.py
index 32d71fc79b30..25762f99cd6b 100644
--- a/worlds/zillion/id_maps.py
+++ b/worlds/zillion/id_maps.py
@@ -1,5 +1,6 @@
from collections import defaultdict
-from typing import Dict, Iterable, Mapping, Tuple, TypedDict
+from collections.abc import Iterable, Mapping
+from typing import TypedDict
from zilliandomizer.logic_components.items import (
Item as ZzItem,
@@ -40,13 +41,13 @@
_zz_empty = zz_item_name_to_zz_item["empty"]
-def make_id_to_others(start_char: Chars) -> Tuple[
- Dict[int, str], Dict[int, int], Dict[int, ZzItem]
+def make_id_to_others(start_char: Chars) -> tuple[
+ dict[int, str], dict[int, int], dict[int, ZzItem]
]:
""" returns id_to_name, id_to_zz_id, id_to_zz_item """
- id_to_name: Dict[int, str] = {}
- id_to_zz_id: Dict[int, int] = {}
- id_to_zz_item: Dict[int, ZzItem] = {}
+ id_to_name: dict[int, str] = {}
+ id_to_zz_id: dict[int, int] = {}
+ id_to_zz_item: dict[int, ZzItem] = {}
if start_char == "JJ":
name_to_zz_item = {
@@ -91,14 +92,14 @@ def make_room_name(row: int, col: int) -> str:
return f"{chr(ord('A') + row - 1)}-{col + 1}"
-loc_name_to_id: Dict[str, int] = {
+loc_name_to_id: dict[str, int] = {
name: id_ + base_id
for name, id_ in pretty_loc_name_to_id.items()
}
def zz_reg_name_to_reg_name(zz_reg_name: str) -> str:
- if zz_reg_name[0] == 'r' and zz_reg_name[3] == 'c':
+ if zz_reg_name[0] == "r" and zz_reg_name[3] == "c":
row, col = parse_reg_name(zz_reg_name)
end = zz_reg_name[5:]
return f"{make_room_name(row, col)} {end.upper()}"
@@ -113,17 +114,17 @@ class ClientRescue(TypedDict):
class ZillionSlotInfo(TypedDict):
start_char: Chars
- rescues: Dict[str, ClientRescue]
- loc_mem_to_id: Dict[int, int]
+ rescues: dict[str, ClientRescue]
+ loc_mem_to_id: dict[int, int]
""" memory location of canister to Archipelago location id number """
def get_slot_info(regions: Iterable[RegionData],
start_char: Chars,
loc_name_to_pretty: Mapping[str, str]) -> ZillionSlotInfo:
- items_placed_in_map_index: Dict[int, int] = defaultdict(int)
- rescue_locations: Dict[int, RescueInfo] = {}
- loc_memory_to_loc_id: Dict[int, int] = {}
+ items_placed_in_map_index: dict[int, int] = defaultdict(int)
+ rescue_locations: dict[int, RescueInfo] = {}
+ loc_memory_to_loc_id: dict[int, int] = {}
for region in regions:
for loc in region.locations:
assert loc.item, ("There should be an item placed in every location before "
@@ -142,7 +143,7 @@ def get_slot_info(regions: Iterable[RegionData],
loc_memory_to_loc_id[loc_memory] = pretty_loc_name_to_id[loc_name_to_pretty[loc.name]]
items_placed_in_map_index[map_index] += 1
- rescues: Dict[str, ClientRescue] = {}
+ rescues: dict[str, ClientRescue] = {}
for i in (0, 1):
if i in rescue_locations:
ri = rescue_locations[i]
diff --git a/worlds/zillion/logic.py b/worlds/zillion/logic.py
index a14910a200e5..f3d1814a9e9b 100644
--- a/worlds/zillion/logic.py
+++ b/worlds/zillion/logic.py
@@ -1,4 +1,5 @@
-from typing import Dict, FrozenSet, Mapping, Tuple, List, Counter as _Counter
+from collections import Counter
+from collections.abc import Mapping
from BaseClasses import CollectionState
@@ -35,7 +36,7 @@ def set_randomizer_locs(cs: CollectionState, p: int, zz_r: Randomizer) -> int:
return _hash
-def item_counts(cs: CollectionState, p: int) -> Tuple[Tuple[str, int], ...]:
+def item_counts(cs: CollectionState, p: int) -> tuple[tuple[str, int], ...]:
"""
the zilliandomizer items that player p has collected
@@ -44,11 +45,11 @@ def item_counts(cs: CollectionState, p: int) -> Tuple[Tuple[str, int], ...]:
return tuple((item_name, cs.count(item_name, p)) for item_name in item_name_to_id)
-_cache_miss: Tuple[None, FrozenSet[Location]] = (None, frozenset())
+_cache_miss: tuple[None, frozenset[Location]] = (None, frozenset())
class ZillionLogicCache:
- _cache: Dict[int, Tuple[_Counter[str], FrozenSet[Location]]]
+ _cache: dict[int, tuple[Counter[str], frozenset[Location]]]
""" `{ hash: (counter_from_prog_items, accessible_zz_locations) }` """
_player: int
_zz_r: Randomizer
@@ -60,7 +61,7 @@ def __init__(self, player: int, zz_r: Randomizer, id_to_zz_item: Mapping[int, It
self._zz_r = zz_r
self._id_to_zz_item = id_to_zz_item
- def cs_to_zz_locs(self, cs: CollectionState) -> FrozenSet[Location]:
+ def cs_to_zz_locs(self, cs: CollectionState) -> frozenset[Location]:
"""
given an Archipelago `CollectionState`,
returns frozenset of accessible zilliandomizer locations
@@ -76,7 +77,7 @@ def cs_to_zz_locs(self, cs: CollectionState) -> FrozenSet[Location]:
return locs
# print("cache miss")
- have_items: List[Item] = []
+ have_items: list[Item] = []
for name, count in counts:
have_items.extend([self._id_to_zz_item[item_name_to_id[name]]] * count)
# have_req is the result of converting AP CollectionState to zilliandomizer collection state
diff --git a/worlds/zillion/options.py b/worlds/zillion/options.py
index 5de0b65c82f0..22a698472265 100644
--- a/worlds/zillion/options.py
+++ b/worlds/zillion/options.py
@@ -1,7 +1,6 @@
from collections import Counter
from dataclasses import dataclass
-from typing import ClassVar, Dict, Literal, Tuple
-from typing_extensions import TypeGuard # remove when Python >= 3.10
+from typing import ClassVar, Literal, TypeGuard
from Options import Choice, DefaultOnToggle, NamedRange, OptionGroup, PerGameCommonOptions, Range, Removed, Toggle
@@ -108,7 +107,7 @@ class ZillionStartChar(Choice):
display_name = "start character"
default = "random"
- _name_capitalization: ClassVar[Dict[int, Chars]] = {
+ _name_capitalization: ClassVar[dict[int, Chars]] = {
option_jj: "JJ",
option_apple: "Apple",
option_champ: "Champ",
@@ -233,6 +232,7 @@ class ZillionSkill(Range):
range_start = 0
range_end = 5
default = 2
+ display_name = "skill"
class ZillionStartingCards(NamedRange):
@@ -263,7 +263,7 @@ class ZillionMapGen(Choice):
option_full = 2
default = 0
- def zz_value(self) -> Literal['none', 'rooms', 'full']:
+ def zz_value(self) -> Literal["none", "rooms", "full"]:
if self.value == ZillionMapGen.option_none:
return "none"
if self.value == ZillionMapGen.option_rooms:
@@ -305,7 +305,7 @@ class ZillionOptions(PerGameCommonOptions):
]
-def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
+def convert_item_counts(ic: Counter[str]) -> ZzItemCounts:
tr: ZzItemCounts = {
ID.card: ic["ID Card"],
ID.red: ic["Red ID Card"],
@@ -319,7 +319,7 @@ def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
return tr
-def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]":
+def validate(options: ZillionOptions) -> tuple[ZzOptions, Counter[str]]:
"""
adjusts options to make game completion possible
diff --git a/worlds/zillion/patch.py b/worlds/zillion/patch.py
index 6bc6d04dd663..0eee3315f4a1 100644
--- a/worlds/zillion/patch.py
+++ b/worlds/zillion/patch.py
@@ -1,5 +1,5 @@
import os
-from typing import Any, BinaryIO, Optional, cast
+from typing import BinaryIO
import zipfile
from typing_extensions import override
@@ -11,11 +11,11 @@
from .gen_data import GenData
-USHASH = 'd4bf9e7bcf9a48da53785d2ae7bc4270'
+US_HASH = "d4bf9e7bcf9a48da53785d2ae7bc4270"
class ZillionPatch(APAutoPatchInterface):
- hash = USHASH
+ hash = US_HASH
game = "Zillion"
patch_file_ending = ".apzl"
result_file_ending = ".sms"
@@ -23,8 +23,14 @@ class ZillionPatch(APAutoPatchInterface):
gen_data_str: str
""" JSON encoded """
- def __init__(self, *args: Any, gen_data_str: str = "", **kwargs: Any) -> None:
- super().__init__(*args, **kwargs)
+ def __init__(self,
+ path: str | None = None,
+ player: int | None = None,
+ player_name: str = "",
+ server: str = "",
+ *,
+ gen_data_str: str = "") -> None:
+ super().__init__(path=path, player=player, player_name=player_name, server=server)
self.gen_data_str = gen_data_str
@classmethod
@@ -44,15 +50,17 @@ def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
super().read_contents(opened_zipfile)
self.gen_data_str = opened_zipfile.read("gen_data.json").decode()
+ @override
def patch(self, target: str) -> None:
self.read()
write_rom_from_gen_data(self.gen_data_str, target)
-def get_base_rom_path(file_name: Optional[str] = None) -> str:
- options = Utils.get_options()
+def get_base_rom_path(file_name: str | None = None) -> str:
+ from . import ZillionSettings, ZillionWorld
+ settings: ZillionSettings = ZillionWorld.settings
if not file_name:
- file_name = cast(str, options["zillion_options"]["rom_file"])
+ file_name = settings.rom_file
if not os.path.exists(file_name):
file_name = Utils.user_path(file_name)
return file_name
diff --git a/worlds/zillion/region.py b/worlds/zillion/region.py
index cf5aa6588950..40565f008263 100644
--- a/worlds/zillion/region.py
+++ b/worlds/zillion/region.py
@@ -1,9 +1,11 @@
-from typing import Optional
-from BaseClasses import MultiWorld, Region, Location, Item, CollectionState
+from typing_extensions import override
+
from zilliandomizer.logic_components.regions import Region as ZzRegion
from zilliandomizer.logic_components.locations import Location as ZzLocation
from zilliandomizer.logic_components.items import RESCUE
+from BaseClasses import MultiWorld, Region, Location, Item, CollectionState
+
from .id_maps import loc_name_to_id
from .item import ZillionItem
@@ -28,12 +30,12 @@ def __init__(self,
zz_loc: ZzLocation,
player: int,
name: str,
- parent: Optional[Region] = None) -> None:
+ parent: Region | None = None) -> None:
loc_id = loc_name_to_id[name]
super().__init__(player, name, loc_id, parent)
self.zz_loc = zz_loc
- # override
+ @override
def can_fill(self, state: CollectionState, item: Item, check_access: bool = True) -> bool:
saved_gun_req = -1
if isinstance(item, ZillionItem) \
diff --git a/worlds/zillion/test/TestReproducibleRandom.py b/worlds/zillion/test/TestReproducibleRandom.py
index a92fae240709..352165449a8b 100644
--- a/worlds/zillion/test/TestReproducibleRandom.py
+++ b/worlds/zillion/test/TestReproducibleRandom.py
@@ -1,4 +1,3 @@
-from typing import cast
from . import ZillionTestBase
from .. import ZillionWorld
@@ -9,7 +8,8 @@ class SeedTest(ZillionTestBase):
def test_reproduce_seed(self) -> None:
self.world_setup(42)
- z_world = cast(ZillionWorld, self.multiworld.worlds[1])
+ z_world = self.multiworld.worlds[1]
+ assert isinstance(z_world, ZillionWorld)
r = z_world.zz_system.randomizer
assert r
randomized_requirements_first = tuple(
@@ -18,7 +18,8 @@ def test_reproduce_seed(self) -> None:
)
self.world_setup(42)
- z_world = cast(ZillionWorld, self.multiworld.worlds[1])
+ z_world = self.multiworld.worlds[1]
+ assert isinstance(z_world, ZillionWorld)
r = z_world.zz_system.randomizer
assert r
randomized_requirements_second = tuple(
diff --git a/worlds/zillion/test/__init__.py b/worlds/zillion/test/__init__.py
index fe62bae34c9e..a669442364fe 100644
--- a/worlds/zillion/test/__init__.py
+++ b/worlds/zillion/test/__init__.py
@@ -1,4 +1,3 @@
-from typing import cast
from test.bases import WorldTestBase
from .. import ZillionWorld
@@ -13,8 +12,9 @@ def ensure_gun_3_requirement(self) -> None:
This makes sure that gun 3 is required by making all the canisters
in O-7 (including key word canisters) require gun 3.
"""
- zz_world = cast(ZillionWorld, self.multiworld.worlds[1])
- assert zz_world.zz_system.randomizer
- for zz_loc_name, zz_loc in zz_world.zz_system.randomizer.locations.items():
+ z_world = self.multiworld.worlds[1]
+ assert isinstance(z_world, ZillionWorld)
+ assert z_world.zz_system.randomizer
+ for zz_loc_name, zz_loc in z_world.zz_system.randomizer.locations.items():
if zz_loc_name.startswith("r15c6"):
zz_loc.req.gun = 3