diff --git a/worlds/kdl3/Aesthetics.py b/worlds/kdl3/Aesthetics.py index 0c7bfa8e7b..bcbe903856 100644 --- a/worlds/kdl3/Aesthetics.py +++ b/worlds/kdl3/Aesthetics.py @@ -1,6 +1,6 @@ import struct from .Options import KirbyFlavorPreset, GooeyFlavorPreset -from typing import TYPE_CHECKING, Optional, Dict, List +from typing import TYPE_CHECKING, Optional, Dict, List, Tuple if TYPE_CHECKING: from . import KDL3World @@ -431,7 +431,7 @@ def get_palette_bytes(palette: Dict[str, str], target: List[str], offset: int, f if hexcol.startswith("#"): hexcol = hexcol.replace("#", "") colint = int(hexcol, 16) - col = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF) + col: Tuple[int, ...] = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF) col = tuple(int(int(factor*x) + offset) for x in col) byte_data = rgb888_to_bgr555(col[0], col[1], col[2]) output_data.extend(bytearray(byte_data)) diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py index c80c3875b2..601ccab91e 100644 --- a/worlds/kdl3/Client.py +++ b/worlds/kdl3/Client.py @@ -84,7 +84,7 @@ deathlink_messages = defaultdict(lambda: " was defeated.", { def cmd_gift(self: "SNIClientCommandProcessor"): """Toggles gifting for the current game.""" if not getattr(self.ctx, "gifting", None): - self.ctx.gifting = True + setattr(self.ctx, "gifting", True) else: self.ctx.gifting = not self.ctx.gifting self.output(f"Gifting set to {self.ctx.gifting}") diff --git a/worlds/kdl3/Gifting.py b/worlds/kdl3/Gifting.py index f820d38e8d..fff8881d35 100644 --- a/worlds/kdl3/Gifting.py +++ b/worlds/kdl3/Gifting.py @@ -36,10 +36,10 @@ async def pop_object(ctx: "CommonContext", key: str, value: str) -> None: async def initialize_giftboxes(ctx: "CommonContext", giftbox_key: str, motherbox_key: str, is_open: bool) -> None: ctx.set_notify(motherbox_key, giftbox_key) await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}": - { - "IsOpen": is_open, - **kdl3_gifting_options - }}) + { + "IsOpen": is_open, + **kdl3_gifting_options + }}) setattr(ctx, "gifting", is_open) diff --git a/worlds/kdl3/Items.py b/worlds/kdl3/Items.py index 66c7f8fee3..1c7f9dc12c 100644 --- a/worlds/kdl3/Items.py +++ b/worlds/kdl3/Items.py @@ -102,4 +102,4 @@ item_names = { "Animal Friend": set(animal_friend_table), } -lookup_name_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code} +lookup_item_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code} diff --git a/worlds/kdl3/Rom.py b/worlds/kdl3/Rom.py index 05077c6851..13983f5995 100644 --- a/worlds/kdl3/Rom.py +++ b/worlds/kdl3/Rom.py @@ -2,7 +2,7 @@ import typing from pkgutil import get_data import Utils -from typing import Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING, Tuple, Dict, List import hashlib import os import struct @@ -261,53 +261,56 @@ class RomData: self.file = bytearray(file) self.name = name - def read_byte(self, offset: int): + def read_byte(self, offset: int) -> int: return self.file[offset] - def read_bytes(self, offset: int, length: int): + def read_bytes(self, offset: int, length: int) -> bytearray: return self.file[offset:offset + length] - def write_byte(self, offset: int, value: int): + def write_byte(self, offset: int, value: int) -> None: self.file[offset] = value - def write_bytes(self, offset: int, values: typing.Sequence) -> None: + def write_bytes(self, offset: int, values: typing.Sequence[int]) -> None: self.file[offset:offset + len(values)] = values def get_bytes(self) -> bytes: return bytes(self.file) -def handle_level_sprites(stages, sprites, palettes): +def handle_level_sprites(stages: List[Tuple[int, ...]], sprites: List[bytearray], palettes: List[List[bytearray]]) \ + -> Tuple[List[bytearray], List[bytearray]]: palette_by_level = list() for palette in palettes: palette_by_level.extend(palette[10:16]) + out_palettes = list() for i in range(5): for j in range(6): palettes[i][10 + j] = palette_by_level[stages[i][j] - 1] - palettes[i] = [x for palette in palettes[i] for x in palette] + out_palettes.append(bytearray([x for palette in palettes[i] for x in palette])) tiles_by_level = list() for spritesheet in sprites: decompressed = hal_decompress(spritesheet) tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)] tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles]) + out_sprites = list() for world in range(5): levels = [stages[world][x] - 1 for x in range(6)] - world_tiles: typing.List[typing.Optional[bytes]] = [None for _ in range(72)] + world_tiles: typing.List[bytes] = [bytes() for _ in range(72)] for i in range(6): for x in range(12): world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x] - sprites[world] = list() + out_sprites.append(bytearray()) for tile in world_tiles: - sprites[world].extend(tile) + out_sprites[world].extend(tile) # insert our fake compression - sprites[world][0:0] = [0xe3, 0xff] - sprites[world][1026:1026] = [0xe3, 0xff] - sprites[world][2052:2052] = [0xe0, 0xff] - sprites[world].append(0xff) - return sprites, palettes + out_sprites[world][0:0] = [0xe3, 0xff] + out_sprites[world][1026:1026] = [0xe3, 0xff] + out_sprites[world][2052:2052] = [0xe0, 0xff] + out_sprites[world].append(0xff) + return out_sprites, out_palettes -def write_heart_star_sprites(rom: RomData): +def write_heart_star_sprites(rom: RomData) -> None: compressed = rom.read_bytes(heart_star_address, heart_star_size) decompressed = hal_decompress(compressed) patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4")) @@ -319,7 +322,7 @@ def write_heart_star_sprites(rom: RomData): rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39]) -def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool): +def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool) -> None: compressed = rom.read_bytes(consumable_address, consumable_size) decompressed = hal_decompress(compressed) patched = bytearray(decompressed) @@ -339,7 +342,7 @@ class KDL3PatchExtensions(APPatchExtension): game = "Kirby's Dream Land 3" @staticmethod - def apply_post_patch(_: APProcedurePatch, rom: bytes): + def apply_post_patch(_: APProcedurePatch, rom: bytes) -> bytes: rom_data = RomData(rom) target_language = rom_data.read_byte(0x3C020) rom_data.write_byte(0x7FD9, target_language) @@ -347,9 +350,9 @@ class KDL3PatchExtensions(APPatchExtension): if rom_data.read_bytes(0x3D014, 1)[0] > 0: stages = [struct.unpack("HHHHHHH", rom_data.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)] palettes = [rom_data.read_bytes(full_pal, 512) for full_pal in stage_palettes] - palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes] + read_palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes] sprites = [rom_data.read_bytes(offset, level_sprites[offset]) for offset in level_sprites] - sprites, palettes = handle_level_sprites(stages, sprites, palettes) + sprites, palettes = handle_level_sprites(stages, sprites, read_palettes) for addr, palette in zip(stage_palettes, palettes): rom_data.write_bytes(addr, palette) for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites): @@ -377,7 +380,7 @@ class KDL3ProcedurePatch(APProcedurePatch, APTokenMixin): return get_base_rom_bytes() -def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch): +def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch) -> None: patch.write_file("kdl3_basepatch.bsdiff4", get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4"))) @@ -415,8 +418,8 @@ def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch): music_map[8] = world.random.choice(music_choices) for room in rooms: room.music = music_map[room.music] - for room in room_music: - patch.write_token(APTokenTypes.WRITE, room + 2, bytes([music_map[room_music[room]]])) + for room_ptr in room_music: + patch.write_token(APTokenTypes.WRITE, room_ptr + 2, bytes([music_map[room_music[room_ptr]]])) for i, old_music in zip(range(5), [25, 26, 28, 27, 30]): # level themes patch.write_token(APTokenTypes.WRITE, 0x133F2 + i, bytes([music_map[old_music]])) @@ -563,13 +566,15 @@ def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch): for addr in kirby_target_palettes: target = kirby_target_palettes[addr] palette = get_kirby_palette(world) - patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2])) + if palette is not None: + patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2])) if world.options.gooey_flavor_preset.value != 0: for addr in gooey_target_palettes: target = gooey_target_palettes[addr] palette = get_gooey_palette(world) - patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2])) + if palette is not None: + patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2])) patch.write_file("token_patch.bin", patch.get_token_binary()) diff --git a/worlds/kdl3/Room.py b/worlds/kdl3/Room.py index 2f4631a0b0..122e4c53dd 100644 --- a/worlds/kdl3/Room.py +++ b/worlds/kdl3/Room.py @@ -56,10 +56,12 @@ class KDL3Room(Region): patch.write_token(APTokenTypes.WRITE, self.pointer + address + 7, animal_map[current_animal].to_bytes(1, "little")) if local_items: - for location in self.locations: - if not location.address or not location.item or location.item.player != self.player: + for location in self.get_locations(): + if location.item is None or location.item.player != self.player: continue item = location.item.code + if item is None: + continue item_idx = item & 0x00000F location_idx = location.address & 0xFFFF if location_idx & 0xF00 in (0x300, 0x400, 0x500, 0x600): @@ -84,7 +86,7 @@ class KDL3Room(Region): load_len = len(self.entity_load) for consumable in self.consumables: location = next(x for x in self.locations if x.name == consumable["name"]) - assert location.item + assert location.item is not None is_progression = location.item.classification & ItemClassification.progression if load_len == 8: # edge case, there is exactly 1 room with 8 entities and only 1 consumable among them @@ -126,5 +128,6 @@ class KDL3Room(Region): vtype = 3 else: vtype = 2 + assert isinstance(consumable["pointer"], int) patch.write_token(APTokenTypes.WRITE, self.pointer + consumable["pointer"] + 7, vtype.to_bytes(1, "little")) diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py index 6a85ef84f0..eed3b43433 100644 --- a/worlds/kdl3/Rules.py +++ b/worlds/kdl3/Rules.py @@ -10,7 +10,7 @@ if typing.TYPE_CHECKING: def can_reach_boss(state: "CollectionState", player: int, level: int, open_world: int, - ow_boss_req: int, player_levels: typing.Dict[int, typing.Dict[int, int]]): + ow_boss_req: int, player_levels: typing.Dict[int, typing.List[int]]) -> bool: if open_world: return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req) else: @@ -86,7 +86,7 @@ ability_map: typing.Dict[str, typing.Callable[["CollectionState", int], bool]] = } -def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): +def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool: # check animal requirements if not (can_reach_coo(state, player) and can_reach_kine(state, player)): return False @@ -103,7 +103,7 @@ def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typi return can_reach_parasol(state, player) and can_reach_stone(state, player) -def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): +def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool: can_reach = True for enemy in {"Sparky", "Blocky", "Jumper Shoot", "Yuki", "Sir Kibble", "Haboki", "Boboo", "Captain Stitch"}: can_reach = can_reach & ability_map[copy_abilities[enemy]](state, player) @@ -310,14 +310,14 @@ def set_rules(world: "KDL3World") -> None: LocationName.iceberg_dedede], range(1, 6)): set_rule(world.multiworld.get_location(boss_flag, world.player), - lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) - and can_reach_boss(state, world.player, i, + lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1]) + and can_reach_boss(state, world.player, x, world.options.open_world.value, world.options.ow_boss_requirement.value, world.player_levels))) set_rule(world.multiworld.get_location(purification, world.player), - lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) - and can_reach_boss(state, world.player, i, + lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1]) + and can_reach_boss(state, world.player, x, world.options.open_world.value, world.options.ow_boss_requirement.value, world.player_levels))) @@ -327,12 +327,12 @@ def set_rules(world: "KDL3World") -> None: for level in range(2, 6): set_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), - lambda state, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player)) + lambda state, x=level: state.has(f"Level {x - 1} Boss Defeated", world.player)) if world.options.strict_bosses: for level in range(2, 6): add_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), - lambda state, i=level: state.has(f"Level {i - 1} Boss Purified", world.player)) + lambda state, x=level: state.has(f"Level {x - 1} Boss Purified", world.player)) if world.options.goal_speed == GoalSpeed.option_normal: add_rule(world.multiworld.get_entrance("To Level 6", world.player), diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index 77fc3dee95..32fc1badd7 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -1,12 +1,13 @@ import logging import typing -from BaseClasses import Tutorial, ItemClassification, MultiWorld, CollectionState +from BaseClasses import Tutorial, ItemClassification, MultiWorld, CollectionState, Item from Fill import fill_restrictive from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld from .Items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \ - trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights, animal_friend_spawn_table + trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights, animal_friend_spawn_table,\ + lookup_item_to_id from .Locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations from .Names.AnimalFriendSpawns import animal_friend_spawns from .Names.EnemyAbilities import vanilla_enemies, enemy_mapping, enemy_restrictive @@ -19,7 +20,7 @@ from .Rules import set_rules from .Rom import KDL3ProcedurePatch, get_base_rom_path, patch_rom, KDL3JHASH, KDL3UHASH from .Client import KDL3SNIClient -from typing import Dict, TextIO, Optional, List +from typing import Dict, TextIO, Optional, List, Any import os import math import threading @@ -63,27 +64,27 @@ class KDL3World(World): game = "Kirby's Dream Land 3" options_dataclass: typing.ClassVar[typing.Type[PerGameCommonOptions]] = KDL3Options options: KDL3Options - item_name_to_id = {item: item_table[item].code for item in item_table} + item_name_to_id = lookup_item_to_id location_name_to_id = {location_table[location]: location for location in location_table} item_name_groups = item_names web = KDL3WebWorld() settings: typing.ClassVar[KDL3Settings] def __init__(self, multiworld: MultiWorld, player: int): - self.rom_name = None + self.rom_name: bytearray = bytearray() self.rom_name_available_event = threading.Event() super().__init__(multiworld, player) self.copy_abilities: Dict[str, str] = vanilla_enemies.copy() self.required_heart_stars: int = 0 # we fill this during create_items - self.boss_requirements: Dict[int, int] = dict() + self.boss_requirements: List[int] = [] self.player_levels = default_levels.copy() self.stage_shuffle_enabled = False - self.boss_butch_bosses: List[Optional[bool]] = list() - self.rooms: Optional[List[KDL3Room]] = None + self.boss_butch_bosses: List[Optional[bool]] = [] + self.rooms: List[KDL3Room] = [] create_regions = create_levels - def create_item(self, name: str, force_non_progression=False) -> KDL3Item: + def create_item(self, name: str, force_non_progression: bool = False) -> KDL3Item: item = item_table[name] classification = ItemClassification.filler if item.progression and not force_non_progression: @@ -93,7 +94,7 @@ class KDL3World(World): classification = ItemClassification.trap return KDL3Item(name, classification, item.code, self.player) - def get_filler_item_name(self, include_stars=True) -> str: + def get_filler_item_name(self, include_stars: bool = True) -> str: if include_stars: return self.random.choices(list(total_filler_weights.keys()), weights=list(total_filler_weights.values()))[0] @@ -107,7 +108,7 @@ class KDL3World(World): self.options.ability_trap_weight.value])[0] def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: typing.List[str], - level: int, stage: int): + level: int, stage: int) -> Optional[str]: valid_rooms = [room for room in self.rooms if (room.level < level) or (room.level == level and room.stage < stage)] # leave out the stage in question to avoid edge valid_enemies = set() @@ -118,7 +119,7 @@ class KDL3World(World): return None # a valid enemy got placed by a more restrictive placement return self.random.choice(sorted([enemy for enemy in valid_enemies if enemy not in placed_enemies])) - def get_pre_fill_items(self) -> List[KDL3Item]: + def get_pre_fill_items(self) -> List[Item]: return [self.create_item(item) for item in [*copy_ability_access_table.keys(), *animal_friend_spawn_table.keys()]] @@ -214,7 +215,7 @@ class KDL3World(World): animal_pool.remove("Kine Spawn") # should always have at least one in the pool spawn.place_locked_item(self.create_item("Kine Spawn")) locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns] - items = [self.create_item(animal) for animal in animal_pool] + items: List[Item] = [self.create_item(animal) for animal in animal_pool] allstate = CollectionState(self.multiworld) for item in [*copy_ability_table, *animal_friend_table, *["Heart Star" for _ in range(50)]]: self.collect(allstate, self.create_item(item)) @@ -290,7 +291,7 @@ class KDL3World(World): def generate_basic(self) -> None: self.stage_shuffle_enabled = self.options.stage_shuffle > 0 - goal = self.options.goal + goal = self.options.goal.value goal_location = self.multiworld.get_location(LocationName.goals[goal], self.player) goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, self.player)) for level in range(1, 6): @@ -310,7 +311,7 @@ class KDL3World(World): else: self.boss_butch_bosses = [False for _ in range(6)] - def generate_output(self, output_directory: str): + def generate_output(self, output_directory: str) -> None: try: patch = KDL3ProcedurePatch() patch_rom(self, patch) @@ -324,9 +325,10 @@ class KDL3World(World): finally: self.rom_name_available_event.set() # make sure threading continues and errors are collected - def modify_multidata(self, multidata: dict): + def modify_multidata(self, multidata: Dict[str, Any]) -> None: # wait for self.rom_name to be available. self.rom_name_available_event.wait() + assert isinstance(self.rom_name, bytes) rom_name = getattr(self, "rom_name", None) # we skip in case of error, so that the original error in the output thread is the one that gets raised if rom_name: @@ -341,21 +343,22 @@ class KDL3World(World): spoiler_handle.write(f"{level} {i}: {location_table[stage].replace(' - Complete', '')}\n") if self.options.animal_randomization: spoiler_handle.write(f"\nAnimal Friends ({self.multiworld.get_player_name(self.player)}):\n") - for level in self.player_levels: + for lvl in self.player_levels: for stage in range(6): - rooms = [room for room in self.rooms if room.level == level and room.stage == stage] + rooms = [room for room in self.rooms if room.level == lvl and room.stage == stage] animals = [] for room in rooms: animals.extend([location.item.name.replace(" Spawn", "") - for location in room.locations if "Animal" in location.name]) - spoiler_handle.write(f"{location_table[self.player_levels[level][stage]].replace(' - Complete','')}" + for location in room.locations if "Animal" in location.name + and location.item is not None]) + spoiler_handle.write(f"{location_table[self.player_levels[lvl][stage]].replace(' - Complete','')}" f": {', '.join(animals)}\n") if self.options.copy_ability_randomization: spoiler_handle.write(f"\nCopy Abilities ({self.multiworld.get_player_name(self.player)}):\n") for enemy in self.copy_abilities: spoiler_handle.write(f"{enemy}: {self.copy_abilities[enemy].replace('No Ability', 'None').replace(' Ability', '')}\n") - def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): + def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None: if self.stage_shuffle_enabled: regions = {LocationName.level_names[level]: level for level in LocationName.level_names} level_hint_data = {} @@ -365,6 +368,6 @@ class KDL3World(World): self.player).name.replace(" - Complete", "") stage_regions = [room for room in self.rooms if stage_name in room.name] for region in stage_regions: - for location in [location for location in region.locations if location.address]: + for location in [location for location in list(region.get_locations()) if location.address]: level_hint_data[location.address] = f"{regions[level]} {stage + 1 if stage < 6 else 'Boss'}" hint_data[self.player] = level_hint_data diff --git a/worlds/kdl3/test/__init__.py b/worlds/kdl3/test/__init__.py index 11a17e63b7..c0986ca6f4 100644 --- a/worlds/kdl3/test/__init__.py +++ b/worlds/kdl3/test/__init__.py @@ -2,10 +2,12 @@ import typing from argparse import Namespace from BaseClasses import MultiWorld, PlandoOptions, CollectionState -from test.TestBase import WorldTestBase +from test.bases import WorldTestBase from test.general import gen_steps from worlds import AutoWorld from worlds.AutoWorld import call_all +# mypy: ignore-errors +# This is a copy of core code, and I'm not smart enough to solve the errors in here class KDL3TestBase(WorldTestBase): diff --git a/worlds/kdl3/test/test_goal.py b/worlds/kdl3/test/test_goal.py index ce53642a97..210f4632ad 100644 --- a/worlds/kdl3/test/test_goal.py +++ b/worlds/kdl3/test/test_goal.py @@ -10,7 +10,7 @@ class TestFastGoal(KDL3TestBase): "filler_percentage": 0, } - def test_goal(self): + def test_goal(self) -> None: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) @@ -35,7 +35,7 @@ class TestNormalGoal(KDL3TestBase): "filler_percentage": 0, } - def test_goal(self): + def test_goal(self) -> None: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) @@ -51,14 +51,14 @@ class TestNormalGoal(KDL3TestBase): self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) self.assertBeatable(True) - def test_kine(self): + def test_kine(self) -> None: self.collect_by_name(["Cutter", "Burning", "Heart Star"]) self.assertBeatable(False) - def test_cutter(self): + def test_cutter(self) -> None: self.collect_by_name(["Kine", "Burning", "Heart Star"]) self.assertBeatable(False) - def test_burning(self): + def test_burning(self) -> None: self.collect_by_name(["Cutter", "Kine", "Heart Star"]) self.assertBeatable(False) diff --git a/worlds/kdl3/test/test_locations.py b/worlds/kdl3/test/test_locations.py index 433b4534d1..6254b83963 100644 --- a/worlds/kdl3/test/test_locations.py +++ b/worlds/kdl3/test/test_locations.py @@ -12,7 +12,7 @@ class TestLocations(KDL3TestBase): # these ensure we can always reach all stages physically } - def test_simple_heart_stars(self): + def test_simple_heart_stars(self) -> None: self.run_location_test(LocationName.grass_land_muchi, ["ChuChu"]) self.run_location_test(LocationName.grass_land_chao, ["Stone"]) self.run_location_test(LocationName.grass_land_mine, ["Kine"]) @@ -23,9 +23,9 @@ class TestLocations(KDL3TestBase): self.run_location_test(LocationName.sand_canyon_auntie, ["Clean"]) self.run_location_test(LocationName.sand_canyon_nyupun, ["ChuChu", "Cutter"]) self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"]) - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]), - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]), - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]), + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]) + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]) + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]) self.run_location_test(LocationName.cloudy_park_hibanamodoki, ["Coo", "Clean"]) self.run_location_test(LocationName.cloudy_park_piyokeko, ["Needle"]) self.run_location_test(LocationName.cloudy_park_mikarin, ["Coo"]) @@ -36,7 +36,7 @@ class TestLocations(KDL3TestBase): self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"]) - def run_location_test(self, location: str, itempool: typing.List[str]): + def run_location_test(self, location: str, itempool: typing.List[str]) -> None: items = itempool.copy() while len(itempool) > 0: self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed)) @@ -59,7 +59,7 @@ class TestShiro(KDL3TestBase): "plando_options": "connections" } - def test_shiro(self): + def test_shiro(self) -> None: self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) self.collect_by_name("Nago") self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) diff --git a/worlds/kdl3/test/test_shuffles.py b/worlds/kdl3/test/test_shuffles.py index 2074a3f7ea..9496735f28 100644 --- a/worlds/kdl3/test/test_shuffles.py +++ b/worlds/kdl3/test/test_shuffles.py @@ -13,7 +13,7 @@ class TestCopyAbilityShuffle(KDL3TestBase): "copy_ability_randomization": "enabled", } - def test_goal(self): + def test_goal(self) -> None: try: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") @@ -33,28 +33,28 @@ class TestCopyAbilityShuffle(KDL3TestBase): # if assert beatable fails, this will catch and print the seed raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_kine(self): + def test_kine(self) -> None: try: self.collect_by_name(["Cutter", "Burning", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter(self): + def test_cutter(self) -> None: try: self.collect_by_name(["Kine", "Burning", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_burning(self): + def test_burning(self) -> None: try: self.collect_by_name(["Cutter", "Kine", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter_and_burning_reachable(self): + def test_cutter_and_burning_reachable(self) -> None: rooms = self.multiworld.worlds[1].rooms copy_abilities = self.multiworld.worlds[1].copy_abilities sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) @@ -76,7 +76,7 @@ class TestCopyAbilityShuffle(KDL3TestBase): else: self.fail("Could not reach Burning Ability before Iceberg 4!") - def test_valid_abilities_for_ROB(self): + def test_valid_abilities_for_ROB(self) -> None: # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach # first we need to identify our bukiset requirements @@ -87,13 +87,13 @@ class TestCopyAbilityShuffle(KDL3TestBase): ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), ] copy_abilities = self.multiworld.worlds[1].copy_abilities - required_abilities: List[Tuple[str]] = [] + required_abilities: List[List[str]] = [] for abilities, bukisets in groups: potential_abilities: List[str] = list() for bukiset in bukisets: if copy_abilities[bukiset] in abilities: potential_abilities.append(copy_abilities[bukiset]) - required_abilities.append(tuple(potential_abilities)) + required_abilities.append(potential_abilities) collected_abilities = list() for group in required_abilities: self.assertFalse(len(group) == 0, str(self.multiworld.seed)) @@ -122,7 +122,7 @@ class TestAnimalShuffle(KDL3TestBase): "animal_randomization": "full", } - def test_goal(self): + def test_goal(self) -> None: try: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") @@ -142,33 +142,36 @@ class TestAnimalShuffle(KDL3TestBase): # if assert beatable fails, this will catch and print the seed raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_kine(self): + def test_kine(self) -> None: try: self.collect_by_name(["Cutter", "Burning", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter(self): + def test_cutter(self) -> None: try: self.collect_by_name(["Kine", "Burning", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_burning(self): + def test_burning(self) -> None: try: self.collect_by_name(["Cutter", "Kine", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_locked_animals(self): - self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn", + def test_locked_animals(self) -> None: + ripple_field_5 = self.multiworld.get_location("Ripple Field 5 - Animal 2", 1) + self.assertTrue(ripple_field_5.item is not None and ripple_field_5.item.name == "Pitch Spawn", f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}") - self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn", + iceberg_4 = self.multiworld.get_location("Iceberg 4 - Animal 1", 1) + self.assertTrue(iceberg_4.item is not None and iceberg_4.item.name == "ChuChu Spawn", f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}") - self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in + sand_canyon_6 = self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1) + self.assertTrue(sand_canyon_6.item is not None and sand_canyon_6.item.name in {"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}") @@ -183,10 +186,7 @@ class TestAllShuffle(KDL3TestBase): "copy_ability_randomization": "enabled", } - def world_setup(self, seed: Optional[int] = None) -> None: - super().world_setup(97344459114886422393) - - def test_goal(self): + def test_goal(self) -> None: try: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") @@ -206,36 +206,39 @@ class TestAllShuffle(KDL3TestBase): # if assert beatable fails, this will catch and print the seed raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_kine(self): + def test_kine(self) -> None: try: self.collect_by_name(["Cutter", "Burning", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter(self): + def test_cutter(self) -> None: try: self.collect_by_name(["Kine", "Burning", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_burning(self): + def test_burning(self) -> None: try: self.collect_by_name(["Cutter", "Kine", "Heart Star"]) self.assertBeatable(False) except AssertionError as ex: raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_locked_animals(self): - self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn", + def test_locked_animals(self) -> None: + ripple_field_5 = self.multiworld.get_location("Ripple Field 5 - Animal 2", 1) + self.assertTrue(ripple_field_5.item is not None and ripple_field_5.item.name == "Pitch Spawn", f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}") - self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn", + iceberg_4 = self.multiworld.get_location("Iceberg 4 - Animal 1", 1) + self.assertTrue(iceberg_4.item is not None and iceberg_4.item.name == "ChuChu Spawn", f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}") - self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in + sand_canyon_6 = self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1) + self.assertTrue(sand_canyon_6.item is not None and sand_canyon_6.item.name in {"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}") - def test_cutter_and_burning_reachable(self): + def test_cutter_and_burning_reachable(self) -> None: rooms = self.multiworld.worlds[1].rooms copy_abilities = self.multiworld.worlds[1].copy_abilities sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) @@ -257,7 +260,7 @@ class TestAllShuffle(KDL3TestBase): else: self.fail("Could not reach Burning Ability before Iceberg 4!") - def test_valid_abilities_for_ROB(self): + def test_valid_abilities_for_ROB(self) -> None: # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach # first we need to identify our bukiset requirements @@ -268,13 +271,13 @@ class TestAllShuffle(KDL3TestBase): ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), ] copy_abilities = self.multiworld.worlds[1].copy_abilities - required_abilities: List[Tuple[str]] = [] + required_abilities: List[List[str]] = [] for abilities, bukisets in groups: potential_abilities: List[str] = list() for bukiset in bukisets: if copy_abilities[bukiset] in abilities: potential_abilities.append(copy_abilities[bukiset]) - required_abilities.append(tuple(potential_abilities)) + required_abilities.append(potential_abilities) collected_abilities = list() for group in required_abilities: self.assertFalse(len(group) == 0, str(self.multiworld.seed))