631 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			631 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from enum import IntEnum
 | |
| from typing import Any, List, Dict, Set, Callable, Optional, TextIO
 | |
| 
 | |
| from BaseClasses import ItemClassification, CollectionState, Region, Entrance, Location, Tutorial, LocationProgressType
 | |
| from worlds.AutoWorld import World, WebWorld
 | |
| 
 | |
| from .Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, Overcooked2GenericLevel
 | |
| from .Locations import Overcooked2Location, oc2_location_name_to_id, oc2_location_id_to_name
 | |
| from .Options import OC2Options, OC2OnToggle, LocationBalancingMode, DeathLinkMode
 | |
| from .Items import item_table, Overcooked2Item, item_name_to_id, item_id_to_name, item_to_unlock_event, item_frequencies, dlc_exclusives
 | |
| from .Logic import has_requirements_for_level_star, has_requirements_for_level_access, level_shuffle_factory, is_item_progression, is_useful
 | |
| 
 | |
| 
 | |
| class Overcooked2Web(WebWorld):
 | |
|     theme = "partyTime"
 | |
| 
 | |
|     bug_report_page = "https://github.com/toasterparty/oc2-modding/issues"
 | |
|     setup_en = Tutorial(
 | |
|         "Multiworld Setup Guide",
 | |
|         "A guide to setting up the Overcooked! 2 randomizer on your computer.",
 | |
|         "English",
 | |
|         "setup_en.md",
 | |
|         "setup/en",
 | |
|         ["toasterparty"]
 | |
|     )
 | |
| 
 | |
|     tutorials = [setup_en]
 | |
| 
 | |
| 
 | |
| class PrepLevelMode(IntEnum):
 | |
|     original = 0
 | |
|     excluded = 1
 | |
|     ayce = 2
 | |
| 
 | |
| 
 | |
| class Overcooked2World(World):
 | |
|     """
 | |
|     Overcooked! 2 is a frantically paced arcade cooking game where
 | |
|     players race against the clock to complete orders for points. Bring
 | |
|     peace to the Onion Kingdom once again by recovering lost items and abilities,
 | |
|     earning stars to unlock levels, and defeating the unbread horde. Levels are
 | |
|     randomized to increase gameplay variety. Play with up to 4 friends.
 | |
|     """
 | |
| 
 | |
|     # Autoworld API
 | |
| 
 | |
|     game = "Overcooked! 2"
 | |
|     web = Overcooked2Web()
 | |
|     required_client_version = (0, 3, 8)
 | |
|     topology_present: bool = False
 | |
| 
 | |
|     item_name_to_id = item_name_to_id
 | |
|     item_id_to_name = item_id_to_name
 | |
| 
 | |
|     location_id_to_name = oc2_location_id_to_name
 | |
|     location_name_to_id = oc2_location_name_to_id
 | |
| 
 | |
|     options_dataclass = OC2Options
 | |
|     options: OC2Options
 | |
|     itempool: List[Overcooked2Item]
 | |
| 
 | |
|     # Helper Functions
 | |
| 
 | |
|     def is_level_horde(self, level_id: int) -> bool:
 | |
|         return self.options.include_horde_levels and \
 | |
|             (self.level_mapping is not None) and \
 | |
|             level_id in self.level_mapping.keys() and \
 | |
|             self.level_mapping[level_id].is_horde
 | |
| 
 | |
|     def create_item(self, item: str, classification: ItemClassification = ItemClassification.progression) -> Overcooked2Item:
 | |
|         return Overcooked2Item(item, classification, self.item_name_to_id[item], self.player)
 | |
| 
 | |
|     def create_event(self, event: str, classification: ItemClassification) -> Overcooked2Item:
 | |
|         return Overcooked2Item(event, classification, None, self.player)
 | |
| 
 | |
|     def place_event(self, location_name: str, item_name: str,
 | |
|                     classification: ItemClassification = ItemClassification.progression_skip_balancing):
 | |
|         location: Location = self.multiworld.get_location(location_name, self.player)
 | |
|         location.place_locked_item(self.create_event(item_name, classification))
 | |
| 
 | |
|     def add_region(self, region_name: str):
 | |
|         region = Region(
 | |
|             region_name,
 | |
|             self.player,
 | |
|             self.multiworld,
 | |
|         )
 | |
