Skip to content

Commit

Permalink
Rework imports
Browse files Browse the repository at this point in the history
  • Loading branch information
lilDavid committed Sep 2, 2024
1 parent be2fde9 commit f37f5bc
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 114 deletions.
7 changes: 3 additions & 4 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
from worlds.AutoWorld import WebWorld, World

from .client import WL4Client
from .data import data_path
from .items import WL4Item, ap_id_from_wl4_data, filter_item_names, filter_items, item_table
from .locations import get_level_locations, location_name_to_id, location_table, event_table
from .data import Passage, data_path
from .items import ItemType, WL4Item, ap_id_from_wl4_data, filter_item_names, filter_items, item_table
from .locations import LocationType, get_level_locations, location_name_to_id, location_table, event_table
from .options import Difficulty, Goal, GoldenJewels, PoolJewels, WL4Options, wl4_option_groups
from .regions import connect_regions, create_regions
from .rom import MD5_JP, MD5_US_EU, WL4ProcedurePatch, write_tokens
from .rules import set_access_rules
from .types import ItemType, LocationType, Passage


class WL4Settings(settings.Group):
Expand Down
13 changes: 5 additions & 8 deletions client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
import Utils
from NetUtils import ClientStatus
import worlds._bizhawk as bizhawk
from worlds._bizhawk import RequestFailedError
from worlds._bizhawk.client import BizHawkClient

from .data import encode_str, get_symbol
from .data import Passage, encode_str, get_symbol
from .locations import get_level_locations, location_name_to_id, location_table
from .types import Passage

if TYPE_CHECKING:
from worlds._bizhawk.context import BizHawkClientContext
Expand Down Expand Up @@ -140,7 +137,7 @@ def __str__(self):
return repr(self)


class WL4Client(BizHawkClient):
class WL4Client(bizhawk.client.BizHawkClient):
game = 'Wario Land 4'
system = 'GBA'
patch_suffix = '.apwl4'
Expand Down Expand Up @@ -171,7 +168,7 @@ async def validate_rom(self, client_ctx: BizHawkClientContext) -> bool:
read(get_symbol('PlayerName'), 64),
read(get_symbol('SeedName'), 64),
]))
except RequestFailedError:
except bizhawk.RequestFailedError:
return False # Should verify on the next pass

game_name = next(read_result).decode('ascii')
Expand Down Expand Up @@ -271,7 +268,7 @@ async def game_watcher(self, client_ctx: BizHawkClientContext) -> None:
read8(fhi_1_address),
read8(fhi_2_address),
]))
except RequestFailedError:
except bizhawk.RequestFailedError:
return

game_mode = next_int(read_result)
Expand Down Expand Up @@ -426,7 +423,7 @@ async def game_watcher(self, client_ctx: BizHawkClientContext) -> None:

try:
await bizhawk.guarded_write(bizhawk_ctx, write_list, guard_list)
except RequestFailedError:
except bizhawk.RequestFailedError:
return

def on_package(self, ctx: BizHawkClientContext, cmd: str, args: dict) -> None:
Expand Down
34 changes: 33 additions & 1 deletion data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from enum import Enum
from enum import Enum, IntEnum, IntFlag
from io import StringIO
import pkgutil
from typing import Mapping
Expand All @@ -9,6 +9,38 @@
ap_id_offset = 0xEC0000


class Passage(IntEnum):
ENTRY = 0
EMERALD = 1
RUBY = 2
TOPAZ = 3
SAPPHIRE = 4
GOLDEN = 5

def long_name(self):
if self == Passage.GOLDEN:
return 'Golden Pyramid'
else:
return self.short_name() + ' Passage'

def short_name(self):
return ('Entry', 'Emerald', 'Ruby', 'Topaz', 'Sapphire', 'Golden')[self]


class ItemFlag(IntFlag):
JEWEL_NE = 1 << 0
JEWEL_SE = 1 << 1
JEWEL_SW = 1 << 2
JEWEL_NW = 1 << 3
CD = 1 << 4
KEYZER = 1 << 5
FULL_HEALTH = 1 << 6
FULL_HEALTH_2 = 1 << 7

