Skip to content

Commit

Permalink
traps! (and option groups)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbrochu committed Nov 29, 2024
1 parent c336529 commit ddbbf97
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 42 deletions.
14 changes: 12 additions & 2 deletions worlds/zork_grand_inquisitor/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ def on_package(self, cmd: str, _args: Any) -> None:
id_to_landmarksanity()[_args["slot_data"]["landmarksanity"]]
)

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
)
Expand Down Expand Up @@ -211,22 +213,30 @@ async def controller(self):
# Enqueue Received Item Delta
goal_item_count: int = 0

trap_item_counts: Dict[ZorkGrandInquisitorItems, int] = {
item: 0 for item in self.game_controller.all_trap_items
}

network_item: NetUtils.NetworkItem
for network_item in self.items_received:
item: ZorkGrandInquisitorItems = self.id_to_items[network_item.item]

if item in self.game_controller.all_goal_items:
goal_item_count += 1
continue

if item not in self.game_controller.received_items:
elif item in self.game_controller.all_trap_items:
trap_item_counts[item] += 1
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.trap_counters = trap_item_counts

# Game Controller Update
if self.game_controller.is_process_running():
self.game_controller.update()
Expand Down
25 changes: 25 additions & 0 deletions worlds/zork_grand_inquisitor/data/item_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1397,4 +1397,29 @@ class ZorkGrandInquisitorItemData(NamedTuple):
classification=ItemClassification.progression,
tags=(ZorkGrandInquisitorTags.GOAL_GRIM_JOURNEY,),
),
# Trap Items
ZorkGrandInquisitorItems.TRAP_INFINITE_CORRIDOR: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 900 + 0,
classification=ItemClassification.trap | ItemClassification.useful,
tags=(ZorkGrandInquisitorTags.TRAP,),
),
ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 900 + 1,
classification=ItemClassification.trap,
tags=(ZorkGrandInquisitorTags.TRAP,),
),
ZorkGrandInquisitorItems.TRAP_TELEPORT: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 900 + 2,
classification=ItemClassification.trap | ItemClassification.useful,
tags=(ZorkGrandInquisitorTags.TRAP,),
),
ZorkGrandInquisitorItems.TRAP_ZVISION: ZorkGrandInquisitorItemData(
statemap_keys=None,
archipelago_id=ITEM_OFFSET + 900 + 3,
classification=ItemClassification.trap,
tags=(ZorkGrandInquisitorTags.TRAP,),
),
}
7 changes: 7 additions & 0 deletions worlds/zork_grand_inquisitor/data/mapping_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,13 @@
ZorkGrandInquisitorStartingLocations.MONASTERY_EXHIBIT: ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT,
}

traps_to_game_state_key: Dict[ZorkGrandInquisitorItems, int] = {
ZorkGrandInquisitorItems.TRAP_INFINITE_CORRIDOR: 19990,
ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS: 19991,
ZorkGrandInquisitorItems.TRAP_TELEPORT: 19992,
ZorkGrandInquisitorItems.TRAP_ZVISION: 19993,
}

voxam_cast_game_locations: Dict[
ZorkGrandInquisitorStartingLocations,
Tuple[Tuple[str, int], ...]
Expand Down
5 changes: 5 additions & 0 deletions worlds/zork_grand_inquisitor/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ class ZorkGrandInquisitorItems(enum.Enum):
TOTEMIZER_DESTINATION_NEWARK_NEW_JERSEY = "Totemizer Destination: Newark, New Jersey"
TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL = "Totemizer Destination: Straight to Hell"
TOTEMIZER_DESTINATION_SURFACE_OF_MERZ = "Totemizer Destination: Surface of Merz"
TRAP_INFINITE_CORRIDOR = "Infinite Corridor Trap"
TRAP_REVERSE_CONTROLS = "Reverse Controls Trap"
TRAP_TELEPORT = "Teleport Trap"
TRAP_ZVISION = "ZVision Trap"
WELL_ROPE = "Well Rope"
ZIMDOR_SCROLL = "ZIMDOR Scroll"
ZORK_ROCKS = "Zork Rocks"
Expand Down Expand Up @@ -534,3 +538,4 @@ class ZorkGrandInquisitorTags(enum.Enum):
TELEPORTER_DESTINATION = "Teleporter Destination"
TOTEMIZER_DESTINATION = "Totemizer Destination"
TOTEM = "Totem"
TRAP = "Trap"
117 changes: 114 additions & 3 deletions worlds/zork_grand_inquisitor/game_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import collections
import datetime
import functools
import logging
import random
Expand All @@ -14,6 +15,7 @@
death_cause_labels,
hotspots_for_regional_hotspot,
labels_for_enum_items,
traps_to_game_state_key,
voxam_cast_game_locations,
)

