Lingo: New game (#1806)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com> Co-authored-by: Phar <zach@alliware.com>
This commit is contained in:
		 Star Rauchenberger
					Star Rauchenberger
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							154e17f4ff
						
					
				
				
					commit
					ea9c31392d
				
			
							
								
								
									
										7505
									
								
								worlds/lingo/LL1.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7505
									
								
								worlds/lingo/LL1.yaml
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										112
									
								
								worlds/lingo/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								worlds/lingo/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| """ | ||||
| Archipelago init file for Lingo | ||||
| """ | ||||
| from BaseClasses import Item, Tutorial | ||||
| from worlds.AutoWorld import WebWorld, World | ||||
| from .items import ALL_ITEM_TABLE, LingoItem | ||||
| from .locations import ALL_LOCATION_TABLE | ||||
| from .options import LingoOptions | ||||
| from .player_logic import LingoPlayerLogic | ||||
| from .regions import create_regions | ||||
| from .static_logic import Room, RoomEntrance | ||||
| from .testing import LingoTestOptions | ||||
|  | ||||
|  | ||||
| class LingoWebWorld(WebWorld): | ||||
|     theme = "grass" | ||||
|     tutorials = [Tutorial( | ||||
|         "Multiworld Setup Guide", | ||||
|         "A guide to playing Lingo with Archipelago.", | ||||
|         "English", | ||||
|         "setup_en.md", | ||||
|         "setup/en", | ||||
|         ["hatkirby"] | ||||
|     )] | ||||
|  | ||||
|  | ||||
| class LingoWorld(World): | ||||
|     """ | ||||
|     Lingo is a first person indie puzzle game in the vein of The Witness. You find yourself in a mazelike, non-Euclidean | ||||
|     world filled with 800 word puzzles that use a variety of different mechanics. | ||||
|     """ | ||||
|     game = "Lingo" | ||||
|     web = LingoWebWorld() | ||||
|  | ||||
|     base_id = 444400 | ||||
|     topology_present = True | ||||
|     data_version = 1 | ||||
|  | ||||
|     options_dataclass = LingoOptions | ||||
|     options: LingoOptions | ||||
|  | ||||
|     item_name_to_id = { | ||||
|         name: data.code for name, data in ALL_ITEM_TABLE.items() | ||||
|     } | ||||
|     location_name_to_id = { | ||||
|         name: data.code for name, data in ALL_LOCATION_TABLE.items() | ||||
|     } | ||||
|  | ||||
|     player_logic: LingoPlayerLogic | ||||
|  | ||||
|     def generate_early(self): | ||||
|         self.player_logic = LingoPlayerLogic(self) | ||||
|  | ||||
|     def create_regions(self): | ||||
|         create_regions(self, self.player_logic) | ||||
|  | ||||
|     def create_items(self): | ||||
|         pool = [self.create_item(name) for name in self.player_logic.REAL_ITEMS] | ||||
|  | ||||
|         if self.player_logic.FORCED_GOOD_ITEM != "": | ||||
|             new_item = self.create_item(self.player_logic.FORCED_GOOD_ITEM) | ||||
|             location_obj = self.multiworld.get_location("Second Room - Good Luck", self.player) | ||||
|             location_obj.place_locked_item(new_item) | ||||
|  | ||||
|         item_difference = len(self.player_logic.REAL_LOCATIONS) - len(pool) | ||||
|         if item_difference: | ||||
|             trap_percentage = self.options.trap_percentage | ||||
|             traps = int(item_difference * trap_percentage / 100.0) | ||||
|             non_traps = item_difference - traps | ||||
|  | ||||
|             if non_traps: | ||||
|                 skip_percentage = self.options.puzzle_skip_percentage | ||||
|                 skips = int(non_traps * skip_percentage / 100.0) | ||||
|                 non_skips = non_traps - skips | ||||
|  | ||||
|                 filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"] | ||||
|                 for i in range(0, non_skips): | ||||
|                     pool.append(self.create_item(filler_list[i % len(filler_list)])) | ||||
|  | ||||
|                 for i in range(0, skips): | ||||
|                     pool.append(self.create_item("Puzzle Skip")) | ||||
|  | ||||
|             if traps: | ||||
|                 traps_list = ["Slowness Trap", "Iceland Trap", "Atbash Trap"] | ||||
|  | ||||
|                 for i in range(0, traps): | ||||
|                     pool.append(self.create_item(traps_list[i % len(traps_list)])) | ||||
|  | ||||
|         self.multiworld.itempool += pool | ||||
|  | ||||
|     def create_item(self, name: str) -> Item: | ||||
|         item = ALL_ITEM_TABLE[name] | ||||
|         return LingoItem(name, item.classification, item.code, self.player) | ||||
|  | ||||
|     def set_rules(self): | ||||
|         self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) | ||||
|  | ||||
|     def fill_slot_data(self): | ||||
|         slot_options = [ | ||||
|             "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", | ||||
|             "mastery_achievements", "level_2_requirement", "location_checks", "early_color_hallways" | ||||
|         ] | ||||
|  | ||||
|         slot_data = { | ||||
|             "seed": self.random.randint(0, 1000000), | ||||
|             **self.options.as_dict(*slot_options), | ||||
|         } | ||||
|  | ||||
|         if self.options.shuffle_paintings: | ||||
|             slot_data["painting_entrance_to_exit"] = self.player_logic.PAINTING_MAPPING | ||||
|  | ||||
|         return slot_data | ||||
							
								
								
									
										42
									
								
								worlds/lingo/docs/en_Lingo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								worlds/lingo/docs/en_Lingo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| # Lingo | ||||
