Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZGI V2 #3

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d02b9b0
add starting location option + implement in client
nbrochu Aug 4, 2024
38050e2
define logic helper items for starting locations; grant proper logic …
nbrochu Nov 1, 2024
c7f5333
remove hardcoded connection data between menu and port foozle; connec…
nbrochu Nov 1, 2024
c96a508
align internal starting location and region naming
nbrochu Nov 1, 2024
6dc1ad2
comment the rough offsets of other managers
nbrochu Nov 2, 2024
508aa17
Merge branch 'main' into zgi-v2
nbrochu Nov 14, 2024
cc602fa
bulk commit -> first playable beta
nbrochu Nov 16, 2024
f94816e
add a small delay before accessing memory after teleporting to starti…
nbrochu Nov 19, 2024
c5607a3
fix skull cage boards being usable without the skull cage hotspot
nbrochu Nov 19, 2024
cb5cde2
hotspots option + regional hotspots
nbrochu Nov 20, 2024
e771800
4 new goals + 19 new checks + new thematic filler items
nbrochu Nov 22, 2024
b9e06c2
typo
nbrochu Nov 22, 2024
4417e59
output starter in client on /zork
nbrochu Nov 22, 2024
110ab68
add option to control how much information about the seed is revealed…
nbrochu Nov 22, 2024
6750b35
implement wild VOXAM option
nbrochu Nov 23, 2024
d189799
start inventory from pool
nbrochu Nov 23, 2024
caf0d45
death link support
nbrochu Nov 23, 2024
06e2062
output death link option in seed information
nbrochu Nov 23, 2024
4735276
wait until the student id card is back in inventory before filtering it
nbrochu Nov 23, 2024
38ad9a6
make it so the well rope cannot be picked up
nbrochu Nov 23, 2024
cdbce91
allow i'm not impressed to be triggered shore-side, while keeping the…
nbrochu Nov 23, 2024
b769fcc
fix cards being visible on first render of the card game
nbrochu Nov 23, 2024
092f634
add save IDs; better robustness around AP connection states and game …
nbrochu Nov 23, 2024
f915d4e
allow client to stay open and switching server and/or slot without we…
nbrochu Nov 24, 2024
d1a03ea
allow spell quickbar to be used on the surface (for VOXAM) + fix way …
nbrochu Nov 24, 2024
31fa87b
can no longer cache is_deathsanity property if we allow switching slo…
nbrochu Nov 24, 2024
c336529
make the amount of needed landmarks and deaths configurable for zork …
nbrochu Nov 25, 2024
ddbbf97
traps! (and option groups)
nbrochu Nov 29, 2024
d2762ef
remove dual classification for teleport traps
nbrochu Nov 29, 2024
d991132
add missing spell bar override for pe2j
nbrochu Nov 29, 2024
9be327f
fix operator precedence issue with voxam casts
nbrochu Nov 29, 2024
baf60cf
only increment save file trap counts by 1 when managing traps
nbrochu Nov 29, 2024
620bfbc
add one-way energy link by hitting the mushroom with the hammer
nbrochu Nov 29, 2024
26aaf89
rework client process messages
nbrochu Nov 30, 2024
eb295ed
make sure traps are activated in received order + trap client messages
nbrochu Nov 30, 2024
6af0dae
universal tracker support
nbrochu Nov 30, 2024
d6b73c3
require 13->5 losses to get lucy's strip grue, fire, water death
nbrochu Nov 30, 2024
9a70818
ZGI V2 Entrance Randomizer
nbrochu Feb 27, 2025
c560edb
Merge remote-tracking branch 'upstream/main' into zgi-v2
nbrochu Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 197 additions & 23 deletions worlds/zork_grand_inquisitor/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,41 @@

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 .data_funcs import (
item_names_to_id,
item_names_to_item,
location_names_to_id,
id_to_client_seed_information,
id_to_craftable_spell_behaviors,
id_to_deathsanity,
id_to_entrance_randomizer,
id_to_hotspots,
id_to_items,
id_to_landmarksanity,
id_to_locations,
id_to_goals,
id_to_starting_locations,
)

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."""
if not self.ctx.server or not self.ctx.slot:
self.output("You must be connected to an Archipelago server before using /zork.")
return

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.")

self.ctx.game_controller.output_seed_information()
self.ctx.game_controller.output_starter_kit()
else:
self.output("Failed to attach to Zork Grand Inquisitor process.")

Expand All @@ -38,6 +60,13 @@ def _cmd_hotspots(self) -> None:
"""List received Hotspots."""
self.ctx.game_controller.list_received_hotspots()

def _cmd_deathlink(self) -> None:
"""Toggle deathlink status."""
if not self.ctx.game_controller.option_death_link:
return

self.ctx.death_link_status = not self.ctx.death_link_status


class ZorkGrandInquisitorContext(CommonClient.CommonContext):
tags: Set[str] = {"AP"}
Expand All @@ -53,6 +82,7 @@ class ZorkGrandInquisitorContext(CommonClient.CommonContext):
id_to_locations: Dict[int, ZorkGrandInquisitorLocations] = id_to_locations()

game_controller: GameController
death_link_status: bool = False

controller_task: Optional[asyncio.Task]

Expand Down Expand Up @@ -86,31 +116,135 @@ async def server_auth(self, password_requested: bool = False):
await self.get_username()
await self.send_connect()

async def disconnect(self, allow_autoreconnect: bool = False):
try:
self.game_controller.close_process_handle()
except Exception:
pass

self.game_controller.reset()

self.items_received = []
self.locations_info = {}

await super().disconnect(allow_autoreconnect)

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_artifacts_of_magic_required = (
_args["slot_data"]["artifacts_of_magic_required"]
)

self.game_controller.option_artifacts_of_magic_total = (
_args["slot_data"]["artifacts_of_magic_total"]
)

self.game_controller.option_landmarks_required = (
_args["slot_data"]["landmarks_required"]
)

self.game_controller.option_deaths_required = (
_args["slot_data"]["deaths_required"]
)

self.game_controller.option_starting_location = (
id_to_starting_locations()[_args["slot_data"]["starting_location"]]
)

self.game_controller.option_hotspots = (
id_to_hotspots()[_args["slot_data"]["hotspots"]]
)

self.game_controller.option_craftable_spells = (
id_to_craftable_spell_behaviors()[_args["slot_data"]["craftable_spells"]]
)

self.game_controller.option_wild_voxam = _args["slot_data"]["wild_voxam"] == 1
self.game_controller.option_wild_voxam_chance = _args["slot_data"]["wild_voxam_chance"]

self.game_controller.option_deathsanity = (
id_to_deathsanity()[_args["slot_data"]["deathsanity"]]
)

self.game_controller.option_landmarksanity = (
id_to_landmarksanity()[_args["slot_data"]["landmarksanity"]]
)

self.game_controller.option_entrance_randomizer = (
id_to_entrance_randomizer()[_args["slot_data"]["entrance_randomizer"]]
)

self.game_controller.option_entrance_randomizer_include_subway_destinations = (
_args["slot_data"]["entrance_randomizer_include_subway_destinations"] == 1
)

self.game_controller.option_trap_percentage = _args["slot_data"]["trap_percentage"]

self.game_controller.option_grant_missable_location_checks = (
_args["slot_data"]["grant_missable_location_checks"] == 1
)

self.game_controller.option_client_seed_information = (
id_to_client_seed_information()[_args["slot_data"]["client_seed_information"]]
)

is_death_link = _args["slot_data"]["death_link"] == 1

self.game_controller.option_death_link = is_death_link # Represents the option; will never change
self.death_link_status = is_death_link # Represents the toggleable status

# Starter Kit
self.game_controller.starter_kit = _args["slot_data"]["starter_kit"]

# Initial Totemizer Destination
self.game_controller.initial_totemizer_destination = item_names_to_item()[
_args["slot_data"]["initial_totemizer_destination"]
]

# Entrance Randomizer Data
self.game_controller.entrance_randomizer_data = _args["slot_data"]["entrance_randomizer_data"]

# Save IDs
self.game_controller.save_ids = tuple(_args["slot_data"]["save_ids"])

def on_deathlink(self, data: Dict[str, Any]) -> None:
self.last_death_link = max(data["time"], self.last_death_link)
self.game_controller.pending_death_link = (True, data.get("source"), data.get("cause"))

async def controller(self):
while not self.exit_event.is_set():
await asyncio.sleep(0.1)

# Enqueue Received Item Delta
goal_item_count: int = 0

received_traps: List[ZorkGrandInquisitorItems] = list()

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 in self.game_controller.all_goal_items:
goal_item_count += 1
continue
elif item in self.game_controller.all_trap_items:
received_traps.append(item)
continue
elif 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)

if goal_item_count > self.game_controller.goal_item_count:
self.game_controller.goal_item_count = goal_item_count
self.game_controller.output_goal_item_update()

self.game_controller.received_traps = received_traps

# Game Controller Update
if self.game_controller.is_process_running():
self.game_controller.update()
Expand All @@ -120,43 +254,83 @@ async def controller(self):

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."
"Connection to the Zork Grand Inquisitor process was lost. Ensure you are connected "
"to an Archipelago server and the game is running, then use the /zork command to reconnect."
)
else:
process_message = (
"Please use the /zork command to attach to a running Zork Grand Inquisitor process."
"To start playing, connect to an Archipelago server and use the /zork command to "
"link to an active 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]
# Network Operations
if self.server and self.slot:
# Send Checked Locations
checked_location_ids: List[int] = list()

checked_location_ids.append(location_id)
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]

await self.send_msgs([
{
"cmd": "LocationChecks",
"locations": checked_location_ids
}
])
checked_location_ids.append(location_id)

# Check for Goal Completion
if self.game_controller.goal_completed:
await self.send_msgs([
{
"cmd": "StatusUpdate",
"status": CommonClient.ClientStatus.CLIENT_GOAL
"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
}
])

# Handle Energy Link
while len(self.game_controller.energy_link_queue) > 0:
energy_to_add: int = self.game_controller.energy_link_queue.popleft()

await self.send_msgs([
{
"cmd": "Set",
"key": f"EnergyLink{self.team}",
"slot": self.slot,
"operations":
[
{"operation": "add", "value": energy_to_add},
],
},
])

CommonClient.logger.info(f"Added {energy_to_add} J to the Energy Link pool")

# Handle Death Link
await self.update_death_link(self.death_link_status)

if self.game_controller.outgoing_death_link[0]:
if self.death_link_status:
death_cause: Optional[str] = self.game_controller.outgoing_death_link[1]

if death_cause:
death_cause = death_cause.replace(
"PLAYER",
self.player_names[self.slot]
)
else:
death_cause = ""

await self.send_death(death_cause)

self.game_controller.outgoing_death_link = (False, None)


def main() -> None:
Utils.init_logging("ZorkGrandInquisitorClient", exception_logger="Client")
Expand Down
Loading
Loading