Expand Down Expand Up @@ -54,6 +56,7 @@ class GameController:
all_spell_items: Set[ZorkGrandInquisitorItems]
all_hotspot_items: Set[ZorkGrandInquisitorItems]
all_goal_items: Set[ZorkGrandInquisitorItems]
all_trap_items: Set[ZorkGrandInquisitorItems]

game_id_to_items: Dict[int, ZorkGrandInquisitorItems]

Expand All @@ -76,13 +79,19 @@ class GameController:
option_wild_voxam_chance: Optional[int]
option_deathsanity: Optional[ZorkGrandInquisitorDeathsanity]
option_landmarksanity: Optional[ZorkGrandInquisitorLandmarksanity]
option_trap_percentage: Optional[int]
option_grant_missable_location_checks: Optional[bool]
option_client_seed_information: Optional[ZorkGrandInquisitorClientSeedInformation]
option_death_link: Optional[bool]

starter_kit: Optional[List[str]]
initial_totemizer_destination: Optional[ZorkGrandInquisitorItems]

trap_counters: Dict[ZorkGrandInquisitorItems, int]

active_trap: Optional[ZorkGrandInquisitorItems]
active_trap_until: Optional[datetime.datetime]

pending_death_link: Tuple[bool, Optional[str], Optional[str]]
outgoing_death_link: Tuple[bool, Optional[str]]
pause_death_monitoring: bool
Expand Down Expand Up @@ -117,6 +126,8 @@ def __init__(self, logger=None) -> None:
ZorkGrandInquisitorItems.DEATH,
}

self.all_trap_items = items_with_tag(ZorkGrandInquisitorTags.TRAP)

self.game_id_to_items = game_id_to_items()