|         self.multiworld.regions.append(region)
 | |
| 
 | |
|     def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None):
 | |
|         sourceRegion = self.multiworld.get_region(source, self.player)
 | |
|         targetRegion = self.multiworld.get_region(target, self.player)
 | |
|         sourceRegion.connect(targetRegion, rule=rule)
 | |
| 
 | |
|     def add_level_location(
 | |
|         self,
 | |
|         region_name: str,
 | |
|         location_name: str,
 | |
|         level_id: int,
 | |
|         stars: int,
 | |
|         is_event: bool = False,
 | |
|         priority=False,
 | |
|     ) -> None:
 | |
| 
 | |
|         if is_event:
 | |
|             location_id = None
 | |
|         else:
 | |
|             location_id = level_id
 | |
| 
 | |
|         region = self.multiworld.get_region(region_name, self.player)
 | |
|         location = Overcooked2Location(
 | |
|             self.player,
 | |
|             location_name,
 | |
|             location_id,
 | |
|             region,
 | |
|         )
 | |
| 
 | |
|         if priority:
 | |
|             location.progress_type = LocationProgressType.PRIORITY
 | |
|         else:
 | |
|             location.progress_type = LocationProgressType.DEFAULT
 | |
| 
 | |
|         # if level_id is none, then it's the 6-6 edge case
 | |
|         if level_id is None:
 | |
|             level_id = 36
 | |
|         if self.level_mapping is not None and level_id in self.level_mapping:
 | |
|             level = self.level_mapping[level_id]
 | |
|         else:
 | |
|             level = Overcooked2GenericLevel(level_id)
 | |
| 
 | |
|         completion_condition: Callable[[CollectionState], bool] = \
 | |
|             lambda state, level=level, stars=stars: \
 | |
|             has_requirements_for_level_star(state, level, stars, self.player)
 | |
|         location.access_rule = completion_condition
 | |
| 
 | |
|         region.locations.append(
 | |
|             location
 | |
|         )
 | |
| 
 | |
|     def get_n_random_locations(self, n: int) -> List[int]:
 | |
|         """Return a list of n random non-repeating level locations"""
 | |
|         levels = list()
 | |
| 
 | |
|         if n == 0:
 | |
|             return levels
 | |
| 
 | |
|         for level in Overcooked2Level():
 | |
|             if level.level_id == 36:
 | |
|                 continue
 | |
|             elif not self.options.kevin_levels and level.level_id > 36:
 | |
|                 break
 | |
| 
 | |
|             levels.append(level.level_id)
 | |
| 
 | |
|         self.multiworld.random.shuffle(levels)
 | |
|         return levels[:n]
 | |
| 
 | |
|     def get_priority_locations(self) -> List[int]:
 | |
|         """Randomly generate list of priority locations, thus insulating this game
 | |
|         from the negative effects of being shuffled with games that contain large
 | |
|         ammounts of filler"""
 | |
| 
 | |
|         if self.multiworld.players == 1:
 | |
|             # random priority locations have no desirable effect on solo seeds
 | |
|             return list()
 | |
| 
 | |
|         balancing_mode = self.options.location_balancing
 | |
| 
 | |
|         if balancing_mode == LocationBalancingMode.disabled:
 | |
|             # Location balancing is disabled, progression density is purely determined by filler
 | |
|             return list()
 | |
| 
 | |
|         # Count how many progression items are required for this overcooked instance
 | |
|         game_item_count = len(self.itempool)
 | |
|         game_progression_count = 0
 | |
|         for item in self.itempool:
 | |
|             if item.advancement:
 | |
|                 game_progression_count += 1
 | |
|         game_progression_density = game_progression_count/game_item_count
 | |
| 
 | |
|         if balancing_mode == LocationBalancingMode.full:
 | |
|             # Location balancing will be employed in an attempt to keep the number of
 | |
|             # progression locations and proression items as close to equal as possible
 | |
|             return self.get_n_random_locations(game_progression_count)
 | |
| 
 | |
|         assert balancing_mode == LocationBalancingMode.compromise
 | |
| 
 | |
|         # Count how many progression items are shuffled between all games
 | |
|         total_item_count = len(self.multiworld.itempool)
 | |
|         total_progression_count = 0
 | |
| 
 | |
