Skip to content

Commit

Permalink
Merge branch 'main' into mlss-bugfixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesbrq authored Aug 14, 2024
2 parents 10d6eef + 0af31c7 commit ebb93eb
Show file tree
Hide file tree
Showing 37 changed files with 435 additions and 448 deletions.
10 changes: 3 additions & 7 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,19 +863,15 @@ def count_group_unique(self, item_name_group: str, player: int) -> int:
)

# Item related
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:
def collect(self, item: Item, prevent_sweep: bool = False, location: Optional[Location] = None) -> bool:
if location:
self.locations_checked.add(location)

changed = self.multiworld.worlds[item.player].collect(self, item)

if not changed and event:
self.prog_items[item.player][item.name] += 1
changed = True

self.stale[item.player] = True

if changed and not event:
if changed and not prevent_sweep:
self.sweep_for_events()

return changed
Expand Down Expand Up @@ -1427,7 +1423,7 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st
# Maybe move the big bomb over to the Event system instead?
if any(exit_path == 'Pyramid Fairy' for path in self.paths.values()
for (_, exit_path) in path):
if multiworld.mode[player] != 'inverted':
if multiworld.worlds[player].options.mode != 'inverted':
self.paths[str(multiworld.get_region('Big Bomb Shop', player))] = \
get_path(state, multiworld.get_region('Big Bomb Shop', player))
else:
Expand Down
19 changes: 13 additions & 6 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@


class FillError(RuntimeError):
pass
def __init__(self, *args: typing.Union[str, typing.Any], **kwargs) -> None:
if "multiworld" in kwargs and isinstance(args[0], str):
placements = (args[0] + f"\nAll Placements:\n" +
f"{[(loc, loc.item) for loc in kwargs['multiworld'].get_filled_locations()]}")
args = (placements, *args[1:])
super().__init__(*args)


def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
Expand Down Expand Up @@ -212,7 +217,7 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
f"Unfilled locations:\n"
f"{', '.join(str(location) for location in locations)}\n"
f"Already placed {len(placements)}:\n"
f"{', '.join(str(place) for place in placements)}")
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)

item_pool.extend(unplaced_items)

Expand Down Expand Up @@ -299,7 +304,7 @@ def remaining_fill(multiworld: MultiWorld,
f"Unfilled locations:\n"
f"{', '.join(str(location) for location in locations)}\n"
f"Already placed {len(placements)}:\n"
f"{', '.join(str(place) for place in placements)}")
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)

itempool.extend(unplaced_items)

Expand Down Expand Up @@ -506,7 +511,8 @@ def mark_for_locking(location: Location):
if progitempool:
raise FillError(
f"Not enough locations for progression items. "
f"There are {len(progitempool)} more progression items than there are available locations."
f"There are {len(progitempool)} more progression items than there are available locations.",
multiworld=multiworld,
)
accessibility_corrections(multiworld, multiworld.state, defaultlocations)

Expand All @@ -523,7 +529,8 @@ def mark_for_locking(location: Location):
if excludedlocations:
raise FillError(
f"Not enough filler items for excluded locations. "
f"There are {len(excludedlocations)} more excluded locations than filler or trap items."
f"There are {len(excludedlocations)} more excluded locations than filler or trap items.",
multiworld=multiworld,
)

restitempool = filleritempool + usefulitempool
Expand Down Expand Up @@ -589,7 +596,7 @@ def flood_items(multiworld: MultiWorld) -> None:
if candidate_item_to_place is not None:
item_to_place = candidate_item_to_place
else:
raise FillError('No more progress items left to place.')
raise FillError('No more progress items left to place.', multiworld=multiworld)

# find item to replace with progress item
location_list = multiworld.get_reachable_locations()
Expand Down
2 changes: 1 addition & 1 deletion Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None
if file and component:
run_component(component, file)
else:
logging.warning(f"unable to identify component for {filename}")
logging.warning(f"unable to identify component for {file}")

def _stop(self, *largs):
# ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm.
Expand Down
20 changes: 13 additions & 7 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

