mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	shapez: Implement New Game (#3960)
Adds shapez as a supported game in AP.
This commit is contained in:
		
							
								
								
									
										417
									
								
								worlds/shapez/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								worlds/shapez/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,417 @@ | ||||
| import math | ||||
| from typing import Any, List, Dict, Tuple, Mapping | ||||
|  | ||||
| from Options import OptionError | ||||
| from .data.strings import OTHER, ITEMS, CATEGORY, LOCATIONS, SLOTDATA, GOALS, OPTIONS | ||||
| from .items import item_descriptions, item_table, ShapezItem, \ | ||||
|     buildings_routing, buildings_processing, buildings_other, \ | ||||
|     buildings_top_row, buildings_wires, gameplay_unlocks, upgrades, \ | ||||
|     big_upgrades, filler, trap, bundles, belt_and_extractor, standard_traps, random_draining_trap, split_draining_traps, \ | ||||
|     whacky_upgrade_traps | ||||
| from .locations import ShapezLocation, addlevels, addupgrades, addachievements, location_description, \ | ||||
|     addshapesanity, addshapesanity_ut, shapesanity_simple, init_shapesanity_pool, achievement_locations, \ | ||||
|     level_locations, upgrade_locations, shapesanity_locations, categories | ||||
| from .presets import options_presets | ||||
| from .options import ShapezOptions | ||||
| from worlds.AutoWorld import World, WebWorld | ||||
| from BaseClasses import Item, Tutorial, LocationProgressType, MultiWorld | ||||
| from .regions import create_shapez_regions, has_x_belt_multiplier | ||||
| from ..generic.Rules import add_rule | ||||
|  | ||||
|  | ||||
| class ShapezWeb(WebWorld): | ||||
|     options_presets = options_presets | ||||
|     rich_text_options_doc = True | ||||
|     theme = "stone" | ||||
|     game_info_languages = ['en', 'de'] | ||||
|     setup_en = Tutorial( | ||||
|         "Multiworld Setup Guide", | ||||
|         "A guide to playing shapez with Archipelago:", | ||||
|         "English", | ||||
|         "setup_en.md", | ||||
|         "setup/en", | ||||
|         ["BlastSlimey"] | ||||
|     ) | ||||
|     setup_de = Tutorial( | ||||
|         setup_en.tutorial_name, | ||||
|         setup_en.description, | ||||
|         "Deutsch", | ||||
|         "setup_de.md", | ||||
|         "setup/de", | ||||
|         ["BlastSlimey"] | ||||
|     ) | ||||
|     datapackage_settings_en = Tutorial( | ||||
|         "Changing datapackage settings", | ||||
|         "3000 locations are too many or not enough? Here's how you can change that:", | ||||
|         "English", | ||||
|         "datapackage_settings_en.md", | ||||
|         "datapackage_settings/en", | ||||
|         ["BlastSlimey"] | ||||
|     ) | ||||
|     datapackage_settings_de = Tutorial( | ||||
|         datapackage_settings_en.tutorial_name, | ||||
|         datapackage_settings_en.description, | ||||
|         "Deutsch", | ||||
|         "datapackage_settings_de.md", | ||||
|         "datapackage_settings/de", | ||||
|         ["BlastSlimey"] | ||||
|     ) | ||||
|     tutorials = [setup_en, setup_de, datapackage_settings_en, datapackage_settings_de] | ||||
|     item_descriptions = item_descriptions | ||||
|     location_descriptions = location_description | ||||
|  | ||||
|  | ||||
| class ShapezWorld(World): | ||||
|     """ | ||||
|     shapez is an automation game about cutting, rotating, stacking, and painting shapes, that you extract from randomly | ||||
|     generated patches on an infinite canvas, without the need to manage your infinite resources or to pay for building | ||||
|     your factories. | ||||
|     """ | ||||
|     game = OTHER.game_name | ||||
|     options_dataclass = ShapezOptions | ||||
|     options: ShapezOptions | ||||
|     topology_present = True | ||||
|     web = ShapezWeb() | ||||
|     base_id = 20010707 | ||||
|     item_name_to_id = {name: id for id, name in enumerate(item_table.keys(), base_id)} | ||||
|     location_name_to_id = {name: id for id, name in enumerate(level_locations + upgrade_locations | ||||
|                                                               + achievement_locations + shapesanity_locations, base_id)} | ||||
|     item_name_groups = { | ||||
|         "Main Buildings": {ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker}, | ||||
|         "Processing Buildings": {*buildings_processing}, | ||||
|         "Goal Buildings": {ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.rotator_ccw, ITEMS.color_mixer, | ||||
|                            ITEMS.stacker, ITEMS.cutter_quad, ITEMS.painter_double, ITEMS.painter_quad, ITEMS.wires, | ||||
|                            ITEMS.switch, ITEMS.const_signal}, | ||||
|         "Most Useful Buildings": {ITEMS.balancer, ITEMS.tunnel, ITEMS.tunnel_tier_ii, ITEMS.comp_merger, | ||||
|                                   ITEMS.comp_splitter, ITEMS.trash, ITEMS.extractor_chain}, | ||||
|         "Most Important Buildings": {*belt_and_extractor}, | ||||
|         "Top Row Buildings": {*buildings_top_row}, | ||||
|         "Wires Layer Buildings": {*buildings_wires}, | ||||
|         "Gameplay Mechanics": {ITEMS.blueprints, ITEMS.wires}, | ||||
|         "Upgrades": {*{ITEMS.upgrade(size, cat) | ||||
|                        for size in {CATEGORY.big, CATEGORY.small, CATEGORY.gigantic, CATEGORY.rising} | ||||
|                        for cat in {CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting}}, | ||||
|                      *{ITEMS.trap_upgrade(cat, size) | ||||
|                        for cat in {CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting} | ||||
|                        for size in {"", CATEGORY.demonic}}, | ||||
|                      *{ITEMS.upgrade(size, CATEGORY.random) | ||||
|                        for size in {CATEGORY.big, CATEGORY.small}}}, | ||||
|         **{f"{cat} Upgrades": {*{ITEMS.upgrade(size, cat) | ||||
|                                  for size in {CATEGORY.big, CATEGORY.small, CATEGORY.gigantic, CATEGORY.rising}}, | ||||
|                                *{ITEMS.trap_upgrade(cat, size) | ||||
|                                  for size in {"", CATEGORY.demonic}}} | ||||
|            for cat in {CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting}}, | ||||
|         "Bundles": {*bundles}, | ||||
|         "Traps": {*standard_traps, *random_draining_trap, *split_draining_traps, *whacky_upgrade_traps}, | ||||
|     } | ||||
|     location_name_groups = { | ||||
|         "Levels": {*level_locations}, | ||||
|         "Upgrades": {*upgrade_locations}, | ||||
|         "Achievements": {*achievement_locations}, | ||||
|         "Shapesanity": {*shapesanity_locations}, | ||||
|         **{f"{cat} Upgrades": {loc for loc in upgrade_locations if loc.startswith(cat)} for cat in categories}, | ||||
|         "Only Belt and Extractor": {LOCATIONS.level(1), LOCATIONS.level(1, 1), | ||||
|                                     LOCATIONS.my_eyes, LOCATIONS.its_a_mess, LOCATIONS.getting_into_it, | ||||
|                                     LOCATIONS.perfectionist, LOCATIONS.oops, LOCATIONS.i_need_trains, LOCATIONS.gps, | ||||
|                                     LOCATIONS.a_long_time, LOCATIONS.addicted, | ||||
|                                     LOCATIONS.shapesanity(1), LOCATIONS.shapesanity(2), LOCATIONS.shapesanity(3)}, | ||||
|     } | ||||
|  | ||||
|     def __init__(self, multiworld: MultiWorld, player: int): | ||||
|         super().__init__(multiworld, player) | ||||
|  | ||||
|         # Defining instance attributes for each shapez world | ||||
|         # These are set to default values that should fail unit tests if not replaced with correct values | ||||
|         self.location_count: int = 0 | ||||
|         self.level_logic: List[str] = [] | ||||
|         self.upgrade_logic: List[str] = [] | ||||
|         self.level_logic_type: str = "" | ||||
|         self.upgrade_logic_type: str = "" | ||||
|         self.random_logic_phase_length: List[int] = [] | ||||
|         self.category_random_logic_amounts: Dict[str, int] = {} | ||||
|         self.maxlevel: int = 0 | ||||
|         self.finaltier: int = 0 | ||||
|         self.included_locations: Dict[str, Tuple[str, LocationProgressType]] = {} | ||||
|         self.client_seed: int = 0 | ||||
|         self.shapesanity_names: List[str] = [] | ||||
|         self.upgrade_traps_allowed: bool = False | ||||
|  | ||||
|         # Universal Tracker support | ||||
|         self.ut_active: bool = False | ||||
|         self.passthrough: Dict[str, any] = {} | ||||
|         self.location_id_to_alias: Dict[int, str] = {} | ||||
|  | ||||
|     @classmethod | ||||
|     def stage_generate_early(cls, multiworld: MultiWorld) -> None: | ||||
|         # Import the 75800 entries long shapesanity pool only once and only if it's actually needed | ||||
|         if len(shapesanity_simple) == 0: | ||||
|             init_shapesanity_pool() | ||||
|  | ||||
|     def generate_early(self) -> None: | ||||
|         # Calculate all the important values used for generating a shapez world, with some of them being random | ||||
|         self.upgrade_traps_allowed: bool = (self.options.include_whacky_upgrades and | ||||
|                                             (not self.options.goal == GOALS.efficiency_iii) and | ||||
|                                             self.options.throughput_levels_ratio == 0) | ||||
|  | ||||
|         # Load values from UT if this is a regenerated world | ||||
|         if hasattr(self.multiworld, "re_gen_passthrough"): | ||||
|             if OTHER.game_name in self.multiworld.re_gen_passthrough: | ||||
|                 self.ut_active = True | ||||
|                 self.passthrough = self.multiworld.re_gen_passthrough[OTHER.game_name] | ||||
|                 self.maxlevel = self.passthrough[SLOTDATA.maxlevel] | ||||
|                 self.finaltier = self.passthrough[SLOTDATA.finaltier] | ||||
|                 self.client_seed = self.passthrough[SLOTDATA.seed] | ||||
|                 self.level_logic = [self.passthrough[SLOTDATA.level_building(i+1)] for i in range(5)] | ||||
|                 self.upgrade_logic = [self.passthrough[SLOTDATA.upgrade_building(i+1)] for i in range(5)] | ||||
|                 self.level_logic_type = self.passthrough[SLOTDATA.rand_level_logic] | ||||
|                 self.upgrade_logic_type = self.passthrough[SLOTDATA.rand_upgrade_logic] | ||||
|                 self.random_logic_phase_length = [self.passthrough[SLOTDATA.phase_length(i)] for i in range(5)] | ||||
|                 self.category_random_logic_amounts = {cat: self.passthrough[SLOTDATA.cat_buildings_amount(cat)] | ||||
|                                                       for cat in [CATEGORY.belt_low, CATEGORY.miner_low, | ||||
|                                                                   CATEGORY.processors_low, CATEGORY.painting_low]} | ||||
|                 # Forces balancers, tunnel, and trash to not appear in regen to make UT more accurate | ||||
|                 self.options.early_balancer_tunnel_and_trash.value = 0 | ||||
|                 return | ||||
|  | ||||
|         # "MAM" goal is supposed to be longer than vanilla, but to not have more options than necessary, | ||||
|         # both goal amounts for "MAM" and "Even fasterer" are set in a single option. | ||||
|         if self.options.goal == GOALS.mam and self.options.goal_amount < 27: | ||||
|             raise OptionError(self.player_name | ||||
|                               + ": When setting goal to 1 ('mam'), goal_amount must be at least 27 and not " | ||||
|                               + str(self.options.goal_amount.value)) | ||||
|  | ||||
|         # If lock_belt_and_extractor is true, the only sphere 1 locations will be achievements | ||||
|         if self.options.lock_belt_and_extractor and not self.options.include_achievements: | ||||
|             raise OptionError(self.player_name + ": Achievements must be included when belt and extractor are locked") | ||||
|  | ||||
|         # Determines maxlevel and finaltier, which are needed for location and item generation | ||||
|         if self.options.goal == GOALS.vanilla: | ||||
|             self.maxlevel = 25 | ||||
|             self.finaltier = 8 | ||||
|         elif self.options.goal == GOALS.mam: | ||||
|             self.maxlevel = self.options.goal_amount - 1 | ||||
|             self.finaltier = 8 | ||||
|         elif self.options.goal == GOALS.even_fasterer: | ||||
|             self.maxlevel = 26 | ||||
|             self.finaltier = self.options.goal_amount.value | ||||
|         else:  # goal == efficiency_iii | ||||
|             self.maxlevel = 26 | ||||
|             self.finaltier = 8 | ||||
|  | ||||
|         # Setting the seed for the game before any other randomization call is done | ||||
|         self.client_seed = self.random.randint(0, 100000) | ||||
|  | ||||
|         # Determines the order of buildings for levels logic | ||||
|         if self.options.randomize_level_requirements: | ||||
|             self.level_logic_type = self.options.randomize_level_logic.current_key | ||||
|             if self.level_logic_type.endswith(OPTIONS.logic_shuffled) or self.level_logic_type == OPTIONS.logic_dopamine: | ||||
|                 vanilla_list = [ITEMS.cutter, ITEMS.painter, ITEMS.stacker] | ||||
|                 while len(vanilla_list) > 0: | ||||
|                     index = self.random.randint(0, len(vanilla_list)-1) | ||||
|                     next_building = vanilla_list.pop(index) | ||||
|                     if next_building == ITEMS.cutter: | ||||
|                         vanilla_list.append(ITEMS.rotator) | ||||
|                     if next_building == ITEMS.painter: | ||||
|                         vanilla_list.append(ITEMS.color_mixer) | ||||
|                     self.level_logic.append(next_building) | ||||
|             else: | ||||
|                 self.level_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker] | ||||
|         else: | ||||
|             self.level_logic_type = OPTIONS.logic_vanilla | ||||
|             self.level_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker] | ||||
|  | ||||
|         # Determines the order of buildings for upgrades logic | ||||
|         if self.options.randomize_upgrade_requirements: | ||||
|             self.upgrade_logic_type = self.options.randomize_upgrade_logic.current_key | ||||
|             if self.upgrade_logic_type == OPTIONS.logic_hardcore: | ||||
|                 self.upgrade_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker] | ||||
|             elif self.upgrade_logic_type == OPTIONS.logic_category: | ||||
|                 self.upgrade_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.stacker, ITEMS.painter, ITEMS.color_mixer] | ||||
|             else: | ||||
|                 vanilla_list = [ITEMS.cutter, ITEMS.painter, ITEMS.stacker] | ||||
|                 while len(vanilla_list) > 0: | ||||
|                     index = self.random.randint(0, len(vanilla_list)-1) | ||||
|                     next_building = vanilla_list.pop(index) | ||||
|                     if next_building == ITEMS.cutter: | ||||
|                         vanilla_list.append(ITEMS.rotator) | ||||
|                     if next_building == ITEMS.painter: | ||||
|                         vanilla_list.append(ITEMS.color_mixer) | ||||
|                     self.upgrade_logic.append(next_building) | ||||
|         else: | ||||
|             self.upgrade_logic_type = OPTIONS.logic_vanilla_like | ||||
|             self.upgrade_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker] | ||||
|  | ||||
|         # Determine lenghts of phases in level logic type "random" | ||||
|         self.random_logic_phase_length = [1, 1, 1, 1, 1] | ||||
|         if self.level_logic_type.startswith(OPTIONS.logic_random_steps): | ||||
|             remaininglength = self.maxlevel - 1 | ||||
|             for phase in range(0, 5): | ||||
|                 if self.random.random() < 0.1:  # Make sure that longer phases are less frequent | ||||
|                     self.random_logic_phase_length[phase] = self.random.randint(0, remaininglength) | ||||
|                 else: | ||||
|                     self.random_logic_phase_length[phase] = self.random.randint(0, remaininglength // (6 - phase)) | ||||
|                 remaininglength -= self.random_logic_phase_length[phase] | ||||
|  | ||||
|         # Determine amount of needed buildings for each category in upgrade logic type "category_random" | ||||
|         self.category_random_logic_amounts = {CATEGORY.belt_low: 0, CATEGORY.miner_low: 1, | ||||
|                                               CATEGORY.processors_low: 2, CATEGORY.painting_low: 3} | ||||
|         if self.upgrade_logic_type == OPTIONS.logic_category_random: | ||||
|             cats = [CATEGORY.belt_low, CATEGORY.miner_low, CATEGORY.processors_low, CATEGORY.painting_low] | ||||
|             nextcat = self.random.choice(cats) | ||||
|             self.category_random_logic_amounts[nextcat] = 0 | ||||
|             cats.remove(nextcat) | ||||
|             for cat in cats: | ||||
|                 self.category_random_logic_amounts[cat] = self.random.randint(0, 5) | ||||
|  | ||||
|     def create_item(self, name: str) -> Item: | ||||
|         return ShapezItem(name, item_table[name](self.options), self.item_name_to_id[name], self.player) | ||||
|  | ||||
|     def get_filler_item_name(self) -> str: | ||||
|         return filler(self.random.random(), bool(self.options.include_whacky_upgrades)) | ||||
|  | ||||
|     def append_shapesanity(self, name: str) -> None: | ||||
|         """This method is given as a parameter when creating the locations for shapesanity.""" | ||||
|         self.shapesanity_names.append(name) | ||||
|  | ||||
|     def add_alias(self, location_name: str, alias: str): | ||||
|         """This method is given as a parameter when locations with helpful aliases for UT are created.""" | ||||
|         if self.ut_active: | ||||
|             self.location_id_to_alias[self.location_name_to_id[location_name]] = alias | ||||
|  | ||||
|     def create_regions(self) -> None: | ||||
|         # Create list of all included level and upgrade locations based on player options | ||||
|         # This already includes the region to be placed in and the LocationProgressType | ||||
|         self.included_locations = {**addlevels(self.maxlevel, self.level_logic_type, | ||||
|                                                self.random_logic_phase_length), | ||||
|                                    **addupgrades(self.finaltier, self.upgrade_logic_type, | ||||
|                                                  self.category_random_logic_amounts)} | ||||
|  | ||||
|         # Add shapesanity to included location and creates the corresponding list based on player options | ||||
|         if self.ut_active: | ||||
|             self.shapesanity_names = self.passthrough[SLOTDATA.shapesanity] | ||||
|             self.included_locations.update(addshapesanity_ut(self.shapesanity_names, self.add_alias)) | ||||
|         else: | ||||
|             self.included_locations.update(addshapesanity(self.options.shapesanity_amount.value, self.random, | ||||
|                                                           self.append_shapesanity, self.add_alias)) | ||||
|  | ||||
|         # Add achievements to included locations based on player options | ||||
|         if self.options.include_achievements: | ||||
|             self.included_locations.update(addachievements( | ||||
|                 bool(self.options.exclude_softlock_achievements), bool(self.options.exclude_long_playtime_achievements), | ||||
|                 bool(self.options.exclude_progression_unreasonable), self.maxlevel, self.upgrade_logic_type, | ||||
|                 self.category_random_logic_amounts, self.options.goal.current_key, self.included_locations, | ||||
|                 self.add_alias, self.upgrade_traps_allowed)) | ||||
|  | ||||
|         # Save the final amount of to-be-filled locations | ||||
|         self.location_count = len(self.included_locations) | ||||
|  | ||||
|         # Create regions and entrances based on included locations and player options | ||||
|         self.multiworld.regions.extend(create_shapez_regions(self.player, self.multiworld, | ||||
|                                                              bool(self.options.allow_floating_layers.value), | ||||
|                                                              self.included_locations, self.location_name_to_id, | ||||
|                                                              self.level_logic, self.upgrade_logic, | ||||
|                                                              self.options.early_balancer_tunnel_and_trash.current_key, | ||||
|                                                              self.options.goal.current_key)) | ||||
|  | ||||
|     def create_items(self) -> None: | ||||
|         # Include guaranteed items (game mechanic unlocks and 7x4 big upgrades) | ||||
|         included_items: List[Item] = ([self.create_item(name) for name in buildings_processing.keys()] | ||||
|                                       + [self.create_item(name) for name in buildings_routing.keys()] | ||||
|                                       + [self.create_item(name) for name in buildings_other.keys()] | ||||
|                                       + [self.create_item(name) for name in buildings_top_row.keys()] | ||||
|                                       + [self.create_item(name) for name in buildings_wires.keys()] | ||||
|                                       + [self.create_item(name) for name in gameplay_unlocks.keys()] | ||||
|                                       + [self.create_item(name) for name in big_upgrades for _ in range(7)]) | ||||
|  | ||||
|         if not self.options.lock_belt_and_extractor: | ||||
|             for name in belt_and_extractor: | ||||
|                 self.multiworld.push_precollected(self.create_item(name)) | ||||
|         else:  # This also requires self.options.include_achievements to be true | ||||
|             included_items.extend([self.create_item(name) for name in belt_and_extractor.keys()]) | ||||
|  | ||||
|         # Give a detailed error message if there are already more items than available locations. | ||||
|         # At the moment, this won't happen, but it's better for debugging in case a future update breaks things. | ||||
|         if len(included_items) > self.location_count: | ||||
|             raise RuntimeError(self.player_name + ": There are more guaranteed items than available locations") | ||||
|  | ||||
|         # Get value from traps probability option and convert to float | ||||
|         traps_probability = self.options.traps_percentage/100 | ||||
|         split_draining = bool(self.options.split_inventory_draining_trap) | ||||
|         # Fill remaining locations with fillers | ||||
|         for x in range(self.location_count - len(included_items)): | ||||
|             if self.random.random() < traps_probability: | ||||
|                 # Fill with trap | ||||
|                 included_items.append(self.create_item(trap(self.random.random(), split_draining, | ||||
|                                                             self.upgrade_traps_allowed))) | ||||
|             else: | ||||
|                 # Fil with random filler item | ||||
|                 included_items.append(self.create_item(self.get_filler_item_name())) | ||||
|  | ||||
|         # Add correct number of items to itempool | ||||
|         self.multiworld.itempool += included_items | ||||
|  | ||||
|         # Add balancer, tunnel, and trash to early items if player options say so | ||||
|         if self.options.early_balancer_tunnel_and_trash == OPTIONS.sphere_1: | ||||
|             self.multiworld.early_items[self.player][ITEMS.balancer] = 1 | ||||
|             self.multiworld.early_items[self.player][ITEMS.tunnel] = 1 | ||||
|             self.multiworld.early_items[self.player][ITEMS.trash] = 1 | ||||
|  | ||||
|     def set_rules(self) -> None: | ||||
|         # Levels might need more belt speed if they require throughput per second. As the randomization of what levels | ||||
|         # need throughput happens in the client mod, this logic needs to be applied to all levels. This is applied to | ||||
|         # every individual level instead of regions, because they would need a much more complex calculation to prevent | ||||
|         # softlocks. | ||||
|  | ||||
|         def f(x: int, name: str): | ||||
|             # These calculations are taken from the client mod | ||||
|             if x < 26: | ||||
|                 throughput = math.ceil((2.999+x*0.333)*self.options.required_shapes_multiplier/10) | ||||
|             else: | ||||
|                 throughput = min((4+(x-26)*0.25)*self.options.required_shapes_multiplier/10, 200) | ||||
|             if throughput/32 >= 1: | ||||
|                 add_rule(self.get_location(name), | ||||
|                          lambda state: has_x_belt_multiplier(state, self.player, throughput/32)) | ||||
|  | ||||
|         if not self.options.throughput_levels_ratio == 0: | ||||
|             f(0, LOCATIONS.level(1, 1)) | ||||
|             f(19, LOCATIONS.level(20, 1)) | ||||
|             f(19, LOCATIONS.level(20, 2)) | ||||
|             for _x in range(self.maxlevel): | ||||
|                 f(_x, LOCATIONS.level(_x+1)) | ||||
|             if self.options.goal.current_key in [GOALS.vanilla, GOALS.mam]: | ||||
|                 f(self.maxlevel, LOCATIONS.goal) | ||||
|  | ||||
|     def fill_slot_data(self) -> Mapping[str, Any]: | ||||
|         # Buildings logic; all buildings as individual parameters | ||||
|         level_logic_data = {SLOTDATA.level_building(x+1): self.level_logic[x] for x in range(5)} | ||||
|         upgrade_logic_data = {SLOTDATA.upgrade_building(x+1): self.upgrade_logic[x] for x in range(5)} | ||||
|         # Randomized values for certain logic types | ||||
|         logic_type_random_data = {SLOTDATA.phase_length(x): self.random_logic_phase_length[x] for x in range(0, 5)} | ||||
|         logic_type_cat_random_data = {SLOTDATA.cat_buildings_amount(cat): self.category_random_logic_amounts[cat] | ||||
|                                       for cat in [CATEGORY.belt_low, CATEGORY.miner_low, | ||||
|                                                   CATEGORY.processors_low, CATEGORY.painting_low]} | ||||
|  | ||||
|         # Options that are relevant to the mod | ||||
|         option_data = { | ||||
|             SLOTDATA.goal: self.options.goal.current_key, | ||||
|             SLOTDATA.maxlevel: self.maxlevel, | ||||
|             SLOTDATA.finaltier: self.finaltier, | ||||
|             SLOTDATA.req_shapes_mult: self.options.required_shapes_multiplier.value, | ||||
|             SLOTDATA.allow_float_layers: bool(self.options.allow_floating_layers), | ||||
|             SLOTDATA.rand_level_req: bool(self.options.randomize_level_requirements), | ||||
|             SLOTDATA.rand_upgrade_req: bool(self.options.randomize_upgrade_requirements), | ||||
|             SLOTDATA.rand_level_logic: self.level_logic_type, | ||||
|             SLOTDATA.rand_upgrade_logic: self.upgrade_logic_type, | ||||
|             SLOTDATA.throughput_levels_ratio: self.options.throughput_levels_ratio.value, | ||||
|             SLOTDATA.comp_growth_gradient: self.options.complexity_growth_gradient.value, | ||||
|             SLOTDATA.same_late: bool(self.options.same_late_upgrade_requirements), | ||||
|             SLOTDATA.toolbar_shuffling: bool(self.options.toolbar_shuffling), | ||||
|         } | ||||
|  | ||||
|         return {**level_logic_data, **upgrade_logic_data, **option_data, **logic_type_random_data, | ||||
|                 **logic_type_cat_random_data, SLOTDATA.seed: self.client_seed, | ||||
|                 SLOTDATA.shapesanity: self.shapesanity_names} | ||||
|  | ||||
|     def interpret_slot_data(self, slot_data: Dict[str, Any]) -> Dict[str, Any]: | ||||
|         """Helper function for Universal Tracker""" | ||||
|         return slot_data | ||||
							
								
								
									
										0
									
								
								worlds/shapez/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								worlds/shapez/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										190
									
								
								worlds/shapez/common/options.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								worlds/shapez/common/options.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| import random | ||||
| import typing | ||||
|  | ||||
| from Options import FreeText, NumericOption | ||||
|  | ||||
|  | ||||
| class FloatRangeText(FreeText, NumericOption): | ||||
|     """FreeText option optimized for entering float numbers. | ||||
|     Supports everything that Range supports. | ||||
|     range_start and range_end have to be floats, while default has to be a string.""" | ||||
|  | ||||
|     default = "0.0" | ||||
|     value: float | ||||
|     range_start: float = 0.0 | ||||
|     range_end: float = 1.0 | ||||
|  | ||||
|     def __init__(self, value: str): | ||||
|         super().__init__(value) | ||||
|         value = value.lower() | ||||
|         if value.startswith("random"): | ||||
|             self.value = self.weighted_range(value) | ||||
|         elif value == "default" and hasattr(self, "default"): | ||||
|             self.value = float(self.default) | ||||
|         elif value == "high": | ||||
|             self.value = self.range_end | ||||
|         elif value == "low": | ||||
|             self.value = self.range_start | ||||
|         elif self.range_start == 0.0 \ | ||||
|                 and hasattr(self, "default") \ | ||||
|                 and self.default != "0.0" \ | ||||
|                 and value in ("true", "false"): | ||||
|             # these are the conditions where "true" and "false" make sense | ||||
|             if value == "true": | ||||
|                 self.value = float(self.default) | ||||
|             else:  # "false" | ||||
|                 self.value = 0.0 | ||||
|         else: | ||||
|             try: | ||||
|                 self.value = float(value) | ||||
|             except ValueError: | ||||
|                 raise Exception(f"Invalid value for option {self.__class__.__name__}: {value}") | ||||
|             except OverflowError: | ||||
|                 raise Exception(f"Out of range floating value for option {self.__class__.__name__}: {value}") | ||||
|             if self.value < self.range_start: | ||||
|                 raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}") | ||||
|             if self.value > self.range_end: | ||||
|                 raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__}") | ||||
|  | ||||
|     @classmethod | ||||
|     def from_text(cls, text: str) -> typing.Any: | ||||
|         return cls(text) | ||||
|  | ||||
|     @classmethod | ||||
|     def weighted_range(cls, text: str) -> float: | ||||
|         if text == "random-low": | ||||
|             return random.triangular(cls.range_start, cls.range_end, cls.range_start) | ||||
|         elif text == "random-high": | ||||
|             return random.triangular(cls.range_start, cls.range_end, cls.range_end) | ||||
|         elif text == "random-middle": | ||||
|             return random.triangular(cls.range_start, cls.range_end) | ||||
|         elif text.startswith("random-range-"): | ||||
|             return cls.custom_range(text) | ||||
|         elif text == "random": | ||||
|             return random.uniform(cls.range_start, cls.range_end) | ||||
|         else: | ||||
|             raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. " | ||||
|                             f"Acceptable values are: random, random-high, random-middle, random-low, " | ||||
|                             f"random-range-low-<min>-<max>, random-range-middle-<min>-<max>, " | ||||
|                             f"random-range-high-<min>-<max>, or random-range-<min>-<max>.") | ||||
|  | ||||
|     @classmethod | ||||
|     def custom_range(cls, text: str) -> float: | ||||
|         textsplit = text.split("-") | ||||
|         try: | ||||
|             random_range = [float(textsplit[len(textsplit) - 2]), float(textsplit[len(textsplit) - 1])] | ||||
|         except ValueError: | ||||
|             raise ValueError(f"Invalid random range {text} for option {cls.__name__}") | ||||
|         except OverflowError: | ||||
|             raise Exception(f"Out of range floating value for option {cls.__name__}: {text}") | ||||
|         random_range.sort() | ||||
|         if random_range[0] < cls.range_start or random_range[1] > cls.range_end: | ||||
|             raise Exception( | ||||
|                 f"{random_range[0]}-{random_range[1]} is outside allowed range " | ||||
|                 f"{cls.range_start}-{cls.range_end} for option {cls.__name__}") | ||||
|         if text.startswith("random-range-low"): | ||||
|             return random.triangular(random_range[0], random_range[1], random_range[0]) | ||||
|         elif text.startswith("random-range-middle"): | ||||
|             return random.triangular(random_range[0], random_range[1]) | ||||
|         elif text.startswith("random-range-high"): | ||||
|             return random.triangular(random_range[0], random_range[1], random_range[1]) | ||||
|         else: | ||||
|             return random.uniform(random_range[0], random_range[1]) | ||||
|  | ||||
|     @property | ||||
|     def current_key(self) -> str: | ||||
|         return str(self.value) | ||||
|  | ||||
|     @classmethod | ||||
|     def get_option_name(cls, value: float) -> str: | ||||
|         return str(value) | ||||
|  | ||||
|     def __eq__(self, other: typing.Any): | ||||
|         if isinstance(other, NumericOption): | ||||
|             return self.value == other.value | ||||
|         else: | ||||
|             return typing.cast(bool, self.value == other) | ||||
|  | ||||
|     def __lt__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||||
|         if isinstance(other, NumericOption): | ||||
|             return self.value < other.value | ||||
|         else: | ||||
|             return self.value < other | ||||
|  | ||||
|     def __le__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||||
|         if isinstance(other, NumericOption): | ||||
|             return self.value <= other.value | ||||
|         else: | ||||
|             return self.value <= other | ||||
|  | ||||
|     def __gt__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||||
|         if isinstance(other, NumericOption): | ||||
|             return self.value > other.value | ||||
|         else: | ||||
|             return self.value > other | ||||
|  | ||||
|     def __ge__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||||
|         if isinstance(other, NumericOption): | ||||
|             return self.value >= other.value | ||||
|         else: | ||||
|             return self.value >= other | ||||
|  | ||||
|     def __int__(self) -> int: | ||||
|         return int(self.value) | ||||
|  | ||||
|     def __and__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("& operator not supported for float values") | ||||
|  | ||||
|     def __floordiv__(self, other: typing.Any) -> int: | ||||
|         return int(self.value // float(other)) | ||||
|  | ||||
|     def __invert__(self) -> int: | ||||
|         raise TypeError("~ operator not supported for float values") | ||||
|  | ||||
|     def __lshift__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("<< operator not supported for float values") | ||||
|  | ||||
|     def __mod__(self, other: typing.Any) -> float: | ||||
|         return self.value % float(other) | ||||
|  | ||||
|     def __neg__(self) -> float: | ||||
|         return -self.value | ||||
|  | ||||
|     def __or__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("| operator not supported for float values") | ||||
|  | ||||
|     def __pos__(self) -> float: | ||||
|         return +self.value | ||||
|  | ||||
|     def __rand__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("& operator not supported for float values") | ||||
|  | ||||
|     def __rfloordiv__(self, other: typing.Any) -> int: | ||||
|         return int(float(other) // self.value) | ||||
|  | ||||
|     def __rlshift__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("<< operator not supported for float values") | ||||
|  | ||||
|     def __rmod__(self, other: typing.Any) -> float: | ||||
|         return float(other) % self.value | ||||
|  | ||||
|     def __ror__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("| operator not supported for float values") | ||||
|  | ||||
|     def __round__(self, ndigits: typing.Optional[int] = None) -> float: | ||||
|         return round(self.value, ndigits) | ||||
|  | ||||
|     def __rpow__(self, base: typing.Any) -> typing.Any: | ||||
|         return base ** self.value | ||||
|  | ||||
|     def __rrshift__(self, other: typing.Any) -> int: | ||||
|         raise TypeError(">> operator not supported for float values") | ||||
|  | ||||
|     def __rshift__(self, other: typing.Any) -> int: | ||||
|         raise TypeError(">> operator not supported for float values") | ||||
|  | ||||
|     def __rxor__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("^ operator not supported for float values") | ||||
|  | ||||
|     def __xor__(self, other: typing.Any) -> int: | ||||
|         raise TypeError("^ operator not supported for float values") | ||||
							
								
								
									
										0
									
								
								worlds/shapez/data/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								worlds/shapez/data/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										134
									
								
								worlds/shapez/data/generate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								worlds/shapez/data/generate.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| import itertools | ||||
| import time | ||||
| from typing import Dict, List | ||||
|  | ||||
| from worlds.shapez.data.strings import SHAPESANITY, REGIONS | ||||
|  | ||||
| shapesanity_simple: Dict[str, str] = {} | ||||
| shapesanity_1_4: Dict[str, str] = {} | ||||
| shapesanity_two_sided: Dict[str, str] = {} | ||||
| shapesanity_three_parts: Dict[str, str] = {} | ||||
| shapesanity_four_parts: Dict[str, str] = {} | ||||
| subshape_names = [SHAPESANITY.circle, SHAPESANITY.square, SHAPESANITY.star, SHAPESANITY.windmill] | ||||
| color_names = [SHAPESANITY.red, SHAPESANITY.blue, SHAPESANITY.green, SHAPESANITY.yellow, SHAPESANITY.purple, | ||||
|                SHAPESANITY.cyan, SHAPESANITY.white, SHAPESANITY.uncolored] | ||||
| short_subshapes = ["C", "R", "S", "W"] | ||||
| short_colors = ["b", "c", "g", "p", "r", "u", "w", "y"] | ||||
|  | ||||
|  | ||||
| def color_to_needed_building(color_list: List[str]) -> str: | ||||
|     for next_color in color_list: | ||||
|         if next_color in [SHAPESANITY.yellow, SHAPESANITY.purple, SHAPESANITY.cyan, SHAPESANITY.white, | ||||
|                           "y", "p", "c", "w"]: | ||||
|             return REGIONS.mixed | ||||
|     for next_color in color_list: | ||||
|         if next_color not in [SHAPESANITY.uncolored, "u"]: | ||||
|             return REGIONS.painted | ||||
|     return REGIONS.uncol | ||||
|  | ||||
|  | ||||
| def generate_shapesanity_pool() -> None: | ||||
|     # same shapes && same color | ||||
|     for color in color_names: | ||||
|         color_region = color_to_needed_building([color]) | ||||
|         shapesanity_simple[SHAPESANITY.full(color, SHAPESANITY.circle)] = REGIONS.sanity(REGIONS.full, color_region) | ||||
|         shapesanity_simple[SHAPESANITY.full(color, SHAPESANITY.square)] = REGIONS.sanity(REGIONS.full, color_region) | ||||
|         shapesanity_simple[SHAPESANITY.full(color, SHAPESANITY.star)] = REGIONS.sanity(REGIONS.full, color_region) | ||||
|         shapesanity_simple[SHAPESANITY.full(color, SHAPESANITY.windmill)] = REGIONS.sanity(REGIONS.east_wind, color_region) | ||||
|     for shape in subshape_names: | ||||
|         for color in color_names: | ||||
|             color_region = color_to_needed_building([color]) | ||||
|             shapesanity_simple[SHAPESANITY.half(color, shape)] = REGIONS.sanity(REGIONS.half, color_region) | ||||
|             shapesanity_simple[SHAPESANITY.piece(color, shape)] = REGIONS.sanity(REGIONS.piece, color_region) | ||||
|             shapesanity_simple[SHAPESANITY.cutout(color, shape)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|             shapesanity_simple[SHAPESANITY.cornered(color, shape)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|  | ||||
|     # one color && 4 shapes (including empty) | ||||
|     for first_color, second_color, third_color, fourth_color in itertools.combinations(short_colors+["-"], 4): | ||||
|         colors = [first_color, second_color, third_color, fourth_color] | ||||
|         color_region = color_to_needed_building(colors) | ||||
|         shape_regions = [REGIONS.stitched, REGIONS.stitched] if fourth_color == "-" else [REGIONS.col_full, REGIONS.col_east_wind] | ||||
|         color_code = ''.join(colors) | ||||
|         shapesanity_1_4[SHAPESANITY.full(color_code, SHAPESANITY.circle)] = REGIONS.sanity(shape_regions[0], color_region) | ||||
|         shapesanity_1_4[SHAPESANITY.full(color_code, SHAPESANITY.square)] = REGIONS.sanity(shape_regions[0], color_region) | ||||
|         shapesanity_1_4[SHAPESANITY.full(color_code, SHAPESANITY.star)] = REGIONS.sanity(shape_regions[0], color_region) | ||||
|         shapesanity_1_4[SHAPESANITY.full(color_code, SHAPESANITY.windmill)] = REGIONS.sanity(shape_regions[1], color_region) | ||||
|  | ||||
|     # one shape && 4 colors (including empty) | ||||
|     for first_shape, second_shape, third_shape, fourth_shape in itertools.combinations(short_subshapes+["-"], 4): | ||||
|         for color in color_names: | ||||
|             shapesanity_1_4[SHAPESANITY.full(color, ''.join([first_shape, second_shape, third_shape, fourth_shape]))] \ | ||||
|                 = REGIONS.sanity(REGIONS.stitched, color_to_needed_building([color])) | ||||
|  | ||||
|     combos = [shape + color for shape in short_subshapes for color in short_colors] | ||||
|     for first_combo, second_combo in itertools.permutations(combos, 2): | ||||
|         # 2-sided shapes | ||||
|         color_region = color_to_needed_building([first_combo[1], second_combo[1]]) | ||||
|         ordered_combo = " ".join(sorted([first_combo, second_combo])) | ||||
|         shape_regions = (([REGIONS.east_wind, REGIONS.east_wind, REGIONS.col_half] | ||||
|                           if first_combo[0] == "W" else [REGIONS.col_full, REGIONS.col_full, REGIONS.col_half]) | ||||
|                          if first_combo[0] == second_combo[0] else [REGIONS.stitched, REGIONS.half_half, REGIONS.stitched]) | ||||
|         shapesanity_two_sided[SHAPESANITY.three_one(first_combo, second_combo)] = REGIONS.sanity(shape_regions[0], color_region) | ||||
|         shapesanity_two_sided[SHAPESANITY.halfhalf(ordered_combo)] = REGIONS.sanity(shape_regions[1], color_region) | ||||
|         shapesanity_two_sided[SHAPESANITY.checkered(ordered_combo)] = REGIONS.sanity(shape_regions[0], color_region) | ||||
|         shapesanity_two_sided[SHAPESANITY.singles(ordered_combo, SHAPESANITY.adjacent_pos)] = REGIONS.sanity(shape_regions[2], color_region) | ||||
|         shapesanity_two_sided[SHAPESANITY.singles(ordered_combo, SHAPESANITY.cornered_pos)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|         shapesanity_two_sided[SHAPESANITY.two_one(first_combo, second_combo, SHAPESANITY.adjacent_pos)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|         shapesanity_two_sided[SHAPESANITY.two_one(first_combo, second_combo, SHAPESANITY.cornered_pos)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|         for third_combo in combos: | ||||
|             if third_combo in [first_combo, second_combo]: | ||||
|                 continue | ||||
|             # 3-part shapes | ||||
|             colors = [first_combo[1], second_combo[1], third_combo[1]] | ||||
|             color_region = color_to_needed_building(colors) | ||||
|             ordered_two = " ".join(sorted([second_combo, third_combo])) | ||||
|             if not (first_combo[1] == second_combo[1] == third_combo[1] or | ||||
|                     first_combo[0] == second_combo[0] == third_combo[0]): | ||||
|                 ordered_all = " ".join(sorted([first_combo, second_combo, third_combo])) | ||||
|                 shapesanity_three_parts[SHAPESANITY.singles(ordered_all)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|             shape_regions = ([REGIONS.stitched, REGIONS.stitched] if not second_combo[0] == third_combo[0] | ||||
|                              else (([REGIONS.east_wind, REGIONS.east_wind] if first_combo[0] == "W" | ||||
|                                     else [REGIONS.col_full, REGIONS.col_full]) | ||||
|                                    if first_combo[0] == second_combo[0] else [REGIONS.col_half_half, REGIONS.stitched])) | ||||
|             shapesanity_three_parts[SHAPESANITY.two_one_one(first_combo, ordered_two, SHAPESANITY.adjacent_pos)] \ | ||||
|                 = REGIONS.sanity(shape_regions[0], color_region) | ||||
|             shapesanity_three_parts[SHAPESANITY.two_one_one(first_combo, ordered_two, SHAPESANITY.cornered_pos)] \ | ||||
|                 = REGIONS.sanity(shape_regions[1], color_region) | ||||
|             for fourth_combo in combos: | ||||
|                 if fourth_combo in [first_combo, second_combo, third_combo]: | ||||
|                     continue | ||||
|                 if (first_combo[1] == second_combo[1] == third_combo[1] == fourth_combo[1] or | ||||
|                     first_combo[0] == second_combo[0] == third_combo[0] == fourth_combo[0]): | ||||
|                     continue | ||||
|                 colors = [first_combo[1], second_combo[1], third_combo[1], fourth_combo[1]] | ||||
|                 color_region = color_to_needed_building(colors) | ||||
|                 ordered_all = " ".join(sorted([first_combo, second_combo, third_combo, fourth_combo])) | ||||
|                 if ((first_combo[0] == second_combo[0] and third_combo[0] == fourth_combo[0]) or | ||||
|                     (first_combo[0] == third_combo[0] and second_combo[0] == fourth_combo[0]) or | ||||
|                     (first_combo[0] == fourth_combo[0] and third_combo[0] == second_combo[0])): | ||||
|                     shapesanity_four_parts[SHAPESANITY.singles(ordered_all)] = REGIONS.sanity(REGIONS.col_half_half, color_region) | ||||
|                 else: | ||||
|                     shapesanity_four_parts[SHAPESANITY.singles(ordered_all)] = REGIONS.sanity(REGIONS.stitched, color_region) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     start = time.time() | ||||
|     generate_shapesanity_pool() | ||||
|     print(time.time() - start) | ||||
|     with open("shapesanity_pool.py", "w") as outfile: | ||||
|         outfile.writelines(["shapesanity_simple = {\n"] | ||||
|                            + [f"    \"{name}\": \"{shapesanity_simple[name]}\",\n" | ||||
|                               for name in shapesanity_simple] | ||||
|                            + ["}\n\nshapesanity_1_4 = {\n"] | ||||
|                            + [f"    \"{name}\": \"{shapesanity_1_4[name]}\",\n" | ||||
|                               for name in shapesanity_1_4] | ||||
|                            + ["}\n\nshapesanity_two_sided = {\n"] | ||||
|                            + [f"    \"{name}\": \"{shapesanity_two_sided[name]}\",\n" | ||||
|                               for name in shapesanity_two_sided] | ||||
|                            + ["}\n\nshapesanity_three_parts = {\n"] | ||||
|                            + [f"    \"{name}\": \"{shapesanity_three_parts[name]}\",\n" | ||||
|                               for name in shapesanity_three_parts] | ||||
|                            + ["}\n\nshapesanity_four_parts = {\n"] | ||||
|                            + [f"    \"{name}\": \"{shapesanity_four_parts[name]}\",\n" | ||||
|                               for name in shapesanity_four_parts] | ||||
|                            + ["}\n"]) | ||||
							
								
								
									
										4
									
								
								worlds/shapez/data/options.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								worlds/shapez/data/options.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| { | ||||
|   "max_levels_and_upgrades": 500, | ||||
|   "max_shapesanity": 1000 | ||||
| } | ||||
							
								
								
									
										75814
									
								
								worlds/shapez/data/shapesanity_pool.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75814
									
								
								worlds/shapez/data/shapesanity_pool.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										337
									
								
								worlds/shapez/data/strings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								worlds/shapez/data/strings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,337 @@ | ||||
|  | ||||
| class OTHER: | ||||
|     game_name = "shapez" | ||||
|  | ||||
|  | ||||
| class SLOTDATA: | ||||
|     goal = "goal" | ||||
|     maxlevel = "maxlevel" | ||||
|     finaltier = "finaltier" | ||||
|     req_shapes_mult = "required_shapes_multiplier" | ||||
|     allow_float_layers = "allow_floating_layers" | ||||
|     rand_level_req = "randomize_level_requirements" | ||||
|     rand_upgrade_req = "randomize_upgrade_requirements" | ||||
|     rand_level_logic = "randomize_level_logic" | ||||
|     rand_upgrade_logic = "randomize_upgrade_logic" | ||||
|     throughput_levels_ratio = "throughput_levels_ratio" | ||||
|     comp_growth_gradient = "complexity_growth_gradient" | ||||
|     same_late = "same_late_upgrade_requirements" | ||||
|     toolbar_shuffling = "toolbar_shuffling" | ||||
|     seed = "seed" | ||||
|     shapesanity = "shapesanity" | ||||
|  | ||||
|     @staticmethod | ||||
|     def level_building(number: int) -> str: | ||||
|         return f"Level building {number}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def upgrade_building(number: int) -> str: | ||||
|         return f"Upgrade building {number}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def phase_length(number: int) -> str: | ||||
|         return f"Phase {number} length" | ||||
|  | ||||
|     @staticmethod | ||||
|     def cat_buildings_amount(category: str) -> str: | ||||
|         return f"{category} category buildings amount" | ||||
|  | ||||
|  | ||||
| class GOALS: | ||||
|     vanilla = "vanilla" | ||||
|     mam = "mam" | ||||
|     even_fasterer = "even_fasterer" | ||||
|     efficiency_iii = "efficiency_iii" | ||||
|  | ||||
|  | ||||
| class CATEGORY: | ||||
|     belt = "Belt" | ||||
|     miner = "Miner" | ||||
|     processors = "Processors" | ||||
|     painting = "Painting" | ||||
|     random = "Random" | ||||
|     belt_low = "belt" | ||||
|     miner_low = "miner" | ||||
|     processors_low = "processors" | ||||
|     painting_low = "painting" | ||||
|     big = "Big" | ||||
|     small = "Small" | ||||
|     gigantic = "Gigantic" | ||||
|     rising = "Rising" | ||||
|     demonic = "Demonic" | ||||
|  | ||||
|  | ||||
| class OPTIONS: | ||||
|     logic_vanilla = "vanilla" | ||||
|     logic_stretched = "stretched" | ||||
|     logic_quick = "quick" | ||||
|     logic_random_steps = "random_steps" | ||||
|     logic_hardcore = "hardcore" | ||||
|     logic_dopamine = "dopamine" | ||||
|     logic_dopamine_overflow = "dopamine_overflow" | ||||
|     logic_vanilla_like = "vanilla_like" | ||||
|     logic_linear = "linear" | ||||
|     logic_category = "category" | ||||
|     logic_category_random = "category_random" | ||||
|     logic_shuffled = "shuffled" | ||||
|     sphere_1 = "sphere_1" | ||||
|     buildings_3 = "3_buildings" | ||||
|     buildings_5 = "5_buildings" | ||||
|  | ||||
|  | ||||
| class REGIONS: | ||||
|     menu = "Menu" | ||||
|     belt = "Shape transportation" | ||||
|     extract = "Shape extraction" | ||||
|     main = "Main" | ||||
|     levels_1 = "Levels with 1 building" | ||||
|     levels_2 = "Levels with 2 buildings" | ||||
|     levels_3 = "Levels with 3 buildings" | ||||
|     levels_4 = "Levels with 4 buildings" | ||||
|     levels_5 = "Levels with 5 buildings" | ||||
|     upgrades_1 = "Upgrades with 1 building" | ||||
|     upgrades_2 = "Upgrades with 2 buildings" | ||||
|     upgrades_3 = "Upgrades with 3 buildings" | ||||
|     upgrades_4 = "Upgrades with 4 buildings" | ||||
|     upgrades_5 = "Upgrades with 5 buildings" | ||||
|     paint_not_quad = "Achievements with (double) painter" | ||||
|     cut_not_quad = "Achievements with half cutter" | ||||
|     rotate_cw = "Achievements with clockwise rotator" | ||||
|     stack_shape = "Achievements with stacker" | ||||
|     store_shape = "Achievements with storage" | ||||
|     trash_shape = "Achievements with trash" | ||||
|     blueprint = "Achievements with blueprints" | ||||
|     wiring = "Achievements with wires" | ||||
|     mam = "Achievements needing a MAM" | ||||
|     any_building = "Achievements with any placeable building" | ||||
|     all_buildings = "Achievements with all main buildings" | ||||
|     all_buildings_x1_6_belt = "Achievements with x1.6 belt speed" | ||||
|     full = "Full" | ||||
|     half = "Half" | ||||
|     piece = "Piece" | ||||
|     stitched = "Stitched" | ||||
|     east_wind = "East Windmill" | ||||
|     half_half = "Half-Half" | ||||
|     col_east_wind = "Colorful East Windmill" | ||||
|     col_half_half = "Colorful Half-Half" | ||||
|     col_full = "Colorful Full" | ||||
|     col_half = "Colorful Half" | ||||
|     uncol = "Uncolored" | ||||
|     painted = "Painted" | ||||
|     mixed = "Mixed" | ||||
|  | ||||
|     @staticmethod | ||||
|     def sanity(processing: str, coloring: str): | ||||
|         return f"Shapesanity {processing} {coloring}" | ||||
|  | ||||
|  | ||||
| class LOCATIONS: | ||||
|     my_eyes = "My eyes no longer hurt" | ||||
|     painter = "Painter" | ||||
|     cutter = "Cutter" | ||||
|     rotater = "Rotater" | ||||
|     wait_they_stack = "Wait, they stack?" | ||||
|     wires = "Wires" | ||||
|     storage = "Storage" | ||||
|     freedom = "Freedom" | ||||
|     the_logo = "The logo!" | ||||
|     to_the_moon = "To the moon" | ||||
|     its_piling_up = "It's piling up" | ||||
|     use_it_later = "I'll use it later" | ||||
|     efficiency_1 = "Efficiency 1" | ||||
|     preparing_to_launch = "Preparing to launch" | ||||
|     spacey = "SpaceY" | ||||
|     stack_overflow = "Stack overflow" | ||||
|     its_a_mess = "It's a mess" | ||||
|     faster = "Faster" | ||||
|     even_faster = "Even faster" | ||||
|     get_rid_of_them = "Get rid of them" | ||||
|     a_long_time = "It's been a long time" | ||||
|     addicted = "Addicted" | ||||
|     cant_stop = "Can't stop" | ||||
|     is_this_the_end = "Is this the end?" | ||||
|     getting_into_it = "Getting into it" | ||||
|     now_its_easy = "Now it's easy" | ||||
|     computer_guy = "Computer Guy" | ||||
|     speedrun_master = "Speedrun Master" | ||||
|     speedrun_novice = "Speedrun Novice" | ||||
|     not_idle_game = "Not an idle game" | ||||
|     efficiency_2 = "Efficiency 2" | ||||
|     branding_1 = "Branding specialist 1" | ||||
|     branding_2 = "Branding specialist 2" | ||||
|     king_of_inefficiency = "King of Inefficiency" | ||||
|     its_so_slow = "It's so slow" | ||||
|     mam = "MAM (Make Anything Machine)" | ||||
|     perfectionist = "Perfectionist" | ||||
|     next_dimension = "The next dimension" | ||||
|     oops = "Oops" | ||||
|     copy_pasta = "Copy-Pasta" | ||||
|     ive_seen_that_before = "I've seen that before ..." | ||||
|     memories = "Memories from the past" | ||||
|     i_need_trains = "I need trains" | ||||
|     a_bit_early = "A bit early?" | ||||
|     gps = "GPS" | ||||
|     goal = "Goal" | ||||
|  | ||||
|     @staticmethod | ||||
|     def level(number: int, additional: int = 0) -> str: | ||||
|         if not additional: | ||||
|             return f"Level {number}" | ||||
|         elif additional == 1: | ||||
|             return f"Level {number} Additional" | ||||
|         else: | ||||
|             return f"Level {number} Additional {additional}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def upgrade(category: str, tier: str) -> str: | ||||
|         return f"{category} Upgrade Tier {tier}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def shapesanity(number: int) -> str: | ||||
|         return f"Shapesanity {number}" | ||||
|  | ||||
|  | ||||
| class ITEMS: | ||||
|     cutter = "Cutter" | ||||
|     cutter_quad = "Quad Cutter" | ||||
|     rotator = "Rotator" | ||||
|     rotator_ccw = "Rotator (CCW)" | ||||
|     rotator_180 = "Rotator (180°)" | ||||
|     stacker = "Stacker" | ||||
|     painter = "Painter" | ||||
|     painter_double = "Double Painter" | ||||
|     painter_quad = "Quad Painter" | ||||
|     color_mixer = "Color Mixer" | ||||
|  | ||||
|     belt = "Belt" | ||||
|     extractor = "Extractor" | ||||
|     extractor_chain = "Chaining Extractor" | ||||
|     balancer = "Balancer" | ||||
|     comp_merger = "Compact Merger" | ||||
|     comp_splitter = "Compact Splitter" | ||||
|     tunnel = "Tunnel" | ||||
|     tunnel_tier_ii = "Tunnel Tier II" | ||||
|     trash = "Trash" | ||||
|  | ||||
|     belt_reader = "Belt Reader" | ||||
|     storage = "Storage" | ||||
|     switch = "Switch" | ||||
|     item_filter = "Item Filter" | ||||
|     display = "Display" | ||||
|     wires = "Wires" | ||||
|     const_signal = "Constant Signal" | ||||
|     logic_gates = "Logic Gates" | ||||
|     virtual_proc = "Virtual Processing" | ||||
|     blueprints = "Blueprints" | ||||
|  | ||||
|     upgrade_big_belt = "Big Belt Upgrade" | ||||
|     upgrade_big_miner = "Big Miner Upgrade" | ||||
|     upgrade_big_proc = "Big Processors Upgrade" | ||||
|     upgrade_big_paint = "Big Painting Upgrade" | ||||
|     upgrade_small_belt = "Small Belt Upgrade" | ||||
|     upgrade_small_miner = "Small Miner Upgrade" | ||||
|     upgrade_small_proc = "Small Processors Upgrade" | ||||
|     upgrade_small_paint = "Small Painting Upgrade" | ||||
|     upgrade_gigantic_belt = "Gigantic Belt Upgrade" | ||||
|     upgrade_gigantic_miner = "Gigantic Miner Upgrade" | ||||
|     upgrade_gigantic_proc = "Gigantic Processors Upgrade" | ||||
|     upgrade_gigantic_paint = "Gigantic Painting Upgrade" | ||||
|     upgrade_rising_belt = "Rising Belt Upgrade" | ||||
|     upgrade_rising_miner = "Rising Miner Upgrade" | ||||
|     upgrade_rising_proc = "Rising Processors Upgrade" | ||||
|     upgrade_rising_paint = "Rising Painting Upgrade" | ||||
|     trap_upgrade_belt = "Belt Upgrade Trap" | ||||
|     trap_upgrade_miner = "Miner Upgrade Trap" | ||||
|     trap_upgrade_proc = "Processors Upgrade Trap" | ||||
|     trap_upgrade_paint = "Painting Upgrade Trap" | ||||
|     trap_upgrade_demonic_belt = "Demonic Belt Upgrade Trap" | ||||
|     trap_upgrade_demonic_miner = "Demonic Miner Upgrade Trap" | ||||
|     trap_upgrade_demonic_proc = "Demonic Processors Upgrade Trap" | ||||
|     trap_upgrade_demonic_paint = "Demonic Painting Upgrade Trap" | ||||
|     upgrade_big_random = "Big Random Upgrade" | ||||
|     upgrade_small_random = "Small Random Upgrade" | ||||
|  | ||||
|     @staticmethod | ||||
|     def upgrade(size: str, category: str) -> str: | ||||
|         return f"{size} {category} Upgrade" | ||||
|  | ||||
|     @staticmethod | ||||
|     def trap_upgrade(category: str, size: str = "") -> str: | ||||
|         return f"{size} {category} Upgrade Trap".strip() | ||||
|  | ||||
|     bundle_blueprint = "Blueprint Shapes Bundle" | ||||
|     bundle_level = "Level Shapes Bundle" | ||||
|     bundle_upgrade = "Upgrade Shapes Bundle" | ||||
|  | ||||
|     trap_locked = "Locked Building Trap" | ||||
|     trap_throttled = "Throttled Building Trap" | ||||
|     trap_malfunction = "Malfunctioning Trap" | ||||
|     trap_inflation = "Inflation Trap" | ||||
|     trap_draining_inv = "Inventory Draining Trap" | ||||
|     trap_draining_blueprint = "Blueprint Shapes Draining Trap" | ||||
|     trap_draining_level = "Level Shapes Draining Trap" | ||||
|     trap_draining_upgrade = "Upgrade Shapes Draining Trap" | ||||
|     trap_clear_belts = "Belts Clearing Trap" | ||||
|  | ||||
|     goal = "Goal" | ||||
|  | ||||
|  | ||||
| class SHAPESANITY: | ||||
|     circle = "Circle" | ||||
|     square = "Square" | ||||
|     star = "Star" | ||||
|     windmill = "Windmill" | ||||
|     red = "Red" | ||||
|     blue = "Blue" | ||||
|     green = "Green" | ||||
|     yellow = "Yellow" | ||||
|     purple = "Purple" | ||||
|     cyan = "Cyan" | ||||
|     white = "White" | ||||
|     uncolored = "Uncolored" | ||||
|     adjacent_pos = "Adjacent" | ||||
|     cornered_pos = "Cornered" | ||||
|  | ||||
|     @staticmethod | ||||
|     def full(color: str, subshape: str): | ||||
|         return f"{color} {subshape}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def half(color: str, subshape: str): | ||||
|         return f"Half {color} {subshape}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def piece(color: str, subshape: str): | ||||
|         return f"{color} {subshape} Piece" | ||||
|  | ||||
|     @staticmethod | ||||
|     def cutout(color: str, subshape: str): | ||||
|         return f"Cut Out {color} {subshape}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def cornered(color: str, subshape: str): | ||||
|         return f"Cornered {color} {subshape}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def three_one(first: str, second: str): | ||||
|         return f"3-1 {first} {second}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def halfhalf(combo: str): | ||||
|         return f"Half-Half {combo}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def checkered(combo: str): | ||||
|         return f"Checkered {combo}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def singles(combo: str, position: str = ""): | ||||
|         return f"{position} Singles {combo}".strip() | ||||
|  | ||||
|     @staticmethod | ||||
|     def two_one(first: str, second: str, position: str): | ||||
|         return f"{position} 2-1 {first} {second}" | ||||
|  | ||||
|     @staticmethod | ||||
|     def two_one_one(first: str, second: str, position: str): | ||||
|         return f"{position} 2-1-1 {first} {second}" | ||||
							
								
								
									
										35
									
								
								worlds/shapez/docs/datapackage_settings_de.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								worlds/shapez/docs/datapackage_settings_de.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # Anleitung zum Ändern der maximalen Anzahl an Locations in shapez | ||||
|  | ||||
| ## Wo finde ich die Einstellungen zum Erhöhen/Verringern der maximalen Anzahl an Locations? | ||||
|  | ||||
| Die Maximalwerte von `goal_amount` und `shapesanity_amount` sind fest eingebaute Einstellungen, die das Datenpaket des  | ||||
| Spiels beeinflussen. Sie sind in einer Datei names `options.json` innerhalb der APWorld festgelegt. Durch das Ändern  | ||||
| dieser Werte erschaffst du eine custom APWorld, die nur auf deinem PC existiert. | ||||
|  | ||||
| ## Wie du die Datenpaket-Einstellungen änderst | ||||
|  | ||||
| Diese Anleitung ist für erfahrene Nutzer und kann in nicht richtig funktionierender Software resultieren, wenn sie nicht | ||||
| ordnungsgemäß befolgt wird. Anwendung auf eigene Gefahr. | ||||
|  | ||||
| 1. Navigiere zu `<AP-Installation>/lib/worlds`. | ||||
| 2. Benenne `shapez.apworld` zu `shapez.zip` um. | ||||
| 3. Öffne die Zip-Datei und navigiere zu `shapez/data/options.json`. | ||||
| 4. Ändere die Werte in dieser Datei nach Belieben und speichere die Datei. | ||||
|    - `max_shapesanity` kann nicht weniger als `4` sein, da dies die benötigte Mindestanzahl zum Verhindern von  | ||||
|       FillErrors ist. | ||||
|    - `max_shapesanity` kann auch nicht mehr als `75800` sein, da dies die maximale Anzahl an möglichen Shapesanity-Namen | ||||
|      ist. Ansonsten könnte die Generierung der Multiworld fehlschlagen. | ||||
|    - `max_levels_and_upgrades` kann nicht weniger als `27` sein, da dies die Mindestanzahl für das `mam`-Ziel ist. | ||||
| 5. Schließe die Zip-Datei und benenne sie zurück zu `shapez.apworld`. | ||||
|  | ||||
| ## Warum muss ich das ganze selbst machen? | ||||
|  | ||||
| Alle Spiele in Archipelago müssen eine Liste aller möglichen Locations **unabhängig der Spieler-Optionen**  | ||||
| bereitstellen. Diese Listen aller in einer Multiworld inkludierten Spiele werden in den Daten der Multiworld gespeichert | ||||
| und an alle verbundenen Clients gesendet. Je mehr mögliche Locations, desto größer das Datenpaket. Und mit ~80000  | ||||
| möglichen Locations hatte shapez zu einem gewissen Zeitpunkt ein (von der Datenmenge her) größeres Datenpaket als alle  | ||||
| supporteten Spiele zusammen. Um also diese Datenmenge zu reduzieren wurden die ausgeschriebenen  | ||||
| Shapesanity-Locations-Namen (`Shapesanity Uncolored Circle`, `Shapesanity Blue Rectangle`, ...) durch standardisierte  | ||||
| Namen (`Shapesanity 1`, `Shapesanity 2`, ...) ersetzt. Durch das Ändern dieser Maximalwerte, und damit das Erstellen  | ||||
| einer custom APWorld, kannst du die Anzahl der möglichen Locations erhöhen, wirst aber auch gleichzeitig das Datenpaket  | ||||
| vergrößern. | ||||
							
								
								
									
										33
									
								
								worlds/shapez/docs/datapackage_settings_en.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								worlds/shapez/docs/datapackage_settings_en.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| # Guide to change maximum locations in shapez | ||||
|  | ||||
| ## Where do I find the settings to increase/decrease the amount of possible locations? | ||||
|  | ||||
| The maximum values of the `goal_amount` and `shapesanity_amount` are hardcoded settings that affect the datapackage.  | ||||
| They are stored in a file called `options.json` inside the apworld. By changing them, you will create a custom apworld  | ||||
| on your local machine. | ||||
|  | ||||
| ## How to change datapackage options | ||||
|  | ||||
| This tutorial is for advanced users and can result in the software not working properly, if not read carefully.  | ||||
| Proceed at your own risk. | ||||
|  | ||||
| 1. Go to `<AP installation>/lib/worlds`. | ||||
| 2. Rename `shapez.apworld` to `shapez.zip`. | ||||
| 3. Open the zip file and go to `shapez/data/options.json`. | ||||
| 4. Edit the values in this file to your desire and save the file. | ||||
|    - `max_shapesanity` cannot be lower than `4`, as this is the minimum amount to prevent FillErrors. | ||||
|    - `max_shapesanity` also cannot be higher than `75800`, as this is the maximum amount of possible shapesanity names.  | ||||
|      Else the multiworld generation might fail. | ||||
|    - `max_levels_and_upgrades` cannot be lower than `27`, as this is the minimum amount for the `mam` goal to properly  | ||||
|      work. | ||||
| 5. Close the zip and rename it back to `shapez.apworld`. | ||||
|  | ||||
| ## Why do I have to do this manually? | ||||
|  | ||||
| For every game in Archipelago, there must be a list of all possible locations, **regardless of player options**. When  | ||||
| generating a multiworld, a list of all locations of all included games will be saved in the multiworld data and sent to  | ||||
| all clients. The higher the amount of possible locations, the bigger the datapackage. And having ~80000 possible  | ||||
| locations at one point made the datapackage for shapez bigger than all other supported games combined. So to reduce the  | ||||
| datapackage of shapez, the locations for shapesanity are named `Shapesanity 1`, `Shapesanity 2` etc. instead of their  | ||||
| actual names. By creating a custom apworld, you can increase the amount of possible locations, but you will also  | ||||
| increase the size of the datapackage at the same time. | ||||
							
								
								
									
										71
									
								
								worlds/shapez/docs/de_shapez.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								worlds/shapez/docs/de_shapez.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| # shapez | ||||
|  | ||||
| ## Was für ein Spiel ist das? | ||||
|  | ||||
| shapez ist ein Automatisierungsspiel, in dem du Formen aus zufällig generierten Vorkommen in einer endlosen Welt  | ||||
| extrahierst, zerschneidest, rotierst, stapelst, anmalst und schließlich zum Zentrum beförderst, um Level abzuschließen | ||||
| und Upgrades zu kaufen. Das Tutorial beinhaltet 26 Level, in denen du (fast) immer ein neues Gebäude oder eine neue | ||||
| Spielmechanik freischaltest. Danach folgen endlos weitere Level mit zufällig generierten Vorgaben. Um das Spiel bzw. | ||||
| deine Gebäude schneller zu machen, kannst du bis zu 1000 Upgrades (pro Kategorie) kaufen. | ||||
|  | ||||
| ## Wo ist die Optionen-Seite? | ||||
|  | ||||
| Die [Spieler-Optionen-Seite für dieses Spiel](../player-options) enthält alle Optionen zum Erstellen und exportieren  | ||||
| einer YAML-Datei. | ||||
| Zusätzlich gibt es zu diesem Spiel "Datenpaket-Einstellungen", die du nach  | ||||
| [dieser Anleitung](/tutorial/shapez/datapackage_settings/de) einstellen kannst. | ||||
|  | ||||
| ## Inwiefern wird das Spiel randomisiert? | ||||
|  | ||||
| Alle Belohnungen aus den Tutorial-Level (das Freischalten von Gebäuden und Spielmechaniken) und Verbesserungen durch | ||||
| Upgrades werden dem Itempool der Multiworld hinzugefügt. Außerdem werden, wenn so in den Spieler-Optionen festgelegt, | ||||
| die Bedingungen zum Abschließen eines Levels und zum Kaufen der Upgrades randomisiert. | ||||
|  | ||||
| ## Was ist das Ziel von shapez in Archipelago? | ||||
|  | ||||
| Da das Spiel eigentlich kein konkretes Ziel (nach dem Tutorial) hat, kann man sich zwischen (momentan) 4 verschiedenen | ||||
| Zielen entscheiden: | ||||
| 1. Vanilla: Schließe Level 26 ab (eigentlich das Ende des Tutorials). | ||||
| 2. MAM: Schließe ein bestimmtes Level nach Level 26 ab, das zuvor in den Spieler-Optionen festgelegt wurde. Es ist | ||||
| empfohlen, eine Maschine zu bauen, die alles automatisch herstellt ("Make-Anything-Machine", kurz MAM). | ||||
| 3. Even Fasterer: Kaufe alle Upgrades bis zu einer in den Spieler-Optionen festgelegten Stufe (nach Stufe 8). | ||||
| 4. Efficiency III: Liefere 256 Blaupausen-Formen pro Sekunde ins Zentrum. | ||||
|  | ||||
| ## Welche Items können in den Welten anderer Spieler erscheinen? | ||||
|  | ||||
| - Freischalten verschiedener Gebäude | ||||
| - Blaupausen freischalten | ||||
| - Große Upgrades (addiert 1 zum Geschwindigkeitsmultiplikator) | ||||
| - Kleine Upgrades (addiert 0.1 zum Geschwindigkeitsmultiplikator) | ||||
| - Andere ungewöhnliche Upgrades (optional) | ||||
| - Verschiedene Bündel, die bestimmte Formen enthalten | ||||
| - Fallen, die bestimmte Formen aus dem Zentrum dränieren (ja, das Wort gibt es) | ||||
| - Fallen, die zufällige Gebäude oder andere Spielmechaniken betreffen | ||||
|  | ||||
| ## Was ist eine Location / ein Check? | ||||
|  | ||||
| - Level (minimum 1-25, bis zu 499 je nach Spieler-Optionen, mit zusätzlichen Checks für Level 1 und 20) | ||||
| - Upgrades (minimum Stufen II-VIII (2-8), bis zu D (500) je nach Spieler-Optionen) | ||||
| - Bestimmte Formen mindestens einmal ins Zentrum liefern ("Shapesanity", bis zu 1000 zufällig gewählte Definitionen) | ||||
| - Errungenschaften (bis zu 45) | ||||
|  | ||||
| ## Was passiert, wenn der Spieler ein Item erhält? | ||||
|  | ||||
| Ein Pop-Up erscheint, das das/die erhaltene(n) Item(s) und eventuell weitere Informationen auflistet. | ||||
|  | ||||
| ## Was bedeuten die Namen dieser ganzen Shapesanity Dinger? | ||||
|  | ||||
| Hier ist ein Spicker für die Englischarbeit (bloß nicht dem Lehrer zeigen): | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Kann ich auch weitere Mods neben dem AP Client installieren? | ||||
|  | ||||
| Zurzeit wird Kompatibilität mit anderen Mods nicht unterstützt, aber niemand kann dich davon abhalten, es trotzdem zu | ||||
| versuchen. Mods, die das Gameplay verändern, werden wahrscheinlich nicht funktionieren, indem sie das Laden der  | ||||
| jeweiligen Mods verhindern oder das Spiel zum Abstürzen bringen, während einfache QoL-Mods vielleicht problemlos | ||||
| funktionieren könnten. Wenn du es versuchst, dann also auf eigene Gefahr. | ||||
|  | ||||
| ## Hast du wirklich eine deutschsprachige Infoseite geschrieben, obwohl man sie aktuell nur über Umwege erreichen kann und du eigentlich an dem Praktikumsportfolio arbeiten solltest? | ||||
|  | ||||
| Ja | ||||
							
								
								
									
										65
									
								
								worlds/shapez/docs/en_shapez.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								worlds/shapez/docs/en_shapez.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| # shapez | ||||
|  | ||||
| ## What is this game? | ||||
|  | ||||
| shapez is an automation game about cutting, rotating, stacking, and painting shapes, that you extract from randomly | ||||
| generated patches on an infinite canvas, and sending them to the hub to complete levels. The "tutorial", where you | ||||
| unlock a new building or game mechanic (almost) each level, lasts until level 26, where you unlock freeplay with  | ||||
| infinitely more levels, that require a new, randomly generated shape. Alongside the levels, you can unlock upgrades, | ||||
| that make your buildings work faster. | ||||
|  | ||||
| ## Where is the options page? | ||||
|  | ||||
| The [player options page for this game](../player-options) contains all the options you need to configure | ||||
| and export a config file. | ||||
| There are also some advanced "datapackage settings" that can be changed by following  | ||||
| [this guide](/tutorial/shapez/datapackage_settings/en). | ||||
|  | ||||
| ## What does randomization do to this game? | ||||
|  | ||||
| Buildings and gameplay mechanics, that you normally unlock by completing a level, and upgrade improvements are put  | ||||
| into the item pool of the multiworld. Also, if enabled, the requirements for completing a level or buying an upgrade are | ||||
| randomized. | ||||
|  | ||||
| ## What is the goal of shapez in Archipelago? | ||||
|  | ||||
| As the game has no actual goal where the game ends, there are (currently) 4 different goals you can choose from in the  | ||||
| player options: | ||||
| 1. Vanilla: Complete level 26 (the end of the tutorial). | ||||
| 2. MAM: Complete a player-specified level after level 26. It's recommended to build a Make-Anything-Machine (MAM). | ||||
| 3. Even Fasterer: Upgrade everything to a player-specified tier after tier 8. | ||||
| 4. Efficiency III: Deliver 256 blueprint shapes per second to the hub. | ||||
|  | ||||
| ## Which items can be in another player's world? | ||||
|  | ||||
| - Unlock different buildings | ||||
| - Unlock blueprints | ||||
| - Big upgrade improvements (adds 1 to the multiplier) | ||||
| - Small upgrade improvements (adds .1 to the multiplier) | ||||
| - Other unusual upgrade improvements (optional) | ||||
| - Different shapes bundles | ||||
| - Inventory draining traps | ||||
| - Different traps afflicting random buildings and game mechanics | ||||
|  | ||||
| ## What is considered a location check? | ||||
|  | ||||
| - Levels (minimum 1-25, up to 499 depending on player options, with additional checks for levels 1 and 20) | ||||
| - Upgrades (minimum tiers II-VIII (2-8), up to D (500) depending on player options) | ||||
| - Delivering certain shapes at least once to the hub ("shapesanity", up to 1000 from a 75800 names pool) | ||||
| - Achievements (up to 45) | ||||
|  | ||||
| ## When the player receives an item, what happens? | ||||
|  | ||||
| A pop-up will show, which item(s) were received, with additional information on some of them. | ||||
|  | ||||
| ## What do the names of all these shapesanity locations mean? | ||||
|  | ||||
| Here's a cheat sheet: | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Can I use other mods alongside the AP client? | ||||
|  | ||||
| At the moment, compatibility with other mods is not supported, but not forbidden. Gameplay altering mods will most | ||||
| likely crash the game or disable loading the afflicted mods, while QoL mods might work without problems. Try at your own | ||||
| risk. | ||||
							
								
								
									
										62
									
								
								worlds/shapez/docs/setup_de.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								worlds/shapez/docs/setup_de.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| # Setup-Anleitung für shapez: Archipelago | ||||
|  | ||||
| ## Schnelle Links | ||||
|  | ||||
| - Info-Seite zum Spiel | ||||
|     * [English](/games/shapez/info/en) | ||||
|     * [Deutsch](/games/shapez/info/de) | ||||
| - [Spieler-Optionen-Seite](/games/shapez/player-options) | ||||
|  | ||||
| ## Benötigte Software | ||||
|  | ||||
| - Eine installierbare und aktuelle PC-Version von shapez ([Steam](https://store.steampowered.com/app/1318690/shapez/)). | ||||
| - Die shapezipelago Mod von der [mod.io-Seite](https://mod.io/g/shapez/m/shapezipelago). | ||||
|  | ||||
| ## Optionale Software | ||||
|  | ||||
| - Archipelago von der [Archipelago-Release-Seite](https://github.com/ArchipelagoMW/Archipelago/releases) | ||||
|     * (Für den Text-Client) | ||||
|     * (Alternativ kannst du auch die eingebaute Konsole (nur lesbar) nutzen, indem du beim Starten des Spiels den  | ||||
|   `-dev`-Parameter verwendest) | ||||
| - Universal Tracker (schau im `#future-game-design`-Thread für UT auf dem Discord-Server nach der aktuellen Anleitung) | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| Da das Spiel einen eingebauten Mod-Loader hat, musst du nur die "shapezipelago@X.X.X.js"-Datei in den dafür vorgesehenen | ||||
| Ordner kopieren. Wenn du nicht weißt, wo dieser ist, dann öffne das Spiel, drücke auf "MODS" und schließlich auf | ||||
| "MODORDNER ÖFFNEN". | ||||
|  | ||||
| Du solltest (egal ob vor oder nach der Installation) die Einstellungen des Spiels öffnen und `HINWEISE & TUTORIALS` im  | ||||
| Reiter `BENUTZEROBERFLÄCHE` ausschalten, da sie sonst den Upgrade-Shop verstecken wird, bis du ein paar Level  | ||||
| abgeschlossen hast. | ||||
|  | ||||
| ## Erstellen deiner YAML-Datei | ||||
|  | ||||
| ### Was ist eine YAML-Datei und wofür brauche ich die? | ||||
|  | ||||
| Deine persönliche YAML-Datei beinhaltet eine Reihe von Optionen, die der Zufallsgenerator zum Erstellen von deinem  | ||||
| Spiel benötigt. Jeder Spieler einer Multiworld stellt seine eigene YAML-Datei zur Verfügung. Dadurch kann jeder Spieler  | ||||
| sein Spiel nach seinem eigenen Geschmack gestalten, während andere Spieler unabhängig davon ihre eigenen Optionen  | ||||
| wählen können! | ||||
|  | ||||
| ### Wo bekomme ich so eine YAML-Datei her? | ||||
|  | ||||
| Du kannst auf der [shapez-Spieler-Optionen-Seite](/games/shapez/player-options) eine YAML-Datei generieren oder ein  | ||||
| Template herunterladen. | ||||
|  | ||||
| ## Einer MultiWorld beitreten | ||||
|  | ||||
| 1. Öffne das Spiel. | ||||
| 2. Gib im Hauptmenü den Slot-Namen, die Adresse, den Port und das Passwort (optional) in die dafür vorgesehene Box ein. | ||||
| 3. Drücke auf "Connect". | ||||
|    - Erneutes Drücken trennt die Verbindung zum Server. | ||||
|    - Ob du verbunden bist, steht direkt daneben. | ||||
| 4. Starte ein neues Spiel. | ||||
|  | ||||
| Nachdem der Speicherstand erstellt wurde und du zum Hauptmenü zurückkehrst, wird das erneute Öffnen des Speicherstandes  | ||||
| erneut verbinden. | ||||
|  | ||||
| ### Der Port/Die Adresse der MultiWorld hat sich geändert, wie trete ich mit meinem existierenden Speicherstand bei?  | ||||
|  | ||||
| Wiederhole die Schritte 1-3 und öffne den existierenden Speicherstand. Dies wird außerdem die gespeicherten Login-Daten  | ||||
| überschreiben, sodass du dies nur einmal machen musst.  | ||||
							
								
								
									
										58
									
								
								worlds/shapez/docs/setup_en.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								worlds/shapez/docs/setup_en.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| # Setup Guide for shapez: Archipelago | ||||
|  | ||||
| ## Quick Links | ||||
|  | ||||
| - Game Info Page | ||||
|     * [English](/games/shapez/info/en) | ||||
|     * [Deutsch](/games/shapez/info/de) | ||||
| - [Player Options Page](/games/shapez/player-options) | ||||
|  | ||||
| ## Required Software | ||||
|  | ||||
| - An installable, up-to-date PC version of shapez ([Steam](https://store.steampowered.com/app/1318690/shapez/)). | ||||
| - The shapezipelago mod from the [mod.io page](https://mod.io/g/shapez/m/shapezipelago). | ||||
|  | ||||
| ## Optional Software | ||||
|  | ||||
| - Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) | ||||
|     * (Only for the TextClient) | ||||
|     * (If you want, you can use the built-in console as a read-only text client by launching the game  | ||||
|   with the `-dev` parameter) | ||||
| - Universal Tracker (check UT's `#future-game-design` thread in the discord server for instructions) | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| As the game has a built-in mod loader, all you need to do is copy the `shapezipelago@X.X.X.js` mod file into the mods | ||||
| folder. If you don't know where that is, open the game, click on `MODS`, and then `OPEN MODS FOLDER`. | ||||
|  | ||||
| It is recommended to go into the settings of the game and disable `HINTS & TUTORIALS` in the `USER INTERFACE` tab, as  | ||||
| this setting will disable the upgrade shop until you complete a few levels. | ||||
|  | ||||
| ## Configuring your YAML file | ||||
|  | ||||
| ### What is a YAML file and why do I need one? | ||||
|  | ||||
| Your YAML file contains a set of configuration options which provide the generator with information about how it should | ||||
| generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy | ||||
| an experience customized for their taste, and different players in the same multiworld can all have different options. | ||||
|  | ||||
| ### Where do I get a YAML file? | ||||
|  | ||||
| You can generate a yaml or download a template by visiting the  | ||||
| [shapez Player Options Page](/games/shapez/player-options) | ||||
|  | ||||
| ## Joining a MultiWorld Game | ||||
|  | ||||
| 1. Open the game. | ||||
| 2. In the main menu, type the slot name, address, port, and password (optional) into the input box. | ||||
| 3. Click "Connect". | ||||
|    - To disconnect, just press this button again. | ||||
|    - The status of your connection is shown right next to the button. | ||||
| 4. Create a new game. | ||||
|  | ||||
| After creating the save file and returning to the main menu, opening the save file again will automatically reconnect.  | ||||
|  | ||||
| ### The MultiWorld changed its port/address, how do I reconnect correctly with my existing save file?  | ||||
|  | ||||
| Repeat steps 1-3 and open the existing save file. This will also overwrite the saved connection details, so you will  | ||||
| only have to do this once.  | ||||
							
								
								
									
										
											BIN
										
									
								
								worlds/shapez/docs/shapesanity_full.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								worlds/shapez/docs/shapesanity_full.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 124 KiB | 
							
								
								
									
										279
									
								
								worlds/shapez/items.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								worlds/shapez/items.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| from typing import Dict, Callable, Any, List | ||||
|  | ||||
| from BaseClasses import Item, ItemClassification as IClass | ||||
| from .options import ShapezOptions | ||||
| from .data.strings import GOALS, ITEMS, OTHER | ||||
|  | ||||
|  | ||||
| def is_mam_achievement_included(options: ShapezOptions) -> IClass: | ||||
|     return IClass.progression if options.include_achievements and (not options.goal == GOALS.vanilla) else IClass.useful | ||||
|  | ||||
|  | ||||
| def is_achievements_included(options: ShapezOptions) -> IClass: | ||||
|     return IClass.progression if options.include_achievements else IClass.useful | ||||
|  | ||||
|  | ||||
| def is_goal_efficiency_iii(options: ShapezOptions) -> IClass: | ||||
|     return IClass.progression if options.goal == GOALS.efficiency_iii else IClass.useful | ||||
|  | ||||
|  | ||||
| def always_progression(options: ShapezOptions) -> IClass: | ||||
|     return IClass.progression | ||||
|  | ||||
|  | ||||
| def always_useful(options: ShapezOptions) -> IClass: | ||||
|     return IClass.useful | ||||
|  | ||||
|  | ||||
| def always_filler(options: ShapezOptions) -> IClass: | ||||
|     return IClass.filler | ||||
|  | ||||
|  | ||||
| def always_trap(options: ShapezOptions) -> IClass: | ||||
|     return IClass.trap | ||||
|  | ||||
|  | ||||
| # Routing buildings are not needed to complete the game, but building factories without balancers and tunnels | ||||
| # would be unreasonably complicated and time-consuming. | ||||
| # Some buildings are not needed to complete the game, but are "logically needed" for the "MAM" achievement. | ||||
|  | ||||
| buildings_processing: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.cutter: always_progression, | ||||
|     ITEMS.cutter_quad: always_progression, | ||||
|     ITEMS.rotator: always_progression, | ||||
|     ITEMS.rotator_ccw: always_progression, | ||||
|     ITEMS.rotator_180: always_progression, | ||||
|     ITEMS.stacker: always_progression, | ||||
|     ITEMS.painter: always_progression, | ||||
|     ITEMS.painter_double: always_progression, | ||||
|     ITEMS.painter_quad: always_progression, | ||||
|     ITEMS.color_mixer: always_progression, | ||||
| } | ||||
|  | ||||
| buildings_routing: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.balancer: always_progression, | ||||
|     ITEMS.comp_merger: always_progression, | ||||
|     ITEMS.comp_splitter: always_progression, | ||||
|     ITEMS.tunnel: always_progression, | ||||
|     ITEMS.tunnel_tier_ii: is_mam_achievement_included, | ||||
| } | ||||
|  | ||||
| buildings_other: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.trash: always_progression, | ||||
|     ITEMS.extractor_chain: always_useful | ||||
| } | ||||
|  | ||||
| buildings_top_row: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.belt_reader: is_mam_achievement_included, | ||||
|     ITEMS.storage: is_achievements_included, | ||||
|     ITEMS.switch: always_progression, | ||||
|     ITEMS.item_filter: is_mam_achievement_included, | ||||
|     ITEMS.display: always_useful | ||||
| } | ||||
|  | ||||
| buildings_wires: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.wires: always_progression, | ||||
|     ITEMS.const_signal: always_progression, | ||||
|     ITEMS.logic_gates: is_mam_achievement_included, | ||||
|     ITEMS.virtual_proc: is_mam_achievement_included | ||||
| } | ||||
|  | ||||
| gameplay_unlocks: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.blueprints: is_achievements_included | ||||
| } | ||||
|  | ||||
| upgrades: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.upgrade_big_belt: always_progression, | ||||
|     ITEMS.upgrade_big_miner: always_useful, | ||||
|     ITEMS.upgrade_big_proc: always_useful, | ||||
|     ITEMS.upgrade_big_paint: always_useful, | ||||
|     ITEMS.upgrade_small_belt: always_filler, | ||||
|     ITEMS.upgrade_small_miner: always_filler, | ||||
|     ITEMS.upgrade_small_proc: always_filler, | ||||
|     ITEMS.upgrade_small_paint: always_filler | ||||
| } | ||||
|  | ||||
| whacky_upgrades: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.upgrade_gigantic_belt: always_progression, | ||||
|     ITEMS.upgrade_gigantic_miner: always_useful, | ||||
|     ITEMS.upgrade_gigantic_proc: always_useful, | ||||
|     ITEMS.upgrade_gigantic_paint: always_useful, | ||||
|     ITEMS.upgrade_rising_belt: always_progression, | ||||
|     ITEMS.upgrade_rising_miner: always_useful, | ||||
|     ITEMS.upgrade_rising_proc: always_useful, | ||||
|     ITEMS.upgrade_rising_paint: always_useful, | ||||
|     ITEMS.upgrade_big_random: always_useful, | ||||
|     ITEMS.upgrade_small_random: always_filler, | ||||
| } | ||||
|  | ||||
| whacky_upgrade_traps: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.trap_upgrade_belt: always_trap, | ||||
|     ITEMS.trap_upgrade_miner: always_trap, | ||||
|     ITEMS.trap_upgrade_proc: always_trap, | ||||
|     ITEMS.trap_upgrade_paint: always_trap, | ||||
|     ITEMS.trap_upgrade_demonic_belt: always_trap, | ||||
|     ITEMS.trap_upgrade_demonic_miner: always_trap, | ||||
|     ITEMS.trap_upgrade_demonic_proc: always_trap, | ||||
|     ITEMS.trap_upgrade_demonic_paint: always_trap, | ||||
| } | ||||
|  | ||||
| bundles: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.bundle_blueprint: always_filler, | ||||
|     ITEMS.bundle_level: always_filler, | ||||
|     ITEMS.bundle_upgrade: always_filler | ||||
| } | ||||
|  | ||||
| standard_traps: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.trap_locked: always_trap, | ||||
|     ITEMS.trap_throttled: always_trap, | ||||
|     ITEMS.trap_malfunction: always_trap, | ||||
|     ITEMS.trap_inflation: always_trap, | ||||
|     ITEMS.trap_clear_belts: always_trap, | ||||
| } | ||||
|  | ||||
| random_draining_trap: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.trap_draining_inv: always_trap | ||||
| } | ||||
|  | ||||
| split_draining_traps: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.trap_draining_blueprint: always_trap, | ||||
|     ITEMS.trap_draining_level: always_trap, | ||||
|     ITEMS.trap_draining_upgrade: always_trap | ||||
| } | ||||
|  | ||||
| belt_and_extractor: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     ITEMS.belt: always_progression, | ||||
|     ITEMS.extractor: always_progression | ||||
| } | ||||
|  | ||||
| item_table: Dict[str, Callable[[ShapezOptions], IClass]] = { | ||||
|     **buildings_processing, | ||||
|     **buildings_routing, | ||||
|     **buildings_other, | ||||
|     **buildings_top_row, | ||||
|     **buildings_wires, | ||||
|     **gameplay_unlocks, | ||||
|     **upgrades, | ||||
|     **whacky_upgrades, | ||||
|     **whacky_upgrade_traps, | ||||
|     **bundles, | ||||
|     **standard_traps, | ||||
|     **random_draining_trap, | ||||
|     **split_draining_traps, | ||||
|     **belt_and_extractor | ||||
| } | ||||
|  | ||||
| big_upgrades = [ | ||||
|     ITEMS.upgrade_big_belt, | ||||
|     ITEMS.upgrade_big_miner, | ||||
|     ITEMS.upgrade_big_proc, | ||||
|     ITEMS.upgrade_big_paint | ||||
| ] | ||||
|  | ||||
| small_upgrades = [ | ||||
|     ITEMS.upgrade_small_belt, | ||||
|     ITEMS.upgrade_small_miner, | ||||
|     ITEMS.upgrade_small_proc, | ||||
|     ITEMS.upgrade_small_paint | ||||
| ] | ||||
|  | ||||
|  | ||||
| def filler(random: float, whacky_allowed: bool) -> str: | ||||
|     """Returns a random filler item.""" | ||||
|     bundles_list = [*bundles] | ||||
|     return random_choice_nested(random, [ | ||||
|         small_upgrades, | ||||
|         [ | ||||
|             bundles_list, | ||||
|             bundles_list, | ||||
|             [ | ||||
|                 big_upgrades, | ||||
|                 [*whacky_upgrades] if whacky_allowed else big_upgrades, | ||||
|             ], | ||||
|         ], | ||||
|     ]) | ||||
|  | ||||
|  | ||||
| def trap(random: float, split_draining: bool, whacky_allowed: bool) -> str: | ||||
|     """Returns a random trap item.""" | ||||
|     pool = [ | ||||
|         *standard_traps, | ||||
|         ITEMS.trap_draining_inv if not split_draining else [*split_draining_traps], | ||||
|     ] | ||||
|     if whacky_allowed: | ||||
|         pool.append([*whacky_upgrade_traps]) | ||||
|     return random_choice_nested(random, pool) | ||||
|  | ||||
|  | ||||
| def random_choice_nested(random: float, nested: List[Any]) -> Any: | ||||
|     """Helper function for getting a random element from a nested list.""" | ||||
|     current: Any = nested | ||||
|     while isinstance(current, List): | ||||
|         index_float = random*len(current) | ||||
|         current = current[int(index_float)] | ||||
|         random = index_float-int(index_float) | ||||
|     return current | ||||
|  | ||||
|  | ||||
| item_descriptions = {  # TODO replace keys with global strings and update with whacky upgrades | ||||
|     "Balancer": "A routing building, that can merge two belts into one, split a belt in two, " + | ||||
|                 "or balance the items of two belts", | ||||
|     "Tunnel": "A routing building consisting of two parts, that allows for gaps in belts", | ||||
|     "Compact Merger": "A small routing building, that merges two belts into one", | ||||
|     "Tunnel Tier II": "A routing building consisting of two parts, that allows for even longer gaps in belts", | ||||
|     "Compact Splitter": "A small routing building, that splits a belt in two", | ||||
|     "Cutter": "A processing building, that cuts shapes vertically in two halves", | ||||
|     "Rotator": "A processing building, that rotates shapes 90 degrees clockwise", | ||||
|     "Painter": "A processing building, that paints shapes in a given color", | ||||
|     "Rotator (CCW)": "A processing building, that rotates shapes 90 degrees counter-clockwise", | ||||
|     "Color Mixer": "A processing building, that mixes two colors together to create a new one", | ||||
|     "Stacker": "A processing building, that combines two shapes with missing parts or puts one on top of the other", | ||||
|     "Quad Cutter": "A processing building, that cuts shapes in four quarter parts", | ||||
|     "Double Painter": "A processing building, that paints two shapes in a given color", | ||||
|     "Rotator (180°)": "A processing building, that rotates shapes 180 degrees", | ||||
|     "Quad Painter": "A processing building, that paint each quarter of a shape in another given color and requires " + | ||||
|                     "wire inputs for each color to work", | ||||
|     "Trash": "A building, that destroys unused shapes", | ||||
|     "Chaining Extractor": "An upgrade to extractors, that can increase the output without balancers or mergers", | ||||
|     "Belt Reader": "A wired building, that shows the average amount of items passing through per second", | ||||
|     "Storage": "A building, that stores up to 5000 of a certain shape", | ||||
|     "Switch": "A building, that sends a constant boolean signal", | ||||
|     "Item Filter": "A wired building, that filters items based on wire input", | ||||
|     "Display": "A wired building, that displays a shape or color based on wire input", | ||||
|     "Wires": "The main building of the wires layer, that carries signals between other buildings", | ||||
|     "Constant Signal": "A building on the wires layer, that sends a constant shape, color, or boolean signal", | ||||
|     "Logic Gates": "Multiple buildings on the wires layer, that perform logical operations on wire signals", | ||||
|     "Virtual Processing": "Multiple buildings on the wires layer, that process wire signals like processor buildings", | ||||
|     "Blueprints": "A game mechanic, that allows copy-pasting multiple buildings at once", | ||||
|     "Big Belt Upgrade": "An upgrade, that adds 1 to the speed multiplier of belts, distributors, and tunnels", | ||||
|     "Big Miner Upgrade": "An upgrade, that adds 1 to the speed multiplier of extractors", | ||||
|     "Big Processors Upgrade": "An upgrade, that adds 1 to the speed multiplier of cutters, rotators, and stackers", | ||||
|     "Big Painting Upgrade": "An upgrade, that adds 1 to the speed multiplier of painters and color mixers", | ||||
|     "Small Belt Upgrade": "An upgrade, that adds 0.1 to the speed multiplier of belts, distributors, and tunnels", | ||||
|     "Small Miner Upgrade": "An upgrade, that adds 0.1 to the speed multiplier of extractors", | ||||
|     "Small Processors Upgrade": "An upgrade, that adds 0.1 to the speed multiplier of cutters, rotators, and stackers", | ||||
|     "Small Painting Upgrade": "An upgrade, that adds 0.1 to the speed multiplier of painters and color mixers", | ||||
|     "Blueprint Shapes Bundle": "A bundle with 1000 blueprint shapes, instantly delivered to the hub", | ||||
|     "Level Shapes Bundle": "A bundle with some shapes needed for the current level, " + | ||||
|                            "instantly delivered to the hub", | ||||
|     "Upgrade Shapes Bundle": "A bundle with some shapes needed for a random upgrade, " + | ||||
|                            "instantly delivered to the hub", | ||||
|     "Inventory Draining Trap": "Randomly drains either blueprint shapes, current level requirement shapes, " + | ||||
|                                "or random upgrade requirement shapes, by half", | ||||
|     "Blueprint Shapes Draining Trap": "Drains the stored blueprint shapes by half", | ||||
|     "Level Shapes Draining Trap": "Drains the current level requirement shapes by half", | ||||
|     "Upgrade Shapes Draining Trap": "Drains a random upgrade requirement shape by half", | ||||
|     "Locked Building Trap": "Locks a random building from being placed for 15-60 seconds", | ||||
|     "Throttled Building Trap": "Halves the speed of a random building for 15-60 seconds", | ||||
|     "Malfunctioning Trap": "Makes a random building process items incorrectly for 15-60 seconds", | ||||
|     "Inflation Trap": "Permanently increases the required shapes multiplier by 1. " | ||||
|                       "In other words: Permanently increases required shapes by 10% of the standard amount.", | ||||
|     "Belt": "One of the most important buildings in the game, that transports your shapes and colors from one " + | ||||
|             "place to another", | ||||
|     "Extractor": "One of the most important buildings in the game, that extracts shapes from those randomly " + | ||||
|                  "generated patches" | ||||
| } | ||||
|  | ||||
|  | ||||
| class ShapezItem(Item): | ||||
|     game = OTHER.game_name | ||||
							
								
								
									
										546
									
								
								worlds/shapez/locations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										546
									
								
								worlds/shapez/locations.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,546 @@ | ||||
| from random import Random | ||||
| from typing import List, Tuple, Dict, Optional, Callable | ||||
|  | ||||
| from BaseClasses import Location, LocationProgressType, Region | ||||
| from .data.strings import CATEGORY, LOCATIONS, REGIONS, OPTIONS, GOALS, OTHER, SHAPESANITY | ||||
| from .options import max_shapesanity, max_levels_and_upgrades | ||||
|  | ||||
| categories = [CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting] | ||||
|  | ||||
| translate: List[Tuple[int, str]] = [ | ||||
|     (1000, "M"), | ||||
|     (900, "CM"), | ||||
|     (500, "D"), | ||||
|     (400, "CD"), | ||||
|     (100, "C"), | ||||
|     (90, "XC"), | ||||
|     (50, "L"), | ||||
|     (40, "XL"), | ||||
|     (10, "X"), | ||||
|     (9, "IX"), | ||||
|     (5, "V"), | ||||
|     (4, "IV"), | ||||
|     (1, "I") | ||||
| ] | ||||
|  | ||||
|  | ||||
| def roman(num: int) -> str: | ||||
|     """Converts positive non-zero integers into roman numbers.""" | ||||
|     rom: str = "" | ||||
|     for key, val in translate: | ||||
|         while num >= key: | ||||
|             rom += val | ||||
|             num -= key | ||||
|     return rom | ||||
|  | ||||
|  | ||||
| location_description = {  # TODO change keys to global strings | ||||
|     "Level 1": "Levels are completed by delivering certain shapes in certain amounts to the hub. The required shape " | ||||
|                "and amount for the current level are always displayed on the hub.", | ||||
|     "Level 1 Additional": "In the vanilla game, levels 1 and 20 have unlock more than one building.", | ||||
|     "Level 20 Additional": "In the vanilla game, levels 1 and 20 have unlock more than one building.", | ||||
|     "Level 20 Additional 2": "In the vanilla game, levels 1 and 20 have unlock more than one building.", | ||||
|     "Level 26": "In the vanilla game, level 26 is the final level of the tutorial, unlocking freeplay.", | ||||
|     f"Level {max_levels_and_upgrades-1}": "This is the highest possible level that can contains an item, if your goal " | ||||
|                                           "is set to \"mam\"", | ||||
|     "Belt Upgrade Tier II": "Upgrades can be purchased by having certain shapes in certain amounts stored in your hub. " | ||||
|                             "This is the first upgrade in the belt, balancers, and tunnel category.", | ||||
|     "Miner Upgrade Tier II": "Upgrades can be purchased by having certain shapes in certain amounts stored in your " | ||||
|                              "hub. This is the first upgrade in the extractor category.", | ||||
|     "Processors Upgrade Tier II": "Upgrades can be purchased by having certain shapes in certain amounts stored in " | ||||
|                                   "your hub. This is the first upgrade in the cutter, rotators, and stacker category.", | ||||
|     "Painting Upgrade Tier II": "Upgrades can be purchased by having certain shapes in certain amounts stored in your " | ||||
|                                 "hub. This is the first upgrade in the painters and color mixer category.", | ||||
|     "Belt Upgrade Tier VIII": "This is the final upgrade in the belt, balancers, and tunnel category, if your goal is " | ||||
|                               "**not** set to \"even_fasterer\".", | ||||
|     "Miner Upgrade Tier VIII": "This is the final upgrade in the extractor category, if your goal is **not** set to " | ||||
|                                "\"even_fasterer\".", | ||||
|     "Processors Upgrade Tier VIII": "This is the final upgrade in the cutter, rotators, and stacker category, if your " | ||||
|                                     "goal is **not** set to \"even_fasterer\".", | ||||
|     "Painting Upgrade Tier VIII": "This is the final upgrade in the painters and color mixer category, if your goal is " | ||||
|                                   "**not** set to \"even_fasterer\".", | ||||
|     f"Belt Upgrade Tier {roman(max_levels_and_upgrades)}": "This is the highest possible upgrade in the belt, " | ||||
|                                                            "balancers, and tunnel category, if your goal is set to " | ||||
|                                                            "\"even_fasterer\".", | ||||
|     f"Miner Upgrade Tier {roman(max_levels_and_upgrades)}": "This is the highest possible upgrade in the extractor " | ||||
|                                                             "category, if your goal is set to \"even_fasterer\".", | ||||
|     f"Processors Upgrade Tier {roman(max_levels_and_upgrades)}": "This is the highest possible upgrade in the cutter, " | ||||
|                                                                  "rotators, and stacker category, if your goal is set " | ||||
|                                                                  "to \"even_fasterer\".", | ||||
|     f"Painting Upgrade Tier {roman(max_levels_and_upgrades)}": "This is the highest possible upgrade in the painters " | ||||
|                                                                "and color mixer category, if your goal is set to " | ||||
|                                                                "\"even_fasterer\".", | ||||
|     "My eyes no longer hurt": "This is an achievement, that is unlocked by activating dark mode.", | ||||
|     "Painter": "This is an achievement, that is unlocked by painting a shape using the painter or double painter.", | ||||
|     "Cutter": "This is an achievement, that is unlocked by cutting a shape in half using the cutter.", | ||||
|     "Rotater": "This is an achievement, that is unlocked by rotating a shape clock wise.", | ||||
|     "Wait, they stack?": "This is an achievement, that is unlocked by stacking two shapes on top of each other.", | ||||
|     "Wires": "This is an achievement, that is unlocked by completing level 20.", | ||||
|     "Storage": "This is an achievement, that is unlocked by storing a shape in a storage.", | ||||
|     "Freedom": "This is an achievement, that is unlocked by completing level 20. It is only included if the goal is " | ||||
|                "**not** set to vanilla.", | ||||
|     "The logo!": "This is an achievement, that is unlocked by producing the logo of the game.", | ||||
|     "To the moon": "This is an achievement, that is unlocked by producing the rocket shape.", | ||||
|     "It's piling up": "This is an achievement, that is unlocked by having 100.000 blueprint shapes stored in the hub.", | ||||
|     "I'll use it later": "This is an achievement, that is unlocked by having one million blueprint shapes stored in " | ||||
|                          "the hub.", | ||||
|     "Efficiency 1": "This is an achievement, that is unlocked by delivering 25 blueprint shapes per second to the hub.", | ||||
|     "Preparing to launch": "This is an achievement, that is unlocked by delivering 10 rocket shapes per second to the " | ||||
|                            "hub.", | ||||
|     "SpaceY": "This is an achievement, that is unlocked by 20 rocket shapes per second to the hub.", | ||||
|     "Stack overflow": "This is an achievement, that is unlocked by stacking 4 layers on top of each other.", | ||||
|     "It's a mess": "This is an achievement, that is unlocked by having 100 different shapes stored in the hub.", | ||||
|     "Faster": "This is an achievement, that is unlocked by upgrading everything to at least tier V.", | ||||
|     "Even faster": "This is an achievement, that is unlocked by upgrading everything to at least tier VIII.", | ||||
|     "Get rid of them": "This is an achievement, that is unlocked by transporting 1000 shapes into a trash can.", | ||||
|     "It's been a long time": "This is an achievement, that is unlocked by playing your save file for 10 hours " | ||||
|                              "(combined playtime).", | ||||
|     "Addicted": "This is an achievement, that is unlocked by playing your save file for 20 hours (combined playtime).", | ||||
|     "Can't stop": "This is an achievement, that is unlocked by reaching level 50.", | ||||
|     "Is this the end?": "This is an achievement, that is unlocked by reaching level 100.", | ||||
|     "Getting into it": "This is an achievement, that is unlocked by playing your save file for 1 hour (combined " | ||||
|                        "playtime).", | ||||
|     "Now it's easy": "This is an achievement, that is unlocked by placing a blueprint.", | ||||
|     "Computer Guy": "This is an achievement, that is unlocked by placing 5000 wires.", | ||||
|     "Speedrun Master": "This is an achievement, that is unlocked by completing level 12 in under 30 Minutes. This " | ||||
|                        "location is excluded by default, as it can become inaccessible in a save file after that time.", | ||||
|     "Speedrun Novice": "This is an achievement, that is unlocked by completing level 12 in under 60 Minutes. This " | ||||
|                        "location is excluded by default, as it can become inaccessible in a save file after that time.", | ||||
|     "Not an idle game": "This is an achievement, that is unlocked by completing level 12 in under 120 Minutes. This " | ||||
|                        "location is excluded by default, as it can become inaccessible in a save file after that time.", | ||||
|     "Efficiency 2": "This is an achievement, that is unlocked by delivering 50 blueprint shapes per second to the hub.", | ||||
|     "Branding specialist 1": "This is an achievement, that is unlocked by delivering 25 logo shapes per second to the " | ||||
|                              "hub.", | ||||
|     "Branding specialist 2": "This is an achievement, that is unlocked by delivering 50 logo shapes per second to the " | ||||
|                              "hub.", | ||||
|     "King of Inefficiency": "This is an achievement, that is unlocked by **not** placing a counter clock wise rotator " | ||||
|                             "until level 14. This location is excluded by default, as it can become inaccessible in a " | ||||
|                             "save file after placing that building.", | ||||
|     "It's so slow": "This is an achievement, that is unlocked by completing level 12 **without** buying any belt " | ||||
|                     "upgrade. This location is excluded by default, as it can become inaccessible in a save file after " | ||||
|                     "buying that upgrade.", | ||||
|     "MAM (Make Anything Machine)": "This is an achievement, that is unlocked by completing any level after level 26 " | ||||
|                                    "**without** modifying your factory. It is recommended to build a Make Anything " | ||||
|                                    "Machine.", | ||||
|     "Perfectionist": "This is an achievement, that is unlocked by destroying more than 1000 buildings at once.", | ||||
|     "The next dimension": "This is an achievement, that is unlocked by opening the wires layer.", | ||||
|     "Oops": "This is an achievement, that is unlocked by delivering a shape, that neither a level requirement nor an " | ||||
|             "upgrade requirement.", | ||||
|     "Copy-Pasta": "This is an achievement, that is unlocked by placing a blueprint with at least 1000 buildings.", | ||||
|     "I've seen that before ...": "This is an achievement, that is unlocked by producing RgRyRbRr.", | ||||
|     "Memories from the past": "This is an achievement, that is unlocked by producing WrRgWrRg:CwCrCwCr:SgSgSgSg.", | ||||
|     "I need trains": "This is an achievement, that is unlocked by placing a 500 tiles long belt.", | ||||
|     "A bit early?": "This is an achievement, that is unlocked by producing the logo shape before reaching level 18. " | ||||
|                     "This location is excluded by default, as it can become inaccessible in a save file after reaching " | ||||
|                     "that level.", | ||||
|     "GPS": "This is an achievement, that is unlocked by placing 15 or more map markers.", | ||||
|     "Shapesanity 1": "Shapesanity locations can be checked by delivering a described shape to the hub, without " | ||||
|                      "requiring a certain roation, orientation, or ordering. Shapesanity 1 is always an uncolored " | ||||
|                      "circle.", | ||||
|     "Shapesanity 2": "Shapesanity locations can be checked by delivering a described shape to the hub, without " | ||||
|                      "requiring a certain roation, orientation, or ordering. Shapesanity 2 is always an uncolored " | ||||
|                      "square.", | ||||
|     "Shapesanity 3": "Shapesanity locations can be checked by delivering a described shape to the hub, without " | ||||
|                      "requiring a certain roation, orientation, or ordering. Shapesanity 3 is always an uncolored " | ||||
|                      "star.", | ||||
|     "Shapesanity 4": "Shapesanity locations can be checked by delivering a described shape to the hub, without " | ||||
|                      "requiring a certain roation, orientation, or ordering. Shapesanity 4 is always an uncolored " | ||||
|                      "windmill.", | ||||
| } | ||||
|  | ||||
| shapesanity_simple: Dict[str, str] = {} | ||||
| shapesanity_1_4: Dict[str, str] = {} | ||||
| shapesanity_two_sided: Dict[str, str] = {} | ||||
| shapesanity_three_parts: Dict[str, str] = {} | ||||
| shapesanity_four_parts: Dict[str, str] = {} | ||||
|  | ||||
| level_locations: List[str] = ([LOCATIONS.level(1, 1), LOCATIONS.level(20, 1), LOCATIONS.level(20, 2)] | ||||
|                               + [LOCATIONS.level(x) for x in range(1, max_levels_and_upgrades)]) | ||||
| upgrade_locations: List[str] = [LOCATIONS.upgrade(cat, roman(x)) | ||||
|                                 for cat in categories for x in range(2, max_levels_and_upgrades+1)] | ||||
| achievement_locations: List[str] = [LOCATIONS.my_eyes, LOCATIONS.painter, LOCATIONS.cutter, LOCATIONS.rotater, | ||||
|                                     LOCATIONS.wait_they_stack, LOCATIONS.wires, LOCATIONS.storage, LOCATIONS.freedom, | ||||
|                                     LOCATIONS.the_logo, LOCATIONS.to_the_moon, LOCATIONS.its_piling_up, | ||||
|                                     LOCATIONS.use_it_later, LOCATIONS.efficiency_1, LOCATIONS.preparing_to_launch, | ||||
|                                     LOCATIONS.spacey, LOCATIONS.stack_overflow, LOCATIONS.its_a_mess, LOCATIONS.faster, | ||||
|                                     LOCATIONS.even_faster, LOCATIONS.get_rid_of_them, LOCATIONS.a_long_time, | ||||
|                                     LOCATIONS.addicted, LOCATIONS.cant_stop, LOCATIONS.is_this_the_end, | ||||
|                                     LOCATIONS.getting_into_it, LOCATIONS.now_its_easy, LOCATIONS.computer_guy, | ||||
|                                     LOCATIONS.speedrun_master, LOCATIONS.speedrun_novice, LOCATIONS.not_idle_game, | ||||
|                                     LOCATIONS.efficiency_2, LOCATIONS.branding_1, | ||||
|                                     LOCATIONS.branding_2, LOCATIONS.king_of_inefficiency, LOCATIONS.its_so_slow, | ||||
|                                     LOCATIONS.mam, LOCATIONS.perfectionist, LOCATIONS.next_dimension, LOCATIONS.oops, | ||||
|                                     LOCATIONS.copy_pasta, LOCATIONS.ive_seen_that_before, LOCATIONS.memories, | ||||
|                                     LOCATIONS.i_need_trains, LOCATIONS.a_bit_early, LOCATIONS.gps] | ||||
| shapesanity_locations: List[str] = [LOCATIONS.shapesanity(x) for x in range(1, max_shapesanity+1)] | ||||
|  | ||||
|  | ||||
| def init_shapesanity_pool() -> None: | ||||
|     """Imports the pregenerated shapesanity pool.""" | ||||
|     from .data import shapesanity_pool | ||||
|     shapesanity_simple.update(shapesanity_pool.shapesanity_simple) | ||||
|     shapesanity_1_4.update(shapesanity_pool.shapesanity_1_4) | ||||
|     shapesanity_two_sided.update(shapesanity_pool.shapesanity_two_sided) | ||||
|     shapesanity_three_parts.update(shapesanity_pool.shapesanity_three_parts) | ||||
|     shapesanity_four_parts.update(shapesanity_pool.shapesanity_four_parts) | ||||
|  | ||||
|  | ||||
| def addlevels(maxlevel: int, logictype: str, | ||||
|               random_logic_phase_length: List[int]) -> Dict[str, Tuple[str, LocationProgressType]]: | ||||
|     """Returns a dictionary with all level locations based on player options (maxlevel INCLUDED). | ||||
|     If shape requirements are not randomized, the logic type is expected to be vanilla.""" | ||||
|  | ||||
|     # Level 1 is always directly accessible | ||||
|     locations: Dict[str, Tuple[str, LocationProgressType]] \ | ||||
|         = {LOCATIONS.level(1): (REGIONS.main, LocationProgressType.PRIORITY), | ||||
|            LOCATIONS.level(1, 1): (REGIONS.main, LocationProgressType.PRIORITY)} | ||||
|     level_regions = [REGIONS.main, REGIONS.levels_1, REGIONS.levels_2, REGIONS.levels_3, | ||||
|                      REGIONS.levels_4, REGIONS.levels_5] | ||||
|  | ||||
|     def f(name: str, region: str, progress: LocationProgressType = LocationProgressType.DEFAULT) -> None: | ||||
|         locations[name] = (region, progress) | ||||
|  | ||||
|     if logictype.startswith(OPTIONS.logic_vanilla): | ||||
|         f(LOCATIONS.level(20, 1), REGIONS.levels_5) | ||||
|         f(LOCATIONS.level(20, 2), REGIONS.levels_5) | ||||
|         f(LOCATIONS.level(2), REGIONS.levels_1) | ||||
|         f(LOCATIONS.level(3), REGIONS.levels_1) | ||||
|         f(LOCATIONS.level(4), REGIONS.levels_1) | ||||
|         f(LOCATIONS.level(5), REGIONS.levels_2) | ||||
|         f(LOCATIONS.level(6), REGIONS.levels_2) | ||||
|         f(LOCATIONS.level(7), REGIONS.levels_3) | ||||
|         f(LOCATIONS.level(8), REGIONS.levels_3) | ||||
|         f(LOCATIONS.level(9), REGIONS.levels_4) | ||||
|         f(LOCATIONS.level(10), REGIONS.levels_4) | ||||
|         for x in range(11, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_5) | ||||
|  | ||||
|     elif logictype.startswith(OPTIONS.logic_stretched): | ||||
|         phaselength = maxlevel//6 | ||||
|         f(LOCATIONS.level(20, 1), level_regions[20//phaselength]) | ||||
|         f(LOCATIONS.level(20, 2), level_regions[20//phaselength]) | ||||
|         for x in range(2, phaselength): | ||||
|             f(LOCATIONS.level(x), REGIONS.main) | ||||
|         for x in range(phaselength, phaselength*2): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_1) | ||||
|         for x in range(phaselength*2, phaselength*3): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_2) | ||||
|         for x in range(phaselength*3, phaselength*4): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_3) | ||||
|         for x in range(phaselength*4, phaselength*5): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_4) | ||||
|         for x in range(phaselength*5, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_5) | ||||
|  | ||||
|     elif logictype.startswith(OPTIONS.logic_quick): | ||||
|         f(LOCATIONS.level(20, 1), REGIONS.levels_5) | ||||
|         f(LOCATIONS.level(20, 2), REGIONS.levels_5) | ||||
|         f(LOCATIONS.level(2), REGIONS.levels_1) | ||||
|         f(LOCATIONS.level(3), REGIONS.levels_2) | ||||
|         f(LOCATIONS.level(4), REGIONS.levels_3) | ||||
|         f(LOCATIONS.level(5), REGIONS.levels_4) | ||||
|         for x in range(6, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_5) | ||||
|  | ||||
|     elif logictype.startswith(OPTIONS.logic_random_steps): | ||||
|         next_level = 2 | ||||
|         for phase in range(5): | ||||
|             for x in range(random_logic_phase_length[phase]): | ||||
|                 f(LOCATIONS.level(next_level+x), level_regions[phase]) | ||||
|             next_level += random_logic_phase_length[phase] | ||||
|             if next_level > 20: | ||||
|                 f(LOCATIONS.level(20, 1), level_regions[phase]) | ||||
|                 f(LOCATIONS.level(20, 2), level_regions[phase]) | ||||
|         for x in range(next_level, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_5) | ||||
|         if next_level <= 20: | ||||
|             f(LOCATIONS.level(20, 1), REGIONS.levels_5) | ||||
|             f(LOCATIONS.level(20, 2), REGIONS.levels_5) | ||||
|  | ||||
|     elif logictype == OPTIONS.logic_hardcore: | ||||
|         f(LOCATIONS.level(20, 1), REGIONS.levels_5) | ||||
|         f(LOCATIONS.level(20, 2), REGIONS.levels_5) | ||||
|         for x in range(2, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_5) | ||||
|  | ||||
|     elif logictype == OPTIONS.logic_dopamine: | ||||
|         f(LOCATIONS.level(20, 1), REGIONS.levels_2) | ||||
|         f(LOCATIONS.level(20, 2), REGIONS.levels_2) | ||||
|         for x in range(2, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.levels_2) | ||||
|  | ||||
|     elif logictype == OPTIONS.logic_dopamine_overflow: | ||||
|         f(LOCATIONS.level(20, 1), REGIONS.main) | ||||
|         f(LOCATIONS.level(20, 2), REGIONS.main) | ||||
|         for x in range(2, maxlevel+1): | ||||
|             f(LOCATIONS.level(x), REGIONS.main) | ||||
|  | ||||
|     else: | ||||
|         raise Exception(f"Illegal level logic type {logictype}") | ||||
|  | ||||
|     return locations | ||||
|  | ||||
|  | ||||
| def addupgrades(finaltier: int, logictype: str, | ||||
|                 category_random_logic_amounts: Dict[str, int]) -> Dict[str, Tuple[str, LocationProgressType]]: | ||||
|     """Returns a dictionary with all upgrade locations based on player options (finaltier INCLUDED). | ||||
|     If shape requirements are not randomized, give logic type 0.""" | ||||
|  | ||||
|     locations: Dict[str, Tuple[str, LocationProgressType]] = {} | ||||
|     upgrade_regions = [REGIONS.main, REGIONS.upgrades_1, REGIONS.upgrades_2, REGIONS.upgrades_3, | ||||
|                        REGIONS.upgrades_4, REGIONS.upgrades_5] | ||||
|  | ||||
|     def f(name: str, region: str, progress: LocationProgressType = LocationProgressType.DEFAULT) -> None: | ||||
|         locations[name] = (region, progress) | ||||
|  | ||||
|     if logictype == OPTIONS.logic_vanilla_like: | ||||
|         f(LOCATIONS.upgrade(CATEGORY.belt, "II"), REGIONS.main) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.miner, "II"), REGIONS.main) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "II"), REGIONS.main) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.painting, "II"), REGIONS.upgrades_3) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.belt, "III"), REGIONS.upgrades_2) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.miner, "III"), REGIONS.upgrades_2) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "III"), REGIONS.upgrades_1) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.painting, "III"), REGIONS.upgrades_3) | ||||
|         for x in range(4, finaltier+1): | ||||
|             tier = roman(x) | ||||
|             for cat in categories: | ||||
|                 f(LOCATIONS.upgrade(cat, tier), REGIONS.upgrades_5) | ||||
|  | ||||
|     elif logictype == OPTIONS.logic_linear: | ||||
|         for x in range(2, 7): | ||||
|             tier = roman(x) | ||||
|             for cat in categories: | ||||
|                 f(LOCATIONS.upgrade(cat, tier), upgrade_regions[x-2]) | ||||
|         for x in range(7, finaltier+1): | ||||
|             tier = roman(x) | ||||
|             for cat in categories: | ||||
|                 f(LOCATIONS.upgrade(cat, tier), REGIONS.upgrades_5) | ||||
|  | ||||
|     elif logictype == OPTIONS.logic_category: | ||||
|         for x in range(2, 7): | ||||
|             tier = roman(x) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.belt, tier), REGIONS.main) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.miner, tier), REGIONS.main) | ||||
|         for x in range(7, finaltier + 1): | ||||
|             tier = roman(x) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.belt, tier), REGIONS.upgrades_5) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.miner, tier), REGIONS.upgrades_5) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "II"), REGIONS.upgrades_1) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "III"), REGIONS.upgrades_2) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "IV"), REGIONS.upgrades_2) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "V"), REGIONS.upgrades_3) | ||||
|         f(LOCATIONS.upgrade(CATEGORY.processors, "VI"), REGIONS.upgrades_3) | ||||
|         for x in range(7, finaltier+1): | ||||
|             f(LOCATIONS.upgrade(CATEGORY.processors, roman(x)), REGIONS.upgrades_5) | ||||
|         for x in range(2, 4): | ||||
|             f(LOCATIONS.upgrade(CATEGORY.painting, roman(x)), REGIONS.upgrades_4) | ||||
|         for x in range(4, finaltier+1): | ||||
|             f(LOCATIONS.upgrade(CATEGORY.painting, roman(x)), REGIONS.upgrades_5) | ||||
|  | ||||
|     elif logictype == OPTIONS.logic_category_random: | ||||
|         for x in range(2, 7): | ||||
|             tier = roman(x) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.belt, tier), | ||||
|               upgrade_regions[category_random_logic_amounts[CATEGORY.belt_low]]) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.miner, tier), | ||||
|               upgrade_regions[category_random_logic_amounts[CATEGORY.miner_low]]) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.processors, tier), | ||||
|               upgrade_regions[category_random_logic_amounts[CATEGORY.processors_low]]) | ||||
|             f(LOCATIONS.upgrade(CATEGORY.painting, tier), | ||||
|               upgrade_regions[category_random_logic_amounts[CATEGORY.painting_low]]) | ||||
|         for x in range(7, finaltier+1): | ||||
|             tier = roman(x) | ||||
|             for cat in categories: | ||||
|                 f(LOCATIONS.upgrade(cat, tier), REGIONS.upgrades_5) | ||||
|  | ||||
|     else:  # logictype == hardcore | ||||
|         for cat in categories: | ||||
|             f(LOCATIONS.upgrade(cat, "II"), REGIONS.main) | ||||
|         for x in range(3, finaltier+1): | ||||
|             tier = roman(x) | ||||
|             for cat in categories: | ||||
|                 f(LOCATIONS.upgrade(cat, tier), REGIONS.upgrades_5) | ||||
|  | ||||
|     return locations | ||||
|  | ||||
|  | ||||
| def addachievements(excludesoftlock: bool, excludelong: bool, excludeprogressive: bool, | ||||
|                     maxlevel: int, upgradelogictype: str, category_random_logic_amounts: Dict[str, int], | ||||
|                     goal: str, presentlocations: Dict[str, Tuple[str, LocationProgressType]], | ||||
|                     add_alias: Callable[[str, str], None], has_upgrade_traps: bool | ||||
|                     ) -> Dict[str, Tuple[str, LocationProgressType]]: | ||||
|     """Returns a dictionary with all achievement locations based on player options.""" | ||||
|  | ||||
|     locations: Dict[str, Tuple[str, LocationProgressType]] = dict() | ||||
|     upgrade_regions = [REGIONS.main, REGIONS.upgrades_1, REGIONS.upgrades_2, REGIONS.upgrades_3, | ||||
|                        REGIONS.upgrades_4, REGIONS.upgrades_5] | ||||
|  | ||||
|     def f(name: str, region: str, alias: str, progress: LocationProgressType = LocationProgressType.DEFAULT): | ||||
|         locations[name] = (region, progress) | ||||
|         add_alias(name, alias) | ||||
|  | ||||
|     f(LOCATIONS.my_eyes, REGIONS.menu, "Activate dark mode") | ||||
|     f(LOCATIONS.painter, REGIONS.paint_not_quad, "Paint a shape (no Quad Painter)") | ||||
|     f(LOCATIONS.cutter, REGIONS.cut_not_quad, "Cut a shape (no Quad Cutter)") | ||||
|     f(LOCATIONS.rotater, REGIONS.rotate_cw, "Rotate a shape clock wise") | ||||
|     f(LOCATIONS.wait_they_stack, REGIONS.stack_shape, "Stack a shape") | ||||
|     f(LOCATIONS.storage, REGIONS.store_shape, "Store a shape in the storage") | ||||
|     f(LOCATIONS.the_logo, REGIONS.all_buildings, "Produce the shapez logo") | ||||
|     f(LOCATIONS.to_the_moon, REGIONS.all_buildings, "Produce the rocket shape") | ||||
|     f(LOCATIONS.its_piling_up, REGIONS.all_buildings, "100k blueprint shapes") | ||||
|     f(LOCATIONS.use_it_later, REGIONS.all_buildings, "1 million blueprint shapes") | ||||
|  | ||||
|     f(LOCATIONS.stack_overflow, REGIONS.stack_shape, "4 layers shape") | ||||
|     f(LOCATIONS.its_a_mess, REGIONS.main, "100 different shapes in hub") | ||||
|     f(LOCATIONS.get_rid_of_them, REGIONS.trash_shape, "1000 shapes trashed") | ||||
|     f(LOCATIONS.getting_into_it, REGIONS.menu, "1 hour") | ||||
|     f(LOCATIONS.now_its_easy, REGIONS.blueprint, "Place a blueprint") | ||||
|     f(LOCATIONS.computer_guy, REGIONS.wiring, "Place 5000 wires") | ||||
|     f(LOCATIONS.perfectionist, REGIONS.any_building, "Destroy more than 1000 objects at once") | ||||
|     f(LOCATIONS.next_dimension, REGIONS.wiring, "Open the wires layer") | ||||
|     f(LOCATIONS.copy_pasta, REGIONS.blueprint, "Place a 1000 buildings blueprint") | ||||
|     f(LOCATIONS.ive_seen_that_before, REGIONS.all_buildings, "Produce RgRyRbRr") | ||||
|     f(LOCATIONS.memories, REGIONS.all_buildings, "Produce WrRgWrRg:CwCrCwCr:SgSgSgSg") | ||||
|     f(LOCATIONS.i_need_trains, REGIONS.belt, "Have a 500 tiles belt") | ||||
|     f(LOCATIONS.gps, REGIONS.menu, "15 map markers") | ||||
|  | ||||
|     # Per second delivery achievements | ||||
|     f(LOCATIONS.preparing_to_launch, REGIONS.all_buildings, "10 rocket shapes / second") | ||||
|     if not has_upgrade_traps: | ||||
|         f(LOCATIONS.spacey, REGIONS.all_buildings, "20 rocket shapes / second") | ||||
|         f(LOCATIONS.efficiency_1, REGIONS.all_buildings, "25 blueprints shapes / second") | ||||
|         f(LOCATIONS.efficiency_2, REGIONS.all_buildings_x1_6_belt, "50 blueprints shapes / second") | ||||
|         f(LOCATIONS.branding_1, REGIONS.all_buildings, "25 logo shapes / second") | ||||
|         f(LOCATIONS.branding_2, REGIONS.all_buildings_x1_6_belt, "50 logo shapes / second") | ||||
|  | ||||
|     # Achievements that depend on upgrades | ||||
|     f(LOCATIONS.even_faster, REGIONS.upgrades_5, "All upgrades on tier VIII") | ||||
|     if upgradelogictype == OPTIONS.logic_linear: | ||||
|         f(LOCATIONS.faster, REGIONS.upgrades_3, "All upgrades on tier V") | ||||
|     elif upgradelogictype == OPTIONS.logic_category_random: | ||||
|         f(LOCATIONS.faster, upgrade_regions[ | ||||
|             max(category_random_logic_amounts[CATEGORY.belt_low], | ||||
|                 category_random_logic_amounts[CATEGORY.miner_low], | ||||
|                 category_random_logic_amounts[CATEGORY.processors_low], | ||||
|                 category_random_logic_amounts[CATEGORY.painting_low]) | ||||
|         ], "All upgrades on tier V") | ||||
|     else: | ||||
|         f(LOCATIONS.faster, REGIONS.upgrades_5, "All upgrades on tier V") | ||||
|  | ||||
|     # Achievements that depend on the level | ||||
|     f(LOCATIONS.wires, presentlocations[LOCATIONS.level(20)][0], "Complete level 20") | ||||
|     if not goal == GOALS.vanilla: | ||||
|         f(LOCATIONS.freedom, presentlocations[LOCATIONS.level(26)][0], "Complete level 26") | ||||
|         f(LOCATIONS.mam, REGIONS.mam, "Complete any level > 26 without modifications") | ||||
|     if maxlevel >= 50: | ||||
|         f(LOCATIONS.cant_stop, presentlocations[LOCATIONS.level(50)][0], "Reach level 50") | ||||
|     elif goal not in [GOALS.vanilla, GOALS.mam]: | ||||
|         f(LOCATIONS.cant_stop, REGIONS.levels_5, "Reach level 50") | ||||
|     if maxlevel >= 100: | ||||
|         f(LOCATIONS.is_this_the_end, presentlocations[LOCATIONS.level(100)][0], "Reach level 100") | ||||
|     elif goal not in [GOALS.vanilla, GOALS.mam]: | ||||
|         f(LOCATIONS.is_this_the_end, REGIONS.levels_5, "Reach level 100") | ||||
|  | ||||
|     # Achievements that depend on player preferences | ||||
|     if excludeprogressive: | ||||
|         unreasonable_type = LocationProgressType.EXCLUDED | ||||
|     else: | ||||
|         unreasonable_type = LocationProgressType.DEFAULT | ||||
|     if not excludesoftlock: | ||||
|         f(LOCATIONS.speedrun_master, presentlocations[LOCATIONS.level(12)][0], | ||||
|           "Complete level 12 in under 30 min", unreasonable_type) | ||||
|         f(LOCATIONS.speedrun_novice, presentlocations[LOCATIONS.level(12)][0], | ||||
|           "Complete level 12 in under 60 min", unreasonable_type) | ||||
|         f(LOCATIONS.not_idle_game, presentlocations[LOCATIONS.level(12)][0], | ||||
|           "Complete level 12 in under 120 min", unreasonable_type) | ||||
|         f(LOCATIONS.its_so_slow, presentlocations[LOCATIONS.level(12)][0], | ||||
|           "Complete level 12 without upgrading belts", unreasonable_type) | ||||
|         f(LOCATIONS.king_of_inefficiency, presentlocations[LOCATIONS.level(14)][0], | ||||
|           "No ccw rotator until level 14", unreasonable_type) | ||||
|         f(LOCATIONS.a_bit_early, REGIONS.all_buildings, | ||||
|           "Produce logo shape before level 18", unreasonable_type) | ||||
|     if not excludelong: | ||||
|         f(LOCATIONS.a_long_time, REGIONS.menu, "10 hours") | ||||
|         f(LOCATIONS.addicted, REGIONS.menu, "20 hours") | ||||
|  | ||||
|     # Achievements with a softlock chance of less than | ||||
|     # 1 divided by 2 to the power of the number of all atoms in the universe | ||||
|     f(LOCATIONS.oops, REGIONS.main, "Deliver an irrelevant shape") | ||||
|  | ||||
|     return locations | ||||
|  | ||||
|  | ||||
| def addshapesanity(amount: int, random: Random, append_shapesanity: Callable[[str], None], | ||||
|                    add_alias: Callable[[str, str], None]) -> Dict[str, Tuple[str, LocationProgressType]]: | ||||
|     """Returns a dictionary with a given number of random shapesanity locations.""" | ||||
|  | ||||
|     included_shapes: Dict[str, Tuple[str, LocationProgressType]] = {} | ||||
|  | ||||
|     def f(name: str, region: str, alias: str, progress: LocationProgressType = LocationProgressType.DEFAULT) -> None: | ||||
|         included_shapes[name] = (region, progress) | ||||
|         append_shapesanity(alias) | ||||
|         shapes_list.remove((alias, region)) | ||||
|         add_alias(name, alias) | ||||
|  | ||||
|     # Always have at least 4 shapesanity checks because of sphere 1 usefulls + both hardcore logic | ||||
|     shapes_list = list(shapesanity_simple.items()) | ||||
|     f(LOCATIONS.shapesanity(1), REGIONS.sanity(REGIONS.full, REGIONS.uncol), | ||||
|       SHAPESANITY.full(SHAPESANITY.uncolored, SHAPESANITY.circle)) | ||||
|     f(LOCATIONS.shapesanity(2), REGIONS.sanity(REGIONS.full, REGIONS.uncol), | ||||
|       SHAPESANITY.full(SHAPESANITY.uncolored, SHAPESANITY.square)) | ||||
|     f(LOCATIONS.shapesanity(3), REGIONS.sanity(REGIONS.full, REGIONS.uncol), | ||||
|       SHAPESANITY.full(SHAPESANITY.uncolored, SHAPESANITY.star)) | ||||
|     f(LOCATIONS.shapesanity(4), REGIONS.sanity(REGIONS.east_wind, REGIONS.uncol), | ||||
|       SHAPESANITY.full(SHAPESANITY.uncolored, SHAPESANITY.windmill)) | ||||
|  | ||||
|     # The pool switches dynamically depending on if either it's ratio or limit is reached | ||||
|     switched = 0 | ||||
|     for counting in range(4, amount): | ||||
|         if switched == 0 and (len(shapes_list) == 0 or counting == amount//2): | ||||
|             shapes_list = list(shapesanity_1_4.items()) | ||||
|             switched = 1 | ||||
|         elif switched == 1 and (len(shapes_list) == 0 or counting == amount*7//12): | ||||
|             shapes_list = list(shapesanity_two_sided.items()) | ||||
|             switched = 2 | ||||
|         elif switched == 2 and (len(shapes_list) == 0 or counting == amount*5//6): | ||||
|             shapes_list = list(shapesanity_three_parts.items()) | ||||
|             switched = 3 | ||||
|         elif switched == 3 and (len(shapes_list) == 0 or counting == amount*11//12): | ||||
|             shapes_list = list(shapesanity_four_parts.items()) | ||||
|             switched = 4 | ||||
|         x = random.randint(0, len(shapes_list)-1) | ||||
|         next_shape = shapes_list.pop(x) | ||||
|         included_shapes[LOCATIONS.shapesanity(counting+1)] = (next_shape[1], LocationProgressType.DEFAULT) | ||||
|         append_shapesanity(next_shape[0]) | ||||
|         add_alias(LOCATIONS.shapesanity(counting+1), next_shape[0]) | ||||
|  | ||||
|     return included_shapes | ||||
|  | ||||
|  | ||||
| def addshapesanity_ut(shapesanity_names: List[str], add_alias: Callable[[str, str], None] | ||||
|                       ) -> Dict[str, Tuple[str, LocationProgressType]]: | ||||
|     """Returns the same information as addshapesanity but will add specific values based on a UT rebuild.""" | ||||
|  | ||||
|     included_shapes: Dict[str, Tuple[str, LocationProgressType]] = {} | ||||
|  | ||||
|     for name in shapesanity_names: | ||||
|         for options in [shapesanity_simple, shapesanity_1_4, shapesanity_two_sided, shapesanity_three_parts, | ||||
|                         shapesanity_four_parts]: | ||||
|             if name in options: | ||||
|                 next_shape = options[name] | ||||
|                 break | ||||
|         else: | ||||
|             raise ValueError(f"Could not find shapesanity name {name}") | ||||
|         included_shapes[LOCATIONS.shapesanity(len(included_shapes)+1)] = (next_shape, LocationProgressType.DEFAULT) | ||||
|         add_alias(LOCATIONS.shapesanity(len(included_shapes)), name) | ||||
|     return included_shapes | ||||
|  | ||||
|  | ||||
| class ShapezLocation(Location): | ||||
|     game = OTHER.game_name | ||||
|  | ||||
|     def __init__(self, player: int, name: str, address: Optional[int], region: Region, | ||||
|                  progress_type: LocationProgressType): | ||||
|         super(ShapezLocation, self).__init__(player, name, address, region) | ||||
|         self.progress_type = progress_type | ||||
							
								
								
									
										310
									
								
								worlds/shapez/options.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								worlds/shapez/options.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,310 @@ | ||||
| import pkgutil | ||||
| from dataclasses import dataclass | ||||
|  | ||||
| import orjson | ||||
|  | ||||
| from Options import Toggle, Choice, PerGameCommonOptions, NamedRange, Range | ||||
| from .common.options import FloatRangeText | ||||
|  | ||||
| datapackage_options = orjson.loads(pkgutil.get_data(__name__, "data/options.json")) | ||||
| max_levels_and_upgrades = datapackage_options["max_levels_and_upgrades"] | ||||
| max_shapesanity = datapackage_options["max_shapesanity"] | ||||
| del datapackage_options | ||||
|  | ||||
|  | ||||
| class Goal(Choice): | ||||
|     """Sets the goal of your world. | ||||
|  | ||||
|     - **Vanilla:** Complete level 26. | ||||
|     - **MAM:** Complete a specified level after level 26. Every level before that will be a location. It's recommended | ||||
|       to build a Make-Anything-Machine (MAM). | ||||
|     - **Even fasterer:** Upgrade everything to a specified tier after tier 8. Every upgrade before that will be a | ||||
|       location. | ||||
|     - **Efficiency III:** Deliver 256 blueprint shapes per second to the hub.""" | ||||
|     display_name = "Goal" | ||||
|     rich_text_doc = True | ||||
|     option_vanilla = 0 | ||||
|     option_mam = 1 | ||||
|     option_even_fasterer = 2 | ||||
|     option_efficiency_iii = 3 | ||||
|     default = 0 | ||||
|  | ||||
|  | ||||
| class GoalAmount(NamedRange): | ||||
|     """Specify, what level or tier (when either MAM or Even Fasterer is chosen as goal) is required to reach the goal. | ||||
|  | ||||
|     If MAM is set as the goal, this has to be set to 27 or more. Else it will raise an error.""" | ||||
|     display_name = "Goal amount" | ||||
|     rich_text_doc = True | ||||
|     range_start = 9 | ||||
|     range_end = max_levels_and_upgrades | ||||
|     default = 27 | ||||
|     special_range_names = { | ||||
|         "minimum_mam": 27, | ||||
|         "recommended_mam": 50, | ||||
|         "long_game_mam": 120, | ||||
|         "minimum_even_fasterer": 9, | ||||
|         "recommended_even_fasterer": 16, | ||||
|         "long_play_even_fasterer": 35, | ||||
|     } | ||||
|  | ||||
|  | ||||
| class RequiredShapesMultiplier(Range): | ||||
|     """Multiplies the amount of required shapes for levels and upgrades by value/10. | ||||
|  | ||||
|     For level 1, the amount of shapes ranges from 3 to 300. | ||||
|  | ||||
|     For level 26, it ranges from 5k to 500k.""" | ||||
|     display_name = "Required shapes multiplier" | ||||
|     rich_text_doc = True | ||||
|     range_start = 1 | ||||
|     range_end = 100 | ||||
|     default = 10 | ||||
|  | ||||
|  | ||||
| class AllowFloatingLayers(Toggle): | ||||
|     """Toggle whether shape requirements are allowed to have floating layers (like the logo or the rocket shape). | ||||
|  | ||||
|     However, be aware that floating shapes make MAMs much more complex.""" | ||||
|     display_name = "Allow floating layers" | ||||
|     rich_text_doc = True | ||||
|     default = False | ||||
|  | ||||
|  | ||||
| class RandomizeLevelRequirements(Toggle): | ||||
|     """Randomize the required shapes to complete levels.""" | ||||
|     display_name = "Randomize level requirements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class RandomizeUpgradeRequirements(Toggle): | ||||
|     """Randomize the required shapes to buy upgrades.""" | ||||
|     display_name = "Randomize upgrade requirements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class RandomizeLevelLogic(Choice): | ||||
|     """If level requirements are randomized, this sets how those random shapes are generated and how logic works for | ||||
|     levels. The shuffled variants shuffle the order of progression buildings obtained in the multiworld. The standard | ||||
|     order is: **cutter -> rotator -> painter -> color mixer -> stacker** | ||||
|  | ||||
|     - **Vanilla:** Level 1 requires nothing, 2-4 require the first building, 5-6 require also the second, 7-8 the | ||||
|       third, 9-10 the fourth, and 11 and onwards the fifth and thereby all buildings. | ||||
|     - **Stretched:** After every floor(maxlevel/6) levels, another building is required. | ||||
|     - **Quick:** Every Level, except level 1, requires another building, with level 6 and onwards requiring all | ||||
|       buildings. | ||||
|     - **Random steps:** After a random amount of levels, another building is required, with level 1 always requiring | ||||
|       none. This can potentially generate like any other option. | ||||
|     - **Hardcore:** All levels (except level 1) have completely random shape requirements and thus require all | ||||
|       buildings. Expect early BKs. | ||||
|     - **Dopamine (overflow):** All levels (except level 1 and the goal) require 2 random buildings (or none in case of | ||||
|       overflow).""" | ||||
|     display_name = "Randomize level logic" | ||||
|     rich_text_doc = True | ||||
|     option_vanilla = 0 | ||||
|     option_vanilla_shuffled = 1 | ||||
|     option_stretched = 2 | ||||
|     option_stretched_shuffled = 3 | ||||
|     option_quick = 4 | ||||
|     option_quick_shuffled = 5 | ||||
|     option_random_steps = 6 | ||||
|     option_random_steps_shuffled = 7 | ||||
|     option_hardcore = 8 | ||||
|     option_dopamine = 9 | ||||
|     option_dopamine_overflow = 10 | ||||
|     default = 2 | ||||
|  | ||||
|  | ||||
| class RandomizeUpgradeLogic(Choice): | ||||
|     """If upgrade requirements are randomized, this sets how those random shapes are generated | ||||
|     and how logic works for upgrades. | ||||
|  | ||||
|     - **Vanilla-like:** Tier II requires up to two random buildings, III requires up to three random buildings, | ||||
|       and IV and onwards require all processing buildings. | ||||
|     - **Linear:** Tier II requires nothing, III-VI require another random building each, | ||||
|       and VII and onwards require all buildings. | ||||
|     - **Category:** Belt and miner upgrades require no building up to tier V, but onwards all buildings, processors | ||||
|       upgrades require the cutter (all tiers), rotator (tier III and onwards), and stacker (tier V and onwards), and | ||||
|       painting upgrades require the cutter, rotator, stacker, painter (all tiers) and color mixer (tiers V and onwards). | ||||
|       Tier VII and onwards will always require all buildings. | ||||
|     - **Category random:** Each upgrades category (up to tier VI) requires a random amount of buildings (in order), | ||||
|       with one category always requiring no buildings. Tier VII and onwards will always require all buildings. | ||||
|     - **Hardcore:** All tiers (except each tier II) have completely random shape requirements and thus require all | ||||
|       buildings. Expect early BKs.""" | ||||
|     display_name = "Randomize upgrade logic" | ||||
|     rich_text_doc = True | ||||
|     option_vanilla_like = 0 | ||||
|     option_linear = 1 | ||||
|     option_category = 2 | ||||
|     option_category_random = 3 | ||||
|     option_hardcore = 4 | ||||
|     default = 1 | ||||
|  | ||||
|  | ||||
| class ThroughputLevelsRatio(NamedRange): | ||||
|     """If level requirements are randomized, this sets the ratio of how many levels (approximately) will require either | ||||
|     a total amount or per second amount (throughput) of shapes delivered. | ||||
|  | ||||
|     0 means only total, 100 means only throughput, and vanilla (-1) means only levels 14, 27 and beyond have throughput. | ||||
|     """ | ||||
|     display_name = "Throughput levels ratio" | ||||
|     rich_text_doc = True | ||||
|     range_start = 0 | ||||
|     range_end = 100 | ||||
|     default = 0 | ||||
|     special_range_names = { | ||||
|         "vanilla": -1, | ||||
|         "only_total": 0, | ||||
|         "half_half": 50, | ||||
|         "only_throughput": 100, | ||||
|     } | ||||
|  | ||||
|  | ||||
| class ComplexityGrowthGradient(FloatRangeText): | ||||
|     """If level requirements are randomized, this determines how fast complexity will grow each level. In other words: | ||||
|     The higher you set this value, the more difficult lategame shapes will be. | ||||
|  | ||||
|     Allowed values are floating numbers ranging from 0.0 to 10.0.""" | ||||
|     display_name = "Complexity growth gradient" | ||||
|     rich_text_doc = True | ||||
|     range_start = 0.0 | ||||
|     range_end = 10.0 | ||||
|     default = "0.5" | ||||
|  | ||||
|  | ||||
| class SameLateUpgradeRequirements(Toggle): | ||||
|     """If upgrade requirements are randomized, should the last 3 shapes for each category be the same, | ||||
|     as in vanilla?""" | ||||
|     display_name = "Same late upgrade requirements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class EarlyBalancerTunnelAndTrash(Choice): | ||||
|     """Makes the balancer, tunnel, and trash appear in earlier spheres. | ||||
|  | ||||
|     - **None:** Complete randomization. | ||||
|     - **5 buildings:** Should be accessible before getting all 5 main buildings. | ||||
|     - **3 buildings:** Should be accessible before getting the first 3 main buildings for levels and upgrades. | ||||
|     - **Sphere 1:** Always accessible from start. **Beware of generation failures.**""" | ||||
|     display_name = "Early balancer, tunnel, and trash" | ||||
|     rich_text_doc = True | ||||
|     option_none = 0 | ||||
|     option_5_buildings = 1 | ||||
|     option_3_buildings = 2 | ||||
|     option_sphere_1 = 3 | ||||
|     default = 2 | ||||
|  | ||||
|  | ||||
| class LockBeltAndExtractor(Toggle): | ||||
|     """Locks Belts and Extractors and adds them to the item pool. | ||||
|  | ||||
|     **If you set this to true, achievements must also be included.**""" | ||||
|     display_name = "Lock Belt and Extractor" | ||||
|     rich_text_doc = True | ||||
|     default = False | ||||
|  | ||||
|  | ||||
| class IncludeAchievements(Toggle): | ||||
|     """Include up to 45 achievements (depending on other options) as additional locations.""" | ||||
|     display_name = "Include Achievements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class ExcludeSoftlockAchievements(Toggle): | ||||
|     """Exclude 6 achievements, that can become unreachable in a save file, if not achieved until a certain level.""" | ||||
|     display_name = "Exclude softlock achievements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class ExcludeLongPlaytimeAchievements(Toggle): | ||||
|     """Exclude 2 achievements, that require actively playing for a really long time.""" | ||||
|     display_name = "Exclude long playtime achievements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class ExcludeProgressionUnreasonable(Toggle): | ||||
|     """Exclude progression and useful items from being placed into softlock and long playtime achievements.""" | ||||
|     display_name = "Exclude progression items in softlock and long playtime achievements" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| class ShapesanityAmount(Range): | ||||
|     """Amount of single-layer shapes that will be included as locations.""" | ||||
|     display_name = "Shapesanity amount" | ||||
|     rich_text_doc = True | ||||
|     range_start = 4 | ||||
|     range_end = max_shapesanity | ||||
|     default = 50 | ||||
|  | ||||
|  | ||||
| class TrapsProbability(NamedRange): | ||||
|     """The probability of any filler item (in percent) being replaced by a trap.""" | ||||
|     display_name = "Traps Percentage" | ||||
|     rich_text_doc = True | ||||
|     range_start = 0 | ||||
|     range_end = 100 | ||||
|     default = 0 | ||||
|     special_range_names = { | ||||
|         "none": 0, | ||||
|         "rare": 4, | ||||
|         "occasionally": 10, | ||||
|         "maximum_suffering": 100, | ||||
|     } | ||||
|  | ||||
|  | ||||
| class IncludeWhackyUpgrades(Toggle): | ||||
|     """Includes some very unusual upgrade items in generation (and logic), that greatly increase or decrease building | ||||
|     speeds. If the goal is set to Efficiency III or throughput levels ratio is not 0, decreasing upgrades (aka traps) | ||||
|     will always be disabled.""" | ||||
|     display_name = "Include Whacky Upgrades" | ||||
|     rich_text_doc = True | ||||
|     default = False | ||||
|  | ||||
|  | ||||
| class SplitInventoryDrainingTrap(Toggle): | ||||
|     """If set to true, the inventory draining trap will be split into level, upgrade, and blueprint draining traps | ||||
|     instead of executing as one of those 3 randomly.""" | ||||
|     display_name = "Split Inventory Draining Trap" | ||||
|     rich_text_doc = True | ||||
|     default = False | ||||
|  | ||||
|  | ||||
| class ToolbarShuffling(Toggle): | ||||
|     """If set to true, the toolbars (main and wires layer) will be shuffled (including bottom and top row). | ||||
|     However, keybindings will still select the same building to place.""" | ||||
|     display_name = "Toolbar Shuffling" | ||||
|     rich_text_doc = True | ||||
|     default = True | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class ShapezOptions(PerGameCommonOptions): | ||||
|     goal: Goal | ||||
|     goal_amount: GoalAmount | ||||
|     required_shapes_multiplier: RequiredShapesMultiplier | ||||
|     allow_floating_layers: AllowFloatingLayers | ||||
|     randomize_level_requirements: RandomizeLevelRequirements | ||||
|     randomize_upgrade_requirements: RandomizeUpgradeRequirements | ||||
|     randomize_level_logic: RandomizeLevelLogic | ||||
|     randomize_upgrade_logic: RandomizeUpgradeLogic | ||||
|     throughput_levels_ratio: ThroughputLevelsRatio | ||||
|     complexity_growth_gradient: ComplexityGrowthGradient | ||||
|     same_late_upgrade_requirements: SameLateUpgradeRequirements | ||||
|     early_balancer_tunnel_and_trash: EarlyBalancerTunnelAndTrash | ||||
|     lock_belt_and_extractor: LockBeltAndExtractor | ||||
|     include_achievements: IncludeAchievements | ||||
|     exclude_softlock_achievements: ExcludeSoftlockAchievements | ||||
|     exclude_long_playtime_achievements: ExcludeLongPlaytimeAchievements | ||||
|     exclude_progression_unreasonable: ExcludeProgressionUnreasonable | ||||
|     shapesanity_amount: ShapesanityAmount | ||||
|     traps_percentage: TrapsProbability | ||||
|     include_whacky_upgrades: IncludeWhackyUpgrades | ||||
|     split_inventory_draining_trap: SplitInventoryDrainingTrap | ||||
|     toolbar_shuffling: ToolbarShuffling | ||||
							
								
								
									
										49
									
								
								worlds/shapez/presets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								worlds/shapez/presets.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| from .options import max_levels_and_upgrades, max_shapesanity | ||||
|  | ||||
| options_presets = { | ||||
|     "Most vanilla": { | ||||
|         "goal": "vanilla", | ||||
|         "randomize_level_requirements": False, | ||||
|         "randomize_upgrade_requirements": False, | ||||
|         "early_balancer_tunnel_and_trash": "3_buildings", | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": False, | ||||
|         "exclude_long_playtime_achievements": False, | ||||
|         "shapesanity_amount": 4, | ||||
|         "toolbar_shuffling": False, | ||||
|     }, | ||||
|     "Minimum checks": { | ||||
|         "goal": "vanilla", | ||||
|         "include_achievements": False, | ||||
|         "shapesanity_amount": 4 | ||||
|     }, | ||||
|     "Maximum checks": { | ||||
|         "goal": "even_fasterer", | ||||
|         "goal_amount": max_levels_and_upgrades, | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": False, | ||||
|         "exclude_long_playtime_achievements": False, | ||||
|         "shapesanity_amount": max_shapesanity | ||||
|     }, | ||||
|     "Restrictive start": { | ||||
|         "goal": "vanilla", | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": "hardcore", | ||||
|         "randomize_upgrade_logic": "hardcore", | ||||
|         "early_balancer_tunnel_and_trash": "sphere_1", | ||||
|         "include_achievements": False, | ||||
|         "shapesanity_amount": 4 | ||||
|     }, | ||||
|     "Quick game": { | ||||
|         "goal": "efficiency_iii", | ||||
|         "required_shapes_multiplier": 1, | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": "hardcore", | ||||
|         "randomize_upgrade_logic": "hardcore", | ||||
|         "include_achievements": False, | ||||
|         "shapesanity_amount": 4, | ||||
|         "include_whacky_upgrades": True, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										277
									
								
								worlds/shapez/regions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								worlds/shapez/regions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,277 @@ | ||||
| from typing import Dict, Tuple, List | ||||
|  | ||||
| from BaseClasses import Region, MultiWorld, LocationProgressType, ItemClassification, CollectionState | ||||
| from .items import ShapezItem | ||||
| from .locations import ShapezLocation | ||||
| from .data.strings import ITEMS, REGIONS, GOALS, LOCATIONS, OPTIONS | ||||
| from worlds.generic.Rules import add_rule | ||||
|  | ||||
| shapesanity_processing = [REGIONS.full, REGIONS.half, REGIONS.piece, REGIONS.stitched, REGIONS.east_wind, | ||||
|                           REGIONS.half_half, REGIONS.col_east_wind, REGIONS.col_half_half, REGIONS.col_full, | ||||
|                           REGIONS.col_half] | ||||
| shapesanity_coloring = [REGIONS.uncol, REGIONS.painted, REGIONS.mixed] | ||||
|  | ||||
| all_regions = [ | ||||
|     REGIONS.menu, REGIONS.belt, REGIONS.extract, REGIONS.main, | ||||
|     REGIONS.levels_1, REGIONS.levels_2, REGIONS.levels_3, REGIONS.levels_4, REGIONS.levels_5, | ||||
|     REGIONS.upgrades_1, REGIONS.upgrades_2, REGIONS.upgrades_3, REGIONS.upgrades_4, REGIONS.upgrades_5, | ||||
|     REGIONS.paint_not_quad, REGIONS.cut_not_quad, REGIONS.rotate_cw, REGIONS.stack_shape, REGIONS.store_shape, | ||||
|     REGIONS.trash_shape, REGIONS.blueprint, REGIONS.wiring, REGIONS.mam, REGIONS.any_building, | ||||
|     REGIONS.all_buildings, REGIONS.all_buildings_x1_6_belt, | ||||
|     *[REGIONS.sanity(processing, coloring) | ||||
|       for processing in shapesanity_processing | ||||
|       for coloring in shapesanity_coloring], | ||||
| ] | ||||
|  | ||||
|  | ||||
| def can_cut_half(state: CollectionState, player: int) -> bool: | ||||
|     return state.has(ITEMS.cutter, player) | ||||
|  | ||||
|  | ||||
| def can_rotate_90(state: CollectionState, player: int) -> bool: | ||||
|     return state.has_any((ITEMS.rotator, ITEMS.rotator_ccw), player) | ||||
|  | ||||
|  | ||||
| def can_rotate_180(state: CollectionState, player: int) -> bool: | ||||
|     return state.has_any((ITEMS.rotator, ITEMS.rotator_ccw, ITEMS.rotator_180), player) | ||||
|  | ||||
|  | ||||
| def can_stack(state: CollectionState, player: int) -> bool: | ||||
|     return state.has(ITEMS.stacker, player) | ||||
|  | ||||
|  | ||||
| def can_paint(state: CollectionState, player: int) -> bool: | ||||
|     return state.has_any((ITEMS.painter, ITEMS.painter_double), player) or can_use_quad_painter(state, player) | ||||
|  | ||||
|  | ||||
| def can_mix_colors(state: CollectionState, player: int) -> bool: | ||||
|     return state.has(ITEMS.color_mixer, player) | ||||
|  | ||||
|  | ||||
| def has_tunnel(state: CollectionState, player: int) -> bool: | ||||
|     return state.has_any((ITEMS.tunnel, ITEMS.tunnel_tier_ii), player) | ||||
|  | ||||
|  | ||||
| def has_balancer(state: CollectionState, player: int) -> bool: | ||||
|     return state.has(ITEMS.balancer, player) or state.has_all((ITEMS.comp_merger, ITEMS.comp_splitter), player) | ||||
|  | ||||
|  | ||||
| def can_use_quad_painter(state: CollectionState, player: int) -> bool: | ||||
|     return (state.has_all((ITEMS.painter_quad, ITEMS.wires), player) and | ||||
|             state.has_any((ITEMS.switch, ITEMS.const_signal), player)) | ||||
|  | ||||
|  | ||||
| def can_make_stitched_shape(state: CollectionState, player: int, floating: bool) -> bool: | ||||
|     return (can_stack(state, player) and | ||||
|             ((state.has(ITEMS.cutter_quad, player) and not floating) or | ||||
|              (can_cut_half(state, player) and can_rotate_90(state, player)))) | ||||
|  | ||||
|  | ||||
| def can_build_mam(state: CollectionState, player: int, floating: bool) -> bool: | ||||
|     return (can_make_stitched_shape(state, player, floating) and can_paint(state, player) and | ||||
|             can_mix_colors(state, player) and has_balancer(state, player) and has_tunnel(state, player) and | ||||
|             state.has_all((ITEMS.belt_reader, ITEMS.storage, ITEMS.item_filter, | ||||
|                            ITEMS.wires, ITEMS.logic_gates, ITEMS.virtual_proc), player)) | ||||
|  | ||||
|  | ||||
| def can_make_east_windmill(state: CollectionState, player: int) -> bool: | ||||
|     # Only used for shapesanity => single layers | ||||
|     return (can_stack(state, player) and | ||||
|             (state.has(ITEMS.cutter_quad, player) or (can_cut_half(state, player) and can_rotate_180(state, player)))) | ||||
|  | ||||
|  | ||||
| def can_make_half_half_shape(state: CollectionState, player: int) -> bool: | ||||
|     # Only used for shapesanity => single layers | ||||
|     return can_stack(state, player) and state.has_any((ITEMS.cutter, ITEMS.cutter_quad), player) | ||||
|  | ||||
|  | ||||
| def can_make_half_shape(state: CollectionState, player: int) -> bool: | ||||
|     # Only used for shapesanity => single layers | ||||
|     return can_cut_half(state, player) or state.has_all((ITEMS.cutter_quad, ITEMS.stacker), player) | ||||
|  | ||||
|  | ||||
| def has_x_belt_multiplier(state: CollectionState, player: int, needed: float) -> bool: | ||||
|     # Assumes there are no upgrade traps | ||||
|     multiplier = 1.0 | ||||
|     # Rising upgrades do the least improvement if received before other upgrades | ||||
|     for _ in range(state.count(ITEMS.upgrade_rising_belt, player)): | ||||
|         multiplier *= 2 | ||||
|     multiplier += state.count(ITEMS.upgrade_gigantic_belt, player)*10 | ||||
|     multiplier += state.count(ITEMS.upgrade_big_belt, player) | ||||
|     multiplier += state.count(ITEMS.upgrade_small_belt, player)*0.1 | ||||
|     return multiplier >= needed | ||||
|  | ||||
|  | ||||
| def has_logic_list_building(state: CollectionState, player: int, buildings: List[str], index: int, | ||||
|                             includeuseful: bool) -> bool: | ||||
|  | ||||
|     # Includes balancer, tunnel, and trash in logic in order to make them appear in earlier spheres | ||||
|     if includeuseful and not (state.has(ITEMS.trash, player) and has_balancer(state, player) and | ||||
|                               has_tunnel(state, player)): | ||||
|         return False | ||||
|  | ||||
|     if buildings[index] == ITEMS.cutter: | ||||
|         if buildings.index(ITEMS.stacker) < index: | ||||
|             return state.has_any((ITEMS.cutter, ITEMS.cutter_quad), player) | ||||
|         else: | ||||
|             return can_cut_half(state, player) | ||||
|     elif buildings[index] == ITEMS.rotator: | ||||
|         return can_rotate_90(state, player) | ||||
|     elif buildings[index] == ITEMS.stacker: | ||||
|         return can_stack(state, player) | ||||
|     elif buildings[index] == ITEMS.painter: | ||||
|         return can_paint(state, player) | ||||
|     elif buildings[index] == ITEMS.color_mixer: | ||||
|         return can_mix_colors(state, player) | ||||
|  | ||||
|  | ||||
| def create_shapez_regions(player: int, multiworld: MultiWorld, floating: bool, | ||||
|                           included_locations: Dict[str, Tuple[str, LocationProgressType]], | ||||
|                           location_name_to_id: Dict[str, int], level_logic_buildings: List[str], | ||||
|                           upgrade_logic_buildings: List[str], early_useful: str, goal: str) -> List[Region]: | ||||
|     """Creates and returns a list of all regions with entrances and all locations placed correctly.""" | ||||
|     regions: Dict[str, Region] = {name: Region(name, player, multiworld) for name in all_regions} | ||||
|  | ||||
|     # Creates ShapezLocations for every included location and puts them into the correct region | ||||
|     for name, data in included_locations.items(): | ||||
|         regions[data[0]].locations.append(ShapezLocation(player, name, location_name_to_id[name], | ||||
|                                                          regions[data[0]], data[1])) | ||||
|  | ||||
|     # Create goal event | ||||
|     if goal in [GOALS.vanilla, GOALS.mam]: | ||||
|         goal_region = regions[REGIONS.levels_5] | ||||
|     elif goal == GOALS.even_fasterer: | ||||
|         goal_region = regions[REGIONS.upgrades_5] | ||||
|     else: | ||||
|         goal_region = regions[REGIONS.all_buildings] | ||||
|     goal_location = ShapezLocation(player, LOCATIONS.goal, None, goal_region, LocationProgressType.DEFAULT) | ||||
|     goal_location.place_locked_item(ShapezItem(ITEMS.goal, ItemClassification.progression_skip_balancing, None, player)) | ||||
|     if goal == GOALS.efficiency_iii: | ||||
|         add_rule(goal_location, lambda state: has_x_belt_multiplier(state, player, 8)) | ||||
|     goal_region.locations.append(goal_location) | ||||
|     multiworld.completion_condition[player] = lambda state: state.has(ITEMS.goal, player) | ||||
|  | ||||
|     # Connect Menu to rest of regions | ||||
|     regions[REGIONS.menu].connect(regions[REGIONS.belt], "Placing belts", lambda state: state.has(ITEMS.belt, player)) | ||||
|     regions[REGIONS.menu].connect(regions[REGIONS.extract], "Extracting shapes from patches", | ||||
|                                   lambda state: state.has_any((ITEMS.extractor, ITEMS.extractor_chain), player)) | ||||
|     regions[REGIONS.extract].connect( | ||||
|         regions[REGIONS.main], "Transporting shapes over the canvas", | ||||
|         lambda state: state.has_any((ITEMS.belt, ITEMS.comp_merger, ITEMS.comp_splitter), player) | ||||
|     ) | ||||
|  | ||||
|     # Connect achievement regions | ||||
|     regions[REGIONS.main].connect(regions[REGIONS.paint_not_quad], "Painting with (double) painter", | ||||
|                                   lambda state: state.has_any((ITEMS.painter, ITEMS.painter_double), player)) | ||||
|     regions[REGIONS.extract].connect(regions[REGIONS.cut_not_quad], "Cutting with half cutter", | ||||
|                                      lambda state: can_cut_half(state, player)) | ||||
|     regions[REGIONS.extract].connect(regions[REGIONS.rotate_cw], "Rotating clockwise", | ||||
|                                      lambda state: state.has(ITEMS.rotator, player)) | ||||
|     regions[REGIONS.extract].connect(regions[REGIONS.stack_shape], "Stacking shapes", | ||||
|                                      lambda state: can_stack(state, player)) | ||||
|     regions[REGIONS.extract].connect(regions[REGIONS.store_shape], "Storing shapes", | ||||
|                                      lambda state: state.has(ITEMS.storage, player)) | ||||
|     regions[REGIONS.extract].connect(regions[REGIONS.trash_shape], "Trashing shapes", | ||||
|                                      lambda state: state.has(ITEMS.trash, player)) | ||||
|     regions[REGIONS.main].connect(regions[REGIONS.blueprint], "Copying and placing blueprints", | ||||
|                                   lambda state: state.has(ITEMS.blueprints, player) and | ||||
|                                                 can_make_stitched_shape(state, player, floating) and | ||||
|                                                 can_paint(state, player) and can_mix_colors(state, player)) | ||||
|     regions[REGIONS.menu].connect(regions[REGIONS.wiring], "Using the wires layer", | ||||
|                                   lambda state: state.has(ITEMS.wires, player)) | ||||
|     regions[REGIONS.main].connect(regions[REGIONS.mam], "Building a MAM", | ||||
|                                   lambda state: can_build_mam(state, player, floating)) | ||||
|     regions[REGIONS.menu].connect(regions[REGIONS.any_building], "Placing any building", lambda state: state.has_any(( | ||||
|         ITEMS.belt, ITEMS.balancer, ITEMS.comp_merger, ITEMS.comp_splitter, ITEMS.tunnel, ITEMS.tunnel_tier_ii, | ||||
|         ITEMS.extractor, ITEMS.extractor_chain, ITEMS.cutter, ITEMS.cutter_quad, ITEMS.rotator, ITEMS.rotator_ccw, | ||||
|         ITEMS.rotator_180, ITEMS.stacker, ITEMS.painter, ITEMS.painter_double, ITEMS.painter_quad, ITEMS.color_mixer, | ||||
|         ITEMS.trash, ITEMS.belt_reader, ITEMS.storage, ITEMS.switch, ITEMS.item_filter, ITEMS.display, ITEMS.wires | ||||
|     ), player)) | ||||
|     regions[REGIONS.main].connect(regions[REGIONS.all_buildings], "Using all main buildings", | ||||
|                                   lambda state: can_make_stitched_shape(state, player, floating) and | ||||
|                                                 can_paint(state, player) and can_mix_colors(state, player)) | ||||
|     regions[REGIONS.all_buildings].connect(regions[REGIONS.all_buildings_x1_6_belt], | ||||
|                                            "Delivering per second with 1.6x belt speed", | ||||
|                                            lambda state: has_x_belt_multiplier(state, player, 1.6)) | ||||
|  | ||||
|     # Progressively connect level and upgrade regions | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.levels_1], "Using first level building", | ||||
|         lambda state: has_logic_list_building(state, player, level_logic_buildings, 0, False)) | ||||
|     regions[REGIONS.levels_1].connect( | ||||
|         regions[REGIONS.levels_2], "Using second level building", | ||||
|         lambda state: has_logic_list_building(state, player, level_logic_buildings, 1, False)) | ||||
|     regions[REGIONS.levels_2].connect( | ||||
|         regions[REGIONS.levels_3], "Using third level building", | ||||
|         lambda state: has_logic_list_building(state, player, level_logic_buildings, 2, | ||||
|                                               early_useful == OPTIONS.buildings_3)) | ||||
|     regions[REGIONS.levels_3].connect( | ||||
|         regions[REGIONS.levels_4], "Using fourth level building", | ||||
|         lambda state: has_logic_list_building(state, player, level_logic_buildings, 3, False)) | ||||
|     regions[REGIONS.levels_4].connect( | ||||
|         regions[REGIONS.levels_5], "Using fifth level building", | ||||
|         lambda state: has_logic_list_building(state, player, level_logic_buildings, 4, | ||||
|                                               early_useful == OPTIONS.buildings_5)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.upgrades_1], "Using first upgrade building", | ||||
|         lambda state: has_logic_list_building(state, player, upgrade_logic_buildings, 0, False)) | ||||
|     regions[REGIONS.upgrades_1].connect( | ||||
|         regions[REGIONS.upgrades_2], "Using second upgrade building", | ||||
|         lambda state: has_logic_list_building(state, player, upgrade_logic_buildings, 1, False)) | ||||
|     regions[REGIONS.upgrades_2].connect( | ||||
|         regions[REGIONS.upgrades_3], "Using third upgrade building", | ||||
|         lambda state: has_logic_list_building(state, player, upgrade_logic_buildings, 2, | ||||
|                                               early_useful == OPTIONS.buildings_3)) | ||||
|     regions[REGIONS.upgrades_3].connect( | ||||
|         regions[REGIONS.upgrades_4], "Using fourth upgrade building", | ||||
|         lambda state: has_logic_list_building(state, player, upgrade_logic_buildings, 3, False)) | ||||
|     regions[REGIONS.upgrades_4].connect( | ||||
|         regions[REGIONS.upgrades_5], "Using fifth upgrade building", | ||||
|         lambda state: has_logic_list_building(state, player, upgrade_logic_buildings, 4, | ||||
|                                               early_useful == OPTIONS.buildings_5)) | ||||
|  | ||||
|     # Connect Uncolored shapesanity regions to Main | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.full, REGIONS.uncol)], "Delivering unprocessed", lambda state: True) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.half, REGIONS.uncol)], "Cutting in single half", | ||||
|         lambda state: can_make_half_shape(state, player)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.piece, REGIONS.uncol)], "Cutting in single piece", | ||||
|         lambda state: (can_cut_half(state, player) and can_rotate_90(state, player)) or | ||||
|                       state.has(ITEMS.cutter_quad, player)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.half_half, REGIONS.uncol)], "Cutting and stacking into two halves", | ||||
|         lambda state: can_make_half_half_shape(state, player)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.stitched, REGIONS.uncol)], "Stitching complex shapes", | ||||
|         lambda state: can_make_stitched_shape(state, player, floating)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.east_wind, REGIONS.uncol)], "Rotating and stitching a single windmill half", | ||||
|         lambda state: can_make_east_windmill(state, player)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.col_full, REGIONS.uncol)], "Painting with a quad painter or stitching", | ||||
|         lambda state: can_make_stitched_shape(state, player, floating) or can_use_quad_painter(state, player)) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.col_east_wind, REGIONS.uncol)], "Why windmill, why?", | ||||
|         lambda state: can_make_stitched_shape(state, player, floating) or | ||||
|                       (can_use_quad_painter(state, player) and can_make_east_windmill(state, player))) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.col_half_half, REGIONS.uncol)], "Quad painting a half-half shape", | ||||
|         lambda state: can_make_stitched_shape(state, player, floating) or | ||||
|                       (can_use_quad_painter(state, player) and can_make_half_half_shape(state, player))) | ||||
|     regions[REGIONS.main].connect( | ||||
|         regions[REGIONS.sanity(REGIONS.col_half, REGIONS.uncol)], "Quad painting a half shape", | ||||
|         lambda state: can_make_stitched_shape(state, player, floating) or | ||||
|                       (can_use_quad_painter(state, player) and can_make_half_shape(state, player))) | ||||
|  | ||||
|     # Progressively connect colored shapesanity regions | ||||
|     for processing in shapesanity_processing: | ||||
|         regions[REGIONS.sanity(processing, REGIONS.uncol)].connect( | ||||
|             regions[REGIONS.sanity(processing, REGIONS.painted)], f"Painting a {processing.lower()} shape", | ||||
|             lambda state: can_paint(state, player)) | ||||
|         regions[REGIONS.sanity(processing, REGIONS.painted)].connect( | ||||
|             regions[REGIONS.sanity(processing, REGIONS.mixed)], f"Mixing colors for a {processing.lower()} shape", | ||||
|             lambda state: can_mix_colors(state, player)) | ||||
|  | ||||
|     return [region for region in regions.values() if len(region.locations) or len(region.exits)] | ||||
							
								
								
									
										213
									
								
								worlds/shapez/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								worlds/shapez/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| from unittest import TestCase | ||||