|         for item in self.multiworld.itempool:
 | |
|             if item.advancement:
 | |
|                 total_progression_count += 1
 | |
|         total_progression_density = total_progression_count/total_item_count
 | |
| 
 | |
|         if total_progression_density >= game_progression_density:
 | |
|             # This game has more filler than the average of all other games.
 | |
|             # It is not in need of location balancing
 | |
|             return list()
 | |
| 
 | |
|         # Calculate the midpoint between the two ratios
 | |
|         target_progression_ratio = (game_progression_density - total_progression_density) / 2.0 + total_progression_density
 | |
|         target_progression_count = int((target_progression_ratio * game_item_count) + 0.5) # I'm sorry I round like an old person
 | |
| 
 | |
|         # Location balancing will be employed in an attempt to find a compromise at
 | |
|         # the half-way point between natural probability and full artifical balancing
 | |
|         return self.get_n_random_locations(target_progression_count)
 | |
| 
 | |
|     # Helper Data
 | |
| 
 | |
|     player_name: str
 | |
|     level_unlock_counts: Dict[int, int]  # level_id, stars to purchase
 | |
|     level_mapping: Dict[int, Overcooked2GenericLevel]  # level_id, level
 | |
|     enabled_dlc: Set[Overcooked2Dlc]
 | |
| 
 | |
|     # Autoworld Hooks
 | |
| 
 | |
|     def generate_early(self):
 | |
|         # 0.0 to 1.0 where 1.0 is World Record
 | |
|         self.star_threshold_scale = self.options.star_threshold_scale / 100.0
 | |
| 
 | |
|         # Parse DLCOptionSet back into enums
 | |
|         self.enabled_dlc = {Overcooked2Dlc(x) for x in self.options.include_dlcs.value}
 | |
| 
 | |
|         # Generate level unlock requirements such that the levels get harder to unlock
 | |
|         # the further the game has progressed, and levels progress radially rather than linearly
 | |
|         self.level_unlock_counts = level_unlock_requirement_factory(self.options.stars_to_win.value)
 | |
| 
 | |
|         # Assign new kitchens to each spot on the overworld using pure random chance and nothing else
 | |
|         if self.options.shuffle_level_order:
 | |
|             self.level_mapping = \
 | |
|                 level_shuffle_factory(
 | |
|                     self.multiworld.random,
 | |
|                     self.options.prep_levels != PrepLevelMode.excluded,
 | |
|                     self.options.include_horde_levels.result,
 | |
|                     self.options.kevin_levels.result,
 | |
|                     self.enabled_dlc,
 | |
|                     self.player_name,
 | |
|                 )
 | |
|         else:
 | |
|             self.level_mapping = None
 | |
|             if Overcooked2Dlc.STORY not in self.enabled_dlc:
 | |
|                 raise Exception(f"Invalid OC2 settings({self.player_name}) Need either Level Shuffle disabled or 'Story' DLC enabled")
 | |
| 
 | |
|             self.enabled_dlc = {Overcooked2Dlc.STORY}
 | |
| 
 | |
|     def set_location_priority(self) -> None:
 | |
|         priority_locations = self.get_priority_locations()
 | |
|         for level in Overcooked2Level():
 | |
|             if level.level_id in priority_locations:
 | |
|                 location: Location = self.multiworld.get_location(level.location_name_item, self.player)
 | |
|                 location.progress_type = LocationProgressType.PRIORITY
 | |
| 
 | |
| 
 | |
|     def create_regions(self) -> None:
 | |
|         # Menu -> Overworld
 | |
|         self.add_region("Menu")
 | |
|         self.add_region("Overworld")
 | |
|         self.connect_regions("Menu", "Overworld")
 | |
| 
 | |
|         # Create and populate "regions" (a.k.a. levels)
 | |
|         for level in Overcooked2Level():
 | |
|             if not self.options.kevin_levels and level.level_id > 36:
 | |
|                 break
 | |
| 
 | |
|             # Create Region (e.g. "1-1")
 | |
|             self.add_region(level.level_name)
 | |
| 
 | |
|             # Add Location to house progression item (1-star)
 | |
|             if level.level_id == 36:
 | |
|                 # 6-6 doesn't have progression, but it does have victory condition which is placed later
 | |
