Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stardew Valley: Fix a bug where walnutsanity would get deactivated even tho ginger island got forced activated (and move some files) #4311

40 changes: 7 additions & 33 deletions worlds/stardew_valley/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Dict, Any, Iterable, Optional, Union, List, TextIO

from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState
from Options import PerGameCommonOptions, Accessibility
from Options import PerGameCommonOptions
from worlds.AutoWorld import World, WebWorld
from . import rules
from .bundles.bundle_room import BundleRoom
Expand All @@ -15,10 +15,11 @@
from .logic.bundle_logic import BundleLogic
from .logic.logic import StardewLogic
from .logic.time_logic import MAX_MONTHS
from .option_groups import sv_option_groups
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, EnabledFillerBuffs, NumberOfMovementBuffs, \
BackpackProgression, BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType, Walnutsanity
from .presets import sv_options_presets
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, EnabledFillerBuffs, NumberOfMovementBuffs, \
BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType, Walnutsanity
from .options.forced_options import force_change_options_if_incompatible
from .options.option_groups import sv_option_groups
from .options.presets import sv_options_presets
from .regions import create_regions
from .rules import set_rules
from .stardew_rule import True_, StardewRule, HasProgressionPercent, true_
Expand Down Expand Up @@ -112,36 +113,9 @@ def interpret_slot_data(self, slot_data: Dict[str, Any]) -> Optional[int]:
return seed

def generate_early(self):
self.force_change_options_if_incompatible()
force_change_options_if_incompatible(self.options, self.player, self.player_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this doesn't just pass the world and you grab the options, player, and name off that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes it easier to test specifically that options were changed without creating a complete world. We've often been rightfully criticized for generating too many worlds just to test small things that could be unit tested, so I'm slowly trying to address that.

See in TestForcedOptions.py

self.content = create_content(self.options)

def force_change_options_if_incompatible(self):
goal_is_walnut_hunter = self.options.goal == Goal.option_greatest_walnut_hunter
goal_is_perfection = self.options.goal == Goal.option_perfection
goal_is_island_related = goal_is_walnut_hunter or goal_is_perfection
exclude_ginger_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true

if goal_is_island_related and exclude_ginger_island:
self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false
goal_name = self.options.goal.current_key
logger.warning(
f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({self.player_name})")

if exclude_ginger_island and self.options.walnutsanity != Walnutsanity.preset_none:
self.options.walnutsanity.value = Walnutsanity.preset_none
logger.warning(
f"Walnutsanity requires Ginger Island. Ginger Island was excluded from {self.player} ({self.player_name})'s world, so walnutsanity was force disabled")

if goal_is_perfection and self.options.accessibility == Accessibility.option_minimal:
self.options.accessibility.value = Accessibility.option_full
logger.warning(
f"Goal 'Perfection' requires full accessibility. Accessibility setting forced to 'Full' for player {self.player} ({self.player_name})")

elif self.options.goal == Goal.option_allsanity and self.options.accessibility == Accessibility.option_minimal:
self.options.accessibility.value = Accessibility.option_full
logger.warning(
f"Goal 'Allsanity' requires full accessibility. Accessibility setting forced to 'Full' for player {self.player} ({self.player_name})")

def create_regions(self):
def create_region(name: str, exits: Iterable[str]) -> Region:
region = Region(name, self.player, self.multiworld)
Expand Down
32 changes: 16 additions & 16 deletions worlds/stardew_valley/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .options import StardewValleyOptions, TrapItems, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Museumsanity, \
BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \
Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, EnabledFillerBuffs
from .strings.ap_names.ap_option_names import OptionName
from .strings.ap_names.ap_option_names import BuffOptionName, WalnutsanityOptionName
from .strings.ap_names.ap_weapon_names import APWeapon
from .strings.ap_names.buff_names import Buff
from .strings.ap_names.community_upgrade_names import CommunityUpgrade
Expand Down Expand Up @@ -538,16 +538,16 @@ def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptio
num_penta_walnuts = 1
# https://stardewvalleywiki.com/Golden_Walnut
# Totals should be accurate, but distribution is slightly offset to make room for baseline walnuts
if OptionName.walnutsanity_puzzles in walnutsanity: # 61
if WalnutsanityOptionName.puzzles in walnutsanity: # 61
num_single_walnuts += 6 # 6
num_triple_walnuts += 5 # 15
num_penta_walnuts += 8 # 40
if OptionName.walnutsanity_bushes in walnutsanity: # 25
if WalnutsanityOptionName.bushes in walnutsanity: # 25
num_single_walnuts += 16 # 16
num_triple_walnuts += 3 # 9
if OptionName.walnutsanity_dig_spots in walnutsanity: # 18
if WalnutsanityOptionName.dig_spots in walnutsanity: # 18
num_single_walnuts += 18 # 18
if OptionName.walnutsanity_repeatables in walnutsanity: # 33
if WalnutsanityOptionName.repeatables in walnutsanity: # 33
num_single_walnuts += 30 # 30
num_triple_walnuts += 1 # 3

Expand Down Expand Up @@ -833,27 +833,27 @@ def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool) -> Li

def get_allowed_player_buffs(buff_option: EnabledFillerBuffs) -> List[ItemData]:
allowed_buffs = []
if OptionName.buff_luck in buff_option:
if BuffOptionName.luck in buff_option:
allowed_buffs.append(item_table[Buff.luck])
if OptionName.buff_damage in buff_option:
if BuffOptionName.damage in buff_option:
allowed_buffs.append(item_table[Buff.damage])
if OptionName.buff_defense in buff_option:
if BuffOptionName.defense in buff_option:
allowed_buffs.append(item_table[Buff.defense])
if OptionName.buff_immunity in buff_option:
if BuffOptionName.immunity in buff_option:
allowed_buffs.append(item_table[Buff.immunity])
if OptionName.buff_health in buff_option:
if BuffOptionName.health in buff_option:
allowed_buffs.append(item_table[Buff.health])
if OptionName.buff_energy in buff_option:
if BuffOptionName.energy in buff_option:
allowed_buffs.append(item_table[Buff.energy])
if OptionName.buff_bite in buff_option:
if BuffOptionName.bite in buff_option:
allowed_buffs.append(item_table[Buff.bite_rate])
if OptionName.buff_fish_trap in buff_option:
if BuffOptionName.fish_trap in buff_option:
allowed_buffs.append(item_table[Buff.fish_trap])
if OptionName.buff_fishing_bar in buff_option:
if BuffOptionName.fishing_bar in buff_option:
allowed_buffs.append(item_table[Buff.fishing_bar])
if OptionName.buff_quality in buff_option:
if BuffOptionName.quality in buff_option:
allowed_buffs.append(item_table[Buff.quality])
if OptionName.buff_glow in buff_option:
if BuffOptionName.glow in buff_option:
allowed_buffs.append(item_table[Buff.glow])
return allowed_buffs

Expand Down
22 changes: 11 additions & 11 deletions worlds/stardew_valley/logic/walnut_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from ..strings.ap_names.event_names import Event
from ..options import ExcludeGingerIsland, Walnutsanity
from ..stardew_rule import StardewRule, False_, True_
from ..strings.ap_names.ap_option_names import OptionName
from ..strings.ap_names.ap_option_names import WalnutsanityOptionName
from ..strings.ap_names.event_names import Event
from ..strings.craftable_names import Furniture
from ..strings.crop_names import Fruit
from ..strings.metal_names import Mineral, Fossil
Expand All @@ -25,7 +25,7 @@ def __init__(self, *args, **kwargs):


class WalnutLogic(BaseLogic[Union[WalnutLogicMixin, ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, CombatLogicMixin,
AbilityLogicMixin]]):
AbilityLogicMixin]]):