|  | ||||
| ## Where is the settings page? | ||||
|  | ||||
| The [player settings page for this game](../player-settings) contains all the options you need to configure and export a | ||||
| config file. | ||||
|  | ||||
| ## What does randomization do to this game? | ||||
|  | ||||
| There are a couple of modes of randomization currently available, and you can pick and choose which ones you would like | ||||
| to use. | ||||
|  | ||||
| * **Door shuffle**: There are many doors in the game, which are opened by completing a set of panels. With door shuffle | ||||
|   on, the doors become items and only open up once you receive the corresponding item. The panel sets that would | ||||
|   ordinarily open the doors become locations. | ||||
|  | ||||
| * **Color shuffle**: There are ten different colors of puzzle in the game, each representing a different mechanic. With | ||||
|   color shuffle on, you would start with only access to white puzzles. Puzzles of other colors will require you to | ||||
|   receive an  item in order to solve them (e.g. you can't solve any red puzzles until you receive the "Red" item). | ||||
|  | ||||
| * **Panel shuffle**: Panel shuffling replaces the puzzles on each panel with different ones. So far, the only mode of | ||||
|   panel shuffling is "rearrange" mode, which simply shuffles the already-existing puzzles from the base game onto | ||||
|   different panels. | ||||
|  | ||||
| * **Painting shuffle**: This randomizes the appearance of the paintings in the game, as well as which of them are warps, | ||||
|   and the locations that they warp you to. It is the equivalent of an entrance randomizer in another game. | ||||
|  | ||||
| ## What is a "check" in this game? | ||||
|  | ||||
| Most panels / panel sets that open a door are now location checks, even if door shuffle is not enabled. Various other | ||||
| puzzles are also location checks, including the achievement panels for each area. | ||||
|  | ||||
| ## What about wall snipes? | ||||
|  | ||||
| "Wall sniping" refers to the fact that you are able to solve puzzles on the other side of opaque walls. This randomizer | ||||
| does not change how wall snipes work, but it will never require the use of them. There are three puzzles from the base | ||||
| game that you would ordinarily be expected to wall snipe. The randomizer moves these panels out of the wall or otherwise | ||||
| reveals them so that a snipe is not necessary. | ||||
|  | ||||
| Because of this, all wall snipes are considered out of logic. This includes sniping The Bearer's MIDDLE while standing | ||||
| outside The Bold, sniping The Colorful without opening all of the color doors, and sniping WELCOME from next to WELCOME | ||||
| BACK. | ||||
							
								
								
									
										45
									
								
								worlds/lingo/docs/setup_en.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								worlds/lingo/docs/setup_en.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| # Lingo Randomizer Setup | ||||
|  | ||||
| ## Required Software | ||||
|  | ||||
| - [Lingo](https://store.steampowered.com/app/1814170/Lingo/) | ||||
| - [Lingo Archipelago Randomizer](https://code.fourisland.com/lingo-archipelago/about/CHANGELOG.md) | ||||
|  | ||||
| ## Optional Software | ||||
|  | ||||
| - [Archipelago Text Client](https://github.com/ArchipelagoMW/Archipelago/releases) | ||||
| - [Lingo AP Tracker](https://code.fourisland.com/lingo-ap-tracker/about/CHANGELOG.md) | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| 1. Download the Lingo Archipelago Randomizer from the above link. | ||||
| 2. Open up Lingo, go to settings, and click View Game Data. This should open up | ||||
|    a folder in Windows Explorer. | ||||
| 3. Unzip the contents of the randomizer into the "maps" folder. You may need to | ||||
|    create the "maps" folder if you have not played a custom Lingo map before. | ||||
| 4. Installation complete! You may have to click Return to go back to the main | ||||
|    menu and then click Settings again in order to get the randomizer to show up | ||||
|    in the level selection list. | ||||
|  | ||||
| ## Joining a Multiworld game | ||||
|  | ||||
| 1. Launch Lingo | ||||
| 2. Click on Settings, and then Level. Choose Archipelago from the list. | ||||
| 3. Start a new game. Leave the name field blank (anything you type in will be | ||||
|    ignored). | ||||
| 4. Enter the Archipelago address, slot name, and password into the fields. | ||||
| 5. Press Connect. | ||||
| 6. Enjoy! | ||||
|  | ||||
| To continue an earlier game, you can perform the exact same steps as above. You | ||||
| do not have to re-select Archipelago in the level selection screen if you were | ||||
| using Archipelago the last time you launched the game. | ||||
|  | ||||
| In order to play the base game again, simply return to the level selection | ||||
| screen and choose Level 1 (or whatever else you want to play). The randomizer | ||||
| will not affect gameplay unless you launch it by starting a new game while it is | ||||
| selected in the level selection screen, so it is safe to play the game normally | ||||
| while the client is installed. | ||||
|  | ||||
| **Note**: Running the randomizer modifies the game's memory. If you want to play | ||||
| the base game after playing the randomizer, you need to restart Lingo first. | ||||
							
								
								
									
										1449
									
								
								worlds/lingo/ids.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1449
									
								
								worlds/lingo/ids.yaml
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										106
									
								
								worlds/lingo/items.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								worlds/lingo/items.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING | ||||
|  | ||||
| from BaseClasses import Item, ItemClassification | ||||
| from .options import ShuffleDoors | ||||
| from .static_logic import DOORS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, \ | ||||
|     get_door_item_id, get_progressive_item_id, get_special_item_id | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from . import LingoWorld | ||||
|  | ||||
|  | ||||
| class ItemData(NamedTuple): | ||||
|     """ | ||||
|     ItemData for an item in Lingo | ||||
|     """ | ||||
|     code: int | ||||
|     classification: ItemClassification | ||||
|     mode: Optional[str] | ||||
|     door_ids: List[str] | ||||
|     painting_ids: List[str] | ||||
|  | ||||
|     def should_include(self, world: "LingoWorld") -> bool: | ||||
|         if self.mode == "colors": | ||||
|             return world.options.shuffle_colors > 0 | ||||
|         elif self.mode == "doors": | ||||
|             return world.options.shuffle_doors != ShuffleDoors.option_none | ||||
|         elif self.mode == "orange tower": | ||||
|             # door shuffle is on and tower isn't progressive | ||||
|             return world.options.shuffle_doors != ShuffleDoors.option_none \ | ||||
|                 and not world.options.progressive_orange_tower | ||||
|         elif self.mode == "complex door": | ||||
|             return world.options.shuffle_doors == ShuffleDoors.option_complex | ||||
|         elif self.mode == "door group": | ||||
|             return world.options.shuffle_doors == ShuffleDoors.option_simple | ||||
|         elif self.mode == "special": | ||||
|             return False | ||||
|         else: | ||||
|             return True | ||||
|  | ||||
|  | ||||
| class LingoItem(Item): | ||||
|     """ | ||||
|     Item from the game Lingo | ||||
|     """ | ||||
|     game: str = "Lingo" | ||||
|  | ||||
|  | ||||
| ALL_ITEM_TABLE: Dict[str, ItemData] = {} | ||||
|  | ||||
|  | ||||
| def load_item_data(): | ||||
|     global ALL_ITEM_TABLE | ||||
|  | ||||
|     for color in ["Black", "Red", "Blue", "Yellow", "Green", "Orange", "Gray", "Brown", "Purple"]: | ||||
|         ALL_ITEM_TABLE[color] = ItemData(get_special_item_id(color), ItemClassification.progression, | ||||
|                                          "colors", [], []) | ||||
|  | ||||
|     door_groups: Dict[str, List[str]] = {} | ||||
|     for room_name, doors in DOORS_BY_ROOM.items(): | ||||
|         for door_name, door in doors.items(): | ||||
|             if door.skip_item is True or door.event is True: | ||||
|                 continue | ||||
|  | ||||
|             if door.group is None: | ||||
|                 door_mode = "doors" | ||||
|             else: | ||||
|                 door_mode = "complex door" | ||||
|                 door_groups.setdefault(door.group, []).extend(door.door_ids) | ||||
|  | ||||
|             if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]: | ||||
|                 if room_name == "Orange Tower": | ||||
|                     door_mode = "orange tower" | ||||
|                 else: | ||||
|                     door_mode = "special" | ||||
|  | ||||
|             ALL_ITEM_TABLE[door.item_name] = \ | ||||
|                 ItemData(get_door_item_id(room_name, door_name), | ||||
|                          ItemClassification.filler if door.junk_item else ItemClassification.progression, door_mode, | ||||
|                          door.door_ids, door.painting_ids) | ||||
|  | ||||
|     for group, group_door_ids in door_groups.items(): | ||||
|         ALL_ITEM_TABLE[group] = ItemData(get_door_group_item_id(group), | ||||
|                                          ItemClassification.progression, "door group", group_door_ids, []) | ||||
|  | ||||
|     special_items: Dict[str, ItemClassification] = { | ||||
|         ":)":                        ItemClassification.filler, | ||||
|         "The Feeling of Being Lost": ItemClassification.filler, | ||||
|         "Wanderlust":                ItemClassification.filler, | ||||
|         "Empty White Hallways":      ItemClassification.filler, | ||||
|         "Slowness Trap":             ItemClassification.trap, | ||||
|         "Iceland Trap":              ItemClassification.trap, | ||||
|         "Atbash Trap":               ItemClassification.trap, | ||||
|         "Puzzle Skip":               ItemClassification.useful, | ||||
|     } | ||||
|  | ||||
|     for item_name, classification in special_items.items(): | ||||
|         ALL_ITEM_TABLE[item_name] = ItemData(get_special_item_id(item_name), classification, | ||||
|                                              "special", [], []) | ||||
|  | ||||
|     for item_name in PROGRESSIVE_ITEMS: | ||||
|         ALL_ITEM_TABLE[item_name] = ItemData(get_progressive_item_id(item_name), | ||||
|                                              ItemClassification.progression, "special", [], []) | ||||
|  | ||||
|  | ||||
| # Initialize the item data at module scope. | ||||
| load_item_data() | ||||
							
								
								
									
										80
									
								
								worlds/lingo/locations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								worlds/lingo/locations.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| from enum import Flag, auto | ||||
| from typing import Dict, List, NamedTuple | ||||
|  | ||||
| from BaseClasses import Location | ||||
| from .static_logic import DOORS_BY_ROOM, PANELS_BY_ROOM, RoomAndPanel, get_door_location_id, get_panel_location_id | ||||
|  | ||||
|  | ||||
| class LocationClassification(Flag): | ||||
|     normal = auto() | ||||
|     reduced = auto() | ||||
|     insanity = auto() | ||||
|  | ||||
|  | ||||
| class LocationData(NamedTuple): | ||||
|     """ | ||||
|     LocationData for a location in Lingo | ||||
|     """ | ||||
|     code: int | ||||
|     room: str | ||||
|     panels: List[RoomAndPanel] | ||||
|     classification: LocationClassification | ||||
|  | ||||
|     def panel_ids(self): | ||||
|         ids = set() | ||||
|         for panel in self.panels: | ||||
|             effective_room = self.room if panel.room is None else panel.room | ||||
|             panel_data = PANELS_BY_ROOM[effective_room][panel.panel] | ||||
|             ids = ids | set(panel_data.internal_ids) | ||||
|         return ids | ||||
|  | ||||
|  | ||||
| class LingoLocation(Location): | ||||
|     """ | ||||
|     Location from the game Lingo | ||||
|     """ | ||||
|     game: str = "Lingo" | ||||
|  | ||||
|  | ||||
| ALL_LOCATION_TABLE: Dict[str, LocationData] = {} | ||||
|  | ||||
|  | ||||
| def load_location_data(): | ||||
|     global ALL_LOCATION_TABLE | ||||
|  | ||||
|     for room_name, panels in PANELS_BY_ROOM.items(): | ||||
|         for panel_name, panel in panels.items(): | ||||
|             location_name = f"{room_name} - {panel_name}" | ||||
|  | ||||
|             classification = LocationClassification.insanity | ||||
|             if panel.check: | ||||
|                 classification |= LocationClassification.normal | ||||
|  | ||||
|                 if not panel.exclude_reduce: | ||||
|                     classification |= LocationClassification.reduced | ||||
|  | ||||
|             ALL_LOCATION_TABLE[location_name] = \ | ||||
|                 LocationData(get_panel_location_id(room_name, panel_name), room_name, | ||||
|                              [RoomAndPanel(None, panel_name)], classification) | ||||
|  | ||||
|     for room_name, doors in DOORS_BY_ROOM.items(): | ||||
|         for door_name, door in doors.items(): | ||||
|             if door.skip_location or door.event or door.panels is None: | ||||
|                 continue | ||||
|  | ||||
|             location_name = door.location_name | ||||
|             classification = LocationClassification.normal | ||||
|             if door.include_reduce: | ||||
|                 classification |= LocationClassification.reduced | ||||
|  | ||||
|             if location_name in ALL_LOCATION_TABLE: | ||||
|                 new_id = ALL_LOCATION_TABLE[location_name].code | ||||
|                 classification |= ALL_LOCATION_TABLE[location_name].classification | ||||
|             else: | ||||
|                 new_id = get_door_location_id(room_name, door_name) | ||||
|  | ||||
|             ALL_LOCATION_TABLE[location_name] = LocationData(new_id, room_name, door.panels, classification) | ||||
|  | ||||
|  | ||||
| # Initialize location data on the module scope. | ||||
| load_location_data() | ||||
							
								
								
									
										126
									
								
								worlds/lingo/options.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								worlds/lingo/options.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| from dataclasses import dataclass | ||||
|  | ||||
| from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions | ||||
|  | ||||
|  | ||||
| class ShuffleDoors(Choice): | ||||
|     """If on, opening doors will require their respective "keys". | ||||
|     In "simple", doors are sorted into logical groups, which are all opened by receiving an item. | ||||
|     In "complex", the items are much more granular, and will usually only open a single door each.""" | ||||
|     display_name = "Shuffle Doors" | ||||
|     option_none = 0 | ||||
|     option_simple = 1 | ||||
|     option_complex = 2 | ||||
|  | ||||
|  | ||||
| class ProgressiveOrangeTower(DefaultOnToggle): | ||||
|     """When "Shuffle Doors" is on, this setting governs the manner in which the Orange Tower floors open up. | ||||
|     If off, there is an item for each floor of the tower, and each floor's item is the only one needed to access that floor. | ||||
|     If on, there are six progressive items, which open up the tower from the bottom floor upward. | ||||
|     """ | ||||
|     display_name = "Progressive Orange Tower" | ||||
|  | ||||
|  | ||||
| class LocationChecks(Choice): | ||||
|     """On "normal", there will be a location check for each panel set that would ordinarily open a door, as well as for | ||||
|     achievement panels and a small handful of other panels. | ||||
|     On "reduced", many of the locations that are associated with opening doors are removed. | ||||
|     On "insanity", every individual panel in the game is a location check.""" | ||||
|     display_name = "Location Checks" | ||||
|     option_normal = 0 | ||||
|     option_reduced = 1 | ||||
|     option_insanity = 2 | ||||
|  | ||||
|  | ||||
| class ShuffleColors(Toggle): | ||||
|     """If on, an item is added to the pool for every puzzle color (besides White). | ||||
|     You will need to unlock the requisite colors in order to be able to solve puzzles of that color.""" | ||||
|     display_name = "Shuffle Colors" | ||||
|  | ||||
|  | ||||
| class ShufflePanels(Choice): | ||||
|     """If on, the puzzles on each panel are randomized. | ||||
|     On "rearrange", the puzzles are the same as the ones in the base game, but are placed in different areas.""" | ||||
|     display_name = "Shuffle Panels" | ||||
|     option_none = 0 | ||||
|     option_rearrange = 1 | ||||
|  | ||||
|  | ||||
| class ShufflePaintings(Toggle): | ||||
|     """If on, the destination, location, and appearance of the painting warps in the game will be randomized.""" | ||||
|     display_name = "Shuffle Paintings" | ||||
|  | ||||
|  | ||||
| class VictoryCondition(Choice): | ||||
|     """Change the victory condition.""" | ||||
|     display_name = "Victory Condition" | ||||
|     option_the_end = 0 | ||||
|     option_the_master = 1 | ||||
|     option_level_2 = 2 | ||||
|  | ||||
|  | ||||
| class MasteryAchievements(Range): | ||||
|     """The number of achievements required to unlock THE MASTER. | ||||
|     In the base game, 21 achievements are needed. | ||||
|     If you include The Scientific and The Unchallenged, which are in the base game but are not counted for mastery, 23 would be required. | ||||
|     If you include the custom achievement (The Wanderer), 24 would be required. | ||||
|     """ | ||||
|     display_name = "Mastery Achievements" | ||||
|     range_start = 1 | ||||
|     range_end = 24 | ||||
|     default = 21 | ||||
|  | ||||
|  | ||||
| class Level2Requirement(Range): | ||||
|     """The number of panel solves required to unlock LEVEL 2. | ||||
|     In the base game, 223 are needed. | ||||
|     Note that this count includes ANOTHER TRY. | ||||
|     """ | ||||
|     display_name = "Level 2 Requirement" | ||||
|     range_start = 2 | ||||
|     range_end = 800 | ||||
|     default = 223 | ||||
|  | ||||
|  | ||||
| class EarlyColorHallways(Toggle): | ||||
|     """When on, a painting warp to the color hallways area will appear in the starting room. | ||||
|     This lets you avoid being trapped in the starting room for long periods of time when door shuffle is on.""" | ||||
|     display_name = "Early Color Hallways" | ||||
|  | ||||
|  | ||||
| class TrapPercentage(Range): | ||||
|     """Replaces junk items with traps, at the specified rate.""" | ||||
|     display_name = "Trap Percentage" | ||||
|     range_start = 0 | ||||
|     range_end = 100 | ||||
|     default = 20 | ||||
|  | ||||
|  | ||||
| class PuzzleSkipPercentage(Range): | ||||
|     """Replaces junk items with puzzle skips, at the specified rate.""" | ||||
|     display_name = "Puzzle Skip Percentage" | ||||
|     range_start = 0 | ||||
|     range_end = 100 | ||||
|     default = 20 | ||||
|  | ||||
|  | ||||
| class DeathLink(Toggle): | ||||
|     """If on: Whenever another player on death link dies, you will be returned to the starting room.""" | ||||
|     display_name = "Death Link" | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class LingoOptions(PerGameCommonOptions): | ||||
|     shuffle_doors: ShuffleDoors | ||||
|     progressive_orange_tower: ProgressiveOrangeTower | ||||
|     location_checks: LocationChecks | ||||
|     shuffle_colors: ShuffleColors | ||||
|     shuffle_panels: ShufflePanels | ||||
|     shuffle_paintings: ShufflePaintings | ||||
|     victory_condition: VictoryCondition | ||||
|     mastery_achievements: MasteryAchievements | ||||
|     level_2_requirement: Level2Requirement | ||||
|     early_color_hallways: EarlyColorHallways | ||||
|     trap_percentage: TrapPercentage | ||||
|     puzzle_skip_percentage: PuzzleSkipPercentage | ||||
|     death_link: DeathLink | ||||
							
								
								
									
										298
									
								
								worlds/lingo/player_logic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								worlds/lingo/player_logic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,298 @@ | ||||
| from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING | ||||
|  | ||||
| from .items import ALL_ITEM_TABLE | ||||
| from .locations import ALL_LOCATION_TABLE, LocationClassification | ||||
| from .options import LocationChecks, ShuffleDoors, VictoryCondition | ||||
| from .static_logic import DOORS_BY_ROOM, Door, PAINTINGS, PAINTINGS_BY_ROOM, PAINTING_ENTRANCES, PAINTING_EXITS, \ | ||||
|     PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, ROOMS, \ | ||||
|     RoomAndPanel | ||||
| from .testing import LingoTestOptions | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from . import LingoWorld | ||||
|  | ||||
|  | ||||
| class PlayerLocation(NamedTuple): | ||||
|     name: str | ||||
|     code: Optional[int] = None | ||||
|     panels: List[RoomAndPanel] = [] | ||||
|  | ||||
|  | ||||
| class LingoPlayerLogic: | ||||
|     """ | ||||
|     Defines logic after a player's options have been applied | ||||
|     """ | ||||
|  | ||||
|     ITEM_BY_DOOR: Dict[str, Dict[str, str]] | ||||
|  | ||||
|     LOCATIONS_BY_ROOM: Dict[str, List[PlayerLocation]] | ||||
|     REAL_LOCATIONS: List[str] | ||||
|  | ||||
|     EVENT_LOC_TO_ITEM: Dict[str, str] | ||||
|     REAL_ITEMS: List[str] | ||||
|  | ||||
|     VICTORY_CONDITION: str | ||||
|     MASTERY_LOCATION: str | ||||
|     LEVEL_2_LOCATION: str | ||||
|  | ||||
|     PAINTING_MAPPING: Dict[str, str] | ||||
|  | ||||
|     FORCED_GOOD_ITEM: str | ||||
|  | ||||
|     def add_location(self, room: str, loc: PlayerLocation): | ||||
|         self.LOCATIONS_BY_ROOM.setdefault(room, []).append(loc) | ||||
|  | ||||
|     def set_door_item(self, room: str, door: str, item: str): | ||||
|         self.ITEM_BY_DOOR.setdefault(room, {})[door] = item | ||||
|  | ||||
|     def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"): | ||||
|         if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]: | ||||
|             if room_name == "Orange Tower" and not world.options.progressive_orange_tower: | ||||
|                 self.set_door_item(room_name, door_data.name, door_data.item_name) | ||||
|             else: | ||||
|                 progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name | ||||
|                 self.set_door_item(room_name, door_data.name, progressive_item_name) | ||||
|                 self.REAL_ITEMS.append(progressive_item_name) | ||||
|         else: | ||||
|             self.set_door_item(room_name, door_data.name, door_data.item_name) | ||||
|  | ||||
|     def __init__(self, world: "LingoWorld"): | ||||
|         self.ITEM_BY_DOOR = {} | ||||
|         self.LOCATIONS_BY_ROOM = {} | ||||
|         self.REAL_LOCATIONS = [] | ||||
|         self.EVENT_LOC_TO_ITEM = {} | ||||
|         self.REAL_ITEMS = [] | ||||
|         self.VICTORY_CONDITION = "" | ||||
|         self.MASTERY_LOCATION = "" | ||||
|         self.LEVEL_2_LOCATION = "" | ||||
|         self.PAINTING_MAPPING = {} | ||||
|         self.FORCED_GOOD_ITEM = "" | ||||
|  | ||||
|         door_shuffle = world.options.shuffle_doors | ||||
|         color_shuffle = world.options.shuffle_colors | ||||
|         painting_shuffle = world.options.shuffle_paintings | ||||
|         location_checks = world.options.location_checks | ||||
|         victory_condition = world.options.victory_condition | ||||
|         early_color_hallways = world.options.early_color_hallways | ||||
|  | ||||
|         if location_checks == LocationChecks.option_reduced and door_shuffle != ShuffleDoors.option_none: | ||||
|             raise Exception("You cannot have reduced location checks when door shuffle is on, because there would not " | ||||
|                             "be enough locations for all of the door items.") | ||||
|  | ||||
|         # Create an event for every room that represents being able to reach that room. | ||||
|         for room_name in ROOMS.keys(): | ||||
|             roomloc_name = f"{room_name} (Reached)" | ||||
|             self.add_location(room_name, PlayerLocation(roomloc_name, None, [])) | ||||
|             self.EVENT_LOC_TO_ITEM[roomloc_name] = roomloc_name | ||||
|  | ||||
|         # Create an event for every door, representing whether that door has been opened. Also create event items for | ||||
|         # doors that are event-only. | ||||
|         for room_name, room_data in DOORS_BY_ROOM.items(): | ||||
|             for door_name, door_data in room_data.items(): | ||||
|                 if door_shuffle == ShuffleDoors.option_none: | ||||
|                     itemloc_name = f"{room_name} - {door_name} (Opened)" | ||||
|                     self.add_location(room_name, PlayerLocation(itemloc_name, None, door_data.panels)) | ||||
|                     self.EVENT_LOC_TO_ITEM[itemloc_name] = itemloc_name | ||||
|                     self.set_door_item(room_name, door_name, itemloc_name) | ||||
|                 else: | ||||
|                     # This line is duplicated from StaticLingoItems | ||||
|                     if door_data.skip_item is False and door_data.event is False: | ||||
|                         if door_data.group is not None and door_shuffle == ShuffleDoors.option_simple: | ||||
|                             # Grouped doors are handled differently if shuffle doors is on simple. | ||||
|                             self.set_door_item(room_name, door_name, door_data.group) | ||||
|                         else: | ||||
|                             self.handle_non_grouped_door(room_name, door_data, world) | ||||
|  | ||||
|                 if door_data.event: | ||||
|                     self.add_location(room_name, PlayerLocation(door_data.item_name, None, door_data.panels)) | ||||
|                     self.EVENT_LOC_TO_ITEM[door_data.item_name] = door_data.item_name + " (Opened)" | ||||
|                     self.set_door_item(room_name, door_name, door_data.item_name + " (Opened)") | ||||
|  | ||||
|         # Create events for each achievement panel, so that we can determine when THE MASTER is accessible. We also | ||||
|         # create events for each counting panel, so that we can determine when LEVEL 2 is accessible. | ||||
|         for room_name, room_data in PANELS_BY_ROOM.items(): | ||||
|             for panel_name, panel_data in room_data.items(): | ||||
|                 if panel_data.achievement: | ||||
|                     event_name = room_name + " - " + panel_name + " (Achieved)" | ||||
|                     self.add_location(room_name, PlayerLocation(event_name, None, | ||||
|                                                                 [RoomAndPanel(room_name, panel_name)])) | ||||
|                     self.EVENT_LOC_TO_ITEM[event_name] = "Mastery Achievement" | ||||
|  | ||||
|                 if not panel_data.non_counting and victory_condition == VictoryCondition.option_level_2: | ||||
|                     event_name = room_name + " - " + panel_name + " (Counted)" | ||||
|                     self.add_location(room_name, PlayerLocation(event_name, None, | ||||
|                                                                 [RoomAndPanel(room_name, panel_name)])) | ||||
|                     self.EVENT_LOC_TO_ITEM[event_name] = "Counting Panel Solved" | ||||
|  | ||||
|         # Handle the victory condition. Victory conditions other than the chosen one become regular checks, so we need | ||||
|         # to prevent the actual victory condition from becoming a check. | ||||
|         self.MASTERY_LOCATION = "Orange Tower Seventh Floor - THE MASTER" | ||||
|         self.LEVEL_2_LOCATION = "N/A" | ||||
|  | ||||
|         if victory_condition == VictoryCondition.option_the_end: | ||||
|             self.VICTORY_CONDITION = "Orange Tower Seventh Floor - THE END" | ||||
|             self.add_location("Orange Tower Seventh Floor", PlayerLocation("The End (Solved)")) | ||||
|             self.EVENT_LOC_TO_ITEM["The End (Solved)"] = "Victory" | ||||
|         elif victory_condition == VictoryCondition.option_the_master: | ||||
|             self.VICTORY_CONDITION = "Orange Tower Seventh Floor - THE MASTER" | ||||
|             self.MASTERY_LOCATION = "Orange Tower Seventh Floor - Mastery Achievements" | ||||
|  | ||||
|             self.add_location("Orange Tower Seventh Floor", PlayerLocation(self.MASTERY_LOCATION, None, [])) | ||||
|             self.EVENT_LOC_TO_ITEM[self.MASTERY_LOCATION] = "Victory" | ||||
|         elif victory_condition == VictoryCondition.option_level_2: | ||||
|             self.VICTORY_CONDITION = "Second Room - LEVEL 2" | ||||
|             self.LEVEL_2_LOCATION = "Second Room - Unlock Level 2" | ||||
|  | ||||
|             self.add_location("Second Room", PlayerLocation(self.LEVEL_2_LOCATION, None, | ||||
|                                                             [RoomAndPanel("Second Room", "LEVEL 2")])) | ||||
|             self.EVENT_LOC_TO_ITEM[self.LEVEL_2_LOCATION] = "Victory" | ||||
|  | ||||
|         # Instantiate all real locations. | ||||
|         location_classification = LocationClassification.normal | ||||
|         if location_checks == LocationChecks.option_reduced: | ||||
|             location_classification = LocationClassification.reduced | ||||
|         elif location_checks == LocationChecks.option_insanity: | ||||
|             location_classification = LocationClassification.insanity | ||||
|  | ||||
|         for location_name, location_data in ALL_LOCATION_TABLE.items(): | ||||
|             if location_name != self.VICTORY_CONDITION: | ||||
|                 if location_classification not in location_data.classification: | ||||
|                     continue | ||||
|  | ||||
|                 self.add_location(location_data.room, PlayerLocation(location_name, location_data.code, | ||||
|                                                                      location_data.panels)) | ||||
|                 self.REAL_LOCATIONS.append(location_name) | ||||
|  | ||||
|         # Instantiate all real items. | ||||
|         for name, item in ALL_ITEM_TABLE.items(): | ||||
|             if item.should_include(world): | ||||
|                 self.REAL_ITEMS.append(name) | ||||
|  | ||||
|         # Create the paintings mapping, if painting shuffle is on. | ||||
|         if painting_shuffle: | ||||
|             # Shuffle paintings until we get something workable. | ||||
|             workable_paintings = False | ||||
|             for i in range(0, 20): | ||||
|                 workable_paintings = self.randomize_paintings(world) | ||||
|                 if workable_paintings: | ||||
|                     break | ||||
|  | ||||
|             if not workable_paintings: | ||||
|                 raise Exception("This Lingo world was unable to generate a workable painting mapping after 20 " | ||||
|                                 "iterations. This is very unlikely to happen on its own, and probably indicates some " | ||||
|                                 "kind of logic error.") | ||||
|  | ||||
|         if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \ | ||||
|                 and not early_color_hallways and LingoTestOptions.disable_forced_good_item is False: | ||||
|             # If shuffle doors is on, force a useful item onto the HI panel. This may not necessarily get you out of BK, | ||||
|             # but the goal is to allow you to reach at least one more check. The non-painting ones are hardcoded right | ||||
|             # now. We only allow the entrance to the Pilgrim Room if color shuffle is off, because otherwise there are | ||||
|             # no extra checks in there. We only include the entrance to the Rhyme Room when color shuffle is off and | ||||
|             # door shuffle is on simple, because otherwise there are no extra checks in there. | ||||
|             good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] | ||||
|  | ||||
|             if not color_shuffle: | ||||
|                 good_item_options.append("Pilgrim Room - Sun Painting") | ||||
|  | ||||
|             if door_shuffle == ShuffleDoors.option_simple: | ||||
|                 good_item_options += ["Welcome Back Doors"] | ||||
|  | ||||
|                 if not color_shuffle: | ||||
|                     good_item_options.append("Rhyme Room Doors") | ||||
|             else: | ||||
|                 good_item_options += ["Welcome Back Area - Shortcut to Starting Room"] | ||||
|  | ||||
|             for painting_obj in PAINTINGS_BY_ROOM["Starting Room"]: | ||||
|                 if not painting_obj.enter_only or painting_obj.required_door is None: | ||||
|                     continue | ||||
|  | ||||
|                 # If painting shuffle is on, we only want to consider paintings that actually go somewhere. | ||||
|                 if painting_shuffle and painting_obj.id not in self.PAINTING_MAPPING.keys(): | ||||
|                     continue | ||||
|  | ||||
|                 pdoor = DOORS_BY_ROOM[painting_obj.required_door.room][painting_obj.required_door.door] | ||||
|                 good_item_options.append(pdoor.item_name) | ||||
|  | ||||
|             # Copied from The Witness -- remove any plandoed items from the possible good items set. | ||||
|             for v in world.multiworld.plando_items[world.player]: | ||||
|                 if v.get("from_pool", True): | ||||
|                     for item_key in {"item", "items"}: | ||||
|                         if item_key in v: | ||||
|                             if type(v[item_key]) is str: | ||||
|                                 if v[item_key] in good_item_options: | ||||
|                                     good_item_options.remove(v[item_key]) | ||||
|                             elif type(v[item_key]) is dict: | ||||
|                                 for item, weight in v[item_key].items(): | ||||
|                                     if weight and item in good_item_options: | ||||
|                                         good_item_options.remove(item) | ||||
|                             else: | ||||
|                                 # Other type of iterable | ||||
|                                 for item in v[item_key]: | ||||
|                                     if item in good_item_options: | ||||
|                                         good_item_options.remove(item) | ||||
|  | ||||
|             if len(good_item_options) > 0: | ||||
|                 self.FORCED_GOOD_ITEM = world.random.choice(good_item_options) | ||||
|                 self.REAL_ITEMS.remove(self.FORCED_GOOD_ITEM) | ||||
|                 self.REAL_LOCATIONS.remove("Second Room - Good Luck") | ||||
|  | ||||
|     def randomize_paintings(self, world: "LingoWorld") -> bool: | ||||
|         self.PAINTING_MAPPING.clear() | ||||
|  | ||||
|         door_shuffle = world.options.shuffle_doors | ||||
|  | ||||
|         # Determine the set of exit paintings. All required-exit paintings are included, as are all | ||||
|         # required-when-no-doors paintings if door shuffle is off. We then fill the set with random other paintings. | ||||
|         chosen_exits = [] | ||||
|         if door_shuffle == ShuffleDoors.option_none: | ||||
|             chosen_exits = [painting_id for painting_id, painting in PAINTINGS.items() | ||||
|                             if painting.required_when_no_doors] | ||||
|         chosen_exits += [painting_id for painting_id, painting in PAINTINGS.items() | ||||
|                          if painting.exit_only and painting.required] | ||||
|         exitable = [painting_id for painting_id, painting in PAINTINGS.items() | ||||
|                     if not painting.enter_only and not painting.disable and not painting.required] | ||||
|         chosen_exits += world.random.sample(exitable, PAINTING_EXITS - len(chosen_exits)) | ||||
|  | ||||
|         # Determine the set of entrance paintings. | ||||
|         enterable = [painting_id for painting_id, painting in PAINTINGS.items() | ||||
|                      if not painting.exit_only and not painting.disable and painting_id not in chosen_exits] | ||||
|         chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES) | ||||
|  | ||||
|         # Create a mapping from entrances to exits. | ||||
|         for warp_exit in chosen_exits: | ||||
|             warp_enter = world.random.choice(chosen_entrances) | ||||
|  | ||||
|             # Check whether this is a warp from a required painting room to another (or the same) required painting | ||||
|             # room. This could cause a cycle that would make certain regions inaccessible. | ||||
|             warp_exit_room = PAINTINGS[warp_exit].room | ||||
|             warp_enter_room = PAINTINGS[warp_enter].room | ||||
|  | ||||
|             required_painting_rooms = REQUIRED_PAINTING_ROOMS | ||||
|             if door_shuffle == ShuffleDoors.option_none: | ||||
|                 required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS | ||||
|  | ||||
|             if warp_exit_room in required_painting_rooms and warp_enter_room in required_painting_rooms: | ||||
|                 # This shuffling is non-workable. Start over. | ||||
|                 return False | ||||
|  | ||||
|             chosen_entrances.remove(warp_enter) | ||||
|             self.PAINTING_MAPPING[warp_enter] = warp_exit | ||||
|  | ||||
|         for warp_enter in chosen_entrances: | ||||
|             warp_exit = world.random.choice(chosen_exits) | ||||
|             self.PAINTING_MAPPING[warp_enter] = warp_exit | ||||
|  | ||||
|         # The Eye Wall painting is unique in that it is both double-sided and also enter only (because it moves). | ||||
|         # There is only one eligible double-sided exit painting, which is the vanilla exit for this warp. If the | ||||
|         # exit painting is an entrance in the shuffle, we will disable the Eye Wall painting. Otherwise, Eye Wall | ||||
|         # is forced to point to the vanilla exit. | ||||
|         if "eye_painting_2" not in self.PAINTING_MAPPING.keys(): | ||||
|             self.PAINTING_MAPPING["eye_painting"] = "eye_painting_2" | ||||
|  | ||||
|         # Just for sanity's sake, ensure that all required painting rooms are accessed. | ||||
|         for painting_id, painting in PAINTINGS.items(): | ||||
|             if painting_id not in self.PAINTING_MAPPING.values() \ | ||||
|                     and (painting.required or (painting.required_when_no_doors and door_shuffle == 0)): | ||||
|                 return False | ||||
|  | ||||
|         return True | ||||
							
								
								
									
										84
									
								
								worlds/lingo/regions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								worlds/lingo/regions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| from typing import Dict, TYPE_CHECKING | ||||
|  | ||||
| from BaseClasses import ItemClassification, Region | ||||
| from .items import LingoItem | ||||
| from .locations import LingoLocation | ||||
| from .player_logic import LingoPlayerLogic | ||||
| from .rules import lingo_can_use_entrance, lingo_can_use_pilgrimage, make_location_lambda | ||||
| from .static_logic import ALL_ROOMS, PAINTINGS, Room | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from . import LingoWorld | ||||
|  | ||||
|  | ||||
| def create_region(room: Room, world: "LingoWorld", player_logic: LingoPlayerLogic) -> Region: | ||||
|     new_region = Region(room.name, world.player, world.multiworld) | ||||
|     for location in player_logic.LOCATIONS_BY_ROOM.get(room.name, {}): | ||||
|         new_location = LingoLocation(world.player, location.name, location.code, new_region) | ||||
|         new_location.access_rule = make_location_lambda(location, room.name, world, player_logic) | ||||
|         new_region.locations.append(new_location) | ||||
|         if location.name in player_logic.EVENT_LOC_TO_ITEM: | ||||
|             event_name = player_logic.EVENT_LOC_TO_ITEM[location.name] | ||||
|             event_item = LingoItem(event_name, ItemClassification.progression, None, world.player) | ||||
|             new_location.place_locked_item(event_item) | ||||
|  | ||||
|     return new_region | ||||
|  | ||||
|  | ||||
| def handle_pilgrim_room(regions: Dict[str, Region], world: "LingoWorld", player_logic: LingoPlayerLogic) -> None: | ||||
|     target_region = regions["Pilgrim Antechamber"] | ||||
|     source_region = regions["Outside The Agreeable"] | ||||
|     source_region.connect( | ||||
|         target_region, | ||||
|         "Pilgrimage", | ||||
|         lambda state: lingo_can_use_pilgrimage(state, world.player, player_logic)) | ||||
|  | ||||
|  | ||||
| def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str, world: "LingoWorld", | ||||
|                      player_logic: LingoPlayerLogic) -> None: | ||||
|     source_painting = PAINTINGS[warp_enter] | ||||
|     target_painting = PAINTINGS[warp_exit] | ||||
|  | ||||
|     target_region = regions[target_painting.room] | ||||
|     source_region = regions[source_painting.room] | ||||
|     source_region.connect( | ||||
|         target_region, | ||||
|         f"{source_painting.room} to {target_painting.room} (Painting)", | ||||
|         lambda state: lingo_can_use_entrance(state, target_painting.room, source_painting.required_door, world.player, | ||||
|                                              player_logic)) | ||||
|  | ||||
|  | ||||
| def create_regions(world: "LingoWorld", player_logic: LingoPlayerLogic) -> None: | ||||
|     regions = { | ||||
|         "Menu": Region("Menu", world.player, world.multiworld) | ||||
|     } | ||||
|  | ||||
|     painting_shuffle = world.options.shuffle_paintings | ||||
|     early_color_hallways = world.options.early_color_hallways | ||||
|  | ||||
|     # Instantiate all rooms as regions with their locations first. | ||||
|     for room in ALL_ROOMS: | ||||
|         regions[room.name] = create_region(room, world, player_logic) | ||||
|  | ||||
|     # Connect all created regions now that they exist. | ||||
|     for room in ALL_ROOMS: | ||||
|         for entrance in room.entrances: | ||||
|             # Don't use the vanilla painting connections if we are shuffling paintings. | ||||
|             if entrance.painting and painting_shuffle: | ||||
|                 continue | ||||
|  | ||||
|             regions[entrance.room].connect( | ||||
|                 regions[room.name], | ||||
|                 f"{entrance.room} to {room.name}", | ||||
|                 lambda state, r=room, e=entrance: lingo_can_use_entrance(state, r.name, e.door, world.player, player_logic)) | ||||
|  | ||||
|     handle_pilgrim_room(regions, world, player_logic) | ||||
|  | ||||
|     if early_color_hallways: | ||||
|         regions["Starting Room"].connect(regions["Outside The Undeterred"], "Early Color Hallways") | ||||
|  | ||||
|     if painting_shuffle: | ||||
|         for warp_enter, warp_exit in player_logic.PAINTING_MAPPING.items(): | ||||
|             connect_painting(regions, warp_enter, warp_exit, world, player_logic) | ||||
|  | ||||
|     world.multiworld.regions += regions.values() | ||||
							
								
								
									
										104
									
								
								worlds/lingo/rules.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								worlds/lingo/rules.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from BaseClasses import CollectionState | ||||
