Skip to content

Commit

Permalink
Merge branch 'main' into sm64-100coins
Browse files Browse the repository at this point in the history
  • Loading branch information
josephwhite authored Nov 29, 2024
2 parents b5ddca4 + 6f2464d commit 49b9c82
Show file tree
Hide file tree
Showing 12 changed files with 3,582 additions and 1,523 deletions.
39 changes: 28 additions & 11 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -998,24 +1015,24 @@ 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']:
m = placement['count']['min']
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(
Expand Down
3 changes: 3 additions & 0 deletions Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions worlds/pokemon_emerald/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
83 changes: 47 additions & 36 deletions worlds/pokemon_emerald/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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]):
Expand Down Expand Up @@ -325,39 +334,39 @@ 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
# their locations for use in `pre_fill` later.
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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions worlds/pokemon_emerald/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,29 @@ 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
parent_region: str
default_item: int
address: Union[int, List[int]]
flag: int
category: LocationCategory
tags: FrozenSet[str]


Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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
)

Expand Down
Loading

0 comments on commit 49b9c82

Please sign in to comment.