BOSS_CLEAR = 1 << 5
DIVA_CLEAR = 1 << 4


class Domain(Enum):
SYSTEM_BUS = 0x0000000
ROM = 0x8000000
Expand Down
24 changes: 20 additions & 4 deletions items.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from __future__ import annotations

from enum import IntEnum
from typing import Any, Iterable, NamedTuple, Optional, Tuple

from BaseClasses import Item
from BaseClasses import ItemClassification as IC
from BaseClasses import Item, ItemClassification as IC

from .data import ap_id_offset
from .types import Box, ItemFlag, ItemType, Passage
from .data import ap_id_offset, ItemFlag, Passage


# Items are encoded as 8-bit numbers as follows:
Expand Down Expand Up @@ -45,6 +44,23 @@
# For AP item, classifications are as reported by ItemClassification.as_flag()


class Box(IntEnum):
JEWEL_NE = 0
JEWEL_SE = 1
JEWEL_SW = 2
JEWEL_NW = 3
CD = 4
FULL_HEALTH = 5


class ItemType(IntEnum):
JEWEL = 0
CD = 1
ITEM = 2
ABILITY = 4
TREASURE = 5


def ap_id_from_wl4_data(data: ItemData) -> int:
cat, itemid, _ = data
if cat == ItemType.JEWEL:
Expand Down
9 changes: 7 additions & 2 deletions locations.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from enum import IntEnum
from typing import NamedTuple, Optional, Sequence, Tuple

from BaseClasses import Location, Region

from .data import ap_id_offset
from .data import ItemFlag, Passage, ap_id_offset
from .options import Difficulty
from .types import ItemFlag, LocationType, Passage


class LocationType(IntEnum):
BOX = 0
CHEST = 3


class LocationData(NamedTuple):
Expand Down
27 changes: 15 additions & 12 deletions regions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from __future__ import annotations

import itertools
from typing import Iterable, Sequence, Set, TYPE_CHECKING
from typing import Callable, Iterable, Optional, Sequence, Set, TYPE_CHECKING

from BaseClasses import Region, Entrance
from BaseClasses import CollectionState, Region, Entrance

from . import rules
from .data import Passage
from .locations import WL4Location, get_level_location_data
from .types import AccessRule, Passage
from .rules import get_access_rule, get_frog_switch_region, get_keyzer_region, make_boss_access_rule
from .options import Goal, OpenDoors

if TYPE_CHECKING:
Expand All @@ -24,6 +24,9 @@ def pairwise(iterable):
return zip(a, b)


AccessRule = Optional[Callable[[CollectionState], bool]]


class WL4Region(Region):
clear_rule: AccessRule

Expand Down Expand Up @@ -132,7 +135,7 @@ def level_regions(name: str, passage: Passage, level: int):
regions = {region: basic_region(region) for region in region_names}
for loc_name, location in get_level_location_data(passage, level):
if not portal_setting:
region_name = rules.get_frog_switch_region(name)
region_name = get_frog_switch_region(name)
elif location.region_in_level is not None:
region_name = f'{name} - {location.region_in_level}'
else:
Expand Down Expand Up @@ -194,7 +197,7 @@ def connect_regions(world: WL4World):
def connect_level(level_name):
regions = get_region_names(level_name)
for source, dest in pairwise(regions):
access_rule = rules.get_access_rule(world, dest)
access_rule = get_access_rule(world, dest)
connect_entrance(world, dest, source, dest, access_rule)

connect_level('Hall of Hieroglyphs')
Expand Down Expand Up @@ -230,7 +233,7 @@ def connect_level_exit(level, destination, rule: AccessRule = None):
):
region = f'{level} (entrance)'
elif portal_setting:
region = rules.get_keyzer_region(level)
region = get_keyzer_region(level)
else:
region = get_region_names(level)[-1]
connect_with_name(region, destination, f'{level} Gate', rule)
Expand All @@ -251,7 +254,7 @@ def connect_level_exit(level, destination, rule: AccessRule = None):
connect_level_exit('Mystic Lake', 'Monsoon Jungle (entrance)')
connect_level_exit('Monsoon Jungle', 'Emerald Minigame Shop')
connect('Emerald Minigame Shop', 'Emerald Passage Boss',
rules.make_boss_access_rule(world, Passage.EMERALD, required_jewels))
make_boss_access_rule(world, Passage.EMERALD, required_jewels))

