mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Stardew Valley 6.x.x: The Content Update (#3478)
Focus of the Update: Compatibility with Stardew Valley 1.6 Released on March 19th 2024 This includes randomization for pretty much all of the new content, including but not limited to - Raccoon Bundles - Booksanity - Skill Masteries - New Recipes, Craftables, Fish, Maps, Farm Type, Festivals and Quests This also includes a significant reorganisation of the code into "Content Packs", to allow for easier modularity of various game mechanics between the settings and the supported mods. This improves maintainability quite a bit. In addition to that, a few **very** requested new features have been introduced, although they weren't the focus of this update - Walnutsanity - Player Buffs - More customizability in settings, such as shorter special orders, ER without farmhouse - New Remixed Bundles
This commit is contained in:
@@ -1,201 +1,183 @@
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import unittest
|
||||
from argparse import Namespace
|
||||
from contextlib import contextmanager
|
||||
from typing import Dict, ClassVar, Iterable, Hashable, Tuple, Optional, List, Union, Any
|
||||
from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, get_seed, Location
|
||||
from BaseClasses import MultiWorld, CollectionState, get_seed, Location, Item, ItemClassification
|
||||
from Options import VerifyKeys
|
||||
from Utils import cache_argsless
|
||||
from test.bases import WorldTestBase
|
||||
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
||||
from worlds.AutoWorld import call_all
|
||||
from .assertion import RuleAssertMixin
|
||||
from .. import StardewValleyWorld, options
|
||||
from ..mods.mod_data import all_mods
|
||||
from .. import StardewValleyWorld, options, StardewItem
|
||||
from ..options import StardewValleyOptions, StardewValleyOption
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_TEST_SEED = get_seed()
|
||||
logger.info(f"Default Test Seed: {DEFAULT_TEST_SEED}")
|
||||
|
||||
|
||||
# TODO is this caching really changing anything?
|
||||
@cache_argsless
|
||||
def disable_5_x_x_options():
|
||||
def default_6_x_x():
|
||||
return {
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_none,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_none,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_none,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.option_none,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.option_none
|
||||
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.default,
|
||||
options.BackpackProgression.internal_name: options.BackpackProgression.default,
|
||||
options.Booksanity.internal_name: options.Booksanity.default,
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.default,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.default,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.default,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.default,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.default,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.default,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.default,
|
||||
options.ElevatorProgression.internal_name: options.ElevatorProgression.default,
|
||||
options.EntranceRandomization.internal_name: options.EntranceRandomization.default,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.default,
|
||||
options.FestivalLocations.internal_name: options.FestivalLocations.default,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.default,
|
||||
options.Friendsanity.internal_name: options.Friendsanity.default,
|
||||
options.FriendsanityHeartSize.internal_name: options.FriendsanityHeartSize.default,
|
||||
options.Goal.internal_name: options.Goal.default,
|
||||
options.Mods.internal_name: options.Mods.default,
|
||||
options.Monstersanity.internal_name: options.Monstersanity.default,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.default,
|
||||
options.NumberOfMovementBuffs.internal_name: options.NumberOfMovementBuffs.default,
|
||||
options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default,
|
||||
options.QuestLocations.internal_name: options.QuestLocations.default,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.default,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.default,
|
||||
options.SkillProgression.internal_name: options.SkillProgression.default,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.default,
|
||||
options.ToolProgression.internal_name: options.ToolProgression.default,
|
||||
options.TrapItems.internal_name: options.TrapItems.default,
|
||||
options.Walnutsanity.internal_name: options.Walnutsanity.default
|
||||
}
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def default_4_x_x_options():
|
||||
option_dict = default_options().copy()
|
||||
option_dict.update(disable_5_x_x_options())
|
||||
option_dict.update({
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled,
|
||||
})
|
||||
return option_dict
|
||||
def allsanity_no_mods_6_x_x():
|
||||
return {
|
||||
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling,
|
||||
options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive,
|
||||
options.Booksanity.internal_name: options.Booksanity.option_all,
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_expensive,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.option_all,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_all,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.option_all,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_enabled,
|
||||
options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive,
|
||||
options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false,
|
||||
options.FestivalLocations.internal_name: options.FestivalLocations.option_hard,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_all,
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage,
|
||||
options.FriendsanityHeartSize.internal_name: 1,
|
||||
options.Goal.internal_name: options.Goal.option_perfection,
|
||||
options.Mods.internal_name: frozenset(),
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_all,
|
||||
options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all,
|
||||
options.NumberOfMovementBuffs.internal_name: 12,
|
||||
options.QuestLocations.internal_name: 56,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_everything,
|
||||
options.SkillProgression.internal_name: options.SkillProgression.option_progressive,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi,
|
||||
options.ToolProgression.internal_name: options.ToolProgression.option_progressive,
|
||||
options.TrapItems.internal_name: options.TrapItems.option_nightmare,
|
||||
options.Walnutsanity.internal_name: options.Walnutsanity.preset_all
|
||||
}
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def default_options():
|
||||
return {}
|
||||
def allsanity_mods_6_x_x():
|
||||
allsanity = allsanity_no_mods_6_x_x()
|
||||
allsanity.update({options.Mods.internal_name: frozenset(options.Mods.valid_keys)})
|
||||
return allsanity
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_minsanity_options():
|
||||
return {
|
||||
options.Goal.internal_name: options.Goal.option_bottom_of_the_mines,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_vanilla,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_very_cheap,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_disabled,
|
||||
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.FestivalLocations.internal_name: options.FestivalLocations.option_disabled,
|
||||
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.QuestLocations.internal_name: -1,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_none,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_none,
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_none,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_none,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_none,
|
||||
options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla,
|
||||
options.Booksanity.internal_name: options.Booksanity.option_none,
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_very_cheap,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_vanilla,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.option_none,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_none,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.option_none,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_disabled,
|
||||
options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla,
|
||||
options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true,
|
||||
options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_none,
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_none,
|
||||
options.FriendsanityHeartSize.internal_name: 8,
|
||||
options.NumberOfMovementBuffs.internal_name: 0,
|
||||
options.NumberOfLuckBuffs.internal_name: 0,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true,
|
||||
options.TrapItems.internal_name: options.TrapItems.option_no_traps,
|
||||
options.Goal.internal_name: options.Goal.option_bottom_of_the_mines,
|
||||
options.Mods.internal_name: frozenset(),
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_none,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_none,
|
||||
options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_none,
|
||||
options.NumberOfMovementBuffs.internal_name: 0,
|
||||
options.QuestLocations.internal_name: -1,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_none,
|
||||
options.SkillProgression.internal_name: options.SkillProgression.option_vanilla,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla,
|
||||
options.ToolProgression.internal_name: options.ToolProgression.option_vanilla,
|
||||
options.TrapItems.internal_name: options.TrapItems.option_no_traps,
|
||||
options.Walnutsanity.internal_name: options.Walnutsanity.preset_none
|
||||
}
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def minimal_locations_maximal_items():
|
||||
min_max_options = {
|
||||
options.Goal.internal_name: options.Goal.option_craft_master,
|
||||
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_disabled,
|
||||
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.FestivalLocations.internal_name: options.FestivalLocations.option_disabled,
|
||||
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.QuestLocations.internal_name: -1,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_none,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_none,
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_none,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_none,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_none,
|
||||
options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla,
|
||||
options.Booksanity.internal_name: options.Booksanity.option_none,
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_expensive,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.option_none,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_none,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.option_none,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_disabled,
|
||||
options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla,
|
||||
options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true,
|
||||
options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_none,
|
||||
options.Friendsanity.internal_name: options.Friendsanity.option_none,
|
||||
options.FriendsanityHeartSize.internal_name: 8,
|
||||
options.Goal.internal_name: options.Goal.option_craft_master,
|
||||
options.Mods.internal_name: frozenset(),
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_none,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_none,
|
||||
options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all,
|
||||
options.NumberOfMovementBuffs.internal_name: 12,
|
||||
options.NumberOfLuckBuffs.internal_name: 12,
|
||||
options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true,
|
||||
options.QuestLocations.internal_name: -1,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_none,
|
||||
options.SkillProgression.internal_name: options.SkillProgression.option_vanilla,
|
||||
options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla,
|
||||
options.ToolProgression.internal_name: options.ToolProgression.option_vanilla,
|
||||
options.TrapItems.internal_name: options.TrapItems.option_nightmare,
|
||||
options.Mods.internal_name: (),
|
||||
options.Walnutsanity.internal_name: options.Walnutsanity.preset_none
|
||||
}
|
||||
return min_max_options
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def minimal_locations_maximal_items_with_island():
|
||||
min_max_options = minimal_locations_maximal_items().copy()
|
||||
min_max_options = minimal_locations_maximal_items()
|
||||
min_max_options.update({options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false})
|
||||
return min_max_options
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def allsanity_4_x_x_options_without_mods():
|
||||
option_dict = {
|
||||
options.Goal.internal_name: options.Goal.option_perfection,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_expensive,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_enabled,
|
||||
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.QuestLocations.internal_name: 56,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_all,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_all,
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_everything,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_all,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.option_all,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.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,
|
||||
}
|
||||
option_dict.update(disable_5_x_x_options())
|
||||
return option_dict
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def allsanity_options_without_mods():
|
||||
return {
|
||||
options.Goal.internal_name: options.Goal.option_perfection,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic,
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_expensive,
|
||||
options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized,
|
||||
options.Cropsanity.internal_name: options.Cropsanity.option_enabled,
|
||||
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.QuestLocations.internal_name: 56,
|
||||
options.Fishsanity.internal_name: options.Fishsanity.option_all,
|
||||
options.Museumsanity.internal_name: options.Museumsanity.option_all,
|
||||
options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals,
|
||||
options.Shipsanity.internal_name: options.Shipsanity.option_everything,
|
||||
options.Cooksanity.internal_name: options.Cooksanity.option_all,
|
||||
options.Chefsanity.internal_name: options.Chefsanity.option_all,
|
||||
options.Craftsanity.internal_name: options.Craftsanity.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,
|
||||
}
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def allsanity_options_with_mods():
|
||||
allsanity = allsanity_options_without_mods().copy()
|
||||
allsanity.update({options.Mods.internal_name: all_mods})
|
||||
return allsanity
|
||||
|
||||
|
||||
class SVTestCase(unittest.TestCase):
|
||||
# Set False to not skip some 'extra' tests
|
||||
skip_base_tests: bool = True
|
||||
@@ -219,7 +201,6 @@ class SVTestCase(unittest.TestCase):
|
||||
*,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
world_caching=True,
|
||||
dirty_state=False,
|
||||
**kwargs) -> Tuple[MultiWorld, StardewValleyWorld]:
|
||||
if msg is not None:
|
||||
msg += " "
|
||||
@@ -228,17 +209,8 @@ class SVTestCase(unittest.TestCase):
|
||||
msg += f"[Seed = {seed}]"
|
||||
|
||||
with self.subTest(msg, **kwargs):
|
||||
if world_caching:
|
||||
multi_world = setup_solo_multiworld(world_options, seed)
|
||||
if dirty_state:
|
||||
original_state = multi_world.state.copy()
|
||||
else:
|
||||
multi_world = setup_solo_multiworld(world_options, seed, _cache={})
|
||||
|
||||
yield multi_world, multi_world.worlds[1]
|
||||
|
||||
if world_caching and dirty_state:
|
||||
multi_world.state = original_state
|
||||
with solo_multiworld(world_options, seed=seed, world_caching=world_caching) as (multiworld, world):
|
||||
yield multiworld, world
|
||||
|
||||
|
||||
class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase):
|
||||
@@ -248,59 +220,140 @@ class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase):
|
||||
|
||||
seed = DEFAULT_TEST_SEED
|
||||
|
||||
options = get_minsanity_options()
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
if cls is SVTestBase:
|
||||
raise unittest.SkipTest("No running tests on SVTestBase import.")
|
||||
|
||||
super().setUpClass()
|
||||
|
||||
def world_setup(self, *args, **kwargs):
|
||||
self.options = parse_class_option_keys(self.options)
|
||||
|
||||
super().world_setup(seed=self.seed)
|
||||
self.multiworld = setup_solo_multiworld(self.options, seed=self.seed)
|
||||
self.multiworld.lock.acquire()
|
||||
world = self.multiworld.worlds[self.player]
|
||||
|
||||
self.original_state = self.multiworld.state.copy()
|
||||
self.original_itempool = self.multiworld.itempool.copy()
|
||||
self.original_prog_item_count = world.total_progression_items
|
||||
self.unfilled_locations = self.multiworld.get_unfilled_locations(1)
|
||||
if self.constructed:
|
||||
self.world = self.multiworld.worlds[self.player] # noqa
|
||||
self.world = world # noqa
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.multiworld.state = self.original_state
|
||||
self.multiworld.itempool = self.original_itempool
|
||||
for location in self.unfilled_locations:
|
||||
location.item = None
|
||||
self.world.total_progression_items = self.original_prog_item_count
|
||||
|
||||
self.multiworld.lock.release()
|
||||
|
||||
@property
|
||||
def run_default_tests(self) -> bool:
|
||||
if self.skip_base_tests:
|
||||
return False
|
||||
# world_setup is overridden, so it'd always run default tests when importing SVTestBase
|
||||
is_not_stardew_test = type(self) is not SVTestBase
|
||||
should_run_default_tests = is_not_stardew_test and super().run_default_tests
|
||||
return should_run_default_tests
|
||||
return super().run_default_tests
|
||||
|
||||
def collect_lots_of_money(self):
|
||||
self.multiworld.state.collect(self.world.create_item("Shipping Bin"), event=False)
|
||||
for i in range(100):
|
||||
required_prog_items = int(round(self.multiworld.worlds[self.player].total_progression_items * 0.25))
|
||||
for i in range(required_prog_items):
|
||||
self.multiworld.state.collect(self.world.create_item("Stardrop"), event=False)
|
||||
|
||||
def collect_all_the_money(self):
|
||||
self.multiworld.state.collect(self.world.create_item("Shipping Bin"), event=False)
|
||||
for i in range(1000):
|
||||
required_prog_items = int(round(self.multiworld.worlds[self.player].total_progression_items * 0.95))
|
||||
for i in range(required_prog_items):
|
||||
self.multiworld.state.collect(self.world.create_item("Stardrop"), event=False)
|
||||
|
||||
def collect_everything(self):
|
||||
non_event_items = [item for item in self.multiworld.get_items() if item.code]
|
||||
for item in non_event_items:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def collect_all_except(self, item_to_not_collect: str):
|
||||
for item in self.multiworld.get_items():
|
||||
if item.name != item_to_not_collect:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def get_real_locations(self) -> List[Location]:
|
||||
return [location for location in self.multiworld.get_locations(self.player) if location.address is not None]
|
||||
|
||||
def get_real_location_names(self) -> List[str]:
|
||||
return [location.name for location in self.get_real_locations()]
|
||||
|
||||
def collect(self, item: Union[str, Item, Iterable[Item]], count: int = 1) -> Union[None, Item, List[Item]]:
|
||||
assert count > 0
|
||||
if not isinstance(item, str):
|
||||
super().collect(item)
|
||||
return
|
||||
if count == 1:
|
||||
item = self.create_item(item)
|
||||
self.multiworld.state.collect(item)
|
||||
return item
|
||||
items = []
|
||||
for i in range(count):
|
||||
item = self.create_item(item)
|
||||
self.multiworld.state.collect(item)
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
def create_item(self, item: str) -> StardewItem:
|
||||
created_item = self.world.create_item(item)
|
||||
if created_item.classification == ItemClassification.progression:
|
||||
self.multiworld.worlds[self.player].total_progression_items -= 1
|
||||
return created_item
|
||||
|
||||
|
||||
pre_generated_worlds = {}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def solo_multiworld(world_options: Optional[Dict[Union[str, StardewValleyOption], Any]] = None,
|
||||
*,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
world_caching=True) -> Tuple[MultiWorld, StardewValleyWorld]:
|
||||
if not world_caching:
|
||||
multiworld = setup_solo_multiworld(world_options, seed, _cache={})
|
||||
yield multiworld, multiworld.worlds[1]
|
||||
else:
|
||||
multiworld = setup_solo_multiworld(world_options, seed)
|
||||
multiworld.lock.acquire()
|
||||
world = multiworld.worlds[1]
|
||||
|
||||
original_state = multiworld.state.copy()
|
||||
original_itempool = multiworld.itempool.copy()
|
||||
unfilled_locations = multiworld.get_unfilled_locations(1)
|
||||
original_prog_item_count = world.total_progression_items
|
||||
|
||||
yield multiworld, world
|
||||
|
||||
multiworld.state = original_state
|
||||
multiworld.itempool = original_itempool
|
||||
for location in unfilled_locations:
|
||||
location.item = None
|
||||
multiworld.total_progression_items = original_prog_item_count
|
||||
|
||||
multiworld.lock.release()
|
||||
|
||||
|
||||
# Mostly a copy of test.general.setup_solo_multiworld, I just don't want to change the core.
|
||||
def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOption], str]] = None,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
_cache: Dict[Hashable, MultiWorld] = {}, # noqa
|
||||
_cache: Dict[frozenset, MultiWorld] = {}, # noqa
|
||||
_steps=gen_steps) -> MultiWorld:
|
||||
test_options = parse_class_option_keys(test_options)
|
||||
|
||||
# Yes I reuse the worlds generated between tests, its speeds the execution by a couple seconds
|
||||
# If the simple dict caching ends up taking too much memory, we could replace it with some kind of lru cache.
|
||||
should_cache = "start_inventory" not in test_options
|
||||
frozen_options = frozenset({})
|
||||
if should_cache:
|
||||
frozen_options = frozenset(test_options.items()).union({seed})
|
||||
if frozen_options in _cache:
|
||||
cached_multi_world = _cache[frozen_options]
|
||||
print(f"Using cached solo multi world [Seed = {cached_multi_world.seed}]")
|
||||
frozen_options = frozenset(test_options.items()).union({("seed", seed)})
|
||||
cached_multi_world = search_world_cache(_cache, frozen_options)
|
||||
if cached_multi_world:
|
||||
print(f"Using cached solo multi world [Seed = {cached_multi_world.seed}] [Cache size = {len(_cache)}]")
|
||||
return cached_multi_world
|
||||
|
||||
multiworld = setup_base_solo_multiworld(StardewValleyWorld, (), seed=seed)
|
||||
@@ -326,28 +379,47 @@ def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOp
|
||||
call_all(multiworld, step)
|
||||
|
||||
if should_cache:
|
||||
_cache[frozen_options] = multiworld
|
||||
add_to_world_cache(_cache, frozen_options, multiworld) # noqa
|
||||
|
||||
# Lock is needed for multi-threading tests
|
||||
setattr(multiworld, "lock", threading.Lock())
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
def parse_class_option_keys(test_options: dict) -> dict:
|
||||
def parse_class_option_keys(test_options: Optional[Dict]) -> dict:
|
||||
""" Now the option class is allowed as key. """
|
||||
if test_options is None:
|
||||
return {}
|
||||
parsed_options = {}
|
||||
|
||||
if test_options:
|
||||
for option, value in test_options.items():
|
||||
if hasattr(option, "internal_name"):
|
||||
assert option.internal_name not in test_options, "Defined two times by class and internal_name"
|
||||
parsed_options[option.internal_name] = value
|
||||
else:
|
||||
assert option in StardewValleyOptions.type_hints, \
|
||||
f"All keys of world_options must be a possible Stardew Valley option, {option} is not."
|
||||
parsed_options[option] = value
|
||||
for option, value in test_options.items():
|
||||
if hasattr(option, "internal_name"):
|
||||
assert option.internal_name not in test_options, "Defined two times by class and internal_name"
|
||||
parsed_options[option.internal_name] = value
|
||||
else:
|
||||
assert option in StardewValleyOptions.type_hints, \
|
||||
f"All keys of world_options must be a possible Stardew Valley option, {option} is not."
|
||||
parsed_options[option] = value
|
||||
|
||||
return parsed_options
|
||||
|
||||
|
||||
def search_world_cache(cache: Dict[frozenset, MultiWorld], frozen_options: frozenset) -> Optional[MultiWorld]:
|
||||
try:
|
||||
return cache[frozen_options]
|
||||
except KeyError:
|
||||
for cached_options, multi_world in cache.items():
|
||||
if frozen_options.issubset(cached_options):
|
||||
return multi_world
|
||||
return None
|
||||
|
||||
|
||||
def add_to_world_cache(cache: Dict[frozenset, MultiWorld], frozen_options: frozenset, multi_world: MultiWorld) -> None:
|
||||
# We could complete the key with all the default options, but that does not seem to improve performances.
|
||||
cache[frozen_options] = multi_world
|
||||
|
||||
|
||||
def complete_options_with_default(options_to_complete=None) -> StardewValleyOptions:
|
||||
if options_to_complete is None:
|
||||
options_to_complete = {}
|
||||
|
||||
Reference in New Issue
Block a user