self.possible_inventory_items = (
Expand All @@ -142,13 +153,24 @@ def __init__(self, logger=None) -> None:
self.option_wild_voxam_chance = None
self.option_deathsanity = None
self.option_landmarksanity = None
self.option_trap_percentage = None
self.option_grant_missable_location_checks = None
self.option_client_seed_information = None
self.option_death_link = None

self.starter_kit = None
self.initial_totemizer_destination = None

self.trap_counters = {
ZorkGrandInquisitorItems.TRAP_INFINITE_CORRIDOR: 0,
ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS: 0,
ZorkGrandInquisitorItems.TRAP_TELEPORT: 0,
ZorkGrandInquisitorItems.TRAP_ZVISION: 0,
}

self.active_trap = None
self.active_trap_until = None

self.pending_death_link = (False, None, None)
self.outgoing_death_link = (False, None)
self.pause_death_monitoring = False
Expand Down Expand Up @@ -246,6 +268,7 @@ def output_seed_information(self) -> None:

self.log(f" Deathsanity: {labels_for_enum_items[self.option_deathsanity]}")
self.log(f" Landmarksanity: {labels_for_enum_items[self.option_landmarksanity]}")
self.log(f" Trap Percentage: {self.option_trap_percentage}%")

if self.option_grant_missable_location_checks:
self.log(f" Grant Missable Location Checks: On")
Expand Down Expand Up @@ -396,6 +419,9 @@ def update(self) -> None:

self._apply_conditional_teleports()

if self.option_trap_percentage:
self._manage_traps()

if self.option_death_link:
self._handle_death_link()

Expand Down Expand Up @@ -428,13 +454,24 @@ def reset(self) -> None:
self.option_wild_voxam_chance = None
self.option_deathsanity = None
self.option_landmarksanity = None
self.option_trap_percentage = None
self.option_grant_missable_location_checks = None
self.option_client_seed_information = None
self.option_death_link = None

self.starter_kit = None
self.initial_totemizer_destination = None

self.trap_counters = {
ZorkGrandInquisitorItems.TRAP_INFINITE_CORRIDOR: 0,
ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS: 0,
ZorkGrandInquisitorItems.TRAP_TELEPORT: 0,
ZorkGrandInquisitorItems.TRAP_ZVISION: 0,
}

self.active_trap = None
self.active_trap_until = None

self.pending_death_link = (False, None, None)
self.outgoing_death_link = (False, None)
self.pause_death_monitoring = False
Expand Down Expand Up @@ -1349,13 +1386,13 @@ def _apply_conditional_teleports(self) -> None:
if zork_rocks_inert:
self._cast_voxam()

def _cast_voxam(self) -> None:
if not self.option_wild_voxam:
def _cast_voxam(self, force_wild: bool = False) -> None:
if not self.option_wild_voxam and not force_wild:
self._apply_starting_location(force=True)

voxam_roll: int = random.randint(1, 100)

if voxam_roll <= self.option_wild_voxam_chance:
if voxam_roll <= self.option_wild_voxam_chance or force_wild:
starting_location: ZorkGrandInquisitorStartingLocations = (
random.choice(tuple(voxam_cast_game_locations.keys()))
)
Expand All @@ -1375,6 +1412,80 @@ def _cast_voxam(self) -> None:
else:
self._apply_starting_location(force=True)

def _manage_traps(self) -> None:
if not self._player_is_afgncaap() or self._read_game_state_value_for(19985) == 0:
return None

if self.active_trap_until:
if datetime.datetime.now() > self.active_trap_until:
if self.active_trap == ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS:
self._deactivate_trap_reverse_controls()
elif self.active_trap == ZorkGrandInquisitorItems.TRAP_ZVISION:
self._deactivate_trap_zvision()

self.active_trap = None
self.active_trap_until = None

if self.active_trap is not None:
if self.active_trap == ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS:
self._activate_trap_reverse_controls()
elif self.active_trap == ZorkGrandInquisitorItems.TRAP_ZVISION:
self._activate_trap_zvision()

return None

trap: ZorkGrandInquisitorItems
count: int
for trap, count in self.trap_counters.items():
game_count: int = self._read_game_state_value_for(traps_to_game_state_key[trap])

if game_count < count:
if trap == ZorkGrandInquisitorItems.TRAP_INFINITE_CORRIDOR:
self._activate_trap_infinite_corridor()
elif trap == ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS:
self.active_trap = ZorkGrandInquisitorItems.TRAP_REVERSE_CONTROLS
self.active_trap_until = datetime.datetime.now() + datetime.timedelta(seconds=30)

self._activate_trap_reverse_controls()
elif trap == ZorkGrandInquisitorItems.TRAP_TELEPORT:
self._activate_trap_teleport()
elif trap == ZorkGrandInquisitorItems.TRAP_ZVISION:
self.active_trap = ZorkGrandInquisitorItems.TRAP_ZVISION
self.active_trap_until = datetime.datetime.now() + datetime.timedelta(seconds=30)

self._activate_trap_zvision()

self._write_game_state_value_for(traps_to_game_state_key[trap], count)
self.trap_counters[trap] = game_count

break

def _activate_trap_infinite_corridor(self) -> None:
depth = random.randint(10, 20)

self._write_game_state_value_for(11005, depth)
self.game_state_manager.set_game_location("th20", random.randint(0, 1800))

time.sleep(0.1)

self._write_game_state_value_for(11005, depth)

def _activate_trap_reverse_controls(self) -> None:
self.game_state_manager.set_panorama_reversed(True)

def _deactivate_trap_reverse_controls(self) -> None:
self.game_state_manager.set_panorama_reversed(False)

def _activate_trap_teleport(self) -> None:
self._cast_voxam(force_wild=True)
time.sleep(0.1)

def _activate_trap_zvision(self) -> None:
self.game_state_manager.set_zvision(True)

def _deactivate_trap_zvision(self) -> None:
self.game_state_manager.set_zvision(False)

def _handle_death_link(self) -> None:
# Pause Monitoring Flag
if self.pause_death_monitoring and not self._player_is_at("gjde"):
Expand Down
24 changes: 24 additions & 0 deletions worlds/zork_grand_inquisitor/game_state_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ def next_location_address(self) -> int:
def next_location_offset_address(self) -> int:
return self.script_manager_struct_address + 0x40C

@property
def zvision_address(self) -> int:
return self.render_manager_struct_address + 0x0

@property
def render_type_address(self) -> int:
return self.render_manager_struct_address + 0x10

@property
def panorama_reversed_address(self) -> int:
return self.render_manager_struct_address + 0x1C
Expand Down Expand Up @@ -250,6 +258,22 @@ def set_game_location(self, game_location: str, offset: int) -> Optional[bool]:

return None

def set_zvision(self, is_zvision: bool) -> Optional[bool]:
if self.is_process_running:
self.process.write_int(self.zvision_address, 320 if is_zvision else 640)

return True

return None

def set_render_type(self, render_type: int) -> Optional[bool]:
if self.is_process_running:
self.process.write_int(self.render_type_address, render_type)

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)
Expand Down
Loading

0 comments on commit ddbbf97

Please sign in to comment.