| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | from typing import Dict, Any, Iterable, Optional, Union | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  | from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | from worlds.AutoWorld import World, WebWorld | 
					
						
							|  |  |  | from . import rules, logic, options | 
					
						
							|  |  |  | from .bundles import get_all_bundles, Bundle | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  | from .items import item_table, create_items, ItemData, Group, items_by_group | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | from .locations import location_table, create_locations, LocationData | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  | from .logic import StardewLogic, StardewRule, True_ | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | from .options import stardew_valley_options, StardewOptions, fetch_options | 
					
						
							|  |  |  | from .regions import create_regions | 
					
						
							|  |  |  | from .rules import set_rules | 
					
						
							| 
									
										
										
										
											2023-05-28 19:00:33 -04:00
										 |  |  | from worlds.generic.Rules import set_rule | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | client_version = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StardewLocation(Location): | 
					
						
							|  |  |  |     game: str = "Stardew Valley" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, player: int, name: str, address: Optional[int], parent=None): | 
					
						
							|  |  |  |         super().__init__(player, name, address, parent) | 
					
						
							|  |  |  |         self.event = not address | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StardewItem(Item): | 
					
						
							|  |  |  |     game: str = "Stardew Valley" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StardewWebWorld(WebWorld): | 
					
						
							|  |  |  |     theme = "dirt" | 
					
						
							|  |  |  |     bug_report_page = "https://github.com/agilbert1412/StardewArchipelago/issues/new?labels=bug&title=%5BBug%5D%3A+Brief+Description+of+bug+here" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     tutorials = [Tutorial( | 
					
						
							|  |  |  |         "Multiworld Setup Guide", | 
					
						
							|  |  |  |         "A guide to playing Stardew Valley with Archipelago.", | 
					
						
							|  |  |  |         "English", | 
					
						
							|  |  |  |         "setup_en.md", | 
					
						
							|  |  |  |         "setup/en", | 
					
						
							|  |  |  |         ["KaitoKid", "Jouramie"] | 
					
						
							|  |  |  |     )] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StardewValleyWorld(World): | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-03-30 10:25:25 -04:00
										 |  |  |     Stardew Valley is an open-ended country-life RPG. You can farm, fish, mine, fight, complete quests, | 
					
						
							|  |  |  |     befriend villagers, and uncover dark secrets. | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     game = "Stardew Valley" | 
					
						
							|  |  |  |     option_definitions = stardew_valley_options | 
					
						
							|  |  |  |     topology_present = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_name_to_id = {name: data.code for name, data in item_table.items()} | 
					
						
							|  |  |  |     location_name_to_id = {name: data.code for name, data in location_table.items()} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |     data_version = 2 | 
					
						
							|  |  |  |     required_client_version = (0, 4, 0) | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     options: StardewOptions | 
					
						
							|  |  |  |     logic: StardewLogic | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     web = StardewWebWorld() | 
					
						
							|  |  |  |     modified_bundles: Dict[str, Bundle] | 
					
						
							|  |  |  |     randomized_entrances: Dict[str, str] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_early(self): | 
					
						
							|  |  |  |         self.options = fetch_options(self.multiworld, self.player) | 
					
						
							|  |  |  |         self.logic = StardewLogic(self.player, self.options) | 
					
						
							|  |  |  |         self.modified_bundles = get_all_bundles(self.multiworld.random, | 
					
						
							|  |  |  |                                                 self.logic, | 
					
						
							|  |  |  |                                                 self.options[options.BundleRandomization], | 
					
						
							|  |  |  |                                                 self.options[options.BundlePrice]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_regions(self): | 
					
						
							|  |  |  |         def create_region(name: str, exits: Iterable[str]) -> Region: | 
					
						
							|  |  |  |             region = Region(name, self.player, self.multiworld) | 
					
						
							|  |  |  |             region.exits = [Entrance(self.player, exit_name, region) for exit_name in exits] | 
					
						
							|  |  |  |             return region | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         world_regions, self.randomized_entrances = create_regions(create_region, self.multiworld.random, self.options) | 
					
						
							|  |  |  |         self.multiworld.regions.extend(world_regions) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def add_location(name: str, code: Optional[int], region: str): | 
					
						
							|  |  |  |             region = self.multiworld.get_region(region, self.player) | 
					
						
							|  |  |  |             location = StardewLocation(self.player, name, code, region) | 
					
						
							|  |  |  |             location.access_rule = lambda _: True | 
					
						
							|  |  |  |             region.locations.append(location) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         create_locations(add_location, self.options, self.multiworld.random) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_items(self): | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         self.precollect_starting_season() | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |         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)] | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled: | 
					
						
							|  |  |  |             items_to_exclude = [item for item in items_to_exclude | 
					
						
							|  |  |  |                                 if item_table[item.name] not in items_by_group[Group.SEASON]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         locations_count = len([location | 
					
						
							|  |  |  |                                for location in self.multiworld.get_locations(self.player) | 
					
						
							|  |  |  |                                if not location.event]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         created_items = create_items(self.create_item, locations_count, items_to_exclude, self.options, | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |                                      self.multiworld.random) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         self.multiworld.itempool += created_items | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         self.setup_early_items() | 
					
						
							|  |  |  |         self.setup_month_events() | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |         self.setup_victory() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |     def precollect_starting_season(self) -> Optional[StardewItem]: | 
					
						
							|  |  |  |         if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         season_pool = items_by_group[Group.SEASON] | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled: | 
					
						
							|  |  |  |             for season in season_pool: | 
					
						
							|  |  |  |                 self.multiworld.push_precollected(self.create_item(season)) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if [item for item in self.multiworld.precollected_items[self.player] | 
					
						
							|  |  |  |             if item.name in {season.name for season in items_by_group[Group.SEASON]}]: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_randomized_not_winter: | 
					
						
							|  |  |  |             season_pool = [season for season in season_pool if season.name != "Winter"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         starting_season = self.create_item(self.multiworld.random.choice(season_pool)) | 
					
						
							|  |  |  |         self.multiworld.push_precollected(starting_season) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def setup_early_items(self): | 
					
						
							|  |  |  |         if (self.options[options.BuildingProgression] == | 
					
						
							|  |  |  |                 options.BuildingProgression.option_progressive_early_shipping_bin): | 
					
						
							|  |  |  |             self.multiworld.early_items[self.player]["Shipping Bin"] = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive: | 
					
						
							|  |  |  |             self.multiworld.early_items[self.player]["Progressive Backpack"] = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def setup_month_events(self): | 
					
						
							|  |  |  |         for i in range(0, 8): | 
					
						
							|  |  |  |             month_end = LocationData(None, "Stardew Valley", f"Month End {i + 1}") | 
					
						
							|  |  |  |             if i == 0: | 
					
						
							|  |  |  |                 self.create_event_location(month_end, True_(), "Month End") | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |             self.create_event_location(month_end, self.logic.received("Month End", i).simplify(), "Month End") | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def setup_victory(self): | 
					
						
							|  |  |  |         if self.options[options.Goal] == options.Goal.option_community_center: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Complete Community Center"], | 
					
						
							|  |  |  |                                        self.logic.can_complete_community_center().simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							|  |  |  |         elif self.options[options.Goal] == options.Goal.option_grandpa_evaluation: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Succeed Grandpa's Evaluation"], | 
					
						
							|  |  |  |                                        self.logic.can_finish_grandpa_evaluation().simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							|  |  |  |         elif self.options[options.Goal] == options.Goal.option_bottom_of_the_mines: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Reach the Bottom of The Mines"], | 
					
						
							|  |  |  |                                        self.logic.can_mine_to_floor(120).simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							|  |  |  |         elif self.options[options.Goal] == options.Goal.option_cryptic_note: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Complete Quest Cryptic Note"], | 
					
						
							|  |  |  |                                        self.logic.can_complete_quest("Cryptic Note").simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							|  |  |  |         elif self.options[options.Goal] == options.Goal.option_master_angler: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Catch Every Fish"], | 
					
						
							|  |  |  |                                        self.logic.can_catch_every_fish().simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         elif self.options[options.Goal] == options.Goal.option_complete_collection: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Complete the Museum Collection"], | 
					
						
							|  |  |  |                                        self.logic.can_complete_museum().simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							|  |  |  |         elif self.options[options.Goal] == options.Goal.option_full_house: | 
					
						
							|  |  |  |             self.create_event_location(location_table["Full House"], | 
					
						
							|  |  |  |                                        self.logic.can_have_two_children().simplify(), | 
					
						
							|  |  |  |                                        "Victory") | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |     def create_item(self, item: Union[str, ItemData]) -> StardewItem: | 
					
						
							|  |  |  |         if isinstance(item, str): | 
					
						
							|  |  |  |             item = item_table[item] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return StardewItem(item.name, item.classification, item.code, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_event_location(self, location_data: LocationData, rule: StardewRule, item: Optional[str] = None): | 
					
						
							|  |  |  |         if item is None: | 
					
						
							|  |  |  |             item = location_data.name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |         region = self.multiworld.get_region(location_data.region, self.player) | 
					
						
							|  |  |  |         location = StardewLocation(self.player, location_data.name, None, region) | 
					
						
							|  |  |  |         location.access_rule = rule | 
					
						
							|  |  |  |         region.locations.append(location) | 
					
						
							|  |  |  |         location.place_locked_item(self.create_item(item)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |     def set_rules(self): | 
					
						
							|  |  |  |         set_rules(self.multiworld, self.player, self.options, self.logic, self.modified_bundles) | 
					
						
							|  |  |  |         self.force_first_month_once_all_early_items_are_found() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def force_first_month_once_all_early_items_are_found(self): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         The Fill algorithm sweeps all event when calculating the early location. This causes an issue where | 
					
						
							|  |  |  |         location only locked behind event are considered early, which they are not really... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         This patches the issue, by adding a dependency to the first month end on all early items, so all the locations | 
					
						
							|  |  |  |         that depends on it will not be considered early. This requires at least one early item to be progression, or | 
					
						
							|  |  |  |         it just won't work... | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         early_items = [] | 
					
						
							|  |  |  |         for player, item_count in self.multiworld.early_items.items(): | 
					
						
							|  |  |  |             for item, count in item_count.items(): | 
					
						
							|  |  |  |                 if self.multiworld.worlds[player].create_item(item).advancement: | 
					
						
							|  |  |  |                     early_items.append((player, item, count)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for item, count in self.multiworld.local_early_items[self.player].items(): | 
					
						
							|  |  |  |             if self.create_item(item).advancement: | 
					
						
							|  |  |  |                 early_items.append((self.player, item, count)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def first_month_require_all_early_items(state: CollectionState) -> bool: | 
					
						
							|  |  |  |             for player, item, count in early_items: | 
					
						
							|  |  |  |                 if not state.has(item, player, count): | 
					
						
							|  |  |  |                     return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         first_month_end = self.multiworld.get_location("Month End 1", self.player) | 
					
						
							|  |  |  |         set_rule(first_month_end, first_month_require_all_early_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_basic(self): | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |         return "Joja Cola" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def fill_slot_data(self) -> Dict[str, Any]: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         modified_bundles = {} | 
					
						
							|  |  |  |         for bundle_key in self.modified_bundles: | 
					
						
							|  |  |  |             key, value = self.modified_bundles[bundle_key].to_pair() | 
					
						
							|  |  |  |             modified_bundles[key] = value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |         excluded_options = [options.ResourcePackMultiplier, options.BundleRandomization, options.BundlePrice, | 
					
						
							|  |  |  |                             options.NumberOfPlayerBuffs] | 
					
						
							|  |  |  |         slot_data = dict(self.options.options) | 
					
						
							|  |  |  |         for option in excluded_options: | 
					
						
							|  |  |  |             slot_data.pop(option.internal_name) | 
					
						
							|  |  |  |         slot_data.update({ | 
					
						
							| 
									
										
										
										
											2023-02-26 19:19:15 -05:00
										 |  |  |             "seed": self.multiworld.per_slot_randoms[self.player].randrange(1000000000),  # Seed should be max 9 digits | 
					
						
							|  |  |  |             "randomized_entrances": self.randomized_entrances, | 
					
						
							| 
									
										
										
										
											2023-04-10 19:44:59 -04:00
										 |  |  |             "modified_bundles": modified_bundles, | 
					
						
							|  |  |  |             "client_version": "3.0.0", | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return slot_data |