Skip to content

Commit

Permalink
Merge branch 'main' into sweep_for_advancements
Browse files Browse the repository at this point in the history
  • Loading branch information
NewSoupVi authored Aug 13, 2024
2 parents 61f11de + 96d48a9 commit 3cfc2fb
Show file tree
Hide file tree
Showing 49 changed files with 9,670 additions and 2,649 deletions.
31 changes: 13 additions & 18 deletions BaseClasses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import collections
import copy
import itertools
import functools
import logging
Expand Down Expand Up @@ -719,14 +718,14 @@ def update_reachable_regions(self, player: int):

def copy(self) -> CollectionState:
ret = CollectionState(self.multiworld)
ret.prog_items = copy.deepcopy(self.prog_items)
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
self.reachable_regions}
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in
self.blocked_connections}
ret.advancements = copy.copy(self.advancements)
ret.path = copy.copy(self.path)
ret.locations_checked = copy.copy(self.locations_checked)
ret.prog_items = {player: counter.copy() for player, counter in self.prog_items.items()}
ret.reachable_regions = {player: region_set.copy() for player, region_set in
self.reachable_regions.items()}
ret.blocked_connections = {player: entrance_set.copy() for player, entrance_set in
self.blocked_connections.items()}
ret.advancements = self.events.copy()
ret.path = self.path.copy()
ret.locations_checked = self.locations_checked.copy()
for function in self.additional_copy_functions:
ret = function(self, ret)
return ret
Expand Down Expand Up @@ -869,19 +868,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_advancements()

return changed
Expand Down Expand Up @@ -1133,9 +1128,9 @@ def can_fill(self, state: CollectionState, item: Item, check_access=True) -> boo
and (not check_access or self.can_reach(state))))

def can_reach(self, state: CollectionState) -> bool:
# self.access_rule computes faster on average, so placing it first for faster abort
# Region.can_reach is just a cache lookup, so placing it first for faster abort on average
assert self.parent_region, "Can't reach location without region"
return self.access_rule(state) and self.parent_region.can_reach(state)
return self.parent_region.can_reach(state) and self.access_rule(state)

def place_locked_item(self, item: Item):
if self.item:
Expand Down Expand Up @@ -1433,7 +1428,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
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
15 changes: 10 additions & 5 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,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 +170,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
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_advancements()
state.update_reachable_regions(1)
self._state_cache[self.multiworld, tuple(items)] = state
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.sweep_for_advancements() # key drop keys repeatedly
state.collect(item, prevent_sweep=True) # prevent_sweep=True prevents running sweep_for_events() and picking up
state.sweep_for_advancements() # 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}")
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
32 changes: 16 additions & 16 deletions worlds/clique/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import List
from typing import List, Dict, Any

from BaseClasses import Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import CliqueItem, item_data_table, item_table
from .Locations import CliqueLocation, location_data_table, location_table, locked_locations
from .Options import clique_options
from .Options import CliqueOptions
from .Regions import region_data_table
from .Rules import get_button_rule

Expand Down Expand Up @@ -38,7 +38,8 @@ class CliqueWorld(World):

game = "Clique"
web = CliqueWebWorld()
option_definitions = clique_options
options: CliqueOptions
options_dataclass = CliqueOptions
location_name_to_id = location_table
item_name_to_id = item_table

Expand All @@ -48,7 +49,7 @@ def create_item(self, name: str) -> CliqueItem:
def create_items(self) -> None:
item_pool: List[CliqueItem] = []
for name, item in item_data_table.items():
if item.code and item.can_create(self.multiworld, self.player):
if item.code and item.can_create(self):
item_pool.append(self.create_item(name))

self.multiworld.itempool += item_pool
Expand All @@ -61,41 +62,40 @@ def create_regions(self) -> None:

# Create locations.
for region_name, region_data in region_data_table.items():
region = self.multiworld.get_region(region_name, self.player)
region = self.get_region(region_name)
region.add_locations({
location_name: location_data.address for location_name, location_data in location_data_table.items()
if location_data.region == region_name and location_data.can_create(self.multiworld, self.player)
if location_data.region == region_name and location_data.can_create(self)
}, CliqueLocation)
region.add_exits(region_data_table[region_name].connecting_regions)

# Place locked locations.
for location_name, location_data in locked_locations.items():
# Ignore locations we never created.
if not location_data.can_create(self.multiworld, self.player):
if not location_data.can_create(self):
continue

locked_item = self.create_item(location_data_table[location_name].locked_item)
self.multiworld.get_location(location_name, self.player).place_locked_item(locked_item)
self.get_location(location_name).place_locked_item(locked_item)

# Set priority location for the Big Red Button!
self.multiworld.priority_locations[self.player].value.add("The Big Red Button")
self.options.priority_locations.value.add("The Big Red Button")

def get_filler_item_name(self) -> str:
return "A Cool Filler Item (No Satisfaction Guaranteed)"

def set_rules(self) -> None:
button_rule = get_button_rule(self.multiworld, self.player)
self.multiworld.get_location("The Big Red Button", self.player).access_rule = button_rule
self.multiworld.get_location("In the Player's Mind", self.player).access_rule = button_rule
button_rule = get_button_rule(self)
self.get_location("The Big Red Button").access_rule = button_rule
self.get_location("In the Player's Mind").access_rule = button_rule

# Do not allow button activations on buttons.
self.multiworld.get_location("The Big Red Button", self.player).item_rule =\
lambda item: item.name != "Button Activation"
self.get_location("The Big Red Button").item_rule = lambda item: item.name != "Button Activation"

# Completion condition.
self.multiworld.completion_condition[self.player] = lambda state: state.has("The Urge to Push", self.player)

def fill_slot_data(self):
def fill_slot_data(self) -> Dict[str, Any]:
return {
"color": getattr(self.multiworld, "color")[self.player].current_key
"color": self.options.color.current_key
}
Loading

0 comments on commit 3cfc2fb

Please sign in to comment.