connect('Menu', 'Ruby Passage')
connect('Ruby Passage', 'The Curious Factory (entrance)')
Expand All @@ -260,7 +263,7 @@ def connect_level_exit(level, destination, rule: AccessRule = None):
connect_level_exit('40 Below Fridge', 'Pinball Zone (entrance)')
connect_level_exit('Pinball Zone', 'Ruby Minigame Shop')
connect('Ruby Minigame Shop', 'Ruby Passage Boss',
rules.make_boss_access_rule(world, Passage.RUBY, required_jewels))
make_boss_access_rule(world, Passage.RUBY, required_jewels))

connect('Menu', 'Topaz Passage')
connect('Topaz Passage', 'Toy Block Tower (entrance)')
Expand All @@ -269,7 +272,7 @@ def connect_level_exit(level, destination, rule: AccessRule = None):
connect_level_exit('Doodle Woods', 'Domino Row (entrance)')
connect_level_exit('Domino Row', 'Topaz Minigame Shop')
connect('Topaz Minigame Shop', 'Topaz Passage Boss',
rules.make_boss_access_rule(world, Passage.TOPAZ, required_jewels))
make_boss_access_rule(world, Passage.TOPAZ, required_jewels))

connect('Menu', 'Sapphire Passage')
connect('Sapphire Passage', 'Crescent Moon Village (entrance)')
Expand All @@ -278,7 +281,7 @@ def connect_level_exit(level, destination, rule: AccessRule = None):
connect_level_exit('Fiery Cavern', 'Hotel Horror (entrance)')
connect_level_exit('Hotel Horror', 'Sapphire Minigame Shop')
connect('Sapphire Minigame Shop', 'Sapphire Passage Boss',
rules.make_boss_access_rule(world, Passage.SAPPHIRE, required_jewels))
make_boss_access_rule(world, Passage.SAPPHIRE, required_jewels))

connect('Menu', 'Golden Pyramid',
lambda state: state.has_all({'Emerald Passage Clear', 'Ruby Passage Clear',
Expand All @@ -287,7 +290,7 @@ def connect_level_exit(level, destination, rule: AccessRule = None):
if world.options.goal != Goal.option_golden_treasure_hunt:
connect_level_exit('Golden Passage', 'Golden Minigame Shop')
connect('Golden Minigame Shop', 'Golden Pyramid Boss',
rules.make_boss_access_rule(world, Passage.GOLDEN, required_jewels_entry))
make_boss_access_rule(world, Passage.GOLDEN, required_jewels_entry))

connect('Menu', 'Sound Room')

Expand Down
5 changes: 2 additions & 3 deletions rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
import Utils
from worlds.Files import APPatchExtension, APProcedurePatch, APTokenMixin, APTokenTypes

from .data import ap_id_offset, encode_str, get_symbol
from .items import WL4Item, filter_items
from .types import ItemType, Passage
from .data import Passage, ap_id_offset, encode_str, get_symbol
from .items import ItemType, WL4Item, filter_items
from .options import Difficulty, Goal, MusicShuffle, OpenDoors, Portal, SmashThroughHardBlocks

if TYPE_CHECKING:
Expand Down
23 changes: 12 additions & 11 deletions rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

from BaseClasses import CollectionState

from . import items, locations, options
from .types import ItemType, Passage
from .options import Goal
from .data import Passage
from .items import ItemType, filter_item_names
from .locations import location_table, event_table
from .options import Difficulty, Goal, Logic

if TYPE_CHECKING:
from . import WL4World
Expand Down Expand Up @@ -55,7 +56,7 @@ def has_any(items: Sequence[RequiredItem]) -> Requirement:

def has_treasures() -> Requirement:
return Requirement(lambda w, s: sum(has(item).inner(w, s)
for item in items.filter_item_names(type=ItemType.TREASURE))
for item in filter_item_names(type=ItemType.TREASURE))
>= w.options.golden_treasure_count)


Expand Down Expand Up @@ -98,7 +99,7 @@ def get_access_rule(world: WL4World, region_name: str):

def make_boss_access_rule(world: WL4World, passage: Passage, jewels_needed: int):
jewel_list = [(name, jewels_needed)
for name in items.filter_item_names(type=ItemType.JEWEL, passage=passage)]
for name in filter_item_names(type=ItemType.JEWEL, passage=passage)]
return has_all(jewel_list).apply_world(world)