| from .options import VictoryCondition | ||||
| from .player_logic import LingoPlayerLogic, PlayerLocation | ||||
| from .static_logic import PANELS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, RoomAndDoor | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from . import LingoWorld | ||||
|  | ||||
|  | ||||
| def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, player: int, | ||||
|                            player_logic: LingoPlayerLogic): | ||||
|     if door is None: | ||||
|         return True | ||||
|  | ||||
|     return _lingo_can_open_door(state, room, room if door.room is None else door.room, door.door, player, player_logic) | ||||
|  | ||||
|  | ||||
| def lingo_can_use_pilgrimage(state: CollectionState, player: int, player_logic: LingoPlayerLogic): | ||||
|     fake_pilgrimage = [ | ||||
|         ["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"], | ||||
|         ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], | ||||
|         ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], | ||||
|         ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], | ||||
|         ["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], | ||||
|         ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"], | ||||
|         ["Outside The Agreeable", "Tenacious Entrance"] | ||||
|     ] | ||||
|     for entrance in fake_pilgrimage: | ||||
|         if not state.has(player_logic.ITEM_BY_DOOR[entrance[0]][entrance[1]], player): | ||||
|             return False | ||||
|  | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def lingo_can_use_location(state: CollectionState, location: PlayerLocation, room_name: str, world: "LingoWorld", | ||||
|                            player_logic: LingoPlayerLogic): | ||||
|     for panel in location.panels: | ||||
|         panel_room = room_name if panel.room is None else panel.room | ||||
|         if not _lingo_can_solve_panel(state, room_name, panel_room, panel.panel, world, player_logic): | ||||
|             return False | ||||
|  | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def lingo_can_use_mastery_location(state: CollectionState, world: "LingoWorld"): | ||||
|     return state.has("Mastery Achievement", world.player, world.options.mastery_achievements.value) | ||||
|  | ||||
|  | ||||
| def _lingo_can_open_door(state: CollectionState, start_room: str, room: str, door: str, player: int, | ||||
|                          player_logic: LingoPlayerLogic): | ||||
|     """ | ||||
|     Determines whether a door can be opened | ||||
|     """ | ||||
|     item_name = player_logic.ITEM_BY_DOOR[room][door] | ||||
|     if item_name in PROGRESSIVE_ITEMS: | ||||
|         progression = PROGRESSION_BY_ROOM[room][door] | ||||
|         return state.has(item_name, player, progression.index) | ||||
|  | ||||
|     return state.has(item_name, player) | ||||
|  | ||||
|  | ||||
| def _lingo_can_solve_panel(state: CollectionState, start_room: str, room: str, panel: str, world: "LingoWorld", | ||||
|                            player_logic: LingoPlayerLogic): | ||||
|     """ | ||||
|     Determines whether a panel can be solved | ||||
|     """ | ||||
|     if start_room != room and not state.has(f"{room} (Reached)", world.player): | ||||
|         return False | ||||
|  | ||||
|     if room == "Second Room" and panel == "ANOTHER TRY" \ | ||||
|             and world.options.victory_condition == VictoryCondition.option_level_2 \ | ||||
|             and not state.has("Counting Panel Solved", world.player, world.options.level_2_requirement.value - 1): | ||||
|         return False | ||||
|  | ||||
|     panel_object = PANELS_BY_ROOM[room][panel] | ||||
|     for req_room in panel_object.required_rooms: | ||||
|         if not state.has(f"{req_room} (Reached)", world.player): | ||||
|             return False | ||||
|  | ||||
|     for req_door in panel_object.required_doors: | ||||
|         if not _lingo_can_open_door(state, start_room, room if req_door.room is None else req_door.room, | ||||
|                                     req_door.door, world.player, player_logic): | ||||
|             return False | ||||
|  | ||||
|     for req_panel in panel_object.required_panels: | ||||
|         if not _lingo_can_solve_panel(state, start_room, room if req_panel.room is None else req_panel.room, | ||||
|                                       req_panel.panel, world, player_logic): | ||||
|             return False | ||||
|  | ||||
|     if len(panel_object.colors) > 0 and world.options.shuffle_colors: | ||||
|         for color in panel_object.colors: | ||||
|             if not state.has(color.capitalize(), world.player): | ||||
|                 return False | ||||
|  | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def make_location_lambda(location: PlayerLocation, room_name: str, world: "LingoWorld", player_logic: LingoPlayerLogic): | ||||
|     if location.name == player_logic.MASTERY_LOCATION: | ||||
|         return lambda state: lingo_can_use_mastery_location(state, world) | ||||
|  | ||||
|     return lambda state: lingo_can_use_location(state, location, room_name, world, player_logic) | ||||
							
								
								
									
										544
									
								
								worlds/lingo/static_logic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								worlds/lingo/static_logic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,544 @@ | ||||