|                 self.add_level_location(
 | |
|                     level.level_name,
 | |
|                     level.location_name_item,
 | |
|                     None,
 | |
|                     1,
 | |
|                 )
 | |
|             else:
 | |
|                 # Location to house progression item
 | |
|                 self.add_level_location(
 | |
|                     level.level_name,
 | |
|                     level.location_name_item,
 | |
|                     level.level_id,
 | |
|                     1,
 | |
|                 )
 | |
| 
 | |
|                 # Location to house level completed event
 | |
|                 self.add_level_location(
 | |
|                     level.level_name,
 | |
|                     level.location_name_level_complete,
 | |
|                     level.level_id,
 | |
|                     1,
 | |
|                     is_event=True,
 | |
|                 )
 | |
| 
 | |
|             # Add Locations to house star aquisition events
 | |
|             if self.is_level_horde(level.level_id):
 | |
|                 # in randomizer, horde levels grant a single star
 | |
|                 star_counts = [1]
 | |
|             else:
 | |
|                 star_counts = [1, 2, 3]
 | |
| 
 | |
|             for n in star_counts:
 | |
|                 self.add_level_location(
 | |
|                     level.level_name,
 | |
|                     level.location_name_star_event(n),
 | |
|                     level.level_id,
 | |
|                     n,
 | |
|                     is_event=True,
 | |
|                 )
 | |
| 
 | |
|             # Overworld -> Level
 | |
|             required_star_count: int = self.level_unlock_counts[level.level_id]
 | |
|             if level.level_id % 6 != 1 and level.level_id <= 36:
 | |
|                 previous_level_completed_event_name: str = Overcooked2GenericLevel(
 | |
|                     level.level_id - 1).shortname.split(" ")[1] + " Level Complete"
 | |
|             else:
 | |
|                 previous_level_completed_event_name = None
 | |
| 
 | |
|             level_access_rule: Callable[[CollectionState], bool] = \
 | |
|                 lambda state, level_name=level.level_name, previous_level_completed_event_name=previous_level_completed_event_name, required_star_count=required_star_count: \
 | |
|                 has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.options.ramp_tricks.result, self.player)
 | |
|             self.connect_regions("Overworld", level.level_name, level_access_rule)
 | |
| 
 | |
|             # Level --> Overworld
 | |
|             self.connect_regions(level.level_name, "Overworld")
 | |
| 
 | |
|         completion_condition: Callable[[CollectionState], bool] = lambda state: \
 | |
|             state.has("Victory", self.player)
 | |
|         self.multiworld.completion_condition[self.player] = completion_condition
 | |
| 
 | |
| 
 | |
|     def create_items(self):
 | |
|         self.itempool = []
 | |
| 
 | |
|         # Make Items
 | |
|         # useful = list()
 | |
|         # filler = list()
 | |
|         # progression = list()
 | |
|         for item_name in item_table:            
 | |
|             if item_name in item_frequencies:
 | |
|                 freq = item_frequencies[item_name]
 | |
|             else:
 | |
|                 freq = 1
 | |
|             
 | |
|             if freq <= 0:
 | |
|                 # not used
 | |
|                 continue
 | |
| 
 | |
|             if item_name in dlc_exclusives:
 | |
|                 if not any(x in dlc_exclusives[item_name] for x in self.enabled_dlc):
 | |
|                     # Item is always useless with these settings
 | |
|                     continue
 | |
| 
 | |
|             if not self.options.include_horde_levels and item_name in ["Calmer Unbread", "Coin Purse"]:
 | |
|                 # skip horde-specific items if no horde levels
 | |
|                 continue
 | |
| 
 | |
|             if not self.options.kevin_levels:
 | |
|                 if item_name.startswith("Kevin"):
 | |
|                     # skip kevin items if no kevin levels
 | |
|                     continue
 | |
| 
 | |
|                 if item_name == "Dark Green Ramp":
 | |
|                     # skip dark green ramp if there's no Kevin-1 to reveal it
 | |
|                     continue
 | |
| 
 | |
|             if is_item_progression(item_name, self.level_mapping, self.options.kevin_levels):
 | |
|                 # progression.append(item_name)
 | |
|                 classification = ItemClassification.progression
 | |
|             else:
 | |
|                 if (is_useful(item_name)):
 | |
|                     # useful.append(item_name)
 | |
