Zork Grand Inquisitor: Implement New Game (#2539)

Adds Archipelago support for Zork Grand Inquisitor, the 1997 point-and-click PC adventure game.

The client (based on `CommonClient`), on top of its regular Archipelago duties, fully handles the randomization of the game and the monitoring / modification of the game state. No game modding needed at all; the player is ready to play an Archipelago seed if they can play the vanilla game through ScummVM.

The "reverse engineering" (there's likely a better term for this...) of the game is my own original work and I included an MIT license at the root of my world directory.

A PopTracker pack was also created to help people learn the game: https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker
This commit is contained in:
Nicholas Brochu 2024-03-15 12:35:37 -04:00 committed by GitHub
parent e0e9fdd86a
commit 2a8784ef72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 9239 additions and 0 deletions

View File

@ -61,6 +61,7 @@ Currently, the following games are supported:
* TUNIC
* Kirby's Dream Land 3
* Celeste 64
* Zork Grand Inquisitor
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@ -188,6 +188,9 @@
# Zillion
/worlds/zillion/ @beauxq
# Zork Grand Inquisitor
/worlds/zork_grand_inquisitor/ @nbrochu
##################################
## Disabled Unmaintained Worlds ##
##################################

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Serpent.AI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,17 @@
import worlds.LauncherComponents as LauncherComponents
from .world import ZorkGrandInquisitorWorld
def launch_client() -> None:
from .client import main
LauncherComponents.launch_subprocess(main, name="ZorkGrandInquisitorClient")
LauncherComponents.components.append(
LauncherComponents.Component(
"Zork Grand Inquisitor Client",
func=launch_client,
component_type=LauncherComponents.Type.CLIENT
)
)

View File

@ -0,0 +1,188 @@
import asyncio
import CommonClient
import NetUtils
import Utils
from typing import Any, Dict, List, Optional, Set, Tuple
from .data_funcs import item_names_to_id, location_names_to_id, id_to_items, id_to_locations, id_to_goals
from .enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations
from .game_controller import GameController
class ZorkGrandInquisitorCommandProcessor(CommonClient.ClientCommandProcessor):
def _cmd_zork(self) -> None:
"""Attach to an open Zork Grand Inquisitor process."""
result: bool = self.ctx.game_controller.open_process_handle()
if result:
self.ctx.process_attached_at_least_once = True
self.output("Successfully attached to Zork Grand Inquisitor process.")
else:
self.output("Failed to attach to Zork Grand Inquisitor process.")
def _cmd_brog(self) -> None:
"""List received Brog items."""
self.ctx.game_controller.list_received_brog_items()
def _cmd_griff(self) -> None:
"""List received Griff items."""
self.ctx.game_controller.list_received_griff_items()
def _cmd_lucy(self) -> None:
"""List received Lucy items."""
self.ctx.game_controller.list_received_lucy_items()
def _cmd_hotspots(self) -> None:
"""List received Hotspots."""
self.ctx.game_controller.list_received_hotspots()
class ZorkGrandInquisitorContext(CommonClient.CommonContext):
tags: Set[str] = {"AP"}
game: str = "Zork Grand Inquisitor"
command_processor: CommonClient.ClientCommandProcessor = ZorkGrandInquisitorCommandProcessor
items_handling: int = 0b111
want_slot_data: bool = True
item_name_to_id: Dict[str, int] = item_names_to_id()
location_name_to_id: Dict[str, int] = location_names_to_id()
id_to_items: Dict[int, ZorkGrandInquisitorItems] = id_to_items()
id_to_locations: Dict[int, ZorkGrandInquisitorLocations] = id_to_locations()
game_controller: GameController
controller_task: Optional[asyncio.Task]
process_attached_at_least_once: bool
can_display_process_message: bool
def __init__(self, server_address: Optional[str], password: Optional[str]) -> None:
super().__init__(server_address, password)
self.game_controller = GameController(logger=CommonClient.logger)
self.controller_task = None
self.process_attached_at_least_once = False
self.can_display_process_message = True
def run_gui(self) -> None:
from kvui import GameManager
class TextManager(GameManager):
logging_pairs: List[Tuple[str, str]] = [("Client", "Archipelago")]
base_title: str = "Archipelago Zork Grand Inquisitor Client"
self.ui = TextManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super().server_auth(password_requested)
await self.get_username()
await self.send_connect()
def on_package(self, cmd: str, _args: Any) -> None:
if cmd == "Connected":
self.game = self.slot_info[self.slot].game
# Options
self.game_controller.option_goal = id_to_goals()[_args["slot_data"]["goal"]]
self.game_controller.option_deathsanity = _args["slot_data"]["deathsanity"] == 1
self.game_controller.option_grant_missable_location_checks = (
_args["slot_data"]["grant_missable_location_checks"] == 1
)
async def controller(self):
while not self.exit_event.is_set():
await asyncio.sleep(0.1)
# Enqueue Received Item Delta
network_item: NetUtils.NetworkItem
for network_item in self.items_received:
item: ZorkGrandInquisitorItems = self.id_to_items[network_item.item]
if item not in self.game_controller.received_items:
if item not in self.game_controller.received_items_queue:
self.game_controller.received_items_queue.append(item)
# Game Controller Update
if self.game_controller.is_process_running():
self.game_controller.update()
self.can_display_process_message = True
else:
process_message: str
if self.process_attached_at_least_once:
process_message = (
"Lost connection to Zork Grand Inquisitor process. Please restart the game and use the /zork "
"command to reattach."
)
else:
process_message = (
"Please use the /zork command to attach to a running Zork Grand Inquisitor process."
)
if self.can_display_process_message:
CommonClient.logger.info(process_message)
self.can_display_process_message = False
# Send Checked Locations
checked_location_ids: List[int] = list()
while len(self.game_controller.completed_locations_queue) > 0:
location: ZorkGrandInquisitorLocations = self.game_controller.completed_locations_queue.popleft()
location_id: int = self.location_name_to_id[location.value]
checked_location_ids.append(location_id)
await self.send_msgs([
{
"cmd": "LocationChecks",
"locations": checked_location_ids
}
])
# Check for Goal Completion
if self.game_controller.goal_completed:
await self.send_msgs([
{
"cmd": "StatusUpdate",
"status": CommonClient.ClientStatus.CLIENT_GOAL
}
])
def main() -> None:
Utils.init_logging("ZorkGrandInquisitorClient", exception_logger="Client")
async def _main():
ctx: ZorkGrandInquisitorContext = ZorkGrandInquisitorContext(None, None)
ctx.server_task = asyncio.create_task(CommonClient.server_loop(ctx), name="server loop")
ctx.controller_task = asyncio.create_task(ctx.controller(), name="ZorkGrandInquisitorController")
if CommonClient.gui_enabled:
ctx.run_gui()
ctx.run_cli()
await ctx.exit_event.wait()
await ctx.shutdown()
import colorama
colorama.init()
asyncio.run(_main())
colorama.deinit()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,419 @@
from typing import Dict, Tuple, Union
from ..enums import ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems, ZorkGrandInquisitorRegions
entrance_rule_data: Dict[
Tuple[
ZorkGrandInquisitorRegions,
ZorkGrandInquisitorRegions,
],
Union[
Tuple[
Tuple[
Union[
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorRegions,
],
...,
],
...,
],
None,
],
] = {
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR): (
(
ZorkGrandInquisitorItems.SWORD,
ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE,
),
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR,
),
),
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH): (
(
ZorkGrandInquisitorItems.SPELL_REZROV,
ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR,
),
),
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH,
),
),
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES,
),
),
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE): None,
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB,
),
),
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): (
(
ZorkGrandInquisitorItems.SUBWAY_TOKEN,
ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT,
),
),
(ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS): None,
(ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): (
(
ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR,
ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.HADES_SHORE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.DM_LAIR): None,
(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WALKING_CASTLE): (
(
ZorkGrandInquisitorItems.HOTSPOT_BLINDS,
ZorkGrandInquisitorEvents.KNOWS_OBIDIL,
),
),
(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE): (
(
ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR,
ZorkGrandInquisitorItems.SPELL_NARWILE,
ZorkGrandInquisitorEvents.KNOWS_YASTARD,
),
),
(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON): (
(
ZorkGrandInquisitorItems.TOTEM_GRIFF,
ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW,
),
),
(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): None,
(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): None,
(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME): (
(
ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP,
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT,
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN,
ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS,
ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH,
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4,
ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY,
ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS,
ZorkGrandInquisitorRegions.WHITE_HOUSE,
ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions
ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH,
ZorkGrandInquisitorItems.BROGS_GRUE_EGG,
ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT,
ZorkGrandInquisitorItems.BROGS_PLANK,
ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE,
),
),
(ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.CROSSROADS): None,
(ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): (
(
ZorkGrandInquisitorItems.SPELL_IGRAM,
ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS,
),
),
(ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): (
(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR,),
),
(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.GUE_TECH): None,
(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): (
(
ZorkGrandInquisitorItems.STUDENT_ID,
ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE,
),
),
(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS): (
(ZorkGrandInquisitorItems.MAP,),
),
(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.DM_LAIR): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR,
),
),
(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.GUE_TECH): None,
(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.HADES_SHORE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES,
),
),
(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB,
),
),
(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY,
),
),
(ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): (
(
ZorkGrandInquisitorEvents.KNOWS_SNAVIG,
ZorkGrandInquisitorItems.TOTEM_BROG, # Visually hiding this totem is tied to owning it; no choice
),
),
(ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_SHORE): (
(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,),
),
(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): (
(
ZorkGrandInquisitorItems.SPELL_NARWILE,
ZorkGrandInquisitorEvents.KNOWS_YASTARD,
),
),
(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.HADES): None,
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS): (
(ZorkGrandInquisitorItems.MAP,),
),
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.DM_LAIR): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR,
),
),
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH,
),
),
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.HADES): (
(
ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER,
ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS,
ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,
),
),
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB,
),
),
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None,
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): (
(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,),
),
(ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,),
),
(ZorkGrandInquisitorRegions.MENU, ZorkGrandInquisitorRegions.PORT_FOOZLE): None,
(ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): (
(
ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL,
ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS,
ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH,
),
),
(ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): (
(
ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION,
ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS,
ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH,
),
),
(ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): None,
(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.MONASTERY): None,
(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): (
(
ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER,
ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT,
ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER,
ZorkGrandInquisitorItems.SPELL_NARWILE,
ZorkGrandInquisitorEvents.KNOWS_YASTARD,
),
),
(ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.CROSSROADS): (
(
ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE,
ZorkGrandInquisitorItems.ROPE,
ZorkGrandInquisitorItems.HOTSPOT_WELL,
),
),
(ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP): (
(
ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE,
ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL,
),
),
(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, ZorkGrandInquisitorRegions.PORT_FOOZLE): None,
(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): None,
(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN): (
(
ZorkGrandInquisitorItems.TOTEM_LUCY,
ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR,
),
),
(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.ENDGAME): (
(
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4,
ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY,
ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS,
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON,
ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP,
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT,
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN,
ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS,
ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH,
ZorkGrandInquisitorRegions.WHITE_HOUSE,
ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions
ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH,
ZorkGrandInquisitorItems.BROGS_GRUE_EGG,
ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT,
ZorkGrandInquisitorItems.BROGS_PLANK,
ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE,
),
),
(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): None,
(ZorkGrandInquisitorRegions.SPELL_LAB, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): None,
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.CROSSROADS): (
(ZorkGrandInquisitorItems.MAP,),
),
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.DM_LAIR): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR,
),
),
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH,
),
),
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): None,
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.HADES_SHORE): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES,
),
),
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SPELL_LAB): (
(
ZorkGrandInquisitorItems.SWORD,
ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE,
ZorkGrandInquisitorEvents.DAM_DESTROYED,
ZorkGrandInquisitorItems.SPELL_GOLGATEM,
ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM,
),
),
(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(
ZorkGrandInquisitorItems.MAP,
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY,
),
),
(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.CROSSROADS): None,
(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): (
(
ZorkGrandInquisitorItems.SPELL_KENDALL,
ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,
),
),
(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): (
(
ZorkGrandInquisitorItems.SPELL_KENDALL,
ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,
),
),
(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(
ZorkGrandInquisitorItems.SPELL_KENDALL,
ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,
),
),
(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.HADES_SHORE): (
(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,),
),
(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None,
(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): (
(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,),
),
(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): (
(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,),
),
(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.MONASTERY): (
(
ZorkGrandInquisitorItems.SWORD,
ZorkGrandInquisitorEvents.ROPE_GLORFABLE,
ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT,
),
),
(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None,
(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): (
(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,),
),
(ZorkGrandInquisitorRegions.WALKING_CASTLE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None,
(ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None,
(ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.ENDGAME): (
(
ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions
ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH,
ZorkGrandInquisitorItems.BROGS_GRUE_EGG,
ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT,
ZorkGrandInquisitorItems.BROGS_PLANK,
ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE,
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON,
ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP,
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT,
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN,
ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS,
ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH,
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3,
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4,
ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY,
ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS,
),
),
}

View File

@ -0,0 +1,792 @@
from typing import Dict, NamedTuple, Optional, Tuple, Union
from BaseClasses import ItemClassification
from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorTags
class ZorkGrandInquisitorItemData(NamedTuple):
statemap_keys: Optional[Tuple[int, ...]]
archipelago_id: Optional[int]
classification: ItemClassification
tags: Tuple[ZorkGrandInquisitorTags, ...]
maximum_quantity: Optional[int] = 1
ITEM_OFFSET = 9758067000
item_data: Dict[ZorkGrandInquisitorItems, ZorkGrandInquisitorItemData] = {
# Inventory Items
ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: ZorkGrandInquisitorItemData(
statemap_keys=(67,), # Extinguished = 103
archipelago_id=ITEM_OFFSET + 0,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: ZorkGrandInquisitorItemData(
statemap_keys=(68,), # Extinguished = 104
archipelago_id=ITEM_OFFSET + 1,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.BROGS_GRUE_EGG: ZorkGrandInquisitorItemData(
statemap_keys=(70,), # Boiled = 71
archipelago_id=ITEM_OFFSET + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.BROGS_PLANK: ZorkGrandInquisitorItemData(
statemap_keys=(69,),
archipelago_id=ITEM_OFFSET + 3,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: ZorkGrandInquisitorItemData(
statemap_keys=(54,),
archipelago_id=ITEM_OFFSET + 4,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP: ZorkGrandInquisitorItemData(
statemap_keys=(86,),
archipelago_id=ITEM_OFFSET + 5,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH: ZorkGrandInquisitorItemData(
statemap_keys=(84,),
archipelago_id=ITEM_OFFSET + 6,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: ZorkGrandInquisitorItemData(
statemap_keys=(9,),
archipelago_id=ITEM_OFFSET + 7,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: ZorkGrandInquisitorItemData(
statemap_keys=(16,),
archipelago_id=ITEM_OFFSET + 8,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.HAMMER: ZorkGrandInquisitorItemData(
statemap_keys=(23,),
archipelago_id=ITEM_OFFSET + 9,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.HUNGUS_LARD: ZorkGrandInquisitorItemData(
statemap_keys=(55,),
archipelago_id=ITEM_OFFSET + 10,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: ZorkGrandInquisitorItemData(
statemap_keys=(56,),
archipelago_id=ITEM_OFFSET + 11,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.LANTERN: ZorkGrandInquisitorItemData(
statemap_keys=(4,),
archipelago_id=ITEM_OFFSET + 12,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: ZorkGrandInquisitorItemData(
statemap_keys=(88,),
archipelago_id=ITEM_OFFSET + 13,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: ZorkGrandInquisitorItemData(
statemap_keys=(116,), # With fly = 120
archipelago_id=ITEM_OFFSET + 14,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: ZorkGrandInquisitorItemData(
statemap_keys=(117,), # With fly = 121
archipelago_id=ITEM_OFFSET + 15,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: ZorkGrandInquisitorItemData(
statemap_keys=(118,), # With fly = 122
archipelago_id=ITEM_OFFSET + 16,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: ZorkGrandInquisitorItemData(
statemap_keys=(119,), # With fly = 123
archipelago_id=ITEM_OFFSET + 17,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.MAP: ZorkGrandInquisitorItemData(
statemap_keys=(6,),
archipelago_id=ITEM_OFFSET + 18,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.MEAD_LIGHT: ZorkGrandInquisitorItemData(
statemap_keys=(2,),
archipelago_id=ITEM_OFFSET + 19,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.MOSS_OF_MAREILON: ZorkGrandInquisitorItemData(
statemap_keys=(57,),
archipelago_id=ITEM_OFFSET + 20,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.MUG: ZorkGrandInquisitorItemData(
statemap_keys=(35,),
archipelago_id=ITEM_OFFSET + 21,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: ZorkGrandInquisitorItemData(
statemap_keys=(17,),
archipelago_id=ITEM_OFFSET + 22,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: ZorkGrandInquisitorItemData(
statemap_keys=(36,),
archipelago_id=ITEM_OFFSET + 23,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: ZorkGrandInquisitorItemData(
statemap_keys=(3,),
archipelago_id=ITEM_OFFSET + 24,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: ZorkGrandInquisitorItemData(
statemap_keys=(5827,),
archipelago_id=ITEM_OFFSET + 25,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.PROZORK_TABLET: ZorkGrandInquisitorItemData(
statemap_keys=(65,),
archipelago_id=ITEM_OFFSET + 26,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: ZorkGrandInquisitorItemData(
statemap_keys=(53,),
archipelago_id=ITEM_OFFSET + 27,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.ROPE: ZorkGrandInquisitorItemData(
statemap_keys=(83,),
archipelago_id=ITEM_OFFSET + 28,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: ZorkGrandInquisitorItemData(
statemap_keys=(101,), # SNA = 41
archipelago_id=ITEM_OFFSET + 29,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: ZorkGrandInquisitorItemData(
statemap_keys=(102,), # VIG = 48
archipelago_id=ITEM_OFFSET + 30,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.SHOVEL: ZorkGrandInquisitorItemData(
statemap_keys=(49,),
archipelago_id=ITEM_OFFSET + 31,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.SNAPDRAGON: ZorkGrandInquisitorItemData(
statemap_keys=(50,),
archipelago_id=ITEM_OFFSET + 32,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.STUDENT_ID: ZorkGrandInquisitorItemData(
statemap_keys=(39,),
archipelago_id=ITEM_OFFSET + 33,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.SUBWAY_TOKEN: ZorkGrandInquisitorItemData(
statemap_keys=(20,),
archipelago_id=ITEM_OFFSET + 34,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.SWORD: ZorkGrandInquisitorItemData(
statemap_keys=(21,),
archipelago_id=ITEM_OFFSET + 35,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.ZIMDOR_SCROLL: ZorkGrandInquisitorItemData(
statemap_keys=(25,),
archipelago_id=ITEM_OFFSET + 36,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
ZorkGrandInquisitorItems.ZORK_ROCKS: ZorkGrandInquisitorItemData(
statemap_keys=(37,),
archipelago_id=ITEM_OFFSET + 37,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,),
),
# Hotspots
ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: ZorkGrandInquisitorItemData(
statemap_keys=(9116,),
archipelago_id=ITEM_OFFSET + 100 + 0,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: ZorkGrandInquisitorItemData(
statemap_keys=(15434, 15436, 15438, 15440),
archipelago_id=ITEM_OFFSET + 100 + 1,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: ZorkGrandInquisitorItemData(
statemap_keys=(12096,),
archipelago_id=ITEM_OFFSET + 100 + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_BLINDS: ZorkGrandInquisitorItemData(
statemap_keys=(4799,),
archipelago_id=ITEM_OFFSET + 100 + 3,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: ZorkGrandInquisitorItemData(
statemap_keys=(12691, 12692, 12693, 12694, 12695, 12696, 12697, 12698, 12699, 12700, 12701),
archipelago_id=ITEM_OFFSET + 100 + 4,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(12702,),
archipelago_id=ITEM_OFFSET + 100 + 5,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(12909,),
archipelago_id=ITEM_OFFSET + 100 + 6,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(12900,),
archipelago_id=ITEM_OFFSET + 100 + 7,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: ZorkGrandInquisitorItemData(
statemap_keys=(5010,),
archipelago_id=ITEM_OFFSET + 100 + 8,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(9539,),
archipelago_id=ITEM_OFFSET + 100 + 9,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: ZorkGrandInquisitorItemData(
statemap_keys=(19712,),
archipelago_id=ITEM_OFFSET + 100 + 10,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: ZorkGrandInquisitorItemData(
statemap_keys=(2586,),
archipelago_id=ITEM_OFFSET + 100 + 11,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: ZorkGrandInquisitorItemData(
statemap_keys=(11878,),
archipelago_id=ITEM_OFFSET + 100 + 12,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: ZorkGrandInquisitorItemData(
statemap_keys=(11751,),
archipelago_id=ITEM_OFFSET + 100 + 13,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: ZorkGrandInquisitorItemData(
statemap_keys=(15147, 15153),
archipelago_id=ITEM_OFFSET + 100 + 14,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: ZorkGrandInquisitorItemData(
statemap_keys=(1705,),
archipelago_id=ITEM_OFFSET + 100 + 15,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: ZorkGrandInquisitorItemData(
statemap_keys=(1425, 1426),
archipelago_id=ITEM_OFFSET + 100 + 16,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: ZorkGrandInquisitorItemData(
statemap_keys=(13106,),
archipelago_id=ITEM_OFFSET + 100 + 17,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: ZorkGrandInquisitorItemData(
statemap_keys=(13219, 13220, 13221, 13222),
archipelago_id=ITEM_OFFSET + 100 + 18,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: ZorkGrandInquisitorItemData(
statemap_keys=(14327, 14332, 14337, 14342),
archipelago_id=ITEM_OFFSET + 100 + 19,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(12528,),
archipelago_id=ITEM_OFFSET + 100 + 20,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: ZorkGrandInquisitorItemData(
statemap_keys=(12523, 12524, 12525),
archipelago_id=ITEM_OFFSET + 100 + 21,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: ZorkGrandInquisitorItemData(
statemap_keys=(13002,),
archipelago_id=ITEM_OFFSET + 100 + 22,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: ZorkGrandInquisitorItemData(
statemap_keys=(10726,),
archipelago_id=ITEM_OFFSET + 100 + 23,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: ZorkGrandInquisitorItemData(
statemap_keys=(12280,),
archipelago_id=ITEM_OFFSET + 100 + 24,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: ZorkGrandInquisitorItemData(
statemap_keys=(
17694,
17695,
17696,
17697,
18200,
17703,
17704,
17705,
17710,
17711,
17712,
17713,
17714,
17715,
17716,
17722,
17723,
17724,
17725,
17726,
17727
),
archipelago_id=ITEM_OFFSET + 100 + 25,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: ZorkGrandInquisitorItemData(
statemap_keys=(8448, 8449, 8450, 8451, 8452, 8453, 8454, 8455, 8456, 8457, 8458, 8459),
archipelago_id=ITEM_OFFSET + 100 + 26,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: ZorkGrandInquisitorItemData(
statemap_keys=(8446,),
archipelago_id=ITEM_OFFSET + 100 + 27,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_HARRY: ZorkGrandInquisitorItemData(
statemap_keys=(4260,),
archipelago_id=ITEM_OFFSET + 100 + 28,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: ZorkGrandInquisitorItemData(
statemap_keys=(18026,),
archipelago_id=ITEM_OFFSET + 100 + 29,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: ZorkGrandInquisitorItemData(
statemap_keys=(17623,),
archipelago_id=ITEM_OFFSET + 100 + 30,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: ZorkGrandInquisitorItemData(
statemap_keys=(13140,),
archipelago_id=ITEM_OFFSET + 100 + 31,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: ZorkGrandInquisitorItemData(
statemap_keys=(10441,),
archipelago_id=ITEM_OFFSET + 100 + 32,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: ZorkGrandInquisitorItemData(
statemap_keys=(19632, 19627),
archipelago_id=ITEM_OFFSET + 100 + 33,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: ZorkGrandInquisitorItemData(
statemap_keys=(3025,),
archipelago_id=ITEM_OFFSET + 100 + 34,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: ZorkGrandInquisitorItemData(
statemap_keys=(3036,),
archipelago_id=ITEM_OFFSET + 100 + 35,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_MIRROR: ZorkGrandInquisitorItemData(
statemap_keys=(5031,),
archipelago_id=ITEM_OFFSET + 100 + 36,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: ZorkGrandInquisitorItemData(
statemap_keys=(13597,),
archipelago_id=ITEM_OFFSET + 100 + 37,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: ZorkGrandInquisitorItemData(
statemap_keys=(13390,),
archipelago_id=ITEM_OFFSET + 100 + 38,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: ZorkGrandInquisitorItemData(
statemap_keys=(2455, 2447),
archipelago_id=ITEM_OFFSET + 100 + 39,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: ZorkGrandInquisitorItemData(
statemap_keys=(12389, 12390),
archipelago_id=ITEM_OFFSET + 100 + 40,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: ZorkGrandInquisitorItemData(
statemap_keys=(4302,),
archipelago_id=ITEM_OFFSET + 100 + 41,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: ZorkGrandInquisitorItemData(
statemap_keys=(16383, 16384),
archipelago_id=ITEM_OFFSET + 100 + 42,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: ZorkGrandInquisitorItemData(
statemap_keys=(2769,),
archipelago_id=ITEM_OFFSET + 100 + 43,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: ZorkGrandInquisitorItemData(
statemap_keys=(4149,),
archipelago_id=ITEM_OFFSET + 100 + 44,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: ZorkGrandInquisitorItemData(
statemap_keys=(12584, 12585, 12586, 12587),
archipelago_id=ITEM_OFFSET + 100 + 45,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(12574,),
archipelago_id=ITEM_OFFSET + 100 + 46,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(13412,),
archipelago_id=ITEM_OFFSET + 100 + 47,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: ZorkGrandInquisitorItemData(
statemap_keys=(12170,),
archipelago_id=ITEM_OFFSET + 100 + 48,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: ZorkGrandInquisitorItemData(
statemap_keys=(16382,),
archipelago_id=ITEM_OFFSET + 100 + 49,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: ZorkGrandInquisitorItemData(
statemap_keys=(4209,),
archipelago_id=ITEM_OFFSET + 100 + 50,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: ZorkGrandInquisitorItemData(
statemap_keys=(11973,),
archipelago_id=ITEM_OFFSET + 100 + 51,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: ZorkGrandInquisitorItemData(
statemap_keys=(13168,),
archipelago_id=ITEM_OFFSET + 100 + 52,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: ZorkGrandInquisitorItemData(
statemap_keys=(15396,),
archipelago_id=ITEM_OFFSET + 100 + 53,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: ZorkGrandInquisitorItemData(
statemap_keys=(9706,),
archipelago_id=ITEM_OFFSET + 100 + 54,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: ZorkGrandInquisitorItemData(
statemap_keys=(9728, 9729, 9730),
archipelago_id=ITEM_OFFSET + 100 + 55,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
ZorkGrandInquisitorItems.HOTSPOT_WELL: ZorkGrandInquisitorItemData(
statemap_keys=(10314,),
archipelago_id=ITEM_OFFSET + 100 + 56,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.HOTSPOT,),
),
# Spells
ZorkGrandInquisitorItems.SPELL_GLORF: ZorkGrandInquisitorItemData(
statemap_keys=(202,),
archipelago_id=ITEM_OFFSET + 200 + 0,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_GOLGATEM: ZorkGrandInquisitorItemData(
statemap_keys=(192,),
archipelago_id=ITEM_OFFSET + 200 + 1,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_IGRAM: ZorkGrandInquisitorItemData(
statemap_keys=(199,),
archipelago_id=ITEM_OFFSET + 200 + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_KENDALL: ZorkGrandInquisitorItemData(
statemap_keys=(196,),
archipelago_id=ITEM_OFFSET + 200 + 3,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_NARWILE: ZorkGrandInquisitorItemData(
statemap_keys=(197,),
archipelago_id=ITEM_OFFSET + 200 + 4,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_REZROV: ZorkGrandInquisitorItemData(
statemap_keys=(195,),
archipelago_id=ITEM_OFFSET + 200 + 5,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_THROCK: ZorkGrandInquisitorItemData(
statemap_keys=(200,),
archipelago_id=ITEM_OFFSET + 200 + 6,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
ZorkGrandInquisitorItems.SPELL_VOXAM: ZorkGrandInquisitorItemData(
statemap_keys=(191,),
archipelago_id=ITEM_OFFSET + 200 + 7,
classification=ItemClassification.useful,
tags=(ZorkGrandInquisitorTags.SPELL,),
),
# Subway Destinations
ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: ZorkGrandInquisitorItemData(
statemap_keys=(13757, 13297, 13486, 13625),
archipelago_id=ITEM_OFFSET + 300 + 0,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,),
),
ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: ZorkGrandInquisitorItemData(
statemap_keys=(13758, 13309, 13498, 13637),
archipelago_id=ITEM_OFFSET + 300 + 1,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,),
),
ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData(
statemap_keys=(13759, 13316, 13505, 13644),
archipelago_id=ITEM_OFFSET + 300 + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,),
),
# Teleporter Destinations
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR: ZorkGrandInquisitorItemData(
statemap_keys=(2203,),
archipelago_id=ITEM_OFFSET + 400 + 0,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,),
),
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH: ZorkGrandInquisitorItemData(
statemap_keys=(7132,),
archipelago_id=ITEM_OFFSET + 400 + 1,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,),
),
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES: ZorkGrandInquisitorItemData(
statemap_keys=(7119,),
archipelago_id=ITEM_OFFSET + 400 + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,),
),
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData(
statemap_keys=(7148,),
archipelago_id=ITEM_OFFSET + 400 + 3,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,),
),
ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB: ZorkGrandInquisitorItemData(
statemap_keys=(16545,),
archipelago_id=ITEM_OFFSET + 400 + 4,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,),
),
# Totemizer Destinations
ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: ZorkGrandInquisitorItemData(
statemap_keys=(9660,),
archipelago_id=ITEM_OFFSET + 500 + 0,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,),
),
ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: ZorkGrandInquisitorItemData(
statemap_keys=(9666,),
archipelago_id=ITEM_OFFSET + 500 + 1,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,),
),
ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: ZorkGrandInquisitorItemData(
statemap_keys=(9668,),
archipelago_id=ITEM_OFFSET + 500 + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,),
),
ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: ZorkGrandInquisitorItemData(
statemap_keys=(9662,),
archipelago_id=ITEM_OFFSET + 500 + 3,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,),
),
# Totems
ZorkGrandInquisitorItems.TOTEM_BROG: ZorkGrandInquisitorItemData(
statemap_keys=(4853,),
archipelago_id=ITEM_OFFSET + 600 + 0,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TOTEM,),
),
ZorkGrandInquisitorItems.TOTEM_GRIFF: ZorkGrandInquisitorItemData(
statemap_keys=(4315,),
archipelago_id=ITEM_OFFSET + 600 + 1,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TOTEM,),
),
ZorkGrandInquisitorItems.TOTEM_LUCY: ZorkGrandInquisitorItemData(
statemap_keys=(5223,),
archipelago_id=ITEM_OFFSET + 600 + 2,
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.TOTEM,),
),
# Filler
ZorkGrandInquisitorItems.FILLER_INQUISITION_PROPAGANDA_FLYER: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 700 + 0,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.FILLER,),
maximum_quantity=None,
),
ZorkGrandInquisitorItems.FILLER_UNREADABLE_SPELL_SCROLL: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 700 + 1,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.FILLER,),
maximum_quantity=None,
),
ZorkGrandInquisitorItems.FILLER_MAGIC_CONTRABAND: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 700 + 2,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.FILLER,),
maximum_quantity=None,
),
ZorkGrandInquisitorItems.FILLER_FROBOZZ_ELECTRIC_GADGET: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 700 + 3,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.FILLER,),
maximum_quantity=None,
),
ZorkGrandInquisitorItems.FILLER_NONSENSICAL_INQUISITION_PAPERWORK: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 700 + 4,
classification=ItemClassification.filler,
tags=(ZorkGrandInquisitorTags.FILLER,),
maximum_quantity=None,
),
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,200 @@
from typing import Dict, NamedTuple, Optional, Tuple
from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations
class ZorkGrandInquisitorMissableLocationGrantConditionsData(NamedTuple):
location_condition: ZorkGrandInquisitorLocations
item_conditions: Optional[Tuple[ZorkGrandInquisitorItems, ...]]
missable_location_grant_conditions_data: Dict[
ZorkGrandInquisitorLocations, ZorkGrandInquisitorMissableLocationGrantConditionsData
] = {
ZorkGrandInquisitorLocations.BOING_BOING_BOING:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.BONK:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.PROZORKED,
item_conditions=(ZorkGrandInquisitorItems.HAMMER,),
)
,
ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.ARREST_THE_VANDAL,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.MAGIC_FOREVER,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.A_SMALLWAY,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.THAR_SHE_BLOWS,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.CRISIS_AVERTED,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE,
item_conditions=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,),
)
,
ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS,
item_conditions=(ZorkGrandInquisitorItems.SPELL_IGRAM,),
)
,
ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.PROZORKED,
item_conditions=(ZorkGrandInquisitorItems.SPELL_THROCK,),
)
,
ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS,
item_conditions=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,),
)
,
ZorkGrandInquisitorLocations.MEAD_LIGHT:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE,
item_conditions=(ZorkGrandInquisitorItems.MEAD_LIGHT,),
)
,
ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.NO_AUTOGRAPHS:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.NO_BONDAGE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE,
item_conditions=(ZorkGrandInquisitorItems.ROPE,),
)
,
ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.THATS_A_ROPE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE,
item_conditions=(ZorkGrandInquisitorItems.ROPE,),
)
,
ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS,
item_conditions=(ZorkGrandInquisitorItems.SPELL_GLORF,),
)
,
ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE,
item_conditions=(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER,),
)
,
ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG,
item_conditions=None,
)
,
ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO,
item_conditions=(ZorkGrandInquisitorItems.SWORD, ZorkGrandInquisitorItems.HOTSPOT_HARRY),
)
,
ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS,
item_conditions=(ZorkGrandInquisitorItems.SPELL_REZROV,),
)
,
ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY:
ZorkGrandInquisitorMissableLocationGrantConditionsData(
location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE,
item_conditions=None,
)
,
}

View File

@ -0,0 +1,183 @@
from typing import Dict, NamedTuple, Optional, Tuple
from ..enums import ZorkGrandInquisitorRegions
class ZorkGrandInquisitorRegionData(NamedTuple):
exits: Optional[Tuple[ZorkGrandInquisitorRegions, ...]]
region_data: Dict[ZorkGrandInquisitorRegions, ZorkGrandInquisitorRegionData] = {
ZorkGrandInquisitorRegions.CROSSROADS: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.DM_LAIR,
ZorkGrandInquisitorRegions.GUE_TECH,
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE,
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.PORT_FOOZLE,
ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,
ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.DM_LAIR: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR,
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE,
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.DM_LAIR,
ZorkGrandInquisitorRegions.WALKING_CASTLE,
ZorkGrandInquisitorRegions.WHITE_HOUSE,
)
),
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON,
ZorkGrandInquisitorRegions.HADES_BEYOND_GATES,
)
),
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO,
ZorkGrandInquisitorRegions.ENDGAME,
)
),
ZorkGrandInquisitorRegions.ENDGAME: ZorkGrandInquisitorRegionData(exits=None),
ZorkGrandInquisitorRegions.GUE_TECH: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY,
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE,
)
),
ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.GUE_TECH,
ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,
)
),
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.DM_LAIR,
ZorkGrandInquisitorRegions.GUE_TECH,
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.HADES: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.HADES_BEYOND_GATES,
ZorkGrandInquisitorRegions.HADES_SHORE,
)
),
ZorkGrandInquisitorRegions.HADES_BEYOND_GATES: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO,
ZorkGrandInquisitorRegions.HADES,
)
),
ZorkGrandInquisitorRegions.HADES_SHORE: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.DM_LAIR,
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE,
ZorkGrandInquisitorRegions.HADES,
ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,
ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS,
ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.MENU: ZorkGrandInquisitorRegionData(
exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,)
),
ZorkGrandInquisitorRegions.MONASTERY: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.MONASTERY,
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST,
)
),
ZorkGrandInquisitorRegions.PORT_FOOZLE: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP,
)
),
ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP: ZorkGrandInquisitorRegionData(
exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,)
),
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT,
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN,
)
),
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.ENDGAME,
ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST,
)
),
ZorkGrandInquisitorRegions.SPELL_LAB: ZorkGrandInquisitorRegionData(
exits=(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,)
),
ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.DM_LAIR,
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE,
ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY,
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.SPELL_LAB,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.CROSSROADS,
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS,
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY,
)
),
ZorkGrandInquisitorRegions.SUBWAY_MONASTERY: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.HADES_SHORE,
ZorkGrandInquisitorRegions.MONASTERY,
ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS,
ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM,
)
),
ZorkGrandInquisitorRegions.WALKING_CASTLE: ZorkGrandInquisitorRegionData(
exits=(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR,)
),
ZorkGrandInquisitorRegions.WHITE_HOUSE: ZorkGrandInquisitorRegionData(
exits=(
ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR,
ZorkGrandInquisitorRegions.ENDGAME,
)
),
}

View File

@ -0,0 +1,247 @@
from typing import Dict, Set, Tuple, Union
from .data.entrance_rule_data import entrance_rule_data
from .data.item_data import item_data, ZorkGrandInquisitorItemData
from .data.location_data import location_data, ZorkGrandInquisitorLocationData
from .enums import (
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorGoals,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorLocations,
ZorkGrandInquisitorRegions,
ZorkGrandInquisitorTags,
)
def item_names_to_id() -> Dict[str, int]:
return {item.value: data.archipelago_id for item, data in item_data.items()}
def item_names_to_item() -> Dict[str, ZorkGrandInquisitorItems]:
return {item.value: item for item in item_data}
def location_names_to_id() -> Dict[str, int]:
return {
location.value: data.archipelago_id
for location, data in location_data.items()
if data.archipelago_id is not None
}
def location_names_to_location() -> Dict[str, ZorkGrandInquisitorLocations]:
return {
location.value: location
for location, data in location_data.items()
if data.archipelago_id is not None
}
def id_to_goals() -> Dict[int, ZorkGrandInquisitorGoals]:
return {goal.value: goal for goal in ZorkGrandInquisitorGoals}
def id_to_items() -> Dict[int, ZorkGrandInquisitorItems]:
return {data.archipelago_id: item for item, data in item_data.items()}
def id_to_locations() -> Dict[int, ZorkGrandInquisitorLocations]:
return {
data.archipelago_id: location
for location, data in location_data.items()
if data.archipelago_id is not None
}
def item_groups() -> Dict[str, Set[str]]:
groups: Dict[str, Set[str]] = dict()
item: ZorkGrandInquisitorItems
data: ZorkGrandInquisitorItemData
for item, data in item_data.items():
if data.tags is not None:
for tag in data.tags:
groups.setdefault(tag.value, set()).add(item.value)
return {k: v for k, v in groups.items() if len(v)}
def items_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorItems]:
items: Set[ZorkGrandInquisitorItems] = set()
item: ZorkGrandInquisitorItems
data: ZorkGrandInquisitorItemData
for item, data in item_data.items():
if data.tags is not None and tag in data.tags:
items.add(item)
return items
def game_id_to_items() -> Dict[int, ZorkGrandInquisitorItems]:
mapping: Dict[int, ZorkGrandInquisitorItems] = dict()
item: ZorkGrandInquisitorItems
data: ZorkGrandInquisitorItemData
for item, data in item_data.items():
if data.statemap_keys is not None:
for key in data.statemap_keys:
mapping[key] = item
return mapping
def location_groups() -> Dict[str, Set[str]]:
groups: Dict[str, Set[str]] = dict()
tag: ZorkGrandInquisitorTags
for tag in ZorkGrandInquisitorTags:
groups[tag.value] = set()
location: ZorkGrandInquisitorLocations
data: ZorkGrandInquisitorLocationData
for location, data in location_data.items():
if data.tags is not None:
for tag in data.tags:
groups[tag.value].add(location.value)
return {k: v for k, v in groups.items() if len(v)}
def locations_by_region(include_deathsanity: bool = False) -> Dict[
ZorkGrandInquisitorRegions, Set[ZorkGrandInquisitorLocations]
]:
mapping: Dict[ZorkGrandInquisitorRegions, Set[ZorkGrandInquisitorLocations]] = dict()
region: ZorkGrandInquisitorRegions
for region in ZorkGrandInquisitorRegions:
mapping[region] = set()
location: ZorkGrandInquisitorLocations
data: ZorkGrandInquisitorLocationData
for location, data in location_data.items():
if not include_deathsanity and ZorkGrandInquisitorTags.DEATHSANITY in (
data.tags or tuple()
):
continue
mapping[data.region].add(location)
return mapping
def locations_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorLocations]:
location: ZorkGrandInquisitorLocations
data: ZorkGrandInquisitorLocationData
return {location for location, data in location_data.items() if data.tags is not None and tag in data.tags}
def location_access_rule_for(location: ZorkGrandInquisitorLocations, player: int) -> str:
data: ZorkGrandInquisitorLocationData = location_data[location]
if data.requirements is None:
return "lambda state: True"
lambda_string: str = "lambda state: "
i: int
requirement: Union[
Tuple[
Union[
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
],
...,
],
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems
]
for i, requirement in enumerate(data.requirements):
if isinstance(requirement, tuple):
lambda_string += "("
ii: int
sub_requirement: Union[ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems]
for ii, sub_requirement in enumerate(requirement):
lambda_string += f"state.has(\"{sub_requirement.value}\", {player})"
if ii < len(requirement) - 1:
lambda_string += " or "
lambda_string += ")"
else:
lambda_string += f"state.has(\"{requirement.value}\", {player})"
if i < len(data.requirements) - 1:
lambda_string += " and "
return lambda_string
def entrance_access_rule_for(
region_origin: ZorkGrandInquisitorRegions,
region_destination: ZorkGrandInquisitorRegions,
player: int
) -> str:
data: Union[
Tuple[
Tuple[
Union[
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorRegions,
],
...,
],
...,
],
None,
] = entrance_rule_data[(region_origin, region_destination)]
if data is None:
return "lambda state: True"
lambda_string: str = "lambda state: "
i: int
requirement_group: Tuple[
Union[
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorRegions,
],
...,
]
for i, requirement_group in enumerate(data):
lambda_string += "("
ii: int
requirement: Union[
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorRegions,
]
for ii, requirement in enumerate(requirement_group):
requirement_type: Union[
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorRegions,
] = type(requirement)
if requirement_type in (ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems):
lambda_string += f"state.has(\"{requirement.value}\", {player})"
elif requirement_type == ZorkGrandInquisitorRegions:
lambda_string += f"state.can_reach(\"{requirement.value}\", \"Region\", {player})"
if ii < len(requirement_group) - 1:
lambda_string += " and "
lambda_string += ")"
if i < len(data) - 1:
lambda_string += " or "
return lambda_string

View File

@ -0,0 +1,102 @@
# Zork Grand Inquisitor
## Where is the options page?
The [player options page for this game](../player-options) contains all the options you need to configure and export a
configuration file.
## Is a tracker available for this game?
Yes! You can download the latest PopTracker pack for Zork Grand Inquisitor [here](https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker/releases/latest).
## What does randomization do to this game?
A majority of inventory items you can normally pick up are completely removed from the game (e.g. the lantern won't be
in the crate, the mead won't be at the fish market, etc.). Instead, these items will be distributed in the multiworld.
This means that you can expect to access areas and be in a position to solve certain puzzles in a completely different
order than you normally would.
Subway, teleporter and totemizer destinations are initially locked and need to be unlocked by receiving the
corresponding item in the multiworld. This alone enables creative routing in a game that would otherwise be rather
linear. The Crossroads destination is always unlocked for both the subway and teleporter to prevent softlocks. Until you
receive your first totemizer destination, it will be locked to Newark, New Jersey.
Important hotspots are also randomized. This means that you will be unable to interact with certain objects until you
receive the corresponding item in the multiworld. This can be a bit confusing at first, but it adds depth to the
randomization and makes the game more interesting to play.
You can travel back to the surface without dying by looking inside the bucket. This will work as long as the rope is
still attached to the well.
Attempting to cast VOXAM will teleport you back to the Crossroads. Fast Travel!
## What item types are distributed in the multiworld?
- Inventory items
- Pouch of Zorkmids
- Spells
- Totems
- Subway destinations
- Teleporter destinations
- Totemizer destinations
- Hotspots (with option to start with the items enabling them instead if you prefer not playing with the randomization
of hotspots)
## When the player receives an item, what happens?
- **Inventory items**: Directly added to the player's inventory.
- **Pouch of Zorkmids**: Appears on the inventory screen. The player can then pick up Zorkmid coins from it.
- **Spells**: Learned and directly added to the spell book.
- **Totems**: Appears on the inventory screen.
- **Subway destinations**: The destination button on the subway map becomes functional.
- **Teleporter destinations**: The destination can show up on the teleporter screen.
- **Totemizer destinations**: The destination button on the panel becomes functional.
- **Hotspots**: The hotspot becomes interactable.
## What is considered a location check in Zork Grand Inquisitor?
- Solving puzzles
- Accessing certain areas for the first time
- Triggering certain interactions, even if they aren't puzzles per se
- Dying in unique ways (Optional; Deathsanity option)
## The location check names are fun but don't always convey well what's needed to unlock them. Is there a guide?
Yes! You can find a complete guide for the location checks [here](https://gist.github.com/nbrochu/f7bed7a1fef4e2beb67ad6ddbf18b970).
## What is the victory condition?
Victory is achieved when the 3 artifacts of magic are retrieved and placed inside the walking castle.
## Can I use the save system without a problem?
Absolutely! The save system is fully supported (and its use is in fact strongly encouraged!). You can save and load your
game as you normally would and the client will automatically sync your items and hotspots with what you should have in
that game state.
Depending on how your game progresses, there's a chance that certain location checks might become missable. This
presents an excellent opportunity to utilize the save system. Simply make it a habit to save before undertaking
irreversible actions, ensuring you can revert to a previous state if necessary. If you prefer not to depend on the save
system for accessing missable location checks, there's an option to automatically unlock them as they become
unavailable.
## Unique Local Commands
The following commands are only available when using the Zork Grand Inquisitor Client to play the game with Archipelago.
- `/zork` Attempts to attach to a running instance of Zork Grand Inquisitor. If successful, the client will then be able
to read and control the state of the game.
- `/brog` Lists received items for Brog.
- `/griff` Lists received items for Griff.
- `/lucy` Lists received items for Lucy.
- `/hotspots` Lists received hotspots.
## Known issues
- You will get a second rope right after using GLORF (one in your inventory and one on your cursor). This is a harmless
side effect that will go away after you store it in your inventory as duplicates are actively removed.
- After climbing up to the Monastery for the first time, a rope will forever remain in place in the vent. When you come
back to the Monastery, you will be able to climb up without needing to combine the sword and rope again. However, when
arriving at the top, you will receive a duplicate sword on a rope. This is a harmless side effect that will go away
after you store it in your inventory as duplicates are actively removed.
- Since the client is reading and manipulating the game's memory, rare game crashes can happen. If you encounter one,
simply restart the game, load your latest save and use the `/zork` command again in the client. Nothing will be lost.

View File

@ -0,0 +1,42 @@
# Zork Grand Inquisitor Randomizer Setup Guide
## Requirements
- Windows OS (Hard required. Client is using memory reading / writing through Win32 API)
- A copy of Zork Grand Inquisitor. Only the GOG version is supported. The Steam version can work with some tinkering but
is not officially supported.
- ScummVM 2.7.1 64-bit (Important: Will not work with any other version. [Direct Download](https://downloads.scummvm.org/frs/scummvm/2.7.1/scummvm-2.7.1-win32-x86_64.zip))
- Archipelago 0.4.4+
## Game Setup Instructions
No game modding is required to play Zork Grand Inquisitor with Archipelago. The client does all the work by attaching to
the game process and reading and manipulating the game state in real-time.
This being said, the game does need to be played through ScummVM 2.7.1, so some configuration is required around that.
### GOG
- Open the directory where you installed Zork Grand Inquisitor. You should see a `Launch Zork Grand Inquisitor`
shortcut.
- Open the `scummvm` directory. Delete the entire contents of that directory.
- Still inside the `scummvm` directory, unzip the contents of the ScummVM 2.7.1 zip file you downloaded earlier.
- Go back to the directory where you installed Zork Grand Inquisitor.
- Verify that the game still launches when using the `Launch Zork Grand Inquisitor` shortcut.
- Your game is now ready to be played with Archipelago. From now on, you can use the `Launch Zork Grand Inquisitor`
shortcut to launch the game.
## Joining a Multiworld Game
- Launch Zork Grand Inquisitor and start a new game.
- Open the Archipelago Launcher and click `Zork Grand Inquisitor Client`.
- Using the `Zork Grand Inquisitor Client`:
- Enter the room's hostname and port number (e.g. `archipelago.gg:54321`) in the top box and press `Connect`.
- Input your player name at the bottom when prompted and press `Enter`.
- You should now be connected to the Archipelago room.
- Next, input `/zork` at the bottom and press `Enter`. This will attach the client to the game process.
- If the command is successful, you are now ready to play Zork Grand Inquisitor with Archipelago.
## Continuing a Multiworld Game
- Perform the same steps as above, but instead of starting a new game, load your latest save file.

View File

@ -0,0 +1,350 @@
import enum
class ZorkGrandInquisitorEvents(enum.Enum):
CHARON_CALLED = "Event: Charon Called"
CIGAR_ACCESSIBLE = "Event: Cigar Accessible"
DALBOZ_LOCKER_OPENABLE = "Event: Dalboz Locker Openable"
DAM_DESTROYED = "Event: Dam Destroyed"
DOOR_DRANK_MEAD = "Event: Door Drank Mead"
DOOR_SMOKED_CIGAR = "Event: Door Smoked Cigar"
DUNCE_LOCKER_OPENABLE = "Event: Dunce Locker Openable"
HAS_REPAIRABLE_OBIDIL = "Event: Has Repairable OBIDIL"
HAS_REPAIRABLE_SNAVIG = "Event: Has Repairable SNAVIG"
KNOWS_BEBURTT = "Event: Knows BEBURTT"
KNOWS_OBIDIL = "Event: Knows OBIDIL"
KNOWS_SNAVIG = "Event: Knows SNAVIG"
KNOWS_YASTARD = "Event: Knows YASTARD"
LANTERN_DALBOZ_ACCESSIBLE = "Event: Lantern (Dalboz) Accessible"
ROPE_GLORFABLE = "Event: Rope GLORFable"
VICTORY = "Victory"
WHITE_HOUSE_LETTER_MAILABLE = "Event: White House Letter Mailable"
ZORKMID_BILL_ACCESSIBLE = "Event: 500 Zorkmid Bill Accessible"
ZORK_ROCKS_ACTIVATED = "Event: Zork Rocks Activated"
ZORK_ROCKS_SUCKABLE = "Event: Zork Rocks Suckable"
class ZorkGrandInquisitorGoals(enum.Enum):
THREE_ARTIFACTS = 0
class ZorkGrandInquisitorItems(enum.Enum):
BROGS_BICKERING_TORCH = "Brog's Bickering Torch"
BROGS_FLICKERING_TORCH = "Brog's Flickering Torch"
BROGS_GRUE_EGG = "Brog's Grue Egg"
BROGS_PLANK = "Brog's Plank"
FILLER_FROBOZZ_ELECTRIC_GADGET = "Frobozz Electric Gadget"
FILLER_INQUISITION_PROPAGANDA_FLYER = "Inquisition Propaganda Flyer"
FILLER_MAGIC_CONTRABAND = "Magic Contraband"
FILLER_NONSENSICAL_INQUISITION_PAPERWORK = "Nonsensical Inquisition Paperwork"
FILLER_UNREADABLE_SPELL_SCROLL = "Unreadable Spell Scroll"
FLATHEADIA_FUDGE = "Flatheadia Fudge"
GRIFFS_AIR_PUMP = "Griff's Air Pump"
GRIFFS_DRAGON_TOOTH = "Griff's Dragon Tooth"
GRIFFS_INFLATABLE_RAFT = "Griff's Inflatable Raft"
GRIFFS_INFLATABLE_SEA_CAPTAIN = "Griff's Inflatable Sea Captain"
HAMMER = "Hammer"
HOTSPOT_666_MAILBOX = "Hotspot: 666 Mailbox"
HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS = "Hotspot: Alpine's Quandry Card Slots"
HOTSPOT_BLANK_SCROLL_BOX = "Hotspot: Blank Scroll Box"
HOTSPOT_BLINDS = "Hotspot: Blinds"
HOTSPOT_CANDY_MACHINE_BUTTONS = "Hotspot: Candy Machine Buttons"
HOTSPOT_CANDY_MACHINE_COIN_SLOT = "Hotspot: Candy Machine Coin Slot"
HOTSPOT_CANDY_MACHINE_VACUUM_SLOT = "Hotspot: Candy Machine Vacuum Slot"
HOTSPOT_CHANGE_MACHINE_SLOT = "Hotspot: Change Machine Slot"
HOTSPOT_CLOSET_DOOR = "Hotspot: Closet Door"
HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT = "Hotspot: Closing the Time Tunnels Hammer Slot"
HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER = "Hotspot: Closing the Time Tunnels Lever"
HOTSPOT_COOKING_POT = "Hotspot: Cooking Pot"
HOTSPOT_DENTED_LOCKER = "Hotspot: Dented Locker"
HOTSPOT_DIRT_MOUND = "Hotspot: Dirt Mound"
HOTSPOT_DOCK_WINCH = "Hotspot: Dock Winch"
HOTSPOT_DRAGON_CLAW = "Hotspot: Dragon Claw"
HOTSPOT_DRAGON_NOSTRILS = "Hotspot: Dragon Nostrils"
HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE = "Hotspot: Dungeon Master's Lair Entrance"
HOTSPOT_FLOOD_CONTROL_BUTTONS = "Hotspot: Flood Control Buttons"
HOTSPOT_FLOOD_CONTROL_DOORS = "Hotspot: Flood Control Doors"
HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT = "Hotspot: Frozen Treat Machine Coin Slot"
HOTSPOT_FROZEN_TREAT_MACHINE_DOORS = "Hotspot: Frozen Treat Machine Doors"
HOTSPOT_GLASS_CASE = "Hotspot: Glass Case"
HOTSPOT_GRAND_INQUISITOR_DOLL = "Hotspot: Grand Inquisitor Doll"
HOTSPOT_GUE_TECH_DOOR = "Hotspot: GUE Tech Door"
HOTSPOT_GUE_TECH_GRASS = "Hotspot: GUE Tech Grass"
HOTSPOT_HADES_PHONE_BUTTONS = "Hotspot: Hades Phone Buttons"
HOTSPOT_HADES_PHONE_RECEIVER = "Hotspot: Hades Phone Receiver"
HOTSPOT_HARRY = "Hotspot: Harry"
HOTSPOT_HARRYS_ASHTRAY = "Hotspot: Harry's Ashtray"
HOTSPOT_HARRYS_BIRD_BATH = "Hotspot: Harry's Bird Bath"
HOTSPOT_IN_MAGIC_WE_TRUST_DOOR = "Hotspot: In Magic We Trust Door"
HOTSPOT_JACKS_DOOR = "Hotspot: Jack's Door"
HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS = "Hotspot: Loudspeaker Volume Buttons"
HOTSPOT_MAILBOX_DOOR = "Hotspot: Mailbox Door"
HOTSPOT_MAILBOX_FLAG = "Hotspot: Mailbox Flag"
HOTSPOT_MIRROR = "Hotspot: Mirror"
HOTSPOT_MONASTERY_VENT = "Hotspot: Monastery Vent"
HOTSPOT_MOSSY_GRATE = "Hotspot: Mossy Grate"
HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR = "Hotspot: Port Foozle Past Tavern Door"
HOTSPOT_PURPLE_WORDS = "Hotspot: Purple Words"
HOTSPOT_QUELBEE_HIVE = "Hotspot: Quelbee Hive"
HOTSPOT_ROPE_BRIDGE = "Hotspot: Rope Bridge"
HOTSPOT_SKULL_CAGE = "Hotspot: Skull Cage"
HOTSPOT_SNAPDRAGON = "Hotspot: Snapdragon"
HOTSPOT_SODA_MACHINE_BUTTONS = "Hotspot: Soda Machine Buttons"
HOTSPOT_SODA_MACHINE_COIN_SLOT = "Hotspot: Soda Machine Coin Slot"
HOTSPOT_SOUVENIR_COIN_SLOT = "Hotspot: Souvenir Coin Slot"
HOTSPOT_SPELL_CHECKER = "Hotspot: Spell Checker"
HOTSPOT_SPELL_LAB_CHASM = "Hotspot: Spell Lab Chasm"
HOTSPOT_SPRING_MUSHROOM = "Hotspot: Spring Mushroom"
HOTSPOT_STUDENT_ID_MACHINE = "Hotspot: Student ID Machine"
HOTSPOT_SUBWAY_TOKEN_SLOT = "Hotspot: Subway Token Slot"
HOTSPOT_TAVERN_FLY = "Hotspot: Tavern Fly"
HOTSPOT_TOTEMIZER_SWITCH = "Hotspot: Totemizer Switch"
HOTSPOT_TOTEMIZER_WHEELS = "Hotspot: Totemizer Wheels"
HOTSPOT_WELL = "Hotspot: Well"
HUNGUS_LARD = "Hungus Lard"
JAR_OF_HOTBUGS = "Jar of Hotbugs"
LANTERN = "Lantern"
LARGE_TELEGRAPH_HAMMER = "Large Telegraph Hammer"
LUCYS_PLAYING_CARD_1 = "Lucy's Playing Card: 1 Pip"
LUCYS_PLAYING_CARD_2 = "Lucy's Playing Card: 2 Pips"
LUCYS_PLAYING_CARD_3 = "Lucy's Playing Card: 3 Pips"
LUCYS_PLAYING_CARD_4 = "Lucy's Playing Card: 4 Pips"
MAP = "Map"
MEAD_LIGHT = "Mead Light"
MOSS_OF_MAREILON = "Moss of Mareilon"
MUG = "Mug"
OLD_SCRATCH_CARD = "Old Scratch Card"
PERMA_SUCK_MACHINE = "Perma-Suck Machine"
PLASTIC_SIX_PACK_HOLDER = "Plastic Six-Pack Holder"
POUCH_OF_ZORKMIDS = "Pouch of Zorkmids"
PROZORK_TABLET = "Prozork Tablet"
QUELBEE_HONEYCOMB = "Quelbee Honeycomb"
ROPE = "Rope"
SCROLL_FRAGMENT_ANS = "Scroll Fragment: ANS"
SCROLL_FRAGMENT_GIV = "Scroll Fragment: GIV"
SHOVEL = "Shovel"
SNAPDRAGON = "Snapdragon"
SPELL_GLORF = "Spell: GLORF"
SPELL_GOLGATEM = "Spell: GOLGATEM"
SPELL_IGRAM = "Spell: IGRAM"
SPELL_KENDALL = "Spell: KENDALL"
SPELL_NARWILE = "Spell: NARWILE"
SPELL_REZROV = "Spell: REZROV"
SPELL_THROCK = "Spell: THROCK"
SPELL_VOXAM = "Spell: VOXAM"
STUDENT_ID = "Student ID"
SUBWAY_DESTINATION_FLOOD_CONTROL_DAM = "Subway Destination: Flood Control Dam #3"
SUBWAY_DESTINATION_HADES = "Subway Destination: Hades"
SUBWAY_DESTINATION_MONASTERY = "Subway Destination: Monastery"
SUBWAY_TOKEN = "Subway Token"
SWORD = "Sword"
TELEPORTER_DESTINATION_DM_LAIR = "Teleporter Destination: Dungeon Master's Lair"
TELEPORTER_DESTINATION_GUE_TECH = "Teleporter Destination: GUE Tech"
TELEPORTER_DESTINATION_HADES = "Teleporter Destination: Hades"
TELEPORTER_DESTINATION_MONASTERY = "Teleporter Destination: Monastery Station"
TELEPORTER_DESTINATION_SPELL_LAB = "Teleporter Destination: Spell Lab"
TOTEM_BROG = "Totem: Brog"
TOTEM_GRIFF = "Totem: Griff"
TOTEM_LUCY = "Totem: Lucy"
TOTEMIZER_DESTINATION_HALL_OF_INQUISITION = "Totemizer Destination: Hall of Inquisition"
TOTEMIZER_DESTINATION_INFINITY = "Totemizer Destination: Infinity"
TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL = "Totemizer Destination: Straight to Hell"
TOTEMIZER_DESTINATION_SURFACE_OF_MERZ = "Totemizer Destination: Surface of Merz"
ZIMDOR_SCROLL = "ZIMDOR Scroll"
ZORK_ROCKS = "Zork Rocks"
class ZorkGrandInquisitorLocations(enum.Enum):
ALARM_SYSTEM_IS_DOWN = "Alarm System is Down"
ARREST_THE_VANDAL = "Arrest the Vandal!"
ARTIFACTS_EXPLAINED = "Artifacts, Explained"
A_BIG_FAT_SASSY_2_HEADED_MONSTER = "A Big, Fat, SASSY 2-Headed Monster"
A_LETTER_FROM_THE_WHITE_HOUSE = "A Letter from the White House"
A_SMALLWAY = "A Smallway"
BEAUTIFUL_THATS_PLENTY = "Beautiful, That's Plenty!"
BEBURTT_DEMYSTIFIED = "BEBURTT, Demystified"
BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES = "Better Spell Manufacturing in Under 10 Minutes"
BOING_BOING_BOING = "Boing, Boing, Boing"
BONK = "Bonk!"
BRAVE_SOULS_WANTED = "Brave Souls Wanted"
BROG_DO_GOOD = "Brog Do Good!"
BROG_EAT_ROCKS = "Brog Eat Rocks"
BROG_KNOW_DUMB_THAT_DUMB = "Brog Know Dumb. That Dumb"
BROG_MUCH_BETTER_AT_THIS_GAME = "Brog Much Better at This Game"
CASTLE_WATCHING_A_FIELD_GUIDE = "Castle Watching: A Field Guide"
CAVES_NOTES = "Cave's Notes"
CLOSING_THE_TIME_TUNNELS = "Closing the Time Tunnels"
CRISIS_AVERTED = "Crisis Averted"
CUT_THAT_OUT_YOU_LITTLE_CREEP = "Cut That Out You Little Creep!"
DEATH_ARRESTED_WITH_JACK = "Death: Arrested With Jack"
DEATH_ATTACKED_THE_QUELBEES = "Death: Attacked the Quelbees"
DEATH_CLIMBED_OUT_OF_THE_WELL = "Death: Climbed Out of the Well"
DEATH_EATEN_BY_A_GRUE = "Death: Eaten by a Grue"
DEATH_JUMPED_IN_BOTTOMLESS_PIT = "Death: Jumped in Bottomless Pit"
DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER = "Death: Lost Game of Strip Grue, Fire, Water"
DEATH_LOST_SOUL_TO_OLD_SCRATCH = "Death: Lost Soul to Old Scratch"
DEATH_OUTSMARTED_BY_THE_QUELBEES = "Death: Outsmarted by the Quelbees"
DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD = "Death: Sliced up by the Invisible Guard"
DEATH_STEPPED_INTO_THE_INFINITE = "Death: Step Into the Infinite"
DEATH_SWALLOWED_BY_A_DRAGON = "Death: Swallowed by a Dragon"
DEATH_THROCKED_THE_GRASS = "Death: THROCKed the Grass"
DEATH_TOTEMIZED = "Death: Totemized?"
DEATH_TOTEMIZED_PERMANENTLY = "Death: Totemized... Permanently"
DEATH_YOURE_NOT_CHARON = "Death: You're Not Charon!?"
DEATH_ZORK_ROCKS_EXPLODED = "Death: Zork Rocks Exploded"
DENIED_BY_THE_LAKE_MONSTER = "Denied by the Lake Monster"
DESPERATELY_SEEKING_TUTOR = "Desperately Seeking Tutor"
DONT_EVEN_START_WITH_US_SPARKY = "Don't Even Start With Us, Sparky"
DOOOOOOWN = "Doooooown"
DOWN = "Down"
DRAGON_ARCHIPELAGO_TIME_TUNNEL = "Dragon Archipelago Time Tunnel"
DUNCE_LOCKER = "Dunce Locker"
EGGPLANTS = "Eggplants"
ELSEWHERE = "Elsewhere"
EMERGENCY_MAGICATRONIC_MESSAGE = "Emergency Magicatronic Message"
ENJOY_YOUR_TRIP = "Enjoy Your Trip!"
FAT_LOT_OF_GOOD_THATLL_DO_YA = "Fat Lot of Good That'll Do Ya"
FIRE_FIRE = "Fire! Fire!"
FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE = "Flood Control Dam #3: The Not Remotely Boring Tale"
FLYING_SNAPDRAGON = "Flying Snapdragon"
FROBUARY_3_UNDERGROUNDHOG_DAY = "Frobruary 3 - Undergroundhog Day"
GETTING_SOME_CHANGE = "Getting Some Change"
GO_AWAY = "GO AWAY!"
GUE_TECH_DEANS_LIST = "GUE Tech Dean's List"
GUE_TECH_ENTRANCE_EXAM = "GUE Tech Entrance Exam"
GUE_TECH_HEALTH_MEMO = "GUE Tech Health Memo"
GUE_TECH_MAGEMEISTERS = "GUE Tech Magemeisters"
HAVE_A_HELL_OF_A_DAY = "Have a Hell of a Day!"
HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING = "Hello, This is Shona from Gurth Publishing"
HELP_ME_CANT_BREATHE = "Help... Me. Can't... Breathe"
HEY_FREE_DIRT = "Hey, Free Dirt!"
HI_MY_NAME_IS_DOUG = "Hi, My Name is Doug"
HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING = "Hmmm. Informative. Yet Deeply Disturbing"
HOLD_ON_FOR_AN_IMPORTANT_MESSAGE = "Hold on for an Important Message"
HOW_TO_HYPNOTIZE_YOURSELF = "How to Hypnotize Yourself"
HOW_TO_WIN_AT_DOUBLE_FANUCCI = "How to Win at Double Fanucci"
IMBUE_BEBURTT = "Imbue BEBURTT"
IM_COMPLETELY_NUDE = "I'm Completely Nude"
INTO_THE_FOLIAGE = "Into the Foliage"
INVISIBLE_FLOWERS = "Invisible Flowers"
IN_CASE_OF_ADVENTURE = "In Case of Adventure, Break Glass!"
IN_MAGIC_WE_TRUST = "In Magic We Trust"
ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN = "It's One of Those Adventurers Again..."
I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY = "I Don't Think You Would've Wanted That to Work Anyway"
I_DONT_WANT_NO_TROUBLE = "I Don't Want No Trouble!"
I_HOPE_YOU_CAN_CLIMB_UP_THERE = "I Hope You Can Climb Up There With All This Junk"
I_LIKE_YOUR_STYLE = "I Like Your Style!"
I_SPIT_ON_YOUR_FILTHY_COINAGE = "I Spit on Your Filthy Coinage"
LIT_SUNFLOWERS = "Lit Sunflowers"
MAGIC_FOREVER = "Magic Forever!"
MAILED_IT_TO_HELL = "Mailed it to Hell"
MAKE_LOVE_NOT_WAR = "Make Love, Not War"
MEAD_LIGHT = "Mead Light?"
MIKES_PANTS = "Mike's Pants"
MUSHROOM_HAMMERED = "Mushroom, Hammered"
NATIONAL_TREASURE = "300 Year Old National Treasure"
NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR = "Natural and Supernatural Creatures of Quendor"
NOOOOOOOOOOOOO = "NOOOOOOOOOOOOO!"
NOTHIN_LIKE_A_GOOD_STOGIE = "Nothin' Like a Good Stogie"
NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT = "Now You Look Like Us, Which is an Improvement"
NO_AUTOGRAPHS = "No Autographs"
NO_BONDAGE = "No Bondage"
OBIDIL_DRIED_UP = "OBIDIL, Dried Up"
OH_DEAR_GOD_ITS_A_DRAGON = "Oh Dear God, It's a Dragon!"
OH_VERY_FUNNY_GUYS = "Oh, Very Funny Guys"
OH_WOW_TALK_ABOUT_DEJA_VU = "Oh, Wow! Talk About Deja Vu"
OLD_SCRATCH_WINNER = "Old Scratch Winner!"
ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES = "Only You Can Prevent Foozle Fires"
OPEN_THE_GATES_OF_HELL = "Open the Gates of Hell"
OUTSMART_THE_QUELBEES = "Outsmart the Quelbees"
PERMASEAL = "PermaSeal"
PLANETFALL = "Planetfall"
PLEASE_DONT_THROCK_THE_GRASS = "Please Don't THROCK the Grass"
PORT_FOOZLE_TIME_TUNNEL = "Port Foozle Time Tunnel"
PROZORKED = "Prozorked"
REASSEMBLE_SNAVIG = "Reassemble SNAVIG"
RESTOCKED_ON_GRUESDAY = "Restocked on Gruesday"
RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE = "Right. Hello. Yes. Uh, This is Sneffle"
RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE = "Right. Uh, Sorry. It's Me Again. Sneffle"
SNAVIG_REPAIRED = "SNAVIG, Repaired"
SOUVENIR = "Souvenir"
STRAIGHT_TO_HELL = "Straight to Hell"
STRIP_GRUE_FIRE_WATER = "Strip Grue, Fire, Water"
SUCKING_ROCKS = "Sucking Rocks"
TALK_TO_ME_GRAND_INQUISITOR = "Talk to Me Grand Inquisitor"
TAMING_YOUR_SNAPDRAGON = "Taming Your Snapdragon"
THAR_SHE_BLOWS = "Thar She Blows!"
THATS_A_ROPE = "That's a Rope"
THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS = "That's it! Just Keep Hitting Those Buttons"
THATS_STILL_A_ROPE = "That's Still a Rope"
THATS_THE_SPIRIT = "That's the Spirit!"
THE_ALCHEMICAL_DEBACLE = "The Alchemical Debacle"
THE_ENDLESS_FIRE = "The Endless Fire"
THE_FLATHEADIAN_FUDGE_FIASCO = "The Flatheadian Fudge Fiasco"
THE_PERILS_OF_MAGIC = "The Perils of Magic"
THE_UNDERGROUND_UNDERGROUND = "The Underground Underground"
THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE = "This Doesn't Look Anything Like the Brochure"
THROCKED_MUSHROOM_HAMMERED = "THROCKed Mushroom, Hammered"
TIME_TRAVEL_FOR_DUMMIES = "Time Travel for Dummies"
TOTEMIZED_DAILY_BILLBOARD = "Totemized Daily Billboard Functioning Correctly"
UH_OH_BROG_CANT_SWIM = "Uh-Oh. Brog Can't Swim"
UMBRELLA_FLOWERS = "Umbrella Flowers"
UP = "Up"
USELESS_BUT_FUN = "Useless, But Fun"
UUUUUP = "Uuuuup"
VOYAGE_OF_CAPTAIN_ZAHAB = "Voyage of Captain Zahab"
WANT_SOME_RYE_COURSE_YA_DO = "Want Some Rye? Course Ya Do!"
WE_DONT_SERVE_YOUR_KIND_HERE = "We Don't Serve Your Kind Here"
WE_GOT_A_HIGH_ROLLER = "We Got a High Roller!"
WHAT_ARE_YOU_STUPID = "What Are You, Stupid?"
WHITE_HOUSE_TIME_TUNNEL = "White House Time Tunnel"
WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE = "Wow! I've Never Gone Inside Him Before!"
YAD_GOHDNUORGREDNU_3_YRAUBORF = "yaD gohdnuorgrednU - 3 yrauborF"
YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY = "Your Puny Weapons Don't Phase Me, Baby!"
YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER = "You Don't Go Messing With a Man's Zipper"
YOU_GAINED_86_EXPERIENCE_POINTS = "You Gained 86 Experience Points"
YOU_ONE_OF_THEM_AGITATORS_AINT_YA = "You One of Them Agitators, Ain't Ya?"
YOU_WANT_A_PIECE_OF_ME_DOCK_BOY = "You Want a Piece of Me, Dock Boy? or Girl"
class ZorkGrandInquisitorRegions(enum.Enum):
CROSSROADS = "Crossroads"
DM_LAIR = "Dungeon Master's Lair"
DM_LAIR_INTERIOR = "Dungeon Master's Lair - Interior"
DRAGON_ARCHIPELAGO = "Dragon Archipelago"
DRAGON_ARCHIPELAGO_DRAGON = "Dragon Archipelago - Dragon"
ENDGAME = "Endgame"
GUE_TECH = "GUE Tech"
GUE_TECH_HALLWAY = "GUE Tech - Hallway"
GUE_TECH_OUTSIDE = "GUE Tech - Outside"
HADES = "Hades"
HADES_BEYOND_GATES = "Hades - Beyond Gates"
HADES_SHORE = "Hades - Shore"
MENU = "Menu"
MONASTERY = "Monastery"
MONASTERY_EXHIBIT = "Monastery - Exhibit"
PORT_FOOZLE = "Port Foozle"
PORT_FOOZLE_JACKS_SHOP = "Port Foozle - Jack's Shop"
PORT_FOOZLE_PAST = "Port Foozle Past"
PORT_FOOZLE_PAST_TAVERN = "Port Foozle Past - Tavern"
SPELL_LAB = "Spell Lab"
SPELL_LAB_BRIDGE = "Spell Lab - Bridge"
SUBWAY_CROSSROADS = "Subway Platform - Crossroads"
SUBWAY_FLOOD_CONTROL_DAM = "Subway Platform - Flood Control Dam #3"
SUBWAY_MONASTERY = "Subway Platform - Monastery"
WALKING_CASTLE = "Walking Castle"
WHITE_HOUSE = "White House"
class ZorkGrandInquisitorTags(enum.Enum):
CORE = "Core"
DEATHSANITY = "Deathsanity"
FILLER = "Filler"
HOTSPOT = "Hotspot"
INVENTORY_ITEM = "Inventory Item"
MISSABLE = "Missable"
SPELL = "Spell"
SUBWAY_DESTINATION = "Subway Destination"
TELEPORTER_DESTINATION = "Teleporter Destination"
TOTEMIZER_DESTINATION = "Totemizer Destination"
TOTEM = "Totem"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,370 @@
from typing import Optional, Tuple
from pymem import Pymem
from pymem.process import close_handle
class GameStateManager:
process_name = "scummvm.exe"
process: Optional[Pymem]
is_process_running: bool
script_manager_struct_address: int
render_manager_struct_address: int
game_location: Optional[str]
game_location_offset: Optional[int]
def __init__(self) -> None:
self.process = None
self.is_process_running = False
self.script_manager_struct_address = 0x0
self.render_manager_struct_address = 0x0
self.game_location = None
self.game_location_offset = None
@property
def game_state_storage_pointer_address(self) -> int:
return self.script_manager_struct_address + 0x88
@property
def game_state_storage_address(self) -> int:
return self.process.read_longlong(self.game_state_storage_pointer_address)
@property
def game_state_hashmap_size_address(self) -> int:
return self.script_manager_struct_address + 0x90
@property
def game_state_key_count_address(self) -> int:
return self.script_manager_struct_address + 0x94
@property
def game_state_deleted_key_count_address(self) -> int:
return self.script_manager_struct_address + 0x98
@property
def game_flags_storage_pointer_address(self) -> int:
return self.script_manager_struct_address + 0x120
@property
def game_flags_storage_address(self) -> int:
return self.process.read_longlong(self.game_flags_storage_pointer_address)
@property
def game_flags_hashmap_size_address(self) -> int:
return self.script_manager_struct_address + 0x128
@property
def game_flags_key_count_address(self) -> int:
return self.script_manager_struct_address + 0x12C
@property
def game_flags_deleted_key_count_address(self) -> int:
return self.script_manager_struct_address + 0x130
@property
def current_location_address(self) -> int:
return self.script_manager_struct_address + 0x400
@property
def current_location_offset_address(self) -> int:
return self.script_manager_struct_address + 0x404
@property
def next_location_address(self) -> int:
return self.script_manager_struct_address + 0x408
@property
def next_location_offset_address(self) -> int:
return self.script_manager_struct_address + 0x40C
@property
def panorama_reversed_address(self) -> int:
return self.render_manager_struct_address + 0x1C
def open_process_handle(self) -> bool:
try:
self.process = Pymem(self.process_name)
self.is_process_running = True
self.script_manager_struct_address = self._resolve_address(0x5276600, (0xC8, 0x0))
self.render_manager_struct_address = self._resolve_address(0x5276600, (0xD0, 0x120))
except Exception:
return False
return True
def close_process_handle(self) -> bool:
if close_handle(self.process.process_handle):
self.is_process_running = False
self.process = None
self.script_manager_struct_address = 0x0
self.render_manager_struct_address = 0x0
return True
return False
def is_process_still_running(self) -> bool:
try:
self.process.read_int(self.process.base_address)
except Exception:
self.is_process_running = False
self.process = None
self.script_manager_struct_address = 0x0
self.render_manager_struct_address = 0x0
return False
return True
def read_game_state_value_for(self, key: int) -> Optional[int]:
return self.read_statemap_value_for(key, scope="game_state")
def read_game_flags_value_for(self, key: int) -> Optional[int]:
return self.read_statemap_value_for(key, scope="game_flags")
def read_statemap_value_for(self, key: int, scope: str = "game_state") -> Optional[int]:
if self.is_process_running:
offset: int
address: int
address_value: int
if scope == "game_state":
offset = self._get_game_state_address_read_offset_for(key)
address = self.game_state_storage_address + offset
address_value = self.process.read_longlong(address)
elif scope == "game_flags":
offset = self._get_game_flags_address_read_offset_for(key)
address = self.game_flags_storage_address + offset
address_value = self.process.read_longlong(address)
else:
raise ValueError(f"Invalid scope: {scope}")
if address_value == 0:
return 0
statemap_value: int = self.process.read_int(address_value + 0x0)
statemap_key: int = self.process.read_int(address_value + 0x4)
assert statemap_key == key
return statemap_value
return None
def write_game_state_value_for(self, key: int, value: int) -> Optional[bool]:
return self.write_statemap_value_for(key, value, scope="game_state")
def write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]:
return self.write_statemap_value_for(key, value, scope="game_flags")
def write_statemap_value_for(self, key: int, value: int, scope: str = "game_state") -> Optional[bool]:
if self.is_process_running:
offset: int
is_existing_node: bool
is_reused_dummy_node: bool
key_count_address: int
deleted_key_count_address: int
storage_address: int
if scope == "game_state":
offset, is_existing_node, is_reused_dummy_node = self._get_game_state_address_write_offset_for(key)
key_count_address = self.game_state_key_count_address
deleted_key_count_address = self.game_state_deleted_key_count_address
storage_address = self.game_state_storage_address
elif scope == "game_flags":
offset, is_existing_node, is_reused_dummy_node = self._get_game_flags_address_write_offset_for(key)
key_count_address = self.game_flags_key_count_address
deleted_key_count_address = self.game_flags_deleted_key_count_address
storage_address = self.game_flags_storage_address
else:
raise ValueError(f"Invalid scope: {scope}")
statemap_key_count: int = self.process.read_int(key_count_address)
statemap_deleted_key_count: int = self.process.read_int(deleted_key_count_address)
if value == 0:
if not is_existing_node:
return False
self.process.write_longlong(storage_address + offset, 1)
self.process.write_int(key_count_address, statemap_key_count - 1)
self.process.write_int(deleted_key_count_address, statemap_deleted_key_count + 1)
else:
if is_existing_node:
address_value: int = self.process.read_longlong(storage_address + offset)
self.process.write_int(address_value + 0x0, value)
else:
write_address: int = self.process.allocate(0x8)
self.process.write_int(write_address + 0x0, value)
self.process.write_int(write_address + 0x4, key)
self.process.write_longlong(storage_address + offset, write_address)
self.process.write_int(key_count_address, statemap_key_count + 1)
if is_reused_dummy_node:
self.process.write_int(deleted_key_count_address, statemap_deleted_key_count - 1)
return True
return None
def refresh_game_location(self) -> Optional[bool]:
if self.is_process_running:
game_location_bytes: bytes = self.process.read_bytes(self.current_location_address, 4)
self.game_location = game_location_bytes.decode("ascii")
self.game_location_offset = self.process.read_int(self.current_location_offset_address)
return True
return None
def set_game_location(self, game_location: str, offset: int) -> Optional[bool]:
if self.is_process_running:
game_location_bytes: bytes = game_location.encode("ascii")
self.process.write_bytes(self.next_location_address, game_location_bytes, 4)
self.process.write_int(self.next_location_offset_address, offset)
return True
return None
def set_panorama_reversed(self, is_reversed: bool) -> Optional[bool]:
if self.is_process_running:
self.process.write_int(self.panorama_reversed_address, 1 if is_reversed else 0)
return True
return None
def _resolve_address(self, base_offset: int, offsets: Tuple[int, ...]):
address: int = self.process.read_longlong(self.process.base_address + base_offset)
for offset in offsets[:-1]:
address = self.process.read_longlong(address + offset)
return address + offsets[-1]
def _get_game_state_address_read_offset_for(self, key: int):
return self._get_statemap_address_read_offset_for(key, scope="game_state")
def _get_game_flags_address_read_offset_for(self, key: int):
return self._get_statemap_address_read_offset_for(key, scope="game_flags")
def _get_statemap_address_read_offset_for(self, key: int, scope: str = "game_state") -> int:
hashmap_size_address: int
storage_address: int
if scope == "game_state":
hashmap_size_address = self.game_state_hashmap_size_address
storage_address = self.game_state_storage_address
elif scope == "game_flags":
hashmap_size_address = self.game_flags_hashmap_size_address
storage_address = self.game_flags_storage_address
else:
raise ValueError(f"Invalid scope: {scope}")
statemap_hashmap_size: int = self.process.read_int(hashmap_size_address)
perturb: int = key
perturb_shift: int = 0x5
index: int = key & statemap_hashmap_size
offset: int = index * 0x8
while True:
offset_value: int = self.process.read_longlong(storage_address + offset)
if offset_value == 0: # Null Pointer
break
elif offset_value == 1: # Dummy Node
pass
elif offset_value > 1: # Existing Node
if self.process.read_int(offset_value + 0x4) == key:
break
index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size
offset = index * 0x8
perturb >>= perturb_shift
return offset
def _get_game_state_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]:
return self._get_statemap_address_write_offset_for(key, scope="game_state")
def _get_game_flags_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]:
return self._get_statemap_address_write_offset_for(key, scope="game_flags")
def _get_statemap_address_write_offset_for(self, key: int, scope: str = "game_state") -> Tuple[int, bool, bool]:
hashmap_size_address: int
storage_address: int
if scope == "game_state":
hashmap_size_address = self.game_state_hashmap_size_address
storage_address = self.game_state_storage_address
elif scope == "game_flags":
hashmap_size_address = self.game_flags_hashmap_size_address
storage_address = self.game_flags_storage_address
else:
raise ValueError(f"Invalid scope: {scope}")
statemap_hashmap_size: int = self.process.read_int(hashmap_size_address)
perturb: int = key
perturb_shift: int = 0x5
index: int = key & statemap_hashmap_size
offset: int = index * 0x8
node_found: bool = False
dummy_node_found: bool = False
dummy_node_offset: Optional[int] = None
while True:
offset_value: int = self.process.read_longlong(storage_address + offset)
if offset_value == 0: # Null Pointer
break
elif offset_value == 1: # Dummy Node
if not dummy_node_found:
dummy_node_offset = offset
dummy_node_found = True
elif offset_value > 1: # Existing Node
if self.process.read_int(offset_value + 0x4) == key:
node_found = True
break
index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size
offset = index * 0x8
perturb >>= perturb_shift
if not node_found and dummy_node_found: # We should reuse the dummy node
return dummy_node_offset, False, True
elif not node_found and not dummy_node_found: # We should allocate a new node
return offset, False, False
return offset, True, False # We should update the existing node

View File

@ -0,0 +1,61 @@
from dataclasses import dataclass
from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Toggle
class Goal(Choice):
"""
Determines the victory condition
Three Artifacts: Retrieve the three artifacts of magic and place them in the walking castle
"""
display_name: str = "Goal"
default: int = 0
option_three_artifacts: int = 0
class QuickPortFoozle(DefaultOnToggle):
"""If true, the items needed to go down the well will be found in early locations for a smoother early game"""
display_name: str = "Quick Port Foozle"
class StartWithHotspotItems(DefaultOnToggle):
"""
If true, the player will be given all the hotspot items at the start of the game, effectively removing the need
to enable the important hotspots in the game before interacting with them. Recommended for beginners
Note: The spots these hotspot items would have occupied in the item pool will instead be filled with junk items.
Expect a higher volume of filler items if you enable this option
"""
display_name: str = "Start with Hotspot Items"
class Deathsanity(Toggle):
"""If true, adds 16 player death locations to the world"""
display_name: str = "Deathsanity"
class GrantMissableLocationChecks(Toggle):
"""
If true, performing an irreversible action will grant the locations checks that would have become unobtainable as a
result of that action when you meet the item requirements
Otherwise, the player is expected to potentially have to use the save system to reach those location checks. If you
don't like the idea of rarely having to reload an earlier save to get a location check, make sure this option is
enabled
"""
display_name: str = "Grant Missable Checks"
@dataclass
class ZorkGrandInquisitorOptions(PerGameCommonOptions):
goal: Goal
quick_port_foozle: QuickPortFoozle
start_with_hotspot_items: StartWithHotspotItems
deathsanity: Deathsanity
grant_missable_location_checks: GrantMissableLocationChecks

View File

@ -0,0 +1 @@
Pymem>=1.13.0

View File

@ -0,0 +1,5 @@
from test.bases import WorldTestBase
class ZorkGrandInquisitorTestBase(WorldTestBase):
game = "Zork Grand Inquisitor"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
import unittest
from ..data_funcs import location_access_rule_for, entrance_access_rule_for
from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorRegions
class DataFuncsTest(unittest.TestCase):
def test_location_access_rule_for(self) -> None:
# No Requirements
self.assertEqual(
"lambda state: True",
location_access_rule_for(ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN, 1),
)
# Single Item Requirement
self.assertEqual(
'lambda state: state.has("Sword", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY, 1),
)
self.assertEqual(
'lambda state: state.has("Spell: NARWILE", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL, 1),
)
# Single Event Requirement
self.assertEqual(
'lambda state: state.has("Event: Knows OBIDIL", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER, 1),
)
self.assertEqual(
'lambda state: state.has("Event: Dunce Locker Openable", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES, 1),
)
# Multiple Item Requirements
self.assertEqual(
'lambda state: state.has("Hotspot: Purple Words", 1) and state.has("Spell: IGRAM", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.A_SMALLWAY, 1),
)
self.assertEqual(
'lambda state: state.has("Hotspot: Mossy Grate", 1) and state.has("Spell: THROCK", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY, 1),
)
# Multiple Item Requirements OR
self.assertEqual(
'lambda state: (state.has("Totem: Griff", 1) or state.has("Totem: Lucy", 1)) and state.has("Hotspot: Mailbox Door", 1) and state.has("Hotspot: Mailbox Flag", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL, 1),
)
# Multiple Mixed Requirements
self.assertEqual(
'lambda state: state.has("Event: Cigar Accessible", 1) and state.has("Hotspot: Grand Inquisitor Doll", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, 1),
)
self.assertEqual(
'lambda state: state.has("Sword", 1) and state.has("Event: Rope GLORFable", 1) and state.has("Hotspot: Monastery Vent", 1)',
location_access_rule_for(ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE, 1),
)
def test_entrance_access_rule_for(self) -> None:
# No Requirements
self.assertEqual(
"lambda state: True",
entrance_access_rule_for(
ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE, 1
),
)
self.assertEqual(
"lambda state: True",
entrance_access_rule_for(
ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS, 1
),
)
# Single Requirement
self.assertEqual(
'lambda state: (state.has("Map", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS, 1
),
)
self.assertEqual(
'lambda state: (state.has("Map", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS, 1
),
)
# Multiple Requirements AND
self.assertEqual(
'lambda state: (state.has("Spell: REZROV", 1) and state.has("Hotspot: In Magic We Trust Door", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH, 1
),
)
self.assertEqual(
'lambda state: (state.has("Event: Door Smoked Cigar", 1) and state.has("Event: Door Drank Mead", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, 1
),
)
self.assertEqual(
'lambda state: (state.has("Hotspot: Closet Door", 1) and state.has("Spell: NARWILE", 1) and state.has("Event: Knows YASTARD", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE, 1
),
)
# Multiple Requirements AND + OR
self.assertEqual(
'lambda state: (state.has("Sword", 1) and state.has("Hotspot: Dungeon Master\'s Lair Entrance", 1)) or (state.has("Map", 1) and state.has("Teleporter Destination: Dungeon Master\'s Lair", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR, 1
),
)
# Multiple Requirements Regions
self.assertEqual(
'lambda state: (state.has("Griff\'s Air Pump", 1) and state.has("Griff\'s Inflatable Raft", 1) and state.has("Griff\'s Inflatable Sea Captain", 1) and state.has("Hotspot: Dragon Nostrils", 1) and state.has("Griff\'s Dragon Tooth", 1) and state.can_reach("Port Foozle Past - Tavern", "Region", 1) and state.has("Lucy\'s Playing Card: 1 Pip", 1) and state.has("Lucy\'s Playing Card: 2 Pips", 1) and state.has("Lucy\'s Playing Card: 3 Pips", 1) and state.has("Lucy\'s Playing Card: 4 Pips", 1) and state.has("Hotspot: Tavern Fly", 1) and state.has("Hotspot: Alpine\'s Quandry Card Slots", 1) and state.can_reach("White House", "Region", 1) and state.has("Totem: Brog", 1) and state.has("Brog\'s Flickering Torch", 1) and state.has("Brog\'s Grue Egg", 1) and state.has("Hotspot: Cooking Pot", 1) and state.has("Brog\'s Plank", 1) and state.has("Hotspot: Skull Cage", 1))',
entrance_access_rule_for(
ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME, 1
),
)

View File

@ -0,0 +1,49 @@
from typing import Dict, Set
from . import ZorkGrandInquisitorTestBase
from ..data_funcs import location_names_to_location, locations_with_tag
from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorTags
class LocationsTestNoDeathsanity(ZorkGrandInquisitorTestBase):
options = {
"deathsanity": "false",
}
def test_correct_locations_exist(self) -> None:
expected_locations: Set[ZorkGrandInquisitorLocations] = locations_with_tag(
ZorkGrandInquisitorTags.CORE
)
self._assert_expected_locations_exist(expected_locations)
def _assert_expected_locations_exist(self, expected_locations: Set[ZorkGrandInquisitorLocations]) -> None:
location_name_to_location: Dict[str, ZorkGrandInquisitorLocations] = location_names_to_location()
for location_object in self.multiworld.get_locations(1):
location: ZorkGrandInquisitorLocations = location_name_to_location.get(
location_object.name
)
if location is None:
continue
self.assertIn(location, expected_locations)
expected_locations.remove(location)
self.assertEqual(0, len(expected_locations))
class LocationsTestDeathsanity(LocationsTestNoDeathsanity):
options = {
"deathsanity": "true",
}
def test_correct_locations_exist(self) -> None:
expected_locations: Set[ZorkGrandInquisitorLocations] = (
locations_with_tag(ZorkGrandInquisitorTags.CORE) | locations_with_tag(ZorkGrandInquisitorTags.DEATHSANITY)
)
self._assert_expected_locations_exist(expected_locations)

View File

@ -0,0 +1,206 @@
from typing import Any, Dict, List, Set, Tuple
from BaseClasses import Item, ItemClassification, Location, Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from .data.item_data import item_data, ZorkGrandInquisitorItemData
from .data.location_data import location_data, ZorkGrandInquisitorLocationData
from .data.region_data import region_data
from .data_funcs import (
item_names_to_id,
item_names_to_item,
location_names_to_id,
item_groups,
items_with_tag,
location_groups,
locations_by_region,
location_access_rule_for,
entrance_access_rule_for,
)
from .enums import (
ZorkGrandInquisitorEvents,
ZorkGrandInquisitorItems,
ZorkGrandInquisitorLocations,
ZorkGrandInquisitorRegions,
ZorkGrandInquisitorTags,
)
from .options import ZorkGrandInquisitorOptions
class ZorkGrandInquisitorItem(Item):
game = "Zork Grand Inquisitor"
class ZorkGrandInquisitorLocation(Location):
game = "Zork Grand Inquisitor"
class ZorkGrandInquisitorWebWorld(WebWorld):
theme: str = "stone"
tutorials: List[Tutorial] = [
Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Zork Grand Inquisitor randomizer connected to an Archipelago Multiworld",
"English",
"setup_en.md",
"setup/en",
["Serpent.AI"],
)
]
class ZorkGrandInquisitorWorld(World):
"""
Zork: Grand Inquisitor is a 1997 point-and-click adventure game for PC.
Magic has been banned from the great Underground Empire of Zork. By edict of the Grand Inquisitor Mir Yannick, the
Empire has been sealed off and the practice of mystic arts declared punishable by "Totemization" (a very bad thing).
The only way to restore magic to the kingdom is to find three hidden artifacts: The Coconut of Quendor, The Cube of
Foundation, and The Skull of Yoruk.
"""
options_dataclass = ZorkGrandInquisitorOptions
options: ZorkGrandInquisitorOptions
game = "Zork Grand Inquisitor"
item_name_to_id = item_names_to_id()
location_name_to_id = location_names_to_id()
item_name_groups = item_groups()
location_name_groups = location_groups()
required_client_version: Tuple[int, int, int] = (0, 4, 4)
web = ZorkGrandInquisitorWebWorld()
item_name_to_item: Dict[str, ZorkGrandInquisitorItems] = item_names_to_item()
def create_regions(self) -> None:
deathsanity: bool = bool(self.options.deathsanity)
region_mapping: Dict[ZorkGrandInquisitorRegions, Region] = dict()
region_enum_item: ZorkGrandInquisitorRegions
for region_enum_item in region_data.keys():
region_mapping[region_enum_item] = Region(region_enum_item.value, self.player, self.multiworld)
region_locations_mapping: Dict[ZorkGrandInquisitorRegions, Set[ZorkGrandInquisitorLocations]]
region_locations_mapping = locations_by_region(include_deathsanity=deathsanity)
region_enum_item: ZorkGrandInquisitorRegions
region: Region
for region_enum_item, region in region_mapping.items():
regions_locations: Set[ZorkGrandInquisitorLocations] = region_locations_mapping[region_enum_item]
# Locations
location_enum_item: ZorkGrandInquisitorLocations
for location_enum_item in regions_locations:
data: ZorkGrandInquisitorLocationData = location_data[location_enum_item]
location: ZorkGrandInquisitorLocation = ZorkGrandInquisitorLocation(
self.player,
location_enum_item.value,
data.archipelago_id,
region_mapping[data.region],
)
location.event = isinstance(location_enum_item, ZorkGrandInquisitorEvents)
if location.event:
location.place_locked_item(
ZorkGrandInquisitorItem(
data.event_item_name,
ItemClassification.progression,
None,
self.player,
)
)
location_access_rule: str = location_access_rule_for(location_enum_item, self.player)
if location_access_rule != "lambda state: True":
location.access_rule = eval(location_access_rule)
region.locations.append(location)
# Connections
region_exit: ZorkGrandInquisitorRegions
for region_exit in region_data[region_enum_item].exits or tuple():
entrance_access_rule: str = entrance_access_rule_for(region_enum_item, region_exit, self.player)
if entrance_access_rule == "lambda state: True":
region.connect(region_mapping[region_exit])
else:
region.connect(region_mapping[region_exit], rule=eval(entrance_access_rule))
self.multiworld.regions.append(region)
def create_items(self) -> None:
quick_port_foozle: bool = bool(self.options.quick_port_foozle)
start_with_hotspot_items: bool = bool(self.options.start_with_hotspot_items)
item_pool: List[ZorkGrandInquisitorItem] = list()
item: ZorkGrandInquisitorItems
data: ZorkGrandInquisitorItemData
for item, data in item_data.items():
tags: Tuple[ZorkGrandInquisitorTags, ...] = data.tags or tuple()
if ZorkGrandInquisitorTags.FILLER in tags:
continue
elif ZorkGrandInquisitorTags.HOTSPOT in tags and start_with_hotspot_items:
continue
item_pool.append(self.create_item(item.value))
total_locations: int = len(self.multiworld.get_unfilled_locations(self.player))
item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))]
self.multiworld.itempool += item_pool
if quick_port_foozle:
self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.ROPE.value] = 1
self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.LANTERN.value] = 1
if not start_with_hotspot_items:
self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_WELL.value] = 1
self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value] = 1
self.multiworld.early_items[self.player][
ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value
] = 1
if start_with_hotspot_items:
item: ZorkGrandInquisitorItems
for item in items_with_tag(ZorkGrandInquisitorTags.HOTSPOT):
self.multiworld.push_precollected(self.create_item(item.value))
def create_item(self, name: str) -> ZorkGrandInquisitorItem:
data: ZorkGrandInquisitorItemData = item_data[self.item_name_to_item[name]]
return ZorkGrandInquisitorItem(
name,
data.classification,
data.archipelago_id,
self.player,
)
def generate_basic(self) -> None:
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
def fill_slot_data(self) -> Dict[str, Any]:
return self.options.as_dict(
"goal",
"quick_port_foozle",
"start_with_hotspot_items",
"deathsanity",
"grant_missable_location_checks",
)
def get_filler_item_name(self) -> str:
return self.random.choice(list(self.item_name_groups["Filler"]))