import time import struct from socket import socket, AF_INET, SOCK_STREAM import pymem from pymem.exception import ProcessNotFound, ProcessError from CommonClient import logger from worlds.jakanddaxter.GameID import jak1_id from worlds.jakanddaxter.Items import item_table from worlds.jakanddaxter.locs import ( CellLocations as Cells, ScoutLocations as Flies, OrbLocations as Orbs, SpecialLocations as Specials) class JakAndDaxterReplClient: ip: str port: int sock: socket connected: bool = False initiated_connect: bool = False # Signals when user tells us to try reconnecting. # The REPL client needs the REPL/compiler process running, but that process # also needs the game running. Therefore, the REPL client needs both running. gk_process: pymem.process = None goalc_process: pymem.process = None item_inbox = {} inbox_index = 0 def __init__(self, ip: str = "127.0.0.1", port: int = 8181): self.ip = ip self.port = port self.connect() async def main_tick(self): if self.initiated_connect: await self.connect() self.initiated_connect = False if self.connected: try: self.gk_process.read_bool(self.gk_process.base_address) # Ping to see if it's alive. except ProcessError: logger.error("The gk process has died. Restart the game and run \"/repl connect\" again.") self.connected = False try: self.goalc_process.read_bool(self.goalc_process.base_address) # Ping to see if it's alive. except ProcessError: logger.error("The goalc process has died. Restart the compiler and run \"/repl connect\" again.") self.connected = False else: return # Receive Items from AP. Handle 1 item per tick. if len(self.item_inbox) > self.inbox_index: 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 blocks on receiving an acknowledgement from the REPL server. But it doesn't print # any log info in the meantime. Is that a problem? def send_form(self, form: str, print_ok: bool = True) -> bool: header = struct.pack(" bool: cell_id = Cells.to_game_id(ap_id) ok = self.send_form("(send-event " "*target* \'get-archipelago " "(pickup-type fuel-cell) " "(the float " + str(cell_id) + "))") if ok: logger.info(f"Received a Power Cell!") else: logger.error(f"Unable to receive a Power Cell!") return ok def receive_scout_fly(self, ap_id: int) -> bool: fly_id = Flies.to_game_id(ap_id) ok = self.send_form("(send-event " "*target* \'get-archipelago " "(pickup-type buzzer) " "(the float " + str(fly_id) + "))") if ok: logger.info(f"Received a {item_table[ap_id]}!") else: logger.error(f"Unable to receive a {item_table[ap_id]}!") return ok def receive_special(self, ap_id: int) -> bool: special_id = Specials.to_game_id(ap_id) ok = self.send_form("(send-event " "*target* \'get-archipelago " "(pickup-type ap-special) " "(the float " + str(special_id) + "))") if ok: logger.info(f"Received special unlock {item_table[ap_id]}!") else: logger.error(f"Unable to receive special unlock {item_table[ap_id]}!") return ok