| from typing import Dict, List, NamedTuple, Optional, Set | ||||
|  | ||||
| import yaml | ||||
|  | ||||
|  | ||||
| class RoomAndDoor(NamedTuple): | ||||
|     room: Optional[str] | ||||
|     door: str | ||||
|  | ||||
|  | ||||
| class RoomAndPanel(NamedTuple): | ||||
|     room: Optional[str] | ||||
|     panel: str | ||||
|  | ||||
|  | ||||
| class RoomEntrance(NamedTuple): | ||||
|     room: str  # source room | ||||
|     door: Optional[RoomAndDoor] | ||||
|     painting: bool | ||||
|  | ||||
|  | ||||
| class Room(NamedTuple): | ||||
|     name: str | ||||
|     entrances: List[RoomEntrance] | ||||
|  | ||||
|  | ||||
| class Door(NamedTuple): | ||||
|     name: str | ||||
|     item_name: str | ||||
|     location_name: Optional[str] | ||||
|     panels: Optional[List[RoomAndPanel]] | ||||
|     skip_location: bool | ||||
|     skip_item: bool | ||||
|     door_ids: List[str] | ||||
|     painting_ids: List[str] | ||||
|     event: bool | ||||
|     group: Optional[str] | ||||
|     include_reduce: bool | ||||
|     junk_item: bool | ||||
|  | ||||
|  | ||||
| class Panel(NamedTuple): | ||||
|     required_rooms: List[str] | ||||
|     required_doors: List[RoomAndDoor] | ||||
|     required_panels: List[RoomAndPanel] | ||||
|     colors: List[str] | ||||
|     check: bool | ||||
|     event: bool | ||||
|     internal_ids: List[str] | ||||
|     exclude_reduce: bool | ||||
|     achievement: bool | ||||
|     non_counting: bool | ||||
|  | ||||
|  | ||||
| class Painting(NamedTuple): | ||||
|     id: str | ||||
|     room: str | ||||
|     enter_only: bool | ||||
|     exit_only: bool | ||||
|     orientation: str | ||||
|     required: bool | ||||
|     required_when_no_doors: bool | ||||
|     required_door: Optional[RoomAndDoor] | ||||
|     disable: bool | ||||
|     move: bool | ||||
|  | ||||
|  | ||||
| class Progression(NamedTuple): | ||||
|     item_name: str | ||||
|     index: int | ||||
|  | ||||
|  | ||||
| ROOMS: Dict[str, Room] = {} | ||||
| PANELS: Dict[str, Panel] = {} | ||||
| DOORS: Dict[str, Door] = {} | ||||
| PAINTINGS: Dict[str, Painting] = {} | ||||
|  | ||||
| ALL_ROOMS: List[Room] = [] | ||||
| DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {} | ||||
| PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {} | ||||
| PAINTINGS_BY_ROOM: Dict[str, List[Painting]] = {} | ||||
|  | ||||
| PROGRESSIVE_ITEMS: List[str] = [] | ||||
| PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {} | ||||
|  | ||||
| PAINTING_ENTRANCES: int = 0 | ||||
| PAINTING_EXIT_ROOMS: Set[str] = set() | ||||
| PAINTING_EXITS: int = 0 | ||||
| REQUIRED_PAINTING_ROOMS: List[str] = [] | ||||
| REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS: List[str] = [] | ||||
|  | ||||
| SPECIAL_ITEM_IDS: Dict[str, int] = {} | ||||
| PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {} | ||||
| DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} | ||||
| DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} | ||||
| DOOR_GROUP_ITEM_IDS: Dict[str, int] = {} | ||||
| PROGRESSIVE_ITEM_IDS: Dict[str, int] = {} | ||||
|  | ||||
|  | ||||
| def load_static_data(): | ||||
|     global PAINTING_EXITS, SPECIAL_ITEM_IDS, PANEL_LOCATION_IDS, DOOR_LOCATION_IDS, DOOR_ITEM_IDS, \ | ||||
|         DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS | ||||
|  | ||||
|     try: | ||||
|         from importlib.resources import files | ||||
|     except ImportError: | ||||
|         from importlib_resources import files | ||||
|  | ||||
|     # Load in all item and location IDs. These are broken up into groups based on the type of item/location. | ||||
|     with files("worlds.lingo").joinpath("ids.yaml").open() as file: | ||||
|         config = yaml.load(file, Loader=yaml.Loader) | ||||
|  | ||||
|         if "special_items" in config: | ||||
|             for item_name, item_id in config["special_items"].items(): | ||||
|                 SPECIAL_ITEM_IDS[item_name] = item_id | ||||
|  | ||||
|         if "panels" in config: | ||||
|             for room_name in config["panels"].keys(): | ||||
|                 PANEL_LOCATION_IDS[room_name] = {} | ||||
|  | ||||
|                 for panel_name, location_id in config["panels"][room_name].items(): | ||||
|                     PANEL_LOCATION_IDS[room_name][panel_name] = location_id | ||||
|  | ||||
|         if "doors" in config: | ||||
|             for room_name in config["doors"].keys(): | ||||
|                 DOOR_LOCATION_IDS[room_name] = {} | ||||
|                 DOOR_ITEM_IDS[room_name] = {} | ||||
|  | ||||
|                 for door_name, door_data in config["doors"][room_name].items(): | ||||
|                     if "location" in door_data: | ||||
|                         DOOR_LOCATION_IDS[room_name][door_name] = door_data["location"] | ||||
|  | ||||
|                     if "item" in door_data: | ||||
|                         DOOR_ITEM_IDS[room_name][door_name] = door_data["item"] | ||||
|  | ||||
|         if "door_groups" in config: | ||||
|             for item_name, item_id in config["door_groups"].items(): | ||||
|                 DOOR_GROUP_ITEM_IDS[item_name] = item_id | ||||
|  | ||||
|         if "progression" in config: | ||||
|             for item_name, item_id in config["progression"].items(): | ||||
|                 PROGRESSIVE_ITEM_IDS[item_name] = item_id | ||||
|  | ||||
|     # Process the main world file. | ||||
|     with files("worlds.lingo").joinpath("LL1.yaml").open() as file: | ||||
|         config = yaml.load(file, Loader=yaml.Loader) | ||||
|  | ||||
|         for room_name, room_data in config.items(): | ||||
|             process_room(room_name, room_data) | ||||
|  | ||||
|         PAINTING_EXITS = len(PAINTING_EXIT_ROOMS) | ||||
|  | ||||
|  | ||||
| def get_special_item_id(name: str): | ||||
|     if name not in SPECIAL_ITEM_IDS: | ||||
|         raise Exception(f"Item ID for special item {name} not found in ids.yaml.") | ||||
|  | ||||
|     return SPECIAL_ITEM_IDS[name] | ||||
|  | ||||
|  | ||||
| def get_panel_location_id(room: str, name: str): | ||||
|     if room not in PANEL_LOCATION_IDS or name not in PANEL_LOCATION_IDS[room]: | ||||
|         raise Exception(f"Location ID for panel {room} - {name} not found in ids.yaml.") | ||||
|  | ||||
|     return PANEL_LOCATION_IDS[room][name] | ||||
|  | ||||
|  | ||||
| def get_door_location_id(room: str, name: str): | ||||
|     if room not in DOOR_LOCATION_IDS or name not in DOOR_LOCATION_IDS[room]: | ||||
|         raise Exception(f"Location ID for door {room} - {name} not found in ids.yaml.") | ||||
|  | ||||
|     return DOOR_LOCATION_IDS[room][name] | ||||
|  | ||||
|  | ||||
| def get_door_item_id(room: str, name: str): | ||||
|     if room not in DOOR_ITEM_IDS or name not in DOOR_ITEM_IDS[room]: | ||||
|         raise Exception(f"Item ID for door {room} - {name} not found in ids.yaml.") | ||||
|  | ||||
|     return DOOR_ITEM_IDS[room][name] | ||||
|  | ||||
|  | ||||
| def get_door_group_item_id(name: str): | ||||
|     if name not in DOOR_GROUP_ITEM_IDS: | ||||
|         raise Exception(f"Item ID for door group {name} not found in ids.yaml.") | ||||
|  | ||||
|     return DOOR_GROUP_ITEM_IDS[name] | ||||
|  | ||||
|  | ||||
| def get_progressive_item_id(name: str): | ||||
|     if name not in PROGRESSIVE_ITEM_IDS: | ||||
|         raise Exception(f"Item ID for progressive item {name} not found in ids.yaml.") | ||||
|  | ||||
|     return PROGRESSIVE_ITEM_IDS[name] | ||||
|  | ||||
|  | ||||
| def process_entrance(source_room, doors, room_obj): | ||||
|     global PAINTING_ENTRANCES, PAINTING_EXIT_ROOMS | ||||
|  | ||||
|     # If the value of an entrance is just True, that means that the entrance is always accessible. | ||||
|     if doors is True: | ||||
|         room_obj.entrances.append(RoomEntrance(source_room, None, False)) | ||||
|     elif isinstance(doors, dict): | ||||
|         # If the value of an entrance is a dictionary, that means the entrance requires a door to be accessible, is a | ||||
|         # painting-based entrance, or both. | ||||
|         if "painting" in doors and "door" not in doors: | ||||
|             PAINTING_EXIT_ROOMS.add(room_obj.name) | ||||
|             PAINTING_ENTRANCES += 1 | ||||
|  | ||||
|             room_obj.entrances.append(RoomEntrance(source_room, None, True)) | ||||
|         else: | ||||
|             if "painting" in doors and doors["painting"]: | ||||
|                 PAINTING_EXIT_ROOMS.add(room_obj.name) | ||||
|                 PAINTING_ENTRANCES += 1 | ||||
|  | ||||
|             room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor( | ||||
|                 doors["room"] if "room" in doors else None, | ||||
|                 doors["door"] | ||||
|             ), doors["painting"] if "painting" in doors else False)) | ||||
|     else: | ||||
|         # If the value of an entrance is a list, then there are multiple possible doors that can give access to the | ||||
|         # entrance. | ||||
|         for door in doors: | ||||
|             if "painting" in door and door["painting"]: | ||||
|                 PAINTING_EXIT_ROOMS.add(room_obj.name) | ||||
|                 PAINTING_ENTRANCES += 1 | ||||
|  | ||||
|             room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor( | ||||
|                 door["room"] if "room" in door else None, | ||||
|                 door["door"] | ||||
|             ), door["painting"] if "painting" in door else False)) | ||||
|  | ||||
|  | ||||
| def process_panel(room_name, panel_name, panel_data): | ||||
|     global PANELS, PANELS_BY_ROOM | ||||
|  | ||||
|     full_name = f"{room_name} - {panel_name}" | ||||
|  | ||||
|     # required_room can either be a single room or a list of rooms. | ||||
|     if "required_room" in panel_data: | ||||
|         if isinstance(panel_data["required_room"], list): | ||||
|             required_rooms = panel_data["required_room"] | ||||
|         else: | ||||
|             required_rooms = [panel_data["required_room"]] | ||||
|     else: | ||||
|         required_rooms = [] | ||||
|  | ||||
|     # required_door can either be a single door or a list of doors. For convenience, the room key for each door does not | ||||
|     # need to be specified if the door is in this room. | ||||
|     required_doors = list() | ||||
|     if "required_door" in panel_data: | ||||
|         if isinstance(panel_data["required_door"], dict): | ||||
|             door = panel_data["required_door"] | ||||
|             required_doors.append(RoomAndDoor( | ||||
|                 door["room"] if "room" in door else None, | ||||
|                 door["door"] | ||||
|             )) | ||||
|         else: | ||||
|             for door in panel_data["required_door"]: | ||||
|                 required_doors.append(RoomAndDoor( | ||||
|                     door["room"] if "room" in door else None, | ||||
|                     door["door"] | ||||
|                 )) | ||||
|  | ||||
|     # required_panel can either be a single panel or a list of panels. For convenience, the room key for each panel does | ||||
|     # not need to be specified if the panel is in this room. | ||||
|     required_panels = list() | ||||
|     if "required_panel" in panel_data: | ||||
|         if isinstance(panel_data["required_panel"], dict): | ||||
|             other_panel = panel_data["required_panel"] | ||||
|             required_panels.append(RoomAndPanel( | ||||
|                 other_panel["room"] if "room" in other_panel else None, | ||||
|                 other_panel["panel"] | ||||
|             )) | ||||
|         else: | ||||
|             for other_panel in panel_data["required_panel"]: | ||||
|                 required_panels.append(RoomAndPanel( | ||||
|                     other_panel["room"] if "room" in other_panel else None, | ||||
|                     other_panel["panel"] | ||||
|                 )) | ||||
|  | ||||
|     # colors can either be a single color or a list of colors. | ||||
|     if "colors" in panel_data: | ||||
|         if isinstance(panel_data["colors"], list): | ||||
|             colors = panel_data["colors"] | ||||
|         else: | ||||
|             colors = [panel_data["colors"]] | ||||
|     else: | ||||
|         colors = [] | ||||
|  | ||||
|     if "check" in panel_data: | ||||
|         check = panel_data["check"] | ||||
|     else: | ||||
|         check = False | ||||
|  | ||||
|     if "event" in panel_data: | ||||
|         event = panel_data["event"] | ||||
|     else: | ||||
|         event = False | ||||
|  | ||||
|     if "achievement" in panel_data: | ||||
|         achievement = True | ||||
|     else: | ||||
|         achievement = False | ||||
|  | ||||
|     if "exclude_reduce" in panel_data: | ||||
|         exclude_reduce = panel_data["exclude_reduce"] | ||||
|     else: | ||||
|         exclude_reduce = False | ||||
|  | ||||
|     if "non_counting" in panel_data: | ||||
|         non_counting = panel_data["non_counting"] | ||||
|     else: | ||||
|         non_counting = False | ||||
|  | ||||
|     if "id" in panel_data: | ||||
|         if isinstance(panel_data["id"], list): | ||||
|             internal_ids = panel_data["id"] | ||||
|         else: | ||||
|             internal_ids = [panel_data["id"]] | ||||
|     else: | ||||
|         internal_ids = [] | ||||
|  | ||||
|     panel_obj = Panel(required_rooms, required_doors, required_panels, colors, check, event, internal_ids, | ||||
|                       exclude_reduce, achievement, non_counting) | ||||
|     PANELS[full_name] = panel_obj | ||||
|     PANELS_BY_ROOM[room_name][panel_name] = panel_obj | ||||
|  | ||||
|  | ||||
| def process_door(room_name, door_name, door_data): | ||||
|     global DOORS, DOORS_BY_ROOM | ||||
|  | ||||
|     # The item name associated with a door can be explicitly specified in the configuration. If it is not, it is | ||||
|     # generated from the room and door name. | ||||
|     if "item_name" in door_data: | ||||
|         item_name = door_data["item_name"] | ||||
|     else: | ||||
|         item_name = f"{room_name} - {door_name}" | ||||
|  | ||||
|     if "skip_location" in door_data: | ||||
|         skip_location = door_data["skip_location"] | ||||
|     else: | ||||
|         skip_location = False | ||||
|  | ||||
|     if "skip_item" in door_data: | ||||
|         skip_item = door_data["skip_item"] | ||||
|     else: | ||||
|         skip_item = False | ||||
|  | ||||
|     if "event" in door_data: | ||||
|         event = door_data["event"] | ||||
|     else: | ||||
|         event = False | ||||
|  | ||||
|     if "include_reduce" in door_data: | ||||
|         include_reduce = door_data["include_reduce"] | ||||
|     else: | ||||
|         include_reduce = False | ||||
|  | ||||
|     if "junk_item" in door_data: | ||||
|         junk_item = door_data["junk_item"] | ||||
|     else: | ||||
|         junk_item = False | ||||
|  | ||||
|     if "group" in door_data: | ||||
|         group = door_data["group"] | ||||
|     else: | ||||
|         group = None | ||||
|  | ||||
|     # panels is a list of panels. Each panel can either be a simple string (the name of a panel in the current room) or | ||||
|     # a dictionary specifying a panel in a different room. | ||||
|     if "panels" in door_data: | ||||
|         panels = list() | ||||
|         for panel in door_data["panels"]: | ||||
|             if isinstance(panel, dict): | ||||
|                 panels.append(RoomAndPanel(panel["room"], panel["panel"])) | ||||
|             else: | ||||
|                 panels.append(RoomAndPanel(None, panel)) | ||||
|     else: | ||||
|         skip_location = True | ||||
|         panels = None | ||||
|  | ||||
|     # The location name associated with a door can be explicitly specified in the configuration. If it is not, then the | ||||
|     # name is generated using a combination of all of the panels that would ordinarily open the door. This can get quite | ||||
|     # messy if there are a lot of panels, especially if panels from multiple rooms are involved, so in these cases it | ||||
|     # would be better to specify a name. | ||||
|     if "location_name" in door_data: | ||||
|         location_name = door_data["location_name"] | ||||
|     elif skip_location is False: | ||||
|         panel_per_room = dict() | ||||
|         for panel in panels: | ||||
|             panel_room_name = room_name if panel.room is None else panel.room | ||||
|             panel_per_room.setdefault(panel_room_name, []).append(panel.panel) | ||||
|  | ||||
|         room_strs = list() | ||||
|         for door_room_str, door_panels_str in panel_per_room.items(): | ||||
|             room_strs.append(door_room_str + " - " + ", ".join(door_panels_str)) | ||||
|  | ||||
|         location_name = " and ".join(room_strs) | ||||
|     else: | ||||
|         location_name = None | ||||
|  | ||||
|     # The id field can be a single item, or a list of door IDs, in the event that the item for this logical door should | ||||
|     # open more than one actual in-game door. | ||||
|     if "id" in door_data: | ||||
|         if isinstance(door_data["id"], list): | ||||
|             door_ids = door_data["id"] | ||||
|         else: | ||||
|             door_ids = [door_data["id"]] | ||||
|     else: | ||||
|         door_ids = [] | ||||
|  | ||||
|     # The painting_id field can be a single item, or a list of painting IDs, in the event that the item for this logical | ||||
|     # door should move more than one actual in-game painting. | ||||
|     if "painting_id" in door_data: | ||||
|         if isinstance(door_data["painting_id"], list): | ||||
|             painting_ids = door_data["painting_id"] | ||||
|         else: | ||||
|             painting_ids = [door_data["painting_id"]] | ||||
|     else: | ||||
|         painting_ids = [] | ||||
|  | ||||
|     door_obj = Door(door_name, item_name, location_name, panels, skip_location, skip_item, door_ids, | ||||
|                     painting_ids, event, group, include_reduce, junk_item) | ||||
|  | ||||
|     DOORS[door_obj.item_name] = door_obj | ||||
|     DOORS_BY_ROOM[room_name][door_name] = door_obj | ||||
|  | ||||
|  | ||||
| def process_painting(room_name, painting_data): | ||||
|     global PAINTINGS, PAINTINGS_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS | ||||
|  | ||||
|     # Read in information about this painting and store it in an object. | ||||
|     painting_id = painting_data["id"] | ||||
|  | ||||
|     if "orientation" in painting_data: | ||||
|         orientation = painting_data["orientation"] | ||||
|     else: | ||||
|         orientation = "" | ||||
|  | ||||
|     if "disable" in painting_data: | ||||
|         disable_painting = painting_data["disable"] | ||||
|     else: | ||||
|         disable_painting = False | ||||
|  | ||||
|     if "required" in painting_data: | ||||
|         required_painting = painting_data["required"] | ||||
|         if required_painting: | ||||
|             REQUIRED_PAINTING_ROOMS.append(room_name) | ||||
|     else: | ||||
|         required_painting = False | ||||
|  | ||||
|     if "move" in painting_data: | ||||
|         move_painting = painting_data["move"] | ||||
|     else: | ||||
|         move_painting = False | ||||
|  | ||||
|     if "required_when_no_doors" in painting_data: | ||||
|         rwnd = painting_data["required_when_no_doors"] | ||||
|         if rwnd: | ||||
|             REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS.append(room_name) | ||||
|     else: | ||||
|         rwnd = False | ||||
|  | ||||
|     if "exit_only" in painting_data: | ||||
|         exit_only = painting_data["exit_only"] | ||||
|     else: | ||||
|         exit_only = False | ||||
|  | ||||
|     if "enter_only" in painting_data: | ||||
|         enter_only = painting_data["enter_only"] | ||||
|     else: | ||||
|         enter_only = False | ||||
|  | ||||
|     required_door = None | ||||
|     if "required_door" in painting_data: | ||||
|         door = painting_data["required_door"] | ||||
|         required_door = RoomAndDoor( | ||||
|             door["room"] if "room" in door else room_name, | ||||
|             door["door"] | ||||
|         ) | ||||
|  | ||||
|     painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation, | ||||
|                             required_painting, rwnd, required_door, disable_painting, move_painting) | ||||
|     PAINTINGS[painting_id] = painting_obj | ||||
|     PAINTINGS_BY_ROOM[room_name].append(painting_obj) | ||||
|  | ||||
|  | ||||
| def process_progression(room_name, progression_name, progression_doors): | ||||
|     global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM | ||||
|  | ||||
|     # Progressive items are configured as a list of doors. | ||||
|     PROGRESSIVE_ITEMS.append(progression_name) | ||||
|  | ||||
|     progression_index = 1 | ||||
|     for door in progression_doors: | ||||
|         if isinstance(door, Dict): | ||||
|             door_room = door["room"] | ||||
|             door_door = door["door"] | ||||
|         else: | ||||
|             door_room = room_name | ||||
|             door_door = door | ||||
|  | ||||
|         room_progressions = PROGRESSION_BY_ROOM.setdefault(door_room, {}) | ||||
|         room_progressions[door_door] = Progression(progression_name, progression_index) | ||||
|         progression_index += 1 | ||||
|  | ||||
|  | ||||
| def process_room(room_name, room_data): | ||||
|     global ROOMS, ALL_ROOMS | ||||
|  | ||||
|     room_obj = Room(room_name, []) | ||||
|  | ||||
|     if "entrances" in room_data: | ||||
|         for source_room, doors in room_data["entrances"].items(): | ||||
|             process_entrance(source_room, doors, room_obj) | ||||
|  | ||||
|     if "panels" in room_data: | ||||
|         PANELS_BY_ROOM[room_name] = dict() | ||||
|  | ||||
|         for panel_name, panel_data in room_data["panels"].items(): | ||||
|             process_panel(room_name, panel_name, panel_data) | ||||
|  | ||||
|     if "doors" in room_data: | ||||
|         DOORS_BY_ROOM[room_name] = dict() | ||||
|  | ||||
|         for door_name, door_data in room_data["doors"].items(): | ||||
|             process_door(room_name, door_name, door_data) | ||||
|  | ||||
|     if "paintings" in room_data: | ||||
|         PAINTINGS_BY_ROOM[room_name] = [] | ||||
|  | ||||
|         for painting_data in room_data["paintings"]: | ||||
|             process_painting(room_name, painting_data) | ||||
|  | ||||
|     if "progression" in room_data: | ||||
|         for progression_name, progression_doors in room_data["progression"].items(): | ||||
|             process_progression(room_name, progression_name, progression_doors) | ||||
|  | ||||
|     ROOMS[room_name] = room_obj | ||||
|     ALL_ROOMS.append(room_obj) | ||||
|  | ||||
|  | ||||
| # Initialize the static data at module scope. | ||||
| load_static_data() | ||||
							
								
								
									
										89
									
								
								worlds/lingo/test/TestDoors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								worlds/lingo/test/TestDoors.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| from . import LingoTestBase | ||||