|  | ||||
| from test.bases import WorldTestBase | ||||
| from .. import options_presets, ShapezWorld | ||||
| from ..data.strings import GOALS, OTHER, ITEMS, LOCATIONS, CATEGORY, OPTIONS, SHAPESANITY | ||||
| from ..options import max_levels_and_upgrades, max_shapesanity | ||||
|  | ||||
|  | ||||
| class ShapezTestBase(WorldTestBase): | ||||
|     game = OTHER.game_name | ||||
|     world: ShapezWorld | ||||
|  | ||||
|     def test_location_count(self): | ||||
|         self.assertTrue(self.world.location_count > 0, | ||||
|                         f"location_count is {self.world.location_count} for some reason.") | ||||
|  | ||||
|     def test_logic_lists(self): | ||||
|         logic_buildings = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker] | ||||
|         for building in logic_buildings: | ||||
|             count = self.world.level_logic.count(building) | ||||
|             self.assertTrue(count == 1, f"{building} was found {count} times in level_logic.") | ||||
|             count = self.world.upgrade_logic.count(building) | ||||
|             self.assertTrue(count == 1, f"{building} was found {count} times in upgrade_logic.") | ||||
|         self.assertTrue(len(self.world.level_logic) == 5, | ||||
|                         f"level_logic contains {len(self.world.level_logic)} entries instead of the expected 5.") | ||||
|         self.assertTrue(len(self.world.upgrade_logic) == 5, | ||||
|                         f"upgrade_logic contains {len(self.world.upgrade_logic)} entries instead of the expected 5.") | ||||
|  | ||||
|     def test_random_logic_phase_length(self): | ||||
|         self.assertTrue(len(self.world.random_logic_phase_length) == 5, | ||||
|                         f"random_logic_phase_length contains {len(self.world.random_logic_phase_length)} entries " + | ||||
|                         f"instead of the expected 5.") | ||||
|         self.assertTrue(sum(self.world.random_logic_phase_length) < self.world.maxlevel, | ||||
|                         f"The sum of all random phase lengths is greater than allowed: " + | ||||
|                         str(sum(self.world.random_logic_phase_length))) | ||||
|         for length in self.world.random_logic_phase_length: | ||||
|             self.assertTrue(length in range(self.world.maxlevel), | ||||
|                             f"Found an illegal value in random_logic_phase_length: {length}") | ||||
|  | ||||
|     def test_category_random_logic_amounts(self): | ||||
|         self.assertTrue(len(self.world.category_random_logic_amounts) == 4, | ||||
|                         f"Found {len(self.world.category_random_logic_amounts)} instead of 4 keys in " | ||||
|                         f"category_random_logic_amounts.") | ||||
|         self.assertTrue(min(self.world.category_random_logic_amounts.values()) == 0, | ||||
|                         "Found a value less than or no 0 in category_random_logic_amounts.") | ||||
|         self.assertTrue(max(self.world.category_random_logic_amounts.values()) <= 5, | ||||
|                         "Found a value greater than 5 in category_random_logic_amounts.") | ||||
|  | ||||
|     def test_maxlevel_and_finaltier(self): | ||||
|         self.assertTrue(self.world.maxlevel in range(25, max_levels_and_upgrades), | ||||
|                         f"Found an illegal value for maxlevel: {self.world.maxlevel}") | ||||
|         self.assertTrue(self.world.finaltier in range(8, max_levels_and_upgrades+1), | ||||
|                         f"Found an illegal value for finaltier: {self.world.finaltier}") | ||||
|  | ||||
|     def test_included_locations(self): | ||||
|         self.assertTrue(len(self.world.included_locations) > 0, "Found no locations cached in included_locations.") | ||||
|         self.assertTrue(LOCATIONS.level(1) in self.world.included_locations.keys(), | ||||
|                         "Could not find Level 1 (guraranteed location) cached in included_locations.") | ||||
|         self.assertTrue(LOCATIONS.upgrade(CATEGORY.belt, "II") in self.world.included_locations.keys(), | ||||
|                         "Could not find Belt Upgrade Tier II (guraranteed location) cached in included_locations.") | ||||
|         self.assertTrue(LOCATIONS.shapesanity(1) in self.world.included_locations.keys(), | ||||
|                         "Could not find Shapesanity 1 (guraranteed location) cached in included_locations.") | ||||
|  | ||||
|     def test_shapesanity_names(self): | ||||
|         names_length = len(self.world.shapesanity_names) | ||||
|         locations_length = len([0 for loc in self.multiworld.get_locations(self.player) if "Shapesanity" in loc.name]) | ||||
|         self.assertEqual(names_length, locations_length, | ||||
|                          f"The amount of shapesanity names ({names_length}) does not match the amount of included " + | ||||
|                          f"shapesanity locations ({locations_length}).") | ||||
|         self.assertTrue(SHAPESANITY.full(SHAPESANITY.uncolored, SHAPESANITY.circle) in self.world.shapesanity_names, | ||||
|                         "Uncolored Circle is guaranteed but was not found in shapesanity_names.") | ||||
|  | ||||
|     def test_efficiency_iii_no_softlock(self): | ||||
|         if self.world.options.goal == GOALS.efficiency_iii: | ||||
|             for item in self.multiworld.itempool: | ||||
|                 self.assertFalse(item.name.endswith("Upgrade Trap"), | ||||
|                                  "Item pool contains an upgrade trap, which could make the efficiency_iii goal " | ||||
|                                  "unreachable if collected.") | ||||
|  | ||||
|  | ||||
| class TestGlobalOptionsImport(TestCase): | ||||
|  | ||||
|     def test_global_options_import(self): | ||||
|         self.assertTrue(isinstance(max_levels_and_upgrades, int), f"The global option max_levels_and_upgrades is not " + | ||||
|                                                                   f"an integer, but instead a " + | ||||
|                                                                   f"{type(max_levels_and_upgrades)}.") | ||||
|         self.assertTrue(max_levels_and_upgrades >= 27, f"max_levels_and_upgrades must be at least 27, but is " + | ||||
|                                                        f"{max_levels_and_upgrades} instead.") | ||||
|         self.assertTrue(isinstance(max_shapesanity, int), f"The global option max_shapesanity is not an integer, but " + | ||||
|                                                           f"instead a {type(max_levels_and_upgrades)}.") | ||||
|         self.assertTrue(max_shapesanity >= 4, f"max_shapesanity must be at least 4, but is " + | ||||
|                                               f"{max_levels_and_upgrades} instead.") | ||||
|  | ||||
|  | ||||
| class TestMinimum(ShapezTestBase): | ||||
|     options = options_presets["Minimum checks"] | ||||
|  | ||||
|  | ||||
| class TestMaximum(ShapezTestBase): | ||||
|     options = options_presets["Maximum checks"] | ||||
|  | ||||
|  | ||||
| class TestRestrictive(ShapezTestBase): | ||||
|     options = options_presets["Restrictive start"] | ||||
|  | ||||
|  | ||||
| class TestAllRelevantOptions1(ShapezTestBase): | ||||
|     options = { | ||||
|         "goal": GOALS.vanilla, | ||||
|         "randomize_level_requirements": False, | ||||
|         "randomize_upgrade_requirements": False, | ||||
|         "complexity_growth_gradient": "0.1234", | ||||
|         "early_balancer_tunnel_and_trash": "none", | ||||
|         "lock_belt_and_extractor": True, | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": False, | ||||
|         "exclude_long_playtime_achievements": False, | ||||
|         "exclude_progression_unreasonable": True, | ||||
|         "shapesanity_amount": max_shapesanity, | ||||
|         "traps_percentage": "random" | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestAllRelevantOptions2(ShapezTestBase): | ||||
|     options = { | ||||
|         "goal": GOALS.mam, | ||||
|         "goal_amount": max_levels_and_upgrades, | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": OPTIONS.logic_random_steps, | ||||
|         "randomize_upgrade_logic": OPTIONS.logic_vanilla_like, | ||||
|         "complexity_growth_gradient": "2", | ||||
|         "early_balancer_tunnel_and_trash": OPTIONS.buildings_5, | ||||
|         "lock_belt_and_extractor": False, | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": False, | ||||
|         "exclude_long_playtime_achievements": False, | ||||
|         "exclude_progression_unreasonable": False, | ||||
|         "shapesanity_amount": 4, | ||||
|         "traps_percentage": 0 | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestAllRelevantOptions3(ShapezTestBase): | ||||
|     options = { | ||||
|         "goal": GOALS.even_fasterer, | ||||
|         "goal_amount": max_levels_and_upgrades, | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": f"{OPTIONS.logic_vanilla}_shuffled", | ||||
|         "randomize_upgrade_logic": OPTIONS.logic_linear, | ||||
|         "complexity_growth_gradient": "1e-003", | ||||
|         "early_balancer_tunnel_and_trash": OPTIONS.buildings_3, | ||||
|         "lock_belt_and_extractor": False, | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": True, | ||||
|         "exclude_long_playtime_achievements": True, | ||||
|         "shapesanity_amount": "random", | ||||
|         "traps_percentage": 100, | ||||
|         "include_whacky_upgrades": True, | ||||
|         "split_inventory_draining_trap": True | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestAllRelevantOptions4(ShapezTestBase): | ||||
|     options = { | ||||
|         "goal": GOALS.efficiency_iii, | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": f"{OPTIONS.logic_stretched}_shuffled", | ||||
|         "randomize_upgrade_logic": OPTIONS.logic_category, | ||||
|         "early_balancer_tunnel_and_trash": OPTIONS.sphere_1, | ||||
|         "lock_belt_and_extractor": False, | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": True, | ||||
|         "exclude_long_playtime_achievements": True, | ||||
|         "shapesanity_amount": "random", | ||||
|         "traps_percentage": "random", | ||||
|         "include_whacky_upgrades": True, | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestAllRelevantOptions5(ShapezTestBase): | ||||
|     options = { | ||||
|         "goal": GOALS.mam, | ||||
|         "goal_amount": "random-range-27-500", | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": f"{OPTIONS.logic_quick}_shuffled", | ||||
|         "randomize_upgrade_logic": OPTIONS.logic_category_random, | ||||
|         "lock_belt_and_extractor": False, | ||||
|         "include_achievements": True, | ||||
|         "exclude_softlock_achievements": True, | ||||
|         "exclude_long_playtime_achievements": True, | ||||
|         "shapesanity_amount": "random", | ||||
|         "traps_percentage": 100, | ||||
|         "split_inventory_draining_trap": False | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestAllRelevantOptions6(ShapezTestBase): | ||||
|     options = { | ||||
|         "goal": GOALS.mam, | ||||
|         "goal_amount": "random-range-27-500", | ||||
|         "randomize_level_requirements": True, | ||||
|         "randomize_upgrade_requirements": True, | ||||
|         "randomize_level_logic": OPTIONS.logic_hardcore, | ||||
|         "randomize_upgrade_logic": OPTIONS.logic_hardcore, | ||||
|         "lock_belt_and_extractor": False, | ||||
|         "include_achievements": False, | ||||
|         "shapesanity_amount": "random", | ||||
|         "traps_percentage": "random" | ||||
|     } | ||||
		Reference in New Issue
	
	Block a user
	 BlastSlimey
					BlastSlimey