Skip to content

Commit

Permalink
Merge branch 'main' into mm2
Browse files Browse the repository at this point in the history
  • Loading branch information
Silvris authored May 18, 2024
2 parents 09069f4 + 5e3c5de commit 60dac37
Show file tree
Hide file tree
Showing 146 changed files with 14,469 additions and 3,074 deletions.
84 changes: 74 additions & 10 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from collections import Counter, deque
from collections.abc import Collection, MutableSequence
from enum import IntEnum, IntFlag
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union, \
Type, ClassVar
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Set, Tuple, \
TypedDict, Union, Type, ClassVar

import NetUtils
import Options
Expand Down Expand Up @@ -707,15 +707,49 @@ def has_any(self, items: Iterable[str], player: int) -> bool:
"""Returns True if at least one item name of items is in state at least once."""
return any(self.prog_items[player][item] for item in items)

def has_all_counts(self, item_counts: Mapping[str, int], player: int) -> bool:
"""Returns True if each item name is in the state at least as many times as specified."""
return all(self.prog_items[player][item] >= count for item, count in item_counts.items())

def has_any_count(self, item_counts: Mapping[str, int], player: int) -> bool:
"""Returns True if at least one item name is in the state at least as many times as specified."""
return any(self.prog_items[player][item] >= count for item, count in item_counts.items())

def count(self, item: str, player: int) -> int:
return self.prog_items[player][item]

def item_count(self, item: str, player: int) -> int:
Utils.deprecate("Use count instead.")
return self.count(item, player)
def has_from_list(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list."""
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in items:
found += player_prog_items[item_name]
if found >= count:
return True
return False

def has_from_list_exclusive(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list.
Ignores duplicates of the same item."""
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in items:
found += player_prog_items[item_name] > 0
if found >= count:
return True
return False

def count_from_list(self, items: Iterable[str], player: int) -> int:
"""Returns the cumulative count of items from a list present in state."""
return sum(self.prog_items[player][item_name] for item_name in items)

def count_from_list_exclusive(self, items: Iterable[str], player: int) -> int:
"""Returns the cumulative count of items from a list present in state. Ignores duplicates of the same item."""
return sum(self.prog_items[player][item_name] > 0 for item_name in items)

# item name group related
def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
"""Returns True if the state contains at least `count` items present in a specified item group."""
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
Expand All @@ -724,12 +758,34 @@ def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
return True
return False

def count_group(self, item_name_group: str, player: int) -> int:
def has_group_exclusive(self, item_name_group: str, player: int, count: int = 1) -> bool:
"""Returns True if the state contains at least `count` items present in a specified item group.
Ignores duplicates of the same item.
"""
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
found += player_prog_items[item_name]
return found
found += player_prog_items[item_name] > 0
if found >= count:
return True
return False

def count_group(self, item_name_group: str, player: int) -> int:
"""Returns the cumulative count of items from an item group present in state."""
player_prog_items = self.prog_items[player]
return sum(
player_prog_items[item_name]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]
)

def count_group_exclusive(self, item_name_group: str, player: int) -> int:
"""Returns the cumulative count of items from an item group present in state.
Ignores duplicates of the same item."""
player_prog_items = self.prog_items[player]
return sum(
player_prog_items[item_name] > 0
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]
)

# Item related
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:
Expand Down Expand Up @@ -990,7 +1046,7 @@ def __init__(self, player: int, name: str = '', address: Optional[int] = None, p
self.parent_region = parent

def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
return ((self.always_allow(state, item) and item.name not in state.multiworld.non_local_items[item.player])
return ((self.always_allow(state, item) and item.name not in state.multiworld.worlds[item.player].options.non_local_items)
or ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful))
and self.item_rule(item)
and (not check_access or self.can_reach(state))))
Expand Down Expand Up @@ -1186,7 +1242,7 @@ def create_playthrough(self, create_paths: bool = True) -> None:
logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (
location.item.name, location.item.player, location.name, location.player) for location in
sphere_candidates])
if any([multiworld.accessibility[location.item.player] != 'minimal' for location in sphere_candidates]):
if any([multiworld.worlds[location.item.player].options.accessibility != 'minimal' for location in sphere_candidates]):
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
f'Something went terribly wrong here.')
else:
Expand Down Expand Up @@ -1397,6 +1453,14 @@ class Tutorial(NamedTuple):
authors: List[str]


class OptionGroup(NamedTuple):
"""Define a grouping of options"""
name: str
"""Name of the group to categorize this option in for display on the WebHost and in generated YAMLS."""
options: List[Type[Options.Option]]
"""Options to be in the defined group. """


class PlandoOptions(IntFlag):
none = 0b0000
items = 0b0001
Expand Down
14 changes: 10 additions & 4 deletions Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def roll_linked_options(weights: dict) -> dict:
return weights


def roll_triggers(weights: dict, triggers: list) -> dict:
def roll_triggers(weights: dict, triggers: list, valid_keys: set) -> dict:
weights = copy.deepcopy(weights) # make sure we don't write back to other weights sets in same_settings
weights["_Generator_Version"] = Utils.__version__
for i, option_set in enumerate(triggers):
Expand All @@ -401,7 +401,7 @@ def roll_triggers(weights: dict, triggers: list) -> dict:
if category_name:
currently_targeted_weights = currently_targeted_weights[category_name]
update_weights(currently_targeted_weights, category_options, "Triggered", option_set["option_name"])

valid_keys.add(key)
except Exception as e:
raise ValueError(f"Your trigger number {i + 1} is invalid. "
f"Please fix your triggers.") from e
Expand All @@ -415,6 +415,7 @@ def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str,
player_option = option.from_any(game_weights[option_key])
else:
player_option = option.from_any(get_choice(option_key, game_weights))
del game_weights[option_key]
else:
player_option = option.from_any(option.default) # call the from_any here to support default "random"
setattr(ret, option_key, player_option)
Expand All @@ -428,8 +429,9 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
if "linked_options" in weights:
weights = roll_linked_options(weights)

valid_trigger_names = set()
if "triggers" in weights:
weights = roll_triggers(weights, weights["triggers"])
weights = roll_triggers(weights, weights["triggers"], valid_trigger_names)

requirements = weights.get("requires", {})
if requirements:
Expand Down Expand Up @@ -469,7 +471,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
raise Exception(f"Merge tag cannot be used outside of trigger contexts.")

if "triggers" in game_weights:
weights = roll_triggers(weights, game_weights["triggers"])
weights = roll_triggers(weights, game_weights["triggers"], valid_trigger_names)
game_weights = weights[ret.game]

ret.name = get_choice('name', weights)
Expand All @@ -478,6 +480,10 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b

for option_key, option in world_type.options_dataclass.type_hints.items():
handle_option(ret, game_weights, option_key, option, plando_options)
for option_key in game_weights:
if option_key in {"triggers", *valid_trigger_names}:
continue
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.")
if PlandoOptions.items in plando_options:
ret.plando_items = game_weights.get("plando_items", [])
if ret.game == "A Link to the Past":
Expand Down
2 changes: 1 addition & 1 deletion Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
elif not args:
args = {}

if "Patch|Game|Component" in args:
if args.get("Patch|Game|Component", None) is not None:
file, component = identify(args["Patch|Game|Component"])
if file:
args['file'] = file
Expand Down
Loading

0 comments on commit 60dac37

Please sign in to comment.