|                     classification = ItemClassification.useful
 | |
|                 else:
 | |
|                     # filler.append(item_name)
 | |
|                     classification = ItemClassification.filler
 | |
| 
 | |
|             while freq > 0:
 | |
|                 self.itempool.append(self.create_item(item_name, classification))
 | |
|                 classification = ItemClassification.useful  # only the first progressive item can be progression
 | |
|                 freq -= 1
 | |
| 
 | |
|         # print(f"progression: {progression}")
 | |
|         # print(f"useful: {useful}")
 | |
|         # print(f"filler: {filler}")
 | |
| 
 | |
|         # Fill any free space with filler
 | |
|         pool_count = len(oc2_location_name_to_id)
 | |
|         if not self.options.kevin_levels:
 | |
|             pool_count -= 8
 | |
| 
 | |
|         while len(self.itempool) < pool_count:
 | |
|             self.itempool.append(self.create_item("Bonus Star", ItemClassification.useful))
 | |
| 
 | |
|         self.multiworld.itempool += self.itempool
 | |
| 
 | |
| 
 | |
|     def place_events(self):
 | |
|         # Add Events (Star Acquisition)
 | |
|         for level in Overcooked2Level():
 | |
|             if not self.options.kevin_levels and level.level_id > 36:
 | |
|                 break
 | |
| 
 | |
|             if level.level_id != 36:
 | |
|                 self.place_event(level.location_name_level_complete, level.event_name_level_complete)
 | |
| 
 | |
|             if self.is_level_horde(level.level_id):
 | |
|                 # in randomizer, horde levels grant a single star
 | |
|                 star_counts = [1]
 | |
|             else:
 | |
|                 star_counts = [1, 2, 3]
 | |
| 
 | |
|             for n in star_counts:
 | |
|                 self.place_event(level.location_name_star_event(n), "Star")
 | |
| 
 | |
|         # Add Victory Condition
 | |
|         self.place_event("6-6 Completed", "Victory")
 | |
| 
 | |
|     def set_rules(self):
 | |
|         pass
 | |
| 
 | |
|     def generate_basic(self) -> None:
 | |
|         self.place_events()
 | |
|         self.set_location_priority()
 | |
| 
 | |
|     # Items get distributed to locations
 | |
| 
 | |
|     def fill_json_data(self) -> Dict[str, Any]:
 | |
|         mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.player_name}"
 | |
| 
 | |
|         # Serialize Level Order
 | |
|         story_level_order = dict()
 | |
| 
 | |
|         if self.options.shuffle_level_order:
 | |
|             for level_id in self.level_mapping:
 | |
|                 level: Overcooked2GenericLevel = self.level_mapping[level_id]
 | |
|                 story_level_order[str(level_id)] = {
 | |
|                     "DLC": level.dlc.value,
 | |
|                     "LevelID": level.level_id,
 | |
|                 }
 | |
| 
 | |
|         custom_level_order = dict()
 | |
|         custom_level_order["Story"] = story_level_order
 | |
| 
 | |
|         # Serialize Unlock Requirements
 | |
|         level_purchase_requirements = dict()
 | |
|         for level_id in self.level_unlock_counts:
 | |
|             level_purchase_requirements[str(level_id)] = self.level_unlock_counts[level_id]
 | |
| 
 | |
|         # Override Vanilla Unlock Chain Behavior
 | |
|         # (all worlds accessible from the start and progressible in any order)
 | |
|         level_unlock_requirements = dict()
 | |
|         level_force_reveal = [
 | |
|             1,   # 1-1
 | |
|             7,   # 2-1
 | |
|             13,  # 3-1
 | |
|             19,  # 4-1
 | |
|             25,  # 5-1
 | |
|             31,  # 6-1
 | |
|         ]
 | |
|         for level_id in range(1, 37):
 | |
|             if (level_id not in level_force_reveal):
 | |
|                 level_unlock_requirements[str(level_id)] = level_id - 1
 | |
| 
 | |
|         # Set Kevin Unlock Requirements
 | |
|         if self.options.kevin_levels:
 | |
|             def kevin_level_to_keyholder_level_id(level_id: int) -> Optional[int]:
 | |
|                 location = self.multiworld.find_item(f"Kevin-{level_id-36}", self.player)
 | |
|                 if location.player != self.player:
 | |