import worlds
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, distribute_planned, \
flood_items
from Options import StartInventoryPool
from Utils import __version__, output_path, version_tuple, get_settings
from settings import get_settings
Expand Down Expand Up @@ -151,6 +152,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
# Because some worlds don't actually create items during create_items this has to be as late as possible.
if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids):
new_items: List[Item] = []
old_items: List[Item] = []
depletion_pool: Dict[int, Dict[str, int]] = {
player: getattr(multiworld.worlds[player].options,
"start_inventory_from_pool",
Expand All @@ -169,20 +171,24 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
depletion_pool[item.player][item.name] -= 1
# quick abort if we have found all items
if not target:
new_items.extend(multiworld.itempool[i+1:])
old_items.extend(multiworld.itempool[i+1:])
break
else:
new_items.append(item)
old_items.append(item)

# leftovers?
if target:
for player, remaining_items in depletion_pool.items():
remaining_items = {name: count for name, count in remaining_items.items() if count}
if remaining_items:
raise Exception(f"{multiworld.get_player_name(player)}"
logger.warning(f"{multiworld.get_player_name(player)}"
f" is trying to remove items from their pool that don't exist: {remaining_items}")
assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change."
multiworld.itempool[:] = new_items
# find all filler we generated for the current player and remove until it matches
removables = [item for item in new_items if item.player == player]
for _ in range(sum(remaining_items.values())):
new_items.remove(removables.pop())
assert len(multiworld.itempool) == len(new_items + old_items), "Item Pool amounts should not change."
multiworld.itempool[:] = new_items + old_items

multiworld.link_items()

Expand Down Expand Up @@ -341,7 +347,7 @@ def precollect_hint(location):
output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result():
if not multiworld.can_beat_game():
raise Exception("Game appears as unbeatable. Aborting.")
raise FillError("Game appears as unbeatable. Aborting.", multiworld=multiworld)
else:
logger.warning("Location Accessibility requirements not fulfilled.")

Expand Down
29 changes: 1 addition & 28 deletions Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,7 @@ def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str,
:param option_names: names of the options to return
:param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab`
"""
assert option_names, "options.as_dict() was used without any option names."
option_results = {}
for option_name in option_names:
if option_name in type(self).type_hints:
Expand Down Expand Up @@ -1517,31 +1518,3 @@ def yaml_dump_scalar(scalar) -> str:

with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
f.write(res)


if __name__ == "__main__":

from worlds.alttp.Options import Logic
import argparse

map_shuffle = Toggle
compass_shuffle = Toggle
key_shuffle = Toggle
big_key_shuffle = Toggle
hints = Toggle
test = argparse.Namespace()
test.logic = Logic.from_text("no_logic")
test.map_shuffle = map_shuffle.from_text("ON")
test.hints = hints.from_text('OFF')
try:
test.logic = Logic.from_text("overworld_glitches_typo")
except KeyError as e:
print(e)
try:
test.logic_owg = Logic.from_text("owg")
except KeyError as e:
print(e)
if test.map_shuffle:
print("map_shuffle is on")
print(f"Hints are {bool(test.hints)}")
print(test)
2 changes: 1 addition & 1 deletion test/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def get_state(self, items):
state = CollectionState(self.multiworld)
for item in items:
item.classification = ItemClassification.progression
state.collect(item, event=True)
state.collect(item, prevent_sweep=True)
state.sweep_for_events()
state.update_reachable_regions(1)
self._state_cache[self.multiworld, tuple(items)] = state
Expand Down
6 changes: 3 additions & 3 deletions test/multiworld/test_multiworlds.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_fills(self) -> None:
all_worlds = list(AutoWorldRegister.world_types.values())
self.multiworld = setup_multiworld(all_worlds, ())
for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_locations
world.options.accessibility.value = Accessibility.option_full
self.assertSteps(gen_steps)
with self.subTest("filling multiworld", seed=self.multiworld.seed):
distribute_items_restrictive(self.multiworld)
Expand All @@ -66,8 +66,8 @@ def test_fills(self) -> None:
class TestTwoPlayerMulti(MultiworldTestBase):
def test_two_player_single_game_fills(self) -> None:
"""Tests that a multiworld of two players for each registered game world can generate."""
for world in AutoWorldRegister.world_types.values():
self.multiworld = setup_multiworld([world, world], ())
for world_type in AutoWorldRegister.world_types.values():
self.multiworld = setup_multiworld([world_type, world_type], ())
for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_full
self.assertSteps(gen_steps)
Expand Down
7 changes: 6 additions & 1 deletion worlds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ def load(self) -> bool:
else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])

mod.__package__ = f"worlds.{mod.__package__}"
if mod.__package__ is not None:
mod.__package__ = f"worlds.{mod.__package__}"
else:
# load_module does not populate package, we'll have to assume mod.__name__ is correct here
# probably safe to remove with 3.8 support
mod.__package__ = f"worlds.{mod.__name__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
Expand Down
4 changes: 2 additions & 2 deletions worlds/alttp/test/dungeons/TestDungeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def run_tests(self, access_pool):

for item in items:
item.classification = ItemClassification.progression
state.collect(item, event=True) # event=True prevents running sweep_for_events() and picking up
state.collect(item, prevent_sweep=True) # prevent_sweep=True prevents running sweep_for_events() and picking up
state.sweep_for_events() # key drop keys repeatedly

self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
13 changes: 8 additions & 5 deletions worlds/clique/Items.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from typing import Callable, Dict, NamedTuple, Optional
from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING

from BaseClasses import Item, ItemClassification, MultiWorld
from BaseClasses import Item, ItemClassification

if TYPE_CHECKING:
from . import CliqueWorld


class CliqueItem(Item):
Expand All @@ -10,7 +13,7 @@ class CliqueItem(Item):
class CliqueItemData(NamedTuple):
code: Optional[int] = None
type: ItemClassification = ItemClassification.filler
can_create: Callable[[MultiWorld, int], bool] = lambda multiworld, player: True
can_create: Callable[["CliqueWorld"], bool] = lambda world: True


item_data_table: Dict[str, CliqueItemData] = {
Expand All @@ -21,11 +24,11 @@ class CliqueItemData(NamedTuple):
"Button Activation": CliqueItemData(
code=69696968,
type=ItemClassification.progression,
can_create=lambda multiworld, player: bool(getattr(multiworld, "hard_mode")[player]),
can_create=lambda world: world.options.hard_mode,
),
"A Cool Filler Item (No Satisfaction Guaranteed)": CliqueItemData(
code=69696967,
can_create=lambda multiworld, player: False # Only created from `get_filler_item_name`.
can_create=lambda world: False # Only created from `get_filler_item_name`.
),
"The Urge to Push": CliqueItemData(
type=ItemClassification.progression,
Expand Down
11 changes: 7 additions & 4 deletions worlds/clique/Locations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from typing import Callable, Dict, NamedTuple, Optional
from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING

from BaseClasses import Location, MultiWorld
from BaseClasses import Location

if TYPE_CHECKING:
from . import CliqueWorld


class CliqueLocation(Location):
Expand All @@ -10,7 +13,7 @@ class CliqueLocation(Location):
class CliqueLocationData(NamedTuple):
region: str
address: Optional[int] = None
can_create: Callable[[MultiWorld, int], bool] = lambda multiworld, player: True
can_create: Callable[["CliqueWorld"], bool] = lambda world: True
locked_item: Optional[str] = None


Expand All @@ -22,7 +25,7 @@ class CliqueLocationData(NamedTuple):
"The Item on the Desk": CliqueLocationData(
region="The Button Realm",
address=69696968,
can_create=lambda multiworld, player: bool(getattr(multiworld, "hard_mode")[player]),
can_create=lambda world: world.options.hard_mode,
),
"In the Player's Mind": CliqueLocationData(
region="The Button Realm",
Expand Down
16 changes: 8 additions & 8 deletions worlds/clique/Options.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import Dict

from Options import Choice, Option, Toggle
from dataclasses import dataclass
from Options import Choice, Toggle, PerGameCommonOptions, StartInventoryPool


class HardMode(Toggle):
Expand All @@ -25,10 +24,11 @@ class ButtonColor(Choice):
option_black = 11


clique_options: Dict[str, type(Option)] = {
"color": ButtonColor,
"hard_mode": HardMode,
@dataclass
class CliqueOptions(PerGameCommonOptions):
color: ButtonColor
hard_mode: HardMode
start_inventory_from_pool: StartInventoryPool

# DeathLink is always on. Always.
# "death_link": DeathLink,
}
# death_link: DeathLink
13 changes: 8 additions & 5 deletions worlds/clique/Rules.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Callable
from typing import Callable, TYPE_CHECKING

from BaseClasses import CollectionState, MultiWorld
from BaseClasses import CollectionState

if TYPE_CHECKING:
from . import CliqueWorld

def get_button_rule(multiworld: MultiWorld, player: int) -> Callable[[CollectionState], bool]:
if getattr(multiworld, "hard_mode")[player]:
return lambda state: state.has("Button Activation", player)

def get_button_rule(world: "CliqueWorld") -> Callable[[CollectionState], bool]:
if world.options.hard_mode:
return lambda state: state.has("Button Activation", world.player)

return lambda state: True
Loading

0 comments on commit ebb93eb

Please sign in to comment.