-
Notifications
You must be signed in to change notification settings - Fork 703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wario Land 4: Implement new game #3266
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments and things I noticed briefly. I did not look at the client or rom or any of the tests. Some things in setup_locations
I haven't quite figured out yet and I will later go back through Rules.py
again since it's doing some pretty strange things I need to wrap my head around.
worlds/wl4/docs/en_Wario Land 4.md
Outdated
|
||
## Where is the settings page? | ||
|
||
The [player settings page for this game](../player-settings) contains all the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are options now (same change should be applied elsewhere in this file)
The [player settings page for this game](../player-settings) contains all the | |
The [player options page for this game](../player-options) contains all the |
worlds/wl4/docs/en_Wario Land 4.md
Outdated
## What does another world's item look like in Wario Land 4? | ||
|
||
Other games' items will look like Archipelago logos. When collected from a box, the item's name will | ||
be displayed at the bottom left corner of the screen. By default, you will have to carry the item to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually "in" is used here
be displayed at the bottom left corner of the screen. By default, you will have to carry the item to | |
be displayed in the bottom left corner of the screen. By default, you will have to carry the item to |
worlds/wl4/docs/en_Wario Land 4.md
Outdated
|
||
Other games' items will look like Archipelago logos. When collected from a box, the item's name will | ||
be displayed at the bottom left corner of the screen. By default, you will have to carry the item to | ||
the end of the level before the other player will receive it, at which point you will be told who |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this reads a bit better:
the end of the level before the other player will receive it, at which point you will be told who | |
the end of the level for the other player to receive it, at which point you will be told who |
- Detailed installation instructions for Bizhawk can be found at the above link. | ||
- Windows users must run the prereq installer first, which can also be found at the above link. | ||
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases) | ||
(select `Wario Land 4 Client` during installation). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a thing anymore
(select `Wario Land 4 Client` during installation). |
worlds/wl4/docs/setup_en.md
Outdated
|
||
### Where do I get a config file? | ||
|
||
The Player Settings page on the website allows you to configure your personal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Settings -> Options
worlds/wl4/__init__.py
Outdated
for _ in range(full_health_items): | ||
itempool.append(self.create_item('Full Health Item')) | ||
|
||
if (treasure_hunt): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are few places with redundant parens, but this one struck me as especially unneeded
if (treasure_hunt): | |
if treasure_hunt: |
worlds/wl4/__init__.py
Outdated
junk_count = total_required_locations - len(itempool) | ||
junk_item_pool = tuple(filter_item_names(type=ItemType.ITEM)) | ||
for _ in range(junk_count): | ||
item_name = self.multiworld.random.choice(junk_item_pool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiworld.random should be avoided
item_name = self.multiworld.random.choice(junk_item_pool) | |
item_name = self.random.choice(junk_item_pool) |
worlds/wl4/__init__.py
Outdated
if self.options.goal in (Goal.option_local_golden_treasure_hunt, Goal.option_local_golden_diva_treasure_hunt): | ||
self.options.local_items.value.update(self.item_name_groups['Golden Treasure']) | ||
if self.options.required_jewels > self.options.pool_jewels: | ||
self.options.pool_jewels = PoolJewels(self.options.required_jewels) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this was working, but it seems like .value
makes more sense
self.options.pool_jewels = PoolJewels(self.options.required_jewels) | |
self.options.pool_jewels = PoolJewels(self.options.required_jewels.value) |
worlds/wl4/__init__.py
Outdated
if difficulty == 0: | ||
full_health_items -= 8 | ||
else: | ||
raise ValueError('Not enough locations to place abilities for ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this error is entirely from user options, so two things.
- This could be changed to an OptionError
- This could be done much earlier, like in
generate_early
so that generation doesn't halt as late ascreate_items
over this
worlds/wl4/__init__.py
Outdated
goal = self.multiworld.get_location(goal, self.player) | ||
goal.place_locked_item(self.create_item('Escape the Pyramid')) | ||
goal.show_in_spoiler = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
goal
changes types here, so using a new variable is best
goal = self.multiworld.get_location(goal, self.player) | |
goal.place_locked_item(self.create_item('Escape the Pyramid')) | |
goal.show_in_spoiler = False | |
goal_loc = self.multiworld.get_location(goal, self.player) | |
goal_loc.place_locked_item(self.create_item('Escape the Pyramid')) | |
goal_loc.show_in_spoiler = False |
worlds/wl4/test/test_helpers.py
Outdated
with self.subTest('Jewel Pieces'): | ||
pieces = items.filter_items(type=ItemType.JEWEL) | ||
assert all(map(lambda p: p[0].endswith('Piece'), pieces)) | ||
assert all(map(lambda p: p[1].type, pieces)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test fails for me. This passes though:
assert all(map(lambda p: p[1].type, pieces)) | |
assert all(map(lambda p: p[1].type == ItemType.JEWEL, pieces)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more comments. One general question I have is why this code is using Sequence
and Mapping
so much instead of List
and Dict
?
worlds/wl4/__init__.py
Outdated
'CDs': set(filter_item_names(type=ItemType.CD)), | ||
'Abilities': set(filter_item_names(type=ItemType.ABILITY)), | ||
'Golden Treasure': set(filter_item_names(type=ItemType.TREASURE)), | ||
'Traps': { 'Wario Form Trap', 'Lightning Trap'}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An extra space
'Traps': { 'Wario Form Trap', 'Lightning Trap'}, | |
'Traps': {'Wario Form Trap', 'Lightning Trap'}, |
worlds/wl4/__init__.py
Outdated
checks = filter(lambda p: self.options.difficulty in p[1].difficulties, location_table.items()) | ||
if not self.options.goal.needs_treasure_hunt(): | ||
checks = filter(lambda p: p[1].source != LocationType.CHEST, checks) | ||
checks = {name for name, _ in checks} | ||
|
||
if self.options.goal.needs_diva(): | ||
checks.remove('Sound Room - Emergency Exit') | ||
else: | ||
checks.remove('Golden Diva') | ||
return checks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here checks
changes from a filter
with tuples to a set
of strings. Could do this instead:
checks = filter(lambda p: self.options.difficulty in p[1].difficulties, location_table.items()) | |
if not self.options.goal.needs_treasure_hunt(): | |
checks = filter(lambda p: p[1].source != LocationType.CHEST, checks) | |
checks = {name for name, _ in checks} | |
if self.options.goal.needs_diva(): | |
checks.remove('Sound Room - Emergency Exit') | |
else: | |
checks.remove('Golden Diva') | |
return checks | |
checks = filter(lambda p: self.options.difficulty in p[1].difficulties, location_table.items()) | |
if not self.options.goal.needs_treasure_hunt(): | |
checks = filter(lambda p: p[1].source != LocationType.CHEST, checks) | |
check_names = {name for name, _ in checks} | |
if self.options.goal.needs_diva(): | |
check_names.remove('Sound Room - Emergency Exit') | |
else: | |
check_names.remove('Golden Diva') | |
return check_names |
worlds/wl4/__init__.py
Outdated
for _ in range(junk_count): | ||
item_name = self.multiworld.random.choice(junk_item_pool) | ||
itempool.append(self.create_item(item_name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use a comprehension instead:
for _ in range(junk_count): | |
item_name = self.multiworld.random.choice(junk_item_pool) | |
itempool.append(self.create_item(item_name)) | |
itempool.extend([self.create_item(self.random.choice(junk_item_pool)) for _ in range(junk_count)]) |
worlds/wl4/__init__.py
Outdated
for _ in range(copies): | ||
itempool.append(self.create_item(name, force_non_progression)) | ||
|
||
for name in filter_item_names(type=ItemType.CD): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little odd to me that you're filtering these for each world instead of creating some lists once on the class that they can all share and only have to be created once.
worlds/wl4/rules.py
Outdated
def set_access_rules(world: WL4World): | ||
for name, rule in location_rules.items(): | ||
try: | ||
location = world.multiworld.get_location(name, world.player) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use the helper function
location = world.multiworld.get_location(name, world.player) | |
location = world.get_location(name) |
worlds/wl4/rules.py
Outdated
except KeyError as k: | ||
# Raise for invalid location names, not ones that aren't in this player's world | ||
if name not in locations.location_table: | ||
raise ValueError(f'Location {name} does not exist') from k |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way for this error to be hit? Seems more debug-y to me, so you could use an assert instead
except KeyError as k: | |
# Raise for invalid location names, not ones that aren't in this player's world | |
if name not in locations.location_table: | |
raise ValueError(f'Location {name} does not exist') from k | |
except KeyError: | |
# Raise for invalid location names, not ones that aren't in this player's world | |
assert name in locations.location_table, f"Location {name} does not exist" |
worlds/wl4/rules.py
Outdated
def option(option_name: str, choice: Option): | ||
return Requirement(lambda w, _: getattr(w.options, option_name) == choice) | ||
|
||
def difficulty(difficulty: options.Difficulty): | ||
return option('difficulty', difficulty) | ||
|
||
def not_difficulty(_difficulty: options.Difficulty): | ||
return Requirement(lambda w, s: not difficulty(_difficulty).inner(w, s)) | ||
|
||
def logic(logic: options.Logic): | ||
return option('logic', logic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way you're doing this, these are all ints
You can also remove the Options import with this change
def option(option_name: str, choice: Option): | |
return Requirement(lambda w, _: getattr(w.options, option_name) == choice) | |
def difficulty(difficulty: options.Difficulty): | |
return option('difficulty', difficulty) | |
def not_difficulty(_difficulty: options.Difficulty): | |
return Requirement(lambda w, s: not difficulty(_difficulty).inner(w, s)) | |
def logic(logic: options.Logic): | |
return option('logic', logic) | |
def option(option_name: str, choice: int): | |
return Requirement(lambda w, _: getattr(w.options, option_name) == choice) | |
def difficulty(difficulty: int): | |
return option('difficulty', difficulty) | |
def not_difficulty(_difficulty: int): | |
return Requirement(lambda w, s: not difficulty(_difficulty).inner(w, s)) | |
def logic(logic: int): | |
return option('logic', logic) |
worlds/wl4/rules.py
Outdated
item, count = resolve_helper(item_name) | ||
return Requirement(lambda w, s: s.has(item, w.player, count)) | ||
|
||
def has_all(items: Sequence[str]) -> Requirement: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make_boss_access_rule
passes in a tuple of strings and ints so this function is used with both types of RequiredItem
def has_all(items: Sequence[str]) -> Requirement: | |
def has_all(items: Sequence[RequiredItem]) -> Requirement: |
worlds/wl4/rules.py
Outdated
return functools.partial(self.inner, world) | ||
|
||
|
||
def has(item_name: str) -> Requirement: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because has_all
can thus call this with a tuple of strings and ints, this also handles all RequiredItem
types
def has(item_name: str) -> Requirement: | |
def has(item_name: RequiredItem) -> Requirement: |
worlds/wl4/rules.py
Outdated
def resolve_helper(item_name: RequiredItem): | ||
requirement = helpers.get(item_name, item_name) | ||
if isinstance(requirement, str): | ||
return (requirement, 1) | ||
else: | ||
return requirement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So because this accepts tuples of ints, you're calling helpers.get
with a tuple of ints which isn't exactly supported here (it works, but it's kind of taking advantage of this not causing an error). You could do this instead:
def resolve_helper(item_name: RequiredItem):
if not isinstance(item_name, str):
return item_name
item_name = helpers.get(item_name, item_name)
if not isinstance(item_name, str):
return item_name
return item_name, 1
Another option that I think is quite a bit cleaner is changing helpers
to a Mapping[str, Tuple[str, int]]
so you'd change 'Progressive Ground Pound'
to ('Progressive Ground Pound', 1)
and then just having this instead:
def resolve_helper(item_name: RequiredItem):
if not isinstance(item_name, str):
return item_name
return helpers.get(item_name, (item_name, 1))
Something else I forgot to mention, you may want to implement a |
I like to use |
Location IDs are now assigned by their level index and bit position. Increase AP ID offset to appropriately dwarf the range of location IDs
Add Start Inventory from Pool option
…nto wario_land_4
What is this fixing or adding?
Adds Wario Land 4 as an available game for AP.
How was this tested?
Many playtests and unsupported games over several APworld releases.
If this makes graphical changes, please attach screenshots.