diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 24ffa8c1..aa825af3 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,16 +1,16 @@ import logging from typing import Dict, Any, Iterable, Optional, Union, Set, List -from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState, ItemClassification, MultiWorld +from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState, ItemClassification, MultiWorld, Group as ItemLinkGroup from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld from . import rules from .bundles import get_all_bundles, Bundle -from .items import item_table, create_items, ItemData, Group, items_by_group +from .items import item_table, create_items, ItemData, Group, items_by_group, get_all_filler_items, remove_limited_amount_packs from .locations import location_table, create_locations, LocationData from .logic import StardewLogic, StardewRule, True_, MAX_MONTHS from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, NumberOfLuckBuffs, NumberOfMovementBuffs, \ - BackpackProgression, BuildingProgression, ExcludeGingerIsland + BackpackProgression, BuildingProgression, ExcludeGingerIsland, TrapItems from .presets import sv_options_presets from .regions import create_regions from .rules import set_rules @@ -74,6 +74,7 @@ class StardewValleyWorld(World): def __init__(self, world: MultiWorld, player: int): super().__init__(world, player) self.all_progression_items = set() + self.filler_item_pool_names = [] def generate_early(self): self.force_change_options_if_incompatible() @@ -270,7 +271,33 @@ class StardewValleyWorld(World): pass def get_filler_item_name(self) -> str: - return "Joja Cola" + if not self.filler_item_pool_names: + self.generate_filler_item_pool_names() + return self.random.choice(self.filler_item_pool_names) + + def generate_filler_item_pool_names(self): + include_traps, exclude_island = self.get_filler_item_rules() + available_filler = get_all_filler_items(include_traps, exclude_island) + available_filler = remove_limited_amount_packs(available_filler) + self.filler_item_pool_names = [item.name for item in available_filler] + + def get_filler_item_rules(self): + if self.player in self.multiworld.groups: + link_group: ItemLinkGroup = self.multiworld.groups[self.player] + include_traps = True + exclude_island = False + for player in link_group["players"]: + player_options = self.multiworld.worlds[player].options + if self.multiworld.game[player] != self.game: + + continue + if player_options.trap_items == TrapItems.option_no_traps: + include_traps = False + if player_options.exclude_ginger_island == ExcludeGingerIsland.option_true: + exclude_island = True + return include_traps, exclude_island + else: + return self.options.trap_items != TrapItems.option_no_traps, self.options.exclude_ginger_island == ExcludeGingerIsland.option_true def fill_slot_data(self) -> Dict[str, Any]: diff --git a/worlds/stardew_valley/data/bundle_data.py b/worlds/stardew_valley/data/bundle_data.py index 8a1a6a5b..183383cc 100644 --- a/worlds/stardew_valley/data/bundle_data.py +++ b/worlds/stardew_valley/data/bundle_data.py @@ -303,8 +303,7 @@ artisan_goods_items = [truffle_oil, cloth, goat_cheese, cheese, honey, beer, jui river_fish_items = [chub, catfish, rainbow_trout, lingcod, walleye, perch, pike, bream, salmon, sunfish, tiger_trout, shad, smallmouth_bass, dorado] -lake_fish_items = [chub, rainbow_trout, lingcod, walleye, perch, carp, midnight_carp, - largemouth_bass, sturgeon, bullhead, midnight_carp] +lake_fish_items = [chub, rainbow_trout, lingcod, walleye, perch, carp, midnight_carp, largemouth_bass, sturgeon, bullhead] ocean_fish_items = [tilapia, pufferfish, tuna, super_cucumber, flounder, anchovy, sardine, red_mullet, herring, eel, octopus, red_snapper, squid, sea_cucumber, albacore, halibut] night_fish_items = [walleye, bream, super_cucumber, eel, squid, midnight_carp] diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index a5a370aa..1f0735f4 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -468,10 +468,6 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options items_already_added: List[Item], number_locations: int) -> List[Item]: 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: - all_filler_packs.extend(items_by_group[Group.TRAP]) items_already_added_names = [item.name for item in items_already_added] useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL] if pack.name not in items_already_added_names] @@ -484,8 +480,9 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options if include_traps: priority_filler_items.extend(trap_items) - all_filler_packs = remove_excluded_packs(all_filler_packs, options) - priority_filler_items = remove_excluded_packs(priority_filler_items, options) + exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true + all_filler_packs = get_all_filler_items(include_traps, exclude_ginger_island) + priority_filler_items = remove_excluded_packs(priority_filler_items, exclude_ginger_island) number_priority_items = len(priority_filler_items) required_resource_pack = number_locations - len(items_already_added) @@ -519,8 +516,21 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options return items -def remove_excluded_packs(packs, options: StardewValleyOptions): +def remove_excluded_packs(packs, exclude_ginger_island: bool): included_packs = [pack for pack in packs if Group.DEPRECATED not in pack.groups] - if options.exclude_ginger_island == ExcludeGingerIsland.option_true: + if exclude_ginger_island: included_packs = [pack for pack in included_packs if Group.GINGER_ISLAND not in pack.groups] return included_packs + + +def remove_limited_amount_packs(packs): + return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.EXACTLY_TWO not in pack.groups] + + +def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool): + 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: + all_filler_packs.extend(items_by_group[Group.TRAP]) + all_filler_packs = remove_excluded_packs(all_filler_packs, exclude_ginger_island) + return all_filler_packs diff --git a/worlds/stardew_valley/test/TestItemLink.py b/worlds/stardew_valley/test/TestItemLink.py new file mode 100644 index 00000000..f55ab8ca --- /dev/null +++ b/worlds/stardew_valley/test/TestItemLink.py @@ -0,0 +1,100 @@ +from . import SVTestBase +from .. import options, item_table, Group + +max_iterations = 2000 + + +class TestItemLinksEverythingIncluded(SVTestBase): + options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.TrapItems.internal_name: options.TrapItems.option_medium} + + def test_filler_of_all_types_generated(self): + max_number_filler = 115 + filler_generated = [] + at_least_one_trap = False + at_least_one_island = False + for i in range(0, max_iterations): + filler = self.multiworld.worlds[1].get_filler_item_name() + if filler in filler_generated: + continue + filler_generated.append(filler) + self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) + self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + if Group.TRAP in item_table[filler].groups: + at_least_one_trap = True + if Group.GINGER_ISLAND in item_table[filler].groups: + at_least_one_island = True + if len(filler_generated) >= max_number_filler: + break + self.assertTrue(at_least_one_trap) + self.assertTrue(at_least_one_island) + self.assertGreaterEqual(len(filler_generated), max_number_filler) + + +class TestItemLinksNoIsland(SVTestBase): + options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.TrapItems.internal_name: options.TrapItems.option_medium} + + def test_filler_has_no_island_but_has_traps(self): + max_number_filler = 109 + filler_generated = [] + at_least_one_trap = False + for i in range(0, max_iterations): + filler = self.multiworld.worlds[1].get_filler_item_name() + if filler in filler_generated: + continue + filler_generated.append(filler) + self.assertNotIn(Group.GINGER_ISLAND, item_table[filler].groups) + self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) + self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + if Group.TRAP in item_table[filler].groups: + at_least_one_trap = True + if len(filler_generated) >= max_number_filler: + break + self.assertTrue(at_least_one_trap) + self.assertGreaterEqual(len(filler_generated), max_number_filler) + + +class TestItemLinksNoTraps(SVTestBase): + options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.TrapItems.internal_name: options.TrapItems.option_no_traps} + + def test_filler_has_no_traps_but_has_island(self): + max_number_filler = 100 + filler_generated = [] + at_least_one_island = False + for i in range(0, max_iterations): + filler = self.multiworld.worlds[1].get_filler_item_name() + if filler in filler_generated: + continue + filler_generated.append(filler) + self.assertNotIn(Group.TRAP, item_table[filler].groups) + self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) + self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + if Group.GINGER_ISLAND in item_table[filler].groups: + at_least_one_island = True + if len(filler_generated) >= max_number_filler: + break + self.assertTrue(at_least_one_island) + self.assertGreaterEqual(len(filler_generated), max_number_filler) + + +class TestItemLinksNoTrapsAndIsland(SVTestBase): + options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.TrapItems.internal_name: options.TrapItems.option_no_traps} + + def test_filler_generated_without_island_or_traps(self): + max_number_filler = 94 + filler_generated = [] + for i in range(0, max_iterations): + filler = self.multiworld.worlds[1].get_filler_item_name() + if filler in filler_generated: + continue + filler_generated.append(filler) + self.assertNotIn(Group.GINGER_ISLAND, item_table[filler].groups) + self.assertNotIn(Group.TRAP, item_table[filler].groups) + self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) + self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + if len(filler_generated) >= max_number_filler: + break + self.assertGreaterEqual(len(filler_generated), max_number_filler)