Expand All @@ -108,7 +109,7 @@ def set_access_rules(world: WL4World):
location = world.get_location(name)
location.access_rule = rule.apply_world(world)
except KeyError:
assert name in locations.location_table or name in locations.event_table, \
assert name in location_table or name in event_table, \
f"{name} is not a valid location name"


Expand Down Expand Up @@ -149,11 +150,11 @@ def set_access_rules(world: WL4World):
}


normal = options.Difficulty.option_normal
hard = options.Difficulty.option_hard
s_hard = options.Difficulty.option_s_hard
basic = options.Logic.option_basic
advanced = options.Logic.option_advanced
normal = Difficulty.option_normal
hard = Difficulty.option_hard
s_hard = Difficulty.option_s_hard
basic = Logic.option_basic
advanced = Logic.option_advanced


# Regions are linear, so each region from the same level adds to the previous
Expand Down
18 changes: 10 additions & 8 deletions test/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from . import WL4TestBase

from .. import items, locations
from ..types import ItemType, Passage
from ..data import Passage
from ..items import ItemType, filter_items, filter_item_names
from ..locations import get_level_locations


main_levels = ['Palm Tree Paradise', 'Wildflower Fields', 'Mystic Lake', 'Monsoon Jungle',
'The Curious Factory', 'The Toxic Landfill', '40 Below Fridge', 'Pinball Zone',
Expand All @@ -13,32 +15,32 @@ class TestHelpers(WL4TestBase):
def test_item_filter(self):
"""Ensure item filters and item names match."""
with self.subTest('Jewel Pieces'):
pieces = items.filter_items(type=ItemType.JEWEL)
pieces = filter_items(type=ItemType.JEWEL)
assert all(map(lambda p: p[0].endswith('Piece'), pieces))
assert all(map(lambda p: p[1].type == ItemType.JEWEL, pieces))

with self.subTest('CDs'):
cds = items.filter_item_names(type=ItemType.CD)
cds = filter_item_names(type=ItemType.CD)
assert all(map(lambda c: c.endswith('CD'), cds))

for passage in Passage:
with self.subTest(passage.long_name()):
pieces = items.filter_item_names(type=ItemType.JEWEL, passage=passage)
pieces = filter_item_names(type=ItemType.JEWEL, passage=passage)
assert all(map(lambda p: passage.short_name() in p, pieces))

def test_location_filter(self):
"""Test that the location filter and location names match"""
with self.subTest('Hall of Hieroglyphs'):
checks = locations.get_level_locations(Passage.ENTRY, 0)
checks = get_level_locations(Passage.ENTRY, 0)
assert all(map(lambda l: l.startswith('Hall of Hieroglyphs'), checks))

for passage in range(1, 5):
for level in range(4):
level_name = main_levels[passage * 4 - 4 + level]
with self.subTest(level_name):
checks = locations.get_level_locations(Passage(passage), level)
checks = get_level_locations(Passage(passage), level)
assert all(map(lambda l: l.startswith(level_name), checks))

with self.subTest('Golden Passage'):
checks = locations.get_level_locations(Passage.GOLDEN, 0)
checks = get_level_locations(Passage.GOLDEN, 0)
assert all(map(lambda l: l.startswith('Golden Passage'), checks))
Loading

0 comments on commit f37f5bc

Please sign in to comment.