|                     return None  # This kevin level will be unlocked by the server at runtime
 | |
|                 level_id = oc2_location_name_to_id[location.name]
 | |
|                 return level_id
 | |
| 
 | |
|             for level_id in range(37, 45):
 | |
|                 keyholder_level_id = kevin_level_to_keyholder_level_id(level_id)
 | |
|                 if keyholder_level_id is not None:
 | |
|                     level_unlock_requirements[str(level_id)] = keyholder_level_id
 | |
| 
 | |
|         # Place Items at Level Completion Screens (local only)
 | |
|         on_level_completed: Dict[str, list[Dict[str, str]]] = dict()
 | |
|         locations = self.multiworld.get_filled_locations(self.player)
 | |
|         for location in locations:
 | |
|             if location.item.code is None:
 | |
|                 continue  # it's an event
 | |
|             if location.item.player != self.player:
 | |
|                 continue  # not for us
 | |
|             level_id = str(oc2_location_name_to_id[location.name])
 | |
|             on_level_completed[level_id] = [item_to_unlock_event(location.item.name)]
 | |
| 
 | |
|         # Put it all together
 | |
|         star_threshold_scale = self.options.star_threshold_scale / 100
 | |
| 
 | |
|         base_data = {
 | |
|             # Changes Inherent to rando
 | |
|             "DisableAllMods": False,
 | |
|             "UnlockAllChefs": True,
 | |
|             "UnlockAllDLC": True,
 | |
|             "DisplayFPS": True,
 | |
|             "SkipTutorial": True,
 | |
|             "SkipAllOnionKing": True,
 | |
|             "SkipTutorialPopups": True,
 | |
|             "RevealAllLevels": False,
 | |
|             "PurchaseAllLevels": False,
 | |
|             "CheatsEnabled": False,
 | |
|             "ImpossibleTutorial": True,
 | |
|             "ForbidDLC": True,
 | |
|             "ForceSingleSaveSlot": True,
 | |
|             "DisableNGP": True,
 | |
|             "LevelForceReveal": level_force_reveal,
 | |
|             "SaveFolderName": mod_name,
 | |
|             "CustomOrderTimeoutPenalty": 10,
 | |
|             "LevelForceHide": [37, 38, 39, 40, 41, 42, 43, 44],
 | |
|             "LocalDeathLink": self.options.deathlink != DeathLinkMode.disabled,
 | |
|             "BurnTriggersDeath": self.options.deathlink == DeathLinkMode.death_and_overcook,
 | |
| 
 | |
|             # Game Modifications
 | |
|             "LevelPurchaseRequirements": level_purchase_requirements,
 | |
|             "Custom66TimerScale": max(0.4, 0.25 + (1.0 - star_threshold_scale)*0.6),
 | |
|             "ShortHordeLevels": self.options.short_horde_levels.result,
 | |
|             "CustomLevelOrder": custom_level_order,
 | |
| 
 | |
|             # Items (Starting Inventory)
 | |
|             "CustomOrderLifetime": 70.0,  # 100 is original
 | |
|             "DisableWood": True,
 | |
|             "DisableCoal": True,
 | |
|             "DisableOnePlate": True,
 | |
|             "DisableFireExtinguisher": True,
 | |
|             "DisableBellows": True,
 | |
|             "PlatesStartDirty": True,
 | |
|             "MaxTipCombo": 2,
 | |
|             "DisableDash": True,
 | |
|             "WeakDash": True,
 | |
|             "DisableThrow": True,
 | |
|             "DisableCatch": True,
 | |
|             "DisableControlStick": True,
 | |
|             "DisableWokDrag": True,
 | |
|             # "DisableRampButton": True, # Unused
 | |
|             "DisableGreenRampButton" : True,
 | |
|             "DisableYellowRampButton" : True,
 | |
|             "DisableBlueRampButton" : True,
 | |
|             "DisablePinkRampButton" : True,
 | |
|             "DisableGreyRampButton" : True,
 | |
|             "DisableRedRampButton" : True,
 | |
|             "DisablePurpleRampButton" : True,
 | |
|             "WashTimeMultiplier": 1.4,
 | |
|             "BurnSpeedMultiplier": 1.43,
 | |
|             "MaxOrdersOnScreenOffset": -2,
 | |
|             "ChoppingTimeScale": 1.4,
 | |
|             "BackpackMovementScale": 0.75,
 | |
|             "RespawnTime": 10.0,
 | |
|             "CarnivalDispenserRefactoryTime": 4.0,
 | |
|             "LevelUnlockRequirements": level_unlock_requirements,
 | |
|             "LockedEmotes": [0, 1, 2, 3, 4, 5],
 | |
|             "StarOffset": 0,
 | |
|             "AggressiveHorde": True,
 | |
|             "DisableEarnHordeMoney": True,
 | |
| 
 | |
|             # Item Unlocking
 | |
|             "OnLevelCompleted": on_level_completed,
 | |
|         }
 | |
