| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | from collections import deque, Counter | 
					
						
							|  |  |  | from contextlib import redirect_stdout | 
					
						
							|  |  |  | import functools | 
					
						
							| 
									
										
										
										
											2022-12-09 17:54:29 -08:00
										 |  |  | import threading | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | from typing import Any, Dict, List, Set, Tuple, Optional, cast | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from BaseClasses import ItemClassification, LocationProgressType, \ | 
					
						
							| 
									
										
										
										
											2023-02-13 18:06:43 -06:00
										 |  |  |     MultiWorld, Item, CollectionState, Entrance, Tutorial | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | from Options import AssembleOptions | 
					
						
							|  |  |  | from .logic import cs_to_zz_locs | 
					
						
							|  |  |  | from .region import ZillionLocation, ZillionRegion | 
					
						
							| 
									
										
										
										
											2022-10-28 12:56:50 -07:00
										 |  |  | from .options import ZillionStartChar, zillion_options, validate | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | from .id_maps import item_name_to_id as _item_name_to_id, \ | 
					
						
							|  |  |  |     loc_name_to_id as _loc_name_to_id, make_id_to_others, \ | 
					
						
							|  |  |  |     zz_reg_name_to_reg_name, base_id | 
					
						
							|  |  |  | from .item import ZillionItem | 
					
						
							|  |  |  | from .patch import ZillionDeltaPatch, get_base_rom_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from zilliandomizer.randomizer import Randomizer as ZzRandomizer | 
					
						
							|  |  |  | from zilliandomizer.system import System | 
					
						
							|  |  |  | from zilliandomizer.logic_components.items import RESCUE, items as zz_items, Item as ZzItem | 
					
						
							|  |  |  | from zilliandomizer.logic_components.locations import Location as ZzLocation, Req | 
					
						
							|  |  |  | from zilliandomizer.options import Chars | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from ..AutoWorld import World, WebWorld | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ZillionWebWorld(WebWorld): | 
					
						
							|  |  |  |     theme = "stone" | 
					
						
							|  |  |  |     tutorials = [Tutorial( | 
					
						
							|  |  |  |         "Multiworld Setup Guide", | 
					
						
							|  |  |  |         "A guide to playing Zillion randomizer.", | 
					
						
							|  |  |  |         "English", | 
					
						
							|  |  |  |         "setup_en.md", | 
					
						
							|  |  |  |         "setup/en", | 
					
						
							|  |  |  |         ["beauxq"] | 
					
						
							|  |  |  |     )] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ZillionWorld(World): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Zillion is a metroidvania style game released in 1987 for the 8-bit Sega Master System. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     It's based on the anime Zillion (赤い光弾ジリオン, Akai Koudan Zillion). | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     game = "Zillion" | 
					
						
							|  |  |  |     web = ZillionWebWorld() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     option_definitions: Dict[str, AssembleOptions] = zillion_options | 
					
						
							|  |  |  |     topology_present: bool = True  # indicate if world type has any meaningful layout/pathing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # map names to their IDs | 
					
						
							|  |  |  |     item_name_to_id: Dict[str, int] = _item_name_to_id | 
					
						
							|  |  |  |     location_name_to_id: Dict[str, int] = _loc_name_to_id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # increment this every time something in your world's names/id mappings changes. | 
					
						
							|  |  |  |     # While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be | 
					
						
							|  |  |  |     # retrieved by clients on every connection. | 
					
						
							|  |  |  |     data_version: int = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger: logging.Logger | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class LogStreamInterface: | 
					
						
							|  |  |  |         logger: logging.Logger | 
					
						
							|  |  |  |         buffer: List[str] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, logger: logging.Logger) -> None: | 
					
						
							|  |  |  |             self.logger = logger | 
					
						
							|  |  |  |             self.buffer = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def write(self, msg: str) -> None: | 
					
						
							|  |  |  |             if msg.endswith('\n'): | 
					
						
							|  |  |  |                 self.buffer.append(msg[:-1]) | 
					
						
							|  |  |  |                 self.logger.debug("".join(self.buffer)) | 
					
						
							|  |  |  |                 self.buffer = [] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.buffer.append(msg) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def flush(self) -> None: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     lsi: LogStreamInterface | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     id_to_zz_item: Optional[Dict[int, ZzItem]] = None | 
					
						
							|  |  |  |     zz_system: System | 
					
						
							|  |  |  |     _item_counts: "Counter[str]" = Counter() | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     These are the items counts that will be in the game, | 
					
						
							|  |  |  |     which might be different from the item counts the player asked for in options | 
					
						
							|  |  |  |     (if the player asked for something invalid). | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     my_locations: List[ZillionLocation] = [] | 
					
						
							|  |  |  |     """ This is kind of a cache to avoid iterating through all the multiworld locations in logic. """ | 
					
						
							| 
									
										
										
										
											2022-12-09 17:54:29 -08:00
										 |  |  |     slot_data_ready: threading.Event | 
					
						
							|  |  |  |     """ This event is set in `generate_output` when the data is ready for `fill_slot_data` """ | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, world: MultiWorld, player: int): | 
					
						
							|  |  |  |         super().__init__(world, player) | 
					
						
							|  |  |  |         self.logger = logging.getLogger("Zillion") | 
					
						
							|  |  |  |         self.lsi = ZillionWorld.LogStreamInterface(self.logger) | 
					
						
							|  |  |  |         self.zz_system = System() | 
					
						
							| 
									
										
										
										
											2022-12-09 17:54:29 -08:00
										 |  |  |         self.slot_data_ready = threading.Event() | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def _make_item_maps(self, start_char: Chars) -> None: | 
					
						
							|  |  |  |         _id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char) | 
					
						
							|  |  |  |         self.id_to_zz_item = id_to_zz_item | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2023-02-16 00:28:02 +01:00
										 |  |  |     def stage_assert_generate(cls, multiworld: MultiWorld) -> None: | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |         """Checks that a game is capable of generating, usually checks for some base file like a ROM.
 | 
					
						
							|  |  |  |         Not run for unittests since they don't produce output""" | 
					
						
							|  |  |  |         rom_file = get_base_rom_path() | 
					
						
							|  |  |  |         if not os.path.exists(rom_file): | 
					
						
							|  |  |  |             raise FileNotFoundError(rom_file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_early(self) -> None: | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         if not hasattr(self.multiworld, "zillion_logic_cache"): | 
					
						
							|  |  |  |             setattr(self.multiworld, "zillion_logic_cache", {}) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         zz_op, item_counts = validate(self.multiworld, self.player) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self._item_counts = item_counts | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         rom_dir_name = os.path.dirname(get_base_rom_path()) | 
					
						
							|  |  |  |         with redirect_stdout(self.lsi):  # type: ignore | 
					
						
							|  |  |  |             self.zz_system.make_patcher(rom_dir_name) | 
					
						
							|  |  |  |             self.zz_system.make_randomizer(zz_op) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-01 06:45:17 -07:00
										 |  |  |             self.zz_system.seed(self.multiworld.seed) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |             self.zz_system.make_map() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # just in case the options changed anything (I don't think they do) | 
					
						
							|  |  |  |         assert self.zz_system.randomizer, "init failed" | 
					
						
							|  |  |  |         for zz_name in self.zz_system.randomizer.locations: | 
					
						
							|  |  |  |             if zz_name != 'main': | 
					
						
							|  |  |  |                 assert self.zz_system.randomizer.loc_name_2_pretty[zz_name] in self.location_name_to_id, \ | 
					
						
							|  |  |  |                     f"{self.zz_system.randomizer.loc_name_2_pretty[zz_name]} not in location map" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self._make_item_maps(zz_op.start_char) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_regions(self) -> None: | 
					
						
							|  |  |  |         assert self.zz_system.randomizer, "generate_early hasn't been called" | 
					
						
							|  |  |  |         assert self.id_to_zz_item, "generate_early hasn't been called" | 
					
						
							|  |  |  |         p = self.player | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         w = self.multiworld | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |         self.my_locations = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.zz_system.randomizer.place_canister_gun_reqs() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         start = self.zz_system.randomizer.regions['start'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         all: Dict[str, ZillionRegion] = {} | 
					
						
							|  |  |  |         for here_zz_name, zz_r in self.zz_system.randomizer.regions.items(): | 
					
						
							|  |  |  |             here_name = "Menu" if here_zz_name == "start" else zz_reg_name_to_reg_name(here_zz_name) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:06:43 -06:00
										 |  |  |             all[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |             self.multiworld.regions.append(all[here_name]) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         limited_skill = Req(gun=3, jump=3, skill=self.zz_system.randomizer.options.skill, hp=940, red=1, floppy=126) | 
					
						
							|  |  |  |         queue = deque([start]) | 
					
						
							|  |  |  |         done: Set[str] = set() | 
					
						
							|  |  |  |         while len(queue): | 
					
						
							|  |  |  |             zz_here = queue.popleft() | 
					
						
							|  |  |  |             here_name = "Menu" if zz_here.name == "start" else zz_reg_name_to_reg_name(zz_here.name) | 
					
						
							|  |  |  |             if here_name in done: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             here = all[here_name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for zz_loc in zz_here.locations: | 
					
						
							|  |  |  |                 # if local gun reqs didn't place "keyword" item | 
					
						
							|  |  |  |                 if not zz_loc.item: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     def access_rule_wrapped(zz_loc_local: ZzLocation, | 
					
						
							|  |  |  |                                             p: int, | 
					
						
							|  |  |  |                                             zz_r: ZzRandomizer, | 
					
						
							|  |  |  |                                             id_to_zz_item: Dict[int, ZzItem], | 
					
						
							|  |  |  |                                             cs: CollectionState) -> bool: | 
					
						
							|  |  |  |                         accessible = cs_to_zz_locs(cs, p, zz_r, id_to_zz_item) | 
					
						
							|  |  |  |                         return zz_loc_local in accessible | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     access_rule = functools.partial(access_rule_wrapped, | 
					
						
							|  |  |  |                                                     zz_loc, self.player, self.zz_system.randomizer, self.id_to_zz_item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     loc_name = self.zz_system.randomizer.loc_name_2_pretty[zz_loc.name] | 
					
						
							|  |  |  |                     loc = ZillionLocation(zz_loc, self.player, loc_name, here) | 
					
						
							|  |  |  |                     loc.access_rule = access_rule | 
					
						
							|  |  |  |                     if not (limited_skill >= zz_loc.req): | 
					
						
							|  |  |  |                         loc.progress_type = LocationProgressType.EXCLUDED | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |                         self.multiworld.exclude_locations[p].value.add(loc.name) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |                     here.locations.append(loc) | 
					
						
							|  |  |  |                     self.my_locations.append(loc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for zz_dest in zz_here.connections.keys(): | 
					
						
							|  |  |  |                 dest_name = "Menu" if zz_dest.name == 'start' else zz_reg_name_to_reg_name(zz_dest.name) | 
					
						
							|  |  |  |                 dest = all[dest_name] | 
					
						
							|  |  |  |                 exit = Entrance(p, f"{here_name} to {dest_name}", here) | 
					
						
							|  |  |  |                 here.exits.append(exit) | 
					
						
							|  |  |  |                 exit.connect(dest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 queue.append(zz_dest) | 
					
						
							|  |  |  |             done.add(here.name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_items(self) -> None: | 
					
						
							|  |  |  |         if not self.id_to_zz_item: | 
					
						
							|  |  |  |             self._make_item_maps("JJ") | 
					
						
							|  |  |  |             self.logger.warning("warning: called `create_items` without calling `generate_early` first") | 
					
						
							|  |  |  |         assert self.id_to_zz_item, "failed to get item maps" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # in zilliandomizer, the Randomizer class puts empties in the item pool to fill space, | 
					
						
							|  |  |  |         # but here in AP, empties are in the options from options.validate | 
					
						
							|  |  |  |         item_counts = self._item_counts | 
					
						
							|  |  |  |         self.logger.debug(item_counts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for item_name, item_id in self.item_name_to_id.items(): | 
					
						
							|  |  |  |             zz_item = self.id_to_zz_item[item_id] | 
					
						
							|  |  |  |             if item_id >= (4 + base_id):  # normal item | 
					
						
							|  |  |  |                 if item_name in item_counts: | 
					
						
							|  |  |  |                     count = item_counts[item_name] | 
					
						
							|  |  |  |                     self.logger.debug(f"Zillion Items: {item_name}  {count}") | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |                     self.multiworld.itempool += [self.create_item(item_name) for _ in range(count)] | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |             elif item_id < (3 + base_id) and zz_item.code == RESCUE: | 
					
						
							|  |  |  |                 # One of the 3 rescues will not be in the pool and its zz_item will be 'empty'. | 
					
						
							|  |  |  |                 self.logger.debug(f"Zillion Items: {item_name}  1") | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |                 self.multiworld.itempool.append(self.create_item(item_name)) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def set_rules(self) -> None: | 
					
						
							|  |  |  |         # logic for this game is in create_regions | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_basic(self) -> None: | 
					
						
							|  |  |  |         assert self.zz_system.randomizer, "generate_early hasn't been called" | 
					
						
							|  |  |  |         # main location name is an alias | 
					
						
							|  |  |  |         main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations['main'].name] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         self.multiworld.get_location(main_loc_name, self.player)\ | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |             .place_locked_item(self.create_item("Win")) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         self.multiworld.completion_condition[self.player] = \ | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |             lambda state: state.has("Win", self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-28 12:56:50 -07:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: | 
					
						
							|  |  |  |         # item link pools are about to be created in main | 
					
						
							|  |  |  |         # JJ can't be an item link unless all the players share the same start_char | 
					
						
							|  |  |  |         # (The reason for this is that the JJ ZillionItem will have a different ZzItem depending | 
					
						
							|  |  |  |         #  on whether the start char is Apple or Champ, and the logic depends on that ZzItem.) | 
					
						
							|  |  |  |         for group in multiworld.groups.values(): | 
					
						
							|  |  |  |             # TODO: remove asserts on group when we can specify which members of TypedDict are optional | 
					
						
							|  |  |  |             assert "game" in group | 
					
						
							|  |  |  |             if group["game"] == "Zillion": | 
					
						
							|  |  |  |                 assert "item_pool" in group | 
					
						
							|  |  |  |                 item_pool = group["item_pool"] | 
					
						
							|  |  |  |                 to_stay = "JJ" | 
					
						
							|  |  |  |                 if "JJ" in item_pool: | 
					
						
							|  |  |  |                     assert "players" in group | 
					
						
							|  |  |  |                     group_players = group["players"] | 
					
						
							|  |  |  |                     start_chars = cast(Dict[int, ZillionStartChar], getattr(multiworld, "start_char")) | 
					
						
							|  |  |  |                     players_start_chars = [ | 
					
						
							|  |  |  |                         (player, start_chars[player].get_current_option_name()) | 
					
						
							|  |  |  |                         for player in group_players | 
					
						
							|  |  |  |                     ] | 
					
						
							|  |  |  |                     start_char_counts = Counter(sc for _, sc in players_start_chars) | 
					
						
							|  |  |  |                     # majority rules | 
					
						
							|  |  |  |                     if start_char_counts["Apple"] > start_char_counts["Champ"]: | 
					
						
							|  |  |  |                         to_stay = "Apple" | 
					
						
							|  |  |  |                     elif start_char_counts["Champ"] > start_char_counts["Apple"]: | 
					
						
							|  |  |  |                         to_stay = "Champ" | 
					
						
							|  |  |  |                     else:  # equal | 
					
						
							|  |  |  |                         to_stay = multiworld.random.choice(("Apple", "Champ")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     for p, sc in players_start_chars: | 
					
						
							|  |  |  |                         if sc != to_stay: | 
					
						
							|  |  |  |                             group_players.remove(p) | 
					
						
							|  |  |  |                 assert "world" in group | 
					
						
							|  |  |  |                 cast(ZillionWorld, group["world"])._make_item_maps(to_stay) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |     def post_fill(self) -> None: | 
					
						
							|  |  |  |         """Optional Method that is called after regular fill. Can be used to do adjustments before output generation.
 | 
					
						
							|  |  |  |         This happens before progression balancing,  so the items may not be in their final locations yet."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.zz_system.post_fill() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def finalize_item_locations(self) -> None: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         sync zilliandomizer item locations with AP item locations | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         assert self.zz_system.randomizer and self.zz_system.patcher, "generate_early hasn't been called" | 
					
						
							|  |  |  |         zz_options = self.zz_system.randomizer.options | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # debug_zz_loc_ids: Dict[str, int] = {} | 
					
						
							|  |  |  |         empty = zz_items[4] | 
					
						
							|  |  |  |         multi_item = empty  # a different patcher method differentiates empty from ap multi item | 
					
						
							|  |  |  |         multi_items: Dict[str, Tuple[str, str]] = {}  # zz_loc_name to (item_name, player_name) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         for loc in self.multiworld.get_locations(): | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |             if loc.player == self.player: | 
					
						
							|  |  |  |                 z_loc = cast(ZillionLocation, loc) | 
					
						
							|  |  |  |                 # debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc) | 
					
						
							|  |  |  |                 if z_loc.item is None: | 
					
						
							|  |  |  |                     self.logger.warn("generate_output location has no item - is that ok?") | 
					
						
							|  |  |  |                     z_loc.zz_loc.item = empty | 
					
						
							|  |  |  |                 elif z_loc.item.player == self.player: | 
					
						
							|  |  |  |                     z_item = cast(ZillionItem, z_loc.item) | 
					
						
							|  |  |  |                     z_loc.zz_loc.item = z_item.zz_item | 
					
						
							|  |  |  |                 else:  # another player's item | 
					
						
							|  |  |  |                     # print(f"put multi item in {z_loc.zz_loc.name}") | 
					
						
							|  |  |  |                     z_loc.zz_loc.item = multi_item | 
					
						
							|  |  |  |                     multi_items[z_loc.zz_loc.name] = ( | 
					
						
							|  |  |  |                         z_loc.item.name, | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |                         self.multiworld.get_player_name(z_loc.item.player) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |                     ) | 
					
						
							|  |  |  |         # debug_zz_loc_ids.sort() | 
					
						
							|  |  |  |         # for name, id_ in debug_zz_loc_ids.items(): | 
					
						
							|  |  |  |         #     print(id_) | 
					
						
							|  |  |  |         # print("size:", len(debug_zz_loc_ids)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # debug_loc_to_id: Dict[str, int] = {} | 
					
						
							|  |  |  |         # regions = self.zz_randomizer.regions | 
					
						
							|  |  |  |         # for region in regions.values(): | 
					
						
							|  |  |  |         #     for loc in region.locations: | 
					
						
							|  |  |  |         #         if loc.name not in self.zz_randomizer.locations: | 
					
						
							|  |  |  |         #             print(f"region {region.name} had location {loc.name} not in locations") | 
					
						
							|  |  |  |         #         debug_loc_to_id[loc.name] = id(loc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # verify that every location got an item | 
					
						
							|  |  |  |         for zz_loc in self.zz_system.randomizer.locations.values(): | 
					
						
							|  |  |  |             assert zz_loc.item, ( | 
					
						
							|  |  |  |                 f"location {self.zz_system.randomizer.loc_name_2_pretty[zz_loc.name]} " | 
					
						
							|  |  |  |                 f"in world {self.player} didn't get an item" | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         zz_patcher = self.zz_system.patcher | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         zz_patcher.write_locations(self.zz_system.randomizer.regions, | 
					
						
							|  |  |  |                                    zz_options.start_char, | 
					
						
							|  |  |  |                                    self.zz_system.randomizer.loc_name_2_pretty) | 
					
						
							| 
									
										
										
										
											2022-12-09 17:54:29 -08:00
										 |  |  |         self.slot_data_ready.set() | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |         zz_patcher.all_fixes_and_options(zz_options) | 
					
						
							|  |  |  |         zz_patcher.set_external_item_interface(zz_options.start_char, zz_options.max_level) | 
					
						
							|  |  |  |         zz_patcher.set_multiworld_items(multi_items) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode() | 
					
						
							| 
									
										
										
										
											2022-10-23 09:18:05 -07:00
										 |  |  |         zz_patcher.set_rom_to_ram_data(game_id) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def generate_output(self, output_directory: str) -> None: | 
					
						
							|  |  |  |         """This method gets called from a threadpool, do not use world.random here.
 | 
					
						
							| 
									
										
										
										
											2023-02-03 10:39:18 -08:00
										 |  |  |         If you need any last-second randomization, use MultiWorld.per_slot_randoms[slot] instead."""
 | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |         self.finalize_item_locations() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert self.zz_system.patcher, "didn't get patcher from generate_early" | 
					
						
							|  |  |  |         # original_rom_bytes = self.zz_patcher.rom | 
					
						
							|  |  |  |         patched_rom_bytes = self.zz_system.patcher.get_patched_bytes() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         out_file_base = self.multiworld.get_out_file_name_base(self.player) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         filename = os.path.join( | 
					
						
							|  |  |  |             output_directory, | 
					
						
							|  |  |  |             f'{out_file_base}{ZillionDeltaPatch.result_file_ending}' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         with open(filename, "wb") as binary_file: | 
					
						
							|  |  |  |             binary_file.write(patched_rom_bytes) | 
					
						
							|  |  |  |         patch = ZillionDeltaPatch( | 
					
						
							|  |  |  |             os.path.splitext(filename)[0] + ZillionDeltaPatch.patch_file_ending, | 
					
						
							|  |  |  |             player=self.player, | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |             player_name=self.multiworld.player_name[self.player], | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |             patched_path=filename | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         patch.write() | 
					
						
							|  |  |  |         os.remove(filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def fill_slot_data(self) -> Dict[str, Any]:  # json of WebHostLib.models.Slot | 
					
						
							|  |  |  |         """Fill in the `slot_data` field in the `Connected` network package.
 | 
					
						
							|  |  |  |         This is a way the generator can give custom data to the client. | 
					
						
							|  |  |  |         The client will receive this as JSON in the `Connected` response."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: share a TypedDict data structure with client | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: tell client which canisters are keywords | 
					
						
							|  |  |  |         # so it can open and get those when restoring doors | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         zz_patcher = self.zz_system.patcher | 
					
						
							|  |  |  |         assert zz_patcher, "didn't get patcher from generate_early" | 
					
						
							|  |  |  |         assert self.zz_system.randomizer, "didn't get randomizer from generate_early" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         rescues: Dict[str, Any] = {} | 
					
						
							| 
									
										
										
										
											2022-12-09 17:54:29 -08:00
										 |  |  |         self.slot_data_ready.wait() | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |         for i in (0, 1): | 
					
						
							|  |  |  |             if i in zz_patcher.rescue_locations: | 
					
						
							|  |  |  |                 ri = zz_patcher.rescue_locations[i] | 
					
						
							|  |  |  |                 rescues[str(i)] = { | 
					
						
							|  |  |  |                     "start_char": ri.start_char, | 
					
						
							|  |  |  |                     "room_code": ri.room_code, | 
					
						
							|  |  |  |                     "mask": ri.mask | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             "start_char": self.zz_system.randomizer.options.start_char, | 
					
						
							|  |  |  |             "rescues": rescues, | 
					
						
							|  |  |  |             "loc_mem_to_id": zz_patcher.loc_memory_to_loc_id | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # def modify_multidata(self, multidata: Dict[str, Any]) -> None: | 
					
						
							|  |  |  |     #     """For deeper modification of server multidata.""" | 
					
						
							|  |  |  |     #     # not modifying multidata, just want to call this at the end of the generation process | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |     #     cache = getattr(self.multiworld, "zillion_logic_cache") | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |     #     import sys | 
					
						
							|  |  |  |     #     print(sys.getsizeof(cache)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # end of ordered Main.py calls | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_item(self, name: str) -> Item: | 
					
						
							|  |  |  |         """Create an item for this world type and player.
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         Warning: this may be called with self.multiworld = None, for example by MultiServer"""
 | 
					
						
							| 
									
										
										
										
											2022-10-20 10:41:11 -07:00
										 |  |  |         item_id = _item_name_to_id[name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not self.id_to_zz_item: | 
					
						
							|  |  |  |             self._make_item_maps("JJ") | 
					
						
							|  |  |  |             self.logger.warning("warning: called `create_item` without calling `generate_early` first") | 
					
						
							|  |  |  |         assert self.id_to_zz_item, "failed to get item maps" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         classification = ItemClassification.filler | 
					
						
							|  |  |  |         zz_item = self.id_to_zz_item[item_id] | 
					
						
							|  |  |  |         if zz_item.required: | 
					
						
							|  |  |  |             classification = ItemClassification.progression | 
					
						
							|  |  |  |             if not zz_item.is_progression: | 
					
						
							|  |  |  |                 classification = ItemClassification.progression_skip_balancing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         z_item = ZillionItem(name, classification, item_id, self.player, zz_item) | 
					
						
							|  |  |  |         return z_item | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |         """Called when the item pool needs to be filled with additional items to match location count.""" | 
					
						
							|  |  |  |         return "Empty" |