def has_walnut(self, number: int) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
Expand All @@ -44,22 +44,22 @@ def has_walnut(self, number: int) -> StardewRule:
total_walnuts = puzzle_walnuts + bush_walnuts + dig_walnuts + repeatable_walnuts
walnuts_to_receive = 0
walnuts_to_collect = number
if OptionName.walnutsanity_puzzles in self.options.walnutsanity:
if WalnutsanityOptionName.puzzles in self.options.walnutsanity:
puzzle_walnut_rate = puzzle_walnuts / total_walnuts
puzzle_walnuts_required = round(puzzle_walnut_rate * number)
walnuts_to_receive += puzzle_walnuts_required
walnuts_to_collect -= puzzle_walnuts_required
if OptionName.walnutsanity_bushes in self.options.walnutsanity:
if WalnutsanityOptionName.bushes in self.options.walnutsanity:
bush_walnuts_rate = bush_walnuts / total_walnuts
bush_walnuts_required = round(bush_walnuts_rate * number)
walnuts_to_receive += bush_walnuts_required
walnuts_to_collect -= bush_walnuts_required
if OptionName.walnutsanity_dig_spots in self.options.walnutsanity:
if WalnutsanityOptionName.dig_spots in self.options.walnutsanity:
dig_walnuts_rate = dig_walnuts / total_walnuts
dig_walnuts_required = round(dig_walnuts_rate * number)
walnuts_to_receive += dig_walnuts_required
walnuts_to_collect -= dig_walnuts_required
if OptionName.walnutsanity_repeatables in self.options.walnutsanity:
if WalnutsanityOptionName.repeatables in self.options.walnutsanity:
repeatable_walnuts_rate = repeatable_walnuts / total_walnuts
repeatable_walnuts_required = round(repeatable_walnuts_rate * number)
walnuts_to_receive += repeatable_walnuts_required
Expand Down Expand Up @@ -104,9 +104,9 @@ def can_get_walnuts(self, number: int) -> StardewRule:
return reach_entire_island
gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz)
return reach_entire_island & self.logic.has(Fruit.banana) & self.logic.has_all(*gems) & \
self.logic.ability.can_mine_perfectly() & self.logic.ability.can_fish_perfectly() & \
self.logic.has(Furniture.flute_block) & self.logic.has(Seed.melon) & self.logic.has(Seed.wheat) & \
self.logic.has(Seed.garlic) & self.can_complete_field_office()
self.logic.ability.can_mine_perfectly() & self.logic.ability.can_fish_perfectly() & \
self.logic.has(Furniture.flute_block) & self.logic.has(Seed.melon) & self.logic.has(Seed.wheat) & \
self.logic.has(Seed.garlic) & self.can_complete_field_office()

