Stardew Valley: 4.x.x - The Ginger Update (#1931)

## What is this fixing or adding?
Major content update for Stardew Valley

## How was this tested?
One large-scale public Beta on the archipelago server, plus several smaller private asyncs and test runs

You can go to https://github.com/agilbert1412/StardewArchipelago/releases to grab the mod (latest 4.x.x version), the supported mods and the apworld, to test this PR

## New Features:
- Festival Checks [Easy mode or Hard Mode]
- Special Orders [Both Board and Qi]
- Willy's Boat
- Ginger Island Parrots
- TV Channels
- Trap Items [Available in various difficulty levels]
- Entrance Randomizer: Buildings and Chaos
- New Fishsanity options: Exclude Legendaries, Exclude Hard fish, Only easy fish
- Resource Pack overhaul [Resource packs are now more enjoyable and varied]
- Goal: Greatest Walnut Hunter [Find every single Golden Walnut]
- Goal: Perfection [Achieve Perfection]
- Option: Profit Margin [Multiplier over all earnings]
- Option: Friendsanity Heart Size [Reduce clutter from friendsanity hearts]
- Option: Exclude Ginger Island - will exclude many locations and items to generate a playthrough that does not go to the island
- Mod Support [Curated list of mods]

## New Contributors:
@Witchybun for the mod support

---------

Co-authored-by: Witchybun <embenham05@gmail.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
This commit is contained in:
agilbert1412
2023-07-19 14:26:38 -04:00
committed by GitHub
parent 1f6db12797
commit 62657df3fb
94 changed files with 8320 additions and 3104 deletions

View File

@@ -8,6 +8,8 @@ from . import options, data
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 .strings.goal_names import Goal
from .strings.region_names import Region
LOCATION_CODE_OFFSET = 717000
@@ -32,7 +34,8 @@ class LocationTags(enum.Enum):
TRASH_CAN_UPGRADE = enum.auto()
FISHING_ROD_UPGRADE = enum.auto()
THE_MINES_TREASURE = enum.auto()
THE_MINES_ELEVATOR = enum.auto()
CROPSANITY = enum.auto()
ELEVATOR = enum.auto()
SKILL_LEVEL = enum.auto()
FARMING_LEVEL = enum.auto()
FISHING_LEVEL = enum.auto()
@@ -51,6 +54,19 @@ class LocationTags(enum.Enum):
MUSEUM_MILESTONES = enum.auto()
MUSEUM_DONATIONS = enum.auto()
FRIENDSANITY = enum.auto()
FESTIVAL = enum.auto()
FESTIVAL_HARD = enum.auto()
SPECIAL_ORDER_BOARD = enum.auto()
SPECIAL_ORDER_QI = enum.auto()
GINGER_ISLAND = enum.auto()
WALNUT_PURCHASE = enum.auto()
# Skill Mods
LUCK_LEVEL = enum.auto()
BINNING_LEVEL = enum.auto()
COOKING_LEVEL = enum.auto()
SOCIALIZING_LEVEL = enum.auto()
MAGIC_LEVEL = enum.auto()
ARCHAEOLOGY_LEVEL = enum.auto()
@dataclass(frozen=True)
@@ -58,6 +74,7 @@ class LocationData:
code_without_offset: Optional[int]
region: str
name: str
mod_name: Optional[str] = None
tags: FrozenSet[LocationTags] = frozenset()
@property
@@ -81,6 +98,7 @@ def load_location_csv() -> List[LocationData]:
return [LocationData(int(location["id"]) if location["id"] else None,
location["region"],
location["name"],
str(location["mod_name"]) if location["mod_name"] else None,
frozenset(LocationTags[group]
for group in location["tags"].split(",")
if group))
@@ -88,13 +106,15 @@ def load_location_csv() -> List[LocationData]:
events_locations = [
LocationData(None, "Stardew Valley", "Succeed Grandpa's Evaluation"),
LocationData(None, "Community Center", "Complete Community Center"),
LocationData(None, "The Mines - Floor 120", "Reach the Bottom of The Mines"),
LocationData(None, "Skull Cavern", "Complete Quest Cryptic Note"),
LocationData(None, "Stardew Valley", "Catch Every Fish"),
LocationData(None, "Stardew Valley", "Complete the Museum Collection"),
LocationData(None, "Stardew Valley", "Full House"),
LocationData(None, Region.farm_house, Goal.grandpa_evaluation),
LocationData(None, Region.community_center, Goal.community_center),
LocationData(None, Region.mines_floor_120, Goal.bottom_of_the_mines),
LocationData(None, Region.skull_cavern_100, Goal.cryptic_note),
LocationData(None, Region.farm, Goal.master_angler),
LocationData(None, Region.museum, Goal.complete_museum),
LocationData(None, Region.farm_house, Goal.full_house),
LocationData(None, Region.island_west, Goal.greatest_walnut_hunter),
LocationData(None, Region.qi_walnut_room, Goal.perfection),
]
all_locations = load_location_csv() + events_locations
@@ -113,6 +133,15 @@ def initialize_groups():
initialize_groups()
def extend_cropsanity_locations(randomized_locations: List[LocationData], world_options):
if world_options[options.Cropsanity] == options.Cropsanity.option_disabled:
return
cropsanity_locations = locations_by_tag[LocationTags.CROPSANITY]
cropsanity_locations = filter_ginger_island(world_options, cropsanity_locations)
randomized_locations.extend(cropsanity_locations)
def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_number_of_quests: int):
for i in range(0, desired_number_of_quests):
batch = i // 7
@@ -128,19 +157,29 @@ 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], fishsanity: int, random: Random):
def extend_fishsanity_locations(randomized_locations: List[LocationData], world_options, random: Random):
prefix = "Fishsanity: "
if fishsanity == options.Fishsanity.option_none:
if world_options[options.Fishsanity] == options.Fishsanity.option_none:
return
elif fishsanity == options.Fishsanity.option_legendaries:
elif world_options[options.Fishsanity] == options.Fishsanity.option_legendaries:
randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish)
elif fishsanity == options.Fishsanity.option_special:
elif world_options[options.Fishsanity] == options.Fishsanity.option_special:
randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish)
elif fishsanity == options.Fishsanity.option_randomized:
randomized_locations.extend(location_table[f"{prefix}{fish.name}"]
for fish in all_fish if random.random() < 0.4)
elif fishsanity == options.Fishsanity.option_all:
randomized_locations.extend(location_table[f"{prefix}{fish.name}"] for fish in all_fish)
elif world_options[options.Fishsanity] == options.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:
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:
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:
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:
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))
def extend_museumsanity_locations(randomized_locations: List[LocationData], museumsanity: int, random: Random):
@@ -156,26 +195,98 @@ def extend_museumsanity_locations(randomized_locations: List[LocationData], muse
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], friendsanity: int):
if friendsanity == options.Friendsanity.option_none:
def extend_friendsanity_locations(randomized_locations: List[LocationData], world_options: options.StardewOptions):
if world_options[options.Friendsanity] == options.Friendsanity.option_none:
return
exclude_non_bachelors = friendsanity == options.Friendsanity.option_bachelors
exclude_locked_villagers = friendsanity == options.Friendsanity.option_starting_npcs or \
friendsanity == options.Friendsanity.option_bachelors
exclude_post_marriage_hearts = friendsanity != options.Friendsanity.option_all_with_marriage
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]
for villager in all_villagers:
if villager.mod_name not in world_options[options.Mods] and villager.mod_name is not None:
continue
if not villager.available and exclude_locked_villagers:
continue
if not villager.bachelor and exclude_non_bachelors:
continue
if villager.name == "Leo" and exclude_leo:
continue
heart_cap = 8 if villager.bachelor else 10
if include_post_marriage_hearts and villager.bachelor:
heart_cap = 14
for heart in range(1, 15):
if villager.bachelor and exclude_post_marriage_hearts and heart > 8:
continue
if villager.bachelor or heart < 11:
if heart > heart_cap:
break
if heart % heart_size == 0 or heart == heart_cap:
randomized_locations.append(location_table[f"Friendsanity: {villager.name} {heart} <3"])
if not exclude_non_bachelors:
for heart in range(1, 6):
randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"])
if heart % heart_size == 0 or heart == 5:
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:
return
festival_locations = locations_by_tag[LocationTags.FESTIVAL]
randomized_locations.extend(festival_locations)
extend_hard_festival_locations(randomized_locations, festival_option)
def extend_hard_festival_locations(randomized_locations, festival_option: int):
if festival_option != options.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:
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])
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
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:
return
randomized_locations.append(location_table["Repair Ticket Machine"])
randomized_locations.append(location_table["Repair Boat Hull"])
randomized_locations.append(location_table["Repair Boat Anchor"])
randomized_locations.append(location_table["Open Professor Snail Cave"])
randomized_locations.append(location_table["Complete Island Field Office"])
randomized_locations.extend(locations_by_tag[LocationTags.WALNUT_PURCHASE])
def extend_mandatory_locations(randomized_locations: List[LocationData], world_options):
mandatory_locations = [location for location in locations_by_tag[LocationTags.MANDATORY]]
filtered_mandatory_locations = filter_disabled_locations(world_options, mandatory_locations)
randomized_locations.extend(filtered_mandatory_locations)
def extend_backpack_locations(randomized_locations: List[LocationData], world_options):
backpack_locations = [location for location in locations_by_tag[LocationTags.BACKPACK]]
filtered_backpack_locations = filter_modded_locations(world_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:
return
elevator_locations = [location for location in locations_by_tag[LocationTags.ELEVATOR]]
filtered_elevator_locations = filter_modded_locations(world_options, elevator_locations)
randomized_locations.extend(filtered_elevator_locations)
def create_locations(location_collector: StardewLocationCollector,
@@ -183,33 +294,55 @@ def create_locations(location_collector: StardewLocationCollector,
random: Random):
randomized_locations = []
randomized_locations.extend(locations_by_tag[LocationTags.MANDATORY])
if not world_options[options.BackpackProgression] == options.BackpackProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.BACKPACK])
extend_mandatory_locations(randomized_locations, world_options)
extend_backpack_locations(randomized_locations, world_options)
if not world_options[options.ToolProgression] == options.ToolProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE])
if not world_options[options.TheMinesElevatorsProgression] == options.TheMinesElevatorsProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.THE_MINES_ELEVATOR])
extend_elevator_locations(randomized_locations, world_options)
if not world_options[options.SkillProgression] == options.SkillProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.SKILL_LEVEL])
for location in locations_by_tag[LocationTags.SKILL_LEVEL]:
if location.mod_name is None or location.mod_name in world_options[options.Mods]:
randomized_locations.append(location_table[location.name])
if not world_options[options.BuildingProgression] == options.BuildingProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.BUILDING_BLUEPRINT])
for location in locations_by_tag[LocationTags.BUILDING_BLUEPRINT]:
if location.mod_name is None or location.mod_name in world_options[options.Mods]:
randomized_locations.append(location_table[location.name])
if not world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_disabled:
if world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_disabled:
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE_VICTORY])
if world_options[options.ArcadeMachineLocations] == options.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[options.Fishsanity], random)
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[options.Friendsanity])
extend_friendsanity_locations(randomized_locations, world_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)
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
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]
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)
return locations_second_pass