diff --git a/BaseClasses.py b/BaseClasses.py index 83f0204a..c8c6a0cb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -5,13 +5,13 @@ from enum import Enum, unique import logging import json from collections import OrderedDict, Counter, deque +from typing import Union, Optional +import secrets +import random from EntranceShuffle import door_addresses, indirect_connections from Utils import int16_as_bytes -from typing import Union, Optional - -import secrets -import random +from Items import item_name_groups class World(object): @@ -429,7 +429,7 @@ class CollectionState(object): self.world = parent self.reachable_regions = {player: set() for player in range(1, parent.players + 1)} self.blocked_connections = {player: set() for player in range(1, parent.players + 1)} - self.events = [] + self.events = set() self.path = {} self.locations_checked = set() self.stale = {player: True for player in range(1, parent.players + 1)} @@ -492,23 +492,19 @@ class CollectionState(object): return spot.can_reach(self) def sweep_for_events(self, key_only: bool = False, locations=None): - # this may need improvement if locations is None: locations = self.world.get_filled_locations() new_locations = True - checked_locations = 0 while new_locations: - reachable_events = [location for location in locations if location.event and + reachable_events = {location for location in locations if location.event and (not key_only or (not self.world.keyshuffle[ location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[ location.item.player] and location.item.bigkey)) - and location.can_reach(self)] - for event in reachable_events: - if event not in self.events: - self.events.append(event) - self.collect(event.item, True, event) - new_locations = len(reachable_events) > checked_locations - checked_locations = len(reachable_events) + and location.can_reach(self)} + new_locations = reachable_events - self.events + for event in new_locations: + self.events.add(event) + self.collect(event.item, True, event) def has(self, item, player: int, count: int = 1): return self.prog_items[item, player] >= count @@ -532,11 +528,10 @@ class CollectionState(object): def has_crystals(self, count: int, player: int) -> bool: found: int = 0 - for itemname, itemplayer in self.prog_items: - if itemplayer == player and itemname.startswith('Crystal '): - found += 1 - if found >= count: - return True + for crystalnumber in range(1, 8): + found += self.prog_items[f"Crystal {crystalnumber}", player] + if found >= count: + return True return False def can_lift_rocks(self, player: int): @@ -546,17 +541,18 @@ class CollectionState(object): return self.has_bottles(1, player) def bottle_count(self, player: int) -> int: - return len( - tuple(item for (item, itemplayer) in self.prog_items if itemplayer == player and item.startswith('Bottle'))) + found: int = 0 + for bottlename in item_name_groups["Bottles"]: + found += self.prog_items[bottlename, player] + return found - def has_bottles(self, bottles: int, player: int): + def has_bottles(self, bottles: int, player: int) -> bool: """Version of bottle_count that allows fast abort""" found: int = 0 - for itemname, itemplayer in self.prog_items: - if itemplayer == player and itemname.startswith('Bottle'): - found += 1 - if found >= bottles: - return True + for bottlename in item_name_groups["Bottles"]: + found += self.prog_items[bottlename, player] + if found >= bottles: + return True return False def has_hearts(self, player: int, count: int) -> int: @@ -995,6 +991,9 @@ class Location(object): world = self.parent_region.world if self.parent_region and self.parent_region.world else None return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' + def __hash__(self): + return hash((self.name, self.player)) + class Item(object): diff --git a/Fill.py b/Fill.py index 279a9c37..77fa1818 100644 --- a/Fill.py +++ b/Fill.py @@ -209,7 +209,7 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si if location.item and not location.event: placements.append(location) raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. ' - f'\nAlready placed {len(placements)}: {", ".join(placements)}') + f'Already placed {len(placements)}: {", ".join(placements)}') world.push_item(spot_to_fill, item_to_place, False) locations.remove(spot_to_fill) diff --git a/Items.py b/Items.py index 440d9c90..e923759b 100644 --- a/Items.py +++ b/Items.py @@ -1,9 +1,9 @@ import logging -from BaseClasses import Item def ItemFactory(items, player): + from BaseClasses import Item ret = [] singleton = False if isinstance(items, str): diff --git a/Rom.py b/Rom.py index f06a92db..1bde7c71 100644 --- a/Rom.py +++ b/Rom.py @@ -348,6 +348,8 @@ def _populate_sprite_table(): if sprite.valid: _sprite_table[sprite.name.lower()] = sprite _sprite_table[os.path.basename(file).split(".")[0].lower()] = sprite # alias for filename base + else: + logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.") with concurrent.futures.ThreadPoolExecutor() as pool: for dir in [local_path('data/sprites/alttpr'), local_path('data/sprites/custom')]: diff --git a/easy.yaml b/easy.yaml index a9cfd0dc..8bf539a6 100644 --- a/easy.yaml +++ b/easy.yaml @@ -17,17 +17,18 @@ # To test if your yaml is valid or not, you can use this website: # http://www.yamllint.com/ -description: Your Description Here # Used to describe your yaml. Useful if you have multiple files -name: YourName # Your name in-game. Spaces and underscores will be replaced with dashes +description: easy template # Used to describe your yaml. Useful if you have multiple files +name: YourName # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit glitches_required: # Determine the logic required to complete the seed none: 1 # No glitches required minor_glitches: 0 # Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic overworld_glitches: 0 # Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches (fake flipper, super bunny shenanigans, water walk and etc.) no_logic: 0 # Items are places completely at random and with no regard for logic. Your fire rod could be on Trinexx meta_ignore: # Nullify options specified in the meta.yaml file. Adding an option here guarantees it will not occur in your seed, even if the .yaml file specifies it - world_state: + mode: - inverted # Never play inverted seeds - - retro # Never play retro seeds + retro: + - on # Never play retro seeds weapons: - swordless # Never play a swordless seed map_shuffle: # Shuffle dungeon maps into the world and other dungeons, including other players' worlds