@cached_property
def can_start_field_office(self) -> StardewRule:
Expand All @@ -132,4 +132,4 @@ def can_complete_bat_collection(self) -> StardewRule:

def can_complete_field_office(self) -> StardewRule:
return self.can_complete_large_animal_collection() & self.can_complete_snake_collection() & \
self.can_complete_frog_collection() & self.can_complete_bat_collection()
self.can_complete_frog_collection() & self.can_complete_bat_collection()
76 changes: 0 additions & 76 deletions worlds/stardew_valley/option_groups.py

This file was deleted.

6 changes: 6 additions & 0 deletions worlds/stardew_valley/options/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .options import StardewValleyOption, Goal, FarmType, StartingMoney, ProfitMargin, BundleRandomization, BundlePrice, EntranceRandomization, \
SeasonRandomization, Cropsanity, BackpackProgression, ToolProgression, ElevatorProgression, SkillProgression, BuildingProgression, FestivalLocations, \
ArcadeMachineLocations, SpecialOrderLocations, QuestLocations, Fishsanity, Museumsanity, Monstersanity, Shipsanity, Cooksanity, Chefsanity, Craftsanity, \
Friendsanity, FriendsanityHeartSize, Booksanity, Walnutsanity, NumberOfMovementBuffs, EnabledFillerBuffs, ExcludeGingerIsland, TrapItems, \
MultipleDaySleepEnabled, MultipleDaySleepCost, ExperienceMultiplier, FriendshipMultiplier, DebrisMultiplier, QuickStart, Gifting, Mods, BundlePlando, \
StardewValleyOptions
48 changes: 48 additions & 0 deletions worlds/stardew_valley/options/forced_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import logging

import Options as ap_options
from . import options

logger = logging.getLogger(__name__)


def force_change_options_if_incompatible(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None:
force_ginger_island_inclusion_when_goal_is_ginger_island_related(world_options, player, player_name)
force_walnutsanity_deactivation_when_ginger_island_is_excluded(world_options, player, player_name)
force_accessibility_to_full_when_goal_requires_all_locations(player, player_name, world_options)


def force_ginger_island_inclusion_when_goal_is_ginger_island_related(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None:
goal_is_walnut_hunter = world_options.goal == options.Goal.option_greatest_walnut_hunter
goal_is_perfection = world_options.goal == options.Goal.option_perfection
goal_is_island_related = goal_is_walnut_hunter or goal_is_perfection
ginger_island_is_excluded = world_options.exclude_ginger_island == options.ExcludeGingerIsland.option_true

if goal_is_island_related and ginger_island_is_excluded:
world_options.exclude_ginger_island.value = options.ExcludeGingerIsland.option_false
goal_name = world_options.goal.current_option_name
logger.warning(f"Goal '{goal_name}' requires Ginger Island. "
f"Exclude Ginger Island option forced to 'False' for player {player} ({player_name})")


def force_walnutsanity_deactivation_when_ginger_island_is_excluded(world_options: options.StardewValleyOptions, player: int, player_name: str):
ginger_island_is_excluded = world_options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
walnutsanity_is_active = world_options.walnutsanity != options.Walnutsanity.preset_none

if ginger_island_is_excluded and walnutsanity_is_active:
world_options.walnutsanity.value = options.Walnutsanity.preset_none
logger.warning(f"Walnutsanity requires Ginger Island. "
f"Ginger Island was excluded from {player} ({player_name})'s world, so walnutsanity was force disabled")


def force_accessibility_to_full_when_goal_requires_all_locations(player, player_name, world_options):
goal_is_allsanity = world_options.goal == options.Goal.option_allsanity
goal_is_perfection = world_options.goal == options.Goal.option_perfection
goal_requires_all_locations = goal_is_allsanity or goal_is_perfection
accessibility_is_minimal = world_options.accessibility == ap_options.Accessibility.option_minimal

if goal_requires_all_locations and accessibility_is_minimal:
world_options.accessibility.value = ap_options.Accessibility.option_full
goal_name = world_options.goal.current_option_name
logger.warning(f"Goal '{goal_name}' requires full accessibility. "
f"Accessibility option forced to 'Full' for player {player} ({player_name})")
Loading
Loading