| 
 | |
|         # Set remaining data in the options dict
 | |
|         bugs = ["FixDoubleServing", "FixSinkBug", "FixControlStickThrowBug", "FixEmptyBurnerThrow"]
 | |
|         for bug in bugs:
 | |
|             base_data[bug] = self.options.fix_bugs.result
 | |
|         base_data["PreserveCookingProgress"] = self.options.always_preserve_cooking_progress.result
 | |
|         base_data["TimerAlwaysStarts"] = self.options.prep_levels == PrepLevelMode.ayce
 | |
|         base_data["LevelTimerScale"] = 0.666 if self.options.shorter_level_duration else 1.0
 | |
|         base_data["LeaderboardScoreScale"] = {
 | |
|             "FourStars": 1.0,
 | |
|             "ThreeStars": star_threshold_scale,
 | |
|             "TwoStars": star_threshold_scale * 0.75,
 | |
|             "OneStar": star_threshold_scale * 0.35,
 | |
|         }
 | |
|         base_data["AlwaysServeOldestOrder"] = self.options.always_serve_oldest_order.result
 | |
| 
 | |
|         return base_data
 | |
| 
 | |
|     def fill_slot_data(self) -> Dict[str, Any]:
 | |
|         return self.fill_json_data()
 | |
| 
 | |
|     def write_spoiler(self, spoiler_handle: TextIO) -> None:
 | |
|         if not self.options.shuffle_level_order:
 | |
|             return
 | |
| 
 | |
|         world: Overcooked2World = self
 | |
|         spoiler_handle.write(f"\n\n{self.player_name}'s Level Order:\n\n")
 | |
|         for overworld_id in world.level_mapping:
 | |
|             overworld_name = Overcooked2GenericLevel(overworld_id).shortname.split("Story ")[1]
 | |
|             kitchen_name = world.level_mapping[overworld_id].shortname
 | |
|             spoiler_handle.write(f'{overworld_name} | {kitchen_name}\n')
 | |
| 
 | |
| 
 | |
| def level_unlock_requirement_factory(stars_to_win: int) -> Dict[int, int]:
 | |
|     level_unlock_counts = dict()
 | |
|     level = 1
 | |
|     sublevel = 1
 | |
|     for n in range(1, 37):
 | |
|         progress: float = float(n)/36.0
 | |
|         progress *= progress  # x^2 curve
 | |
| 
 | |
|         star_count = int(progress*float(stars_to_win))
 | |
|         min = (n-1)*3
 | |
|         if (star_count > min):
 | |
|             star_count = min
 | |
| 
 | |
|         level_id = (level-1)*6 + sublevel
 | |
| 
 | |
|         # print("%d-%d (%d) = %d" % (level, sublevel, level_id, star_count))
 | |
| 
 | |
|         level_unlock_counts[level_id] = star_count
 | |
| 
 | |
|         level += 1
 | |
|         if level > 6:
 | |
|             level = 1
 | |
|             sublevel += 1
 | |
| 
 | |
|     # force sphere 1 to 0 stars to help keep our promises to the item fill algo
 | |
|     level_unlock_counts[1] = 0  # 1-1
 | |
|     level_unlock_counts[7] = 0  # 2-1
 | |
|     level_unlock_counts[19] = 0  # 4-1
 | |
| 
 | |
|     # Force 5-1 into sphere 1 to help things out
 | |
|     level_unlock_counts[25] = 1  # 5-1
 | |
| 
 | |
|     for n in range(37, 46):
 | |
|         level_unlock_counts[n] = 0
 | |
| 
 | |
|     return level_unlock_counts
 | 
