mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-02 05:43:23 -07:00
Jak 1: In tandem with new ArchipelaGOAL memory structure, define read_memory.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import typing
|
||||
import asyncio
|
||||
import colorama
|
||||
@@ -38,6 +39,26 @@ class JakAndDaxterClientCommandProcessor(ClientCommandProcessor):
|
||||
# 2. Listen (have the REPL compiler connect and listen on the game's REPL server's socket).
|
||||
# 3. Compile (have the REPL compiler compile the game into object code it can run).
|
||||
# All 3 need to be done, and in this order, for this to work.
|
||||
def _cmd_repl(self, *arguments: str):
|
||||
"""Sends a command to the OpenGOAL REPL. Arguments:
|
||||
- connect <ip> <port> : connect a new client to the REPL.
|
||||
- listen : listen to the game's internal socket.
|
||||
- compile : compile the game into executable object code.
|
||||
- verify : verify successful compilation."""
|
||||
if arguments:
|
||||
if arguments[0] == "connect":
|
||||
if arguments[1] and arguments[2]:
|
||||
self.ctx.repl.ip = str(arguments[1])
|
||||
self.ctx.repl.port = int(arguments[2])
|
||||
self.ctx.repl.connect()
|
||||
else:
|
||||
logging.error("You must provide the ip address and port (default 127.0.0.1 port 8181).")
|
||||
if arguments[0] == "listen":
|
||||
self.ctx.repl.listen()
|
||||
if arguments[0] == "compile":
|
||||
self.ctx.repl.compile()
|
||||
if arguments[0] == "verify":
|
||||
self.ctx.repl.verify()
|
||||
|
||||
|
||||
class JakAndDaxterContext(CommonContext):
|
||||
|
||||
@@ -3,6 +3,14 @@ import subprocess
|
||||
import pymem
|
||||
from pymem import pattern
|
||||
from pymem.exception import ProcessNotFound
|
||||
from worlds.jakanddaxter.locs import CellLocations as Cells, ScoutLocations as Flies
|
||||
|
||||
# Some helpful constants.
|
||||
next_cell_index_offset = 0 # Each of these is an uint64, so 8 bytes.
|
||||
next_buzzer_index_offset = 8 # Each of these is an uint64, so 8 bytes.
|
||||
cells_offset = 16
|
||||
buzzers_offset = 420 # cells_offset + (sizeof uint32 * 101 cells) = 16 + (4 * 101)
|
||||
end_marker_offset = 868 # buzzers_offset + (sizeof uint32 * 112 flies) = 420 + (4 * 112)
|
||||
|
||||
|
||||
class JakAndDaxterMemoryReader:
|
||||
@@ -14,7 +22,7 @@ class JakAndDaxterMemoryReader:
|
||||
marker_address = None
|
||||
goal_address = None
|
||||
|
||||
location_outbox = {}
|
||||
location_outbox = []
|
||||
outbox_index = 0
|
||||
|
||||
def __init__(self, marker: typing.ByteString = b'UnLiStEdStRaTs_JaK1\x00'):
|
||||
@@ -22,7 +30,6 @@ class JakAndDaxterMemoryReader:
|
||||
self.connected = self.connect()
|
||||
if self.connected and self.marker:
|
||||
self.marked = self.find_marker()
|
||||
pass
|
||||
|
||||
async def main_tick(self, location_callback: typing.Callable):
|
||||
self.read_memory()
|
||||
@@ -55,5 +62,17 @@ class JakAndDaxterMemoryReader:
|
||||
return True
|
||||
return False
|
||||
|
||||
def read_memory(self) -> typing.Dict:
|
||||
pass
|
||||
def read_memory(self) -> typing.List[int]:
|
||||
next_cell_index = int.from_bytes(self.process.read_bytes(self.goal_address, 8))
|
||||
next_buzzer_index = int.from_bytes(self.process.read_bytes(self.goal_address + next_buzzer_index_offset, 8))
|
||||
next_cell = int.from_bytes(self.process.read_bytes(self.goal_address + cells_offset + (next_cell_index * 4), 4))
|
||||
next_buzzer = int.from_bytes(self.process.read_bytes(self.goal_address + cells_offset + (next_buzzer_index * 4), 4))
|
||||
|
||||
if next_cell not in self.location_outbox:
|
||||
self.location_outbox.append(Cells.to_ap_id(next_cell))
|
||||
if next_buzzer not in self.location_outbox:
|
||||
self.location_outbox.append(Flies.to_ap_id(next_buzzer))
|
||||
|
||||
return self.location_outbox
|
||||
|
||||
|
||||
|
||||
@@ -22,29 +22,29 @@ class JakAndDaxterReplClient:
|
||||
self.port = port
|
||||
|
||||
async def init(self):
|
||||
self.connected = await self.connect()
|
||||
self.connected = self.connect()
|
||||
if self.connected:
|
||||
self.listening = await self.listen()
|
||||
self.listening = self.listen()
|
||||
if self.connected and self.listening:
|
||||
self.compiled = await self.compile()
|
||||
self.compiled = self.compile()
|
||||
|
||||
async def main_tick(self):
|
||||
|
||||
# Receive Items from AP. Handle 1 item per tick.
|
||||
if len(self.item_inbox) > self.inbox_index:
|
||||
await self.receive_item()
|
||||
self.receive_item()
|
||||
self.inbox_index += 1
|
||||
|
||||
# This helper function formats and sends `form` as a command to the REPL.
|
||||
# ALL commands to the REPL should be sent using this function.
|
||||
# TODO - this needs to block on receiving an acknowledgement from the REPL server.
|
||||
# Problem is, it doesn't ack anything right now. So we need that to happen first.
|
||||
async def send_form(self, form: str) -> None:
|
||||
def send_form(self, form: str) -> None:
|
||||
header = struct.pack("<II", len(form), 10)
|
||||
self.socket.sendall(header + form.encode())
|
||||
logger.info("Sent Form: " + form)
|
||||
|
||||
async def connect(self) -> bool:
|
||||
def connect(self) -> bool:
|
||||
if not self.ip or not self.port:
|
||||
return False
|
||||
|
||||
@@ -57,71 +57,67 @@ class JakAndDaxterReplClient:
|
||||
except ConnectionRefusedError:
|
||||
return False
|
||||
|
||||
async def listen(self) -> bool:
|
||||
await self.send_form("(lt)")
|
||||
def listen(self) -> bool:
|
||||
self.send_form("(lt)")
|
||||
return True
|
||||
|
||||
async def compile(self) -> bool:
|
||||
def compile(self) -> bool:
|
||||
# Show this visual cue when compilation is started.
|
||||
# It's the version number of the OpenGOAL Compiler.
|
||||
await self.send_form("(set! *debug-segment* #t)")
|
||||
self.send_form("(set! *debug-segment* #t)")
|
||||
|
||||
# Play this audio cue when compilation is started.
|
||||
# It's the sound you hear when you press START + CIRCLE to open the Options menu.
|
||||
await self.send_form("(dotimes (i 1) "
|
||||
"(sound-play-by-name "
|
||||
"(static-sound-name \"start-options\") "
|
||||
"(new-sound-id) 1024 0 0 (sound-group sfx) #t))")
|
||||
self.send_form("(dotimes (i 1) "
|
||||
"(sound-play-by-name "
|
||||
"(static-sound-name \"start-options\") "
|
||||
"(new-sound-id) 1024 0 0 (sound-group sfx) #t))")
|
||||
|
||||
# Start compilation. This is blocking, so nothing will happen until the REPL is done.
|
||||
await self.send_form("(mi)")
|
||||
self.send_form("(mi)")
|
||||
|
||||
# Play this audio cue when compilation is complete.
|
||||
# It's the sound you hear when you press START + START to close the Options menu.
|
||||
await self.send_form("(dotimes (i 1) "
|
||||
"(sound-play-by-name "
|
||||
"(static-sound-name \"menu close\") "
|
||||
"(new-sound-id) 1024 0 0 (sound-group sfx) #t))")
|
||||
self.send_form("(dotimes (i 1) "
|
||||
"(sound-play-by-name "
|
||||
"(static-sound-name \"menu-close\") "
|
||||
"(new-sound-id) 1024 0 0 (sound-group sfx) #t))")
|
||||
|
||||
# Disable cheat-mode and debug (close the visual cue).
|
||||
await self.send_form("(set! *cheat-mode* #f)")
|
||||
# await self.send_form("(set! *debug-segment* #f)")
|
||||
self.send_form("(set! *cheat-mode* #f)")
|
||||
# self.send_form("(set! *debug-segment* #f)")
|
||||
return True
|
||||
|
||||
async def verify(self) -> bool:
|
||||
await self.send_form("(dotimes (i 1) "
|
||||
"(sound-play-by-name "
|
||||
"(static-sound-name \"menu close\") "
|
||||
"(new-sound-id) 1024 0 0 (sound-group sfx) #t))")
|
||||
def verify(self) -> bool:
|
||||
self.send_form("(dotimes (i 1) "
|
||||
"(sound-play-by-name "
|
||||
"(static-sound-name \"menu-close\") "
|
||||
"(new-sound-id) 1024 0 0 (sound-group sfx) #t))")
|
||||
return True
|
||||
|
||||
async def receive_item(self):
|
||||
def receive_item(self):
|
||||
ap_id = self.item_inbox[self.inbox_index]["item"]
|
||||
|
||||
# Determine the type of item to receive.
|
||||
if ap_id in range(jak1_id, jak1_id + Flies.fly_offset):
|
||||
await self.receive_power_cell(ap_id)
|
||||
self.receive_power_cell(ap_id)
|
||||
|
||||
elif ap_id in range(jak1_id + Flies.fly_offset, jak1_id + Orbs.orb_offset):
|
||||
await self.receive_scout_fly(ap_id)
|
||||
self.receive_scout_fly(ap_id)
|
||||
|
||||
elif ap_id > jak1_id + Orbs.orb_offset:
|
||||
pass # TODO
|
||||
|
||||
# TODO - In ArchipelaGOAL, override the 'get-pickup event so that it doesn't give you the item,
|
||||
# it just plays the victory animation. Then define a new event type like 'get-archipelago
|
||||
# to actually give ourselves the item. See game-info.gc and target-handler.gc.
|
||||
|
||||
async def receive_power_cell(self, ap_id: int) -> None:
|
||||
def receive_power_cell(self, ap_id: int) -> None:
|
||||
cell_id = Cells.to_game_id(ap_id)
|
||||
await self.send_form("(send-event "
|
||||
"*target* \'get-archipelago "
|
||||
"(pickup-type fuel-cell) "
|
||||
"(the float " + str(cell_id) + "))")
|
||||
self.send_form("(send-event "
|
||||
"*target* \'get-archipelago "
|
||||
"(pickup-type fuel-cell) "
|
||||
"(the float " + str(cell_id) + "))")
|
||||
|
||||
async def receive_scout_fly(self, ap_id: int) -> None:
|
||||
def receive_scout_fly(self, ap_id: int) -> None:
|
||||
fly_id = Flies.to_game_id(ap_id)
|
||||
await self.send_form("(send-event "
|
||||
"*target* \'get-archipelago "
|
||||
"(pickup-type buzzer) "
|
||||
"(the float " + str(fly_id) + "))")
|
||||
self.send_form("(send-event "
|
||||
"*target* \'get-archipelago "
|
||||
"(pickup-type buzzer) "
|
||||
"(the float " + str(fly_id) + "))")
|
||||
|
||||
Reference in New Issue
Block a user