mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-28 16:33:26 -07:00
mypy phase 1
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user