Core: move option results to the World class instead of MultiWorld (#993)
🤞 * map option objects to a `World.options` dict * convert RoR2 to options dict system for testing * add temp behavior for lttp with notes * copy/paste bad * convert `set_default_common_options` to a namespace property * reorganize test call order * have fill_restrictive use the new options system * update world api * update soe tests * fix world api * core: auto initialize a dataclass on the World class with the option results * core: auto initialize a dataclass on the World class with the option results: small tying improvement * add `as_dict` method to the options dataclass * fix namespace issues with tests * have current option updates use `.value` instead of changing the option * update ror2 to use the new options system again * revert the junk pool dict since it's cased differently * fix begin_with_loop typo * write new and old options to spoiler * change factorio option behavior back * fix comparisons * move common and per_game_common options to new system * core: automatically create missing options_dataclass from legacy option_definitions * remove spoiler special casing and add back the Factorio option changing but in new system * give ArchipIDLE the default options_dataclass so its options get generated and spoilered properly * reimplement `inspect.get_annotations` * move option info generation for webhost to new system * need to include Common and PerGame common since __annotations__ doesn't include super * use get_type_hints for the options dictionary * typing.get_type_hints returns the bases too. * forgot to sweep through generate * sweep through all the tests * swap to a metaclass property * move remaining usages from get_type_hints to metaclass property * move remaining usages from __annotations__ to metaclass property * move remaining usages from legacy dictionaries to metaclass property * remove legacy dictionaries * cache the metaclass property * clarify inheritance in world api * move the messenger to new options system * add an assert for my dumb * update the doc * rename o to options * missed a spot * update new messenger options * comment spacing Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * fix tests * fix missing import * make the documentation definition more accurate * use options system for loc creation * type cast MessengerWorld * fix typo and use quotes for cast * LTTP: set random seed in tests * ArchipIdle: remove change here as it's default on AutoWorld * Stardew: Need to set state because `set_default_common_options` used to * The Messenger: update shop rando and helpers to new system; optimize imports * Add a kwarg to `as_dict` to do the casing for you * RoR2: use new kwarg for less code * RoR2: revert some accidental reverts * The Messenger: remove an unnecessary variable * remove TypeVar that isn't used * CommonOptions not abstract * Docs: fix mistake in options api.md Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * create options for item link worlds * revert accidental doc removals * Item Links: set default options on group * change Zillion to new options dataclass * remove unused parameter to function * use TypeGuard for Literal narrowing * move dlc quest to new api * move overcooked 2 to new api * fixed some missed code in oc2 * - Tried to be compliant with 993 (WIP?) * - I think it all works now * - Removed last trace of me touching core * typo * It now passes all tests! * Improve options, fix all issues I hope * - Fixed init options * dlcquest: fix bad imports * missed a file * - Reduce code duplication * add as_dict documentation * - Use .items(), get option name more directly, fix slot data content * - Remove generic options from the slot data * improve slot data documentation * remove `CommonOptions.get_value` (#21) * better slot data description Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> Co-authored-by: Doug Hoskisson <beauxq@yahoo.com> Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Alex Gilbert <alexgilbert@yahoo.com>
This commit is contained in:
parent
a7b4914bb7
commit
7193182294
|
@ -226,25 +226,24 @@ class MultiWorld():
|
|||
range(1, self.players + 1)}
|
||||
|
||||
def set_options(self, args: Namespace) -> None:
|
||||
for option_key in Options.common_options:
|
||||
setattr(self, option_key, getattr(args, option_key, {}))
|
||||
for option_key in Options.per_game_common_options:
|
||||
setattr(self, option_key, getattr(args, option_key, {}))
|
||||
|
||||
for player in self.player_ids:
|
||||
self.custom_data[player] = {}
|
||||
world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]]
|
||||
for option_key in world_type.option_definitions:
|
||||
setattr(self, option_key, getattr(args, option_key, {}))
|
||||
|
||||
self.worlds[player] = world_type(self, player)
|
||||
self.worlds[player].random = self.per_slot_randoms[player]
|
||||
for option_key in world_type.options_dataclass.type_hints:
|
||||
option_values = getattr(args, option_key, {})
|
||||
setattr(self, option_key, option_values)
|
||||
# TODO - remove this loop once all worlds use options dataclasses
|
||||
options_dataclass: typing.Type[Options.PerGameCommonOptions] = self.worlds[player].options_dataclass
|
||||
self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player]
|
||||
for option_key in options_dataclass.type_hints})
|
||||
|
||||
def set_item_links(self):
|
||||
item_links = {}
|
||||
replacement_prio = [False, True, None]
|
||||
for player in self.player_ids:
|
||||
for item_link in self.item_links[player].value:
|
||||
for item_link in self.worlds[player].options.item_links.value:
|
||||
if item_link["name"] in item_links:
|
||||
if item_links[item_link["name"]]["game"] != self.game[player]:
|
||||
raise Exception(f"Cannot ItemLink across games. Link: {item_link['name']}")
|
||||
|
@ -299,14 +298,6 @@ class MultiWorld():
|
|||
group["non_local_items"] = item_link["non_local_items"]
|
||||
group["link_replacement"] = replacement_prio[item_link["link_replacement"]]
|
||||
|
||||
# intended for unittests
|
||||
def set_default_common_options(self):
|
||||
for option_key, option in Options.common_options.items():
|
||||
setattr(self, option_key, {player_id: option(option.default) for player_id in self.player_ids})
|
||||
for option_key, option in Options.per_game_common_options.items():
|
||||
setattr(self, option_key, {player_id: option(option.default) for player_id in self.player_ids})
|
||||
self.state = CollectionState(self)
|
||||
|
||||
def secure(self):
|
||||
self.random = ThreadBarrierProxy(secrets.SystemRandom())
|
||||
self.is_race = True
|
||||
|
@ -863,19 +854,19 @@ class Region:
|
|||
"""
|
||||
Adds locations to the Region object, where location_type is your Location class and locations is a dict of
|
||||
location names to address.
|
||||
|
||||
|
||||
:param locations: dictionary of locations to be created and added to this Region `{name: ID}`
|
||||
:param location_type: Location class to be used to create the locations with"""
|
||||
if location_type is None:
|
||||
location_type = Location
|
||||
for location, address in locations.items():
|
||||
self.locations.append(location_type(self.player, location, address, self))
|
||||
|
||||
|
||||
def connect(self, connecting_region: Region, name: Optional[str] = None,
|
||||
rule: Optional[Callable[[CollectionState], bool]] = None) -> None:
|
||||
"""
|
||||
Connects this Region to another Region, placing the provided rule on the connection.
|
||||
|
||||
|
||||
:param connecting_region: Region object to connect to path is `self -> exiting_region`
|
||||
:param name: name of the connection being created
|
||||
:param rule: callable to determine access of this connection to go from self to the exiting_region"""
|
||||
|
@ -883,11 +874,11 @@ class Region:
|
|||
if rule:
|
||||
exit_.access_rule = rule
|
||||
exit_.connect(connecting_region)
|
||||
|
||||
|
||||
def create_exit(self, name: str) -> Entrance:
|
||||
"""
|
||||
Creates and returns an Entrance object as an exit of this region.
|
||||
|
||||
|
||||
:param name: name of the Entrance being created
|
||||
"""
|
||||
exit_ = self.entrance_type(self.player, name, self)
|
||||
|
@ -1257,7 +1248,7 @@ class Spoiler:
|
|||
|
||||
def to_file(self, filename: str) -> None:
|
||||
def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
|
||||
res = getattr(self.multiworld, option_key)[player]
|
||||
res = getattr(self.multiworld.worlds[player].options, option_key)
|
||||
display_name = getattr(option_obj, "display_name", option_key)
|
||||
outfile.write(f"{display_name + ':':33}{res.current_option_name}\n")
|
||||
|
||||
|
@ -1275,8 +1266,7 @@ class Spoiler:
|
|||
outfile.write('\nPlayer %d: %s\n' % (player, self.multiworld.get_player_name(player)))
|
||||
outfile.write('Game: %s\n' % self.multiworld.game[player])
|
||||
|
||||
options = ChainMap(Options.per_game_common_options, self.multiworld.worlds[player].option_definitions)
|
||||
for f_option, option in options.items():
|
||||
for f_option, option in self.multiworld.worlds[player].options_dataclass.type_hints.items():
|
||||
write_option(f_option, option)
|
||||
|
||||
AutoWorld.call_single(self.multiworld, "write_spoiler_header", player, outfile)
|
||||
|
|
12
Fill.py
12
Fill.py
|
@ -5,6 +5,8 @@ import typing
|
|||
from collections import Counter, deque
|
||||
|
||||
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld
|
||||
from Options import Accessibility
|
||||
|
||||
from worlds.AutoWorld import call_all
|
||||
from worlds.generic.Rules import add_item_rule
|
||||
|
||||
|
@ -70,7 +72,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
|
|||
spot_to_fill: typing.Optional[Location] = None
|
||||
|
||||
# if minimal accessibility, only check whether location is reachable if game not beatable
|
||||
if world.accessibility[item_to_place.player] == 'minimal':
|
||||
if world.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal:
|
||||
perform_access_check = not world.has_beaten_game(maximum_exploration_state,
|
||||
item_to_place.player) \
|
||||
if single_player_placement else not has_beaten_game
|
||||
|
@ -265,7 +267,7 @@ def fast_fill(world: MultiWorld,
|
|||
|
||||
def accessibility_corrections(world: MultiWorld, state: CollectionState, locations, pool=[]):
|
||||
maximum_exploration_state = sweep_from_pool(state, pool)
|
||||
minimal_players = {player for player in world.player_ids if world.accessibility[player] == "minimal"}
|
||||
minimal_players = {player for player in world.player_ids if world.worlds[player].options.accessibility == "minimal"}
|
||||
unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and
|
||||
not location.can_reach(maximum_exploration_state)]
|
||||
for location in unreachable_locations:
|
||||
|
@ -288,7 +290,7 @@ def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locat
|
|||
unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)]
|
||||
if unreachable_locations:
|
||||
def forbid_important_item_rule(item: Item):
|
||||
return not ((item.classification & 0b0011) and world.accessibility[item.player] != 'minimal')
|
||||
return not ((item.classification & 0b0011) and world.worlds[item.player].options.accessibility != 'minimal')
|
||||
|
||||
for location in unreachable_locations:
|
||||
add_item_rule(location, forbid_important_item_rule)
|
||||
|
@ -531,9 +533,9 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
|||
# If other players are below the threshold value, swap progression in this sphere into earlier spheres,
|
||||
# which gives more locations available by this sphere.
|
||||
balanceable_players: typing.Dict[int, float] = {
|
||||
player: world.progression_balancing[player] / 100
|
||||
player: world.worlds[player].options.progression_balancing / 100
|
||||
for player in world.player_ids
|
||||
if world.progression_balancing[player] > 0
|
||||
if world.worlds[player].options.progression_balancing > 0
|
||||
}
|
||||
if not balanceable_players:
|
||||
logging.info('Skipping multiworld progression balancing.')
|
||||
|
|
18
Generate.py
18
Generate.py
|
@ -157,7 +157,8 @@ def main(args=None, callback=ERmain):
|
|||
for yaml in weights_cache[path]:
|
||||
if category_name is None:
|
||||
for category in yaml:
|
||||
if category in AutoWorldRegister.world_types and key in Options.common_options:
|
||||
if category in AutoWorldRegister.world_types and \
|
||||
key in Options.CommonOptions.type_hints:
|
||||
yaml[category][key] = option
|
||||
elif category_name not in yaml:
|
||||
logging.warning(f"Meta: Category {category_name} is not present in {path}.")
|
||||
|
@ -340,7 +341,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
|
|||
return get_choice(option_key, category_dict)
|
||||
if game in AutoWorldRegister.world_types:
|
||||
game_world = AutoWorldRegister.world_types[game]
|
||||
options = ChainMap(game_world.option_definitions, Options.per_game_common_options)
|
||||
options = game_world.options_dataclass.type_hints
|
||||
if option_key in options:
|
||||
if options[option_key].supports_weighting:
|
||||
return get_choice(option_key, category_dict)
|
||||
|
@ -445,8 +446,8 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
|||
f"which is not enabled.")
|
||||
|
||||
ret = argparse.Namespace()
|
||||
for option_key in Options.per_game_common_options:
|
||||
if option_key in weights and option_key not in Options.common_options:
|
||||
for option_key in Options.PerGameCommonOptions.type_hints:
|
||||
if option_key in weights and option_key not in Options.CommonOptions.type_hints:
|
||||
raise Exception(f"Option {option_key} has to be in a game's section, not on its own.")
|
||||
|
||||
ret.game = get_choice("game", weights)
|
||||
|
@ -466,16 +467,11 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
|||
game_weights = weights[ret.game]
|
||||
|
||||
ret.name = get_choice('name', weights)
|
||||
for option_key, option in Options.common_options.items():
|
||||
for option_key, option in Options.CommonOptions.type_hints.items():
|
||||
setattr(ret, option_key, option.from_any(get_choice(option_key, weights, option.default)))
|
||||
|
||||
for option_key, option in world_type.option_definitions.items():
|
||||
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, option in Options.per_game_common_options.items():
|
||||
# skip setting this option if already set from common_options, defaulting to root option
|
||||
if option_key not in world_type.option_definitions and \
|
||||
(option_key not in Options.common_options or option_key in game_weights):
|
||||
handle_option(ret, game_weights, option_key, option, plando_options)
|
||||
if PlandoOptions.items in plando_options:
|
||||
ret.plando_items = game_weights.get("plando_items", [])
|
||||
if ret.game == "Minecraft" or ret.game == "Ocarina of Time":
|
||||
|
|
22
Main.py
22
Main.py
|
@ -108,7 +108,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
logger.info('')
|
||||
|
||||
for player in world.player_ids:
|
||||
for item_name, count in world.start_inventory[player].value.items():
|
||||
for item_name, count in world.worlds[player].options.start_inventory.value.items():
|
||||
for _ in range(count):
|
||||
world.push_precollected(world.create_item(item_name, player))
|
||||
|
||||
|
@ -130,15 +130,15 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
|
||||
for player in world.player_ids:
|
||||
# items can't be both local and non-local, prefer local
|
||||
world.non_local_items[player].value -= world.local_items[player].value
|
||||
world.non_local_items[player].value -= set(world.local_early_items[player])
|
||||
world.worlds[player].options.non_local_items.value -= world.worlds[player].options.local_items.value
|
||||
world.worlds[player].options.non_local_items.value -= set(world.local_early_items[player])
|
||||
|
||||
AutoWorld.call_all(world, "set_rules")
|
||||
|
||||
for player in world.player_ids:
|
||||
exclusion_rules(world, player, world.exclude_locations[player].value)
|
||||
world.priority_locations[player].value -= world.exclude_locations[player].value
|
||||
for location_name in world.priority_locations[player].value:
|
||||
exclusion_rules(world, player, world.worlds[player].options.exclude_locations.value)
|
||||
world.worlds[player].options.priority_locations.value -= world.worlds[player].options.exclude_locations.value
|
||||
for location_name in world.worlds[player].options.priority_locations.value:
|
||||
try:
|
||||
location = world.get_location(location_name, player)
|
||||
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
|
||||
|
@ -151,8 +151,8 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
if world.players > 1:
|
||||
locality_rules(world)
|
||||
else:
|
||||
world.non_local_items[1].value = set()
|
||||
world.local_items[1].value = set()
|
||||
world.worlds[1].options.non_local_items.value = set()
|
||||
world.worlds[1].options.local_items.value = set()
|
||||
|
||||
AutoWorld.call_all(world, "generate_basic")
|
||||
|
||||
|
@ -360,11 +360,11 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
f" {location}"
|
||||
locations_data[location.player][location.address] = \
|
||||
location.item.code, location.item.player, location.item.flags
|
||||
if location.name in world.start_location_hints[location.player]:
|
||||
if location.name in world.worlds[location.player].options.start_location_hints:
|
||||
precollect_hint(location)
|
||||
elif location.item.name in world.start_hints[location.item.player]:
|
||||
elif location.item.name in world.worlds[location.item.player].options.start_hints:
|
||||
precollect_hint(location)
|
||||
elif any([location.item.name in world.start_hints[player]
|
||||
elif any([location.item.name in world.worlds[player].options.start_hints
|
||||
for player in world.groups.get(location.item.player, {}).get("players", [])]):
|
||||
precollect_hint(location)
|
||||
|
||||
|
|
88
Options.py
88
Options.py
|
@ -2,6 +2,9 @@ from __future__ import annotations
|
|||
|
||||
import abc
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
import functools
|
||||
import math
|
||||
import numbers
|
||||
import random
|
||||
|
@ -211,6 +214,12 @@ class NumericOption(Option[int], numbers.Integral, abc.ABC):
|
|||
else:
|
||||
return self.value > other
|
||||
|
||||
def __ge__(self, other: typing.Union[int, NumericOption]) -> bool:
|
||||
if isinstance(other, NumericOption):
|
||||
return self.value >= other.value
|
||||
else:
|
||||
return self.value >= other
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.value)
|
||||
|
||||
|
@ -896,10 +905,55 @@ class ProgressionBalancing(SpecialRange):
|
|||
}
|
||||
|
||||
|
||||
common_options = {
|
||||
"progression_balancing": ProgressionBalancing,
|
||||
"accessibility": Accessibility
|
||||
}
|
||||
class OptionsMetaProperty(type):
|
||||
def __new__(mcs,
|
||||
name: str,
|
||||
bases: typing.Tuple[type, ...],
|
||||
attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty":
|
||||
for attr_type in attrs.values():
|
||||
assert not isinstance(attr_type, AssembleOptions),\
|
||||
f"Options for {name} should be type hinted on the class, not assigned"
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
@property
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def type_hints(cls) -> typing.Dict[str, typing.Type[Option[typing.Any]]]:
|
||||
"""Returns type hints of the class as a dictionary."""
|
||||
return typing.get_type_hints(cls)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommonOptions(metaclass=OptionsMetaProperty):
|
||||
progression_balancing: ProgressionBalancing
|
||||
accessibility: Accessibility
|
||||
|
||||
def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Returns a dictionary of [str, Option.value]
|
||||
|
||||
:param option_names: names of the options to return
|
||||
:param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab`
|
||||
"""
|
||||
option_results = {}
|
||||
for option_name in option_names:
|
||||
if option_name in type(self).type_hints:
|
||||
if casing == "snake":
|
||||
display_name = option_name
|
||||
elif casing == "camel":
|
||||
split_name = [name.title() for name in option_name.split("_")]
|
||||
split_name[0] = split_name[0].lower()
|
||||
display_name = "".join(split_name)
|
||||
elif casing == "pascal":
|
||||
display_name = "".join([name.title() for name in option_name.split("_")])
|
||||
elif casing == "kebab":
|
||||
display_name = option_name.replace("_", "-")
|
||||
else:
|
||||
raise ValueError(f"{casing} is invalid casing for as_dict. "
|
||||
"Valid names are 'snake', 'camel', 'pascal', 'kebab'.")
|
||||
option_results[display_name] = getattr(self, option_name).value
|
||||
else:
|
||||
raise ValueError(f"{option_name} not found in {tuple(type(self).type_hints)}")
|
||||
return option_results
|
||||
|
||||
|
||||
class LocalItems(ItemSet):
|
||||
|
@ -1020,17 +1074,16 @@ class ItemLinks(OptionList):
|
|||
link.setdefault("link_replacement", None)
|
||||
|
||||
|
||||
per_game_common_options = {
|
||||
**common_options, # can be overwritten per-game
|
||||
"local_items": LocalItems,
|
||||
"non_local_items": NonLocalItems,
|
||||
"start_inventory": StartInventory,
|
||||
"start_hints": StartHints,
|
||||
"start_location_hints": StartLocationHints,
|
||||
"exclude_locations": ExcludeLocations,
|
||||
"priority_locations": PriorityLocations,
|
||||
"item_links": ItemLinks
|
||||
}
|
||||
@dataclass
|
||||
class PerGameCommonOptions(CommonOptions):
|
||||
local_items: LocalItems
|
||||
non_local_items: NonLocalItems
|
||||
start_inventory: StartInventory
|
||||
start_hints: StartHints
|
||||
start_location_hints: StartLocationHints
|
||||
exclude_locations: ExcludeLocations
|
||||
priority_locations: PriorityLocations
|
||||
item_links: ItemLinks
|
||||
|
||||
|
||||
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True):
|
||||
|
@ -1071,10 +1124,7 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
|||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
if not world.hidden or generate_hidden:
|
||||
all_options: typing.Dict[str, AssembleOptions] = {
|
||||
**per_game_common_options,
|
||||
**world.option_definitions
|
||||
}
|
||||
all_options: typing.Dict[str, AssembleOptions] = world.options_dataclass.type_hints
|
||||
|
||||
with open(local_path("data", "options.yaml")) as f:
|
||||
file_data = f.read()
|
||||
|
|
|
@ -36,10 +36,7 @@ def create():
|
|||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
|
||||
all_options: typing.Dict[str, Options.AssembleOptions] = {
|
||||
**Options.per_game_common_options,
|
||||
**world.option_definitions
|
||||
}
|
||||
all_options: typing.Dict[str, Options.AssembleOptions] = world.options_dataclass.type_hints
|
||||
|
||||
# Generate JSON files for player-settings pages
|
||||
player_settings = {
|
||||
|
|
|
@ -28,19 +28,23 @@ Choice, and defining `alias_true = option_full`.
|
|||
and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`.
|
||||
|
||||
As an example, suppose we want an option that lets the user start their game with a sword in their inventory. Let's
|
||||
create our option class (with a docstring), give it a `display_name`, and add it to a dictionary that keeps track of our
|
||||
options:
|
||||
create our option class (with a docstring), give it a `display_name`, and add it to our game's options dataclass:
|
||||
|
||||
```python
|
||||
# Options.py
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import Toggle, PerGameCommonOptions
|
||||
|
||||
|
||||
class StartingSword(Toggle):
|
||||
"""Adds a sword to your starting inventory."""
|
||||
display_name = "Start With Sword"
|
||||
|
||||
|
||||
example_options = {
|
||||
"starting_sword": StartingSword
|
||||
}
|
||||
@dataclass
|
||||
class ExampleGameOptions(PerGameCommonOptions):
|
||||
starting_sword: StartingSword
|
||||
```
|
||||
|
||||
This will create a `Toggle` option, internally called `starting_sword`. To then submit this to the multiworld, we add it
|
||||
|
@ -48,27 +52,30 @@ to our world's `__init__.py`:
|
|||
|
||||
```python
|
||||
from worlds.AutoWorld import World
|
||||
from .Options import options
|
||||
from .Options import ExampleGameOptions
|
||||
|
||||
|
||||
class ExampleWorld(World):
|
||||
option_definitions = options
|
||||
# this gives the generator all the definitions for our options
|
||||
options_dataclass = ExampleGameOptions
|
||||
# this gives us typing hints for all the options we defined
|
||||
options: ExampleGameOptions
|
||||
```
|
||||
|
||||
### Option Checking
|
||||
Options are parsed by `Generate.py` before the worlds are created, and then the option classes are created shortly after
|
||||
world instantiation. These are created as attributes on the MultiWorld and can be accessed with
|
||||
`self.multiworld.my_option_name[self.player]`. This is the option class, which supports direct comparison methods to
|
||||
`self.options.my_option_name`. This is an instance of the option class, which supports direct comparison methods to
|
||||
relevant objects (like comparing a Toggle class to a `bool`). If you need to access the option result directly, this is
|
||||
the option class's `value` attribute. For our example above we can do a simple check:
|
||||
```python
|
||||
if self.multiworld.starting_sword[self.player]:
|
||||
if self.options.starting_sword:
|
||||
do_some_things()
|
||||
```
|
||||
|
||||
or if I need a boolean object, such as in my slot_data I can access it as:
|
||||
```python
|
||||
start_with_sword = bool(self.multiworld.starting_sword[self.player].value)
|
||||
start_with_sword = bool(self.options.starting_sword.value)
|
||||
```
|
||||
|
||||
## Generic Option Classes
|
||||
|
@ -120,7 +127,7 @@ Like Toggle, but 1 (true) is the default value.
|
|||
A numeric option allowing you to define different sub options. Values are stored as integers, but you can also do
|
||||
comparison methods with the class and strings, so if you have an `option_early_sword`, this can be compared with:
|
||||
```python
|
||||
if self.multiworld.sword_availability[self.player] == "early_sword":
|
||||
if self.options.sword_availability == "early_sword":
|
||||
do_early_sword_things()
|
||||
```
|
||||
|
||||
|
@ -128,7 +135,7 @@ or:
|
|||
```python
|
||||
from .Options import SwordAvailability
|
||||
|
||||
if self.multiworld.sword_availability[self.player] == SwordAvailability.option_early_sword:
|
||||
if self.options.sword_availability == SwordAvailability.option_early_sword:
|
||||
do_early_sword_things()
|
||||
```
|
||||
|
||||
|
@ -160,7 +167,7 @@ within the world.
|
|||
Like choice allows you to predetermine options and has all of the same comparison methods and handling. Also accepts any
|
||||
user defined string as a valid option, so will either need to be validated by adding a validation step to the option
|
||||
class or within world, if necessary. Value for this class is `Union[str, int]` so if you need the value at a specified
|
||||
point, `self.multiworld.my_option[self.player].current_key` will always return a string.
|
||||
point, `self.options.my_option.current_key` will always return a string.
|
||||
|
||||
### PlandoBosses
|
||||
An option specifically built for handling boss rando, if your game can use it. Is a subclass of TextChoice so supports
|
||||
|
|
|
@ -86,9 +86,11 @@ inside a `World` object.
|
|||
### Player Options
|
||||
|
||||
Players provide customized settings for their World in the form of yamls.
|
||||
Those are accessible through `self.multiworld.<option_name>[self.player]`. A dict
|
||||
of valid options has to be provided in `self.option_definitions`. Options are automatically
|
||||
added to the `World` object for easy access.
|
||||
A `dataclass` of valid options definitions has to be provided in `self.options_dataclass`.
|
||||
(It must be a subclass of `PerGameCommonOptions`.)
|
||||
Option results are automatically added to the `World` object for easy access.
|
||||
Those are accessible through `self.options.<option_name>`, and you can get a dictionary of the option values via
|
||||
`self.options.as_dict(<option_names>)`, passing the desired options as strings.
|
||||
|
||||
### World Settings
|
||||
|
||||
|
@ -221,11 +223,11 @@ See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requireme
|
|||
AP will only import the `__init__.py`. Depending on code size it makes sense to
|
||||
use multiple files and use relative imports to access them.
|
||||
|
||||
e.g. `from .Options import mygame_options` from your `__init__.py` will load
|
||||
`worlds/<world_name>/Options.py` and make its `mygame_options` accessible.
|
||||
e.g. `from .Options import MyGameOptions` from your `__init__.py` will load
|
||||
`world/[world_name]/Options.py` and make its `MyGameOptions` accessible.
|
||||
|
||||
When imported names pile up it may be easier to use `from . import Options`
|
||||
and access the variable as `Options.mygame_options`.
|
||||
and access the variable as `Options.MyGameOptions`.
|
||||
|
||||
Imports from directories outside your world should use absolute imports.
|
||||
Correct use of relative / absolute imports is required for zipped worlds to
|
||||
|
@ -273,8 +275,9 @@ Each option has its own class, inherits from a base option type, has a docstring
|
|||
to describe it and a `display_name` property for display on the website and in
|
||||
spoiler logs.
|
||||
|
||||
The actual name as used in the yaml is defined in a `Dict[str, AssembleOptions]`, that is
|
||||
assigned to the world under `self.option_definitions`.
|
||||
The actual name as used in the yaml is defined via the field names of a `dataclass` that is
|
||||
assigned to the world under `self.options_dataclass`. By convention, the strings
|
||||
that define your option names should be in `snake_case`.
|
||||
|
||||
Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, `Range`.
|
||||
For more see `Options.py` in AP's base directory.
|
||||
|
@ -309,8 +312,8 @@ default = 0
|
|||
```python
|
||||
# Options.py
|
||||
|
||||
from Options import Toggle, Range, Choice, Option
|
||||
import typing
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, Range, Choice, PerGameCommonOptions
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""Sets overall game difficulty."""
|
||||
|
@ -333,23 +336,27 @@ class FixXYZGlitch(Toggle):
|
|||
"""Fixes ABC when you do XYZ"""
|
||||
display_name = "Fix XYZ Glitch"
|
||||
|
||||
# By convention we call the options dict variable `<world>_options`.
|
||||
mygame_options: typing.Dict[str, AssembleOptions] = {
|
||||
"difficulty": Difficulty,
|
||||
"final_boss_hp": FinalBossHP,
|
||||
"fix_xyz_glitch": FixXYZGlitch,
|
||||
}
|
||||
# By convention, we call the options dataclass `<world>Options`.
|
||||
# It has to be derived from 'PerGameCommonOptions'.
|
||||
@dataclass
|
||||
class MyGameOptions(PerGameCommonOptions):
|
||||
difficulty: Difficulty
|
||||
final_boss_hp: FinalBossHP
|
||||
fix_xyz_glitch: FixXYZGlitch
|
||||
```
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
from .Options import mygame_options # import the options dict
|
||||
from .Options import MyGameOptions # import the options dataclass
|
||||
|
||||
|
||||
class MyGameWorld(World):
|
||||
#...
|
||||
option_definitions = mygame_options # assign the options dict to the world
|
||||
#...
|
||||
# ...
|
||||
options_dataclass = MyGameOptions # assign the options dataclass to the world
|
||||
options: MyGameOptions # typing for option results
|
||||
# ...
|
||||
```
|
||||
|
||||
### A World Class Skeleton
|
||||
|
@ -359,13 +366,14 @@ class MyGameWorld(World):
|
|||
|
||||
import settings
|
||||
import typing
|
||||
from .Options import mygame_options # the options we defined earlier
|
||||
from .Options import MyGameOptions # the options we defined earlier
|
||||
from .Items import mygame_items # data used below to add items to the World
|
||||
from .Locations import mygame_locations # same as above
|
||||
from worlds.AutoWorld import World
|
||||
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
|
||||
|
||||
|
||||
|
||||
class MyGameItem(Item): # or from Items import MyGameItem
|
||||
game = "My Game" # name of the game/world this item is from
|
||||
|
||||
|
@ -374,6 +382,7 @@ class MyGameLocation(Location): # or from Locations import MyGameLocation
|
|||
game = "My Game" # name of the game/world this location is in
|
||||
|
||||
|
||||
|
||||
class MyGameSettings(settings.Group):
|
||||
class RomFile(settings.SNESRomPath):
|
||||
"""Insert help text for host.yaml here."""
|
||||
|
@ -384,7 +393,8 @@ class MyGameSettings(settings.Group):
|
|||
class MyGameWorld(World):
|
||||
"""Insert description of the world/game here."""
|
||||
game = "My Game" # name of the game/world
|
||||
option_definitions = mygame_options # options the player can set
|
||||
options_dataclass = MyGameOptions # options the player can set
|
||||
options: MyGameOptions # typing hints for option results
|
||||
settings: typing.ClassVar[MyGameSettings] # will be automatically assigned from type hint
|
||||
topology_present = True # show path to required location checks in spoiler
|
||||
|
||||
|
@ -460,7 +470,7 @@ In addition, the following methods can be implemented and are called in this ord
|
|||
```python
|
||||
def generate_early(self) -> None:
|
||||
# read player settings to world instance
|
||||
self.final_boss_hp = self.multiworld.final_boss_hp[self.player].value
|
||||
self.final_boss_hp = self.options.final_boss_hp.value
|
||||
```
|
||||
|
||||
#### create_item
|
||||
|
@ -687,9 +697,9 @@ def generate_output(self, output_directory: str):
|
|||
in self.multiworld.precollected_items[self.player]],
|
||||
"final_boss_hp": self.final_boss_hp,
|
||||
# store option name "easy", "normal" or "hard" for difficuly
|
||||
"difficulty": self.multiworld.difficulty[self.player].current_key,
|
||||
"difficulty": self.options.difficulty.current_key,
|
||||
# store option value True or False for fixing a glitch
|
||||
"fix_xyz_glitch": self.multiworld.fix_xyz_glitch[self.player].value,
|
||||
"fix_xyz_glitch": self.options.fix_xyz_glitch.value,
|
||||
}
|
||||
# point to a ROM specified by the installation
|
||||
src = self.settings.rom_file
|
||||
|
@ -702,6 +712,26 @@ def generate_output(self, output_directory: str):
|
|||
generate_mod(src, out_file, data)
|
||||
```
|
||||
|
||||
### Slot Data
|
||||
|
||||
If the game client needs to know information about the generated seed, a preferred method of transferring the data
|
||||
is through the slot data. This can be filled from the `fill_slot_data` method of your world by returning a `Dict[str, Any]`,
|
||||
but should be limited to data that is absolutely necessary to not waste resources. Slot data is sent to your client once
|
||||
it has successfully [connected](network%20protocol.md#connected).
|
||||
If you need to know information about locations in your world, instead
|
||||
of propagating the slot data, it is preferable to use [LocationScouts](network%20protocol.md#locationscouts) since that
|
||||
data already exists on the server. The most common usage of slot data is to send option results that the client needs
|
||||
to be aware of.
|
||||
|
||||
```python
|
||||
def fill_slot_data(self):
|
||||
# in order for our game client to handle the generated seed correctly we need to know what the user selected
|
||||
# for their difficulty and final boss HP
|
||||
# a dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting
|
||||
# the options dataclass has a method to return a `Dict[str, Any]` of each option name provided and the option's value
|
||||
return self.options.as_dict("difficulty", "final_boss_hp")
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Each world implementation should have a tutorial and a game info page. These are both rendered on the website by reading
|
||||
|
|
|
@ -125,13 +125,13 @@ class WorldTestBase(unittest.TestCase):
|
|||
self.multiworld.game[1] = self.game
|
||||
self.multiworld.player_name = {1: "Tester"}
|
||||
self.multiworld.set_seed(seed)
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].option_definitions.items():
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items():
|
||||
setattr(args, name, {
|
||||
1: option.from_any(self.options.get(name, getattr(option, "default")))
|
||||
})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
for step in gen_steps:
|
||||
call_all(self.multiworld, step)
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
from typing import List, Iterable
|
||||
import unittest
|
||||
|
||||
import Options
|
||||
from Options import Accessibility
|
||||
from worlds.AutoWorld import World
|
||||
from Fill import FillError, balance_multiworld_progression, fill_restrictive, \
|
||||
distribute_early_items, distribute_items_restrictive
|
||||
from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, Item, Location, \
|
||||
ItemClassification
|
||||
ItemClassification, CollectionState
|
||||
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
|
||||
|
||||
|
||||
def generate_multi_world(players: int = 1) -> MultiWorld:
|
||||
multi_world = MultiWorld(players)
|
||||
multi_world.player_name = {}
|
||||
multi_world.state = CollectionState(multi_world)
|
||||
for i in range(players):
|
||||
player_id = i+1
|
||||
world = World(multi_world, player_id)
|
||||
|
@ -19,9 +23,16 @@ def generate_multi_world(players: int = 1) -> MultiWorld:
|
|||
multi_world.player_name[player_id] = "Test Player " + str(player_id)
|
||||
region = Region("Menu", player_id, multi_world, "Menu Region Hint")
|
||||
multi_world.regions.append(region)
|
||||
for option_key, option in Options.PerGameCommonOptions.type_hints.items():
|
||||
if hasattr(multi_world, option_key):
|
||||
getattr(multi_world, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
|
||||
else:
|
||||
setattr(multi_world, option_key, {player_id: option.from_any(getattr(option, "default"))})
|
||||
# TODO - remove this loop once all worlds use options dataclasses
|
||||
world.options = world.options_dataclass(**{option_key: getattr(multi_world, option_key)[player_id]
|
||||
for option_key in world.options_dataclass.type_hints})
|
||||
|
||||
multi_world.set_seed(0)
|
||||
multi_world.set_default_common_options()
|
||||
|
||||
return multi_world
|
||||
|
||||
|
@ -186,7 +197,7 @@ class TestFillRestrictive(unittest.TestCase):
|
|||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
|
||||
multi_world.accessibility[player1.id].value = multi_world.accessibility[player1.id].option_minimal
|
||||
multi_world.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal)
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
items[1].name, player1.id)
|
||||
set_rule(locations[1], lambda state: state.has(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from argparse import Namespace
|
||||
from typing import Dict, Optional, Callable
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, Region
|
||||
|
@ -13,7 +14,6 @@ class TestHelpers(unittest.TestCase):
|
|||
self.multiworld.game[self.player] = "helper_test_game"
|
||||
self.multiworld.player_name = {1: "Tester"}
|
||||
self.multiworld.set_seed()
|
||||
self.multiworld.set_default_common_options()
|
||||
|
||||
def testRegionHelpers(self) -> None:
|
||||
regions: Dict[str, str] = {
|
||||
|
|
|
@ -6,6 +6,6 @@ class TestOptions(unittest.TestCase):
|
|||
def testOptionsHaveDocString(self):
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
if not world_type.hidden:
|
||||
for option_key, option in world_type.option_definitions.items():
|
||||
for option_key, option in world_type.options_dataclass.type_hints.items():
|
||||
with self.subTest(game=gamename, option=option_key):
|
||||
self.assertTrue(option.__doc__)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from argparse import Namespace
|
||||
from typing import Type, Tuple
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from worlds.AutoWorld import call_all, World
|
||||
|
||||
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
|
||||
|
@ -12,11 +12,11 @@ def setup_solo_multiworld(world_type: Type[World], steps: Tuple[str, ...] = gen_
|
|||
multiworld.game[1] = world_type.game
|
||||
multiworld.player_name = {1: "Tester"}
|
||||
multiworld.set_seed()
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
args = Namespace()
|
||||
for name, option in world_type.option_definitions.items():
|
||||
for name, option in world_type.options_dataclass.type_hints.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
multiworld.set_options(args)
|
||||
multiworld.set_default_common_options()
|
||||
for step in steps:
|
||||
call_all(multiworld, step)
|
||||
return multiworld
|
||||
|
|
|
@ -4,11 +4,12 @@ import hashlib
|
|||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Any, Callable, ClassVar, Dict, FrozenSet, List, Optional, Set, TYPE_CHECKING, TextIO, Tuple, Type, \
|
||||
from dataclasses import make_dataclass
|
||||
from typing import Any, Callable, ClassVar, Dict, Set, Tuple, FrozenSet, List, Optional, TYPE_CHECKING, TextIO, Type, \
|
||||
Union
|
||||
|
||||
from Options import PerGameCommonOptions
|
||||
from BaseClasses import CollectionState
|
||||
from Options import AssembleOptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import random
|
||||
|
@ -63,6 +64,12 @@ class AutoWorldRegister(type):
|
|||
dct["required_client_version"] = max(dct["required_client_version"],
|
||||
base.__dict__["required_client_version"])
|
||||
|
||||
# create missing options_dataclass from legacy option_definitions
|
||||
# TODO - remove this once all worlds use options dataclasses
|
||||
if "options_dataclass" not in dct and "option_definitions" in dct:
|
||||
dct["options_dataclass"] = make_dataclass(f"{name}Options", dct["option_definitions"].items(),
|
||||
bases=(PerGameCommonOptions,))
|
||||
|
||||
# construct class
|
||||
new_class = super().__new__(mcs, name, bases, dct)
|
||||
if "game" in dct:
|
||||
|
@ -163,8 +170,11 @@ class World(metaclass=AutoWorldRegister):
|
|||
"""A World object encompasses a game's Items, Locations, Rules and additional data or functionality required.
|
||||
A Game should have its own subclass of World in which it defines the required data structures."""
|
||||
|
||||
option_definitions: ClassVar[Dict[str, AssembleOptions]] = {}
|
||||
options_dataclass: ClassVar[Type[PerGameCommonOptions]] = PerGameCommonOptions
|
||||
"""link your Options mapping"""
|
||||
options: PerGameCommonOptions
|
||||
"""resulting options for the player of this world"""
|
||||
|
||||
game: ClassVar[str]
|
||||
"""name the game"""
|
||||
topology_present: ClassVar[bool] = False
|
||||
|
@ -362,16 +372,14 @@ class World(metaclass=AutoWorldRegister):
|
|||
def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World:
|
||||
"""Creates a group, which is an instance of World that is responsible for multiple others.
|
||||
An example case is ItemLinks creating these."""
|
||||
import Options
|
||||
# TODO remove loop when worlds use options dataclass
|
||||
for option_key, option in cls.options_dataclass.type_hints.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
group = cls(multiworld, new_player_id)
|
||||
group.options = cls.options_dataclass(**{option_key: option(option.default)
|
||||
for option_key, option in cls.options_dataclass.type_hints.items()})
|
||||
|
||||
for option_key, option in cls.option_definitions.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
for option_key, option in Options.common_options.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
for option_key, option in Options.per_game_common_options.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
|
||||
return cls(multiworld, new_player_id)
|
||||
return group
|
||||
|
||||
# decent place to implement progressive items, in most cases can stay as-is
|
||||
def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]:
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import unittest
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from worlds import AutoWorldRegister
|
||||
|
||||
|
||||
class LTTPTestBase(unittest.TestCase):
|
||||
def world_setup(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorldRegister.world_types["A Link to the Past"].options_dataclass.type_hints.items():
|
||||
setattr(args, name, {1: option.from_any(getattr(option, "default"))})
|
||||
self.multiworld.set_options(args)
|
|
@ -1,25 +1,16 @@
|
|||
import unittest
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, ItemClassification
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from BaseClasses import CollectionState, ItemClassification
|
||||
from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import mandatory_connections, connect_simple
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from worlds.alttp.Regions import create_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
|
||||
class TestDungeon(unittest.TestCase):
|
||||
class TestDungeon(LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.starting_regions = [] # Where to start exploring
|
||||
self.remove_exits = [] # Block dungeon exits
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
|
@ -10,17 +7,12 @@ from worlds.alttp.Regions import mark_light_world_regions
|
|||
from worlds.alttp.Shops import create_shops
|
||||
from test.TestBase import TestBase
|
||||
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
class TestInverted(TestBase):
|
||||
|
||||
class TestInverted(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
|
|
|
@ -1,27 +1,17 @@
|
|||
import unittest
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import create_dungeons
|
||||
from worlds.alttp.EntranceShuffle import connect_entrance, Inverted_LW_Entrances, Inverted_LW_Dungeon_Entrances, Inverted_LW_Single_Cave_Doors, Inverted_Old_Man_Entrances, Inverted_DW_Entrances, Inverted_DW_Dungeon_Entrances, Inverted_DW_Single_Cave_Doors, \
|
||||
Inverted_LW_Entrances_Must_Exit, Inverted_LW_Dungeon_Entrances_Must_Exit, Inverted_Bomb_Shop_Multi_Cave_Doors, Inverted_Bomb_Shop_Single_Cave_Doors, Blacksmith_Single_Cave_Doors, Inverted_Blacksmith_Multi_Cave_Doors
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Rules import set_inverted_big_bomb_rules
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
|
||||
class TestInvertedBombRules(unittest.TestCase):
|
||||
class TestInvertedBombRules(LTTPTestBase):
|
||||
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
self.world_setup()
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
args = Namespace
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_dungeons()
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from worlds.alttp.Regions import mark_light_world_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from worlds.alttp.Rules import set_rules
|
||||
from test.TestBase import TestBase
|
||||
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
class TestInvertedMinor(TestBase):
|
||||
|
||||
class TestInvertedMinor(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
self.multiworld.logic[1] = "minorglitches"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
|
|
|
@ -1,28 +1,18 @@
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from worlds.alttp.Regions import mark_light_world_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from worlds.alttp.Rules import set_rules
|
||||
from test.TestBase import TestBase
|
||||
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
|
||||
class TestInvertedOWG(TestBase):
|
||||
class TestInvertedOWG(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.multiworld.logic[1] = "owglitches"
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
|
|
|
@ -1,27 +1,15 @@
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_entrances
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from worlds.alttp.Regions import create_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from test.TestBase import TestBase
|
||||
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
|
||||
class TestMinor(TestBase):
|
||||
class TestMinor(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.multiworld.logic[1] = "minorglitches"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from test.TestBase import TestBase
|
||||
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
|
||||
class TestVanillaOWG(TestBase):
|
||||
class TestVanillaOWG(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.logic[1] = "owglitches"
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from test.TestBase import TestBase
|
||||
from worlds import AutoWorld
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
|
||||
class TestVanilla(TestBase):
|
||||
|
||||
class TestVanilla(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.world_setup()
|
||||
self.multiworld.logic[1] = "noglitches"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import csv
|
||||
import enum
|
||||
import math
|
||||
from typing import Protocol, Union, Dict, List, Set
|
||||
from BaseClasses import Item, ItemClassification
|
||||
from . import Options, data
|
||||
from dataclasses import dataclass, field
|
||||
from random import Random
|
||||
from typing import Dict, List, Set
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
from . import Options, data
|
||||
|
||||
|
||||
class DLCQuestItem(Item):
|
||||
|
@ -93,38 +94,35 @@ def create_trap_items(world, World_Options: Options.DLCQuestOptions, trap_needed
|
|||
|
||||
def create_items(world, World_Options: Options.DLCQuestOptions, locations_count: int, random: Random):
|
||||
created_items = []
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_basic or World_Options[
|
||||
Options.Campaign] == Options.Campaign.option_both:
|
||||
if World_Options.campaign == Options.Campaign.option_basic or World_Options.campaign == Options.Campaign.option_both:
|
||||
for item in items_by_group[Group.DLCQuest]:
|
||||
if item.has_any_group(Group.DLC):
|
||||
created_items.append(world.create_item(item))
|
||||
if item.has_any_group(Group.Item) and World_Options[
|
||||
Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if item.has_any_group(Group.Item) and World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
created_items.append(world.create_item(item))
|
||||
if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(825 / World_Options[Options.CoinSanityRange])
|
||||
if World_Options.coinsanity == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(825 / World_Options.coinbundlequantity)
|
||||
for item in items_by_group[Group.DLCQuest]:
|
||||
if item.has_any_group(Group.Coin):
|
||||
for i in range(coin_bundle_needed):
|
||||
created_items.append(world.create_item(item))
|
||||
if 825 % World_Options[Options.CoinSanityRange] != 0:
|
||||
if 825 % World_Options.coinbundlequantity != 0:
|
||||
created_items.append(world.create_item(item))
|
||||
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die or World_Options[
|
||||
Options.Campaign] == Options.Campaign.option_both:
|
||||
if (World_Options.campaign == Options.Campaign.option_live_freemium_or_die or
|
||||
World_Options.campaign == Options.Campaign.option_both):
|
||||
for item in items_by_group[Group.Freemium]:
|
||||
if item.has_any_group(Group.DLC):
|
||||
created_items.append(world.create_item(item))
|
||||
if item.has_any_group(Group.Item) and World_Options[
|
||||
Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if item.has_any_group(Group.Item) and World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
created_items.append(world.create_item(item))
|
||||
if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(889 / World_Options[Options.CoinSanityRange])
|
||||
if World_Options.coinsanity == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(889 / World_Options.coinbundlequantity)
|
||||
for item in items_by_group[Group.Freemium]:
|
||||
if item.has_any_group(Group.Coin):
|
||||
for i in range(coin_bundle_needed):
|
||||
created_items.append(world.create_item(item))
|
||||
if 889 % World_Options[Options.CoinSanityRange] != 0:
|
||||
if 889 % World_Options.coinbundlequantity != 0:
|
||||
created_items.append(world.create_item(item))
|
||||
|
||||
trap_items = create_trap_items(world, World_Options, locations_count - len(created_items), random)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from BaseClasses import Location, MultiWorld
|
||||
from . import Options
|
||||
from BaseClasses import Location
|
||||
|
||||
|
||||
class DLCQuestLocation(Location):
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
from typing import Union, Dict, runtime_checkable, Protocol
|
||||
from Options import Option, DeathLink, Choice, Toggle, SpecialRange
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class DLCQuestOption(Protocol):
|
||||
internal_name: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class DLCQuestOptions:
|
||||
options: Dict[str, Union[bool, int]]
|
||||
|
||||
def __getitem__(self, item: Union[str, DLCQuestOption]) -> Union[bool, int]:
|
||||
if isinstance(item, DLCQuestOption):
|
||||
item = item.internal_name
|
||||
|
||||
return self.options.get(item, None)
|
||||
from Options import Choice, DeathLink, PerGameCommonOptions, SpecialRange
|
||||
|
||||
|
||||
class DoubleJumpGlitch(Choice):
|
||||
|
@ -94,31 +78,13 @@ class ItemShuffle(Choice):
|
|||
default = 0
|
||||
|
||||
|
||||
DLCQuest_options: Dict[str, type(Option)] = {
|
||||
option.internal_name: option
|
||||
for option in [
|
||||
DoubleJumpGlitch,
|
||||
CoinSanity,
|
||||
CoinSanityRange,
|
||||
TimeIsMoney,
|
||||
EndingChoice,
|
||||
Campaign,
|
||||
ItemShuffle,
|
||||
]
|
||||
}
|
||||
default_options = {option.internal_name: option.default for option in DLCQuest_options.values()}
|
||||
DLCQuest_options["death_link"] = DeathLink
|
||||
|
||||
|
||||
def fetch_options(world, player: int) -> DLCQuestOptions:
|
||||
return DLCQuestOptions({option: get_option_value(world, player, option) for option in DLCQuest_options})
|
||||
|
||||
|
||||
def get_option_value(world, player: int, name: str) -> Union[bool, int]:
|
||||
assert name in DLCQuest_options, f"{name} is not a valid option for DLC Quest."
|
||||
|
||||
value = getattr(world, name)
|
||||
|
||||
if issubclass(DLCQuest_options[name], Toggle):
|
||||
return bool(value[player].value)
|
||||
return value[player].value
|
||||
@dataclass
|
||||
class DLCQuestOptions(PerGameCommonOptions):
|
||||
double_jump_glitch: DoubleJumpGlitch
|
||||
coinsanity: CoinSanity
|
||||
coinbundlequantity: CoinSanityRange
|
||||
time_is_money: TimeIsMoney
|
||||
ending_choice: EndingChoice
|
||||
campaign: Campaign
|
||||
item_shuffle: ItemShuffle
|
||||
death_link: DeathLink
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import math
|
||||
from BaseClasses import MultiWorld, Region, Location, Entrance, ItemClassification
|
||||
|
||||
from BaseClasses import Entrance, MultiWorld, Region
|
||||
from . import Options
|
||||
from .Locations import DLCQuestLocation, location_table
|
||||
from .Rules import create_event
|
||||
from . import Options
|
||||
|
||||
DLCQuestRegion = ["Movement Pack", "Behind Tree", "Psychological Warfare", "Double Jump Left",
|
||||
"Double Jump Behind the Tree", "The Forest", "Final Room"]
|
||||
|
@ -26,16 +27,16 @@ def add_coin_dlcquest(region: Region, Coin: int, player: int):
|
|||
|
||||
def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQuestOptions):
|
||||
Regmenu = Region("Menu", player, world)
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_basic or World_Options[
|
||||
Options.Campaign] == Options.Campaign.option_both:
|
||||
if (World_Options.campaign == Options.Campaign.option_basic or World_Options.campaign
|
||||
== Options.Campaign.option_both):
|
||||
Regmenu.exits += [Entrance(player, "DLC Quest Basic", Regmenu)]
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die or World_Options[
|
||||
Options.Campaign] == Options.Campaign.option_both:
|
||||
if (World_Options.campaign == Options.Campaign.option_live_freemium_or_die or World_Options.campaign
|
||||
== Options.Campaign.option_both):
|
||||
Regmenu.exits += [Entrance(player, "Live Freemium or Die", Regmenu)]
|
||||
world.regions.append(Regmenu)
|
||||
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_basic or World_Options[
|
||||
Options.Campaign] == Options.Campaign.option_both:
|
||||
if (World_Options.campaign == Options.Campaign.option_basic or World_Options.campaign
|
||||
== Options.Campaign.option_both):
|
||||
|
||||
Regmoveright = Region("Move Right", player, world, "Start of the basic game")
|
||||
Locmoveright_name = ["Movement Pack", "Animation Pack", "Audio Pack", "Pause Menu Pack"]
|
||||
|
@ -43,13 +44,13 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
Regmoveright.locations += [DLCQuestLocation(player, loc_name, location_table[loc_name], Regmoveright) for
|
||||
loc_name in Locmoveright_name]
|
||||
add_coin_dlcquest(Regmoveright, 4, player)
|
||||
if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(825 / World_Options[Options.CoinSanityRange])
|
||||
if World_Options.coinsanity == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(825 / World_Options.coinbundlequantity)
|
||||
for i in range(coin_bundle_needed):
|
||||
item_coin = f"DLC Quest: {World_Options[Options.CoinSanityRange] * (i + 1)} Coin"
|
||||
item_coin = f"DLC Quest: {World_Options.coinbundlequantity * (i + 1)} Coin"
|
||||
Regmoveright.locations += [
|
||||
DLCQuestLocation(player, item_coin, location_table[item_coin], Regmoveright)]
|
||||
if 825 % World_Options[Options.CoinSanityRange] != 0:
|
||||
if 825 % World_Options.coinbundlequantity != 0:
|
||||
Regmoveright.locations += [
|
||||
DLCQuestLocation(player, "DLC Quest: 825 Coin", location_table["DLC Quest: 825 Coin"],
|
||||
Regmoveright)]
|
||||
|
@ -58,7 +59,7 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
Regmovpack = Region("Movement Pack", player, world)
|
||||
Locmovpack_name = ["Time is Money Pack", "Psychological Warfare Pack", "Armor for your Horse Pack",
|
||||
"Shepherd Sheep"]
|
||||
if World_Options[Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
Locmovpack_name += ["Sword"]
|
||||
Regmovpack.exits = [Entrance(player, "Tree", Regmovpack), Entrance(player, "Cloud", Regmovpack)]
|
||||
Regmovpack.locations += [DLCQuestLocation(player, loc_name, location_table[loc_name], Regmovpack) for loc_name
|
||||
|
@ -68,7 +69,7 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
|
||||
Regbtree = Region("Behind Tree", player, world)
|
||||
Locbtree_name = ["Double Jump Pack", "Map Pack", "Between Trees Sheep", "Hole in the Wall Sheep"]
|
||||
if World_Options[Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
Locbtree_name += ["Gun"]
|
||||
Regbtree.exits = [Entrance(player, "Behind Tree Double Jump", Regbtree),
|
||||
Entrance(player, "Forest Entrance", Regbtree)]
|
||||
|
@ -191,27 +192,27 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
|
||||
world.get_entrance("True Double Jump", player).connect(world.get_region("True Double Jump Behind Tree", player))
|
||||
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die or World_Options[
|
||||
Options.Campaign] == Options.Campaign.option_both:
|
||||
if (World_Options.campaign == Options.Campaign.option_live_freemium_or_die or World_Options.campaign
|
||||
== Options.Campaign.option_both):
|
||||
|
||||
Regfreemiumstart = Region("Freemium Start", player, world)
|
||||
Locfreemiumstart_name = ["Particles Pack", "Day One Patch Pack", "Checkpoint Pack", "Incredibly Important Pack",
|
||||
"Nice Try", "Story is Important", "I Get That Reference!"]
|
||||
if World_Options[Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
Locfreemiumstart_name += ["Wooden Sword"]
|
||||
Regfreemiumstart.exits = [Entrance(player, "Vines", Regfreemiumstart)]
|
||||
Regfreemiumstart.locations += [DLCQuestLocation(player, loc_name, location_table[loc_name], Regfreemiumstart)
|
||||
for loc_name in
|
||||
Locfreemiumstart_name]
|
||||
add_coin_freemium(Regfreemiumstart, 50, player)
|
||||
if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(889 / World_Options[Options.CoinSanityRange])
|
||||
if World_Options.coinsanity == Options.CoinSanity.option_coin:
|
||||
coin_bundle_needed = math.floor(889 / World_Options.coinbundlequantity)
|
||||
for i in range(coin_bundle_needed):
|
||||
item_coin_freemium = f"Live Freemium or Die: {World_Options[Options.CoinSanityRange] * (i + 1)} Coin"
|
||||
item_coin_freemium = f"Live Freemium or Die: {World_Options.coinbundlequantity * (i + 1)} Coin"
|
||||
Regfreemiumstart.locations += [
|
||||
DLCQuestLocation(player, item_coin_freemium, location_table[item_coin_freemium],
|
||||
Regfreemiumstart)]
|
||||
if 889 % World_Options[Options.CoinSanityRange] != 0:
|
||||
if 889 % World_Options.coinbundlequantity != 0:
|
||||
Regfreemiumstart.locations += [
|
||||
DLCQuestLocation(player, "Live Freemium or Die: 889 Coin",
|
||||
location_table["Live Freemium or Die: 889 Coin"],
|
||||
|
@ -220,7 +221,7 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
|
||||
Regbehindvine = Region("Behind the Vines", player, world)
|
||||
Locbehindvine_name = ["Wall Jump Pack", "Health Bar Pack", "Parallax Pack"]
|
||||
if World_Options[Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
Locbehindvine_name += ["Pickaxe"]
|
||||
Regbehindvine.exits = [Entrance(player, "Wall Jump Entrance", Regbehindvine)]
|
||||
Regbehindvine.locations += [DLCQuestLocation(player, loc_name, location_table[loc_name], Regbehindvine) for
|
||||
|
@ -260,7 +261,7 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
|
||||
Regcutcontent = Region("Cut Content", player, world)
|
||||
Loccutcontent_name = []
|
||||
if World_Options[Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
Loccutcontent_name += ["Humble Indie Bindle"]
|
||||
Regcutcontent.locations += [DLCQuestLocation(player, loc_name, location_table[loc_name], Regcutcontent) for
|
||||
loc_name in Loccutcontent_name]
|
||||
|
@ -269,7 +270,7 @@ def create_regions(world: MultiWorld, player: int, World_Options: Options.DLCQue
|
|||
|
||||
Regnamechange = Region("Name Change", player, world)
|
||||
Locnamechange_name = []
|
||||
if World_Options[Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle == Options.ItemShuffle.option_shuffled:
|
||||
Locnamechange_name += ["Box of Various Supplies"]
|
||||
Regnamechange.exits = [Entrance(player, "Behind Rocks", Regnamechange)]
|
||||
Regnamechange.locations += [DLCQuestLocation(player, loc_name, location_table[loc_name], Regnamechange) for
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import math
|
||||
import re
|
||||
from .Locations import DLCQuestLocation
|
||||
from ..generic.Rules import add_rule, set_rule, item_name_in_locations
|
||||
from .Items import DLCQuestItem
|
||||
|
||||
from BaseClasses import ItemClassification
|
||||
from worlds.generic.Rules import add_rule, item_name_in_locations, set_rule
|
||||
from . import Options
|
||||
from .Items import DLCQuestItem
|
||||
|
||||
|
||||
def create_event(player, event: str):
|
||||
|
@ -42,7 +42,7 @@ def set_rules(world, player, World_Options: Options.DLCQuestOptions):
|
|||
|
||||
|
||||
def set_basic_rules(World_Options, has_enough_coin, player, world):
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die:
|
||||
if World_Options.campaign == Options.Campaign.option_live_freemium_or_die:
|
||||
return
|
||||
set_basic_entrance_rules(player, world)
|
||||
set_basic_self_obtained_items_rules(World_Options, player, world)
|
||||
|
@ -66,12 +66,12 @@ def set_basic_entrance_rules(player, world):
|
|||
|
||||
|
||||
def set_basic_self_obtained_items_rules(World_Options, player, world):
|
||||
if World_Options[Options.ItemShuffle] != Options.ItemShuffle.option_disabled:
|
||||
if World_Options.item_shuffle != Options.ItemShuffle.option_disabled:
|
||||
return
|
||||
set_rule(world.get_entrance("Behind Ogre", player),
|
||||
lambda state: state.has("Gun Pack", player))
|
||||
|
||||
if World_Options[Options.TimeIsMoney] == Options.TimeIsMoney.option_required:
|
||||
if World_Options.time_is_money == Options.TimeIsMoney.option_required:
|
||||
set_rule(world.get_entrance("Tree", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
set_rule(world.get_entrance("Cave Tree", player),
|
||||
|
@ -87,7 +87,7 @@ def set_basic_self_obtained_items_rules(World_Options, player, world):
|
|||
|
||||
|
||||
def set_basic_shuffled_items_rules(World_Options, player, world):
|
||||
if World_Options[Options.ItemShuffle] != Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle != Options.ItemShuffle.option_shuffled:
|
||||
return
|
||||
set_rule(world.get_entrance("Behind Ogre", player),
|
||||
lambda state: state.has("Gun", player))
|
||||
|
@ -108,13 +108,13 @@ def set_basic_shuffled_items_rules(World_Options, player, world):
|
|||
set_rule(world.get_location("Gun", player),
|
||||
lambda state: state.has("Gun Pack", player))
|
||||
|
||||
if World_Options[Options.TimeIsMoney] == Options.TimeIsMoney.option_required:
|
||||
if World_Options.time_is_money == Options.TimeIsMoney.option_required:
|
||||
set_rule(world.get_location("Sword", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
|
||||
|
||||
def set_double_jump_glitchless_rules(World_Options, player, world):
|
||||
if World_Options[Options.DoubleJumpGlitch] != Options.DoubleJumpGlitch.option_none:
|
||||
if World_Options.double_jump_glitch != Options.DoubleJumpGlitch.option_none:
|
||||
return
|
||||
set_rule(world.get_entrance("Cloud Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
|
@ -123,7 +123,7 @@ def set_double_jump_glitchless_rules(World_Options, player, world):
|
|||
|
||||
|
||||
def set_easy_double_jump_glitch_rules(World_Options, player, world):
|
||||
if World_Options[Options.DoubleJumpGlitch] == Options.DoubleJumpGlitch.option_all:
|
||||
if World_Options.double_jump_glitch == Options.DoubleJumpGlitch.option_all:
|
||||
return
|
||||
set_rule(world.get_entrance("Behind Tree Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
|
@ -132,70 +132,70 @@ def set_easy_double_jump_glitch_rules(World_Options, player, world):
|
|||
|
||||
|
||||
def self_basic_coinsanity_funded_purchase_rules(World_Options, has_enough_coin, player, world):
|
||||
if World_Options[Options.CoinSanity] != Options.CoinSanity.option_coin:
|
||||
if World_Options.coinsanity != Options.CoinSanity.option_coin:
|
||||
return
|
||||
number_of_bundle = math.floor(825 / World_Options[Options.CoinSanityRange])
|
||||
number_of_bundle = math.floor(825 / World_Options.coinbundlequantity)
|
||||
for i in range(number_of_bundle):
|
||||
|
||||
item_coin = f"DLC Quest: {World_Options[Options.CoinSanityRange] * (i + 1)} Coin"
|
||||
item_coin = f"DLC Quest: {World_Options.coinbundlequantity * (i + 1)} Coin"
|
||||
set_rule(world.get_location(item_coin, player),
|
||||
has_enough_coin(player, World_Options[Options.CoinSanityRange] * (i + 1)))
|
||||
if 825 % World_Options[Options.CoinSanityRange] != 0:
|
||||
has_enough_coin(player, World_Options.coinbundlequantity * (i + 1)))
|
||||
if 825 % World_Options.coinbundlequantity != 0:
|
||||
set_rule(world.get_location("DLC Quest: 825 Coin", player),
|
||||
has_enough_coin(player, 825))
|
||||
|
||||
set_rule(world.get_location("Movement Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(4 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(4 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Animation Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Audio Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Pause Menu Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Time is Money Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(20 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(20 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Double Jump Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(100 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(100 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Pet Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Sexy Outfits Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Top Hat Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Map Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(140 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(140 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Gun Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(75 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(75 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("The Zombie Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Night Map Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(75 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(75 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Psychological Warfare Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(50 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(50 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Armor for your Horse Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(250 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(250 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Finish the Fight Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
|
||||
|
||||
def set_basic_self_funded_purchase_rules(World_Options, has_enough_coin, player, world):
|
||||
if World_Options[Options.CoinSanity] != Options.CoinSanity.option_none:
|
||||
if World_Options.coinsanity != Options.CoinSanity.option_none:
|
||||
return
|
||||
set_rule(world.get_location("Movement Pack", player),
|
||||
has_enough_coin(player, 4))
|
||||
|
@ -232,17 +232,17 @@ def set_basic_self_funded_purchase_rules(World_Options, has_enough_coin, player,
|
|||
|
||||
|
||||
def self_basic_win_condition(World_Options, player, world):
|
||||
if World_Options[Options.EndingChoice] == Options.EndingChoice.option_any:
|
||||
if World_Options.ending_choice == Options.EndingChoice.option_any:
|
||||
set_rule(world.get_location("Winning Basic", player),
|
||||
lambda state: state.has("Finish the Fight Pack", player))
|
||||
if World_Options[Options.EndingChoice] == Options.EndingChoice.option_true:
|
||||
if World_Options.ending_choice == Options.EndingChoice.option_true:
|
||||
set_rule(world.get_location("Winning Basic", player),
|
||||
lambda state: state.has("Armor for your Horse Pack", player) and state.has("Finish the Fight Pack",
|
||||
player))
|
||||
|
||||
|
||||
def set_lfod_rules(World_Options, has_enough_coin_freemium, player, world):
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_basic:
|
||||
if World_Options.campaign == Options.Campaign.option_basic:
|
||||
return
|
||||
set_lfod_entrance_rules(player, world)
|
||||
set_boss_door_requirements_rules(player, world)
|
||||
|
@ -297,7 +297,7 @@ def set_boss_door_requirements_rules(player, world):
|
|||
|
||||
|
||||
def set_lfod_self_obtained_items_rules(World_Options, player, world):
|
||||
if World_Options[Options.ItemShuffle] != Options.ItemShuffle.option_disabled:
|
||||
if World_Options.item_shuffle != Options.ItemShuffle.option_disabled:
|
||||
return
|
||||
set_rule(world.get_entrance("Vines", player),
|
||||
lambda state: state.has("Incredibly Important Pack", player))
|
||||
|
@ -309,7 +309,7 @@ def set_lfod_self_obtained_items_rules(World_Options, player, world):
|
|||
|
||||
|
||||
def set_lfod_shuffled_items_rules(World_Options, player, world):
|
||||
if World_Options[Options.ItemShuffle] != Options.ItemShuffle.option_shuffled:
|
||||
if World_Options.item_shuffle != Options.ItemShuffle.option_shuffled:
|
||||
return
|
||||
set_rule(world.get_entrance("Vines", player),
|
||||
lambda state: state.has("Wooden Sword", player) or state.has("Pickaxe", player))
|
||||
|
@ -328,79 +328,79 @@ def set_lfod_shuffled_items_rules(World_Options, player, world):
|
|||
|
||||
|
||||
def self_lfod_coinsanity_funded_purchase_rules(World_Options, has_enough_coin_freemium, player, world):
|
||||
if World_Options[Options.CoinSanity] != Options.CoinSanity.option_coin:
|
||||
if World_Options.coinsanity != Options.CoinSanity.option_coin:
|
||||
return
|
||||
number_of_bundle = math.floor(889 / World_Options[Options.CoinSanityRange])
|
||||
number_of_bundle = math.floor(889 / World_Options.coinbundlequantity)
|
||||
for i in range(number_of_bundle):
|
||||
|
||||
item_coin_freemium = "Live Freemium or Die: number Coin"
|
||||
item_coin_loc_freemium = re.sub("number", str(World_Options[Options.CoinSanityRange] * (i + 1)),
|
||||
item_coin_loc_freemium = re.sub("number", str(World_Options.coinbundlequantity * (i + 1)),
|
||||
item_coin_freemium)
|
||||
set_rule(world.get_location(item_coin_loc_freemium, player),
|
||||
has_enough_coin_freemium(player, World_Options[Options.CoinSanityRange] * (i + 1)))
|
||||
if 889 % World_Options[Options.CoinSanityRange] != 0:
|
||||
has_enough_coin_freemium(player, World_Options.coinbundlequantity * (i + 1)))
|
||||
if 889 % World_Options.coinbundlequantity != 0:
|
||||
set_rule(world.get_location("Live Freemium or Die: 889 Coin", player),
|
||||
has_enough_coin_freemium(player, 889))
|
||||
|
||||
add_rule(world.get_entrance("Boss Door", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(889 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(889 / World_Options.coinbundlequantity)))
|
||||
|
||||
set_rule(world.get_location("Particles Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Day One Patch Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Checkpoint Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Incredibly Important Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(15 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(15 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Wall Jump Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(35 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(35 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Health Bar Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Parallax Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(5 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Harmless Plants Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(130 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(130 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Death of Comedy Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(15 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(15 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Canadian Dialog Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(10 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(10 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("DLC NPC Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(15 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(15 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Cut Content Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(40 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(40 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Name Change Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(150 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(150 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Season Pass", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(199 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(199 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("High Definition Next Gen Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(20 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(20 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Increased HP Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(10 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(10 / World_Options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Remove Ads Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(25 / World_Options[Options.CoinSanityRange])))
|
||||
math.ceil(25 / World_Options.coinbundlequantity)))
|
||||
|
||||
|
||||
def set_lfod_self_funded_purchase_rules(World_Options, has_enough_coin_freemium, player, world):
|
||||
if World_Options[Options.CoinSanity] != Options.CoinSanity.option_none:
|
||||
if World_Options.coinsanity != Options.CoinSanity.option_none:
|
||||
return
|
||||
add_rule(world.get_entrance("Boss Door", player),
|
||||
has_enough_coin_freemium(player, 889))
|
||||
|
@ -442,10 +442,10 @@ def set_lfod_self_funded_purchase_rules(World_Options, has_enough_coin_freemium,
|
|||
|
||||
|
||||
def set_completion_condition(World_Options, player, world):
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_basic:
|
||||
if World_Options.campaign == Options.Campaign.option_basic:
|
||||
world.completion_condition[player] = lambda state: state.has("Victory Basic", player)
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die:
|
||||
if World_Options.campaign == Options.Campaign.option_live_freemium_or_die:
|
||||
world.completion_condition[player] = lambda state: state.has("Victory Freemium", player)
|
||||
if World_Options[Options.Campaign] == Options.Campaign.option_both:
|
||||
if World_Options.campaign == Options.Campaign.option_both:
|
||||
world.completion_condition[player] = lambda state: state.has("Victory Basic", player) and state.has(
|
||||
"Victory Freemium", player)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from typing import Dict, Any, Iterable, Optional, Union
|
||||
from typing import Union
|
||||
|
||||
from BaseClasses import Tutorial
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Items import DLCQuestItem, item_table, ItemData, create_items
|
||||
from .Locations import location_table, DLCQuestLocation
|
||||
from .Options import DLCQuest_options, DLCQuestOptions, fetch_options
|
||||
from .Rules import set_rules
|
||||
from .Regions import create_regions
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from . import Options
|
||||
from .Items import DLCQuestItem, ItemData, create_items, item_table
|
||||
from .Locations import DLCQuestLocation, location_table
|
||||
from .Options import DLCQuestOptions
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules
|
||||
|
||||
client_version = 0
|
||||
|
||||
|
@ -35,10 +36,8 @@ class DLCqworld(World):
|
|||
|
||||
data_version = 1
|
||||
|
||||
option_definitions = DLCQuest_options
|
||||
|
||||
def generate_early(self):
|
||||
self.options = fetch_options(self.multiworld, self.player)
|
||||
options_dataclass = DLCQuestOptions
|
||||
options: DLCQuestOptions
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.multiworld, self.player, self.options)
|
||||
|
@ -68,8 +67,8 @@ class DLCqworld(World):
|
|||
self.multiworld.itempool.remove(item)
|
||||
|
||||
def precollect_coinsanity(self):
|
||||
if self.options[Options.Campaign] == Options.Campaign.option_basic:
|
||||
if self.options[Options.CoinSanity] == Options.CoinSanity.option_coin and self.options[Options.CoinSanityRange] >= 5:
|
||||
if self.options.campaign == Options.Campaign.option_basic:
|
||||
if self.options.coinsanity == Options.CoinSanity.option_coin and self.options.coinbundlequantity >= 5:
|
||||
self.multiworld.push_precollected(self.create_item("Movement Pack"))
|
||||
|
||||
|
||||
|
@ -80,12 +79,11 @@ class DLCqworld(World):
|
|||
return DLCQuestItem(item.name, item.classification, item.code, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"death_link": self.multiworld.death_link[self.player].value,
|
||||
"ending_choice": self.multiworld.ending_choice[self.player].value,
|
||||
"campaign": self.multiworld.campaign[self.player].value,
|
||||
"coinsanity": self.multiworld.coinsanity[self.player].value,
|
||||
"coinbundlerange": self.multiworld.coinbundlequantity[self.player].value,
|
||||
"item_shuffle": self.multiworld.item_shuffle[self.player].value,
|
||||
"seed": self.multiworld.per_slot_randoms[self.player].randrange(99999999)
|
||||
}
|
||||
options_dict = self.options.as_dict(
|
||||
"death_link", "ending_choice", "campaign", "coinsanity", "item_shuffle"
|
||||
)
|
||||
options_dict.update({
|
||||
"coinbundlerange": self.options.coinbundlequantity.value,
|
||||
"seed": self.random.randrange(99999999)
|
||||
})
|
||||
return options_dict
|
||||
|
|
|
@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional
|
|||
from BaseClasses import CollectionState, Item, ItemClassification, Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .constants import ALL_ITEMS, ALWAYS_LOCATIONS, BOSS_LOCATIONS, FILLER, NOTES, PHOBEKINS
|
||||
from .options import Goal, Logic, NotesNeeded, PowerSeals, messenger_options
|
||||
from .options import Goal, Logic, MessengerOptions, NotesNeeded, PowerSeals
|
||||
from .regions import MEGA_SHARDS, REGIONS, REGION_CONNECTIONS, SEALS
|
||||
from .rules import MessengerHardRules, MessengerOOBRules, MessengerRules
|
||||
from .shop import FIGURINES, SHOP_ITEMS, shuffle_shop_prices
|
||||
|
@ -44,7 +44,8 @@ class MessengerWorld(World):
|
|||
"Phobekin": set(PHOBEKINS),
|
||||
}
|
||||
|
||||
option_definitions = messenger_options
|
||||
options_dataclass = MessengerOptions
|
||||
options: MessengerOptions
|
||||
|
||||
base_offset = 0xADD_000
|
||||
item_name_to_id = {item: item_id
|
||||
|
@ -74,9 +75,9 @@ class MessengerWorld(World):
|
|||
_filler_items: List[str]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||
self.multiworld.shuffle_seals[self.player].value = PowerSeals.option_true
|
||||
self.total_seals = self.multiworld.total_seals[self.player].value
|
||||
if self.options.goal == Goal.option_power_seal_hunt:
|
||||
self.options.shuffle_seals.value = PowerSeals.option_true
|
||||
self.total_seals = self.options.total_seals.value
|
||||
|
||||
self.shop_prices, self.figurine_prices = shuffle_shop_prices(self)
|
||||
|
||||
|
@ -87,7 +88,7 @@ class MessengerWorld(World):
|
|||
|
||||
def create_items(self) -> None:
|
||||
# create items that are always in the item pool
|
||||
itempool = [
|
||||
itempool: List[MessengerItem] = [
|
||||
self.create_item(item)
|
||||
for item in self.item_name_to_id
|
||||
if item not in
|
||||
|
@ -97,13 +98,13 @@ class MessengerWorld(World):
|
|||
} and "Time Shard" not in item
|
||||
]
|
||||
|
||||
if self.multiworld.goal[self.player] == Goal.option_open_music_box:
|
||||
if self.options.goal == Goal.option_open_music_box:
|
||||
# make a list of all notes except those in the player's defined starting inventory, and adjust the
|
||||
# amount we need to put in the itempool and precollect based on that
|
||||
notes = [note for note in NOTES if note not in self.multiworld.precollected_items[self.player]]
|
||||
self.random.shuffle(notes)
|
||||
precollected_notes_amount = NotesNeeded.range_end - \
|
||||
self.multiworld.notes_needed[self.player] - \
|
||||
self.options.notes_needed - \
|
||||
(len(NOTES) - len(notes))
|
||||
if precollected_notes_amount:
|
||||
for note in notes[:precollected_notes_amount]:
|
||||
|
@ -111,15 +112,14 @@ class MessengerWorld(World):
|
|||
notes = notes[precollected_notes_amount:]
|
||||
itempool += [self.create_item(note) for note in notes]
|
||||
|
||||
elif self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||
elif self.options.goal == Goal.option_power_seal_hunt:
|
||||
total_seals = min(len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool),
|
||||
self.multiworld.total_seals[self.player].value)
|
||||
self.options.total_seals.value)
|
||||
if total_seals < self.total_seals:
|
||||
logging.warning(f"Not enough locations for total seals setting "
|
||||
f"({self.multiworld.total_seals[self.player].value}). Adjusting to {total_seals}")
|
||||
f"({self.options.total_seals}). Adjusting to {total_seals}")
|
||||
self.total_seals = total_seals
|
||||
self.required_seals =\
|
||||
int(self.multiworld.percent_seals_required[self.player].value / 100 * self.total_seals)
|
||||
self.required_seals = int(self.options.percent_seals_required.value / 100 * self.total_seals)
|
||||
|
||||
seals = [self.create_item("Power Seal") for _ in range(self.total_seals)]
|
||||
for i in range(self.required_seals):
|
||||
|
@ -138,7 +138,7 @@ class MessengerWorld(World):
|
|||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self) -> None:
|
||||
logic = self.multiworld.logic_level[self.player]
|
||||
logic = self.options.logic_level
|
||||
if logic == Logic.option_normal:
|
||||
MessengerRules(self).set_messenger_rules()
|
||||
elif logic == Logic.option_hard:
|
||||
|
@ -151,12 +151,12 @@ class MessengerWorld(World):
|
|||
figure_prices = {FIGURINES[item].internal_name: price for item, price in self.figurine_prices.items()}
|
||||
|
||||
return {
|
||||
"deathlink": self.multiworld.death_link[self.player].value,
|
||||
"goal": self.multiworld.goal[self.player].current_key,
|
||||
"music_box": self.multiworld.music_box[self.player].value,
|
||||
"deathlink": self.options.death_link.value,
|
||||
"goal": self.options.goal.current_key,
|
||||
"music_box": self.options.music_box.value,
|
||||
"required_seals": self.required_seals,
|
||||
"mega_shards": self.multiworld.shuffle_shards[self.player].value,
|
||||
"logic": self.multiworld.logic_level[self.player].current_key,
|
||||
"mega_shards": self.options.shuffle_shards.value,
|
||||
"logic": self.options.logic_level.current_key,
|
||||
"shop": shop_prices,
|
||||
"figures": figure_prices,
|
||||
"max_price": self.total_shards,
|
||||
|
@ -175,7 +175,7 @@ class MessengerWorld(World):
|
|||
item_id: Optional[int] = self.item_name_to_id.get(name, None)
|
||||
override_prog = getattr(self, "multiworld") is not None and \
|
||||
name in {"Windmill Shuriken"} and \
|
||||
self.multiworld.logic_level[self.player] > Logic.option_normal
|
||||
self.options.logic_level > Logic.option_normal
|
||||
count = 0
|
||||
if "Time Shard " in name:
|
||||
count = int(name.strip("Time Shard ()"))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from .shop import FIGURINES, SHOP_ITEMS
|
||||
|
||||
# items
|
||||
# listing individual groups first for easy lookup
|
||||
from .shop import SHOP_ITEMS, FIGURINES
|
||||
|
||||
NOTES = [
|
||||
"Key of Hope",
|
||||
"Key of Chaos",
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict
|
||||
from schema import Schema, Or, And, Optional
|
||||
|
||||
from Options import DefaultOnToggle, DeathLink, Range, Accessibility, Choice, Toggle, OptionDict, StartInventoryPool
|
||||
from schema import And, Optional, Or, Schema
|
||||
|
||||
from Options import Accessibility, Choice, DeathLink, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \
|
||||
StartInventoryPool, Toggle
|
||||
|
||||
|
||||
class MessengerAccessibility(Accessibility):
|
||||
|
@ -129,18 +132,19 @@ class PlannedShopPrices(OptionDict):
|
|||
})
|
||||
|
||||
|
||||
messenger_options = {
|
||||
"accessibility": MessengerAccessibility,
|
||||
"start_inventory": StartInventoryPool,
|
||||
"logic_level": Logic,
|
||||
"shuffle_seals": PowerSeals,
|
||||
"shuffle_shards": MegaShards,
|
||||
"goal": Goal,
|
||||
"music_box": MusicBox,
|
||||
"notes_needed": NotesNeeded,
|
||||
"total_seals": AmountSeals,
|
||||
"percent_seals_required": RequiredSeals,
|
||||
"shop_price": ShopPrices,
|
||||
"shop_price_plan": PlannedShopPrices,
|
||||
"death_link": DeathLink,
|
||||
}
|
||||
@dataclass
|
||||
class MessengerOptions(PerGameCommonOptions):
|
||||
accessibility: MessengerAccessibility
|
||||
start_inventory: StartInventoryPool
|
||||
logic_level: Logic
|
||||
shuffle_seals: PowerSeals
|
||||
shuffle_shards: MegaShards
|
||||
goal: Goal
|
||||
music_box: MusicBox
|
||||
notes_needed: NotesNeeded
|
||||
total_seals: AmountSeals
|
||||
percent_seals_required: RequiredSeals
|
||||
shop_price: ShopPrices
|
||||
shop_price_plan: PlannedShopPrices
|
||||
death_link: DeathLink
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, Set, List
|
||||
from typing import Dict, List, Set
|
||||
|
||||
REGIONS: Dict[str, List[str]] = {
|
||||
"Menu": [],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from typing import Dict, Callable, TYPE_CHECKING
|
||||
from typing import Callable, Dict, TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.generic.Rules import set_rule, allow_self_locking_items, add_rule
|
||||
from .options import MessengerAccessibility, Goal
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import add_rule, allow_self_locking_items, set_rule
|
||||
from .constants import NOTES, PHOBEKINS
|
||||
from .options import Goal, MessengerAccessibility
|
||||
from .subclasses import MessengerShopLocation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -145,13 +145,13 @@ class MessengerRules:
|
|||
if region.name == "The Shop":
|
||||
for loc in [location for location in region.locations if isinstance(location, MessengerShopLocation)]:
|
||||
loc.access_rule = loc.can_afford
|
||||
if multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||
if self.world.options.goal == Goal.option_power_seal_hunt:
|
||||
set_rule(multiworld.get_entrance("Tower HQ -> Music Box", self.player),
|
||||
lambda state: state.has("Shop Chest", self.player))
|
||||
|
||||
multiworld.completion_condition[self.player] = lambda state: state.has("Rescue Phantom", self.player)
|
||||
if multiworld.accessibility[self.player] > MessengerAccessibility.option_locations:
|
||||
set_self_locking_items(multiworld, self.player)
|
||||
set_self_locking_items(self.world, self.player)
|
||||
|
||||
|
||||
class MessengerHardRules(MessengerRules):
|
||||
|
@ -212,9 +212,9 @@ class MessengerHardRules(MessengerRules):
|
|||
def set_messenger_rules(self) -> None:
|
||||
super().set_messenger_rules()
|
||||
for loc, rule in self.extra_rules.items():
|
||||
if not self.world.multiworld.shuffle_seals[self.player] and "Seal" in loc:
|
||||
if not self.world.options.shuffle_seals and "Seal" in loc:
|
||||
continue
|
||||
if not self.world.multiworld.shuffle_shards[self.player] and "Shard" in loc:
|
||||
if not self.world.options.shuffle_shards and "Shard" in loc:
|
||||
continue
|
||||
add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
|
||||
|
||||
|
@ -249,20 +249,22 @@ class MessengerOOBRules(MessengerRules):
|
|||
def set_messenger_rules(self) -> None:
|
||||
super().set_messenger_rules()
|
||||
self.world.multiworld.completion_condition[self.player] = lambda state: True
|
||||
self.world.multiworld.accessibility[self.player].value = MessengerAccessibility.option_minimal
|
||||
self.world.options.accessibility.value = MessengerAccessibility.option_minimal
|
||||
|
||||
|
||||
def set_self_locking_items(multiworld: MultiWorld, player: int) -> None:
|
||||
def set_self_locking_items(world: MessengerWorld, player: int) -> None:
|
||||
multiworld = world.multiworld
|
||||
|
||||
# do the ones for seal shuffle on and off first
|
||||
allow_self_locking_items(multiworld.get_location("Searing Crags - Key of Strength", player), "Power Thistle")
|
||||
allow_self_locking_items(multiworld.get_location("Sunken Shrine - Key of Love", player), "Sun Crest", "Moon Crest")
|
||||
allow_self_locking_items(multiworld.get_location("Corrupted Future - Key of Courage", player), "Demon King Crown")
|
||||
|
||||
# add these locations when seals are shuffled
|
||||
if multiworld.shuffle_seals[player]:
|
||||
if world.options.shuffle_seals:
|
||||
allow_self_locking_items(multiworld.get_location("Elemental Skylands Seal - Water", player), "Currents Master")
|
||||
# add these locations when seals and shards aren't shuffled
|
||||
elif not multiworld.shuffle_shards[player]:
|
||||
elif not world.options.shuffle_shards:
|
||||
for entrance in multiworld.get_region("Cloud Ruins", player).entrances:
|
||||
entrance.access_rule = lambda state: state.has("Wingsuit", player) or state.has("Rope Dart", player)
|
||||
allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS)
|
||||
|
|
|
@ -74,8 +74,8 @@ FIGURINES: Dict[str, ShopData] = {
|
|||
|
||||
|
||||
def shuffle_shop_prices(world: MessengerWorld) -> Tuple[Dict[str, int], Dict[str, int]]:
|
||||
shop_price_mod = world.multiworld.shop_price[world.player].value
|
||||
shop_price_planned = world.multiworld.shop_price_plan[world.player]
|
||||
shop_price_mod = world.options.shop_price.value
|
||||
shop_price_planned = world.options.shop_price_plan
|
||||
|
||||
shop_prices: Dict[str, int] = {}
|
||||
figurine_prices: Dict[str, int] = {}
|
||||
|
|
|
@ -9,16 +9,15 @@ from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from . import MessengerWorld
|
||||
else:
|
||||
MessengerWorld = object
|
||||
|
||||
|
||||
class MessengerRegion(Region):
|
||||
def __init__(self, name: str, world: MessengerWorld) -> None:
|
||||
|
||||
def __init__(self, name: str, world: "MessengerWorld") -> None:
|
||||
super().__init__(name, world.player, world.multiworld)
|
||||
locations = [loc for loc in REGIONS[self.name]]
|
||||
if self.name == "The Shop":
|
||||
if self.multiworld.goal[self.player] > Goal.option_open_music_box:
|
||||
if world.options.goal > Goal.option_open_music_box:
|
||||
locations.append("Shop Chest")
|
||||
shop_locations = {f"The Shop - {shop_loc}": world.location_name_to_id[f"The Shop - {shop_loc}"]
|
||||
for shop_loc in SHOP_ITEMS}
|
||||
|
@ -26,9 +25,9 @@ class MessengerRegion(Region):
|
|||
self.add_locations(shop_locations, MessengerShopLocation)
|
||||
elif self.name == "Tower HQ":
|
||||
locations.append("Money Wrench")
|
||||
if self.multiworld.shuffle_seals[self.player] and self.name in SEALS:
|
||||
if world.options.shuffle_seals and self.name in SEALS:
|
||||
locations += [seal_loc for seal_loc in SEALS[self.name]]
|
||||
if self.multiworld.shuffle_shards[self.player] and self.name in MEGA_SHARDS:
|
||||
if world.options.shuffle_shards and self.name in MEGA_SHARDS:
|
||||
locations += [shard for shard in MEGA_SHARDS[self.name]]
|
||||
loc_dict = {loc: world.location_name_to_id[loc] if loc in world.location_name_to_id else None
|
||||
for loc in locations}
|
||||
|
@ -49,7 +48,7 @@ class MessengerShopLocation(MessengerLocation):
|
|||
@cached_property
|
||||
def cost(self) -> int:
|
||||
name = self.name.replace("The Shop - ", "") # TODO use `remove_prefix` when 3.8 finally gets dropped
|
||||
world: MessengerWorld = self.parent_region.multiworld.worlds[self.player]
|
||||
world = cast("MessengerWorld", self.parent_region.multiworld.worlds[self.player])
|
||||
# short circuit figurines which all require demon's bane be purchased, but nothing else
|
||||
if "Figurine" in name:
|
||||
return world.figurine_prices[name] +\
|
||||
|
@ -70,9 +69,8 @@ class MessengerShopLocation(MessengerLocation):
|
|||
return world.shop_prices[name]
|
||||
|
||||
def can_afford(self, state: CollectionState) -> bool:
|
||||
world: MessengerWorld = state.multiworld.worlds[self.player]
|
||||
cost = self.cost
|
||||
can_afford = state.has("Shards", self.player, min(cost, world.total_shards))
|
||||
world = cast("MessengerWorld", state.multiworld.worlds[self.player])
|
||||
can_afford = state.has("Shards", self.player, min(self.cost, world.total_shards))
|
||||
if "Figurine" in self.name:
|
||||
can_afford = state.has("Money Wrench", self.player) and can_afford\
|
||||
and state.can_reach("Money Wrench", "Location", self.player)
|
||||
|
|
|
@ -32,7 +32,7 @@ from .Cosmetics import patch_cosmetics
|
|||
|
||||
from Utils import get_options
|
||||
from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType
|
||||
from Options import Range, Toggle, VerifyKeys
|
||||
from Options import Range, Toggle, VerifyKeys, Accessibility
|
||||
from Fill import fill_restrictive, fast_fill, FillError
|
||||
from worlds.generic.Rules import exclusion_rules, add_item_rule
|
||||
from ..AutoWorld import World, AutoLogicRegister, WebWorld
|
||||
|
@ -286,7 +286,7 @@ class OOTWorld(World):
|
|||
# No Logic forces all tricks on, prog balancing off and beatable-only
|
||||
elif self.logic_rules == 'no_logic':
|
||||
self.multiworld.progression_balancing[self.player].value = False
|
||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
||||
self.multiworld.accessibility[self.player].value = Accessibility.option_minimal
|
||||
for trick in normalized_name_tricks.values():
|
||||
setattr(self, trick['name'], True)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
from typing import TypedDict
|
||||
from Options import DefaultOnToggle, Toggle, Range, Choice, OptionSet
|
||||
from Options import DefaultOnToggle, PerGameCommonOptions, Toggle, Range, Choice, OptionSet
|
||||
from .Overcooked2Levels import Overcooked2Dlc
|
||||
|
||||
class LocationBalancingMode(IntEnum):
|
||||
|
@ -167,32 +168,30 @@ class StarThresholdScale(Range):
|
|||
default = 35
|
||||
|
||||
|
||||
overcooked_options = {
|
||||
@dataclass
|
||||
class OC2Options(PerGameCommonOptions):
|
||||
# generator options
|
||||
"location_balancing": LocationBalancing,
|
||||
"ramp_tricks": RampTricks,
|
||||
|
||||
location_balancing: LocationBalancing
|
||||
ramp_tricks: RampTricks
|
||||
|
||||
# deathlink
|
||||
"deathlink": DeathLink,
|
||||
|
||||
deathlink: DeathLink
|
||||
|
||||
# randomization options
|
||||
"shuffle_level_order": ShuffleLevelOrder,
|
||||
"include_dlcs": DLCOptionSet,
|
||||
"include_horde_levels": IncludeHordeLevels,
|
||||
"prep_levels": PrepLevels,
|
||||
"kevin_levels": KevinLevels,
|
||||
|
||||
shuffle_level_order: ShuffleLevelOrder
|
||||
include_dlcs: DLCOptionSet
|
||||
include_horde_levels: IncludeHordeLevels
|
||||
prep_levels: PrepLevels
|
||||
kevin_levels: KevinLevels
|
||||
|
||||
# quality of life options
|
||||
"fix_bugs": FixBugs,
|
||||
"shorter_level_duration": ShorterLevelDuration,
|
||||
"short_horde_levels": ShortHordeLevels,
|
||||
"always_preserve_cooking_progress": AlwaysPreserveCookingProgress,
|
||||
"always_serve_oldest_order": AlwaysServeOldestOrder,
|
||||
"display_leaderboard_scores": DisplayLeaderboardScores,
|
||||
|
||||
fix_bugs: FixBugs
|
||||
shorter_level_duration: ShorterLevelDuration
|
||||
short_horde_levels: ShortHordeLevels
|
||||
always_preserve_cooking_progress: AlwaysPreserveCookingProgress
|
||||
always_serve_oldest_order: AlwaysServeOldestOrder
|
||||
display_leaderboard_scores: DisplayLeaderboardScores
|
||||
|
||||
# difficulty settings
|
||||
"stars_to_win": StarsToWin,
|
||||
"star_threshold_scale": StarThresholdScale,
|
||||
}
|
||||
|
||||
OC2Options = TypedDict("OC2Options", {option.__name__: option for option in overcooked_options.values()})
|
||||
stars_to_win: StarsToWin
|
||||
star_threshold_scale: StarThresholdScale
|
||||
|
|
|
@ -6,7 +6,7 @@ from worlds.AutoWorld import World, WebWorld
|
|||
|
||||
from .Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, Overcooked2GenericLevel
|
||||
from .Locations import Overcooked2Location, oc2_location_name_to_id, oc2_location_id_to_name
|
||||
from .Options import overcooked_options, OC2Options, OC2OnToggle, LocationBalancingMode, DeathLinkMode
|
||||
from .Options import OC2Options, OC2OnToggle, LocationBalancingMode, DeathLinkMode
|
||||
from .Items import item_table, Overcooked2Item, item_name_to_id, item_id_to_name, item_to_unlock_event, item_frequencies, dlc_exclusives
|
||||
from .Logic import has_requirements_for_level_star, has_requirements_for_level_access, level_shuffle_factory, is_item_progression, is_useful
|
||||
|
||||
|
@ -47,7 +47,6 @@ class Overcooked2World(World):
|
|||
game = "Overcooked! 2"
|
||||
web = Overcooked2Web()
|
||||
required_client_version = (0, 3, 8)
|
||||
option_definitions = overcooked_options
|
||||
topology_present: bool = False
|
||||
data_version = 3
|
||||
|
||||
|
@ -57,13 +56,14 @@ class Overcooked2World(World):
|
|||
location_id_to_name = oc2_location_id_to_name
|
||||
location_name_to_id = oc2_location_name_to_id
|
||||
|
||||
options: Dict[str, Any]
|
||||
options_dataclass = OC2Options
|
||||
options: OC2Options
|
||||
itempool: List[Overcooked2Item]
|
||||
|
||||
# Helper Functions
|
||||
|
||||
def is_level_horde(self, level_id: int) -> bool:
|
||||
return self.options["IncludeHordeLevels"] and \
|
||||
return self.options.include_horde_levels and \
|
||||
(self.level_mapping is not None) and \
|
||||
level_id in self.level_mapping.keys() and \
|
||||
self.level_mapping[level_id].is_horde
|
||||
|
@ -145,11 +145,6 @@ class Overcooked2World(World):
|
|||
location
|
||||
)
|
||||
|
||||
def get_options(self) -> Dict[str, Any]:
|
||||
return OC2Options({option.__name__: getattr(self.multiworld, name)[self.player].result
|
||||
if issubclass(option, OC2OnToggle) else getattr(self.multiworld, name)[self.player].value
|
||||
for name, option in overcooked_options.items()})
|
||||
|
||||
def get_n_random_locations(self, n: int) -> List[int]:
|
||||
"""Return a list of n random non-repeating level locations"""
|
||||
levels = list()
|
||||
|
@ -160,7 +155,7 @@ class Overcooked2World(World):
|
|||
for level in Overcooked2Level():
|
||||
if level.level_id == 36:
|
||||
continue
|
||||
elif not self.options["KevinLevels"] and level.level_id > 36:
|
||||
elif not self.options.kevin_levels and level.level_id > 36:
|
||||
break
|
||||
|
||||
levels.append(level.level_id)
|
||||
|
@ -231,26 +226,25 @@ class Overcooked2World(World):
|
|||
|
||||
def generate_early(self):
|
||||
self.player_name = self.multiworld.player_name[self.player]
|
||||
self.options = self.get_options()
|
||||
|
||||
# 0.0 to 1.0 where 1.0 is World Record
|
||||
self.star_threshold_scale = self.options["StarThresholdScale"] / 100.0
|
||||
self.star_threshold_scale = self.options.star_threshold_scale / 100.0
|
||||
|
||||
# Parse DLCOptionSet back into enums
|
||||
self.enabled_dlc = {Overcooked2Dlc(x) for x in self.options["DLCOptionSet"]}
|
||||
self.enabled_dlc = {Overcooked2Dlc(x) for x in self.options.include_dlcs.value}
|
||||
|
||||
# Generate level unlock requirements such that the levels get harder to unlock
|
||||
# the further the game has progressed, and levels progress radially rather than linearly
|
||||
self.level_unlock_counts = level_unlock_requirement_factory(self.options["StarsToWin"])
|
||||
self.level_unlock_counts = level_unlock_requirement_factory(self.options.stars_to_win.value)
|
||||
|
||||
# Assign new kitchens to each spot on the overworld using pure random chance and nothing else
|
||||
if self.options["ShuffleLevelOrder"]:
|
||||
if self.options.shuffle_level_order:
|
||||
self.level_mapping = \
|
||||
level_shuffle_factory(
|
||||
self.multiworld.random,
|
||||
self.options["PrepLevels"] != PrepLevelMode.excluded,
|
||||
self.options["IncludeHordeLevels"],
|
||||
self.options["KevinLevels"],
|
||||
self.options.prep_levels != PrepLevelMode.excluded,
|
||||
self.options.include_horde_levels.result,
|
||||
self.options.kevin_levels.result,
|
||||
self.enabled_dlc,
|
||||
self.player_name,
|
||||
)
|
||||
|
@ -277,7 +271,7 @@ class Overcooked2World(World):
|
|||
|
||||
# Create and populate "regions" (a.k.a. levels)
|
||||
for level in Overcooked2Level():
|
||||
if not self.options["KevinLevels"] and level.level_id > 36:
|
||||
if not self.options.kevin_levels and level.level_id > 36:
|
||||
break
|
||||
|
||||
# Create Region (e.g. "1-1")
|
||||
|
@ -336,7 +330,7 @@ class Overcooked2World(World):
|
|||
|
||||
level_access_rule: Callable[[CollectionState], bool] = \
|
||||
lambda state, level_name=level.level_name, previous_level_completed_event_name=previous_level_completed_event_name, required_star_count=required_star_count: \
|
||||
has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.options["RampTricks"], self.player)
|
||||
has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.options.ramp_tricks.result, self.player)
|
||||
self.connect_regions("Overworld", level.level_name, level_access_rule)
|
||||
|
||||
# Level --> Overworld
|
||||
|
@ -369,11 +363,11 @@ class Overcooked2World(World):
|
|||
# Item is always useless with these settings
|
||||
continue
|
||||
|
||||
if not self.options["IncludeHordeLevels"] and item_name in ["Calmer Unbread", "Coin Purse"]:
|
||||
if not self.options.include_horde_levels and item_name in ["Calmer Unbread", "Coin Purse"]:
|
||||
# skip horde-specific items if no horde levels
|
||||
continue
|
||||
|
||||
if not self.options["KevinLevels"]:
|
||||
if not self.options.kevin_levels:
|
||||
if item_name.startswith("Kevin"):
|
||||
# skip kevin items if no kevin levels
|
||||
continue
|
||||
|
@ -382,7 +376,7 @@ class Overcooked2World(World):
|
|||
# skip dark green ramp if there's no Kevin-1 to reveal it
|
||||
continue
|
||||
|
||||
if is_item_progression(item_name, self.level_mapping, self.options["KevinLevels"]):
|
||||
if is_item_progression(item_name, self.level_mapping, self.options.kevin_levels):
|
||||
# progression.append(item_name)
|
||||
classification = ItemClassification.progression
|
||||
else:
|
||||
|
@ -404,7 +398,7 @@ class Overcooked2World(World):
|
|||
|
||||
# Fill any free space with filler
|
||||
pool_count = len(oc2_location_name_to_id)
|
||||
if not self.options["KevinLevels"]:
|
||||
if not self.options.kevin_levels:
|
||||
pool_count -= 8
|
||||
|
||||
while len(self.itempool) < pool_count:
|
||||
|
@ -416,7 +410,7 @@ class Overcooked2World(World):
|
|||
def place_events(self):
|
||||
# Add Events (Star Acquisition)
|
||||
for level in Overcooked2Level():
|
||||
if not self.options["KevinLevels"] and level.level_id > 36:
|
||||
if not self.options.kevin_levels and level.level_id > 36:
|
||||
break
|
||||
|
||||
if level.level_id != 36:
|
||||
|
@ -449,7 +443,7 @@ class Overcooked2World(World):
|
|||
# Serialize Level Order
|
||||
story_level_order = dict()
|
||||
|
||||
if self.options["ShuffleLevelOrder"]:
|
||||
if self.options.shuffle_level_order:
|
||||
for level_id in self.level_mapping:
|
||||
level: Overcooked2GenericLevel = self.level_mapping[level_id]
|
||||
story_level_order[str(level_id)] = {
|
||||
|
@ -481,7 +475,7 @@ class Overcooked2World(World):
|
|||
level_unlock_requirements[str(level_id)] = level_id - 1
|
||||
|
||||
# Set Kevin Unlock Requirements
|
||||
if self.options["KevinLevels"]:
|
||||
if self.options.kevin_levels:
|
||||
def kevin_level_to_keyholder_level_id(level_id: int) -> Optional[int]:
|
||||
location = self.multiworld.find_item(f"Kevin-{level_id-36}", self.player)
|
||||
if location.player != self.player:
|
||||
|
@ -506,7 +500,7 @@ class Overcooked2World(World):
|
|||
on_level_completed[level_id] = [item_to_unlock_event(location.item.name)]
|
||||
|
||||
# Put it all together
|
||||
star_threshold_scale = self.options["StarThresholdScale"] / 100
|
||||
star_threshold_scale = self.options.star_threshold_scale / 100
|
||||
|
||||
base_data = {
|
||||
# Changes Inherent to rando
|
||||
|
@ -528,13 +522,13 @@ class Overcooked2World(World):
|
|||
"SaveFolderName": mod_name,
|
||||
"CustomOrderTimeoutPenalty": 10,
|
||||
"LevelForceHide": [37, 38, 39, 40, 41, 42, 43, 44],
|
||||
"LocalDeathLink": self.options["DeathLink"] != DeathLinkMode.disabled,
|
||||
"BurnTriggersDeath": self.options["DeathLink"] == DeathLinkMode.death_and_overcook,
|
||||
"LocalDeathLink": self.options.deathlink != DeathLinkMode.disabled,
|
||||
"BurnTriggersDeath": self.options.deathlink == DeathLinkMode.death_and_overcook,
|
||||
|
||||
# Game Modifications
|
||||
"LevelPurchaseRequirements": level_purchase_requirements,
|
||||
"Custom66TimerScale": max(0.4, 0.25 + (1.0 - star_threshold_scale)*0.6),
|
||||
"ShortHordeLevels": self.options["ShortHordeLevels"],
|
||||
"ShortHordeLevels": self.options.short_horde_levels,
|
||||
"CustomLevelOrder": custom_level_order,
|
||||
|
||||
# Items (Starting Inventory)
|
||||
|
@ -580,28 +574,27 @@ class Overcooked2World(World):
|
|||
# Set remaining data in the options dict
|
||||
bugs = ["FixDoubleServing", "FixSinkBug", "FixControlStickThrowBug", "FixEmptyBurnerThrow"]
|
||||
for bug in bugs:
|
||||
self.options[bug] = self.options["FixBugs"]
|
||||
self.options["PreserveCookingProgress"] = self.options["AlwaysPreserveCookingProgress"]
|
||||
self.options["TimerAlwaysStarts"] = self.options["PrepLevels"] == PrepLevelMode.ayce
|
||||
self.options["LevelTimerScale"] = 0.666 if self.options["ShorterLevelDuration"] else 1.0
|
||||
self.options["LeaderboardScoreScale"] = {
|
||||
base_data[bug] = self.options.fix_bugs.result
|
||||
base_data["PreserveCookingProgress"] = self.options.always_preserve_cooking_progress.result
|
||||
base_data["TimerAlwaysStarts"] = self.options.prep_levels == PrepLevelMode.ayce
|
||||
base_data["LevelTimerScale"] = 0.666 if self.options.shorter_level_duration else 1.0
|
||||
base_data["LeaderboardScoreScale"] = {
|
||||
"FourStars": 1.0,
|
||||
"ThreeStars": star_threshold_scale,
|
||||
"TwoStars": star_threshold_scale * 0.75,
|
||||
"OneStar": star_threshold_scale * 0.35,
|
||||
}
|
||||
|
||||
base_data.update(self.options)
|
||||
return base_data
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return self.fill_json_data()
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO) -> None:
|
||||
if not self.options["ShuffleLevelOrder"]:
|
||||
if not self.options.shuffle_level_order:
|
||||
return
|
||||
|
||||
world: Overcooked2World = self.multiworld.worlds[self.player]
|
||||
world: Overcooked2World = self
|
||||
spoiler_handle.write(f"\n\n{self.player_name}'s Level Order:\n\n")
|
||||
for overworld_id in world.level_mapping:
|
||||
overworld_name = Overcooked2GenericLevel(overworld_id).shortname.split("Story ")[1]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from typing import Dict
|
||||
from Options import Option, Toggle, DefaultOnToggle, DeathLink, Range, Choice
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCommonOptions
|
||||
|
||||
|
||||
# NOTE be aware that since the range of item ids that RoR2 uses is based off of the maximums of checks
|
||||
|
@ -274,39 +274,40 @@ class ItemWeights(Choice):
|
|||
option_void = 9
|
||||
|
||||
|
||||
# define a dictionary for the weights of the generated item pool.
|
||||
ror2_weights: Dict[str, type(Option)] = {
|
||||
"green_scrap": GreenScrap,
|
||||
"red_scrap": RedScrap,
|
||||
"yellow_scrap": YellowScrap,
|
||||
"white_scrap": WhiteScrap,
|
||||
"common_item": CommonItem,
|
||||
"uncommon_item": UncommonItem,
|
||||
"legendary_item": LegendaryItem,
|
||||
"boss_item": BossItem,
|
||||
"lunar_item": LunarItem,
|
||||
"void_item": VoidItem,
|
||||
"equipment": Equipment
|
||||
}
|
||||
|
||||
ror2_options: Dict[str, type(Option)] = {
|
||||
"goal": Goal,
|
||||
"total_locations": TotalLocations,
|
||||
"chests_per_stage": ChestsPerEnvironment,
|
||||
"shrines_per_stage": ShrinesPerEnvironment,
|
||||
"scavengers_per_stage": ScavengersPerEnvironment,
|
||||
"scanner_per_stage": ScannersPerEnvironment,
|
||||
"altars_per_stage": AltarsPerEnvironment,
|
||||
"total_revivals": TotalRevivals,
|
||||
"start_with_revive": StartWithRevive,
|
||||
"final_stage_death": FinalStageDeath,
|
||||
"begin_with_loop": BeginWithLoop,
|
||||
"dlc_sotv": DLC_SOTV,
|
||||
"death_link": DeathLink,
|
||||
"item_pickup_step": ItemPickupStep,
|
||||
"shrine_use_step": ShrineUseStep,
|
||||
"enable_lunar": AllowLunarItems,
|
||||
"item_weights": ItemWeights,
|
||||
"item_pool_presets": ItemPoolPresetToggle,
|
||||
**ror2_weights
|
||||
}
|
||||
|
||||
# define a class for the weights of the generated item pool.
|
||||
@dataclass
|
||||
class ROR2Weights:
|
||||
green_scrap: GreenScrap
|
||||
red_scrap: RedScrap
|
||||
yellow_scrap: YellowScrap
|
||||
white_scrap: WhiteScrap
|
||||
common_item: CommonItem
|
||||
uncommon_item: UncommonItem
|
||||
legendary_item: LegendaryItem
|
||||
boss_item: BossItem
|
||||
lunar_item: LunarItem
|
||||
void_item: VoidItem
|
||||
equipment: Equipment
|
||||
|
||||
@dataclass
|
||||
class ROR2Options(PerGameCommonOptions, ROR2Weights):
|
||||
goal: Goal
|
||||
total_locations: TotalLocations
|
||||
chests_per_stage: ChestsPerEnvironment
|
||||
shrines_per_stage: ShrinesPerEnvironment
|
||||
scavengers_per_stage: ScavengersPerEnvironment
|
||||
scanner_per_stage: ScannersPerEnvironment
|
||||
altars_per_stage: AltarsPerEnvironment
|
||||
total_revivals: TotalRevivals
|
||||
start_with_revive: StartWithRevive
|
||||
final_stage_death: FinalStageDeath
|
||||
begin_with_loop: BeginWithLoop
|
||||
dlc_sotv: DLC_SOTV
|
||||
death_link: DeathLink
|
||||
item_pickup_step: ItemPickupStep
|
||||
shrine_use_step: ShrineUseStep
|
||||
enable_lunar: AllowLunarItems
|
||||
item_weights: ItemWeights
|
||||
item_pool_presets: ItemPoolPresetToggle
|
|
@ -6,7 +6,7 @@ from .Rules import set_rules
|
|||
from .RoR2Environments import *
|
||||
|
||||
from BaseClasses import Region, Entrance, Item, ItemClassification, MultiWorld, Tutorial
|
||||
from .Options import ror2_options, ItemWeights
|
||||
from .Options import ItemWeights, ROR2Options
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Regions import create_regions
|
||||
|
||||
|
@ -28,8 +28,9 @@ class RiskOfRainWorld(World):
|
|||
Combine loot in surprising ways and master each character until you become the havoc you feared upon your
|
||||
first crash landing.
|
||||
"""
|
||||
game: str = "Risk of Rain 2"
|
||||
option_definitions = ror2_options
|
||||
game = "Risk of Rain 2"
|
||||
options_dataclass = ROR2Options
|
||||
options: ROR2Options
|
||||
topology_present = False
|
||||
|
||||
item_name_to_id = item_table
|
||||
|
@ -46,45 +47,44 @@ class RiskOfRainWorld(World):
|
|||
|
||||
def generate_early(self) -> None:
|
||||
# figure out how many revivals should exist in the pool
|
||||
if self.multiworld.goal[self.player] == "classic":
|
||||
total_locations = self.multiworld.total_locations[self.player].value
|
||||
if self.options.goal == "classic":
|
||||
total_locations = self.options.total_locations.value
|
||||
else:
|
||||
total_locations = len(
|
||||
orderedstage_location.get_locations(
|
||||
chests=self.multiworld.chests_per_stage[self.player].value,
|
||||
shrines=self.multiworld.shrines_per_stage[self.player].value,
|
||||
scavengers=self.multiworld.scavengers_per_stage[self.player].value,
|
||||
scanners=self.multiworld.scanner_per_stage[self.player].value,
|
||||
altars=self.multiworld.altars_per_stage[self.player].value,
|
||||
dlc_sotv=self.multiworld.dlc_sotv[self.player].value
|
||||
chests=self.options.chests_per_stage.value,
|
||||
shrines=self.options.shrines_per_stage.value,
|
||||
scavengers=self.options.scavengers_per_stage.value,
|
||||
scanners=self.options.scanner_per_stage.value,
|
||||
altars=self.options.altars_per_stage.value,
|
||||
dlc_sotv=self.options.dlc_sotv.value
|
||||
)
|
||||
)
|
||||
self.total_revivals = int(self.multiworld.total_revivals[self.player].value / 100 *
|
||||
self.total_revivals = int(self.options.total_revivals.value / 100 *
|
||||
total_locations)
|
||||
# self.total_revivals = self.multiworld.total_revivals[self.player].value
|
||||
if self.multiworld.start_with_revive[self.player].value:
|
||||
if self.options.start_with_revive:
|
||||
self.total_revivals -= 1
|
||||
|
||||
def create_items(self) -> None:
|
||||
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
|
||||
if self.multiworld.start_with_revive[self.player]:
|
||||
if self.options.start_with_revive:
|
||||
self.multiworld.push_precollected(self.multiworld.create_item("Dio's Best Friend", self.player))
|
||||
|
||||
environments_pool = {}
|
||||
# only mess with the environments if they are set as items
|
||||
if self.multiworld.goal[self.player] == "explore":
|
||||
if self.options.goal == "explore":
|
||||
|
||||
# figure out all available ordered stages for each tier
|
||||
environment_available_orderedstages_table = environment_vanilla_orderedstages_table
|
||||
if self.multiworld.dlc_sotv[self.player]:
|
||||
if self.options.dlc_sotv:
|
||||
environment_available_orderedstages_table = collapse_dict_list_vertical(environment_available_orderedstages_table, environment_sotv_orderedstages_table)
|
||||
|
||||
environments_pool = shift_by_offset(environment_vanilla_table, environment_offest)
|
||||
|
||||
if self.multiworld.dlc_sotv[self.player]:
|
||||
if self.options.dlc_sotv:
|
||||
environment_offset_table = shift_by_offset(environment_sotv_table, environment_offest)
|
||||
environments_pool = {**environments_pool, **environment_offset_table}
|
||||
environments_to_precollect = 5 if self.multiworld.begin_with_loop[self.player].value else 1
|
||||
environments_to_precollect = 5 if self.options.begin_with_loop else 1
|
||||
# percollect environments for each stage (or just stage 1)
|
||||
for i in range(environments_to_precollect):
|
||||
unlock = self.multiworld.random.choices(list(environment_available_orderedstages_table[i].keys()), k=1)
|
||||
|
@ -100,19 +100,19 @@ class RiskOfRainWorld(World):
|
|||
for env_name, _ in environments_pool.items():
|
||||
itempool += [env_name]
|
||||
|
||||
if self.multiworld.goal[self.player] == "classic":
|
||||
if self.options.goal == "classic":
|
||||
# classic mode
|
||||
total_locations = self.multiworld.total_locations[self.player].value
|
||||
total_locations = self.options.total_locations.value
|
||||
else:
|
||||
# explore mode
|
||||
total_locations = len(
|
||||
orderedstage_location.get_locations(
|
||||
chests=self.multiworld.chests_per_stage[self.player].value,
|
||||
shrines=self.multiworld.shrines_per_stage[self.player].value,
|
||||
scavengers=self.multiworld.scavengers_per_stage[self.player].value,
|
||||
scanners=self.multiworld.scanner_per_stage[self.player].value,
|
||||
altars=self.multiworld.altars_per_stage[self.player].value,
|
||||
dlc_sotv=self.multiworld.dlc_sotv[self.player].value
|
||||
chests=self.options.chests_per_stage.value,
|
||||
shrines=self.options.shrines_per_stage.value,
|
||||
scavengers=self.options.scavengers_per_stage.value,
|
||||
scanners=self.options.scanner_per_stage.value,
|
||||
altars=self.options.altars_per_stage.value,
|
||||
dlc_sotv=self.options.dlc_sotv.value
|
||||
)
|
||||
)
|
||||
# Create junk items
|
||||
|
@ -138,9 +138,9 @@ class RiskOfRainWorld(World):
|
|||
|
||||
def create_junk_pool(self) -> Dict:
|
||||
# if presets are enabled generate junk_pool from the selected preset
|
||||
pool_option = self.multiworld.item_weights[self.player].value
|
||||
pool_option = self.options.item_weights.value
|
||||
junk_pool: Dict[str, int] = {}
|
||||
if self.multiworld.item_pool_presets[self.player]:
|
||||
if self.options.item_pool_presets:
|
||||
# generate chaos weights if the preset is chosen
|
||||
if pool_option == ItemWeights.option_chaos:
|
||||
for name, max_value in item_pool_weights[pool_option].items():
|
||||
|
@ -149,31 +149,31 @@ class RiskOfRainWorld(World):
|
|||
junk_pool = item_pool_weights[pool_option].copy()
|
||||
else: # generate junk pool from user created presets
|
||||
junk_pool = {
|
||||
"Item Scrap, Green": self.multiworld.green_scrap[self.player].value,
|
||||
"Item Scrap, Red": self.multiworld.red_scrap[self.player].value,
|
||||
"Item Scrap, Yellow": self.multiworld.yellow_scrap[self.player].value,
|
||||
"Item Scrap, White": self.multiworld.white_scrap[self.player].value,
|
||||
"Common Item": self.multiworld.common_item[self.player].value,
|
||||
"Uncommon Item": self.multiworld.uncommon_item[self.player].value,
|
||||
"Legendary Item": self.multiworld.legendary_item[self.player].value,
|
||||
"Boss Item": self.multiworld.boss_item[self.player].value,
|
||||
"Lunar Item": self.multiworld.lunar_item[self.player].value,
|
||||
"Void Item": self.multiworld.void_item[self.player].value,
|
||||
"Equipment": self.multiworld.equipment[self.player].value
|
||||
"Item Scrap, Green": self.options.green_scrap.value,
|
||||
"Item Scrap, Red": self.options.red_scrap.value,
|
||||
"Item Scrap, Yellow": self.options.yellow_scrap.value,
|
||||
"Item Scrap, White": self.options.white_scrap.value,
|
||||
"Common Item": self.options.common_item.value,
|
||||
"Uncommon Item": self.options.uncommon_item.value,
|
||||
"Legendary Item": self.options.legendary_item.value,
|
||||
"Boss Item": self.options.boss_item.value,
|
||||
"Lunar Item": self.options.lunar_item.value,
|
||||
"Void Item": self.options.void_item.value,
|
||||
"Equipment": self.options.equipment.value
|
||||
}
|
||||
|
||||
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
|
||||
if not (self.multiworld.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
|
||||
if not (self.options.enable_lunar or pool_option == ItemWeights.option_lunartic):
|
||||
junk_pool.pop("Lunar Item")
|
||||
# remove void items from the pool
|
||||
if not (self.multiworld.dlc_sotv[self.player] or pool_option == ItemWeights.option_void):
|
||||
if not (self.options.dlc_sotv or pool_option == ItemWeights.option_void):
|
||||
junk_pool.pop("Void Item")
|
||||
|
||||
return junk_pool
|
||||
|
||||
def create_regions(self) -> None:
|
||||
|
||||
if self.multiworld.goal[self.player] == "classic":
|
||||
if self.options.goal == "classic":
|
||||
# classic mode
|
||||
menu = create_region(self.multiworld, self.player, "Menu")
|
||||
self.multiworld.regions.append(menu)
|
||||
|
@ -182,7 +182,7 @@ class RiskOfRainWorld(World):
|
|||
victory_region = create_region(self.multiworld, self.player, "Victory")
|
||||
self.multiworld.regions.append(victory_region)
|
||||
petrichor = create_region(self.multiworld, self.player, "Petrichor V",
|
||||
get_classic_item_pickups(self.multiworld.total_locations[self.player].value))
|
||||
get_classic_item_pickups(self.options.total_locations.value))
|
||||
self.multiworld.regions.append(petrichor)
|
||||
|
||||
# classic mode can get to victory from the beginning of the game
|
||||
|
@ -200,21 +200,13 @@ class RiskOfRainWorld(World):
|
|||
create_events(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
options_dict = self.options.as_dict("item_pickup_step", "shrine_use_step", "goal", "total_locations",
|
||||
"chests_per_stage", "shrines_per_stage", "scavengers_per_stage",
|
||||
"scanner_per_stage", "altars_per_stage", "total_revivals", "start_with_revive",
|
||||
"final_stage_death", "death_link", casing="camel")
|
||||
return {
|
||||
"itemPickupStep": self.multiworld.item_pickup_step[self.player].value,
|
||||
"shrineUseStep": self.multiworld.shrine_use_step[self.player].value,
|
||||
"goal": self.multiworld.goal[self.player].value,
|
||||
**options_dict,
|
||||
"seed": "".join(self.multiworld.per_slot_randoms[self.player].choice(string.digits) for _ in range(16)),
|
||||
"totalLocations": self.multiworld.total_locations[self.player].value,
|
||||
"chestsPerStage": self.multiworld.chests_per_stage[self.player].value,
|
||||
"shrinesPerStage": self.multiworld.shrines_per_stage[self.player].value,
|
||||
"scavengersPerStage": self.multiworld.scavengers_per_stage[self.player].value,
|
||||
"scannerPerStage": self.multiworld.scanner_per_stage[self.player].value,
|
||||
"altarsPerStage": self.multiworld.altars_per_stage[self.player].value,
|
||||
"totalRevivals": self.multiworld.total_revivals[self.player].value,
|
||||
"startWithDio": self.multiworld.start_with_revive[self.player].value,
|
||||
"finalStageDeath": self.multiworld.final_stage_death[self.player].value,
|
||||
"deathLink": self.multiworld.death_link[self.player].value,
|
||||
}
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
|
@ -241,12 +233,12 @@ class RiskOfRainWorld(World):
|
|||
|
||||
|
||||
def create_events(world: MultiWorld, player: int) -> None:
|
||||
total_locations = world.total_locations[player].value
|
||||
total_locations = world.worlds[player].options.total_locations.value
|
||||
num_of_events = total_locations // 25
|
||||
if total_locations / 25 == num_of_events:
|
||||
num_of_events -= 1
|
||||
world_region = world.get_region("Petrichor V", player)
|
||||
if world.goal[player] == "classic":
|
||||
if world.worlds[player].options.goal == "classic":
|
||||
# only setup Pickups when using classic_mode
|
||||
for i in range(num_of_events):
|
||||
event_loc = RiskOfRainLocation(player, f"Pickup{(i + 1) * 25}", None, world_region)
|
||||
|
@ -254,7 +246,7 @@ def create_events(world: MultiWorld, player: int) -> None:
|
|||
event_loc.access_rule = \
|
||||
lambda state, i=i: state.can_reach(f"ItemPickup{((i + 1) * 25) - 1}", "Location", player)
|
||||
world_region.locations.append(event_loc)
|
||||
elif world.goal[player] == "explore":
|
||||
elif world.worlds[player].options.goal == "explore":
|
||||
for n in range(1, 6):
|
||||
|
||||
event_region = world.get_region(f"OrderedStage_{n}", player)
|
||||
|
|
|
@ -148,7 +148,7 @@ class SMWorld(World):
|
|||
self.remote_items = self.multiworld.remote_items[self.player]
|
||||
|
||||
if (len(self.variaRando.randoExec.setup.restrictedLocs) > 0):
|
||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
||||
self.multiworld.accessibility[self.player].value = Accessibility.option_minimal
|
||||
logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings")
|
||||
|
||||
def create_items(self):
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import logging
|
||||
from typing import Dict, Any, Iterable, Optional, Union, Set
|
||||
from typing import Dict, Any, Iterable, Optional, Union, Set, List
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState, ItemClassification, MultiWorld
|
||||
from Options import PerGameCommonOptions
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from . import rules, logic, options
|
||||
from . import rules
|
||||
from .bundles import get_all_bundles, Bundle
|
||||
from .items import item_table, create_items, ItemData, Group, items_by_group
|
||||
from .locations import location_table, create_locations, LocationData
|
||||
from .logic import StardewLogic, StardewRule, True_, MAX_MONTHS
|
||||
from .options import stardew_valley_options, StardewOptions, fetch_options
|
||||
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, NumberOfLuckBuffs, NumberOfMovementBuffs, \
|
||||
BackpackProgression, BuildingProgression, ExcludeGingerIsland
|
||||
from .regions import create_regions
|
||||
from .rules import set_rules
|
||||
from worlds.generic.Rules import set_rule
|
||||
from .strings.goal_names import Goal
|
||||
from .strings.goal_names import Goal as GoalName
|
||||
|
||||
client_version = 0
|
||||
|
||||
|
@ -50,7 +52,6 @@ class StardewValleyWorld(World):
|
|||
befriend villagers, and uncover dark secrets.
|
||||
"""
|
||||
game = "Stardew Valley"
|
||||
option_definitions = stardew_valley_options
|
||||
topology_present = False
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
|
@ -59,7 +60,8 @@ class StardewValleyWorld(World):
|
|||
data_version = 3
|
||||
required_client_version = (0, 4, 0)
|
||||
|
||||
options: StardewOptions
|
||||
options_dataclass = StardewValleyOptions
|
||||
options: StardewValleyOptions
|
||||
logic: StardewLogic
|
||||
|
||||
web = StardewWebWorld()
|
||||
|
@ -72,25 +74,24 @@ class StardewValleyWorld(World):
|
|||
self.all_progression_items = set()
|
||||
|
||||
def generate_early(self):
|
||||
self.options = fetch_options(self.multiworld, self.player)
|
||||
self.force_change_options_if_incompatible()
|
||||
|
||||
self.logic = StardewLogic(self.player, self.options)
|
||||
self.modified_bundles = get_all_bundles(self.multiworld.random,
|
||||
self.logic,
|
||||
self.options[options.BundleRandomization],
|
||||
self.options[options.BundlePrice])
|
||||
self.options.bundle_randomization,
|
||||
self.options.bundle_price)
|
||||
|
||||
def force_change_options_if_incompatible(self):
|
||||
goal_is_walnut_hunter = self.options[options.Goal] == options.Goal.option_greatest_walnut_hunter
|
||||
goal_is_perfection = self.options[options.Goal] == options.Goal.option_perfection
|
||||
goal_is_walnut_hunter = self.options.goal == Goal.option_greatest_walnut_hunter
|
||||
goal_is_perfection = self.options.goal == Goal.option_perfection
|
||||
goal_is_island_related = goal_is_walnut_hunter or goal_is_perfection
|
||||
exclude_ginger_island = self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
|
||||
exclude_ginger_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
if goal_is_island_related and exclude_ginger_island:
|
||||
self.options[options.ExcludeGingerIsland] = options.ExcludeGingerIsland.option_false
|
||||
goal = options.Goal.name_lookup[self.options[options.Goal]]
|
||||
self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false
|
||||
goal_name = self.options.goal.current_key
|
||||
player_name = self.multiworld.player_name[self.player]
|
||||
logging.warning(f"Goal '{goal}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
|
||||
logging.warning(f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
|
||||
|
||||
def create_regions(self):
|
||||
def create_region(name: str, exits: Iterable[str]) -> Region:
|
||||
|
@ -116,7 +117,7 @@ class StardewValleyWorld(World):
|
|||
if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK,
|
||||
Group.FRIENDSHIP_PACK)]
|
||||
|
||||
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
|
||||
if self.options.season_randomization == SeasonRandomization.option_disabled:
|
||||
items_to_exclude = [item for item in items_to_exclude
|
||||
if item_table[item.name] not in items_by_group[Group.SEASON]]
|
||||
|
||||
|
@ -134,12 +135,12 @@ class StardewValleyWorld(World):
|
|||
self.setup_victory()
|
||||
|
||||
def precollect_starting_season(self) -> Optional[StardewItem]:
|
||||
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive:
|
||||
if self.options.season_randomization == SeasonRandomization.option_progressive:
|
||||
return
|
||||
|
||||
season_pool = items_by_group[Group.SEASON]
|
||||
|
||||
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
|
||||
if self.options.season_randomization == SeasonRandomization.option_disabled:
|
||||
for season in season_pool:
|
||||
self.multiworld.push_precollected(self.create_item(season))
|
||||
return
|
||||
|
@ -148,18 +149,18 @@ class StardewValleyWorld(World):
|
|||
if item.name in {season.name for season in items_by_group[Group.SEASON]}]:
|
||||
return
|
||||
|
||||
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_randomized_not_winter:
|
||||
if self.options.season_randomization == SeasonRandomization.option_randomized_not_winter:
|
||||
season_pool = [season for season in season_pool if season.name != "Winter"]
|
||||
|
||||
starting_season = self.create_item(self.multiworld.random.choice(season_pool))
|
||||
self.multiworld.push_precollected(starting_season)
|
||||
|
||||
def setup_early_items(self):
|
||||
if (self.options[options.BuildingProgression] ==
|
||||
options.BuildingProgression.option_progressive_early_shipping_bin):
|
||||
if (self.options.building_progression ==
|
||||
BuildingProgression.option_progressive_early_shipping_bin):
|
||||
self.multiworld.early_items[self.player]["Shipping Bin"] = 1
|
||||
|
||||
if self.options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive:
|
||||
if self.options.backpack_progression == BackpackProgression.option_early_progressive:
|
||||
self.multiworld.early_items[self.player]["Progressive Backpack"] = 1
|
||||
|
||||
def setup_month_events(self):
|
||||
|
@ -172,40 +173,40 @@ class StardewValleyWorld(World):
|
|||
self.create_event_location(month_end, self.logic.received("Month End", i).simplify(), "Month End")
|
||||
|
||||
def setup_victory(self):
|
||||
if self.options[options.Goal] == options.Goal.option_community_center:
|
||||
self.create_event_location(location_table[Goal.community_center],
|
||||
if self.options.goal == Goal.option_community_center:
|
||||
self.create_event_location(location_table[GoalName.community_center],
|
||||
self.logic.can_complete_community_center().simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_grandpa_evaluation:
|
||||
self.create_event_location(location_table[Goal.grandpa_evaluation],
|
||||
elif self.options.goal == Goal.option_grandpa_evaluation:
|
||||
self.create_event_location(location_table[GoalName.grandpa_evaluation],
|
||||
self.logic.can_finish_grandpa_evaluation().simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_bottom_of_the_mines:
|
||||
self.create_event_location(location_table[Goal.bottom_of_the_mines],
|
||||
elif self.options.goal == Goal.option_bottom_of_the_mines:
|
||||
self.create_event_location(location_table[GoalName.bottom_of_the_mines],
|
||||
self.logic.can_mine_to_floor(120).simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_cryptic_note:
|
||||
self.create_event_location(location_table[Goal.cryptic_note],
|
||||
elif self.options.goal == Goal.option_cryptic_note:
|
||||
self.create_event_location(location_table[GoalName.cryptic_note],
|
||||
self.logic.can_complete_quest("Cryptic Note").simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_master_angler:
|
||||
self.create_event_location(location_table[Goal.master_angler],
|
||||
elif self.options.goal == Goal.option_master_angler:
|
||||
self.create_event_location(location_table[GoalName.master_angler],
|
||||
self.logic.can_catch_every_fish().simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_complete_collection:
|
||||
self.create_event_location(location_table[Goal.complete_museum],
|
||||
elif self.options.goal == Goal.option_complete_collection:
|
||||
self.create_event_location(location_table[GoalName.complete_museum],
|
||||
self.logic.can_complete_museum().simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_full_house:
|
||||
self.create_event_location(location_table[Goal.full_house],
|
||||
elif self.options.goal == Goal.option_full_house:
|
||||
self.create_event_location(location_table[GoalName.full_house],
|
||||
(self.logic.has_children(2) & self.logic.can_reproduce()).simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_greatest_walnut_hunter:
|
||||
self.create_event_location(location_table[Goal.greatest_walnut_hunter],
|
||||
elif self.options.goal == Goal.option_greatest_walnut_hunter:
|
||||
self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
|
||||
self.logic.has_walnut(130).simplify(),
|
||||
"Victory")
|
||||
elif self.options[options.Goal] == options.Goal.option_perfection:
|
||||
self.create_event_location(location_table[Goal.perfection],
|
||||
elif self.options.goal == Goal.option_perfection:
|
||||
self.create_event_location(location_table[GoalName.perfection],
|
||||
self.logic.has_everything(self.all_progression_items).simplify(),
|
||||
"Victory")
|
||||
|
||||
|
@ -230,7 +231,7 @@ class StardewValleyWorld(World):
|
|||
location.place_locked_item(self.create_item(item))
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.multiworld, self.player, self.options, self.logic, self.modified_bundles)
|
||||
set_rules(self)
|
||||
self.force_first_month_once_all_early_items_are_found()
|
||||
|
||||
def force_first_month_once_all_early_items_are_found(self):
|
||||
|
@ -276,11 +277,12 @@ class StardewValleyWorld(World):
|
|||
key, value = self.modified_bundles[bundle_key].to_pair()
|
||||
modified_bundles[key] = value
|
||||
|
||||
excluded_options = [options.BundleRandomization, options.BundlePrice,
|
||||
options.NumberOfMovementBuffs, options.NumberOfLuckBuffs]
|
||||
slot_data = dict(self.options.options)
|
||||
for option in excluded_options:
|
||||
slot_data.pop(option.internal_name)
|
||||
excluded_options = [BundleRandomization, BundlePrice, NumberOfMovementBuffs, NumberOfLuckBuffs]
|
||||
excluded_option_names = [option.internal_name for option in excluded_options]
|
||||
generic_option_names = [option_name for option_name in PerGameCommonOptions.type_hints]
|
||||
excluded_option_names.extend(generic_option_names)
|
||||
included_option_names: List[str] = [option_name for option_name in self.options_dataclass.type_hints if option_name not in excluded_option_names]
|
||||
slot_data = self.options.as_dict(*included_option_names)
|
||||
slot_data.update({
|
||||
"seed": self.multiworld.per_slot_randoms[self.player].randrange(1000000000), # Seed should be max 9 digits
|
||||
"randomized_entrances": self.randomized_entrances,
|
||||
|
|
|
@ -152,7 +152,7 @@ class Bundle:
|
|||
# shuffle_vault_amongst_themselves(random, bundles)
|
||||
|
||||
|
||||
def get_all_bundles(random: Random, logic: StardewLogic, randomization: int, price: int) -> Dict[str, Bundle]:
|
||||
def get_all_bundles(random: Random, logic: StardewLogic, randomization: BundleRandomization, price: BundlePrice) -> Dict[str, Bundle]:
|
||||
bundles = {}
|
||||
for bundle_key in vanilla_bundles:
|
||||
bundle_value = vanilla_bundles[bundle_key]
|
||||
|
|
|
@ -7,10 +7,11 @@ from random import Random
|
|||
from typing import Dict, List, Protocol, Union, Set, Optional
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
from . import options, data
|
||||
from . import data
|
||||
from .data.villagers_data import all_villagers
|
||||
from .mods.mod_data import ModNames
|
||||
from .options import StardewOptions
|
||||
from .options import StardewValleyOptions, TrapItems, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Cropsanity, Friendsanity, Museumsanity, \
|
||||
Fishsanity, BuildingProgression, SkillProgression, ToolProgression, ElevatorProgression, BackpackProgression, ArcadeMachineLocations
|
||||
from .strings.ap_names.buff_names import Buff
|
||||
|
||||
ITEM_CODE_OFFSET = 717000
|
||||
|
@ -138,10 +139,9 @@ initialize_groups()
|
|||
|
||||
|
||||
def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item],
|
||||
world_options: StardewOptions,
|
||||
random: Random) -> List[Item]:
|
||||
options: StardewValleyOptions, random: Random) -> List[Item]:
|
||||
items = []
|
||||
unique_items = create_unique_items(item_factory, world_options, random)
|
||||
unique_items = create_unique_items(item_factory, options, random)
|
||||
|
||||
for item in items_to_exclude:
|
||||
if item in unique_items:
|
||||
|
@ -151,58 +151,58 @@ def create_items(item_factory: StardewItemFactory, locations_count: int, items_t
|
|||
items += unique_items
|
||||
logger.debug(f"Created {len(unique_items)} unique items")
|
||||
|
||||
unique_filler_items = create_unique_filler_items(item_factory, world_options, random, locations_count - len(items))
|
||||
unique_filler_items = create_unique_filler_items(item_factory, options, random, locations_count - len(items))
|
||||
items += unique_filler_items
|
||||
logger.debug(f"Created {len(unique_filler_items)} unique filler items")
|
||||
|
||||
resource_pack_items = fill_with_resource_packs_and_traps(item_factory, world_options, random, items, locations_count)
|
||||
resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items, locations_count)
|
||||
items += resource_pack_items
|
||||
logger.debug(f"Created {len(resource_pack_items)} resource packs")
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def create_unique_items(item_factory: StardewItemFactory, world_options: StardewOptions, random: Random) -> List[Item]:
|
||||
def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random) -> List[Item]:
|
||||
items = []
|
||||
|
||||
items.extend(item_factory(item) for item in items_by_group[Group.COMMUNITY_REWARD])
|
||||
|
||||
create_backpack_items(item_factory, world_options, items)
|
||||
create_backpack_items(item_factory, options, items)
|
||||
create_mine_rewards(item_factory, items, random)
|
||||
create_elevators(item_factory, world_options, items)
|
||||
create_tools(item_factory, world_options, items)
|
||||
create_skills(item_factory, world_options, items)
|
||||
create_wizard_buildings(item_factory, world_options, items)
|
||||
create_carpenter_buildings(item_factory, world_options, items)
|
||||
create_elevators(item_factory, options, items)
|
||||
create_tools(item_factory, options, items)
|
||||
create_skills(item_factory, options, items)
|
||||
create_wizard_buildings(item_factory, options, items)
|
||||
create_carpenter_buildings(item_factory, options, items)
|
||||
items.append(item_factory("Beach Bridge"))
|
||||
items.append(item_factory("Dark Talisman"))
|
||||
create_tv_channels(item_factory, items)
|
||||
create_special_quest_rewards(item_factory, items)
|
||||
create_stardrops(item_factory, world_options, items)
|
||||
create_museum_items(item_factory, world_options, items)
|
||||
create_arcade_machine_items(item_factory, world_options, items)
|
||||
create_stardrops(item_factory, options, items)
|
||||
create_museum_items(item_factory, options, items)
|
||||
create_arcade_machine_items(item_factory, options, items)
|
||||
items.append(item_factory(random.choice(items_by_group[Group.GALAXY_WEAPONS])))
|
||||
create_player_buffs(item_factory, world_options, items)
|
||||
create_player_buffs(item_factory, options, items)
|
||||
create_traveling_merchant_items(item_factory, items)
|
||||
items.append(item_factory("Return Scepter"))
|
||||
create_seasons(item_factory, world_options, items)
|
||||
create_seeds(item_factory, world_options, items)
|
||||
create_friendsanity_items(item_factory, world_options, items)
|
||||
create_festival_rewards(item_factory, world_options, items)
|
||||
create_seasons(item_factory, options, items)
|
||||
create_seeds(item_factory, options, items)
|
||||
create_friendsanity_items(item_factory, options, items)
|
||||
create_festival_rewards(item_factory, options, items)
|
||||
create_babies(item_factory, items, random)
|
||||
create_special_order_board_rewards(item_factory, world_options, items)
|
||||
create_special_order_qi_rewards(item_factory, world_options, items)
|
||||
create_walnut_purchase_rewards(item_factory, world_options, items)
|
||||
create_magic_mod_spells(item_factory, world_options, items)
|
||||
create_special_order_board_rewards(item_factory, options, items)
|
||||
create_special_order_qi_rewards(item_factory, options, items)
|
||||
create_walnut_purchase_rewards(item_factory, options, items)
|
||||
create_magic_mod_spells(item_factory, options, items)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def create_backpack_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if (world_options[options.BackpackProgression] == options.BackpackProgression.option_progressive or
|
||||
world_options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive):
|
||||
def create_backpack_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if (options.backpack_progression == BackpackProgression.option_progressive or
|
||||
options.backpack_progression == BackpackProgression.option_early_progressive):
|
||||
items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2)
|
||||
if ModNames.big_backpack in world_options[options.Mods]:
|
||||
if ModNames.big_backpack in options.mods:
|
||||
items.append(item_factory("Progressive Backpack"))
|
||||
|
||||
|
||||
|
@ -220,46 +220,46 @@ def create_mine_rewards(item_factory: StardewItemFactory, items: List[Item], ran
|
|||
items.append(item_factory("Skull Key"))
|
||||
|
||||
|
||||
def create_elevators(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla:
|
||||
def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.elevator_progression == ElevatorProgression.option_vanilla:
|
||||
return
|
||||
|
||||
items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24])
|
||||
if ModNames.deepwoods in world_options[options.Mods]:
|
||||
if ModNames.deepwoods in options.mods:
|
||||
items.extend([item_factory(item) for item in ["Progressive Woods Obelisk Sigils"] * 10])
|
||||
if ModNames.skull_cavern_elevator in world_options[options.Mods]:
|
||||
if ModNames.skull_cavern_elevator in options.mods:
|
||||
items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8])
|
||||
|
||||
|
||||
def create_tools(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
def create_tools(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.tool_progression == ToolProgression.option_progressive:
|
||||
items.extend(item_factory(item) for item in items_by_group[Group.PROGRESSIVE_TOOLS] * 4)
|
||||
items.append(item_factory("Golden Scythe"))
|
||||
|
||||
|
||||
def create_skills(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
def create_skills(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.skill_progression == SkillProgression.option_progressive:
|
||||
for item in items_by_group[Group.SKILL_LEVEL_UP]:
|
||||
if item.mod_name not in world_options[options.Mods] and item.mod_name is not None:
|
||||
if item.mod_name not in options.mods and item.mod_name is not None:
|
||||
continue
|
||||
items.extend(item_factory(item) for item in [item.name] * 10)
|
||||
|
||||
|
||||
def create_wizard_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
items.append(item_factory("Earth Obelisk"))
|
||||
items.append(item_factory("Water Obelisk"))
|
||||
items.append(item_factory("Desert Obelisk"))
|
||||
items.append(item_factory("Junimo Hut"))
|
||||
items.append(item_factory("Gold Clock"))
|
||||
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false:
|
||||
if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
|
||||
items.append(item_factory("Island Obelisk"))
|
||||
if ModNames.deepwoods in world_options[options.Mods]:
|
||||
if ModNames.deepwoods in options.mods:
|
||||
items.append(item_factory("Woods Obelisk"))
|
||||
|
||||
|
||||
def create_carpenter_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.BuildingProgression] in {options.BuildingProgression.option_progressive,
|
||||
options.BuildingProgression.option_progressive_early_shipping_bin}:
|
||||
def create_carpenter_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.building_progression in {BuildingProgression.option_progressive,
|
||||
BuildingProgression.option_progressive_early_shipping_bin}:
|
||||
items.append(item_factory("Progressive Coop"))
|
||||
items.append(item_factory("Progressive Coop"))
|
||||
items.append(item_factory("Progressive Coop"))
|
||||
|
@ -278,7 +278,7 @@ def create_carpenter_buildings(item_factory: StardewItemFactory, world_options:
|
|||
items.append(item_factory("Progressive House"))
|
||||
items.append(item_factory("Progressive House"))
|
||||
items.append(item_factory("Progressive House"))
|
||||
if ModNames.tractor in world_options[options.Mods]:
|
||||
if ModNames.tractor in options.mods:
|
||||
items.append(item_factory("Tractor Garage"))
|
||||
|
||||
|
||||
|
@ -290,17 +290,17 @@ def create_special_quest_rewards(item_factory: StardewItemFactory, items: List[I
|
|||
items.append(item_factory("Iridium Snake Milk"))
|
||||
|
||||
|
||||
def create_stardrops(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
def create_stardrops(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
items.append(item_factory("Stardrop")) # The Mines level 100
|
||||
items.append(item_factory("Stardrop")) # Old Master Cannoli
|
||||
if world_options[options.Fishsanity] != options.Fishsanity.option_none:
|
||||
if options.fishsanity != Fishsanity.option_none:
|
||||
items.append(item_factory("Stardrop")) #Master Angler Stardrop
|
||||
if ModNames.deepwoods in world_options[options.Mods]:
|
||||
if ModNames.deepwoods in options.mods:
|
||||
items.append(item_factory("Stardrop")) # Petting the Unicorn
|
||||
|
||||
|
||||
def create_museum_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.Museumsanity] == options.Museumsanity.option_none:
|
||||
def create_museum_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.museumsanity == Museumsanity.option_none:
|
||||
return
|
||||
items.extend(item_factory(item) for item in ["Magic Rock Candy"] * 5)
|
||||
items.extend(item_factory(item) for item in ["Ancient Seeds"] * 5)
|
||||
|
@ -311,17 +311,17 @@ def create_museum_items(item_factory: StardewItemFactory, world_options: Stardew
|
|||
items.append(item_factory("Dwarvish Translation Guide"))
|
||||
|
||||
|
||||
def create_friendsanity_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.Friendsanity] == options.Friendsanity.option_none:
|
||||
def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.friendsanity == Friendsanity.option_none:
|
||||
return
|
||||
exclude_non_bachelors = world_options[options.Friendsanity] == options.Friendsanity.option_bachelors
|
||||
exclude_locked_villagers = world_options[options.Friendsanity] == options.Friendsanity.option_starting_npcs or \
|
||||
world_options[options.Friendsanity] == options.Friendsanity.option_bachelors
|
||||
include_post_marriage_hearts = world_options[options.Friendsanity] == options.Friendsanity.option_all_with_marriage
|
||||
exclude_ginger_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
|
||||
heart_size = world_options[options.FriendsanityHeartSize]
|
||||
exclude_non_bachelors = options.friendsanity == Friendsanity.option_bachelors
|
||||
exclude_locked_villagers = options.friendsanity == Friendsanity.option_starting_npcs or \
|
||||
options.friendsanity == Friendsanity.option_bachelors
|
||||
include_post_marriage_hearts = options.friendsanity == Friendsanity.option_all_with_marriage
|
||||
exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
heart_size = options.friendsanity_heart_size
|
||||
for villager in all_villagers:
|
||||
if villager.mod_name not in world_options[options.Mods] and villager.mod_name is not None:
|
||||
if villager.mod_name not in options.mods and villager.mod_name is not None:
|
||||
continue
|
||||
if not villager.available and exclude_locked_villagers:
|
||||
continue
|
||||
|
@ -350,8 +350,8 @@ def create_babies(item_factory: StardewItemFactory, items: List[Item], random: R
|
|||
items.append(item_factory(chosen_baby))
|
||||
|
||||
|
||||
def create_arcade_machine_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
|
||||
def create_arcade_machine_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.arcade_machine_locations == ArcadeMachineLocations.option_full_shuffling:
|
||||
items.append(item_factory("JotPK: Progressive Boots"))
|
||||
items.append(item_factory("JotPK: Progressive Boots"))
|
||||
items.append(item_factory("JotPK: Progressive Gun"))
|
||||
|
@ -367,11 +367,9 @@ def create_arcade_machine_items(item_factory: StardewItemFactory, world_options:
|
|||
items.extend(item_factory(item) for item in ["Junimo Kart: Extra Life"] * 8)
|
||||
|
||||
|
||||
def create_player_buffs(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
|
||||
number_of_movement_buffs: int = world_options[options.NumberOfMovementBuffs]
|
||||
number_of_luck_buffs: int = world_options[options.NumberOfLuckBuffs]
|
||||
items.extend(item_factory(item) for item in [Buff.movement] * number_of_movement_buffs)
|
||||
items.extend(item_factory(item) for item in [Buff.luck] * number_of_luck_buffs)
|
||||
def create_player_buffs(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
items.extend(item_factory(item) for item in [Buff.movement] * options.number_of_movement_buffs.value)
|
||||
items.extend(item_factory(item) for item in [Buff.luck] * options.number_of_luck_buffs.value)
|
||||
|
||||
|
||||
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
|
||||
|
@ -380,36 +378,36 @@ def create_traveling_merchant_items(item_factory: StardewItemFactory, items: Lis
|
|||
*(item_factory(item) for item in ["Traveling Merchant Discount"] * 8)])
|
||||
|
||||
|
||||
def create_seasons(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
|
||||
def create_seasons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.season_randomization == SeasonRandomization.option_disabled:
|
||||
return
|
||||
|
||||
if world_options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive:
|
||||
if options.season_randomization == SeasonRandomization.option_progressive:
|
||||
items.extend([item_factory(item) for item in ["Progressive Season"] * 3])
|
||||
return
|
||||
|
||||
items.extend([item_factory(item) for item in items_by_group[Group.SEASON]])
|
||||
|
||||
|
||||
def create_seeds(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.Cropsanity] == options.Cropsanity.option_disabled:
|
||||
def create_seeds(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.cropsanity == Cropsanity.option_disabled:
|
||||
return
|
||||
|
||||
include_ginger_island = world_options[options.ExcludeGingerIsland] != options.ExcludeGingerIsland.option_true
|
||||
include_ginger_island = options.exclude_ginger_island != ExcludeGingerIsland.option_true
|
||||
seed_items = [item_factory(item) for item in items_by_group[Group.CROPSANITY] if include_ginger_island or Group.GINGER_ISLAND not in item.groups]
|
||||
items.extend(seed_items)
|
||||
|
||||
|
||||
def create_festival_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.FestivalLocations] == options.FestivalLocations.option_disabled:
|
||||
def create_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.festival_locations == FestivalLocations.option_disabled:
|
||||
return
|
||||
|
||||
items.extend([*[item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler],
|
||||
item_factory("Stardrop")])
|
||||
|
||||
|
||||
def create_walnut_purchase_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return
|
||||
|
||||
items.extend([item_factory("Boat Repair"),
|
||||
|
@ -420,16 +418,16 @@ def create_walnut_purchase_rewards(item_factory: StardewItemFactory, world_optio
|
|||
|
||||
|
||||
|
||||
def create_special_order_board_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled:
|
||||
def create_special_order_board_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if options.special_order_locations == SpecialOrderLocations.option_disabled:
|
||||
return
|
||||
|
||||
items.extend([item_factory(item) for item in items_by_group[Group.SPECIAL_ORDER_BOARD]])
|
||||
|
||||
|
||||
def create_special_order_qi_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if (world_options[options.SpecialOrderLocations] != options.SpecialOrderLocations.option_board_qi or
|
||||
world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true):
|
||||
def create_special_order_qi_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if (options.special_order_locations != SpecialOrderLocations.option_board_qi or
|
||||
options.exclude_ginger_island == ExcludeGingerIsland.option_true):
|
||||
return
|
||||
qi_gem_rewards = ["100 Qi Gems", "10 Qi Gems", "40 Qi Gems", "25 Qi Gems", "25 Qi Gems",
|
||||
"40 Qi Gems", "20 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems"]
|
||||
|
@ -441,35 +439,35 @@ def create_tv_channels(item_factory: StardewItemFactory, items: List[Item]):
|
|||
items.extend([item_factory(item) for item in items_by_group[Group.TV_CHANNEL]])
|
||||
|
||||
|
||||
def create_filler_festival_rewards(item_factory: StardewItemFactory, world_options: StardewOptions) -> List[Item]:
|
||||
if world_options[options.FestivalLocations] == options.FestivalLocations.option_disabled:
|
||||
def create_filler_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions) -> List[Item]:
|
||||
if options.festival_locations == FestivalLocations.option_disabled:
|
||||
return []
|
||||
|
||||
return [item_factory(item) for item in items_by_group[Group.FESTIVAL] if
|
||||
item.classification == ItemClassification.filler]
|
||||
|
||||
|
||||
def create_magic_mod_spells(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
|
||||
if ModNames.magic not in world_options[options.Mods]:
|
||||
def create_magic_mod_spells(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
||||
if ModNames.magic not in options.mods:
|
||||
return []
|
||||
items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]])
|
||||
|
||||
|
||||
def create_unique_filler_items(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random,
|
||||
def create_unique_filler_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
|
||||
available_item_slots: int) -> List[Item]:
|
||||
items = []
|
||||
|
||||
items.extend(create_filler_festival_rewards(item_factory, world_options))
|
||||
items.extend(create_filler_festival_rewards(item_factory, options))
|
||||
|
||||
if len(items) > available_item_slots:
|
||||
items = random.sample(items, available_item_slots)
|
||||
return items
|
||||
|
||||
|
||||
def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random,
|
||||
def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
|
||||
items_already_added: List[Item],
|
||||
number_locations: int) -> List[Item]:
|
||||
include_traps = world_options[options.TrapItems] != options.TrapItems.option_no_traps
|
||||
include_traps = options.trap_items != TrapItems.option_no_traps
|
||||
all_filler_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK]]
|
||||
all_filler_packs.extend(items_by_group[Group.TRASH])
|
||||
if include_traps:
|
||||
|
@ -479,15 +477,15 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, world_o
|
|||
if pack.name not in items_already_added_names]
|
||||
trap_items = [pack for pack in items_by_group[Group.TRAP]
|
||||
if pack.name not in items_already_added_names and
|
||||
(pack.mod_name is None or pack.mod_name in world_options[options.Mods])]
|
||||
(pack.mod_name is None or pack.mod_name in options.mods)]
|
||||
|
||||
priority_filler_items = []
|
||||
priority_filler_items.extend(useful_resource_packs)
|
||||
if include_traps:
|
||||
priority_filler_items.extend(trap_items)
|
||||
|
||||
all_filler_packs = remove_excluded_packs(all_filler_packs, world_options)
|
||||
priority_filler_items = remove_excluded_packs(priority_filler_items, world_options)
|
||||
all_filler_packs = remove_excluded_packs(all_filler_packs, options)
|
||||
priority_filler_items = remove_excluded_packs(priority_filler_items, options)
|
||||
|
||||
number_priority_items = len(priority_filler_items)
|
||||
required_resource_pack = number_locations - len(items_already_added)
|
||||
|
@ -521,8 +519,8 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, world_o
|
|||
return items
|
||||
|
||||
|
||||
def remove_excluded_packs(packs, world_options):
|
||||
def remove_excluded_packs(packs, options: StardewValleyOptions):
|
||||
included_packs = [pack for pack in packs if Group.DEPRECATED not in pack.groups]
|
||||
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
included_packs = [pack for pack in included_packs if Group.GINGER_ISLAND not in pack.groups]
|
||||
return included_packs
|
||||
|
|
|
@ -4,10 +4,12 @@ from dataclasses import dataclass
|
|||
from random import Random
|
||||
from typing import Optional, Dict, Protocol, List, FrozenSet
|
||||
|
||||
from . import options, data
|
||||
from . import data
|
||||
from .options import StardewValleyOptions
|
||||
from .data.fish_data import legendary_fish, special_fish, all_fish
|
||||
from .data.museum_data import all_museum_items
|
||||
from .data.villagers_data import all_villagers
|
||||
from .options import ExcludeGingerIsland, Friendsanity, ArcadeMachineLocations, SpecialOrderLocations, Cropsanity, Fishsanity, Museumsanity, FestivalLocations, SkillProgression, BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression
|
||||
from .strings.goal_names import Goal
|
||||
from .strings.region_names import Region
|
||||
|
||||
|
@ -133,12 +135,12 @@ def initialize_groups():
|
|||
initialize_groups()
|
||||
|
||||
|
||||
def extend_cropsanity_locations(randomized_locations: List[LocationData], world_options):
|
||||
if world_options[options.Cropsanity] == options.Cropsanity.option_disabled:
|
||||
def extend_cropsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.cropsanity == Cropsanity.option_disabled:
|
||||
return
|
||||
|
||||
cropsanity_locations = locations_by_tag[LocationTags.CROPSANITY]
|
||||
cropsanity_locations = filter_ginger_island(world_options, cropsanity_locations)
|
||||
cropsanity_locations = filter_ginger_island(options, cropsanity_locations)
|
||||
randomized_locations.extend(cropsanity_locations)
|
||||
|
||||
|
||||
|
@ -157,56 +159,56 @@ def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_
|
|||
randomized_locations.append(location_table[f"Help Wanted: Gathering {batch + 1}"])
|
||||
|
||||
|
||||
def extend_fishsanity_locations(randomized_locations: List[LocationData], world_options, random: Random):
|
||||
def extend_fishsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
|
||||
prefix = "Fishsanity: "
|
||||
if world_options[options.Fishsanity] == options.Fishsanity.option_none:
|
||||
if options.fishsanity == Fishsanity.option_none:
|
||||
return
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_legendaries:
|
||||
elif options.fishsanity == Fishsanity.option_legendaries:
|
||||
randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish)
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_special:
|
||||
elif options.fishsanity == Fishsanity.option_special:
|
||||
randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish)
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_randomized:
|
||||
elif options.fishsanity == Fishsanity.option_randomized:
|
||||
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if random.random() < 0.4]
|
||||
randomized_locations.extend(filter_ginger_island(world_options, fish_locations))
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_all:
|
||||
randomized_locations.extend(filter_ginger_island(options, fish_locations))
|
||||
elif options.fishsanity == Fishsanity.option_all:
|
||||
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish]
|
||||
randomized_locations.extend(filter_ginger_island(world_options, fish_locations))
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_exclude_legendaries:
|
||||
randomized_locations.extend(filter_ginger_island(options, fish_locations))
|
||||
elif options.fishsanity == Fishsanity.option_exclude_legendaries:
|
||||
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish not in legendary_fish]
|
||||
randomized_locations.extend(filter_ginger_island(world_options, fish_locations))
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_exclude_hard_fish:
|
||||
randomized_locations.extend(filter_ginger_island(options, fish_locations))
|
||||
elif options.fishsanity == Fishsanity.option_exclude_hard_fish:
|
||||
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish.difficulty < 80]
|
||||
randomized_locations.extend(filter_ginger_island(world_options, fish_locations))
|
||||
elif world_options[options.Fishsanity] == options.Fishsanity.option_only_easy_fish:
|
||||
randomized_locations.extend(filter_ginger_island(options, fish_locations))
|
||||
elif options.fishsanity == Fishsanity.option_only_easy_fish:
|
||||
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish.difficulty < 50]
|
||||
randomized_locations.extend(filter_ginger_island(world_options, fish_locations))
|
||||
randomized_locations.extend(filter_ginger_island(options, fish_locations))
|
||||
|
||||
|
||||
def extend_museumsanity_locations(randomized_locations: List[LocationData], museumsanity: int, random: Random):
|
||||
def extend_museumsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
|
||||
prefix = "Museumsanity: "
|
||||
if museumsanity == options.Museumsanity.option_none:
|
||||
if options.museumsanity == Museumsanity.option_none:
|
||||
return
|
||||
elif museumsanity == options.Museumsanity.option_milestones:
|
||||
elif options.museumsanity == Museumsanity.option_milestones:
|
||||
randomized_locations.extend(locations_by_tag[LocationTags.MUSEUM_MILESTONES])
|
||||
elif museumsanity == options.Museumsanity.option_randomized:
|
||||
elif options.museumsanity == Museumsanity.option_randomized:
|
||||
randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"]
|
||||
for museum_item in all_museum_items if random.random() < 0.4)
|
||||
elif museumsanity == options.Museumsanity.option_all:
|
||||
elif options.museumsanity == Museumsanity.option_all:
|
||||
randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"] for museum_item in all_museum_items)
|
||||
|
||||
|
||||
def extend_friendsanity_locations(randomized_locations: List[LocationData], world_options: options.StardewOptions):
|
||||
if world_options[options.Friendsanity] == options.Friendsanity.option_none:
|
||||
def extend_friendsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.friendsanity == Friendsanity.option_none:
|
||||
return
|
||||
|
||||
exclude_leo = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
|
||||
exclude_non_bachelors = world_options[options.Friendsanity] == options.Friendsanity.option_bachelors
|
||||
exclude_locked_villagers = world_options[options.Friendsanity] == options.Friendsanity.option_starting_npcs or \
|
||||
world_options[options.Friendsanity] == options.Friendsanity.option_bachelors
|
||||
include_post_marriage_hearts = world_options[options.Friendsanity] == options.Friendsanity.option_all_with_marriage
|
||||
heart_size = world_options[options.FriendsanityHeartSize]
|
||||
exclude_leo = options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
exclude_non_bachelors = options.friendsanity == Friendsanity.option_bachelors
|
||||
exclude_locked_villagers = options.friendsanity == Friendsanity.option_starting_npcs or \
|
||||
options.friendsanity == Friendsanity.option_bachelors
|
||||
include_post_marriage_hearts = options.friendsanity == Friendsanity.option_all_with_marriage
|
||||
heart_size = options.friendsanity_heart_size
|
||||
for villager in all_villagers:
|
||||
if villager.mod_name not in world_options[options.Mods] and villager.mod_name is not None:
|
||||
if villager.mod_name not in options.mods and villager.mod_name is not None:
|
||||
continue
|
||||
if not villager.available and exclude_locked_villagers:
|
||||
continue
|
||||
|
@ -228,38 +230,38 @@ def extend_friendsanity_locations(randomized_locations: List[LocationData], worl
|
|||
randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"])
|
||||
|
||||
|
||||
def extend_festival_locations(randomized_locations: List[LocationData], festival_option: int):
|
||||
if festival_option == options.FestivalLocations.option_disabled:
|
||||
def extend_festival_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.festival_locations == FestivalLocations.option_disabled:
|
||||
return
|
||||
|
||||
festival_locations = locations_by_tag[LocationTags.FESTIVAL]
|
||||
randomized_locations.extend(festival_locations)
|
||||
extend_hard_festival_locations(randomized_locations, festival_option)
|
||||
extend_hard_festival_locations(randomized_locations, options)
|
||||
|
||||
|
||||
def extend_hard_festival_locations(randomized_locations, festival_option: int):
|
||||
if festival_option != options.FestivalLocations.option_hard:
|
||||
def extend_hard_festival_locations(randomized_locations, options: StardewValleyOptions):
|
||||
if options.festival_locations != FestivalLocations.option_hard:
|
||||
return
|
||||
|
||||
hard_festival_locations = locations_by_tag[LocationTags.FESTIVAL_HARD]
|
||||
randomized_locations.extend(hard_festival_locations)
|
||||
|
||||
|
||||
def extend_special_order_locations(randomized_locations: List[LocationData], world_options):
|
||||
if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled:
|
||||
def extend_special_order_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.special_order_locations == SpecialOrderLocations.option_disabled:
|
||||
return
|
||||
|
||||
include_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false
|
||||
board_locations = filter_disabled_locations(world_options, locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD])
|
||||
include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false
|
||||
board_locations = filter_disabled_locations(options, locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD])
|
||||
randomized_locations.extend(board_locations)
|
||||
if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_board_qi and include_island:
|
||||
include_arcade = world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_disabled
|
||||
if options.special_order_locations == SpecialOrderLocations.option_board_qi and include_island:
|
||||
include_arcade = options.arcade_machine_locations != ArcadeMachineLocations.option_disabled
|
||||
qi_orders = [location for location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI] if include_arcade or LocationTags.JUNIMO_KART not in location.tags]
|
||||
randomized_locations.extend(qi_orders)
|
||||
|
||||
|
||||
def extend_walnut_purchase_locations(randomized_locations: List[LocationData], world_options):
|
||||
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
def extend_walnut_purchase_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return
|
||||
randomized_locations.append(location_table["Repair Ticket Machine"])
|
||||
randomized_locations.append(location_table["Repair Boat Hull"])
|
||||
|
@ -269,82 +271,82 @@ def extend_walnut_purchase_locations(randomized_locations: List[LocationData], w
|
|||
randomized_locations.extend(locations_by_tag[LocationTags.WALNUT_PURCHASE])
|
||||
|
||||
|
||||
def extend_mandatory_locations(randomized_locations: List[LocationData], world_options):
|
||||
def extend_mandatory_locations(randomized_locations: List[LocationData], options):
|
||||
mandatory_locations = [location for location in locations_by_tag[LocationTags.MANDATORY]]
|
||||
filtered_mandatory_locations = filter_disabled_locations(world_options, mandatory_locations)
|
||||
filtered_mandatory_locations = filter_disabled_locations(options, mandatory_locations)
|
||||
randomized_locations.extend(filtered_mandatory_locations)
|
||||
|
||||
|
||||
def extend_backpack_locations(randomized_locations: List[LocationData], world_options):
|
||||
if world_options[options.BackpackProgression] == options.BackpackProgression.option_vanilla:
|
||||
def extend_backpack_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.backpack_progression == BackpackProgression.option_vanilla:
|
||||
return
|
||||
backpack_locations = [location for location in locations_by_tag[LocationTags.BACKPACK]]
|
||||
filtered_backpack_locations = filter_modded_locations(world_options, backpack_locations)
|
||||
filtered_backpack_locations = filter_modded_locations(options, backpack_locations)
|
||||
randomized_locations.extend(filtered_backpack_locations)
|
||||
|
||||
|
||||
def extend_elevator_locations(randomized_locations: List[LocationData], world_options):
|
||||
if world_options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla:
|
||||
def extend_elevator_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
|
||||
if options.elevator_progression == ElevatorProgression.option_vanilla:
|
||||
return
|
||||
elevator_locations = [location for location in locations_by_tag[LocationTags.ELEVATOR]]
|
||||
filtered_elevator_locations = filter_modded_locations(world_options, elevator_locations)
|
||||
filtered_elevator_locations = filter_modded_locations(options, elevator_locations)
|
||||
randomized_locations.extend(filtered_elevator_locations)
|
||||
|
||||
|
||||
def create_locations(location_collector: StardewLocationCollector,
|
||||
world_options: options.StardewOptions,
|
||||
options: StardewValleyOptions,
|
||||
random: Random):
|
||||
randomized_locations = []
|
||||
|
||||
extend_mandatory_locations(randomized_locations, world_options)
|
||||
extend_backpack_locations(randomized_locations, world_options)
|
||||
extend_mandatory_locations(randomized_locations, options)
|
||||
extend_backpack_locations(randomized_locations, options)
|
||||
|
||||
if not world_options[options.ToolProgression] == options.ToolProgression.option_vanilla:
|
||||
if not options.tool_progression == ToolProgression.option_vanilla:
|
||||
randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE])
|
||||
|
||||
extend_elevator_locations(randomized_locations, world_options)
|
||||
extend_elevator_locations(randomized_locations, options)
|
||||
|
||||
if not world_options[options.SkillProgression] == options.SkillProgression.option_vanilla:
|
||||
if not options.skill_progression == SkillProgression.option_vanilla:
|
||||
for location in locations_by_tag[LocationTags.SKILL_LEVEL]:
|
||||
if location.mod_name is None or location.mod_name in world_options[options.Mods]:
|
||||
if location.mod_name is None or location.mod_name in options.mods:
|
||||
randomized_locations.append(location_table[location.name])
|
||||
|
||||
if not world_options[options.BuildingProgression] == options.BuildingProgression.option_vanilla:
|
||||
if not options.building_progression == BuildingProgression.option_vanilla:
|
||||
for location in locations_by_tag[LocationTags.BUILDING_BLUEPRINT]:
|
||||
if location.mod_name is None or location.mod_name in world_options[options.Mods]:
|
||||
if location.mod_name is None or location.mod_name in options.mods:
|
||||
randomized_locations.append(location_table[location.name])
|
||||
|
||||
if world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_disabled:
|
||||
if options.arcade_machine_locations != ArcadeMachineLocations.option_disabled:
|
||||
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE_VICTORY])
|
||||
|
||||
if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
|
||||
if options.arcade_machine_locations == ArcadeMachineLocations.option_full_shuffling:
|
||||
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE])
|
||||
|
||||
extend_cropsanity_locations(randomized_locations, world_options)
|
||||
extend_help_wanted_quests(randomized_locations, world_options[options.HelpWantedLocations])
|
||||
extend_fishsanity_locations(randomized_locations, world_options, random)
|
||||
extend_museumsanity_locations(randomized_locations, world_options[options.Museumsanity], random)
|
||||
extend_friendsanity_locations(randomized_locations, world_options)
|
||||
extend_cropsanity_locations(randomized_locations, options)
|
||||
extend_help_wanted_quests(randomized_locations, options.help_wanted_locations.value)
|
||||
extend_fishsanity_locations(randomized_locations, options, random)
|
||||
extend_museumsanity_locations(randomized_locations, options, random)
|
||||
extend_friendsanity_locations(randomized_locations, options)
|
||||
|
||||
extend_festival_locations(randomized_locations, world_options[options.FestivalLocations])
|
||||
extend_special_order_locations(randomized_locations, world_options)
|
||||
extend_walnut_purchase_locations(randomized_locations, world_options)
|
||||
extend_festival_locations(randomized_locations, options)
|
||||
extend_special_order_locations(randomized_locations, options)
|
||||
extend_walnut_purchase_locations(randomized_locations, options)
|
||||
|
||||
for location_data in randomized_locations:
|
||||
location_collector(location_data.name, location_data.code, location_data.region)
|
||||
|
||||
|
||||
def filter_ginger_island(world_options: options.StardewOptions, locations: List[LocationData]) -> List[LocationData]:
|
||||
include_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false
|
||||
def filter_ginger_island(options: StardewValleyOptions, locations: List[LocationData]) -> List[LocationData]:
|
||||
include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false
|
||||
return [location for location in locations if include_island or LocationTags.GINGER_ISLAND not in location.tags]
|
||||
|
||||
|
||||
def filter_modded_locations(world_options: options.StardewOptions, locations: List[LocationData]) -> List[LocationData]:
|
||||
current_mod_names = world_options[options.Mods]
|
||||
def filter_modded_locations(options: StardewValleyOptions, locations: List[LocationData]) -> List[LocationData]:
|
||||
current_mod_names = options.mods
|
||||
return [location for location in locations if location.mod_name is None or location.mod_name in current_mod_names]
|
||||
|
||||
|
||||
def filter_disabled_locations(world_options: options.StardewOptions, locations: List[LocationData]) -> List[LocationData]:
|
||||
locations_first_pass = filter_ginger_island(world_options, locations)
|
||||
locations_second_pass = filter_modded_locations(world_options, locations_first_pass)
|
||||
def filter_disabled_locations(options: StardewValleyOptions, locations: List[LocationData]) -> List[LocationData]:
|
||||
locations_first_pass = filter_ginger_island(options, locations)
|
||||
locations_second_pass = filter_modded_locations(options, locations_first_pass)
|
||||
return locations_second_pass
|
||||
|
|
|
@ -4,7 +4,6 @@ import math
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Union, Optional, Iterable, Sized, List, Set
|
||||
|
||||
from . import options
|
||||
from .data import all_fish, FishItem, all_purchasable_seeds, SeedItem, all_crops, CropItem
|
||||
from .data.bundle_data import BundleItem
|
||||
from .data.crops_data import crops_by_name
|
||||
|
@ -20,7 +19,8 @@ from .mods.logic.special_orders import get_modded_special_orders_rules
|
|||
from .mods.logic.skullcavernelevator import has_skull_cavern_elevator_to_floor
|
||||
from .mods.mod_data import ModNames
|
||||
from .mods.logic import magic, skills
|
||||
from .options import StardewOptions
|
||||
from .options import Museumsanity, SeasonRandomization, StardewValleyOptions, BuildingProgression, SkillProgression, ToolProgression, Friendsanity, Cropsanity, \
|
||||
ExcludeGingerIsland, ElevatorProgression, ArcadeMachineLocations, FestivalLocations, SpecialOrderLocations
|
||||
from .regions import vanilla_regions
|
||||
from .stardew_rule import False_, Reach, Or, True_, Received, Count, And, Has, TotalReceived, StardewRule
|
||||
from .strings.animal_names import Animal, coop_animals, barn_animals
|
||||
|
@ -81,10 +81,11 @@ tool_upgrade_prices = {
|
|||
|
||||
fishing_regions = [Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west]
|
||||
|
||||
|
||||
@dataclass(frozen=True, repr=False)
|
||||
class StardewLogic:
|
||||
player: int
|
||||
options: StardewOptions
|
||||
options: StardewValleyOptions
|
||||
|
||||
item_rules: Dict[str, StardewRule] = field(default_factory=dict)
|
||||
sapling_rules: Dict[str, StardewRule] = field(default_factory=dict)
|
||||
|
@ -398,7 +399,7 @@ class StardewLogic:
|
|||
Building.cellar: self.can_spend_money_at(Region.carpenter, 100000) & self.has_house(2),
|
||||
})
|
||||
|
||||
self.building_rules.update(get_modded_building_rules(self, self.options[options.Mods]))
|
||||
self.building_rules.update(get_modded_building_rules(self, self.options.mods))
|
||||
|
||||
self.quest_rules.update({
|
||||
Quest.introductions: self.can_reach_region(Region.town),
|
||||
|
@ -455,7 +456,7 @@ class StardewLogic:
|
|||
self.can_meet(NPC.wizard) & self.can_meet(NPC.willy),
|
||||
})
|
||||
|
||||
self.quest_rules.update(get_modded_quest_rules(self, self.options[options.Mods]))
|
||||
self.quest_rules.update(get_modded_quest_rules(self, self.options.mods))
|
||||
|
||||
self.festival_rules.update({
|
||||
FestivalCheck.egg_hunt: self.has_season(Season.spring) & self.can_reach_region(Region.town) & self.can_win_egg_hunt(),
|
||||
|
@ -539,7 +540,7 @@ class StardewLogic:
|
|||
self.can_spend_money(80000), # I need this extra rule because money rules aren't additive...
|
||||
})
|
||||
|
||||
self.special_order_rules.update(get_modded_special_orders_rules(self, self.options[options.Mods]))
|
||||
self.special_order_rules.update(get_modded_special_orders_rules(self, self.options.mods))
|
||||
|
||||
def has(self, items: Union[str, (Iterable[str], Sized)], count: Optional[int] = None) -> StardewRule:
|
||||
if isinstance(items, str):
|
||||
|
@ -596,7 +597,7 @@ class StardewLogic:
|
|||
return self.has_lived_months(min(8, amount // MONEY_PER_MONTH))
|
||||
|
||||
def can_spend_money(self, amount: int) -> StardewRule:
|
||||
if self.options[options.StartingMoney] == -1:
|
||||
if self.options.starting_money == -1:
|
||||
return True_()
|
||||
return self.has_lived_months(min(8, amount // (MONEY_PER_MONTH // 5)))
|
||||
|
||||
|
@ -607,7 +608,7 @@ class StardewLogic:
|
|||
if material == ToolMaterial.basic or tool == Tool.scythe:
|
||||
return True_()
|
||||
|
||||
if self.options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
if self.options.tool_progression == ToolProgression.option_progressive:
|
||||
return self.received(f"Progressive {tool}", count=tool_materials[material])
|
||||
|
||||
return self.has(f"{material} Bar") & self.can_spend_money(tool_upgrade_prices[material])
|
||||
|
@ -644,7 +645,7 @@ class StardewLogic:
|
|||
if level <= 0:
|
||||
return True_()
|
||||
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
return self.received(f"{skill} Level", count=level)
|
||||
|
||||
return self.can_earn_skill_level(skill, level)
|
||||
|
@ -656,7 +657,7 @@ class StardewLogic:
|
|||
if level <= 0:
|
||||
return True_()
|
||||
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
skills_items = ["Farming Level", "Mining Level", "Foraging Level",
|
||||
"Fishing Level", "Combat Level"]
|
||||
if allow_modded_skills:
|
||||
|
@ -672,7 +673,7 @@ class StardewLogic:
|
|||
|
||||
def has_building(self, building: str) -> StardewRule:
|
||||
carpenter_rule = self.can_reach_region(Region.carpenter)
|
||||
if not self.options[options.BuildingProgression] == options.BuildingProgression.option_vanilla:
|
||||
if not self.options.building_progression == BuildingProgression.option_vanilla:
|
||||
count = 1
|
||||
if building in [Building.coop, Building.barn, Building.shed]:
|
||||
building = f"Progressive {building}"
|
||||
|
@ -693,7 +694,7 @@ class StardewLogic:
|
|||
if upgrade_level > 3:
|
||||
return False_()
|
||||
|
||||
if not self.options[options.BuildingProgression] == options.BuildingProgression.option_vanilla:
|
||||
if not self.options.building_progression == BuildingProgression.option_vanilla:
|
||||
return self.received(f"Progressive House", upgrade_level) & self.can_reach_region(Region.carpenter)
|
||||
|
||||
if upgrade_level == 1:
|
||||
|
@ -734,7 +735,7 @@ class StardewLogic:
|
|||
return tool_rule & enemy_rule
|
||||
|
||||
def can_get_fishing_xp(self) -> StardewRule:
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
return self.can_fish() | self.can_crab_pot()
|
||||
|
||||
return self.can_fish()
|
||||
|
@ -746,7 +747,7 @@ class StardewLogic:
|
|||
skill_rule = self.has_skill_level(Skill.fishing, skill_required)
|
||||
region_rule = self.can_reach_any_region(fishing_regions)
|
||||
number_fishing_rod_required = 1 if difficulty < 50 else 2
|
||||
if self.options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
if self.options.tool_progression == ToolProgression.option_progressive:
|
||||
return self.received("Progressive Fishing Rod", number_fishing_rod_required) & skill_rule & region_rule
|
||||
|
||||
return skill_rule & region_rule
|
||||
|
@ -763,7 +764,7 @@ class StardewLogic:
|
|||
return self.has_max_fishing_rod() & skill_rule
|
||||
|
||||
def can_buy_seed(self, seed: SeedItem) -> StardewRule:
|
||||
if self.options[options.Cropsanity] == options.Cropsanity.option_disabled:
|
||||
if self.options.cropsanity == Cropsanity.option_disabled:
|
||||
item_rule = True_()
|
||||
else:
|
||||
item_rule = self.received(seed.name)
|
||||
|
@ -781,7 +782,7 @@ class StardewLogic:
|
|||
Fruit.peach: 6000,
|
||||
Fruit.pomegranate: 6000, Fruit.banana: 0, Fruit.mango: 0}
|
||||
received_sapling = self.received(f"{fruit} Sapling")
|
||||
if self.options[options.Cropsanity] == options.Cropsanity.option_disabled:
|
||||
if self.options.cropsanity == Cropsanity.option_disabled:
|
||||
allowed_buy_sapling = True_()
|
||||
else:
|
||||
allowed_buy_sapling = received_sapling
|
||||
|
@ -824,14 +825,14 @@ class StardewLogic:
|
|||
def can_catch_every_fish(self) -> StardewRule:
|
||||
rules = [self.has_skill_level(Skill.fishing, 10), self.has_max_fishing_rod()]
|
||||
for fish in all_fish:
|
||||
if self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true and \
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true and \
|
||||
fish in island_fish:
|
||||
continue
|
||||
rules.append(self.can_catch_fish(fish))
|
||||
return And(rules)
|
||||
|
||||
def has_max_fishing_rod(self) -> StardewRule:
|
||||
if self.options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
if self.options.tool_progression == ToolProgression.option_progressive:
|
||||
return self.received(APTool.fishing_rod, 4)
|
||||
return self.can_get_fishing_xp()
|
||||
|
||||
|
@ -875,7 +876,7 @@ class StardewLogic:
|
|||
|
||||
def can_crab_pot(self, region: str = Generic.any) -> StardewRule:
|
||||
crab_pot_rule = self.has(Craftable.bait)
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
crab_pot_rule = crab_pot_rule & self.has(Machine.crab_pot)
|
||||
else:
|
||||
crab_pot_rule = crab_pot_rule & self.can_get_fishing_xp()
|
||||
|
@ -926,9 +927,7 @@ class StardewLogic:
|
|||
return region_rule & ((tool_rule & foraging_rule) | magic_rule)
|
||||
|
||||
def has_max_buffs(self) -> StardewRule:
|
||||
number_of_movement_buffs: int = self.options[options.NumberOfMovementBuffs]
|
||||
number_of_luck_buffs: int = self.options[options.NumberOfLuckBuffs]
|
||||
return self.received(Buff.movement, number_of_movement_buffs) & self.received(Buff.luck, number_of_luck_buffs)
|
||||
return self.received(Buff.movement, self.options.number_of_movement_buffs.value) & self.received(Buff.luck, self.options.number_of_luck_buffs.value)
|
||||
|
||||
def get_weapon_rule_for_floor_tier(self, tier: int):
|
||||
if tier >= 4:
|
||||
|
@ -946,9 +945,9 @@ class StardewLogic:
|
|||
rules = []
|
||||
weapon_rule = self.get_weapon_rule_for_floor_tier(tier)
|
||||
rules.append(weapon_rule)
|
||||
if self.options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
if self.options.tool_progression == ToolProgression.option_progressive:
|
||||
rules.append(self.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier]))
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
combat_tier = min(10, max(0, tier * 2))
|
||||
rules.append(self.has_skill_level(Skill.combat, combat_tier))
|
||||
return And(rules)
|
||||
|
@ -958,15 +957,15 @@ class StardewLogic:
|
|||
rules = []
|
||||
weapon_rule = self.get_weapon_rule_for_floor_tier(tier)
|
||||
rules.append(weapon_rule)
|
||||
if self.options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
if self.options.tool_progression == ToolProgression.option_progressive:
|
||||
rules.append(self.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier]))
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
combat_tier = min(10, max(0, tier * 2))
|
||||
rules.append(self.has_skill_level(Skill.combat, combat_tier))
|
||||
return And(rules)
|
||||
|
||||
def has_mine_elevator_to_floor(self, floor: int) -> StardewRule:
|
||||
if self.options[options.ElevatorProgression] != options.ElevatorProgression.option_vanilla:
|
||||
if self.options.elevator_progression != ElevatorProgression.option_vanilla:
|
||||
return self.received("Progressive Mine Elevator", count=int(floor / 5))
|
||||
return True_()
|
||||
|
||||
|
@ -984,9 +983,9 @@ class StardewLogic:
|
|||
weapon_rule = self.has_great_weapon()
|
||||
rules.append(weapon_rule)
|
||||
rules.append(self.can_cook())
|
||||
if self.options[options.ToolProgression] == options.ToolProgression.option_progressive:
|
||||
if self.options.tool_progression == ToolProgression.option_progressive:
|
||||
rules.append(self.received("Progressive Pickaxe", min(4, max(0, tier + 2))))
|
||||
if self.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if self.options.skill_progression == SkillProgression.option_progressive:
|
||||
skill_tier = min(10, max(0, tier * 2 + 6))
|
||||
rules.extend({self.has_skill_level(Skill.combat, skill_tier),
|
||||
self.has_skill_level(Skill.mining, skill_tier)})
|
||||
|
@ -1005,20 +1004,20 @@ class StardewLogic:
|
|||
self.can_progress_easily_in_the_skull_cavern_from_floor(previous_previous_elevator))) & has_mine_elevator
|
||||
|
||||
def has_jotpk_power_level(self, power_level: int) -> StardewRule:
|
||||
if self.options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
if self.options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling:
|
||||
return True_()
|
||||
jotpk_buffs = ["JotPK: Progressive Boots", "JotPK: Progressive Gun",
|
||||
"JotPK: Progressive Ammo", "JotPK: Extra Life", "JotPK: Increased Drop Rate"]
|
||||
return self.received(jotpk_buffs, power_level)
|
||||
|
||||
def has_junimo_kart_power_level(self, power_level: int) -> StardewRule:
|
||||
if self.options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
if self.options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling:
|
||||
return True_()
|
||||
return self.received("Junimo Kart: Extra Life", power_level)
|
||||
|
||||
def has_junimo_kart_max_level(self) -> StardewRule:
|
||||
play_rule = self.can_reach_region(Region.junimo_kart_3)
|
||||
if self.options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
if self.options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling:
|
||||
return play_rule
|
||||
return self.has_junimo_kart_power_level(8)
|
||||
|
||||
|
@ -1043,12 +1042,12 @@ class StardewLogic:
|
|||
def has_relationship(self, npc: str, hearts: int = 1) -> StardewRule:
|
||||
if hearts <= 0:
|
||||
return True_()
|
||||
friendsanity = self.options[options.Friendsanity]
|
||||
if friendsanity == options.Friendsanity.option_none:
|
||||
friendsanity = self.options.friendsanity
|
||||
if friendsanity == Friendsanity.option_none:
|
||||
return self.can_earn_relationship(npc, hearts)
|
||||
if npc not in all_villagers_by_name:
|
||||
if npc == NPC.pet:
|
||||
if friendsanity == options.Friendsanity.option_bachelors:
|
||||
if friendsanity == Friendsanity.option_bachelors:
|
||||
return self.can_befriend_pet(hearts)
|
||||
return self.received_hearts(NPC.pet, hearts)
|
||||
if npc == Generic.any or npc == Generic.bachelor:
|
||||
|
@ -1078,11 +1077,11 @@ class StardewLogic:
|
|||
if not self.npc_is_in_current_slot(npc):
|
||||
return True_()
|
||||
villager = all_villagers_by_name[npc]
|
||||
if friendsanity == options.Friendsanity.option_bachelors and not villager.bachelor:
|
||||
if friendsanity == Friendsanity.option_bachelors and not villager.bachelor:
|
||||
return self.can_earn_relationship(npc, hearts)
|
||||
if friendsanity == options.Friendsanity.option_starting_npcs and not villager.available:
|
||||
if friendsanity == Friendsanity.option_starting_npcs and not villager.available:
|
||||
return self.can_earn_relationship(npc, hearts)
|
||||
is_capped_at_8 = villager.bachelor and friendsanity != options.Friendsanity.option_all_with_marriage
|
||||
is_capped_at_8 = villager.bachelor and friendsanity != Friendsanity.option_all_with_marriage
|
||||
if is_capped_at_8 and hearts > 8:
|
||||
return self.received_hearts(villager, 8) & self.can_earn_relationship(npc, hearts)
|
||||
return self.received_hearts(villager, hearts)
|
||||
|
@ -1090,7 +1089,7 @@ class StardewLogic:
|
|||
def received_hearts(self, npc: Union[str, Villager], hearts: int) -> StardewRule:
|
||||
if isinstance(npc, Villager):
|
||||
return self.received_hearts(npc.name, hearts)
|
||||
heart_size: int = self.options[options.FriendsanityHeartSize]
|
||||
heart_size = self.options.friendsanity_heart_size.value
|
||||
return self.received(self.heart(npc), math.ceil(hearts / heart_size))
|
||||
|
||||
def can_meet(self, npc: str) -> StardewRule:
|
||||
|
@ -1122,13 +1121,13 @@ class StardewLogic:
|
|||
if hearts <= 0:
|
||||
return True_()
|
||||
|
||||
heart_size: int = self.options[options.FriendsanityHeartSize]
|
||||
heart_size = self.options.friendsanity_heart_size.value
|
||||
previous_heart = hearts - heart_size
|
||||
previous_heart_rule = self.has_relationship(npc, previous_heart)
|
||||
|
||||
if npc == NPC.pet:
|
||||
earn_rule = self.can_befriend_pet(hearts)
|
||||
elif npc == NPC.wizard and ModNames.magic in self.options[options.Mods]:
|
||||
elif npc == NPC.wizard and ModNames.magic in self.options.mods:
|
||||
earn_rule = self.can_meet(npc) & self.has_lived_months(hearts)
|
||||
elif npc in all_villagers_by_name:
|
||||
if not self.npc_is_in_current_slot(npc):
|
||||
|
@ -1284,7 +1283,7 @@ class StardewLogic:
|
|||
return self.has_lived_months(8)
|
||||
|
||||
def can_speak_dwarf(self) -> StardewRule:
|
||||
if self.options[options.Museumsanity] == options.Museumsanity.option_none:
|
||||
if self.options.museumsanity == Museumsanity.option_none:
|
||||
return And([self.can_donate_museum_item(item) for item in dwarf_scrolls])
|
||||
return self.received("Dwarvish Translation Guide")
|
||||
|
||||
|
@ -1334,7 +1333,7 @@ class StardewLogic:
|
|||
def can_complete_museum(self) -> StardewRule:
|
||||
rules = [self.can_reach_region(Region.museum), self.can_mine_perfectly()]
|
||||
|
||||
if self.options[options.Museumsanity] != options.Museumsanity.option_none:
|
||||
if self.options.museumsanity != Museumsanity.option_none:
|
||||
rules.append(self.received("Traveling Merchant Metal Detector", 4))
|
||||
|
||||
for donation in all_museum_items:
|
||||
|
@ -1345,9 +1344,9 @@ class StardewLogic:
|
|||
if season == Generic.any:
|
||||
return True_()
|
||||
seasons_order = [Season.spring, Season.summer, Season.fall, Season.winter]
|
||||
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive:
|
||||
if self.options.season_randomization == SeasonRandomization.option_progressive:
|
||||
return self.received(Season.progressive, seasons_order.index(season))
|
||||
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
|
||||
if self.options.season_randomization == SeasonRandomization.option_disabled:
|
||||
if season == Season.spring:
|
||||
return True_()
|
||||
return self.has_lived_months(1)
|
||||
|
@ -1371,19 +1370,19 @@ class StardewLogic:
|
|||
return self.received("Month End", number)
|
||||
|
||||
def has_rusty_key(self) -> StardewRule:
|
||||
if self.options[options.Museumsanity] == options.Museumsanity.option_none:
|
||||
if self.options.museumsanity == Museumsanity.option_none:
|
||||
required_donations = 80 # It's 60, but without a metal detector I'd rather overshoot so players don't get screwed by RNG
|
||||
return self.has([item.name for item in all_museum_items], required_donations) & self.can_reach_region(Region.museum)
|
||||
return self.received(Wallet.rusty_key)
|
||||
|
||||
def can_win_egg_hunt(self) -> StardewRule:
|
||||
number_of_movement_buffs: int = self.options[options.NumberOfMovementBuffs]
|
||||
if self.options[options.FestivalLocations] == options.FestivalLocations.option_hard or number_of_movement_buffs < 2:
|
||||
number_of_movement_buffs = self.options.number_of_movement_buffs.value
|
||||
if self.options.festival_locations == FestivalLocations.option_hard or number_of_movement_buffs < 2:
|
||||
return True_()
|
||||
return self.received(Buff.movement, number_of_movement_buffs // 2)
|
||||
|
||||
def can_succeed_luau_soup(self) -> StardewRule:
|
||||
if self.options[options.FestivalLocations] != options.FestivalLocations.option_hard:
|
||||
if self.options.festival_locations != FestivalLocations.option_hard:
|
||||
return True_()
|
||||
eligible_fish = [Fish.blobfish, Fish.crimsonfish, "Ice Pip", Fish.lava_eel, Fish.legend, Fish.angler, Fish.catfish, Fish.glacierfish,
|
||||
Fish.mutant_carp, Fish.spookfish, Fish.stingray, Fish.sturgeon, "Super Cucumber"]
|
||||
|
@ -1398,7 +1397,7 @@ class StardewLogic:
|
|||
return Or(fish_rule) | Or(aged_rule)
|
||||
|
||||
def can_succeed_grange_display(self) -> StardewRule:
|
||||
if self.options[options.FestivalLocations] != options.FestivalLocations.option_hard:
|
||||
if self.options.festival_locations != FestivalLocations.option_hard:
|
||||
return True_()
|
||||
animal_rule = self.has_animal(Generic.any)
|
||||
artisan_rule = self.can_keg(Generic.any) | self.can_preserves_jar(Generic.any)
|
||||
|
@ -1527,12 +1526,12 @@ class StardewLogic:
|
|||
return blacksmith_access & self.has(geode)
|
||||
|
||||
def has_island_trader(self) -> StardewRule:
|
||||
if self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return False_()
|
||||
return self.can_reach_region(Region.island_trader)
|
||||
|
||||
def has_walnut(self, number: int) -> StardewRule:
|
||||
if self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return False_()
|
||||
if number <= 0:
|
||||
return True_()
|
||||
|
@ -1592,7 +1591,7 @@ class StardewLogic:
|
|||
def npc_is_in_current_slot(self, name: str) -> bool:
|
||||
npc = all_villagers_by_name[name]
|
||||
mod = npc.mod_name
|
||||
return mod is None or mod in self.options[options.Mods]
|
||||
return mod is None or mod in self.options.mods
|
||||
|
||||
def can_do_combat_at_level(self, level: str) -> StardewRule:
|
||||
if level == Performance.basic:
|
||||
|
@ -1612,7 +1611,7 @@ class StardewLogic:
|
|||
return tool_rule | spell_rule
|
||||
|
||||
def has_prismatic_jelly_reward_access(self) -> StardewRule:
|
||||
if self.options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled:
|
||||
if self.options.special_order_locations == SpecialOrderLocations.option_disabled:
|
||||
return self.can_complete_special_order("Prismatic Jelly")
|
||||
return self.received("Monster Musk Recipe")
|
||||
|
||||
|
|
|
@ -17,14 +17,14 @@ def can_reach_woods_depth(vanilla_logic, depth: int) -> StardewRule:
|
|||
if depth > 50:
|
||||
rules.append(vanilla_logic.can_do_combat_at_level(Performance.great) & vanilla_logic.can_cook() &
|
||||
vanilla_logic.received(ModTransportation.woods_obelisk))
|
||||
if vanilla_logic.options[options.SkillProgression] == options.SkillProgression.option_progressive:
|
||||
if vanilla_logic.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
combat_tier = min(10, max(0, tier + 5))
|
||||
rules.append(vanilla_logic.has_skill_level(Skill.combat, combat_tier))
|
||||
return And(rules)
|
||||
|
||||
|
||||
def has_woods_rune_to_depth(vanilla_logic, floor: int) -> StardewRule:
|
||||
if vanilla_logic.options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla:
|
||||
if vanilla_logic.options.elevator_progression == options.ElevatorProgression.option_vanilla:
|
||||
return True_()
|
||||
return vanilla_logic.received("Progressive Woods Obelisk Sigils", count=int(floor / 10))
|
||||
|
||||
|
|
|
@ -7,19 +7,19 @@ from ... import options
|
|||
|
||||
|
||||
def can_use_clear_debris_instead_of_tool_level(vanilla_logic, level: int) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
return vanilla_logic.received(MagicSpell.clear_debris) & can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, level)
|
||||
|
||||
|
||||
def can_use_altar(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
return vanilla_logic.can_reach_region(MagicRegion.altar)
|
||||
|
||||
|
||||
def has_any_spell(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
return can_use_altar(vanilla_logic)
|
||||
|
||||
|
@ -40,7 +40,7 @@ def has_support_spell_count(vanilla_logic, count: int) -> StardewRule:
|
|||
|
||||
|
||||
def has_decent_spells(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 2)
|
||||
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 1)
|
||||
|
@ -48,7 +48,7 @@ def has_decent_spells(vanilla_logic) -> StardewRule:
|
|||
|
||||
|
||||
def has_good_spells(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 4)
|
||||
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 2)
|
||||
|
@ -57,7 +57,7 @@ def has_good_spells(vanilla_logic) -> StardewRule:
|
|||
|
||||
|
||||
def has_great_spells(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 6)
|
||||
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 3)
|
||||
|
@ -66,7 +66,7 @@ def has_great_spells(vanilla_logic) -> StardewRule:
|
|||
|
||||
|
||||
def has_amazing_spells(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 8)
|
||||
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 4)
|
||||
|
@ -75,6 +75,6 @@ def has_amazing_spells(vanilla_logic) -> StardewRule:
|
|||
|
||||
|
||||
def can_blink(vanilla_logic) -> StardewRule:
|
||||
if ModNames.magic not in vanilla_logic.options[options.Mods]:
|
||||
if ModNames.magic not in vanilla_logic.options.mods:
|
||||
return False_()
|
||||
return vanilla_logic.received(MagicSpell.blink) & can_use_altar(vanilla_logic)
|
||||
|
|
|
@ -29,17 +29,17 @@ def append_mod_skill_level(skills_items: List[str], active_mods):
|
|||
|
||||
|
||||
def can_earn_mod_skill_level(logic, skill: str, level: int) -> StardewRule:
|
||||
if ModNames.luck_skill in logic.options[options.Mods] and skill == ModSkill.luck:
|
||||
if ModNames.luck_skill in logic.options.mods and skill == ModSkill.luck:
|
||||
return can_earn_luck_skill_level(logic, level)
|
||||
if ModNames.magic in logic.options[options.Mods] and skill == ModSkill.magic:
|
||||
if ModNames.magic in logic.options.mods and skill == ModSkill.magic:
|
||||
return can_earn_magic_skill_level(logic, level)
|
||||
if ModNames.socializing_skill in logic.options[options.Mods] and skill == ModSkill.socializing:
|
||||
if ModNames.socializing_skill in logic.options.mods and skill == ModSkill.socializing:
|
||||
return can_earn_socializing_skill_level(logic, level)
|
||||
if ModNames.archaeology in logic.options[options.Mods] and skill == ModSkill.archaeology:
|
||||
if ModNames.archaeology in logic.options.mods and skill == ModSkill.archaeology:
|
||||
return can_earn_archaeology_skill_level(logic, level)
|
||||
if ModNames.cooking_skill in logic.options[options.Mods] and skill == ModSkill.cooking:
|
||||
if ModNames.cooking_skill in logic.options.mods and skill == ModSkill.cooking:
|
||||
return can_earn_cooking_skill_level(logic, level)
|
||||
if ModNames.binning_skill in logic.options[options.Mods] and skill == ModSkill.binning:
|
||||
if ModNames.binning_skill in logic.options.mods and skill == ModSkill.binning:
|
||||
return can_earn_binning_skill_level(logic, level)
|
||||
return False_()
|
||||
|
||||
|
@ -65,7 +65,7 @@ def can_earn_magic_skill_level(vanilla_logic, level: int) -> StardewRule:
|
|||
def can_earn_socializing_skill_level(vanilla_logic, level: int) -> StardewRule:
|
||||
villager_count = []
|
||||
for villager in all_villagers:
|
||||
if villager.mod_name in vanilla_logic.options[options.Mods] or villager.mod_name is None:
|
||||
if villager.mod_name in vanilla_logic.options.mods or villager.mod_name is None:
|
||||
villager_count.append(vanilla_logic.can_earn_relationship(villager.name, level))
|
||||
return Count(level * 2, villager_count)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from ... import options
|
|||
|
||||
|
||||
def has_skull_cavern_elevator_to_floor(self, floor: int) -> StardewRule:
|
||||
if self.options[options.ElevatorProgression] != options.ElevatorProgression.option_vanilla and \
|
||||
ModNames.skull_cavern_elevator in self.options[options.Mods]:
|
||||
if self.options.elevator_progression != options.ElevatorProgression.option_vanilla and \
|
||||
ModNames.skull_cavern_elevator in self.options.mods:
|
||||
return self.received("Progressive Skull Cavern Elevator", floor // 25)
|
||||
return True_()
|
||||
|
|
|
@ -1,29 +1,9 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict, Union, Protocol, runtime_checkable, ClassVar
|
||||
from typing import Dict
|
||||
|
||||
from Options import Option, Range, DeathLink, SpecialRange, Toggle, Choice, OptionSet
|
||||
from Options import Range, SpecialRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink, Option
|
||||
from .mods.mod_data import ModNames
|
||||
|
||||
@runtime_checkable
|
||||
class StardewOption(Protocol):
|
||||
internal_name: ClassVar[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class StardewOptions:
|
||||
options: Dict[str, Union[bool, int, str]]
|
||||
|
||||
def __getitem__(self, item: Union[str, StardewOption]) -> Union[bool, int, str]:
|
||||
if isinstance(item, StardewOption):
|
||||
item = item.internal_name
|
||||
|
||||
return self.options.get(item, None)
|
||||
|
||||
def __setitem__(self, key: Union[str, StardewOption], value: Union[bool, int, str]):
|
||||
if isinstance(key, StardewOption):
|
||||
key = key.internal_name
|
||||
self.options[key] = value
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
"""What's your goal with this play-through?
|
||||
|
@ -553,56 +533,39 @@ class Mods(OptionSet):
|
|||
}
|
||||
|
||||
|
||||
stardew_valley_option_classes = [
|
||||
Goal,
|
||||
StartingMoney,
|
||||
ProfitMargin,
|
||||
BundleRandomization,
|
||||
BundlePrice,
|
||||
EntranceRandomization,
|
||||
SeasonRandomization,
|
||||
Cropsanity,
|
||||
BackpackProgression,
|
||||
ToolProgression,
|
||||
SkillProgression,
|
||||
BuildingProgression,
|
||||
FestivalLocations,
|
||||
ElevatorProgression,
|
||||
ArcadeMachineLocations,
|
||||
SpecialOrderLocations,
|
||||
HelpWantedLocations,
|
||||
Fishsanity,
|
||||
Museumsanity,
|
||||
Friendsanity,
|
||||
FriendsanityHeartSize,
|
||||
NumberOfMovementBuffs,
|
||||
NumberOfLuckBuffs,
|
||||
ExcludeGingerIsland,
|
||||
TrapItems,
|
||||
MultipleDaySleepEnabled,
|
||||
MultipleDaySleepCost,
|
||||
ExperienceMultiplier,
|
||||
FriendshipMultiplier,
|
||||
DebrisMultiplier,
|
||||
QuickStart,
|
||||
Gifting,
|
||||
Mods,
|
||||
]
|
||||
stardew_valley_options: Dict[str, type(Option)] = {option.internal_name: option for option in
|
||||
stardew_valley_option_classes}
|
||||
default_options = {option.internal_name: option.default for option in stardew_valley_options.values()}
|
||||
stardew_valley_options["death_link"] = DeathLink
|
||||
|
||||
|
||||
def fetch_options(world, player: int) -> StardewOptions:
|
||||
return StardewOptions({option: get_option_value(world, player, option) for option in stardew_valley_options})
|
||||
|
||||
|
||||
def get_option_value(world, player: int, name: str) -> Union[bool, int]:
|
||||
assert name in stardew_valley_options, f"{name} is not a valid option for Stardew Valley."
|
||||
|
||||
value = getattr(world, name)
|
||||
|
||||
if issubclass(stardew_valley_options[name], Toggle):
|
||||
return bool(value[player].value)
|
||||
return value[player].value
|
||||
@dataclass
|
||||
class StardewValleyOptions(PerGameCommonOptions):
|
||||
goal: Goal
|
||||
starting_money: StartingMoney
|
||||
profit_margin: ProfitMargin
|
||||
bundle_randomization: BundleRandomization
|
||||
bundle_price: BundlePrice
|
||||
entrance_randomization: EntranceRandomization
|
||||
season_randomization: SeasonRandomization
|
||||
cropsanity: Cropsanity
|
||||
backpack_progression: BackpackProgression
|
||||
tool_progression: ToolProgression
|
||||
skill_progression: SkillProgression
|
||||
building_progression: BuildingProgression
|
||||
festival_locations: FestivalLocations
|
||||
elevator_progression: ElevatorProgression
|
||||
arcade_machine_locations: ArcadeMachineLocations
|
||||
special_order_locations: SpecialOrderLocations
|
||||
help_wanted_locations: HelpWantedLocations
|
||||
fishsanity: Fishsanity
|
||||
museumsanity: Museumsanity
|
||||
friendsanity: Friendsanity
|
||||
friendsanity_heart_size: FriendsanityHeartSize
|
||||
number_of_movement_buffs: NumberOfMovementBuffs
|
||||
number_of_luck_buffs: NumberOfLuckBuffs
|
||||
exclude_ginger_island: ExcludeGingerIsland
|
||||
trap_items: TrapItems
|
||||
multiple_day_sleep_enabled: MultipleDaySleepEnabled
|
||||
multiple_day_sleep_cost: MultipleDaySleepCost
|
||||
experience_multiplier: ExperienceMultiplier
|
||||
friendship_multiplier: FriendshipMultiplier
|
||||
debris_multiplier: DebrisMultiplier
|
||||
quick_start: QuickStart
|
||||
gifting: Gifting
|
||||
mods: Mods
|
||||
death_link: DeathLink
|
||||
|
|
|
@ -2,11 +2,10 @@ from random import Random
|
|||
from typing import Iterable, Dict, Protocol, List, Tuple, Set
|
||||
|
||||
from BaseClasses import Region, Entrance
|
||||
from . import options
|
||||
from .options import EntranceRandomization, ExcludeGingerIsland, Museumsanity
|
||||
from .strings.entrance_names import Entrance
|
||||
from .strings.region_names import Region
|
||||
from .region_classes import RegionData, ConnectionData, RandomizationFlag
|
||||
from .options import StardewOptions
|
||||
from .mods.mod_regions import ModDataList
|
||||
|
||||
|
||||
|
@ -397,12 +396,12 @@ vanilla_connections = [
|
|||
]
|
||||
|
||||
|
||||
def create_final_regions(world_options: StardewOptions) -> List[RegionData]:
|
||||
def create_final_regions(world_options) -> List[RegionData]:
|
||||
final_regions = []
|
||||
final_regions.extend(vanilla_regions)
|
||||
if world_options[options.Mods] is None:
|
||||
if world_options.mods is None:
|
||||
return final_regions
|
||||
for mod in world_options[options.Mods]:
|
||||
for mod in world_options.mods.value:
|
||||
if mod not in ModDataList:
|
||||
continue
|
||||
for mod_region in ModDataList[mod].regions:
|
||||
|
@ -417,19 +416,19 @@ def create_final_regions(world_options: StardewOptions) -> List[RegionData]:
|
|||
return final_regions
|
||||
|
||||
|
||||
def create_final_connections(world_options: StardewOptions) -> List[ConnectionData]:
|
||||
def create_final_connections(world_options) -> List[ConnectionData]:
|
||||
final_connections = []
|
||||
final_connections.extend(vanilla_connections)
|
||||
if world_options[options.Mods] is None:
|
||||
if world_options.mods is None:
|
||||
return final_connections
|
||||
for mod in world_options[options.Mods]:
|
||||
for mod in world_options.mods.value:
|
||||
if mod not in ModDataList:
|
||||
continue
|
||||
final_connections.extend(ModDataList[mod].connections)
|
||||
return final_connections
|
||||
|
||||
|
||||
def create_regions(region_factory: RegionFactory, random: Random, world_options: StardewOptions) -> Tuple[
|
||||
def create_regions(region_factory: RegionFactory, random: Random, world_options) -> Tuple[
|
||||
Iterable[Region], Dict[str, str]]:
|
||||
final_regions = create_final_regions(world_options)
|
||||
regions: Dict[str: Region] = {region.name: region_factory(region.name, region.exits) for region in
|
||||
|
@ -448,21 +447,21 @@ def create_regions(region_factory: RegionFactory, random: Random, world_options:
|
|||
return regions.values(), randomized_data
|
||||
|
||||
|
||||
def randomize_connections(random: Random, world_options: StardewOptions, regions_by_name) -> Tuple[
|
||||
def randomize_connections(random: Random, world_options, regions_by_name) -> Tuple[
|
||||
List[ConnectionData], Dict[str, str]]:
|
||||
connections_to_randomize = []
|
||||
final_connections = create_final_connections(world_options)
|
||||
connections_by_name: Dict[str, ConnectionData] = {connection.name: connection for connection in final_connections}
|
||||
if world_options[options.EntranceRandomization] == options.EntranceRandomization.option_pelican_town:
|
||||
if world_options.entrance_randomization == EntranceRandomization.option_pelican_town:
|
||||
connections_to_randomize = [connection for connection in final_connections if
|
||||
RandomizationFlag.PELICAN_TOWN in connection.flag]
|
||||
elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_non_progression:
|
||||
elif world_options.entrance_randomization == EntranceRandomization.option_non_progression:
|
||||
connections_to_randomize = [connection for connection in final_connections if
|
||||
RandomizationFlag.NON_PROGRESSION in connection.flag]
|
||||
elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_buildings:
|
||||
elif world_options.entrance_randomization == EntranceRandomization.option_buildings:
|
||||
connections_to_randomize = [connection for connection in final_connections if
|
||||
RandomizationFlag.BUILDINGS in connection.flag]
|
||||
elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_chaos:
|
||||
elif world_options.entrance_randomization == EntranceRandomization.option_chaos:
|
||||
connections_to_randomize = [connection for connection in final_connections if
|
||||
RandomizationFlag.BUILDINGS in connection.flag]
|
||||
connections_to_randomize = exclude_island_if_necessary(connections_to_randomize, world_options)
|
||||
|
@ -491,8 +490,8 @@ def randomize_connections(random: Random, world_options: StardewOptions, regions
|
|||
|
||||
|
||||
def remove_excluded_entrances(connections_to_randomize, world_options):
|
||||
exclude_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
|
||||
exclude_sewers = world_options[options.Museumsanity] == options.Museumsanity.option_none
|
||||
exclude_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
exclude_sewers = world_options.museumsanity == Museumsanity.option_none
|
||||
if exclude_island:
|
||||
connections_to_randomize = [connection for connection in connections_to_randomize if RandomizationFlag.GINGER_ISLAND not in connection.flag]
|
||||
if exclude_sewers:
|
||||
|
@ -502,7 +501,7 @@ def remove_excluded_entrances(connections_to_randomize, world_options):
|
|||
|
||||
|
||||
def exclude_island_if_necessary(connections_to_randomize: List[ConnectionData], world_options) -> List[ConnectionData]:
|
||||
exclude_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
|
||||
exclude_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
if exclude_island:
|
||||
connections_to_randomize = [connection for connection in connections_to_randomize if
|
||||
RandomizationFlag.GINGER_ISLAND not in connection.flag]
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import itertools
|
||||
from typing import Dict, List
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.generic import Rules as MultiWorldRules
|
||||
from . import options, locations
|
||||
from .bundles import Bundle
|
||||
from .options import StardewValleyOptions, ToolProgression, BuildingProgression, SkillProgression, ExcludeGingerIsland, Cropsanity, SpecialOrderLocations, Museumsanity, \
|
||||
BackpackProgression, ArcadeMachineLocations
|
||||
from .strings.entrance_names import dig_to_mines_floor, dig_to_skull_floor, Entrance, move_to_woods_depth, \
|
||||
DeepWoodsEntrance, AlecEntrance, MagicEntrance
|
||||
from .data.museum_data import all_museum_items, all_museum_minerals, all_museum_artifacts, \
|
||||
|
@ -13,9 +13,8 @@ from .data.museum_data import all_museum_items, all_museum_minerals, all_museum_
|
|||
from .strings.region_names import Region
|
||||
from .mods.mod_data import ModNames
|
||||
from .mods.logic import magic, deepwoods
|
||||
from .locations import LocationTags
|
||||
from .locations import LocationTags, locations_by_tag
|
||||
from .logic import StardewLogic, And, tool_upgrade_prices
|
||||
from .options import StardewOptions
|
||||
from .strings.ap_names.transport_names import Transportation
|
||||
from .strings.artisan_good_names import ArtisanGood
|
||||
from .strings.calendar_names import Weekday
|
||||
|
@ -28,251 +27,256 @@ from .strings.villager_names import NPC, ModNPC
|
|||
from .strings.wallet_item_names import Wallet
|
||||
|
||||
|
||||
def set_rules(multi_world: MultiWorld, player: int, world_options: StardewOptions, logic: StardewLogic,
|
||||
current_bundles: Dict[str, Bundle]):
|
||||
all_location_names = list(location.name for location in multi_world.get_locations(player))
|
||||
def set_rules(world):
|
||||
multiworld = world.multiworld
|
||||
world_options = world.options
|
||||
player = world.player
|
||||
logic = world.logic
|
||||
current_bundles = world.modified_bundles
|
||||
|
||||
all_location_names = list(location.name for location in multiworld.get_locations(player))
|
||||
|
||||
set_entrance_rules(logic, multi_world, player, world_options)
|
||||
set_entrance_rules(logic, multiworld, player, world_options)
|
||||
|
||||
set_ginger_island_rules(logic, multi_world, player, world_options)
|
||||
set_ginger_island_rules(logic, multiworld, player, world_options)
|
||||
|
||||
# Those checks do not exist if ToolProgression is vanilla
|
||||
if world_options[options.ToolProgression] != options.ToolProgression.option_vanilla:
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Purchase Fiberglass Rod", player),
|
||||
if world_options.tool_progression != ToolProgression.option_vanilla:
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Purchase Fiberglass Rod", player),
|
||||
(logic.has_skill_level(Skill.fishing, 2) & logic.can_spend_money(1800)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Purchase Iridium Rod", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Purchase Iridium Rod", player),
|
||||
(logic.has_skill_level(Skill.fishing, 6) & logic.can_spend_money(7500)).simplify())
|
||||
|
||||
materials = [None, "Copper", "Iron", "Gold", "Iridium"]
|
||||
tool = [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.watering_can, Tool.trash_can]
|
||||
for (previous, material), tool in itertools.product(zip(materials[:4], materials[1:]), tool):
|
||||
if previous is None:
|
||||
MultiWorldRules.add_rule(multi_world.get_location(f"{material} {tool} Upgrade", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location(f"{material} {tool} Upgrade", player),
|
||||
(logic.has(f"{material} Ore") &
|
||||
logic.can_spend_money(tool_upgrade_prices[material])).simplify())
|
||||
else:
|
||||
MultiWorldRules.add_rule(multi_world.get_location(f"{material} {tool} Upgrade", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location(f"{material} {tool} Upgrade", player),
|
||||
(logic.has(f"{material} Ore") & logic.has_tool(tool, previous) &
|
||||
logic.can_spend_money(tool_upgrade_prices[material])).simplify())
|
||||
|
||||
set_skills_rules(logic, multi_world, player, world_options)
|
||||
set_skills_rules(logic, multiworld, player, world_options)
|
||||
|
||||
# Bundles
|
||||
for bundle in current_bundles.values():
|
||||
location = multi_world.get_location(bundle.get_name_with_bundle(), player)
|
||||
location = multiworld.get_location(bundle.get_name_with_bundle(), player)
|
||||
rules = logic.can_complete_bundle(bundle.requirements, bundle.number_required)
|
||||
simplified_rules = rules.simplify()
|
||||
MultiWorldRules.set_rule(location, simplified_rules)
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Crafts Room", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Crafts Room", player),
|
||||
And(logic.can_reach_location(bundle.name)
|
||||
for bundle in locations.locations_by_tag[LocationTags.CRAFTS_ROOM_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Pantry", player),
|
||||
for bundle in locations_by_tag[LocationTags.CRAFTS_ROOM_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Pantry", player),
|
||||
And(logic.can_reach_location(bundle.name)
|
||||
for bundle in locations.locations_by_tag[LocationTags.PANTRY_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Fish Tank", player),
|
||||
for bundle in locations_by_tag[LocationTags.PANTRY_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Fish Tank", player),
|
||||
And(logic.can_reach_location(bundle.name)
|
||||
for bundle in locations.locations_by_tag[LocationTags.FISH_TANK_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Boiler Room", player),
|
||||
for bundle in locations_by_tag[LocationTags.FISH_TANK_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Boiler Room", player),
|
||||
And(logic.can_reach_location(bundle.name)
|
||||
for bundle in locations.locations_by_tag[LocationTags.BOILER_ROOM_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Bulletin Board", player),
|
||||
for bundle in locations_by_tag[LocationTags.BOILER_ROOM_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Bulletin Board", player),
|
||||
And(logic.can_reach_location(bundle.name)
|
||||
for bundle
|
||||
in locations.locations_by_tag[LocationTags.BULLETIN_BOARD_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Vault", player),
|
||||
in locations_by_tag[LocationTags.BULLETIN_BOARD_BUNDLE]).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Vault", player),
|
||||
And(logic.can_reach_location(bundle.name)
|
||||
for bundle in locations.locations_by_tag[LocationTags.VAULT_BUNDLE]).simplify())
|
||||
for bundle in locations_by_tag[LocationTags.VAULT_BUNDLE]).simplify())
|
||||
|
||||
# Buildings
|
||||
if world_options[options.BuildingProgression] != options.BuildingProgression.option_vanilla:
|
||||
for building in locations.locations_by_tag[LocationTags.BUILDING_BLUEPRINT]:
|
||||
if building.mod_name is not None and building.mod_name not in world_options[options.Mods]:
|
||||
if world_options.building_progression != BuildingProgression.option_vanilla:
|
||||
for building in locations_by_tag[LocationTags.BUILDING_BLUEPRINT]:
|
||||
if building.mod_name is not None and building.mod_name not in world_options.mods:
|
||||
continue
|
||||
MultiWorldRules.set_rule(multi_world.get_location(building.name, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location(building.name, player),
|
||||
logic.building_rules[building.name.replace(" Blueprint", "")].simplify())
|
||||
|
||||
set_cropsanity_rules(all_location_names, logic, multi_world, player, world_options)
|
||||
set_story_quests_rules(all_location_names, logic, multi_world, player, world_options)
|
||||
set_special_order_rules(all_location_names, logic, multi_world, player, world_options)
|
||||
set_help_wanted_quests_rules(logic, multi_world, player, world_options)
|
||||
set_fishsanity_rules(all_location_names, logic, multi_world, player)
|
||||
set_museumsanity_rules(all_location_names, logic, multi_world, player, world_options)
|
||||
set_friendsanity_rules(all_location_names, logic, multi_world, player)
|
||||
set_backpack_rules(logic, multi_world, player, world_options)
|
||||
set_festival_rules(all_location_names, logic, multi_world, player)
|
||||
set_cropsanity_rules(all_location_names, logic, multiworld, player, world_options)
|
||||
set_story_quests_rules(all_location_names, logic, multiworld, player, world_options)
|
||||
set_special_order_rules(all_location_names, logic, multiworld, player, world_options)
|
||||
set_help_wanted_quests_rules(logic, multiworld, player, world_options)
|
||||
set_fishsanity_rules(all_location_names, logic, multiworld, player)
|
||||
set_museumsanity_rules(all_location_names, logic, multiworld, player, world_options)
|
||||
set_friendsanity_rules(all_location_names, logic, multiworld, player)
|
||||
set_backpack_rules(logic, multiworld, player, world_options)
|
||||
set_festival_rules(all_location_names, logic, multiworld, player)
|
||||
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Old Master Cannoli", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Old Master Cannoli", player),
|
||||
logic.has("Sweet Gem Berry").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Galaxy Sword Shrine", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Galaxy Sword Shrine", player),
|
||||
logic.has("Prismatic Shard").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Have a Baby", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Have a Baby", player),
|
||||
logic.can_reproduce(1).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Have Another Baby", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Have Another Baby", player),
|
||||
logic.can_reproduce(2).simplify())
|
||||
|
||||
set_traveling_merchant_rules(logic, multi_world, player)
|
||||
set_arcade_machine_rules(logic, multi_world, player, world_options)
|
||||
set_deepwoods_rules(logic, multi_world, player, world_options)
|
||||
set_magic_spell_rules(logic, multi_world, player, world_options)
|
||||
set_traveling_merchant_rules(logic, multiworld, player)
|
||||
set_arcade_machine_rules(logic, multiworld, player, world_options)
|
||||
set_deepwoods_rules(logic, multiworld, player, world_options)
|
||||
set_magic_spell_rules(logic, multiworld, player, world_options)
|
||||
|
||||
|
||||
def set_skills_rules(logic, multi_world, player, world_options):
|
||||
def set_skills_rules(logic, multiworld, player, world_options):
|
||||
# Skills
|
||||
if world_options[options.SkillProgression] != options.SkillProgression.option_vanilla:
|
||||
if world_options.skill_progression != SkillProgression.option_vanilla:
|
||||
for i in range(1, 11):
|
||||
set_skill_rule(logic, multi_world, player, Skill.farming, i)
|
||||
set_skill_rule(logic, multi_world, player, Skill.fishing, i)
|
||||
set_skill_rule(logic, multi_world, player, Skill.foraging, i)
|
||||
set_skill_rule(logic, multi_world, player, Skill.mining, i)
|
||||
set_skill_rule(logic, multi_world, player, Skill.combat, i)
|
||||
set_skill_rule(logic, multiworld, player, Skill.farming, i)
|
||||
set_skill_rule(logic, multiworld, player, Skill.fishing, i)
|
||||
set_skill_rule(logic, multiworld, player, Skill.foraging, i)
|
||||
set_skill_rule(logic, multiworld, player, Skill.mining, i)
|
||||
set_skill_rule(logic, multiworld, player, Skill.combat, i)
|
||||
|
||||
# Modded Skills
|
||||
if ModNames.luck_skill in world_options[options.Mods]:
|
||||
set_skill_rule(logic, multi_world, player, ModSkill.luck, i)
|
||||
if ModNames.magic in world_options[options.Mods]:
|
||||
set_skill_rule(logic, multi_world, player, ModSkill.magic, i)
|
||||
if ModNames.binning_skill in world_options[options.Mods]:
|
||||
set_skill_rule(logic, multi_world, player, ModSkill.binning, i)
|
||||
if ModNames.cooking_skill in world_options[options.Mods]:
|
||||
set_skill_rule(logic, multi_world, player, ModSkill.cooking, i)
|
||||
if ModNames.socializing_skill in world_options[options.Mods]:
|
||||
set_skill_rule(logic, multi_world, player, ModSkill.socializing, i)
|
||||
if ModNames.archaeology in world_options[options.Mods]:
|
||||
set_skill_rule(logic, multi_world, player, ModSkill.archaeology, i)
|
||||
if ModNames.luck_skill in world_options.mods:
|
||||
set_skill_rule(logic, multiworld, player, ModSkill.luck, i)
|
||||
if ModNames.magic in world_options.mods:
|
||||
set_skill_rule(logic, multiworld, player, ModSkill.magic, i)
|
||||
if ModNames.binning_skill in world_options.mods:
|
||||
set_skill_rule(logic, multiworld, player, ModSkill.binning, i)
|
||||
if ModNames.cooking_skill in world_options.mods:
|
||||
set_skill_rule(logic, multiworld, player, ModSkill.cooking, i)
|
||||
if ModNames.socializing_skill in world_options.mods:
|
||||
set_skill_rule(logic, multiworld, player, ModSkill.socializing, i)
|
||||
if ModNames.archaeology in world_options.mods:
|
||||
set_skill_rule(logic, multiworld, player, ModSkill.archaeology, i)
|
||||
|
||||
|
||||
def set_skill_rule(logic, multi_world, player, skill: str, level: int):
|
||||
def set_skill_rule(logic, multiworld, player, skill: str, level: int):
|
||||
location_name = f"Level {level} {skill}"
|
||||
location = multi_world.get_location(location_name, player)
|
||||
location = multiworld.get_location(location_name, player)
|
||||
rule = logic.can_earn_skill_level(skill, level).simplify()
|
||||
MultiWorldRules.set_rule(location, rule)
|
||||
|
||||
|
||||
def set_entrance_rules(logic, multi_world, player, world_options: StardewOptions):
|
||||
def set_entrance_rules(logic, multiworld, player, world_options: StardewValleyOptions):
|
||||
for floor in range(5, 120 + 5, 5):
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(dig_to_mines_floor(floor), player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(dig_to_mines_floor(floor), player),
|
||||
logic.can_mine_to_floor(floor).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_tide_pools, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_tide_pools, player),
|
||||
logic.received("Beach Bridge") | (magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_quarry, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_quarry, player),
|
||||
logic.received("Bridge Repair") | (magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_secret_woods, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_secret_woods, player),
|
||||
logic.has_tool(Tool.axe, "Iron") | (magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.forest_to_sewer, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.forest_to_sewer, player),
|
||||
logic.has_rusty_key().simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.town_to_sewer, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.town_to_sewer, player),
|
||||
logic.has_rusty_key().simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.take_bus_to_desert, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.take_bus_to_desert, player),
|
||||
logic.received("Bus Repair").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_skull_cavern, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_skull_cavern, player),
|
||||
logic.received(Wallet.skull_key).simplify())
|
||||
for floor in range(25, 200 + 25, 25):
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(dig_to_skull_floor(floor), player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(dig_to_skull_floor(floor), player),
|
||||
logic.can_mine_to_skull_cavern_floor(floor).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.talk_to_mines_dwarf, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.talk_to_mines_dwarf, player),
|
||||
logic.can_speak_dwarf() & logic.has_tool(Tool.pickaxe, ToolMaterial.iron))
|
||||
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_desert_obelisk, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.use_desert_obelisk, player),
|
||||
logic.received(Transportation.desert_obelisk).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_island_obelisk, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.use_island_obelisk, player),
|
||||
logic.received(Transportation.island_obelisk).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_farm_obelisk, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.use_farm_obelisk, player),
|
||||
logic.received(Transportation.farm_obelisk).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.buy_from_traveling_merchant, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.buy_from_traveling_merchant, player),
|
||||
logic.has_traveling_merchant())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_greenhouse, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_greenhouse, player),
|
||||
logic.received("Greenhouse"))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_adventurer_guild, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.mountain_to_adventurer_guild, player),
|
||||
logic.received("Adventurer's Guild"))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_railroad, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.mountain_to_railroad, player),
|
||||
logic.has_lived_months(2))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_witch_warp_cave, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_witch_warp_cave, player),
|
||||
logic.received(Wallet.dark_talisman) | (magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_witch_hut, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_witch_hut, player),
|
||||
(logic.has(ArtisanGood.void_mayonnaise) | magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_mutant_bug_lair, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_mutant_bug_lair, player),
|
||||
((logic.has_rusty_key() & logic.can_reach_region(Region.railroad) &
|
||||
logic.can_meet(NPC.krobus) | magic.can_blink(logic)).simplify()))
|
||||
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_harvey_room, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_harvey_room, player),
|
||||
logic.has_relationship(NPC.harvey, 2))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_maru_room, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.mountain_to_maru_room, player),
|
||||
logic.has_relationship(NPC.maru, 2))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_sebastian_room, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_sebastian_room, player),
|
||||
(logic.has_relationship(NPC.sebastian, 2) | magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.forest_to_leah_cottage, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.forest_to_leah_cottage, player),
|
||||
logic.has_relationship(NPC.leah, 2))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_elliott_house, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_elliott_house, player),
|
||||
logic.has_relationship(NPC.elliott, 2))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_sunroom, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_sunroom, player),
|
||||
logic.has_relationship(NPC.caroline, 2))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_wizard_basement, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.enter_wizard_basement, player),
|
||||
logic.has_relationship(NPC.wizard, 4))
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_leo_treehouse, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.mountain_to_leo_treehouse, player),
|
||||
logic.received("Treehouse"))
|
||||
if ModNames.alec in world_options[options.Mods]:
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(AlecEntrance.petshop_to_bedroom, player),
|
||||
if ModNames.alec in world_options.mods:
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(AlecEntrance.petshop_to_bedroom, player),
|
||||
(logic.has_relationship(ModNPC.alec, 2) | magic.can_blink(logic)).simplify())
|
||||
|
||||
|
||||
def set_ginger_island_rules(logic: StardewLogic, multi_world, player, world_options: StardewOptions):
|
||||
set_island_entrances_rules(logic, multi_world, player)
|
||||
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
def set_ginger_island_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions):
|
||||
set_island_entrances_rules(logic, multiworld, player)
|
||||
if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return
|
||||
|
||||
set_boat_repair_rules(logic, multi_world, player)
|
||||
set_island_parrot_rules(logic, multi_world, player)
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Open Professor Snail Cave", player),
|
||||
set_boat_repair_rules(logic, multiworld, player)
|
||||
set_island_parrot_rules(logic, multiworld, player)
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Open Professor Snail Cave", player),
|
||||
logic.has(Craftable.cherry_bomb).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Complete Island Field Office", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Complete Island Field Office", player),
|
||||
logic.can_complete_field_office().simplify())
|
||||
|
||||
|
||||
def set_boat_repair_rules(logic: StardewLogic, multi_world, player):
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Repair Boat Hull", player),
|
||||
def set_boat_repair_rules(logic: StardewLogic, multiworld, player):
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Repair Boat Hull", player),
|
||||
logic.has(Material.hardwood).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Repair Boat Anchor", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Repair Boat Anchor", player),
|
||||
logic.has(MetalBar.iridium).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Repair Ticket Machine", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Repair Ticket Machine", player),
|
||||
logic.has(ArtisanGood.battery_pack).simplify())
|
||||
|
||||
|
||||
def set_island_entrances_rules(logic: StardewLogic, multi_world, player):
|
||||
def set_island_entrances_rules(logic: StardewLogic, multiworld, player):
|
||||
boat_repaired = logic.received(Transportation.boat_repair).simplify()
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.fish_shop_to_boat_tunnel, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.fish_shop_to_boat_tunnel, player),
|
||||
boat_repaired)
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.boat_to_ginger_island, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.boat_to_ginger_island, player),
|
||||
boat_repaired)
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_south_to_west, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_south_to_west, player),
|
||||
logic.received("Island West Turtle").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_south_to_north, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_south_to_north, player),
|
||||
logic.received("Island North Turtle").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_west_to_islandfarmhouse, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_west_to_islandfarmhouse, player),
|
||||
logic.received("Island Farmhouse").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_west_to_gourmand_cave, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_west_to_gourmand_cave, player),
|
||||
logic.received("Island Farmhouse").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_north_to_dig_site, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_north_to_dig_site, player),
|
||||
logic.received("Dig Site Bridge").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.dig_site_to_professor_snail_cave, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.dig_site_to_professor_snail_cave, player),
|
||||
logic.received("Open Professor Snail Cave").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.talk_to_island_trader, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.talk_to_island_trader, player),
|
||||
logic.received("Island Trader").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_south_to_southeast, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_south_to_southeast, player),
|
||||
logic.received("Island Resort").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_island_resort, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.use_island_resort, player),
|
||||
logic.received("Island Resort").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_west_to_qi_walnut_room, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_west_to_qi_walnut_room, player),
|
||||
logic.received("Qi Walnut Room").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_north_to_volcano, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.island_north_to_volcano, player),
|
||||
(logic.can_water(0) | logic.received("Volcano Bridge") |
|
||||
magic.can_blink(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.volcano_to_secret_beach, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.volcano_to_secret_beach, player),
|
||||
logic.can_water(2).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.climb_to_volcano_5, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.climb_to_volcano_5, player),
|
||||
(logic.can_mine_perfectly() & logic.can_water(1)).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.talk_to_volcano_dwarf, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.talk_to_volcano_dwarf, player),
|
||||
logic.can_speak_dwarf())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.climb_to_volcano_10, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(Entrance.climb_to_volcano_10, player),
|
||||
(logic.can_mine_perfectly() & logic.can_water(1) & logic.received("Volcano Exit Shortcut")).simplify())
|
||||
parrots = [Entrance.parrot_express_docks_to_volcano, Entrance.parrot_express_jungle_to_volcano,
|
||||
Entrance.parrot_express_dig_site_to_volcano, Entrance.parrot_express_docks_to_dig_site,
|
||||
|
@ -281,78 +285,78 @@ def set_island_entrances_rules(logic: StardewLogic, multi_world, player):
|
|||
Entrance.parrot_express_volcano_to_jungle, Entrance.parrot_express_jungle_to_docks,
|
||||
Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_volcano_to_docks]
|
||||
for parrot in parrots:
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(parrot, player), logic.received(Transportation.parrot_express).simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(parrot, player), logic.received(Transportation.parrot_express).simplify())
|
||||
|
||||
|
||||
def set_island_parrot_rules(logic: StardewLogic, multi_world, player):
|
||||
def set_island_parrot_rules(logic: StardewLogic, multiworld, player):
|
||||
has_walnut = logic.has_walnut(1).simplify()
|
||||
has_5_walnut = logic.has_walnut(5).simplify()
|
||||
has_10_walnut = logic.has_walnut(10).simplify()
|
||||
has_20_walnut = logic.has_walnut(20).simplify()
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Leo's Parrot", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Leo's Parrot", player),
|
||||
has_walnut)
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Island West Turtle", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Island West Turtle", player),
|
||||
has_10_walnut & logic.received("Island North Turtle"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Island Farmhouse", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Island Farmhouse", player),
|
||||
has_20_walnut)
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Island Mailbox", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Island Mailbox", player),
|
||||
has_5_walnut & logic.received("Island Farmhouse"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location(Transportation.farm_obelisk, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location(Transportation.farm_obelisk, player),
|
||||
has_20_walnut & logic.received("Island Mailbox"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Dig Site Bridge", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Dig Site Bridge", player),
|
||||
has_10_walnut & logic.received("Island West Turtle"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Island Trader", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Island Trader", player),
|
||||
has_10_walnut & logic.received("Island Farmhouse"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Volcano Bridge", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Volcano Bridge", player),
|
||||
has_5_walnut & logic.received("Island West Turtle") &
|
||||
logic.can_reach_region(Region.volcano_floor_10))
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Volcano Exit Shortcut", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Volcano Exit Shortcut", player),
|
||||
has_5_walnut & logic.received("Island West Turtle"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Island Resort", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Island Resort", player),
|
||||
has_20_walnut & logic.received("Island Farmhouse"))
|
||||
MultiWorldRules.add_rule(multi_world.get_location(Transportation.parrot_express, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location(Transportation.parrot_express, player),
|
||||
has_10_walnut)
|
||||
|
||||
|
||||
def set_cropsanity_rules(all_location_names: List[str], logic, multi_world, player, world_options: StardewOptions):
|
||||
if world_options[options.Cropsanity] == options.Cropsanity.option_disabled:
|
||||
def set_cropsanity_rules(all_location_names: List[str], logic, multiworld, player, world_options: StardewValleyOptions):
|
||||
if world_options.cropsanity == Cropsanity.option_disabled:
|
||||
return
|
||||
|
||||
harvest_prefix = "Harvest "
|
||||
harvest_prefix_length = len(harvest_prefix)
|
||||
for harvest_location in locations.locations_by_tag[LocationTags.CROPSANITY]:
|
||||
if harvest_location.name in all_location_names and (harvest_location.mod_name is None or harvest_location.mod_name in world_options[options.Mods]):
|
||||
for harvest_location in locations_by_tag[LocationTags.CROPSANITY]:
|
||||
if harvest_location.name in all_location_names and (harvest_location.mod_name is None or harvest_location.mod_name in world_options.mods):
|
||||
crop_name = harvest_location.name[harvest_prefix_length:]
|
||||
MultiWorldRules.set_rule(multi_world.get_location(harvest_location.name, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location(harvest_location.name, player),
|
||||
logic.has(crop_name).simplify())
|
||||
|
||||
|
||||
def set_story_quests_rules(all_location_names: List[str], logic, multi_world, player, world_options: StardewOptions):
|
||||
for quest in locations.locations_by_tag[LocationTags.QUEST]:
|
||||
if quest.name in all_location_names and (quest.mod_name is None or quest.mod_name in world_options[options.Mods]):
|
||||
MultiWorldRules.set_rule(multi_world.get_location(quest.name, player),
|
||||
def set_story_quests_rules(all_location_names: List[str], logic, multiworld, player, world_options: StardewValleyOptions):
|
||||
for quest in locations_by_tag[LocationTags.QUEST]:
|
||||
if quest.name in all_location_names and (quest.mod_name is None or quest.mod_name in world_options.mods):
|
||||
MultiWorldRules.set_rule(multiworld.get_location(quest.name, player),
|
||||
logic.quest_rules[quest.name].simplify())
|
||||
|
||||
|
||||
def set_special_order_rules(all_location_names: List[str], logic: StardewLogic, multi_world, player,
|
||||
world_options: StardewOptions):
|
||||
if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled:
|
||||
def set_special_order_rules(all_location_names: List[str], logic: StardewLogic, multiworld, player,
|
||||
world_options: StardewValleyOptions):
|
||||
if world_options.special_order_locations == SpecialOrderLocations.option_disabled:
|
||||
return
|
||||
board_rule = logic.received("Special Order Board") & logic.has_lived_months(4)
|
||||
for board_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]:
|
||||
for board_order in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]:
|
||||
if board_order.name in all_location_names:
|
||||
order_rule = board_rule & logic.special_order_rules[board_order.name]
|
||||
MultiWorldRules.set_rule(multi_world.get_location(board_order.name, player), order_rule.simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_location(board_order.name, player), order_rule.simplify())
|
||||
|
||||
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
|
||||
if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return
|
||||
if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_board_only:
|
||||
if world_options.special_order_locations == SpecialOrderLocations.option_board_only:
|
||||
return
|
||||
qi_rule = logic.can_reach_region(Region.qi_walnut_room) & logic.has_lived_months(8)
|
||||
for qi_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_QI]:
|
||||
for qi_order in locations_by_tag[LocationTags.SPECIAL_ORDER_QI]:
|
||||
if qi_order.name in all_location_names:
|
||||
order_rule = qi_rule & logic.special_order_rules[qi_order.name]
|
||||
MultiWorldRules.set_rule(multi_world.get_location(qi_order.name, player), order_rule.simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_location(qi_order.name, player), order_rule.simplify())
|
||||
|
||||
|
||||
help_wanted_prefix = "Help Wanted:"
|
||||
|
@ -362,8 +366,8 @@ fishing = "Fishing"
|
|||
slay_monsters = "Slay Monsters"
|
||||
|
||||
|
||||
def set_help_wanted_quests_rules(logic: StardewLogic, multi_world, player, world_options):
|
||||
help_wanted_number = world_options[options.HelpWantedLocations]
|
||||
def set_help_wanted_quests_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions):
|
||||
help_wanted_number = world_options.help_wanted_locations
|
||||
for i in range(0, help_wanted_number):
|
||||
set_number = i // 7
|
||||
month_rule = logic.has_lived_months(set_number).simplify()
|
||||
|
@ -371,58 +375,58 @@ def set_help_wanted_quests_rules(logic: StardewLogic, multi_world, player, world
|
|||
quest_number_in_set = i % 7
|
||||
if quest_number_in_set < 4:
|
||||
quest_number = set_number * 4 + quest_number_in_set + 1
|
||||
set_help_wanted_delivery_rule(multi_world, player, month_rule, quest_number)
|
||||
set_help_wanted_delivery_rule(multiworld, player, month_rule, quest_number)
|
||||
elif quest_number_in_set == 4:
|
||||
set_help_wanted_fishing_rule(logic, multi_world, player, month_rule, quest_number)
|
||||
set_help_wanted_fishing_rule(logic, multiworld, player, month_rule, quest_number)
|
||||
elif quest_number_in_set == 5:
|
||||
set_help_wanted_slay_monsters_rule(logic, multi_world, player, month_rule, quest_number)
|
||||
set_help_wanted_slay_monsters_rule(logic, multiworld, player, month_rule, quest_number)
|
||||
elif quest_number_in_set == 6:
|
||||
set_help_wanted_gathering_rule(multi_world, player, month_rule, quest_number)
|
||||
set_help_wanted_gathering_rule(multiworld, player, month_rule, quest_number)
|
||||
|
||||
|
||||
def set_help_wanted_delivery_rule(multi_world, player, month_rule, quest_number):
|
||||
def set_help_wanted_delivery_rule(multiworld, player, month_rule, quest_number):
|
||||
location_name = f"{help_wanted_prefix} {item_delivery} {quest_number}"
|
||||
MultiWorldRules.set_rule(multi_world.get_location(location_name, player), month_rule)
|
||||
MultiWorldRules.set_rule(multiworld.get_location(location_name, player), month_rule)
|
||||
|
||||
|
||||
def set_help_wanted_gathering_rule(multi_world, player, month_rule, quest_number):
|
||||
def set_help_wanted_gathering_rule(multiworld, player, month_rule, quest_number):
|
||||
location_name = f"{help_wanted_prefix} {gathering} {quest_number}"
|
||||
MultiWorldRules.set_rule(multi_world.get_location(location_name, player), month_rule)
|
||||
MultiWorldRules.set_rule(multiworld.get_location(location_name, player), month_rule)
|
||||
|
||||
|
||||
def set_help_wanted_fishing_rule(logic: StardewLogic, multi_world, player, month_rule, quest_number):
|
||||
def set_help_wanted_fishing_rule(logic: StardewLogic, multiworld, player, month_rule, quest_number):
|
||||
location_name = f"{help_wanted_prefix} {fishing} {quest_number}"
|
||||
fishing_rule = month_rule & logic.can_fish()
|
||||
MultiWorldRules.set_rule(multi_world.get_location(location_name, player), fishing_rule.simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_location(location_name, player), fishing_rule.simplify())
|
||||
|
||||
|
||||
def set_help_wanted_slay_monsters_rule(logic: StardewLogic, multi_world, player, month_rule, quest_number):
|
||||
def set_help_wanted_slay_monsters_rule(logic: StardewLogic, multiworld, player, month_rule, quest_number):
|
||||
location_name = f"{help_wanted_prefix} {slay_monsters} {quest_number}"
|
||||
slay_rule = month_rule & logic.can_do_combat_at_level("Basic")
|
||||
MultiWorldRules.set_rule(multi_world.get_location(location_name, player), slay_rule.simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_location(location_name, player), slay_rule.simplify())
|
||||
|
||||
|
||||
def set_fishsanity_rules(all_location_names: List[str], logic: StardewLogic, multi_world: MultiWorld, player: int):
|
||||
def set_fishsanity_rules(all_location_names: List[str], logic: StardewLogic, multiworld: MultiWorld, player: int):
|
||||
fish_prefix = "Fishsanity: "
|
||||
for fish_location in locations.locations_by_tag[LocationTags.FISHSANITY]:
|
||||
for fish_location in locations_by_tag[LocationTags.FISHSANITY]:
|
||||
if fish_location.name in all_location_names:
|
||||
fish_name = fish_location.name[len(fish_prefix):]
|
||||
MultiWorldRules.set_rule(multi_world.get_location(fish_location.name, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location(fish_location.name, player),
|
||||
logic.has(fish_name).simplify())
|
||||
|
||||
|
||||
def set_museumsanity_rules(all_location_names: List[str], logic: StardewLogic, multi_world: MultiWorld, player: int,
|
||||
world_options: StardewOptions):
|
||||
def set_museumsanity_rules(all_location_names: List[str], logic: StardewLogic, multiworld: MultiWorld, player: int,
|
||||
world_options: StardewValleyOptions):
|
||||
museum_prefix = "Museumsanity: "
|
||||
if world_options[options.Museumsanity] == options.Museumsanity.option_milestones:
|
||||
for museum_milestone in locations.locations_by_tag[LocationTags.MUSEUM_MILESTONES]:
|
||||
set_museum_milestone_rule(logic, multi_world, museum_milestone, museum_prefix, player)
|
||||
elif world_options[options.Museumsanity] != options.Museumsanity.option_none:
|
||||
set_museum_individual_donations_rules(all_location_names, logic, multi_world, museum_prefix, player)
|
||||
if world_options.museumsanity == Museumsanity.option_milestones:
|
||||
for museum_milestone in locations_by_tag[LocationTags.MUSEUM_MILESTONES]:
|
||||
set_museum_milestone_rule(logic, multiworld, museum_milestone, museum_prefix, player)
|
||||
elif world_options.museumsanity != Museumsanity.option_none:
|
||||
set_museum_individual_donations_rules(all_location_names, logic, multiworld, museum_prefix, player)
|
||||
|
||||
|
||||
def set_museum_individual_donations_rules(all_location_names, logic: StardewLogic, multi_world, museum_prefix, player):
|
||||
all_donations = sorted(locations.locations_by_tag[LocationTags.MUSEUM_DONATIONS],
|
||||
def set_museum_individual_donations_rules(all_location_names, logic: StardewLogic, multiworld, museum_prefix, player):
|
||||
all_donations = sorted(locations_by_tag[LocationTags.MUSEUM_DONATIONS],
|
||||
key=lambda x: all_museum_items_by_name[x.name[len(museum_prefix):]].difficulty, reverse=True)
|
||||
counter = 0
|
||||
number_donations = len(all_donations)
|
||||
|
@ -430,13 +434,14 @@ def set_museum_individual_donations_rules(all_location_names, logic: StardewLogi
|
|||
if museum_location.name in all_location_names:
|
||||
donation_name = museum_location.name[len(museum_prefix):]
|
||||
required_detectors = counter * 5 // number_donations
|
||||
rule = logic.can_donate_museum_item(all_museum_items_by_name[donation_name]) & logic.received("Traveling Merchant Metal Detector", required_detectors)
|
||||
MultiWorldRules.set_rule(multi_world.get_location(museum_location.name, player),
|
||||
rule = logic.can_donate_museum_item(all_museum_items_by_name[donation_name]) & logic.received("Traveling Merchant Metal Detector",
|
||||
required_detectors)
|
||||
MultiWorldRules.set_rule(multiworld.get_location(museum_location.name, player),
|
||||
rule.simplify())
|
||||
counter += 1
|
||||
|
||||
|
||||
def set_museum_milestone_rule(logic: StardewLogic, multi_world: MultiWorld, museum_milestone, museum_prefix: str,
|
||||
def set_museum_milestone_rule(logic: StardewLogic, multiworld: MultiWorld, museum_milestone, museum_prefix: str,
|
||||
player: int):
|
||||
milestone_name = museum_milestone.name[len(museum_prefix):]
|
||||
donations_suffix = " Donations"
|
||||
|
@ -462,7 +467,7 @@ def set_museum_milestone_rule(logic: StardewLogic, multi_world: MultiWorld, muse
|
|||
rule = logic.can_donate_museum_item(Artifact.ancient_seed) & logic.received(metal_detector, 4)
|
||||
if rule is None:
|
||||
return
|
||||
MultiWorldRules.set_rule(multi_world.get_location(museum_milestone.name, player), rule.simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_location(museum_milestone.name, player), rule.simplify())
|
||||
|
||||
|
||||
def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items, donation_func):
|
||||
|
@ -473,156 +478,156 @@ def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, acce
|
|||
return rule
|
||||
|
||||
|
||||
def set_backpack_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options):
|
||||
if world_options[options.BackpackProgression] != options.BackpackProgression.option_vanilla:
|
||||
MultiWorldRules.set_rule(multi_world.get_location("Large Pack", player),
|
||||
def set_backpack_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions):
|
||||
if world_options.backpack_progression != BackpackProgression.option_vanilla:
|
||||
MultiWorldRules.set_rule(multiworld.get_location("Large Pack", player),
|
||||
logic.can_spend_money(2000).simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_location("Deluxe Pack", player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location("Deluxe Pack", player),
|
||||
(logic.can_spend_money(10000) & logic.received("Progressive Backpack")).simplify())
|
||||
if ModNames.big_backpack in world_options[options.Mods]:
|
||||
MultiWorldRules.set_rule(multi_world.get_location("Premium Pack", player),
|
||||
if ModNames.big_backpack in world_options.mods:
|
||||
MultiWorldRules.set_rule(multiworld.get_location("Premium Pack", player),
|
||||
(logic.can_spend_money(150000) &
|
||||
logic.received("Progressive Backpack", 2)).simplify())
|
||||
|
||||
|
||||
def set_festival_rules(all_location_names: List[str], logic: StardewLogic, multi_world, player):
|
||||
def set_festival_rules(all_location_names: List[str], logic: StardewLogic, multiworld, player):
|
||||
festival_locations = []
|
||||
festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL])
|
||||
festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL_HARD])
|
||||
festival_locations.extend(locations_by_tag[LocationTags.FESTIVAL])
|
||||
festival_locations.extend(locations_by_tag[LocationTags.FESTIVAL_HARD])
|
||||
for festival in festival_locations:
|
||||
if festival.name in all_location_names:
|
||||
MultiWorldRules.set_rule(multi_world.get_location(festival.name, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location(festival.name, player),
|
||||
logic.festival_rules[festival.name].simplify())
|
||||
|
||||
|
||||
def set_traveling_merchant_rules(logic: StardewLogic, multi_world: MultiWorld, player: int):
|
||||
def set_traveling_merchant_rules(logic: StardewLogic, multiworld: MultiWorld, player: int):
|
||||
for day in Weekday.all_days:
|
||||
item_for_day = f"Traveling Merchant: {day}"
|
||||
for i in range(1, 4):
|
||||
location_name = f"Traveling Merchant {day} Item {i}"
|
||||
MultiWorldRules.set_rule(multi_world.get_location(location_name, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location(location_name, player),
|
||||
logic.received(item_for_day))
|
||||
|
||||
|
||||
def set_arcade_machine_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options):
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.play_junimo_kart, player),
|
||||
def set_arcade_machine_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions):
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_junimo_kart, player),
|
||||
logic.received(Wallet.skull_key).simplify())
|
||||
if world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
if world_options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling:
|
||||
return
|
||||
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.play_junimo_kart, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_junimo_kart, player),
|
||||
logic.has("Junimo Kart Small Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_junimo_kart_2, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_2, player),
|
||||
logic.has("Junimo Kart Medium Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_junimo_kart_3, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_3, player),
|
||||
logic.has("Junimo Kart Big Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Junimo Kart: Sunset Speedway (Victory)", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Junimo Kart: Sunset Speedway (Victory)", player),
|
||||
logic.has("Junimo Kart Max Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.play_journey_of_the_prairie_king, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_journey_of_the_prairie_king, player),
|
||||
logic.has("JotPK Small Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_jotpk_world_2, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_jotpk_world_2, player),
|
||||
logic.has("JotPK Medium Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_jotpk_world_3, player),
|
||||
MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_jotpk_world_3, player),
|
||||
logic.has("JotPK Big Buff").simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Journey of the Prairie King Victory", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Journey of the Prairie King Victory", player),
|
||||
logic.has("JotPK Max Buff").simplify())
|
||||
|
||||
|
||||
def set_friendsanity_rules(all_location_names: List[str], logic: StardewLogic, multi_world: MultiWorld, player: int):
|
||||
def set_friendsanity_rules(all_location_names: List[str], logic: StardewLogic, multiworld: MultiWorld, player: int):
|
||||
friend_prefix = "Friendsanity: "
|
||||
friend_suffix = " <3"
|
||||
for friend_location in locations.locations_by_tag[LocationTags.FRIENDSANITY]:
|
||||
if not friend_location.name in all_location_names:
|
||||
for friend_location in locations_by_tag[LocationTags.FRIENDSANITY]:
|
||||
if friend_location.name not in all_location_names:
|
||||
continue
|
||||
friend_location_without_prefix = friend_location.name[len(friend_prefix):]
|
||||
friend_location_trimmed = friend_location_without_prefix[:friend_location_without_prefix.index(friend_suffix)]
|
||||
split_index = friend_location_trimmed.rindex(" ")
|
||||
friend_name = friend_location_trimmed[:split_index]
|
||||
num_hearts = int(friend_location_trimmed[split_index + 1:])
|
||||
MultiWorldRules.set_rule(multi_world.get_location(friend_location.name, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_location(friend_location.name, player),
|
||||
logic.can_earn_relationship(friend_name, num_hearts).simplify())
|
||||
|
||||
|
||||
def set_deepwoods_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options: StardewOptions):
|
||||
if ModNames.deepwoods in world_options[options.Mods]:
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Breaking Up Deep Woods Gingerbread House", player),
|
||||
def set_deepwoods_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions):
|
||||
if ModNames.deepwoods in world_options.mods:
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Breaking Up Deep Woods Gingerbread House", player),
|
||||
logic.has_tool(Tool.axe, "Gold") & deepwoods.can_reach_woods_depth(logic, 50).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Chop Down a Deep Woods Iridium Tree", player),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Chop Down a Deep Woods Iridium Tree", player),
|
||||
logic.has_tool(Tool.axe, "Iridium").simplify())
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(DeepWoodsEntrance.use_woods_obelisk, player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(DeepWoodsEntrance.use_woods_obelisk, player),
|
||||
logic.received("Woods Obelisk").simplify())
|
||||
for depth in range(10, 100 + 10, 10):
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(move_to_woods_depth(depth), player),
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(move_to_woods_depth(depth), player),
|
||||
deepwoods.can_chop_to_depth(logic, depth).simplify())
|
||||
|
||||
|
||||
def set_magic_spell_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options: StardewOptions):
|
||||
if ModNames.magic not in world_options[options.Mods]:
|
||||
def set_magic_spell_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions):
|
||||
if ModNames.magic not in world_options.mods:
|
||||
return
|
||||
|
||||
MultiWorldRules.set_rule(multi_world.get_entrance(MagicEntrance.store_to_altar, player),
|
||||
(logic.has_relationship(NPC.wizard, 3) &
|
||||
logic.can_reach_region(Region.wizard_tower)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Clear Debris", player),
|
||||
((logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic"))
|
||||
& magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Till", player),
|
||||
(logic.has_tool("Hoe", "Basic") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Water", player),
|
||||
(logic.has_tool("Watering Can", "Basic") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze All Toil School Locations", player),
|
||||
(logic.has_tool("Watering Can", "Basic") & logic.has_tool("Hoe", "Basic")
|
||||
& (logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic"))
|
||||
& magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.set_rule(multiworld.get_entrance(MagicEntrance.store_to_altar, player),
|
||||
(logic.has_relationship(NPC.wizard, 3) &
|
||||
logic.can_reach_region(Region.wizard_tower)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Clear Debris", player),
|
||||
((logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic"))
|
||||
& magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Till", player),
|
||||
(logic.has_tool("Hoe", "Basic") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Water", player),
|
||||
(logic.has_tool("Watering Can", "Basic") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze All Toil School Locations", player),
|
||||
(logic.has_tool("Watering Can", "Basic") & logic.has_tool("Hoe", "Basic")
|
||||
& (logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic"))
|
||||
& magic.can_use_altar(logic)).simplify())
|
||||
# Do I *want* to add boots into logic when you get them even in vanilla without effort? idk
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Evac", player),
|
||||
(logic.can_mine_perfectly() & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Haste", player),
|
||||
(logic.has("Coffee") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Heal", player),
|
||||
(logic.has("Life Elixir") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze All Life School Locations", player),
|
||||
(logic.has("Coffee") & logic.has("Life Elixir")
|
||||
& logic.can_mine_perfectly() & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Descend", player),
|
||||
(logic.can_reach_region(Region.mines) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Fireball", player),
|
||||
(logic.has("Fire Quartz") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Frostbite", player),
|
||||
(logic.can_mine_to_floor(70) & logic.can_fish(85) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze All Elemental School Locations", player),
|
||||
(logic.can_reach_region(Region.mines) & logic.has("Fire Quartz")
|
||||
& logic.can_reach_region(Region.mines_floor_70) & logic.can_fish(85) &
|
||||
magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Lantern", player),
|
||||
magic.can_use_altar(logic).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Tendrils", player),
|
||||
(logic.can_reach_region(Region.farm) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Shockwave", player),
|
||||
(logic.has("Earth Crystal") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze All Nature School Locations", player),
|
||||
(logic.has("Earth Crystal") & logic.can_reach_region("Farm") &
|
||||
magic.can_use_altar(logic)).simplify()),
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Meteor", player),
|
||||
(logic.can_reach_region(Region.farm) & logic.has_lived_months(12)
|
||||
& magic.can_use_altar(logic)).simplify()),
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Lucksteal", player),
|
||||
(logic.can_reach_region(Region.witch_hut) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze: Bloodmana", player),
|
||||
(logic.can_reach_region(Region.mines_floor_100) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze All Eldritch School Locations", player),
|
||||
(logic.can_reach_region(Region.witch_hut) &
|
||||
logic.can_reach_region(Region.mines_floor_100) &
|
||||
logic.can_reach_region(Region.farm) & logic.has_lived_months(12) &
|
||||
magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multi_world.get_location("Analyze Every Magic School Location", player),
|
||||
(logic.has_tool("Watering Can", "Basic") & logic.has_tool("Hoe", "Basic")
|
||||
& (logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic")) &
|
||||
logic.has("Coffee") & logic.has("Life Elixir")
|
||||
& logic.can_mine_perfectly() & logic.has("Earth Crystal") &
|
||||
logic.can_reach_region(Region.mines) &
|
||||
logic.has("Fire Quartz") & logic.can_fish(85) &
|
||||
logic.can_reach_region(Region.witch_hut) &
|
||||
logic.can_reach_region(Region.mines_floor_100) &
|
||||
logic.can_reach_region(Region.farm) & logic.has_lived_months(12) &
|
||||
magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Evac", player),
|
||||
(logic.can_mine_perfectly() & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Haste", player),
|
||||
(logic.has("Coffee") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Heal", player),
|
||||
(logic.has("Life Elixir") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze All Life School Locations", player),
|
||||
(logic.has("Coffee") & logic.has("Life Elixir")
|
||||
& logic.can_mine_perfectly() & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Descend", player),
|
||||
(logic.can_reach_region(Region.mines) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Fireball", player),
|
||||
(logic.has("Fire Quartz") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Frostbite", player),
|
||||
(logic.can_mine_to_floor(70) & logic.can_fish(85) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze All Elemental School Locations", player),
|
||||
(logic.can_reach_region(Region.mines) & logic.has("Fire Quartz")
|
||||
& logic.can_reach_region(Region.mines_floor_70) & logic.can_fish(85) &
|
||||
magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Lantern", player),
|
||||
magic.can_use_altar(logic).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Tendrils", player),
|
||||
(logic.can_reach_region(Region.farm) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Shockwave", player),
|
||||
(logic.has("Earth Crystal") & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze All Nature School Locations", player),
|
||||
(logic.has("Earth Crystal") & logic.can_reach_region("Farm") &
|
||||
magic.can_use_altar(logic)).simplify()),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Meteor", player),
|
||||
(logic.can_reach_region(Region.farm) & logic.has_lived_months(12)
|
||||
& magic.can_use_altar(logic)).simplify()),
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Lucksteal", player),
|
||||
(logic.can_reach_region(Region.witch_hut) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze: Bloodmana", player),
|
||||
(logic.can_reach_region(Region.mines_floor_100) & magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze All Eldritch School Locations", player),
|
||||
(logic.can_reach_region(Region.witch_hut) &
|
||||
logic.can_reach_region(Region.mines_floor_100) &
|
||||
logic.can_reach_region(Region.farm) & logic.has_lived_months(12) &
|
||||
magic.can_use_altar(logic)).simplify())
|
||||
MultiWorldRules.add_rule(multiworld.get_location("Analyze Every Magic School Location", player),
|
||||
(logic.has_tool("Watering Can", "Basic") & logic.has_tool("Hoe", "Basic")
|
||||
& (logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic")) &
|
||||
logic.has("Coffee") & logic.has("Life Elixir")
|
||||
& logic.can_mine_perfectly() & logic.has("Earth Crystal") &
|
||||
logic.can_reach_region(Region.mines) &
|
||||
logic.has("Fire Quartz") & logic.can_fish(85) &
|
||||
logic.can_reach_region(Region.witch_hut) &
|
||||
logic.can_reach_region(Region.mines_floor_100) &
|
||||
logic.can_reach_region(Region.farm) & logic.has_lived_months(12) &
|
||||
magic.can_use_altar(logic)).simplify())
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from .. import True_
|
||||
from ..logic import Received, Has, False_, And, Or, StardewLogic
|
||||
from ..options import default_options, StardewOptions
|
||||
from ..logic import Received, Has, False_, And, Or
|
||||
|
||||
|
||||
def test_simplify_true_in_and():
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import itertools
|
||||
import unittest
|
||||
from random import random
|
||||
from typing import Dict
|
||||
|
||||
from BaseClasses import ItemClassification, MultiWorld
|
||||
from Options import SpecialRange, OptionSet
|
||||
from Options import SpecialRange
|
||||
from . import setup_solo_multiworld, SVTestBase
|
||||
from .. import StardewItem, options, items_by_group, Group
|
||||
from .. import StardewItem, items_by_group, Group, StardewValleyWorld
|
||||
from ..locations import locations_by_tag, LocationTags, location_table
|
||||
from ..options import StardewOption, stardew_valley_option_classes, Mods
|
||||
from ..strings.goal_names import Goal
|
||||
from ..options import ExcludeGingerIsland, ToolProgression, Goal, SeasonRandomization, TrapItems, SpecialOrderLocations, ArcadeMachineLocations
|
||||
from ..strings.goal_names import Goal as GoalName
|
||||
from ..strings.season_names import Season
|
||||
from ..strings.special_order_names import SpecialOrder
|
||||
from ..strings.tool_names import ToolMaterial, Tool
|
||||
|
@ -51,39 +50,41 @@ def get_option_choices(option) -> Dict[str, int]:
|
|||
|
||||
class TestGenerateDynamicOptions(SVTestBase):
|
||||
def test_given_special_range_when_generate_then_basic_checks(self):
|
||||
for option in stardew_valley_option_classes:
|
||||
if not issubclass(option, SpecialRange):
|
||||
options = self.world.options_dataclass.type_hints
|
||||
for option_name, option in options.items():
|
||||
if not isinstance(option, SpecialRange):
|
||||
continue
|
||||
for value in option.special_range_names:
|
||||
with self.subTest(f"{option.internal_name}: {value}"):
|
||||
choices = {option.internal_name: option.special_range_names[value]}
|
||||
with self.subTest(f"{option_name}: {value}"):
|
||||
choices = {option_name: option.special_range_names[value]}
|
||||
multiworld = setup_solo_multiworld(choices)
|
||||
basic_checks(self, multiworld)
|
||||
|
||||
def test_given_choice_when_generate_then_basic_checks(self):
|
||||
seed = int(random() * pow(10, 18) - 1)
|
||||
for option in stardew_valley_option_classes:
|
||||
options = self.world.options_dataclass.type_hints
|
||||
for option_name, option in options.items():
|
||||
if not option.options:
|
||||
continue
|
||||
for value in option.options:
|
||||
with self.subTest(f"{option.internal_name}: {value} [Seed: {seed}]"):
|
||||
world_options = {option.internal_name: option.options[value]}
|
||||
with self.subTest(f"{option_name}: {value} [Seed: {seed}]"):
|
||||
world_options = {option_name: option.options[value]}
|
||||
multiworld = setup_solo_multiworld(world_options, seed)
|
||||
basic_checks(self, multiworld)
|
||||
|
||||
|
||||
class TestGoal(SVTestBase):
|
||||
def test_given_goal_when_generate_then_victory_is_in_correct_location(self):
|
||||
for goal, location in [("community_center", Goal.community_center),
|
||||
("grandpa_evaluation", Goal.grandpa_evaluation),
|
||||
("bottom_of_the_mines", Goal.bottom_of_the_mines),
|
||||
("cryptic_note", Goal.cryptic_note),
|
||||
("master_angler", Goal.master_angler),
|
||||
("complete_collection", Goal.complete_museum),
|
||||
("full_house", Goal.full_house),
|
||||
("perfection", Goal.perfection)]:
|
||||
for goal, location in [("community_center", GoalName.community_center),
|
||||
("grandpa_evaluation", GoalName.grandpa_evaluation),
|
||||
("bottom_of_the_mines", GoalName.bottom_of_the_mines),
|
||||
("cryptic_note", GoalName.cryptic_note),
|
||||
("master_angler", GoalName.master_angler),
|
||||
("complete_collection", GoalName.complete_museum),
|
||||
("full_house", GoalName.full_house),
|
||||
("perfection", GoalName.perfection)]:
|
||||
with self.subTest(msg=f"Goal: {goal}, Location: {location}"):
|
||||
world_options = {options.Goal.internal_name: options.Goal.options[goal]}
|
||||
world_options = {Goal.internal_name: Goal.options[goal]}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
victory = multi_world.find_item("Victory", 1)
|
||||
self.assertEqual(victory.name, location)
|
||||
|
@ -91,14 +92,14 @@ class TestGoal(SVTestBase):
|
|||
|
||||
class TestSeasonRandomization(SVTestBase):
|
||||
def test_given_disabled_when_generate_then_all_seasons_are_precollected(self):
|
||||
world_options = {options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled}
|
||||
world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_disabled}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
precollected_items = {item.name for item in multi_world.precollected_items[1]}
|
||||
self.assertTrue(all([season in precollected_items for season in SEASONS]))
|
||||
|
||||
def test_given_randomized_when_generate_then_all_seasons_are_in_the_pool_or_precollected(self):
|
||||
world_options = {options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized}
|
||||
world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_randomized}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
precollected_items = {item.name for item in multi_world.precollected_items[1]}
|
||||
items = {item.name for item in multi_world.get_items()} | precollected_items
|
||||
|
@ -106,7 +107,7 @@ class TestSeasonRandomization(SVTestBase):
|
|||
self.assertEqual(len(SEASONS.intersection(precollected_items)), 1)
|
||||
|
||||
def test_given_progressive_when_generate_then_3_progressive_seasons_are_in_the_pool(self):
|
||||
world_options = {options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive}
|
||||
world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_progressive}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
items = [item.name for item in multi_world.get_items()]
|
||||
|
@ -115,7 +116,7 @@ class TestSeasonRandomization(SVTestBase):
|
|||
|
||||
class TestToolProgression(SVTestBase):
|
||||
def test_given_vanilla_when_generate_then_no_tool_in_pool(self):
|
||||
world_options = {options.ToolProgression.internal_name: options.ToolProgression.option_vanilla}
|
||||
world_options = {ToolProgression.internal_name: ToolProgression.option_vanilla}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
items = {item.name for item in multi_world.get_items()}
|
||||
|
@ -123,7 +124,7 @@ class TestToolProgression(SVTestBase):
|
|||
self.assertNotIn(tool, items)
|
||||
|
||||
def test_given_progressive_when_generate_then_progressive_tool_of_each_is_in_pool_four_times(self):
|
||||
world_options = {options.ToolProgression.internal_name: options.ToolProgression.option_progressive}
|
||||
world_options = {ToolProgression.internal_name: ToolProgression.option_progressive}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
items = [item.name for item in multi_world.get_items()]
|
||||
|
@ -131,7 +132,7 @@ class TestToolProgression(SVTestBase):
|
|||
self.assertEqual(items.count("Progressive " + tool), 4)
|
||||
|
||||
def test_given_progressive_when_generate_then_tool_upgrades_are_locations(self):
|
||||
world_options = {options.ToolProgression.internal_name: options.ToolProgression.option_progressive}
|
||||
world_options = {ToolProgression.internal_name: ToolProgression.option_progressive}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
locations = {locations.name for locations in multi_world.get_locations(1)}
|
||||
|
@ -148,50 +149,52 @@ class TestToolProgression(SVTestBase):
|
|||
|
||||
class TestGenerateAllOptionsWithExcludeGingerIsland(SVTestBase):
|
||||
def test_given_special_range_when_generate_exclude_ginger_island(self):
|
||||
for option in stardew_valley_option_classes:
|
||||
if not issubclass(option,
|
||||
SpecialRange) or option.internal_name == options.ExcludeGingerIsland.internal_name:
|
||||
options = self.world.options_dataclass.type_hints
|
||||
for option_name, option in options.items():
|
||||
if not isinstance(option, SpecialRange) or option_name == ExcludeGingerIsland.internal_name:
|
||||
continue
|
||||
for value in option.special_range_names:
|
||||
with self.subTest(f"{option.internal_name}: {value}"):
|
||||
with self.subTest(f"{option_name}: {value}"):
|
||||
multiworld = setup_solo_multiworld(
|
||||
{options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true,
|
||||
option.internal_name: option.special_range_names[value]})
|
||||
{ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true,
|
||||
option_name: option.special_range_names[value]})
|
||||
check_no_ginger_island(self, multiworld)
|
||||
|
||||
def test_given_choice_when_generate_exclude_ginger_island(self):
|
||||
seed = int(random() * pow(10, 18) - 1)
|
||||
island_option = options.ExcludeGingerIsland
|
||||
for option in stardew_valley_option_classes:
|
||||
if not option.options or option.internal_name == island_option.internal_name:
|
||||
options = self.world.options_dataclass.type_hints
|
||||
for option_name, option in options.items():
|
||||
if not option.options or option_name == ExcludeGingerIsland.internal_name:
|
||||
continue
|
||||
for value in option.options:
|
||||
with self.subTest(f"{option.internal_name}: {value} [Seed: {seed}]"):
|
||||
with self.subTest(f"{option_name}: {value} [Seed: {seed}]"):
|
||||
multiworld = setup_solo_multiworld(
|
||||
{island_option.internal_name: island_option.option_true,
|
||||
option.internal_name: option.options[value]}, seed)
|
||||
if multiworld.worlds[self.player].options[island_option.internal_name] != island_option.option_true:
|
||||
{ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true,
|
||||
option_name: option.options[value]}, seed)
|
||||
stardew_world: StardewValleyWorld = multiworld.worlds[self.player]
|
||||
if stardew_world.options.exclude_ginger_island != ExcludeGingerIsland.option_true:
|
||||
continue
|
||||
basic_checks(self, multiworld)
|
||||
check_no_ginger_island(self, multiworld)
|
||||
|
||||
def test_given_island_related_goal_then_override_exclude_ginger_island(self):
|
||||
island_goals = [value for value in options.Goal.options if value in ["walnut_hunter", "perfection"]]
|
||||
island_option = options.ExcludeGingerIsland
|
||||
island_goals = [value for value in Goal.options if value in ["walnut_hunter", "perfection"]]
|
||||
island_option = ExcludeGingerIsland
|
||||
for goal in island_goals:
|
||||
for value in island_option.options:
|
||||
with self.subTest(f"Goal: {goal}, {island_option.internal_name}: {value}"):
|
||||
multiworld = setup_solo_multiworld(
|
||||
{options.Goal.internal_name: options.Goal.options[goal],
|
||||
{Goal.internal_name: Goal.options[goal],
|
||||
island_option.internal_name: island_option.options[value]})
|
||||
self.assertEqual(multiworld.worlds[self.player].options[island_option.internal_name], island_option.option_false)
|
||||
stardew_world: StardewValleyWorld = multiworld.worlds[self.player]
|
||||
self.assertEqual(stardew_world.options.exclude_ginger_island, island_option.option_false)
|
||||
basic_checks(self, multiworld)
|
||||
|
||||
|
||||
class TestTraps(SVTestBase):
|
||||
def test_given_no_traps_when_generate_then_no_trap_in_pool(self):
|
||||
world_options = self.allsanity_options_without_mods()
|
||||
world_options.update({options.TrapItems.internal_name: options.TrapItems.option_no_traps})
|
||||
world_options.update({TrapItems.internal_name: TrapItems.option_no_traps})
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
trap_items = [item_data.name for item_data in items_by_group[Group.TRAP]]
|
||||
|
@ -202,12 +205,12 @@ class TestTraps(SVTestBase):
|
|||
self.assertNotIn(item, multiworld_items)
|
||||
|
||||
def test_given_traps_when_generate_then_all_traps_in_pool(self):
|
||||
trap_option = options.TrapItems
|
||||
trap_option = TrapItems
|
||||
for value in trap_option.options:
|
||||
if value == "no_traps":
|
||||
continue
|
||||
world_options = self.allsanity_options_with_mods()
|
||||
world_options.update({options.TrapItems.internal_name: trap_option.options[value]})
|
||||
world_options.update({TrapItems.internal_name: trap_option.options[value]})
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if Group.DEPRECATED not in item_data.groups and item_data.mod_name is None]
|
||||
multiworld_items = [item.name for item in multi_world.get_items()]
|
||||
|
@ -218,7 +221,7 @@ class TestTraps(SVTestBase):
|
|||
|
||||
class TestSpecialOrders(SVTestBase):
|
||||
def test_given_disabled_then_no_order_in_pool(self):
|
||||
world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_disabled}
|
||||
world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_disabled}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table}
|
||||
|
@ -228,7 +231,7 @@ class TestSpecialOrders(SVTestBase):
|
|||
self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags)
|
||||
|
||||
def test_given_board_only_then_no_qi_order_in_pool(self):
|
||||
world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_only}
|
||||
world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_only}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table}
|
||||
|
@ -242,8 +245,8 @@ class TestSpecialOrders(SVTestBase):
|
|||
self.assertIn(board_location.name, locations_in_pool)
|
||||
|
||||
def test_given_board_and_qi_then_all_orders_in_pool(self):
|
||||
world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi,
|
||||
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_victories}
|
||||
world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
|
||||
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_victories}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
locations_in_pool = {location.name for location in multi_world.get_locations()}
|
||||
|
@ -258,8 +261,8 @@ class TestSpecialOrders(SVTestBase):
|
|||
self.assertIn(board_location.name, locations_in_pool)
|
||||
|
||||
def test_given_board_and_qi_without_arcade_machines_then_lets_play_a_game_not_in_pool(self):
|
||||
world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi,
|
||||
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled}
|
||||
world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
|
||||
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled}
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
|
||||
locations_in_pool = {location.name for location in multi_world.get_locations()}
|
||||
|
|
|
@ -3,7 +3,8 @@ import sys
|
|||
import unittest
|
||||
|
||||
from . import SVTestBase, setup_solo_multiworld
|
||||
from .. import StardewOptions, options, StardewValleyWorld
|
||||
from .. import options, StardewValleyWorld, StardewValleyOptions
|
||||
from ..options import EntranceRandomization, ExcludeGingerIsland
|
||||
from ..regions import vanilla_regions, vanilla_connections, randomize_connections, RandomizationFlag
|
||||
|
||||
connections_by_name = {connection.name for connection in vanilla_connections}
|
||||
|
@ -37,11 +38,12 @@ class TestEntranceRando(unittest.TestCase):
|
|||
seed = random.randrange(sys.maxsize)
|
||||
with self.subTest(flag=flag, msg=f"Seed: {seed}"):
|
||||
rand = random.Random(seed)
|
||||
world_options = StardewOptions({options.EntranceRandomization.internal_name: option,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false})
|
||||
world_options = {EntranceRandomization.internal_name: option,
|
||||
ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false}
|
||||
multiworld = setup_solo_multiworld(world_options)
|
||||
regions_by_name = {region.name: region for region in vanilla_regions}
|
||||
|
||||
_, randomized_connections = randomize_connections(rand, world_options, regions_by_name)
|
||||
_, randomized_connections = randomize_connections(rand, multiworld.worlds[1].options, regions_by_name)
|
||||
|
||||
for connection in vanilla_connections:
|
||||
if flag in connection.flag:
|
||||
|
@ -62,11 +64,12 @@ class TestEntranceRando(unittest.TestCase):
|
|||
with self.subTest(option=option, flag=flag):
|
||||
seed = random.randrange(sys.maxsize)
|
||||
rand = random.Random(seed)
|
||||
world_options = StardewOptions({options.EntranceRandomization.internal_name: option,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true})
|
||||
world_options = {EntranceRandomization.internal_name: option,
|
||||
ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true}
|
||||
multiworld = setup_solo_multiworld(world_options)
|
||||
regions_by_name = {region.name: region for region in vanilla_regions}
|
||||
|
||||
_, randomized_connections = randomize_connections(rand, world_options, regions_by_name)
|
||||
_, randomized_connections = randomize_connections(rand, multiworld.worlds[1].options, regions_by_name)
|
||||
|
||||
for connection in vanilla_connections:
|
||||
if flag in connection.flag:
|
||||
|
|
|
@ -5,9 +5,12 @@ from typing import Dict, FrozenSet, Tuple, Any, ClassVar
|
|||
from BaseClasses import MultiWorld
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
||||
from .. import StardewValleyWorld, options
|
||||
from .. import StardewValleyWorld
|
||||
from ..mods.mod_data import ModNames
|
||||
from worlds.AutoWorld import call_all
|
||||
from ..options import Cropsanity, SkillProgression, SpecialOrderLocations, Friendsanity, NumberOfLuckBuffs, SeasonRandomization, ToolProgression, \
|
||||
ElevatorProgression, Museumsanity, BackpackProgression, BuildingProgression, ArcadeMachineLocations, HelpWantedLocations, Fishsanity, NumberOfMovementBuffs, \
|
||||
BundleRandomization, BundlePrice, FestivalLocations, FriendsanityHeartSize, ExcludeGingerIsland, TrapItems, Goal, Mods
|
||||
|
||||
|
||||
class SVTestBase(WorldTestBase):
|
||||
|
@ -33,48 +36,48 @@ class SVTestBase(WorldTestBase):
|
|||
|
||||
def minimal_locations_maximal_items(self):
|
||||
min_max_options = {
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_shuffled,
|
||||
options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla,
|
||||
options.ToolProgression.internal_name: options.ToolProgression.option_vanilla,
|
||||
options.SkillProgression.internal_name: options.SkillProgression.option_vanilla,
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla,
|
||||
options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla,
|
||||
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_disabled,
|
||||
options.HelpWantedLocations.internal_name: 0,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_none,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_none,
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_none,
|
||||
options.NumberOfMovementBuffs.internal_name: 12,
|
||||
options.NumberOfLuckBuffs.internal_name: 12,
|
||||
SeasonRandomization.internal_name: SeasonRandomization.option_randomized,
|
||||
Cropsanity.internal_name: Cropsanity.option_shuffled,
|
||||
BackpackProgression.internal_name: BackpackProgression.option_vanilla,
|
||||
ToolProgression.internal_name: ToolProgression.option_vanilla,
|
||||
SkillProgression.internal_name: SkillProgression.option_vanilla,
|
||||
BuildingProgression.internal_name: BuildingProgression.option_vanilla,
|
||||
ElevatorProgression.internal_name: ElevatorProgression.option_vanilla,
|
||||
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled,
|
||||
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_disabled,
|
||||
HelpWantedLocations.internal_name: 0,
|
||||
Fishsanity.internal_name: Fishsanity.option_none,
|
||||
Museumsanity.internal_name: Museumsanity.option_none,
|
||||
Friendsanity.internal_name: Friendsanity.option_none,
|
||||
NumberOfMovementBuffs.internal_name: 12,
|
||||
NumberOfLuckBuffs.internal_name: 12,
|
||||
}
|
||||
return min_max_options
|
||||
|
||||
def allsanity_options_without_mods(self):
|
||||
allsanity = {
|
||||
options.Goal.internal_name: options.Goal.option_perfection,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_expensive,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_shuffled,
|
||||
options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive,
|
||||
options.ToolProgression.internal_name: options.ToolProgression.option_progressive,
|
||||
options.SkillProgression.internal_name: options.SkillProgression.option_progressive,
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive,
|
||||
options.FestivalLocations.internal_name: options.FestivalLocations.option_hard,
|
||||
options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive,
|
||||
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi,
|
||||
options.HelpWantedLocations.internal_name: 56,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_all,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_all,
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage,
|
||||
options.FriendsanityHeartSize.internal_name: 1,
|
||||
options.NumberOfMovementBuffs.internal_name: 12,
|
||||
options.NumberOfLuckBuffs.internal_name: 12,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false,
|
||||
options.TrapItems.internal_name: options.TrapItems.option_nightmare,
|
||||
Goal.internal_name: Goal.option_perfection,
|
||||
BundleRandomization.internal_name: BundleRandomization.option_shuffled,
|
||||
BundlePrice.internal_name: BundlePrice.option_expensive,
|
||||
SeasonRandomization.internal_name: SeasonRandomization.option_randomized,
|
||||
Cropsanity.internal_name: Cropsanity.option_shuffled,
|
||||
BackpackProgression.internal_name: BackpackProgression.option_progressive,
|
||||
ToolProgression.internal_name: ToolProgression.option_progressive,
|
||||
SkillProgression.internal_name: SkillProgression.option_progressive,
|
||||
BuildingProgression.internal_name: BuildingProgression.option_progressive,
|
||||
FestivalLocations.internal_name: FestivalLocations.option_hard,
|
||||
ElevatorProgression.internal_name: ElevatorProgression.option_progressive,
|
||||
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_full_shuffling,
|
||||
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
|
||||
HelpWantedLocations.internal_name: 56,
|
||||
Fishsanity.internal_name: Fishsanity.option_all,
|
||||
Museumsanity.internal_name: Museumsanity.option_all,
|
||||
Friendsanity.internal_name: Friendsanity.option_all_with_marriage,
|
||||
FriendsanityHeartSize.internal_name: 1,
|
||||
NumberOfMovementBuffs.internal_name: 12,
|
||||
NumberOfLuckBuffs.internal_name: 12,
|
||||
ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false,
|
||||
TrapItems.internal_name: TrapItems.option_nightmare,
|
||||
}
|
||||
return allsanity
|
||||
|
||||
|
@ -89,7 +92,7 @@ class SVTestBase(WorldTestBase):
|
|||
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
|
||||
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator
|
||||
)
|
||||
allsanity.update({options.Mods.internal_name: all_mods})
|
||||
allsanity.update({Mods.internal_name: all_mods})
|
||||
return allsanity
|
||||
|
||||
pre_generated_worlds = {}
|
||||
|
@ -110,7 +113,7 @@ def setup_solo_multiworld(test_options=None, seed=None,
|
|||
multiworld.set_seed(seed)
|
||||
# print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test
|
||||
args = Namespace()
|
||||
for name, option in StardewValleyWorld.option_definitions.items():
|
||||
for name, option in StardewValleyWorld.options_dataclass.type_hints.items():
|
||||
value = option(test_options[name]) if name in test_options else option.from_any(option.default)
|
||||
setattr(args, name, {1: value})
|
||||
multiworld.set_options(args)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from BaseClasses import MultiWorld
|
||||
from .option_checks import is_setting, assert_is_setting
|
||||
from .option_checks import get_stardew_options
|
||||
from ... import options
|
||||
from .. import SVTestBase
|
||||
|
||||
|
||||
def is_goal(multiworld: MultiWorld, goal: int) -> bool:
|
||||
return is_setting(multiworld, options.Goal.internal_name, goal)
|
||||
return get_stardew_options(multiworld).goal.value == goal
|
||||
|
||||
|
||||
def is_bottom_mines(multiworld: MultiWorld) -> bool:
|
||||
|
@ -33,7 +33,7 @@ def is_not_perfection(multiworld: MultiWorld) -> bool:
|
|||
|
||||
|
||||
def assert_ginger_island_is_included(tester: SVTestBase, multiworld: MultiWorld):
|
||||
assert_is_setting(tester, multiworld, options.ExcludeGingerIsland.internal_name, options.ExcludeGingerIsland.option_false)
|
||||
tester.assertEqual(get_stardew_options(multiworld).exclude_ginger_island, options.ExcludeGingerIsland.option_false)
|
||||
|
||||
|
||||
def assert_walnut_hunter_world_is_valid(tester: SVTestBase, multiworld: MultiWorld):
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Union
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from .world_checks import get_all_item_names, get_all_location_names
|
||||
from .. import SVTestBase
|
||||
|
@ -8,32 +6,16 @@ from ...locations import LocationTags
|
|||
from ...strings.ap_names.transport_names import Transportation
|
||||
|
||||
|
||||
def get_stardew_world(multiworld: MultiWorld) -> Union[StardewValleyWorld, None]:
|
||||
def get_stardew_world(multiworld: MultiWorld) -> StardewValleyWorld:
|
||||
for world_key in multiworld.worlds:
|
||||
world = multiworld.worlds[world_key]
|
||||
if isinstance(world, StardewValleyWorld):
|
||||
return world
|
||||
return None
|
||||
raise ValueError("no stardew world in this multiworld")
|
||||
|
||||
|
||||
def is_setting(multiworld: MultiWorld, setting_name: str, setting_value: int) -> bool:
|
||||
stardew_world = get_stardew_world(multiworld)
|
||||
if not stardew_world:
|
||||
return False
|
||||
current_value = stardew_world.options[setting_name]
|
||||
return current_value == setting_value
|
||||
|
||||
|
||||
def is_not_setting(multiworld: MultiWorld, setting_name: str, setting_value: int) -> bool:
|
||||
return not is_setting(multiworld, setting_name, setting_value)
|
||||
|
||||
|
||||
def assert_is_setting(tester: SVTestBase, multiworld: MultiWorld, setting_name: str, setting_value: int) -> bool:
|
||||
stardew_world = get_stardew_world(multiworld)
|
||||
if not stardew_world:
|
||||
return False
|
||||
current_value = stardew_world.options[setting_name]
|
||||
tester.assertEqual(current_value, setting_value)
|
||||
def get_stardew_options(multiworld: MultiWorld) -> options.StardewValleyOptions:
|
||||
return get_stardew_world(multiworld).options
|
||||
|
||||
|
||||
def assert_can_reach_island(tester: SVTestBase, multiworld: MultiWorld):
|
||||
|
@ -49,7 +31,8 @@ def assert_cannot_reach_island(tester: SVTestBase, multiworld: MultiWorld):
|
|||
|
||||
|
||||
def assert_can_reach_island_if_should(tester: SVTestBase, multiworld: MultiWorld):
|
||||
include_island = is_setting(multiworld, options.ExcludeGingerIsland.internal_name, options.ExcludeGingerIsland.option_false)
|
||||
stardew_options = get_stardew_options(multiworld)
|
||||
include_island = stardew_options.exclude_ginger_island.value == options.ExcludeGingerIsland.option_false
|
||||
if include_island:
|
||||
assert_can_reach_island(tester, multiworld)
|
||||
else:
|
||||
|
@ -57,7 +40,7 @@ def assert_can_reach_island_if_should(tester: SVTestBase, multiworld: MultiWorld
|
|||
|
||||
|
||||
def assert_cropsanity_same_number_items_and_locations(tester: SVTestBase, multiworld: MultiWorld):
|
||||
is_cropsanity = is_setting(multiworld, options.Cropsanity.internal_name, options.Cropsanity.option_shuffled)
|
||||
is_cropsanity = get_stardew_options(multiworld).cropsanity.value == options.Cropsanity.option_shuffled
|
||||
if not is_cropsanity:
|
||||
return
|
||||
|
||||
|
@ -80,11 +63,10 @@ def assert_has_deluxe_scarecrow_recipe(tester: SVTestBase, multiworld: MultiWorl
|
|||
|
||||
|
||||
def assert_festivals_give_access_to_deluxe_scarecrow(tester: SVTestBase, multiworld: MultiWorld):
|
||||
has_festivals = is_not_setting(multiworld, options.FestivalLocations.internal_name, options.FestivalLocations.option_disabled)
|
||||
stardew_options = get_stardew_options(multiworld)
|
||||
has_festivals = stardew_options.festival_locations.value != options.FestivalLocations.option_disabled
|
||||
if not has_festivals:
|
||||
return
|
||||
|
||||
assert_all_rarecrows_exist(tester, multiworld)
|
||||
assert_has_deluxe_scarecrow_recipe(tester, multiworld)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
from typing import List, Union
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.stardew_valley.mods.mod_data import ModNames
|
||||
from worlds.stardew_valley.test import setup_solo_multiworld
|
||||
from worlds.stardew_valley.test.TestOptions import basic_checks, SVTestBase
|
||||
from worlds.stardew_valley.items import item_table
|
||||
from worlds.stardew_valley.locations import location_table
|
||||
from worlds.stardew_valley.options import Mods
|
||||
from .option_names import options_to_include
|
||||
|
||||
all_mods = frozenset({ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack,
|
||||
ModNames.luck_skill, ModNames.magic, ModNames.socializing_skill, ModNames.archaeology,
|
||||
ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna,
|
||||
ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene,
|
||||
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
|
||||
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator})
|
||||
|
||||
|
||||
def check_stray_mod_items(chosen_mods: Union[List[str], str], tester: SVTestBase, multiworld: MultiWorld):
|
||||
if isinstance(chosen_mods, str):
|
||||
chosen_mods = [chosen_mods]
|
||||
for multiworld_item in multiworld.get_items():
|
||||
item = item_table[multiworld_item.name]
|
||||
tester.assertTrue(item.mod_name is None or item.mod_name in chosen_mods)
|
||||
for multiworld_location in multiworld.get_locations():
|
||||
if multiworld_location.event:
|
||||
continue
|
||||
location = location_table[multiworld_location.name]
|
||||
tester.assertTrue(location.mod_name is None or location.mod_name in chosen_mods)
|
||||
|
||||
|
||||
class TestGenerateModsOptions(SVTestBase):
|
||||
|
||||
def test_given_mod_pairs_when_generate_then_basic_checks(self):
|
||||
if self.skip_long_tests:
|
||||
return
|
||||
mods = list(all_mods)
|
||||
num_mods = len(mods)
|
||||
for mod1_index in range(0, num_mods):
|
||||
for mod2_index in range(mod1_index + 1, num_mods):
|
||||
mod1 = mods[mod1_index]
|
||||
mod2 = mods[mod2_index]
|
||||
mod_pair = (mod1, mod2)
|
||||
with self.subTest(f"Mods: {mod_pair}"):
|
||||
multiworld = setup_solo_multiworld({Mods: mod_pair})
|
||||
basic_checks(self, multiworld)
|
||||
check_stray_mod_items(list(mod_pair), self, multiworld)
|
||||
|
||||
def test_given_mod_names_when_generate_paired_with_other_options_then_basic_checks(self):
|
||||
if self.skip_long_tests:
|
||||
return
|
||||
num_options = len(options_to_include)
|
||||
for option_index in range(0, num_options):
|
||||
option = options_to_include[option_index]
|
||||
if not option.options:
|
||||
continue
|
||||
for value in option.options:
|
||||
for mod in all_mods:
|
||||
with self.subTest(f"{option.internal_name}: {value}, Mod: {mod}"):
|
||||
multiworld = setup_solo_multiworld({option.internal_name: option.options[value], Mods: mod})
|
||||
basic_checks(self, multiworld)
|
||||
check_stray_mod_items(mod, self, multiworld)
|
|
@ -24,7 +24,6 @@ class TestGenerateDynamicOptions(SVTestBase):
|
|||
def test_given_option_pair_when_generate_then_basic_checks(self):
|
||||
if self.skip_long_tests:
|
||||
return
|
||||
|
||||
num_options = len(options_to_include)
|
||||
for option1_index in range(0, num_options):
|
||||
for option2_index in range(option1_index + 1, num_options):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List
|
||||
from typing import Dict
|
||||
import random
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
|
@ -9,7 +9,6 @@ from ..checks.goal_checks import assert_perfection_world_is_valid, assert_goal_w
|
|||
from ..checks.option_checks import assert_can_reach_island_if_should, assert_cropsanity_same_number_items_and_locations, \
|
||||
assert_festivals_give_access_to_deluxe_scarecrow
|
||||
from ..checks.world_checks import assert_same_number_items_locations, assert_victory_exists
|
||||
from ... import options
|
||||
|
||||
|
||||
def get_option_choices(option) -> Dict[str, int]:
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from worlds.stardew_valley.options import stardew_valley_option_classes
|
||||
from ... import StardewValleyWorld
|
||||
|
||||
options_to_exclude = ["profit_margin", "starting_money", "multiple_day_sleep_enabled", "multiple_day_sleep_cost",
|
||||
"experience_multiplier", "friendship_multiplier", "debris_multiplier",
|
||||
"quick_start", "gifting", "gift_tax"]
|
||||
options_to_include = [option_to_include for option_to_include in stardew_valley_option_classes
|
||||
if option_to_include.internal_name not in options_to_exclude]
|
||||
"quick_start", "gifting", "gift_tax", "progression_balancing", "accessibility", "start_inventory", "start_hints", "death_link"]
|
||||
|
||||
options_to_include = [option for option_name, option in StardewValleyWorld.options_dataclass.type_hints.items()
|
||||
if option_name not in options_to_exclude]
|
||||
|
|
|
@ -4,21 +4,21 @@ import random
|
|||
import sys
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.stardew_valley.test import setup_solo_multiworld
|
||||
from worlds.stardew_valley.test.TestOptions import basic_checks, SVTestBase
|
||||
from worlds.stardew_valley import options, locations, items, Group, ItemClassification, StardewOptions
|
||||
from worlds.stardew_valley.mods.mod_data import ModNames
|
||||
from worlds.stardew_valley.regions import RandomizationFlag, create_final_connections, randomize_connections, create_final_regions
|
||||
from worlds.stardew_valley.items import item_table, items_by_group
|
||||
from worlds.stardew_valley.locations import location_table, LocationTags
|
||||
from worlds.stardew_valley.options import stardew_valley_option_classes, Mods, EntranceRandomization
|
||||
from ...mods.mod_data import ModNames
|
||||
from .. import setup_solo_multiworld
|
||||
from ..TestOptions import basic_checks, SVTestBase
|
||||
from ... import items, Group, ItemClassification
|
||||
from ...regions import RandomizationFlag, create_final_connections, randomize_connections, create_final_regions
|
||||
from ...items import item_table, items_by_group
|
||||
from ...locations import location_table
|
||||
from ...options import Mods, EntranceRandomization, Friendsanity, SeasonRandomization, SpecialOrderLocations, ExcludeGingerIsland, TrapItems
|
||||
|
||||
mod_list = ["DeepWoods", "Tractor Mod", "Bigger Backpack",
|
||||
"Luck Skill", "Magic", "Socializing Skill", "Archaeology",
|
||||
"Cooking Skill", "Binning Skill", "Juna - Roommate NPC",
|
||||
"Professor Jasper Thomas", "Alec Revisited", "Custom NPC - Yoba", "Custom NPC Eugene",
|
||||
"'Prophet' Wellwick", "Mister Ginger (cat npc)", "Shiko - New Custom NPC", "Delores - Custom NPC",
|
||||
"Ayeisha - The Postal Worker (Custom NPC)", "Custom NPC - Riley", "Skull Cavern Elevator"]
|
||||
all_mods = frozenset({ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack,
|
||||
ModNames.luck_skill, ModNames.magic, ModNames.socializing_skill, ModNames.archaeology,
|
||||
ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna,
|
||||
ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene,
|
||||
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
|
||||
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator})
|
||||
|
||||
|
||||
def check_stray_mod_items(chosen_mods: Union[List[str], str], tester: SVTestBase, multiworld: MultiWorld):
|
||||
|
@ -37,54 +37,27 @@ def check_stray_mod_items(chosen_mods: Union[List[str], str], tester: SVTestBase
|
|||
class TestGenerateModsOptions(SVTestBase):
|
||||
|
||||
def test_given_single_mods_when_generate_then_basic_checks(self):
|
||||
for mod in mod_list:
|
||||
for mod in all_mods:
|
||||
with self.subTest(f"Mod: {mod}"):
|
||||
multi_world = setup_solo_multiworld({Mods: mod})
|
||||
basic_checks(self, multi_world)
|
||||
check_stray_mod_items(mod, self, multi_world)
|
||||
|
||||
def test_given_mod_pairs_when_generate_then_basic_checks(self):
|
||||
if self.skip_long_tests:
|
||||
return
|
||||
num_mods = len(mod_list)
|
||||
for mod1_index in range(0, num_mods):
|
||||
for mod2_index in range(mod1_index + 1, num_mods):
|
||||
mod1 = mod_list[mod1_index]
|
||||
mod2 = mod_list[mod2_index]
|
||||
mods = (mod1, mod2)
|
||||
with self.subTest(f"Mods: {mods}"):
|
||||
multiworld = setup_solo_multiworld({Mods: mods})
|
||||
basic_checks(self, multiworld)
|
||||
check_stray_mod_items(list(mods), self, multiworld)
|
||||
|
||||
def test_given_mod_names_when_generate_paired_with_entrance_randomizer_then_basic_checks(self):
|
||||
for option in EntranceRandomization.options:
|
||||
for mod in mod_list:
|
||||
for mod in all_mods:
|
||||
with self.subTest(f"entrance_randomization: {option}, Mod: {mod}"):
|
||||
multiworld = setup_solo_multiworld({EntranceRandomization.internal_name: option, Mods: mod})
|
||||
basic_checks(self, multiworld)
|
||||
check_stray_mod_items(mod, self, multiworld)
|
||||
|
||||
def test_given_mod_names_when_generate_paired_with_other_options_then_basic_checks(self):
|
||||
if self.skip_long_tests:
|
||||
return
|
||||
for option in stardew_valley_option_classes:
|
||||
if not option.options:
|
||||
continue
|
||||
for value in option.options:
|
||||
for mod in mod_list:
|
||||
with self.subTest(f"{option.internal_name}: {value}, Mod: {mod}"):
|
||||
multiworld = setup_solo_multiworld({option.internal_name: option.options[value], Mods: mod})
|
||||
basic_checks(self, multiworld)
|
||||
check_stray_mod_items(mod, self, multiworld)
|
||||
|
||||
|
||||
class TestBaseItemGeneration(SVTestBase):
|
||||
options = {
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi,
|
||||
options.Mods.internal_name: mod_list
|
||||
Friendsanity.internal_name: Friendsanity.option_all_with_marriage,
|
||||
SeasonRandomization.internal_name: SeasonRandomization.option_progressive,
|
||||
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
|
||||
Mods.internal_name: all_mods
|
||||
}
|
||||
|
||||
def test_all_progression_items_are_added_to_the_pool(self):
|
||||
|
@ -105,10 +78,10 @@ class TestBaseItemGeneration(SVTestBase):
|
|||
|
||||
class TestNoGingerIslandModItemGeneration(SVTestBase):
|
||||
options = {
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true,
|
||||
options.Mods.internal_name: mod_list
|
||||
Friendsanity.internal_name: Friendsanity.option_all_with_marriage,
|
||||
SeasonRandomization.internal_name: SeasonRandomization.option_progressive,
|
||||
ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true,
|
||||
Mods.internal_name: all_mods
|
||||
}
|
||||
|
||||
def test_all_progression_items_except_island_are_added_to_the_pool(self):
|
||||
|
@ -134,29 +107,31 @@ class TestModEntranceRando(unittest.TestCase):
|
|||
|
||||
def test_mod_entrance_randomization(self):
|
||||
|
||||
for option, flag in [(options.EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN),
|
||||
(options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION),
|
||||
(options.EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]:
|
||||
for option, flag in [(EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN),
|
||||
(EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION),
|
||||
(EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]:
|
||||
with self.subTest(option=option, flag=flag):
|
||||
seed = random.randrange(sys.maxsize)
|
||||
rand = random.Random(seed)
|
||||
world_options = StardewOptions({options.EntranceRandomization.internal_name: option,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false,
|
||||
options.Mods.internal_name: mod_list})
|
||||
final_regions = create_final_regions(world_options)
|
||||
final_connections = create_final_connections(world_options)
|
||||
world_options = {EntranceRandomization.internal_name: option,
|
||||
ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false,
|
||||
Mods.internal_name: all_mods}
|
||||
multiworld = setup_solo_multiworld(world_options)
|
||||
world = multiworld.worlds[1]
|
||||
final_regions = create_final_regions(world.options)
|
||||
final_connections = create_final_connections(world.options)
|
||||
|
||||
regions_by_name = {region.name: region for region in final_regions}
|
||||
_, randomized_connections = randomize_connections(rand, world_options, regions_by_name)
|
||||
_, randomized_connections = randomize_connections(rand, world.options, regions_by_name)
|
||||
|
||||
for connection in final_connections:
|
||||
if flag in connection.flag:
|
||||
connection_in_randomized = connection.name in randomized_connections
|
||||
reverse_in_randomized = connection.reverse in randomized_connections
|
||||
self.assertTrue(connection_in_randomized,
|
||||
f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}")
|
||||
f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}")
|
||||
self.assertTrue(reverse_in_randomized,
|
||||
f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}")
|
||||
f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}")
|
||||
|
||||
self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()),
|
||||
f"Connections are duplicated in randomization. Seed = {seed}")
|
||||
|
@ -164,12 +139,11 @@ class TestModEntranceRando(unittest.TestCase):
|
|||
|
||||
class TestModTraps(SVTestBase):
|
||||
def test_given_traps_when_generate_then_all_traps_in_pool(self):
|
||||
trap_option = options.TrapItems
|
||||
for value in trap_option.options:
|
||||
for value in TrapItems.options:
|
||||
if value == "no_traps":
|
||||
continue
|
||||
world_options = self.allsanity_options_without_mods()
|
||||
world_options.update({options.TrapItems.internal_name: trap_option.options[value], Mods: "Magic"})
|
||||
world_options.update({TrapItems.internal_name: TrapItems.options[value], Mods: "Magic"})
|
||||
multi_world = setup_solo_multiworld(world_options)
|
||||
trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if Group.DEPRECATED not in item_data.groups]
|
||||
multiworld_items = [item.name for item in multi_world.get_items()]
|
||||
|
|
|
@ -12,7 +12,7 @@ from BaseClasses import ItemClassification, LocationProgressType, \
|
|||
MultiWorld, Item, CollectionState, Entrance, Tutorial
|
||||
from .logic import cs_to_zz_locs
|
||||
from .region import ZillionLocation, ZillionRegion
|
||||
from .options import ZillionStartChar, zillion_options, validate
|
||||
from .options import ZillionOptions, ZillionStartChar, validate
|
||||
from .id_maps import item_name_to_id as _item_name_to_id, \
|
||||
loc_name_to_id as _loc_name_to_id, make_id_to_others, \
|
||||
zz_reg_name_to_reg_name, base_id
|
||||
|
@ -70,7 +70,9 @@ class ZillionWorld(World):
|
|||
game = "Zillion"
|
||||
web = ZillionWebWorld()
|
||||
|
||||
option_definitions = zillion_options
|
||||
options_dataclass = ZillionOptions
|
||||
options: ZillionOptions
|
||||
|
||||
settings: typing.ClassVar[ZillionSettings]
|
||||
topology_present = True # indicate if world type has any meaningful layout/pathing
|
||||
|
||||
|
@ -142,7 +144,10 @@ class ZillionWorld(World):
|
|||
if not hasattr(self.multiworld, "zillion_logic_cache"):
|
||||
setattr(self.multiworld, "zillion_logic_cache", {})
|
||||
|
||||
zz_op, item_counts = validate(self.multiworld, self.player)
|
||||
zz_op, item_counts = validate(self.options)
|
||||
|
||||
if zz_op.early_scope:
|
||||
self.multiworld.early_items[self.player]["Scope"] = 1
|
||||
|
||||
self._item_counts = item_counts
|
||||
|
||||
|
@ -299,7 +304,8 @@ class ZillionWorld(World):
|
|||
elif start_char_counts["Champ"] > start_char_counts["Apple"]:
|
||||
to_stay = "Champ"
|
||||
else: # equal
|
||||
to_stay = multiworld.random.choice(("Apple", "Champ"))
|
||||
choices: Tuple[Literal['Apple', 'Champ', 'JJ'], ...] = ("Apple", "Champ")
|
||||
to_stay = multiworld.random.choice(choices)
|
||||
|
||||
for p, sc in players_start_chars:
|
||||
if sc != to_stay:
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from collections import Counter
|
||||
# import logging
|
||||
from typing import TYPE_CHECKING, Any, Dict, Tuple, cast
|
||||
from Options import AssembleOptions, DefaultOnToggle, Range, SpecialRange, Toggle, Choice
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple
|
||||
from typing_extensions import TypeGuard # remove when Python >= 3.10
|
||||
|
||||
from Options import DefaultOnToggle, PerGameCommonOptions, Range, SpecialRange, Toggle, Choice
|
||||
|
||||
from zilliandomizer.options import \
|
||||
Options as ZzOptions, char_to_gun, char_to_jump, ID, \
|
||||
VBLR as ZzVBLR, chars, Chars, ItemCounts as ZzItemCounts
|
||||
from zilliandomizer.options.parsing import validate as zz_validate
|
||||
if TYPE_CHECKING:
|
||||
from BaseClasses import MultiWorld
|
||||
|
||||
|
||||
class ZillionContinues(SpecialRange):
|
||||
|
@ -41,6 +42,19 @@ class VBLR(Choice):
|
|||
option_restrictive = 3
|
||||
default = 1
|
||||
|
||||
def to_zz_vblr(self) -> ZzVBLR:
|
||||
def is_vblr(o: str) -> TypeGuard[ZzVBLR]:
|
||||
"""
|
||||
This function is because mypy doesn't support narrowing with `in`,
|
||||
https://github.com/python/mypy/issues/12535
|
||||
so this is the only way I see to get type narrowing to `Literal`.
|
||||
"""
|
||||
return o in ("vanilla", "balanced", "low", "restrictive")
|
||||
|
||||
key = self.current_key
|
||||
assert is_vblr(key), f"{key=}"
|
||||
return key
|
||||
|
||||
|
||||
class ZillionGunLevels(VBLR):
|
||||
"""
|
||||
|
@ -225,27 +239,27 @@ class ZillionRoomGen(Toggle):
|
|||
display_name = "room generation"
|
||||
|
||||
|
||||
zillion_options: Dict[str, AssembleOptions] = {
|
||||
"continues": ZillionContinues,
|
||||
"floppy_req": ZillionFloppyReq,
|
||||
"gun_levels": ZillionGunLevels,
|
||||
"jump_levels": ZillionJumpLevels,
|
||||
"randomize_alarms": ZillionRandomizeAlarms,
|
||||
"max_level": ZillionMaxLevel,
|
||||
"start_char": ZillionStartChar,
|
||||
"opas_per_level": ZillionOpasPerLevel,
|
||||
"id_card_count": ZillionIDCardCount,
|
||||
"bread_count": ZillionBreadCount,
|
||||
"opa_opa_count": ZillionOpaOpaCount,
|
||||
"zillion_count": ZillionZillionCount,
|
||||
"floppy_disk_count": ZillionFloppyDiskCount,
|
||||
"scope_count": ZillionScopeCount,
|
||||
"red_id_card_count": ZillionRedIDCardCount,
|
||||
"early_scope": ZillionEarlyScope,
|
||||
"skill": ZillionSkill,
|
||||
"starting_cards": ZillionStartingCards,
|
||||
"room_gen": ZillionRoomGen,
|
||||
}
|
||||
@dataclass
|
||||
class ZillionOptions(PerGameCommonOptions):
|
||||
continues: ZillionContinues
|
||||
floppy_req: ZillionFloppyReq
|
||||
gun_levels: ZillionGunLevels
|
||||
jump_levels: ZillionJumpLevels
|
||||
randomize_alarms: ZillionRandomizeAlarms
|
||||
max_level: ZillionMaxLevel
|
||||
start_char: ZillionStartChar
|
||||
opas_per_level: ZillionOpasPerLevel
|
||||
id_card_count: ZillionIDCardCount
|
||||
bread_count: ZillionBreadCount
|
||||
opa_opa_count: ZillionOpaOpaCount
|
||||
zillion_count: ZillionZillionCount
|
||||
floppy_disk_count: ZillionFloppyDiskCount
|
||||
scope_count: ZillionScopeCount
|
||||
red_id_card_count: ZillionRedIDCardCount
|
||||
early_scope: ZillionEarlyScope
|
||||
skill: ZillionSkill
|
||||
starting_cards: ZillionStartingCards
|
||||
room_gen: ZillionRoomGen
|
||||
|
||||
|
||||
def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
|
||||
|
@ -262,47 +276,34 @@ def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
|
|||
return tr
|
||||
|
||||
|
||||
def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
|
||||
def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]":
|
||||
"""
|
||||
adjusts options to make game completion possible
|
||||
|
||||
`world` parameter is MultiWorld object that has my options on it
|
||||
`p` is my player id
|
||||
`options` parameter is ZillionOptions object that was put on my world by the core
|
||||
"""
|
||||
for option_name in zillion_options:
|
||||
assert hasattr(world, option_name), f"Zillion option {option_name} didn't get put in world object"
|
||||
wo = cast(Any, world) # so I don't need getattr on all the options
|
||||
|
||||
skill = wo.skill[p].value
|
||||
skill = options.skill.value
|
||||
|
||||
jump_levels = cast(ZillionJumpLevels, wo.jump_levels[p])
|
||||
jump_option = jump_levels.current_key
|
||||
required_level = char_to_jump["Apple"][cast(ZzVBLR, jump_option)].index(3) + 1
|
||||
jump_option = options.jump_levels.to_zz_vblr()
|
||||
required_level = char_to_jump["Apple"][jump_option].index(3) + 1
|
||||
if skill == 0:
|
||||
# because of hp logic on final boss
|
||||
required_level = 8
|
||||
|
||||
gun_levels = cast(ZillionGunLevels, wo.gun_levels[p])
|
||||
gun_option = gun_levels.current_key
|
||||
guns_required = char_to_gun["Champ"][cast(ZzVBLR, gun_option)].index(3)
|
||||
gun_option = options.gun_levels.to_zz_vblr()
|
||||
guns_required = char_to_gun["Champ"][gun_option].index(3)
|
||||
|
||||
floppy_req = cast(ZillionFloppyReq, wo.floppy_req[p])
|
||||
floppy_req = options.floppy_req
|
||||
|
||||
card = cast(ZillionIDCardCount, wo.id_card_count[p])
|
||||
bread = cast(ZillionBreadCount, wo.bread_count[p])
|
||||
opa = cast(ZillionOpaOpaCount, wo.opa_opa_count[p])
|
||||
gun = cast(ZillionZillionCount, wo.zillion_count[p])
|
||||
floppy = cast(ZillionFloppyDiskCount, wo.floppy_disk_count[p])
|
||||
scope = cast(ZillionScopeCount, wo.scope_count[p])
|
||||
red = cast(ZillionRedIDCardCount, wo.red_id_card_count[p])
|
||||
item_counts = Counter({
|
||||
"ID Card": card,
|
||||
"Bread": bread,
|
||||
"Opa-Opa": opa,
|
||||
"Zillion": gun,
|
||||
"Floppy Disk": floppy,
|
||||
"Scope": scope,
|
||||
"Red ID Card": red
|
||||
"ID Card": options.id_card_count,
|
||||
"Bread": options.bread_count,
|
||||
"Opa-Opa": options.opa_opa_count,
|
||||
"Zillion": options.zillion_count,
|
||||
"Floppy Disk": options.floppy_disk_count,
|
||||
"Scope": options.scope_count,
|
||||
"Red ID Card": options.red_id_card_count
|
||||
})
|
||||
minimums = Counter({
|
||||
"ID Card": 0,
|
||||
|
@ -335,10 +336,10 @@ def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
|
|||
item_counts["Empty"] += diff
|
||||
assert sum(item_counts.values()) == 144
|
||||
|
||||
max_level = cast(ZillionMaxLevel, wo.max_level[p])
|
||||
max_level = options.max_level
|
||||
max_level.value = max(required_level, max_level.value)
|
||||
|
||||
opas_per_level = cast(ZillionOpasPerLevel, wo.opas_per_level[p])
|
||||
opas_per_level = options.opas_per_level
|
||||
while (opas_per_level.value > 1) and (1 + item_counts["Opa-Opa"] // opas_per_level.value < max_level.value):
|
||||
# logging.warning(
|
||||
# "zillion options validate: option opas_per_level incompatible with options max_level and opa_opa_count"
|
||||
|
@ -347,39 +348,34 @@ def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
|
|||
|
||||
# that should be all of the level requirements met
|
||||
|
||||
name_capitalization = {
|
||||
name_capitalization: Dict[str, Chars] = {
|
||||
"jj": "JJ",
|
||||
"apple": "Apple",
|
||||
"champ": "Champ",
|
||||
}
|
||||
|
||||
start_char = cast(ZillionStartChar, wo.start_char[p])
|
||||
start_char = options.start_char
|
||||
start_char_name = name_capitalization[start_char.current_key]
|
||||
assert start_char_name in chars
|
||||
start_char_name = cast(Chars, start_char_name)
|
||||
|
||||
starting_cards = cast(ZillionStartingCards, wo.starting_cards[p])
|
||||
starting_cards = options.starting_cards
|
||||
|
||||
room_gen = cast(ZillionRoomGen, wo.room_gen[p])
|
||||
|
||||
early_scope = cast(ZillionEarlyScope, wo.early_scope[p])
|
||||
if early_scope:
|
||||
world.early_items[p]["Scope"] = 1
|
||||
room_gen = options.room_gen
|
||||
|
||||
zz_item_counts = convert_item_counts(item_counts)
|
||||
zz_op = ZzOptions(
|
||||
zz_item_counts,
|
||||
cast(ZzVBLR, jump_option),
|
||||
cast(ZzVBLR, gun_option),
|
||||
jump_option,
|
||||
gun_option,
|
||||
opas_per_level.value,
|
||||
max_level.value,
|
||||
False, # tutorial
|
||||
skill,
|
||||
start_char_name,
|
||||
floppy_req.value,
|
||||
wo.continues[p].value,
|
||||
wo.randomize_alarms[p].value,
|
||||
False, # early scope is done with AP early_items API
|
||||
options.continues.value,
|
||||
bool(options.randomize_alarms.value),
|
||||
bool(options.early_scope.value),
|
||||
True, # balance defense
|
||||
starting_cards.value,
|
||||
bool(room_gen.value)
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@d7122bcbeda40da5db26d60fad06246a1331706f#0.5.4
|
||||
typing-extensions>=4.7, <5
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from . import ZillionTestBase
|
||||
|
||||
from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, validate
|
||||
from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, ZillionOptions, validate
|
||||
from zilliandomizer.options import VBLR_CHOICES
|
||||
|
||||
|
||||
|
@ -9,7 +9,9 @@ class OptionsTest(ZillionTestBase):
|
|||
|
||||
def test_validate_default(self) -> None:
|
||||
self.world_setup()
|
||||
validate(self.multiworld, 1)
|
||||
options = self.multiworld.worlds[1].options
|
||||
assert isinstance(options, ZillionOptions)
|
||||
validate(options)
|
||||
|
||||
def test_vblr_ap_to_zz(self) -> None:
|
||||
""" all of the valid values for the AP options map to valid values for ZZ options """
|
||||
|
@ -20,7 +22,9 @@ class OptionsTest(ZillionTestBase):
|
|||
for value in vblr_class.name_lookup.values():
|
||||
self.options = {option_name: value}
|
||||
self.world_setup()
|
||||
zz_options, _item_counts = validate(self.multiworld, 1)
|
||||
options = self.multiworld.worlds[1].options
|
||||
assert isinstance(options, ZillionOptions)
|
||||
zz_options, _item_counts = validate(options)
|
||||
assert getattr(zz_options, option_name) in VBLR_CHOICES
|
||||
|
||||
# TODO: test validate with invalid combinations of options
|
||||
|
|
Loading…
Reference in New Issue