| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  | import itertools | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2021-11-14 15:46:07 +01:00
										 |  |  | import os.path | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | import threading | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | # from . import pyevermizer  # as part of the source tree | 
					
						
							|  |  |  | import pyevermizer  # from package | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | import settings | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | from BaseClasses import Item, ItemClassification, Location, LocationProgressType, Region, Tutorial | 
					
						
							|  |  |  | from Utils import output_path | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  | from worlds.AutoWorld import WebWorld, World | 
					
						
							|  |  |  | from worlds.generic.Rules import add_item_rule, set_rule | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | from .logic import SoEPlayerLogic | 
					
						
							| 
									
										
										
										
											2024-01-15 09:17:46 +01:00
										 |  |  | from .options import Difficulty, EnergyCore, SoEOptions | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | from .patch import SoEDeltaPatch, get_base_rom_path | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | if typing.TYPE_CHECKING: | 
					
						
							|  |  |  |     from BaseClasses import MultiWorld, CollectionState | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 09:17:46 +01:00
										 |  |  | __all__ = ["pyevermizer", "SoEWorld"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | In evermizer: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Items are uniquely defined by a pair of (type, id). | 
					
						
							|  |  |  | For most items this is their vanilla location (i.e. CHECK_GOURD, number). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Items have `provides`, which give the actual progression | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | instead of providing multiple events per item, we iterate through them in logic.py | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |     e.g. Found any weapon | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Locations have `requires` and `provides`. | 
					
						
							|  |  |  | Requirements have to be converted to (access) rules for AP | 
					
						
							|  |  |  |     e.g. Chest locked behind having a weapon | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | Provides could be events, but instead we iterate through the entire logic in logic.py | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |     e.g. NPC available after fighting a Boss | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Rules are special locations that don't have a physical location | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | instead of implementing virtual locations and virtual items, we simply use them in logic.py | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |     e.g. 2DEs+Wheel+Gauge = Rocket | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Rules and Locations live on the same logic tree returned by pyevermizer.get_logic() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TODO: for balancing we may want to generate Regions (with Entrances) for some | 
					
						
							|  |  |  | common rules, place the locations in those Regions and shorten the rules. | 
					
						
							| 
									
										
										
										
											2022-03-21 22:34:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Item grouping currently supports | 
					
						
							|  |  |  | * Any <ingredient name> - "Any Water" matches all Water drops | 
					
						
							|  |  |  | * Any <healing item name> - "Any Petal" matches all Petal drops | 
					
						
							|  |  |  | * Any Moniez - Matches the talon/jewel/gold coin/credit drops from chests (not market, fountain or Mungola) | 
					
						
							|  |  |  | * Ingredients - Matches all ingredient drops | 
					
						
							|  |  |  | * Alchemy - Matches all alchemy formulas | 
					
						
							|  |  |  | * Weapons - Matches all weapons but Bazooka, Bone Crusher, Neutron Blade | 
					
						
							|  |  |  | * Traps - Matches all traps | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | _id_base = 64000 | 
					
						
							|  |  |  | _id_offset: typing.Dict[int, int] = { | 
					
						
							|  |  |  |     pyevermizer.CHECK_ALCHEMY: _id_base + 0,  # alchemy 64000..64049 | 
					
						
							|  |  |  |     pyevermizer.CHECK_BOSS: _id_base + 50,  # bosses 64050..6499 | 
					
						
							|  |  |  |     pyevermizer.CHECK_GOURD: _id_base + 100,  # gourds 64100..64399 | 
					
						
							|  |  |  |     pyevermizer.CHECK_NPC: _id_base + 400,  # npc 64400..64499 | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |     # TODO: sniff 64500..64799 | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |     pyevermizer.CHECK_EXTRA: _id_base + 800,  # extra items 64800..64899 | 
					
						
							|  |  |  |     pyevermizer.CHECK_TRAP: _id_base + 900,  # trap 64900..64999 | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | # cache native evermizer items and locations | 
					
						
							|  |  |  | _items = pyevermizer.get_items() | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  | _traps = pyevermizer.get_traps() | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  | _extras = pyevermizer.get_extra_items()  # items that are not placed by default | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | _locations = pyevermizer.get_locations() | 
					
						
							|  |  |  | # fix up texts for AP | 
					
						
							|  |  |  | for _loc in _locations: | 
					
						
							|  |  |  |     if _loc.type == pyevermizer.CHECK_GOURD: | 
					
						
							|  |  |  |         _loc.name = f'{_loc.name} #{_loc.index}' | 
					
						
							| 
									
										
										
										
											2022-03-21 22:34:28 +01:00
										 |  |  | # item helpers | 
					
						
							|  |  |  | _ingredients = ( | 
					
						
							|  |  |  |     'Wax', 'Water', 'Vinegar', 'Root', 'Oil', 'Mushroom', 'Mud Pepper', 'Meteorite', 'Limestone', 'Iron', | 
					
						
							| 
									
										
										
										
											2024-01-21 19:34:24 +01:00
										 |  |  |     'Gunpowder', 'Grease', 'Feather', 'Ethanol', 'Dry Ice', 'Crystal', 'Clay', 'Brimstone', 'Bone', 'Atlas Medallion', | 
					
						
							| 
									
										
										
										
											2022-03-21 22:34:28 +01:00
										 |  |  |     'Ash', 'Acorn' | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | _other_items = ( | 
					
						
							|  |  |  |     'Call bead', 'Petal', 'Biscuit', 'Pixie Dust', 'Nectar', 'Honey', 'Moniez' | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  | def _match_item_name(item: pyevermizer.Item, substr: str) -> bool: | 
					
						
							|  |  |  |     sub: str = item.name.split(' ', 1)[1] if item.name[0].isdigit() else item.name | 
					
						
							| 
									
										
										
										
											2022-03-21 22:34:28 +01:00
										 |  |  |     return sub == substr or sub == substr+'s' | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]: | 
					
						
							|  |  |  |     name_to_id = {} | 
					
						
							|  |  |  |     id_to_raw = {} | 
					
						
							|  |  |  |     for loc in _locations: | 
					
						
							| 
									
										
										
										
											2022-02-05 14:54:22 +01:00
										 |  |  |         ap_id = _id_offset[loc.type] + loc.index | 
					
						
							|  |  |  |         id_to_raw[ap_id] = loc | 
					
						
							|  |  |  |         name_to_id[loc.name] = ap_id | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     name_to_id['Done'] = None | 
					
						
							|  |  |  |     return name_to_id, id_to_raw | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]: | 
					
						
							|  |  |  |     name_to_id = {} | 
					
						
							|  |  |  |     id_to_raw = {} | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |     for item in itertools.chain(_items, _extras, _traps): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         if item.name in name_to_id: | 
					
						
							|  |  |  |             continue | 
					
						
							| 
									
										
										
										
											2022-02-05 14:54:22 +01:00
										 |  |  |         ap_id = _id_offset[item.type] + item.index | 
					
						
							|  |  |  |         id_to_raw[ap_id] = item | 
					
						
							|  |  |  |         name_to_id[item.name] = ap_id | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     name_to_id['Victory'] = None | 
					
						
							|  |  |  |     return name_to_id, id_to_raw | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 22:34:28 +01:00
										 |  |  | def _get_item_grouping() -> typing.Dict[str, typing.Set[str]]: | 
					
						
							|  |  |  |     groups = {} | 
					
						
							|  |  |  |     ingredients_group = set() | 
					
						
							|  |  |  |     for ingredient in _ingredients: | 
					
						
							|  |  |  |         group = set(item.name for item in _items if _match_item_name(item, ingredient)) | 
					
						
							|  |  |  |         groups[f'Any {ingredient}'] = group | 
					
						
							|  |  |  |         ingredients_group |= group | 
					
						
							|  |  |  |     groups['Ingredients'] = ingredients_group | 
					
						
							|  |  |  |     for other in _other_items: | 
					
						
							|  |  |  |         groups[f'Any {other}'] = set(item.name for item in _items if _match_item_name(item, other)) | 
					
						
							|  |  |  |     groups['Alchemy'] = set(item.name for item in _items if item.type == pyevermizer.CHECK_ALCHEMY) | 
					
						
							|  |  |  |     groups['Weapons'] = {'Spider Claw', 'Horn Spear', 'Gladiator Sword', 'Bronze Axe', 'Bronze Spear', 'Crusader Sword', | 
					
						
							|  |  |  |                          'Lance (Weapon)', 'Knight Basher', 'Atom Smasher', 'Laser Lance'} | 
					
						
							|  |  |  |     groups['Traps'] = {trap.name for trap in _traps} | 
					
						
							|  |  |  |     return groups | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 04:48:43 +02:00
										 |  |  | class SoEWebWorld(WebWorld): | 
					
						
							|  |  |  |     theme = 'jungle' | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  |     tutorials = [Tutorial( | 
					
						
							|  |  |  |         "Multiworld Setup Guide", | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |         "A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related" | 
					
						
							|  |  |  |         " software.", | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  |         "English", | 
					
						
							|  |  |  |         "multiworld_en.md", | 
					
						
							|  |  |  |         "multiworld/en", | 
					
						
							|  |  |  |         ["Black Sliver"] | 
					
						
							|  |  |  |     )] | 
					
						
							| 
									
										
										
										
											2022-04-03 04:48:43 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | class SoESettings(settings.Group): | 
					
						
							|  |  |  |     class RomFile(settings.SNESRomPath): | 
					
						
							|  |  |  |         """File name of the SoE US ROM""" | 
					
						
							|  |  |  |         description = "Secret of Evermore (USA) ROM" | 
					
						
							|  |  |  |         copy_to = "Secret of Evermore (USA).sfc" | 
					
						
							|  |  |  |         md5s = [SoEDeltaPatch.hash] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rom_file: RomFile = RomFile(RomFile.copy_to) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | class SoEWorld(World): | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     Secret of Evermore is a SNES action RPG. You learn alchemy spells, fight bosses and gather rocket parts to visit a | 
					
						
							| 
									
										
										
										
											2024-01-15 09:17:46 +01:00
										 |  |  |     space station where the final boss must be defeated. | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     game: typing.ClassVar[str] = "Secret of Evermore" | 
					
						
							|  |  |  |     options_dataclass = SoEOptions | 
					
						
							|  |  |  |     options: SoEOptions | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     settings: typing.ClassVar[SoESettings] | 
					
						
							| 
									
										
										
										
											2022-04-03 04:48:43 +02:00
										 |  |  |     topology_present = False | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |     data_version = 4 | 
					
						
							| 
									
										
										
										
											2022-04-03 04:48:43 +02:00
										 |  |  |     web = SoEWebWorld() | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |     required_client_version = (0, 3, 5) | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     item_name_to_id, item_id_to_raw = _get_item_mapping() | 
					
						
							|  |  |  |     location_name_to_id, location_id_to_raw = _get_location_mapping() | 
					
						
							| 
									
										
										
										
											2022-03-21 22:34:28 +01:00
										 |  |  |     item_name_groups = _get_item_grouping() | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     logic: SoEPlayerLogic | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     evermizer_seed: int | 
					
						
							| 
									
										
										
										
											2021-11-07 15:56:43 +01:00
										 |  |  |     connect_name: str | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-22 01:29:20 +01:00
										 |  |  |     _halls_ne_chest_names: typing.List[str] = [loc.name for loc in _locations if 'Halls NE' in loc.name] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def __init__(self, multiworld: "MultiWorld", player: int): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         self.connect_name_available_event = threading.Event() | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         super(SoEWorld, self).__init__(multiworld, player) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |     def generate_early(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         # create logic from options | 
					
						
							|  |  |  |         if self.options.required_fragments.value > self.options.available_fragments.value: | 
					
						
							|  |  |  |             self.options.available_fragments.value = self.options.required_fragments.value | 
					
						
							|  |  |  |         self.logic = SoEPlayerLogic(self.player, self.options) | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     def create_event(self, event: str) -> Item: | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |         return SoEItem(event, ItemClassification.progression, None, self.player) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-24 01:39:18 +01:00
										 |  |  |     def create_item(self, item: typing.Union[pyevermizer.Item, str]) -> Item: | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         if isinstance(item, str): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |             item = self.item_id_to_raw[self.item_name_to_id[item]] | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |         if item.type == pyevermizer.CHECK_TRAP: | 
					
						
							|  |  |  |             classification = ItemClassification.trap | 
					
						
							|  |  |  |         elif item.progression: | 
					
						
							|  |  |  |             classification = ItemClassification.progression | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |         elif item.useful: | 
					
						
							|  |  |  |             classification = ItemClassification.useful | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |         else: | 
					
						
							|  |  |  |             classification = ItemClassification.filler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return SoEItem(item.name, classification, self.item_name_to_id[item.name], self.player) | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 20:37:28 -05:00
										 |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def stage_assert_generate(cls, _: "MultiWorld") -> None: | 
					
						
							| 
									
										
										
										
											2022-04-29 20:37:28 -05:00
										 |  |  |         rom_file = get_base_rom_path() | 
					
						
							|  |  |  |         if not os.path.exists(rom_file): | 
					
						
							|  |  |  |             raise FileNotFoundError(rom_file) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def create_regions(self) -> None: | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         # exclude 'hidden' on easy | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         max_difficulty = 1 if self.options.difficulty == Difficulty.option_easy else 256 | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         # TODO: generate *some* regions from locations' requirements? | 
					
						
							| 
									
										
										
										
											2023-10-25 09:34:59 +02:00
										 |  |  |         menu = Region('Menu', self.player, self.multiworld) | 
					
						
							|  |  |  |         self.multiworld.regions += [menu] | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         def get_sphere_index(evermizer_loc: pyevermizer.Location) -> int: | 
					
						
							| 
									
										
										
										
											2022-11-01 13:14:38 +01:00
										 |  |  |             """Returns 0, 1 or 2 for locations in spheres 1, 2, 3+""" | 
					
						
							|  |  |  |             if len(evermizer_loc.requires) == 1 and evermizer_loc.requires[0][1] != pyevermizer.P_WEAPON: | 
					
						
							|  |  |  |                 return 2 | 
					
						
							|  |  |  |             return min(2, len(evermizer_loc.requires)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-25 09:34:59 +02:00
										 |  |  |         # create ingame region | 
					
						
							|  |  |  |         ingame = Region('Ingame', self.player, self.multiworld) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         # group locations into spheres (1, 2, 3+ at index 0, 1, 2) | 
					
						
							|  |  |  |         spheres: typing.Dict[int, typing.Dict[int, typing.List[SoELocation]]] = {} | 
					
						
							|  |  |  |         for loc in _locations: | 
					
						
							| 
									
										
										
										
											2022-11-01 13:14:38 +01:00
										 |  |  |             spheres.setdefault(get_sphere_index(loc), {}).setdefault(loc.type, []).append( | 
					
						
							| 
									
										
										
										
											2023-10-25 09:34:59 +02:00
										 |  |  |                 SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], ingame, | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |                             loc.difficulty > max_difficulty)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # location balancing data | 
					
						
							|  |  |  |         trash_fills: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int, int]]] = { | 
					
						
							|  |  |  |             0: {pyevermizer.CHECK_GOURD: (20, 40, 40, 40)},  # remove up to 40 gourds from sphere 1 | 
					
						
							|  |  |  |             1: {pyevermizer.CHECK_GOURD: (70, 90, 90, 90)},  # remove up to 90 gourds from sphere 2 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # mark some as excluded based on numbers above | 
					
						
							|  |  |  |         for trash_sphere, fills in trash_fills.items(): | 
					
						
							|  |  |  |             for typ, counts in fills.items(): | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |                 count = counts[self.options.difficulty.value] | 
					
						
							|  |  |  |                 for location in self.random.sample(spheres[trash_sphere][typ], count): | 
					
						
							| 
									
										
										
										
											2022-11-01 13:14:38 +01:00
										 |  |  |                     assert location.name != "Energy Core #285", "Error in sphere generation" | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |                     location.progress_type = LocationProgressType.EXCLUDED | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         def sphere1_blocked_items_rule(item: pyevermizer.Item) -> bool: | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |             if isinstance(item, SoEItem): | 
					
						
							|  |  |  |                 # disable certain items in sphere 1 | 
					
						
							|  |  |  |                 if item.name in {"Gauge", "Wheel"}: | 
					
						
							|  |  |  |                     return False | 
					
						
							|  |  |  |                 # and some more for non-easy, non-mystery | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |                 if self.options.difficulty not in (Difficulty.option_easy, Difficulty.option_mystery): | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |                     if item.name in {"Laser Lance", "Atom Smasher", "Diamond Eye"}: | 
					
						
							|  |  |  |                         return False | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for locations in spheres[0].values(): | 
					
						
							|  |  |  |             for location in locations: | 
					
						
							|  |  |  |                 add_item_rule(location, sphere1_blocked_items_rule) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # make some logically late(r) bosses priority locations to increase complexity | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         if self.options.difficulty == Difficulty.option_mystery: | 
					
						
							|  |  |  |             late_count = self.random.randint(0, 2) | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             late_count = self.options.difficulty.value | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         late_bosses = ("Tiny", "Aquagoth", "Megataur", "Rimsala", | 
					
						
							|  |  |  |                        "Mungola", "Lightning Storm", "Magmar", "Volcano Viper") | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         late_locations = self.random.sample(late_bosses, late_count) | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # add locations to the world | 
					
						
							|  |  |  |         for sphere in spheres.values(): | 
					
						
							|  |  |  |             for locations in sphere.values(): | 
					
						
							|  |  |  |                 for location in locations: | 
					
						
							| 
									
										
										
										
											2023-10-25 09:34:59 +02:00
										 |  |  |                     ingame.locations.append(location) | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |                     if location.name in late_locations: | 
					
						
							|  |  |  |                         location.progress_type = LocationProgressType.PRIORITY | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-25 09:34:59 +02:00
										 |  |  |         ingame.locations.append(SoELocation(self.player, 'Done', None, ingame)) | 
					
						
							|  |  |  |         menu.connect(ingame, "New Game") | 
					
						
							|  |  |  |         self.multiworld.regions += [ingame] | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def create_items(self) -> None: | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |         # add regular items to the pool | 
					
						
							|  |  |  |         exclusions: typing.List[str] = [] | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         if self.options.energy_core != EnergyCore.option_shuffle: | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |             exclusions.append("Energy Core")  # will be placed in generate_basic or replaced by a fragment below | 
					
						
							|  |  |  |         items = list(map(lambda item: self.create_item(item), (item for item in _items if item.name not in exclusions))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # remove one pair of wings that will be placed in generate_basic | 
					
						
							|  |  |  |         items.remove(self.create_item("Wings")) | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         def is_ingredient(item: pyevermizer.Item) -> bool: | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |             for ingredient in _ingredients: | 
					
						
							|  |  |  |                 if _match_item_name(item, ingredient): | 
					
						
							|  |  |  |                     return True | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # add energy core fragments to the pool | 
					
						
							|  |  |  |         ingredients = [n for n, item in enumerate(items) if is_ingredient(item)] | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         if self.options.energy_core == EnergyCore.option_fragments: | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |             items.append(self.create_item("Energy Core Fragment"))  # replaces the vanilla energy core | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             for _ in range(self.options.available_fragments - 1): | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |                 if len(ingredients) < 1: | 
					
						
							|  |  |  |                     break  # out of ingredients to replace | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |                 r = self.random.choice(ingredients) | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |                 ingredients.remove(r) | 
					
						
							|  |  |  |                 items[r] = self.create_item("Energy Core Fragment") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # add traps to the pool | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         trap_count = self.options.trap_count.value | 
					
						
							|  |  |  |         trap_names: typing.List[str] = [] | 
					
						
							|  |  |  |         trap_weights: typing.List[int] = [] | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  |         if trap_count > 0: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             for trap_option in self.options.trap_chances: | 
					
						
							|  |  |  |                 trap_names.append(trap_option.item_name) | 
					
						
							|  |  |  |                 trap_weights.append(trap_option.value) | 
					
						
							|  |  |  |             if sum(trap_weights) == 0: | 
					
						
							|  |  |  |                 trap_weights = [1 for _ in trap_weights] | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-24 01:39:18 +01:00
										 |  |  |         def create_trap() -> Item: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             return self.create_item(self.random.choices(trap_names, trap_weights)[0]) | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |         for _ in range(trap_count): | 
					
						
							|  |  |  |             if len(ingredients) < 1: | 
					
						
							|  |  |  |                 break  # out of ingredients to replace | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             r = self.random.choice(ingredients) | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |             ingredients.remove(r) | 
					
						
							|  |  |  |             items[r] = create_trap() | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         self.multiworld.itempool += items | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def set_rules(self) -> None: | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         self.multiworld.completion_condition[self.player] = lambda state: state.has('Victory', self.player) | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |         # set Done from goal option once we have multiple goals | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         set_rule(self.multiworld.get_location('Done', self.player), | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |                  lambda state: self.logic.has(state, pyevermizer.P_FINAL_BOSS)) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         set_rule(self.multiworld.get_entrance('New Game', self.player), lambda state: True) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         for loc in _locations: | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |             location = self.multiworld.get_location(loc.name, self.player) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |             set_rule(location, self.make_rule(loc.requires)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |     def make_rule(self, requires: typing.List[typing.Tuple[int, int]]) -> typing.Callable[[typing.Any], bool]: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         def rule(state: "CollectionState") -> bool: | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |             for count, progress in requires: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |                 if not self.logic.has(state, progress, count): | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |                     return False | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return rule | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def generate_basic(self) -> None: | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         # place Victory event | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         self.multiworld.get_location('Done', self.player).place_locked_item(self.create_event('Victory')) | 
					
						
							| 
									
										
										
										
											2022-01-22 01:29:20 +01:00
										 |  |  |         # place wings in halls NE to avoid softlock | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         wings_location = self.random.choice(self._halls_ne_chest_names) | 
					
						
							| 
									
										
										
										
											2022-01-22 01:29:20 +01:00
										 |  |  |         wings_item = self.create_item('Wings') | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         self.multiworld.get_location(wings_location, self.player).place_locked_item(wings_item) | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |         # place energy core at vanilla location for vanilla mode | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         if self.options.energy_core == EnergyCore.option_vanilla: | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |             energy_core = self.create_item('Energy Core') | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |             self.multiworld.get_location('Energy Core #285', self.player).place_locked_item(energy_core) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         # generate stuff for later | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         self.evermizer_seed = self.random.randint(0, 2 ** 16 - 1)  # TODO: make this an option for "full" plando? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_output(self, output_directory: str) -> None: | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         player_name = self.multiworld.get_player_name(self.player) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         self.connect_name = player_name[:32] | 
					
						
							|  |  |  |         while len(self.connect_name.encode('utf-8')) > 32: | 
					
						
							|  |  |  |             self.connect_name = self.connect_name[:-1] | 
					
						
							|  |  |  |         self.connect_name_available_event.set() | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         placement_file = "" | 
					
						
							|  |  |  |         out_file = "" | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             money = self.options.money_modifier.value | 
					
						
							|  |  |  |             exp = self.options.exp_modifier.value | 
					
						
							| 
									
										
										
										
											2022-07-15 18:01:07 +02:00
										 |  |  |             switches: typing.List[str] = [] | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             if self.options.death_link.value: | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  |                 switches.append("--death-link") | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             if self.options.energy_core == EnergyCore.option_fragments: | 
					
						
							|  |  |  |                 switches.extend(('--available-fragments', str(self.options.available_fragments.value), | 
					
						
							|  |  |  |                                  '--required-fragments', str(self.options.required_fragments.value))) | 
					
						
							| 
									
										
										
										
											2022-03-18 04:53:09 +01:00
										 |  |  |             rom_file = get_base_rom_path() | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |             out_base = output_path(output_directory, self.multiworld.get_out_file_name_base(self.player)) | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |             out_file = out_base + '.sfc' | 
					
						
							|  |  |  |             placement_file = out_base + '.txt' | 
					
						
							|  |  |  |             patch_file = out_base + '.apsoe' | 
					
						
							|  |  |  |             flags = 'l'  # spoiler log | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |             flags += self.options.flags | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             with open(placement_file, "wb") as f:  # generate placement file | 
					
						
							| 
									
										
										
										
											2023-10-29 19:47:37 +01:00
										 |  |  |                 for location in self.multiworld.get_locations(self.player): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |                     item = location.item | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |                     assert item is not None, "Can't handle unfilled location" | 
					
						
							|  |  |  |                     if item.code is None or location.address is None: | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |                         continue  # skip events | 
					
						
							|  |  |  |                     loc = self.location_id_to_raw[location.address] | 
					
						
							|  |  |  |                     if item.player != self.player: | 
					
						
							|  |  |  |                         line = f'{loc.type},{loc.index}:{pyevermizer.CHECK_NONE},{item.code},{item.player}\n' | 
					
						
							|  |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |                         soe_item = self.item_id_to_raw[item.code] | 
					
						
							|  |  |  |                         line = f'{loc.type},{loc.index}:{soe_item.type},{soe_item.index}\n' | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |                     f.write(line.encode('utf-8')) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-14 15:46:07 +01:00
										 |  |  |             if not os.path.exists(rom_file): | 
					
						
							|  |  |  |                 raise FileNotFoundError(rom_file) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |             if (pyevermizer.main(rom_file, out_file, placement_file, self.multiworld.seed_name, self.connect_name, | 
					
						
							| 
									
										
										
										
											2022-03-22 01:13:30 +01:00
										 |  |  |                                  self.evermizer_seed, flags, money, exp, switches)): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |                 raise RuntimeError() | 
					
						
							| 
									
										
										
										
											2022-03-18 04:53:09 +01:00
										 |  |  |             patch = SoEDeltaPatch(patch_file, player=self.player, | 
					
						
							|  |  |  |                                   player_name=player_name, patched_path=out_file) | 
					
						
							|  |  |  |             patch.write() | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         except Exception: | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |             raise | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 os.unlink(placement_file) | 
					
						
							|  |  |  |                 os.unlink(out_file) | 
					
						
							| 
									
										
										
										
											2022-02-22 11:48:08 +01:00
										 |  |  |                 os.unlink(out_file[:-4] + '_SPOILER.log') | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |             except FileNotFoundError: | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |                 pass | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |     def modify_multidata(self, multidata: typing.Dict[str, typing.Any]) -> None: | 
					
						
							| 
									
										
										
										
											2021-11-07 15:56:43 +01:00
										 |  |  |         # wait for self.connect_name to be available. | 
					
						
							|  |  |  |         self.connect_name_available_event.wait() | 
					
						
							|  |  |  |         # we skip in case of error, so that the original error in the output thread is the one that gets raised | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         if self.connect_name and self.connect_name != self.multiworld.player_name[self.player]: | 
					
						
							|  |  |  |             payload = multidata["connect_names"][self.multiworld.player_name[self.player]] | 
					
						
							| 
									
										
										
										
											2021-11-07 15:56:43 +01:00
										 |  |  |             multidata["connect_names"][self.connect_name] = payload | 
					
						
							| 
									
										
										
										
											2022-02-09 21:06:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-19 09:37:26 -04:00
										 |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							| 
									
										
										
										
											2024-01-12 01:07:40 +01:00
										 |  |  |         return self.random.choice(list(self.item_name_groups["Ingredients"])) | 
					
						
							| 
									
										
										
										
											2022-05-19 09:37:26 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | class SoEItem(Item): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     game: str = "Secret of Evermore" | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |     __slots__ = ()  # disable __dict__ | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SoELocation(Location): | 
					
						
							| 
									
										
										
										
											2021-11-07 15:38:02 +01:00
										 |  |  |     game: str = "Secret of Evermore" | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |     __slots__ = ()  # disables __dict__ once Location has __slots__ | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |     def __init__(self, player: int, name: str, address: typing.Optional[int], parent: Region, exclude: bool = False): | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |         super().__init__(player, name, address, parent) | 
					
						
							| 
									
										
										
										
											2022-10-21 23:26:40 +02:00
										 |  |  |         # unconditional assignments favor a split dict, saving memory | 
					
						
							|  |  |  |         self.progress_type = LocationProgressType.EXCLUDED if exclude else LocationProgressType.DEFAULT | 
					
						
							| 
									
										
										
										
											2021-09-29 09:12:23 +02:00
										 |  |  |         self.event = not address |