mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-29 17:43:22 -07:00
Stardew Valley: Fixed an issue where some specific option combinations could create more items than locations (#6012)
* - Improved the dynamic locations count algorithm to take into account the nature of various heavy settings in both directions * - Fixes from Code Review * - We're only testing for sunday locations, might as well only take sunday locations in the list to test * - One more slight optimization * - Added consideration for bundles per room in filler locations counting * - Registered some more IDs to handle items up to 10
This commit is contained in:
@@ -438,6 +438,8 @@ id,region,name,tags,content_packs
|
||||
906,Traveling Cart Sunday,Traveling Merchant Sunday Item 6,"TRAVELING_MERCHANT",
|
||||
907,Traveling Cart Sunday,Traveling Merchant Sunday Item 7,"TRAVELING_MERCHANT",
|
||||
908,Traveling Cart Sunday,Traveling Merchant Sunday Item 8,"TRAVELING_MERCHANT",
|
||||
909,Traveling Cart Sunday,Traveling Merchant Sunday Item 9,"TRAVELING_MERCHANT",
|
||||
910,Traveling Cart Sunday,Traveling Merchant Sunday Item 10,"TRAVELING_MERCHANT",
|
||||
911,Traveling Cart Monday,Traveling Merchant Monday Item 1,"MANDATORY,TRAVELING_MERCHANT",
|
||||
912,Traveling Cart Monday,Traveling Merchant Monday Item 2,"TRAVELING_MERCHANT",
|
||||
913,Traveling Cart Monday,Traveling Merchant Monday Item 3,"TRAVELING_MERCHANT",
|
||||
@@ -446,6 +448,8 @@ id,region,name,tags,content_packs
|
||||
916,Traveling Cart Monday,Traveling Merchant Monday Item 6,"TRAVELING_MERCHANT",
|
||||
917,Traveling Cart Monday,Traveling Merchant Monday Item 7,"TRAVELING_MERCHANT",
|
||||
918,Traveling Cart Monday,Traveling Merchant Monday Item 8,"TRAVELING_MERCHANT",
|
||||
919,Traveling Cart Monday,Traveling Merchant Monday Item 9,"TRAVELING_MERCHANT",
|
||||
920,Traveling Cart Monday,Traveling Merchant Monday Item 10,"TRAVELING_MERCHANT",
|
||||
921,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 1,"MANDATORY,TRAVELING_MERCHANT",
|
||||
922,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 2,"TRAVELING_MERCHANT",
|
||||
923,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 3,"TRAVELING_MERCHANT",
|
||||
@@ -454,6 +458,8 @@ id,region,name,tags,content_packs
|
||||
926,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 6,"TRAVELING_MERCHANT",
|
||||
927,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 7,"TRAVELING_MERCHANT",
|
||||
928,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 8,"TRAVELING_MERCHANT",
|
||||
929,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 9,"TRAVELING_MERCHANT",
|
||||
930,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 10,"TRAVELING_MERCHANT",
|
||||
931,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 1,"MANDATORY,TRAVELING_MERCHANT",
|
||||
932,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 2,"TRAVELING_MERCHANT",
|
||||
933,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 3,"TRAVELING_MERCHANT",
|
||||
@@ -462,6 +468,8 @@ id,region,name,tags,content_packs
|
||||
936,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 6,"TRAVELING_MERCHANT",
|
||||
937,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 7,"TRAVELING_MERCHANT",
|
||||
938,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 8,"TRAVELING_MERCHANT",
|
||||
939,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 9,"TRAVELING_MERCHANT",
|
||||
940,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 10,"TRAVELING_MERCHANT",
|
||||
941,Traveling Cart Thursday,Traveling Merchant Thursday Item 1,"MANDATORY,TRAVELING_MERCHANT",
|
||||
942,Traveling Cart Thursday,Traveling Merchant Thursday Item 2,"TRAVELING_MERCHANT",
|
||||
943,Traveling Cart Thursday,Traveling Merchant Thursday Item 3,"TRAVELING_MERCHANT",
|
||||
@@ -470,6 +478,8 @@ id,region,name,tags,content_packs
|
||||
946,Traveling Cart Thursday,Traveling Merchant Thursday Item 6,"TRAVELING_MERCHANT",
|
||||
947,Traveling Cart Thursday,Traveling Merchant Thursday Item 7,"TRAVELING_MERCHANT",
|
||||
948,Traveling Cart Thursday,Traveling Merchant Thursday Item 8,"TRAVELING_MERCHANT",
|
||||
949,Traveling Cart Thursday,Traveling Merchant Thursday Item 9,"TRAVELING_MERCHANT",
|
||||
950,Traveling Cart Thursday,Traveling Merchant Thursday Item 10,"TRAVELING_MERCHANT",
|
||||
951,Traveling Cart Friday,Traveling Merchant Friday Item 1,"MANDATORY,TRAVELING_MERCHANT",
|
||||
952,Traveling Cart Friday,Traveling Merchant Friday Item 2,"TRAVELING_MERCHANT",
|
||||
953,Traveling Cart Friday,Traveling Merchant Friday Item 3,"TRAVELING_MERCHANT",
|
||||
@@ -478,6 +488,8 @@ id,region,name,tags,content_packs
|
||||
956,Traveling Cart Friday,Traveling Merchant Friday Item 6,"TRAVELING_MERCHANT",
|
||||
957,Traveling Cart Friday,Traveling Merchant Friday Item 7,"TRAVELING_MERCHANT",
|
||||
958,Traveling Cart Friday,Traveling Merchant Friday Item 8,"TRAVELING_MERCHANT",
|
||||
959,Traveling Cart Friday,Traveling Merchant Friday Item 9,"TRAVELING_MERCHANT",
|
||||
960,Traveling Cart Friday,Traveling Merchant Friday Item 10,"TRAVELING_MERCHANT",
|
||||
961,Traveling Cart Saturday,Traveling Merchant Saturday Item 1,"MANDATORY,TRAVELING_MERCHANT",
|
||||
962,Traveling Cart Saturday,Traveling Merchant Saturday Item 2,"TRAVELING_MERCHANT",
|
||||
963,Traveling Cart Saturday,Traveling Merchant Saturday Item 3,"TRAVELING_MERCHANT",
|
||||
@@ -486,6 +498,8 @@ id,region,name,tags,content_packs
|
||||
966,Traveling Cart Saturday,Traveling Merchant Saturday Item 6,"TRAVELING_MERCHANT",
|
||||
967,Traveling Cart Saturday,Traveling Merchant Saturday Item 7,"TRAVELING_MERCHANT",
|
||||
968,Traveling Cart Saturday,Traveling Merchant Saturday Item 8,"TRAVELING_MERCHANT",
|
||||
969,Traveling Cart Saturday,Traveling Merchant Saturday Item 9,"TRAVELING_MERCHANT",
|
||||
970,Traveling Cart Saturday,Traveling Merchant Saturday Item 10,"TRAVELING_MERCHANT",
|
||||
1001,Fishing,Fishsanity: Carp,FISHSANITY,
|
||||
1002,Fishing,Fishsanity: Herring,FISHSANITY,
|
||||
1003,Fishing,Fishsanity: Smallmouth Bass,FISHSANITY,
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import csv
|
||||
import enum
|
||||
import logging
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from random import Random
|
||||
from typing import Optional, Dict, Protocol, List, Iterable
|
||||
@@ -16,7 +17,7 @@ from .mods.mod_data import ModNames
|
||||
from .options import ArcadeMachineLocations, SpecialOrderLocations, Museumsanity, \
|
||||
FestivalLocations, ElevatorProgression, BackpackProgression, FarmType
|
||||
from .options import StardewValleyOptions, Craftsanity, Chefsanity, Cooksanity, Shipsanity, Monstersanity
|
||||
from .options.options import BackpackSize, Moviesanity, Eatsanity, IncludeEndgameLocations, Friendsanity
|
||||
from .options.options import BackpackSize, Moviesanity, Eatsanity, IncludeEndgameLocations, Friendsanity, Fishsanity, SkillProgression, Cropsanity
|
||||
from .strings.ap_names.ap_option_names import WalnutsanityOptionName, SecretsanityOptionName, EatsanityOptionName, ChefsanityOptionName, StartWithoutOptionName
|
||||
from .strings.backpack_tiers import Backpack
|
||||
from .strings.goal_names import Goal
|
||||
@@ -665,19 +666,48 @@ def extend_endgame_locations(randomized_locations: List[LocationData], options:
|
||||
|
||||
def extend_filler_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent):
|
||||
days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
||||
i = 1
|
||||
while len(randomized_locations) < 90:
|
||||
location_name = f"Traveling Merchant Sunday Item {i}"
|
||||
while any(location.name == location_name for location in randomized_locations):
|
||||
i += 1
|
||||
location_name = f"Traveling Merchant Sunday Item {i}"
|
||||
number_locations_to_add_per_day = 0
|
||||
min_number_locations = 90 # Under 90 locations we can run out of rooms for the mandatory core items
|
||||
if len(randomized_locations) < min_number_locations:
|
||||
number_locations_to_add = min_number_locations - len(randomized_locations)
|
||||
number_locations_to_add_per_day += math.ceil(number_locations_to_add / 7)
|
||||
|
||||
# These settings generate a lot of empty locations, so they can absorb a lot of items
|
||||
filler_heavy_settings = [options.fishsanity != Fishsanity.option_none,
|
||||
options.shipsanity != Shipsanity.option_none,
|
||||
options.cooksanity != Cooksanity.option_none,
|
||||
options.craftsanity != Craftsanity.option_none,
|
||||
len(options.eatsanity.value) > 0,
|
||||
options.museumsanity == Museumsanity.option_all,
|
||||
options.quest_locations.value >= 0,
|
||||
options.bundle_per_room >= 2]
|
||||
# These settings generate orphan items and can cause too many items, if enabled without a complementary of the filler heavy settings
|
||||
orphan_settings = [len(options.chefsanity.value) > 0,
|
||||
options.friendsanity != Friendsanity.option_none,
|
||||
options.skill_progression == SkillProgression.option_progressive_with_masteries,
|
||||
options.cropsanity != Cropsanity.option_disabled,
|
||||
len(options.start_without.value) > 0,
|
||||
options.bundle_per_room <= -1,
|
||||
options.bundle_per_room <= -2]
|
||||
|
||||
enabled_filler_heavy_settings = len([val for val in filler_heavy_settings if val])
|
||||
enabled_orphan_settings = len([val for val in orphan_settings if val])
|
||||
if enabled_orphan_settings > enabled_filler_heavy_settings:
|
||||
number_locations_to_add_per_day += enabled_orphan_settings - enabled_filler_heavy_settings
|
||||
|
||||
if number_locations_to_add_per_day <= 0:
|
||||
return
|
||||
|
||||
existing_traveling_merchant_locations = [location.name for location in randomized_locations if location.name.startswith("Traveling Merchant Sunday Item ")]
|
||||
start_num_to_add = len(existing_traveling_merchant_locations) + 1
|
||||
|
||||
for i in range(start_num_to_add, start_num_to_add+number_locations_to_add_per_day):
|
||||
logger.debug(f"Player too few locations, adding Traveling Merchant Items #{i}")
|
||||
for day in days:
|
||||
location_name = f"Traveling Merchant {day} Item {i}"
|
||||
randomized_locations.append(location_table[location_name])
|
||||
|
||||
|
||||
|
||||
def create_locations(location_collector: StardewLocationCollector,
|
||||
bundle_rooms: List[BundleRoom],
|
||||
trash_bear_requests: Dict[str, List[str]],
|
||||
|
||||
@@ -7,6 +7,13 @@ from ..items import Group, item_table
|
||||
from ..items.item_data import FILLER_GROUPS
|
||||
|
||||
|
||||
def get_real_item_count(multiworld):
|
||||
number_items = len([item for item in multiworld.itempool
|
||||
if all(filler_group not in item_table[item.name].groups for filler_group in FILLER_GROUPS) and Group.TRAP not in item_table[
|
||||
item.name].groups and (item.classification & ItemClassification.progression)])
|
||||
return number_items
|
||||
|
||||
|
||||
class TestLocationGeneration(SVTestBase):
|
||||
|
||||
def test_all_location_created_are_in_location_table(self):
|
||||
@@ -20,8 +27,7 @@ class TestMinLocationAndMaxItem(SVTestBase):
|
||||
def test_minimal_location_maximal_items_still_valid(self):
|
||||
valid_locations = self.get_real_locations()
|
||||
number_locations = len(valid_locations)
|
||||
number_items = len([item for item in self.multiworld.itempool
|
||||
if all(filler_group not in item_table[item.name].groups for filler_group in FILLER_GROUPS) and Group.TRAP not in item_table[item.name].groups])
|
||||
number_items = get_real_item_count(self.multiworld)
|
||||
print(f"Stardew Valley - Minimum Locations: {number_locations}, Maximum Items: {number_items} [ISLAND EXCLUDED]")
|
||||
self.assertGreaterEqual(number_locations, number_items)
|
||||
|
||||
@@ -32,8 +38,7 @@ class TestMinLocationAndMaxItemWithIsland(SVTestBase):
|
||||
def test_minimal_location_maximal_items_with_island_still_valid(self):
|
||||
valid_locations = self.get_real_locations()
|
||||
number_locations = len(valid_locations)
|
||||
number_items = len([item for item in self.multiworld.itempool
|
||||
if all(filler_group not in item_table[item.name].groups for filler_group in FILLER_GROUPS) and Group.TRAP not in item_table[item.name].groups and (item.classification & ItemClassification.progression)])
|
||||
number_items = get_real_item_count(self.multiworld)
|
||||
print(f"Stardew Valley - Minimum Locations: {number_locations}, Maximum Items: {number_items} [ISLAND INCLUDED]")
|
||||
self.assertGreaterEqual(number_locations, number_items)
|
||||
|
||||
@@ -99,3 +104,5 @@ class TestAllSanityWithModsSettingsHasAllExpectedLocations(SVTestBase):
|
||||
f"\n\tPlease update test_allsanity_with_mods_has_at_least_locations"
|
||||
f"\n\t\tExpected: {expected_locations}"
|
||||
f"\n\t\tActual: {number_locations}")
|
||||
|
||||
|
||||
|
||||
62
worlds/stardew_valley/test/long/TestNumberLocationsLong.py
Normal file
62
worlds/stardew_valley/test/long/TestNumberLocationsLong.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import unittest
|
||||
|
||||
from BaseClasses import ItemClassification
|
||||
from ..assertion import get_all_location_names
|
||||
from ..bases import skip_long_tests, SVTestCase, solo_multiworld
|
||||
from ..options.presets import setting_mins_and_maxes, allsanity_no_mods_7_x_x, get_minsanity_options, default_7_x_x
|
||||
from ...items import Group, item_table
|
||||
from ...items.item_data import FILLER_GROUPS
|
||||
|
||||
if skip_long_tests():
|
||||
raise unittest.SkipTest("Long tests disabled")
|
||||
|
||||
|
||||
def get_real_item_count(multiworld):
|
||||
number_items = len([item for item in multiworld.itempool
|
||||
if all(filler_group not in item_table[item.name].groups for filler_group in FILLER_GROUPS) and Group.TRAP not in item_table[
|
||||
item.name].groups and (item.classification & ItemClassification.progression)])
|
||||
return number_items
|
||||
|
||||
|
||||
class TestCountsPerSetting(SVTestCase):
|
||||
|
||||
def test_items_locations_counts_per_setting_with_ginger_island(self):
|
||||
option_mins_and_maxes = setting_mins_and_maxes()
|
||||
|
||||
for name in option_mins_and_maxes:
|
||||
values = option_mins_and_maxes[name]
|
||||
if not isinstance(values, list):
|
||||
continue
|
||||
with self.subTest(f"{name}"):
|
||||
highest_variance_items = -1
|
||||
highest_variance_locations = -1
|
||||
for preset in [allsanity_no_mods_7_x_x, default_7_x_x, get_minsanity_options]:
|
||||
lowest_items = 9999
|
||||
lowest_locations = 9999
|
||||
highest_items = -1
|
||||
highest_locations = -1
|
||||
for value in values:
|
||||
world_options = preset()
|
||||
world_options[name] = value
|
||||
with solo_multiworld(world_options, world_caching=False) as (multiworld, _):
|
||||
num_locations = len([loc for loc in get_all_location_names(multiworld) if not loc.startswith("Traveling Merchant")])
|
||||
num_items = get_real_item_count(multiworld)
|
||||
if num_items > highest_items:
|
||||
highest_items = num_items
|
||||
if num_items < lowest_items:
|
||||
lowest_items = num_items
|
||||
if num_locations > highest_locations:
|
||||
highest_locations = num_locations
|
||||
if num_locations < lowest_locations:
|
||||
lowest_locations = num_locations
|
||||
|
||||
variance_items = highest_items - lowest_items
|
||||
variance_locations = highest_locations - lowest_locations
|
||||
if variance_locations > highest_variance_locations:
|
||||
highest_variance_locations = variance_locations
|
||||
if variance_items > highest_variance_items:
|
||||
highest_variance_items = variance_items
|
||||
if highest_variance_locations > highest_variance_items:
|
||||
print(f"Options `{name}` can create up to {highest_variance_locations - highest_variance_items} filler ({highest_variance_locations} locations and up to {highest_variance_items} items)")
|
||||
if highest_variance_locations < highest_variance_items:
|
||||
print(f"Options `{name}` can create up to {highest_variance_items - highest_variance_locations} orphan ({highest_variance_locations} locations and up to {highest_variance_items} items)")
|
||||
@@ -292,3 +292,48 @@ def minimal_locations_maximal_items_with_island():
|
||||
min_max_options = minimal_locations_maximal_items()
|
||||
min_max_options.update({options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false})
|
||||
return min_max_options
|
||||
|
||||
|
||||
def setting_mins_and_maxes():
|
||||
low_orphan_options = {
|
||||
options.ArcadeMachineLocations.internal_name: [options.ArcadeMachineLocations.option_disabled, options.ArcadeMachineLocations.option_full_shuffling],
|
||||
options.BackpackProgression.internal_name: [options.BackpackProgression.option_vanilla, options.BackpackProgression.option_progressive],
|
||||
options.BackpackSize.internal_name: [options.BackpackSize.option_1, options.BackpackSize.option_12],
|
||||
options.Booksanity.internal_name: [options.Booksanity.option_none, options.Booksanity.option_power_skill, options.Booksanity.option_power, options.Booksanity.option_all],
|
||||
options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla_cheap,
|
||||
options.BundlePerRoom.internal_name: [options.BundlePerRoom.option_two_fewer, options.BundlePerRoom.option_four_extra],
|
||||
options.BundlePrice.internal_name: options.BundlePrice.option_normal,
|
||||
options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed,
|
||||
options.Chefsanity.internal_name: [options.Chefsanity.preset_none, options.Chefsanity.preset_all],
|
||||
options.Cooksanity.internal_name: [options.Cooksanity.option_none, options.Cooksanity.option_all],
|
||||
options.Craftsanity.internal_name: [options.Craftsanity.option_none, options.Craftsanity.option_all],
|
||||
options.Cropsanity.internal_name: [options.Cropsanity.option_disabled, options.Cropsanity.option_enabled],
|
||||
options.Eatsanity.internal_name: [options.Eatsanity.preset_none, options.Eatsanity.preset_all],
|
||||
options.ElevatorProgression.internal_name: [options.ElevatorProgression.option_vanilla, options.ElevatorProgression.option_progressive],
|
||||
options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all,
|
||||
options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled,
|
||||
options.ExcludeGingerIsland.internal_name: [options.ExcludeGingerIsland.option_false, options.ExcludeGingerIsland.option_true],
|
||||
options.FarmType.internal_name: [options.FarmType.option_standard, options.FarmType.option_meadowlands],
|
||||
options.FestivalLocations.internal_name: [options.FestivalLocations.option_disabled, options.FestivalLocations.option_hard],
|
||||
options.Fishsanity.internal_name: [options.Fishsanity.option_none, options.Fishsanity.option_all],
|
||||
options.Friendsanity.internal_name: [options.Friendsanity.option_none, options.Friendsanity.option_all_with_marriage],
|
||||
options.FriendsanityHeartSize.internal_name: [1, 8],
|
||||
options.Goal.internal_name: options.Goal.option_allsanity,
|
||||
options.IncludeEndgameLocations.internal_name: [options.IncludeEndgameLocations.option_false, options.IncludeEndgameLocations.option_true],
|
||||
options.Mods.internal_name: frozenset(),
|
||||
options.Monstersanity.internal_name: [options.Monstersanity.option_none, options.Monstersanity.option_one_per_monster],
|
||||
options.Moviesanity.internal_name: [options.Moviesanity.option_none, options.Moviesanity.option_all_movies_and_all_loved_snacks],
|
||||
options.Museumsanity.internal_name: [options.Museumsanity.option_none, options.Museumsanity.option_all],
|
||||
options.NumberOfMovementBuffs.internal_name: [0, 12],
|
||||
options.QuestLocations.internal_name: [-1, 56],
|
||||
options.SeasonRandomization.internal_name: [options.SeasonRandomization.option_disabled, options.SeasonRandomization.option_randomized_not_winter],
|
||||
options.Secretsanity.internal_name: [options.Secretsanity.preset_none, options.Secretsanity.preset_all],
|
||||
options.Shipsanity.internal_name: [options.Shipsanity.option_none, options.Shipsanity.option_everything],
|
||||
options.SkillProgression.internal_name: [options.SkillProgression.option_vanilla, options.SkillProgression.option_progressive_with_masteries],
|
||||
options.SpecialOrderLocations.internal_name: [options.SpecialOrderLocations.option_vanilla, options.SpecialOrderLocations.option_board_qi],
|
||||
options.StartWithout.internal_name: [options.StartWithout.preset_none, options.StartWithout.preset_all],
|
||||
options.ToolProgression.internal_name: [options.ToolProgression.option_vanilla, options.ToolProgression.option_progressive],
|
||||
options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium,
|
||||
options.Walnutsanity.internal_name: [options.Walnutsanity.preset_none, options.Walnutsanity.preset_all],
|
||||
}
|
||||
return low_orphan_options
|
||||
|
||||
Reference in New Issue
Block a user