diff --git a/Main.py b/Main.py index dcef4392..19025500 100644 --- a/Main.py +++ b/Main.py @@ -93,6 +93,8 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No f"Location IDs: {min(cls.location_id_to_name):{numlength}} - " f"{max(cls.location_id_to_name):{numlength}}") + AutoWorld.call_stage(world, "assert_generate") + AutoWorld.call_all(world, "generate_early") logger.info('') diff --git a/docs/api.md b/docs/api.md index f81c29b3..239e65bc 100644 --- a/docs/api.md +++ b/docs/api.md @@ -426,6 +426,9 @@ In addition, the following methods can be implemented and attributes can be set * `required_client_version: Tuple(int, int, int)` Client version as tuple of 3 ints to make sure the client is compatible to this world (e.g. implements all required features) when connecting. +* `assert_generate(cls, world)` is a class method called at the start of + generation to check the existence of prerequisite files, usually a ROM for + games which require one. #### generate_early diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index ee7f618d..7f974741 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -171,6 +171,12 @@ class World(metaclass=AutoWorldRegister): # can also be implemented as a classmethod and called "stage_", # in that case the MultiWorld object is passed as an argument and it gets called once for the entire multiworld. # An example of this can be found in alttp as stage_pre_fill + @classmethod + def assert_generate(cls) -> None: + """Checks that a game is capable of generating, usually checks for some base file like a ROM. + Not run for unittests since they don't produce output""" + pass + def generate_early(self) -> None: pass diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 1a3453f9..9e028b2d 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -56,6 +56,12 @@ class ALTTPWorld(World): self.has_progressive_bows = False super(ALTTPWorld, self).__init__(*args, **kwargs) + @classmethod + def stage_assert_generate(cls, world): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + def generate_early(self): player = self.player world = self.world diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index 18b0f939..cc68488c 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -89,6 +89,10 @@ class OOTWorld(World): self.hint_data_available = threading.Event() super(OOTWorld, self).__init__(world, player) + @classmethod + def stage_assert_generate(cls, world: MultiWorld): + rom = Rom(file=get_options()['oot_options']['rom_file']) + def generate_early(self): # Player name MUST be at most 16 bytes ascii-encoded, otherwise won't write to ROM correctly if len(bytes(self.world.get_player_name(self.player), 'ascii')) > 16: diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 305ac87b..50115093 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -83,6 +83,12 @@ class SMWorld(World): self.rom_name_available_event = threading.Event() super().__init__(world, player) + @classmethod + def stage_assert_generate(cls, world): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + def generate_early(self): Logic.factory('vanilla') diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index 6cb43878..8d3cd06b 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -60,6 +60,10 @@ class SMZ3World(World): self.unreachable = [] super().__init__(world, player) + @classmethod + def stage_assert_generate(cls, world): + base_combined_rom = get_base_rom_bytes() + def generate_early(self): config = Config({}) config.GameMode = GameMode.Multiworld diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 244c6f07..fd94ebf2 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -175,6 +175,12 @@ class SoEWorld(World): res.trap = item.type == pyevermizer.CHECK_TRAP return res + @classmethod + def stage_assert_generate(cls, world): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + def create_regions(self): # TODO: generate *some* regions from locations' requirements? r = Region('Menu', RegionType.Generic, 'Menu', self.player, self.world)