From 543dcb27d85510f1eec01c3744172d41440c4cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:51:03 -0400 Subject: [PATCH] Stardew Valley: Exclude maximum one resource packs from pool when in start inventory (#4839) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/stardew_valley/__init__.py | 6 ++--- worlds/stardew_valley/data/items.csv | 4 +-- worlds/stardew_valley/items.py | 27 +++++++++---------- .../strings/wallet_item_names.py | 1 + worlds/stardew_valley/test/TestGeneration.py | 4 +-- worlds/stardew_valley/test/TestItemLink.py | 8 +++--- worlds/stardew_valley/test/TestItems.py | 18 ++++++++++++- 7 files changed, 42 insertions(+), 26 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index bad0ab9e..7f420eb8 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,7 +1,7 @@ import logging import typing from random import Random -from typing import Dict, Any, Iterable, Optional, List, TextIO, cast +from typing import Dict, Any, Iterable, Optional, List, TextIO from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions @@ -148,8 +148,8 @@ class StardewValleyWorld(World): self.precollect_building_items() items_to_exclude = [excluded_items for excluded_items in self.multiworld.precollected_items[self.player] - if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK, - Group.FRIENDSHIP_PACK)] + if item_table[excluded_items.name].has_any_group(Group.MAXIMUM_ONE) + or not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK, Group.FRIENDSHIP_PACK)] if self.options.season_randomization == SeasonRandomization.option_disabled: items_to_exclude = [item for item in items_to_exclude diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index 44e16c5d..11a22e95 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -748,7 +748,7 @@ id,name,classification,groups,mod_name 5208,Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5209,Stone Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5210,Quality Bobber,filler,RESOURCE_PACK, -5211,Mini-Obelisk,filler,"EXACTLY_TWO,RESOURCE_PACK", +5211,Mini-Obelisk,filler,"AT_LEAST_TWO,RESOURCE_PACK", 5212,Monster Musk,filler,RESOURCE_PACK, 5213,Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5214,Quality Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", @@ -760,7 +760,7 @@ id,name,classification,groups,mod_name 5220,Lightning Rod,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5221,Resource Pack: 5000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5222,Resource Pack: 10000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5223,Junimo Chest,filler,"EXACTLY_TWO,RESOURCE_PACK", +5223,Junimo Chest,filler,"AT_LEAST_TWO,RESOURCE_PACK", 5224,Horse Flute,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5225,Pierre's Missing Stocklist,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5226,Hopper,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index b4b1175c..a0f901a2 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -69,7 +69,7 @@ class Group(enum.Enum): TRAP = enum.auto() BONUS = enum.auto() MAXIMUM_ONE = enum.auto() - EXACTLY_TWO = enum.auto() + AT_LEAST_TWO = enum.auto() DEPRECATED = enum.auto() RESOURCE_PACK_USEFUL = enum.auto() SPECIAL_ORDER_BOARD = enum.auto() @@ -181,7 +181,7 @@ def create_items(item_factory: StardewItemFactory, locations_count: int, items_t 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, options, random, items, locations_count) + resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items + items_to_exclude, locations_count - len(items)) items += resource_pack_items logger.debug(f"Created {len(resource_pack_items)} resource packs") @@ -711,7 +711,7 @@ def weapons_count(options: StardewValleyOptions): def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random, items_already_added: List[Item], - number_locations: int) -> List[Item]: + available_item_slots: int) -> List[Item]: include_traps = options.trap_items != TrapItems.option_no_traps 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] @@ -734,10 +734,9 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options priority_filler_items = remove_excluded_items(priority_filler_items, options) number_priority_items = len(priority_filler_items) - required_resource_pack = number_locations - len(items_already_added) - if required_resource_pack < number_priority_items: + if available_item_slots < number_priority_items: chosen_priority_items = [item_factory(resource_pack) for resource_pack in - random.sample(priority_filler_items, required_resource_pack)] + random.sample(priority_filler_items, available_item_slots)] return chosen_priority_items items = [] @@ -745,24 +744,24 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options ItemClassification.trap if resource_pack.classification == ItemClassification.trap else ItemClassification.useful) for resource_pack in priority_filler_items] items.extend(chosen_priority_items) - required_resource_pack -= number_priority_items + available_item_slots -= number_priority_items all_filler_packs = [filler_pack for filler_pack in all_filler_packs if Group.MAXIMUM_ONE not in filler_pack.groups or (filler_pack.name not in [priority_item.name for priority_item in priority_filler_items] and filler_pack.name not in items_already_added_names)] - while required_resource_pack > 0: + while available_item_slots > 0: resource_pack = random.choice(all_filler_packs) - exactly_2 = Group.EXACTLY_TWO in resource_pack.groups - while exactly_2 and required_resource_pack == 1: + exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups + while exactly_2 and available_item_slots == 1: resource_pack = random.choice(all_filler_packs) - exactly_2 = Group.EXACTLY_TWO in resource_pack.groups + exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups classification = ItemClassification.useful if resource_pack.classification == ItemClassification.progression else resource_pack.classification items.append(item_factory(resource_pack, classification)) - required_resource_pack -= 1 + available_item_slots -= 1 if exactly_2: items.append(item_factory(resource_pack, classification)) - required_resource_pack -= 1 + available_item_slots -= 1 if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups: all_filler_packs.remove(resource_pack) @@ -803,7 +802,7 @@ def generate_filler_choice_pool(options: StardewValleyOptions) -> list[str]: 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] + return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.AT_LEAST_TWO not in pack.groups] def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool) -> List[ItemData]: diff --git a/worlds/stardew_valley/strings/wallet_item_names.py b/worlds/stardew_valley/strings/wallet_item_names.py index 32655efe..743d1f0c 100644 --- a/worlds/stardew_valley/strings/wallet_item_names.py +++ b/worlds/stardew_valley/strings/wallet_item_names.py @@ -9,3 +9,4 @@ class Wallet: dark_talisman = "Dark Talisman" club_card = "Club Card" mastery_of_the_five_ways = "Mastery Of The Five Ways" + key_to_the_town = "Key To The Town" diff --git a/worlds/stardew_valley/test/TestGeneration.py b/worlds/stardew_valley/test/TestGeneration.py index 35cd2007..77092c78 100644 --- a/worlds/stardew_valley/test/TestGeneration.py +++ b/worlds/stardew_valley/test/TestGeneration.py @@ -66,7 +66,7 @@ class TestBaseItemGeneration(SVTestBase): def test_does_not_create_or_create_two_of_exactly_two_items(self): all_created_items = self.get_all_created_items() - for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: + for exactly_two_item in items.items_by_group[items.Group.AT_LEAST_TWO]: with self.subTest(f"{exactly_two_item.name}"): count = all_created_items.count(exactly_two_item.name) self.assertTrue(count == 0 or count == 2) @@ -114,7 +114,7 @@ class TestNoGingerIslandItemGeneration(SVTestBase): def test_does_not_create_exactly_two_items(self): all_created_items = self.get_all_created_items() - for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: + for exactly_two_item in items.items_by_group[items.Group.AT_LEAST_TWO]: with self.subTest(f"{exactly_two_item.name}"): count = all_created_items.count(exactly_two_item.name) self.assertTrue(count == 0 or count == 2) diff --git a/worlds/stardew_valley/test/TestItemLink.py b/worlds/stardew_valley/test/TestItemLink.py index 39bf553c..3a0d9765 100644 --- a/worlds/stardew_valley/test/TestItemLink.py +++ b/worlds/stardew_valley/test/TestItemLink.py @@ -19,7 +19,7 @@ class TestItemLinksEverythingIncluded(SVTestBase): continue filler_generated.append(filler) self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) - self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + self.assertNotIn(Group.AT_LEAST_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: @@ -46,7 +46,7 @@ class TestItemLinksNoIsland(SVTestBase): 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) + self.assertNotIn(Group.AT_LEAST_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: @@ -70,7 +70,7 @@ class TestItemLinksNoTraps(SVTestBase): 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) + self.assertNotIn(Group.AT_LEAST_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: @@ -94,7 +94,7 @@ class TestItemLinksNoTrapsAndIsland(SVTestBase): 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) + self.assertNotIn(Group.AT_LEAST_TWO, item_table[filler].groups) if len(filler_generated) >= max_number_filler: break self.assertGreaterEqual(len(filler_generated), max_number_filler) diff --git a/worlds/stardew_valley/test/TestItems.py b/worlds/stardew_valley/test/TestItems.py index 9cff1465..1d6f9689 100644 --- a/worlds/stardew_valley/test/TestItems.py +++ b/worlds/stardew_valley/test/TestItems.py @@ -1,4 +1,4 @@ -from BaseClasses import MultiWorld, get_seed +from BaseClasses import MultiWorld, get_seed, ItemClassification from . import setup_solo_multiworld, SVTestCase, solo_multiworld from .options.presets import allsanity_no_mods_6_x_x, get_minsanity_options from .. import StardewValleyWorld @@ -72,6 +72,22 @@ class TestItems(SVTestCase): self.assertEqual(len(starting_seasons_rolled), 4) +class TestStartInventoryFillersAreProperlyExcluded(SVTestCase): + def test_given_maximum_one_resource_pack_in_start_inventory_when_create_items_then_item_is_properly_excluded(self): + assert item_table[Wallet.key_to_the_town].classification == ItemClassification.useful \ + and {Group.MAXIMUM_ONE, Group.RESOURCE_PACK_USEFUL}.issubset(item_table[Wallet.key_to_the_town].groups), \ + "'Key to the Town' is no longer suitable to test this usecase." + + options = { + "start_inventory": { + Wallet.key_to_the_town: 1, + } + } + + with solo_multiworld(options, world_caching=False) as (multiworld, world): + self.assertNotIn(world.create_item(Wallet.key_to_the_town), multiworld.get_items()) + + class TestMetalDetectors(SVTestCase): def test_minsanity_1_metal_detector(self): options = get_minsanity_options()