|  | ||||
|  | ||||
| class TestRequiredRoomLogic(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex" | ||||
|     } | ||||
|  | ||||
|     def test_pilgrim_first(self) -> None: | ||||
|         self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Pilgrim Antechamber", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|         self.collect_by_name("Pilgrim Room - Sun Painting") | ||||
|         self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Pilgrim Antechamber", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|         self.collect_by_name("Pilgrim Room - Shortcut to The Seeker") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|         self.collect_by_name("Starting Room - Back Right Door") | ||||
|         self.assertTrue(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|     def test_hidden_first(self) -> None: | ||||
|         self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|         self.collect_by_name("Starting Room - Back Right Door") | ||||
|         self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|         self.collect_by_name("Pilgrim Room - Shortcut to The Seeker") | ||||
|         self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|         self.collect_by_name("Pilgrim Room - Sun Painting") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) | ||||
|         self.assertTrue(self.can_reach_location("The Seeker - Achievement")) | ||||
|  | ||||
|  | ||||
| class TestRequiredDoorLogic(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex" | ||||
|     } | ||||
|  | ||||
|     def test_through_rhyme(self) -> None: | ||||
|         self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|         self.collect_by_name("Starting Room - Rhyme Room Entrance") | ||||
|         self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|         self.collect_by_name("Rhyme Room (Looped Square) - Door to Circle") | ||||
|         self.assertTrue(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|     def test_through_hidden(self) -> None: | ||||
|         self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|         self.collect_by_name("Starting Room - Rhyme Room Entrance") | ||||
|         self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|         self.collect_by_name("Starting Room - Back Right Door") | ||||
|         self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|         self.collect_by_name("Hidden Room - Rhyme Room Entrance") | ||||
|         self.assertTrue(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) | ||||
|  | ||||
|  | ||||
| class TestSimpleDoors(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "simple" | ||||
|     } | ||||
|  | ||||
|     def test_requirement(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Rhyme Room Doors") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|  | ||||
							
								
								
									
										39
									
								
								worlds/lingo/test/TestMastery.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								worlds/lingo/test/TestMastery.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| from . import LingoTestBase | ||||
|  | ||||
|  | ||||
| class TestMasteryWhenVictoryIsTheEnd(LingoTestBase): | ||||
|     options = { | ||||
|         "mastery_achievements": "22", | ||||
|         "victory_condition": "the_end", | ||||
|         "shuffle_colors": "true" | ||||
|     } | ||||
|  | ||||
|     def test_requirement(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name(["Red", "Blue", "Black", "Purple", "Orange"]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.can_reach_location("The End (Solved)")) | ||||
|         self.assertFalse(self.can_reach_location("Orange Tower Seventh Floor - THE MASTER")) | ||||
|  | ||||
|         self.collect_by_name(["Green", "Brown", "Yellow"]) | ||||
|         self.assertTrue(self.can_reach_location("Orange Tower Seventh Floor - THE MASTER")) | ||||
|  | ||||
|  | ||||
| class TestMasteryWhenVictoryIsTheMaster(LingoTestBase): | ||||
|     options = { | ||||
|         "mastery_achievements": "24", | ||||
|         "victory_condition": "the_master", | ||||
|         "shuffle_colors": "true" | ||||
|     } | ||||
|  | ||||
|     def test_requirement(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name(["Red", "Blue", "Black", "Purple", "Orange"]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.can_reach_location("Orange Tower Seventh Floor - THE END")) | ||||
|         self.assertFalse(self.can_reach_location("Orange Tower Seventh Floor - Mastery Achievements")) | ||||
|  | ||||
|         self.collect_by_name(["Green", "Gray", "Brown", "Yellow"]) | ||||
|         self.assertTrue(self.can_reach_location("Orange Tower Seventh Floor - Mastery Achievements")) | ||||
							
								
								
									
										31
									
								
								worlds/lingo/test/TestOptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								worlds/lingo/test/TestOptions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| from . import LingoTestBase | ||||
|  | ||||
|  | ||||
| class TestMultiShuffleOptions(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex", | ||||
|         "progressive_orange_tower": "true", | ||||
|         "shuffle_colors": "true", | ||||
|         "shuffle_paintings": "true", | ||||
|         "early_color_hallways": "true" | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestPanelsanity(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex", | ||||
|         "progressive_orange_tower": "true", | ||||
|         "location_checks": "insanity", | ||||
|         "shuffle_colors": "true" | ||||
|     } | ||||
|  | ||||
|  | ||||
| class TestAllPanelHunt(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex", | ||||
|         "progressive_orange_tower": "true", | ||||
|         "shuffle_colors": "true", | ||||
|         "victory_condition": "level_2", | ||||
|         "level_2_requirement": "800", | ||||
|         "early_color_hallways": "true" | ||||
|     } | ||||
							
								
								
									
										175
									
								
								worlds/lingo/test/TestOrangeTower.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								worlds/lingo/test/TestOrangeTower.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| from . import LingoTestBase | ||||
|  | ||||
|  | ||||
| class TestProgressiveOrangeTower(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex", | ||||
|         "progressive_orange_tower": "true" | ||||
|     } | ||||
|  | ||||
|     def test_from_welcome_back(self) -> None: | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Welcome Back Area - Shortcut to Starting Room") | ||||
|         self.collect_by_name("Orange Tower Fifth Floor - Welcome Back") | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         progressive_tower = self.get_items_by_name("Progressive Orange Tower") | ||||
|  | ||||
|         self.collect(progressive_tower[0]) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[1]) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[2]) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[3]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[4]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[5]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|     def test_from_hub_room(self) -> None: | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Second Room - Exit Door") | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Orange Tower First Floor - Shortcut to Hub Room") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         progressive_tower = self.get_items_by_name("Progressive Orange Tower") | ||||
|  | ||||
|         self.collect(progressive_tower[0]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.remove(self.get_item_by_name("Orange Tower First Floor - Shortcut to Hub Room")) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[1]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[2]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[3]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[4]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_tower[5]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fourth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Sixth Floor", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) | ||||
							
								
								
									
										191
									
								
								worlds/lingo/test/TestProgressive.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								worlds/lingo/test/TestProgressive.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| from . import LingoTestBase | ||||
|  | ||||
|  | ||||
| class TestComplexProgressiveHallwayRoom(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex" | ||||
|     } | ||||
|  | ||||
|     def test_item(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name(["Second Room - Exit Door", "The Tenacious - Shortcut to Hub Room", | ||||
|                               "Outside The Agreeable - Tenacious Entrance"]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         progressive_hallway_room = self.get_items_by_name("Progressive Hallway Room") | ||||
|  | ||||
|         self.collect(progressive_hallway_room[0]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_hallway_room[1]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_hallway_room[2]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_hallway_room[3]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|  | ||||
| class TestSimpleHallwayRoom(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "simple" | ||||
|     } | ||||
|  | ||||
|     def test_item(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name(["Second Room - Exit Door", "Entrances to The Tenacious"]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Hallway Room Doors") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Hallway Room (4)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Elements Area", "Region", self.player)) | ||||
|  | ||||
|  | ||||
| class TestProgressiveArtGallery(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "complex" | ||||
|     } | ||||
|  | ||||
|     def test_item(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name(["Second Room - Exit Door", "Crossroads - Tower Entrance", | ||||
|                               "Orange Tower Fourth Floor - Hot Crusts Door"]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         progressive_gallery_room = self.get_items_by_name("Progressive Art Gallery") | ||||
|  | ||||
|         self.collect(progressive_gallery_room[0]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_gallery_room[1]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_gallery_room[2]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_gallery_room[3]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect(progressive_gallery_room[4]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|  | ||||
| class TestNoDoorsArtGallery(LingoTestBase): | ||||
|     options = { | ||||
|         "shuffle_doors": "none", | ||||
|         "shuffle_colors": "true" | ||||
|     } | ||||
|  | ||||
|     def test_item(self): | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Yellow") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Brown") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name("Blue") | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
|  | ||||
|         self.collect_by_name(["Orange", "Gray"]) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) | ||||
|         self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) | ||||
|         self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) | ||||
							
								
								
									
										13
									
								
								worlds/lingo/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								worlds/lingo/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| from typing import ClassVar | ||||
|  | ||||
| from test.bases import WorldTestBase | ||||
| from .. import LingoTestOptions | ||||
|  | ||||
|  | ||||
| class LingoTestBase(WorldTestBase): | ||||
|     game = "Lingo" | ||||
|     player: ClassVar[int] = 1 | ||||
|  | ||||
|     def world_setup(self, *args, **kwargs): | ||||
|         LingoTestOptions.disable_forced_good_item = True | ||||
|         super().world_setup(*args, **kwargs) | ||||
							
								
								
									
										2
									
								
								worlds/lingo/testing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								worlds/lingo/testing.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| class LingoTestOptions: | ||||
|     disable_forced_good_item: bool = False | ||||
							
								
								
									
										178
									
								
								worlds/lingo/utils/assign_ids.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								worlds/lingo/utils/assign_ids.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| # This utility goes through the provided Lingo config and assigns item and | ||||
| # location IDs to entities that require them (such as doors and panels). These | ||||
| # IDs are output in a separate yaml file. If the output file already exists, | ||||
| # then it will be updated with any newly assigned IDs rather than overwritten. | ||||
| # In this event, all new IDs will be greater than any already existing IDs, | ||||
| # even if there are gaps in the ID space; this is to prevent collision when IDs | ||||
| # are retired. | ||||
| # | ||||
| # This utility should be run whenever logically new items or locations are | ||||
| # required. If an item or location is created that is logically equivalent to | ||||
| # one that used to exist, this utility should not be used, and instead the ID | ||||
| # file should be manually edited so that the old ID can be reused. | ||||
|  | ||||
| require 'set' | ||||
| require 'yaml' | ||||
|  | ||||
| configpath = ARGV[0] | ||||
| outputpath = ARGV[1] | ||||
|  | ||||
| next_item_id = 444400 | ||||
| next_location_id = 444400 | ||||
|  | ||||
| location_id_by_name = {} | ||||
|  | ||||
| old_generated = YAML.load_file(outputpath) | ||||
| File.write(outputpath + ".old", old_generated.to_yaml) | ||||
|  | ||||
| if old_generated.include? "special_items" then | ||||
|   old_generated["special_items"].each do |name, id| | ||||
|     if id >= next_item_id then | ||||
|       next_item_id = id + 1 | ||||
|     end | ||||
|   end | ||||
| end | ||||
| if old_generated.include? "special_locations" then | ||||
|   old_generated["special_locations"].each do |name, id| | ||||
|     if id >= next_location_id then | ||||
|       next_location_id = id + 1 | ||||
|     end | ||||
|   end | ||||
| end | ||||
| if old_generated.include? "panels" then | ||||
|   old_generated["panels"].each do |room, panels| | ||||
|     panels.each do |name, id| | ||||
|       if id >= next_location_id then | ||||
|         next_location_id = id + 1 | ||||
|       end | ||||
|       location_name = "#{room} - #{name}" | ||||
|       location_id_by_name[location_name] = id | ||||
|     end | ||||
|   end | ||||
| end | ||||
| if old_generated.include? "doors" then | ||||
|   old_generated["doors"].each do |room, doors| | ||||
|     doors.each do |name, ids| | ||||
|       if ids.include? "location" then | ||||
|         if ids["location"] >= next_location_id then | ||||
|           next_location_id = ids["location"] + 1 | ||||
|         end | ||||
|       end | ||||
|       if ids.include? "item" then | ||||
|         if ids["item"] >= next_item_id then | ||||
|           next_item_id = ids["item"] + 1 | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| if old_generated.include? "door_groups" then | ||||
|   old_generated["door_groups"].each do |name, id| | ||||
|     if id >= next_item_id then | ||||
|       next_item_id = id + 1 | ||||
|     end | ||||
|   end | ||||
| end | ||||
| if old_generated.include? "progression" then | ||||
|   old_generated["progression"].each do |name, id| | ||||
|     if id >= next_item_id then | ||||
|       next_item_id = id + 1 | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| door_groups = Set[] | ||||
|  | ||||
| config = YAML.load_file(configpath) | ||||
| config.each do |room_name, room_data| | ||||
|   if room_data.include? "panels" | ||||
|     room_data["panels"].each do |panel_name, panel| | ||||
|       unless old_generated.include? "panels" and old_generated["panels"].include? room_name and old_generated["panels"][room_name].include? panel_name then | ||||
|         old_generated["panels"] ||= {} | ||||
|         old_generated["panels"][room_name] ||= {} | ||||
|         old_generated["panels"][room_name][panel_name] = next_location_id | ||||
|  | ||||
|         location_name = "#{room_name} - #{panel_name}" | ||||
|         location_id_by_name[location_name] = next_location_id | ||||
|  | ||||
|         next_location_id += 1 | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| config.each do |room_name, room_data| | ||||
|   if room_data.include? "doors" | ||||
|     room_data["doors"].each do |door_name, door| | ||||
|       if door.include? "event" and door["event"] then | ||||
|         next | ||||
|       end | ||||
|  | ||||
|       unless door.include? "skip_item" and door["skip_item"] then | ||||
|         unless old_generated.include? "doors" and old_generated["doors"].include? room_name and old_generated["doors"][room_name].include? door_name and old_generated["doors"][room_name][door_name].include? "item" then | ||||
|           old_generated["doors"] ||= {} | ||||
|           old_generated["doors"][room_name] ||= {} | ||||
|           old_generated["doors"][room_name][door_name] ||= {} | ||||
|           old_generated["doors"][room_name][door_name]["item"] = next_item_id | ||||
|    | ||||
|           next_item_id += 1 | ||||
|         end | ||||
|  | ||||
|         if door.include? "group" and not door_groups.include? door["group"] then | ||||
|           door_groups.add(door["group"]) | ||||
|  | ||||
|           unless old_generated.include? "door_groups" and old_generated["door_groups"].include? door["group"] then | ||||
|             old_generated["door_groups"] ||= {} | ||||
|             old_generated["door_groups"][door["group"]] = next_item_id | ||||
|      | ||||
|             next_item_id += 1 | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|  | ||||
|       unless door.include? "skip_location" and door["skip_location"] then | ||||
|         location_name = "" | ||||
|         if door.include? "location_name" then | ||||
|           location_name = door["location_name"] | ||||
|         elsif door.include? "panels" then | ||||
|           location_name = door["panels"].map do |panel| | ||||
|             if panel.kind_of? Hash then | ||||
|               panel | ||||
|             else | ||||
|               {"room" => room_name, "panel" => panel} | ||||
|             end | ||||
|           end.sort_by {|panel| panel["room"]}.chunk {|panel| panel["room"]}.map do |room_panels| | ||||
|             room_panels[0] + " - " + room_panels[1].map{|panel| panel["panel"]}.join(", ") | ||||
|           end.join(" and ") | ||||
|         end | ||||
|  | ||||
|         if location_id_by_name.has_key? location_name then | ||||
|           old_generated["doors"] ||= {} | ||||
|           old_generated["doors"][room_name] ||= {} | ||||
|           old_generated["doors"][room_name][door_name] ||= {} | ||||
|           old_generated["doors"][room_name][door_name]["location"] = location_id_by_name[location_name] | ||||
|         elsif not (old_generated.include? "doors" and old_generated["doors"].include? room_name and old_generated["doors"][room_name].include? door_name and old_generated["doors"][room_name][door_name].include? "location") then | ||||
|           old_generated["doors"] ||= {} | ||||
|           old_generated["doors"][room_name] ||= {} | ||||
|           old_generated["doors"][room_name][door_name] ||= {} | ||||
|           old_generated["doors"][room_name][door_name]["location"] = next_location_id | ||||
|    | ||||
|           next_location_id += 1 | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   if room_data.include? "progression" | ||||
|     room_data["progression"].each do |progression_name, pdata| | ||||
|       unless old_generated.include? "progression" and old_generated["progression"].include? progression_name then | ||||
|         old_generated["progression"] ||= {} | ||||
|         old_generated["progression"][progression_name] = next_item_id | ||||
|  | ||||
|         next_item_id += 1 | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| File.write(outputpath, old_generated.to_yaml) | ||||
							
								
								
									
										329
									
								
								worlds/lingo/utils/validate_config.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								worlds/lingo/utils/validate_config.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | ||||
| # Script to validate a level config file. This checks that the names used within | ||||
| # the file are consistent. It also checks that the panel and door IDs mentioned | ||||
| # all exist in the map file. | ||||
| # | ||||
| # Usage: validate_config.rb [config file] [map file] | ||||
|  | ||||
| require 'set' | ||||
| require 'yaml' | ||||
|  | ||||
| configpath = ARGV[0] | ||||
| mappath = ARGV[1] | ||||
|  | ||||
| panels = Set["Countdown Panels/Panel_1234567890_wanderlust"] | ||||
| doors = Set["Naps Room Doors/Door_hider_new1", "Tower Room Area Doors/Door_wanderer_entrance"] | ||||
| paintings = Set[] | ||||
|  | ||||
| File.readlines(mappath).each do |line| | ||||
|   line.match(/node name=\"(.*)\" parent=\"Panels\/(.*)\" instance/) do |m| | ||||
|     panels.add(m[2] + "/" + m[1]) | ||||
|   end | ||||
|   line.match(/node name=\"(.*)\" parent=\"Doors\/(.*)\" instance/) do |m| | ||||
|     doors.add(m[2] + "/" + m[1]) | ||||
|   end | ||||
|   line.match(/node name=\"(.*)\" parent=\"Decorations\/Paintings\" instance/) do |m| | ||||
|     paintings.add(m[1]) | ||||
|   end | ||||
|   line.match(/node name=\"(.*)\" parent=\"Decorations\/EndPanel\" instance/) do |m| | ||||
|     panels.add("EndPanel/" + m[1]) | ||||
|   end | ||||
| end | ||||
|  | ||||
| configured_rooms = Set["Menu"] | ||||
| configured_doors = Set[] | ||||
| configured_panels = Set[] | ||||
|  | ||||
| mentioned_rooms = Set[] | ||||
| mentioned_doors = Set[] | ||||
| mentioned_panels = Set[] | ||||
|  | ||||
| door_groups = {} | ||||
|  | ||||
| directives = Set["entrances", "panels", "doors", "paintings", "progression"] | ||||
| panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting"] | ||||
| door_directives = Set["id", "painting_id", "panels", "item_name", "location_name", "skip_location", "skip_item", "group", "include_reduce", "junk_item", "event"] | ||||
| painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move"] | ||||
|  | ||||
| non_counting = 0 | ||||
|  | ||||
| config = YAML.load_file(configpath) | ||||
| config.each do |room_name, room| | ||||
|   configured_rooms.add(room_name) | ||||
|  | ||||
|   used_directives = Set[] | ||||
|   room.each_key do |key| | ||||
|     used_directives.add(key) | ||||
|   end | ||||
|   diff_directives = used_directives - directives | ||||
|   unless diff_directives.empty? then | ||||
|     puts("#{room_name} has the following invalid top-level directives: #{diff_directives.to_s}") | ||||
|   end | ||||
|  | ||||
|   (room["entrances"] || {}).each do |source_room, entrance| | ||||
|     mentioned_rooms.add(source_room) | ||||
|  | ||||
|     entrances = [] | ||||
|     if entrance.kind_of? Hash | ||||
|       if entrance.keys() != ["painting"] then | ||||
|         entrances = [entrance] | ||||
|       end | ||||
|     elsif entrance.kind_of? Array | ||||
|       entrances = entrance | ||||
|     end | ||||
|  | ||||
|     entrances.each do |e| | ||||
|       entrance_room = e.include?("room") ? e["room"] : room_name | ||||
|       mentioned_rooms.add(entrance_room) | ||||
|       mentioned_doors.add(entrance_room + " - " + e["door"]) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   (room["panels"] || {}).each do |panel_name, panel| | ||||
|     unless panel_name.kind_of? String then | ||||
|       puts "#{room_name} has an invalid panel name" | ||||
|     end | ||||
|  | ||||
|     configured_panels.add(room_name + " - " + panel_name) | ||||
|  | ||||
|     if panel.include?("id") | ||||
|       panel_ids = [] | ||||
|       if panel["id"].kind_of? Array | ||||
|         panel_ids = panel["id"] | ||||
|       else | ||||
|         panel_ids = [panel["id"]] | ||||
|       end | ||||
|  | ||||
|       panel_ids.each do |panel_id| | ||||
|         unless panels.include? panel_id then | ||||
|           puts "#{room_name} - #{panel_name} :::: Invalid Panel ID #{panel_id}" | ||||
|         end | ||||
|       end | ||||
|     else | ||||
|       puts "#{room_name} - #{panel_name} :::: Panel is missing an ID" | ||||
|     end | ||||
|  | ||||
|     if panel.include?("required_room") | ||||
|       required_rooms = [] | ||||
|       if panel["required_room"].kind_of? Array | ||||
|         required_rooms = panel["required_room"] | ||||
|       else | ||||
|         required_rooms = [panel["required_room"]] | ||||
|       end | ||||
|  | ||||
|       required_rooms.each do |required_room| | ||||
|         mentioned_rooms.add(required_room) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     if panel.include?("required_door") | ||||
|       required_doors = [] | ||||
|       if panel["required_door"].kind_of? Array | ||||
|         required_doors = panel["required_door"] | ||||
|       else | ||||
|         required_doors = [panel["required_door"]] | ||||
|       end | ||||
|  | ||||
|       required_doors.each do |required_door| | ||||
|         other_room = required_door.include?("room") ? required_door["room"] : room_name | ||||
|         mentioned_rooms.add(other_room) | ||||
|         mentioned_doors.add("#{other_room} - #{required_door["door"]}") | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     if panel.include?("required_panel") | ||||
|       required_panels = [] | ||||
|       if panel["required_panel"].kind_of? Array | ||||
|         required_panels = panel["required_panel"] | ||||
|       else | ||||
|         required_panels = [panel["required_panel"]] | ||||
|       end | ||||
|  | ||||
|       required_panels.each do |required_panel| | ||||
|         other_room = required_panel.include?("room") ? required_panel["room"] : room_name | ||||
|         mentioned_rooms.add(other_room) | ||||
|         mentioned_panels.add("#{other_room} - #{required_panel["panel"]}") | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     unless panel.include?("tag") then | ||||
|       puts "#{room_name} - #{panel_name} :::: Panel is missing a tag" | ||||
|     end | ||||
|  | ||||
|     if panel.include?("non_counting") then | ||||
|       non_counting += 1 | ||||
|     end | ||||
|  | ||||
|     bad_subdirectives = [] | ||||
|     panel.keys.each do |key| | ||||
|       unless panel_directives.include?(key) then | ||||
|         bad_subdirectives << key | ||||
|       end | ||||
|     end | ||||
|     unless bad_subdirectives.empty? then | ||||
|       puts "#{room_name} - #{panel_name} :::: Panel has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   (room["doors"] || {}).each do |door_name, door| | ||||
|     configured_doors.add("#{room_name} - #{door_name}") | ||||
|  | ||||
|     if door.include?("id") | ||||
|       door_ids = [] | ||||
|       if door["id"].kind_of? Array | ||||
|         door_ids = door["id"] | ||||
|       else | ||||
|         door_ids = [door["id"]] | ||||
|       end | ||||
|  | ||||
|       door_ids.each do |door_id| | ||||
|         unless doors.include? door_id then | ||||
|           puts "#{room_name} - #{door_name} :::: Invalid Door ID #{door_id}" | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     if door.include?("painting_id") | ||||
|       painting_ids = [] | ||||
|       if door["painting_id"].kind_of? Array | ||||
|         painting_ids = door["painting_id"] | ||||
|       else | ||||
|         painting_ids = [door["painting_id"]] | ||||
|       end | ||||
|  | ||||
|       painting_ids.each do |painting_id| | ||||
|         unless paintings.include? painting_id then | ||||
|           puts "#{room_name} - #{door_name} :::: Invalid Painting ID #{painting_id}" | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     if not door.include?("id") and not door.include?("painting_id") and not door["skip_item"] and not door["event"] then | ||||
|       puts "#{room_name} - #{door_name} :::: Should be marked skip_item or event if there are no doors or paintings" | ||||
|     end | ||||
|  | ||||
|     if door.include?("panels") | ||||
|       door["panels"].each do |panel| | ||||
|         if panel.kind_of? Hash then | ||||
|           other_room = panel.include?("room") ? panel["room"] : room_name | ||||
|           mentioned_panels.add("#{other_room} - #{panel["panel"]}") | ||||
|         else | ||||
|           other_room = panel.include?("room") ? panel["room"] : room_name | ||||
|           mentioned_panels.add("#{room_name} - #{panel}") | ||||
|         end | ||||
|       end | ||||
|     elsif not door["skip_location"] | ||||
|       puts "#{room_name} - #{door_name} :::: Should be marked skip_location if there are no panels" | ||||
|     end | ||||
|  | ||||
|     if door.include?("group") | ||||
|       door_groups[door["group"]] ||= 0 | ||||
|       door_groups[door["group"]] += 1 | ||||
|     end | ||||
|  | ||||
|     bad_subdirectives = [] | ||||
|     door.keys.each do |key| | ||||
|       unless door_directives.include?(key) then | ||||
|         bad_subdirectives << key | ||||
|       end | ||||
|     end | ||||
|     unless bad_subdirectives.empty? then | ||||
|       puts "#{room_name} - #{door_name} :::: Door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   (room["paintings"] || []).each do |painting| | ||||
|     if painting.include?("id") and painting["id"].kind_of? String then | ||||
|       unless paintings.include? painting["id"] then | ||||
|         puts "#{room_name} :::: Invalid Painting ID #{painting["id"]}" | ||||
|       end | ||||
|     else | ||||
|       puts "#{room_name} :::: Painting is missing an ID" | ||||
|     end | ||||
|  | ||||
|     if painting["disable"] then | ||||
|       # We're good. | ||||
|       next | ||||
|     end | ||||
|  | ||||
|     if painting.include?("orientation") then | ||||
|       unless ["north", "south", "east", "west"].include? painting["orientation"] then | ||||
|         puts "#{room_name} - #{painting["id"] || "painting"} :::: Invalid orientation #{painting["orientation"]}" | ||||
|       end | ||||
|     else | ||||
|       puts "#{room_name} :::: Painting is missing an orientation" | ||||
|     end | ||||
|  | ||||
|     if painting.include?("required_door") | ||||
|       other_room = painting["required_door"].include?("room") ? painting["required_door"]["room"] : room_name | ||||
|       mentioned_doors.add("#{other_room} - #{painting["required_door"]["door"]}") | ||||
|  | ||||
|       unless painting["enter_only"] then | ||||
|         puts "#{room_name} - #{painting["id"] || "painting"} :::: Should be marked enter_only if there is a required_door" | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     bad_subdirectives = [] | ||||
|     painting.keys.each do |key| | ||||
|       unless painting_directives.include?(key) then | ||||
|         bad_subdirectives << key | ||||
|       end | ||||
|     end | ||||
|     unless bad_subdirectives.empty? then | ||||
|       puts "#{room_name} - #{painting["id"] || "painting"} :::: Painting has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   (room["progression"] || {}).each do |progression_name, door_list| | ||||
|     door_list.each do |door| | ||||
|       if door.kind_of? Hash then | ||||
|         mentioned_doors.add("#{door["room"]} - #{door["door"]}") | ||||
|       else | ||||
|         mentioned_doors.add("#{room_name} - #{door}") | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| errored_rooms = mentioned_rooms - configured_rooms | ||||
| unless errored_rooms.empty? then | ||||
|   puts "The folloring rooms are mentioned but do not exist: " + errored_rooms.to_s | ||||
| end | ||||
|  | ||||
| errored_panels = mentioned_panels - configured_panels | ||||
| unless errored_panels.empty? then | ||||
|   puts "The folloring panels are mentioned but do not exist: " + errored_panels.to_s | ||||
| end | ||||
|  | ||||
| errored_doors = mentioned_doors - configured_doors | ||||
| unless errored_doors.empty? then | ||||
|   puts "The folloring doors are mentioned but do not exist: " + errored_doors.to_s | ||||
| end | ||||
|  | ||||
| door_groups.each do |group,num| | ||||
|   if num == 1 then | ||||
|     puts "Door group \"#{group}\" only has one door in it" | ||||
|   end | ||||
| end | ||||
|  | ||||
| slashed_rooms = configured_rooms.select do |room| | ||||
|   room.include? "/" | ||||
| end | ||||
| unless slashed_rooms.empty? then | ||||
|   puts "The following rooms have slashes in their names: " + slashed_rooms.to_s | ||||
| end | ||||
|  | ||||
| slashed_panels = configured_panels.select do |panel| | ||||
|   panel.include? "/" | ||||
| end | ||||
| unless slashed_panels.empty? then | ||||
|   puts "The following panels have slashes in their names: " + slashed_panels.to_s | ||||
| end | ||||
|  | ||||
| slashed_doors = configured_doors.select do |door| | ||||
|   door.include? "/" | ||||
| end | ||||
| unless slashed_doors.empty? then | ||||
|   puts "The following doors have slashes in their names: " + slashed_doors.to_s | ||||
| end | ||||
|  | ||||
| puts "#{configured_panels.size} panels (#{non_counting} non counting)" | ||||
		Reference in New Issue
	
	Block a user