Files
dockipelago/worlds/tloz_ph/MapWarp.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

332 lines
12 KiB
Python

from typing import TYPE_CHECKING
from .DSZeldaClient.DSZeldaClient import logger
from .DSZeldaClient.subclasses import get_stored_data, storage_key
from .data.Items import ITEMS
from .data.Entrances import ENTRANCES, entrance_id_to_entrance
import worlds._bizhawk as bizhawk
from .data.Addresses import *
if TYPE_CHECKING:
from .Client import PhantomHourglassClient
from worlds._bizhawk.context import BizHawkClientContext
transition_lookup = {
0x1F: "Ocean SW Cannon",
0x20: "Ocean SE Harrow",
0x21: "Ocean NW Uncharted",
0x22: "Ocean NE Maze",
0x23: "Mercay SE Board Ship",
0x24: "Molida South Board Ship",
0x25: "Ember West Board Ship",
0x26: "Gust South Board Ship",
0x27: "Frost SW Board Ship",
0x28: "Goron SW Board Ship",
0x29: "Ruins SW Board Ship",
0x33: "Dee Ess Board Ship",
0x2B: "Cannon Board Ship",
0x2C: "Bannan West Board Ship",
0x2D: "IotD Board Ship",
0x2E: "Zauz Board Ship",
0x2F: "Spirit Board Ship",
0x30: "Harrow Board Ship",
0x31: "Maze Board Ship",
0x32: "Uncharted Board Ship",
}
warp_item_lookup = {
0x23: "Map Warp: Mercay",
0x24: "Map Warp: Molida",
0x25: "Map Warp: Ember",
0x26: "Map Warp: Gust",
0x27: "Map Warp: Frost",
0x28: "Map Warp: Goron",
0x29: "Map Warp: Ruins",
0x33: "Map Warp: Dee Ess",
0x2B: "Map Warp: Cannon",
0x2C: "Map Warp: Bannan",
0x2D: "Map Warp: Isle of the Dead",
0x2E: "Map Warp: Zauz",
0x2F: "Map Warp: Spirit",
0x30: "Map Warp: Harrow",
0x31: "Map Warp: Maze",
0x32: "Map Warp: Uncharted",
}
ut_event_lookup = {
0x1F: "wsw",
0x20: "wse",
0x21: "wnw",
0x22: "wne",
0x23: "wmc",
0x24: "wml",
0x25: "we",
0x26: "wgu",
0x27: "wf",
0x28: "wgo",
0x29: "wr",
0x33: "wds",
0x2B: "wc",
0x2C: "wb",
0x2D: "wd",
0x2E: "wz",
0x2F: "ws",
0x30: "wh",
0x31: "wmz",
0x32: "wu",
}
no_ow_er_lookup = {
0x1F: [0],
0x20: [2],
0x21: [1],
0x22: [3],
0x23: [0xb00, 0xb02, 0xb03],
0x24: [0xc00],
0x25: [0xd00, 0xd01],
0x26: [0xe00, 0xe01],
0x27: [0xf00, 0xf02],
0x28: [0x1000, 0x1001, 0x1002, 0x1003],
0x29: [0x1100, 0x1101, 0x1103,
0x1200, 0x1201, 0x1203],
0x33: [0x1B00],
0x2B: [0x1300],
0x2C: [0x1400],
0x2D: [0x1500],
0x2E: [0x1600],
0x2F: [0x1700],
0x30: [0x1800],
0x31: [0x1900],
0x32: [0x1A00],
}
ow_er_lookup = {
0x1F: [0],
0x20: [2],
0x21: [1],
0x22: [3],
0x23: [0xb03],
0x24: [0xc00],
0x25: [0xd00],
0x26: [0xe00],
0x27: [0xf00],
0x28: [0x1002],
0x29: [0x1100,
0x1200],
0x33: [0x1B00],
0x2B: [0x1300],
0x2C: [0x1400],
0x2D: [0x1500],
0x2E: [0x1600],
0x2F: [0x1700],
0x30: [0x1800],
0x31: [0x1900],
0x32: [0x1A00],
}
def item_count(ctx, item_name) -> int:
return ITEMS[item_name].get_count(ctx)
safe_entrances_common = {
0x1F: ["Ocean SW Mercay",
"Ocean SW Cannon",
"Ocean SW Ember"],
0x21: ["Ocean NW Uncharted",
"Ocean NW Zauz",
"Ocean NW Gust",
"Ocean NW Bannan",
"Ocean NW Board Ghost Ship"],
0x22: ["Ocean NE Maze",
"Ocean NE IotD"],
0x2C: ["Bannan West Board Ship",
"Bannan West Cave",
"Bannan West Hut"],
0x32: ["Uncharted Board Ship"],
}
safe_entrances_ow = safe_entrances_common | {
0x26: ["Gust South Board Ship", "Gust Secret Cave", "Gust Cave West", "Gust Cave East",
"Gust South Temple Road North", "Gust South Above Temple North"],
0x29: ["Ruins SW Board Ship", "Ruins SW Upper Maze North", "Ruins SW Port Cliff North",
"Ruins SW East", "Ruins SW Port Cave", "Ruins SW Cliff Cave"],
}
island_visibility_addr = {
"Map Warp: Mercay": PHAddr.island_visible_mercay,
"Map Warp: Molida": PHAddr.island_visible_molida,
"Map Warp: Ember": PHAddr.island_visible_ember,
"Map Warp: Cannon": PHAddr.island_visible_cannon,
"Map Warp: Spirit": PHAddr.island_visible_spirit,
"Map Warp: Gust": PHAddr.island_visible_gust,
"Map Warp: Bannan": PHAddr.island_visible_bannan,
"Map Warp: Zauz": PHAddr.island_visible_zauz,
"Map Warp: Uncharted": PHAddr.island_visible_uncharted,
"Map Warp: Goron": PHAddr.island_visible_goron,
"Map Warp: Frost": PHAddr.island_visible_frost,
"Map Warp: Harrow": PHAddr.island_visible_harrow,
"Map Warp: Dee Ess": PHAddr.island_visible_ds,
"Map Warp: Ruins": PHAddr.island_visible_ruins,
"Map Warp: Isle of the Dead": PHAddr.island_visible_iotd,
"Map Warp: Maze": PHAddr.island_visible_maze,
}
safe_entrances_no_ow = safe_entrances_common | {}
# Ruins lower does not count! check entrances for that!
# Uncharted cave exit does not count
# bannan east does not count
#
stage_lookup = {}
def check_any_er(ctx):
return any([ctx.slot_data[i] for i in ["shuffle_dungeon_entrances",
"shuffle_ports", "shuffle_caves",
"shuffle_houses",
"shuffle_overworld_transitions",
"shuffle_bosses"]
])
# Check for safe entrances
def check_entrances(client: "PhantomHourglassClient", ctx: "BizHawkClientContext", trans_value, safe_entrance_map):
if trans_value not in safe_entrance_map:
return True
if not ctx.slot_data["shuffle_ports"]: # Entrances only exist if things are actually randomized, this solves most cases. actually it prevents warping to uncharted but i dont care
if not (ctx.slot_data["shuffle_caves"] and trans_value in [0x2C, 0x32]):
return True
client.visited_entrances |= set(get_stored_data(ctx, "ph_traversed_entrances", []))
visited_entrances = client.visited_entrances
print(f"Visited entrances: {visited_entrances}")
for entr in safe_entrance_map[trans_value]:
entr_id = ENTRANCES[entr].id
if entr_id in visited_entrances:
return True
return False
# Check if player has visited an island
def check_visited_scenes(client, ctx, transition_mode, scene_lookup, safe_entrances_lookup):
for scene in scene_lookup[transition_mode]:
if (scene in client.visited_scenes
and check_entrances(client, ctx, transition_mode, safe_entrances_lookup)):
print(f"Has visited scene {hex(scene)}. Done!")
return ENTRANCES[transition_lookup[transition_mode]]
return None
async def set_visibility(ctx: "BizHawkClientContext"):
write_list = []
for item, addr in island_visibility_addr.items():
if item_count(ctx, item):
write_list += addr.get_write_list(1)
else:
write_list += addr.get_write_list(0)
await bizhawk.write(ctx.bizhawk_ctx, write_list)
async def map_mode(client: "PhantomHourglassClient", ctx: "BizHawkClientContext", read_list):
# Check options
if ctx.slot_data.get("map_warp_options", 0) == 0 and False: return
if client.warp_to_start_flag:
client.warp_to_start_flag = False
logger.info(f"Canceled warp to start due to opening map warp menu")
# read transition mode
transition_mode = await PHAddr.changing_map_scene.read(ctx, silent=True)
client.visited_scenes |= set(get_stored_data(ctx, 'ph_visited_scenes', []))
if client.pen_mode_pointer: # Do fun stuff with the pen and eraser buttons
current_pen_mode = await client.pen_mode_pointer.read(ctx, silent=True)
if current_pen_mode in [0x18, 0x19] and current_pen_mode != client.last_pen_mode:
if current_pen_mode == 0x19:
def quick_entrance_log(key):
logger.info(f"Current storage {key}:")
for e in get_stored_data(ctx, key, []):
logger.info(f" {entrance_id_to_entrance[e].name}")
quick_entrance_log("ph_checked_entrances")
quick_entrance_log("ph_disconnect_entrances")
quick_entrance_log("ph_traversed_entrances")
logger.info(f"local traverses: {client.visited_entrances}")
logger.info(f"Currently stored scenes:")
for i in set(get_stored_data(ctx, 'ph_visited_scenes', [])) | client.visited_scenes:
logger.info(f" {hex(i)}")
client.last_pen_mode = current_pen_mode
if not transition_mode: return
# Enter map mode
if transition_mode == 6:
client.map_mode = True
if client.map_warp:
client.map_warp = None
logger.info(f"Canceled map warp")
if ctx.slot_data["map_warp_options"] == 1:
await set_visibility(ctx)
if not client.map_mode: return
# Exit map mode when appropriate
if transition_mode == 0x1E:
client.map_mode = False
print(f"Exiting Map Menu")
elif client.map_warp_reselector and transition_mode in transition_lookup:
print(f"bool map warp {client.map_warp} {bool(client.map_warp)}")
client.map_warp_reselector = False
# Setup pen mode stuff
pen_mode_pointer = await PHAddr.pen_mode_pointer.read(ctx, silent=True)
print(f"pen mode pointer {hex(pen_mode_pointer)}")
pen_mode_check = pen_mode_pointer+25*4-0x2000000
if pen_mode_check < 0x400000:
client.pen_mode_pointer = Address(pen_mode_check, size=4)
client.last_pen_mode = await client.pen_mode_pointer.read(ctx)
else: client.pen_mode_pointer = None
# Do detailed warp instructions
if check_any_er(ctx):
print(f"Seed has ER. Checking ER conditions")
if not ctx.slot_data["shuffle_overworld_transitions"]:
# No OW er: any island access allows warping
client.map_warp = check_visited_scenes(client, ctx, transition_mode,
no_ow_er_lookup, safe_entrances_no_ow)
else:
# other: require port quadrant access
client.map_warp = check_visited_scenes(client, ctx, transition_mode,
ow_er_lookup, safe_entrances_ow)
else:
client.map_warp = ENTRANCES[transition_lookup[transition_mode]]
# Failure conditions
if not client.map_warp:
land_type = "ocean" if transition_mode in range(0x1f, 0x23) else "island's port"
if ctx.slot_data["shuffle_caves"] and not ctx.slot_data["shuffle_ports"] and transition_mode == 0x32:
logger.info(f"You can't warp to uncharted with these settings, cause i don't know if you have boat access.")
else:
logger.info(f"You have yet to visit that {land_type}")
elif client.current_scene in ow_er_lookup[transition_mode]:
logger.info(f"You are already in that scene, you can't warp there")
client.map_warp = None
elif ctx.slot_data["map_warp_options"] == 1:
if transition_mode in warp_item_lookup and not item_count(ctx, warp_item_lookup[transition_mode]):
client.map_warp = None
logger.info(f"Missing warp unlock item for that island")
elif transition_mode in range(0x1f, 0x23) and ctx.slot_data["boat_requires_sea_chart"]:
# If warping to sea, check sea chart reqs
trans_mode_to_chart = {0x1F: "SW Sea Chart", 0x20: "SE Sea Chart", 0x21: "NW Sea Chart", 0x22: "NE Sea Chart"}
if not item_count(ctx, trans_mode_to_chart[transition_mode]):
client.map_warp = None
logger.info(f"You do not have the correct sea chart")
# Success
if client.map_warp:
logger.info(f"Selected map warp destination: {transition_lookup[transition_mode]}")
# Store connection for ut glp
await client.store_data(ctx, storage_key(ctx, "ph_ut_events"), [ut_event_lookup[transition_mode]])
elif transition_mode == 0x17: # Return to big map, reset selector
client.map_warp_reselector = True