Skip to content

Commit

Permalink
Add missing locations and IDs
Browse files Browse the repository at this point in the history
Add new root AM2RClient.py
Moved old AM2RClient.py to world root
Update __init__.py
  • Loading branch information
Ehseezed committed Jan 28, 2024
1 parent 61ae729 commit 431496a
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 197 deletions.
205 changes: 8 additions & 197 deletions AM2RClient.py
Original file line number Diff line number Diff line change
@@ -1,200 +1,11 @@
import asyncio
import copy
import json
import time
from asyncio import StreamReader, StreamWriter
from typing import List
from worlds.AM2R.items import item_table
from worlds.AM2R.locations import get_location_datas
from __future__ import annotations

import Utils
from Utils import async_start
from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandProcessor, logger, \
get_base_parser

CONNECTION_TIMING_OUT_STATUS = "Connection timing out"
CONNECTION_REFUSED_STATUS = "Connection Refused"
CONNECTION_RESET_STATUS = "Connection was reset"
CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
CONNECTION_CONNECTED_STATUS = "Connected"
CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
item_location_scouts = {}
item_id_to_game_id: dict = {item.code: item.game_id for item in item_table.values()}
location_id_to_game_id: dict = {location.code: location.game_id for location in get_location_datas(None, None)}
game_id_to_location_id: dict = {location.game_id: location.code for location in get_location_datas(None, None) if location.code != None}



class AM2RCommandProcessor(ClientCommandProcessor):
def __init__(self, ctx: CommonContext):
super().__init__(ctx)

def _cmd_am2r(self):
"""Check AM2R Connection State"""
if isinstance(self.ctx, AM2RContext):
logger.info(f"Connection Status: {self.ctx.am2r_status}")

class AM2RContext(CommonContext):
command_processor = AM2RCommandProcessor
game = 'AM2R'
items_handling = 0b111 # full remote

def __init__(self, server_address, password):
super().__init__(server_address, password)
self.waiting_for_client = False
self.am2r_streams: (StreamReader, StreamWriter) = None
self.am2r_sync_task = None
self.am2r_status = CONNECTION_INITIAL_STATUS
self.received_locscouts = False
self.metroids_required = 41
self.client_requesting_scouts = False

async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super().server_auth(password_requested)
if not self.auth:
self.waiting_for_client = True
logger.info('No AM2R details found. Reconnect to MW server after AM2R is connected.')
return

await self.send_connect()

def run_gui(self):
from kvui import GameManager

class AM2RManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "AM2R Multiworld Client"

self.ui = AM2RManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
import ModuleUpdate
ModuleUpdate.update()

def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.metroids_required = args["slot_data"]["MetroidsRequired"]
elif cmd == "LocationInfo":
logger.info("Received Location Info")
def get_payload(ctx: AM2RContext):
items_to_give = [item_id_to_game_id[item.item] for item in ctx.items_received]
if not ctx.locations_info:
locations = [location.code for location in get_location_datas(None, None) if location.code is not None]
async_start(ctx.send_msgs([{"cmd": "LocationScouts", "locations": locations, "create_as_hint": 0}]))
return json.dumps({
"cmd": "items", "items": items_to_give
})

if ctx.client_requesting_scouts:
itemdict = {}
for locationid, netitem in ctx.locations_info.items():
gamelocation = location_id_to_game_id[locationid]
if netitem.item in item_id_to_game_id:
gameitem = item_id_to_game_id[netitem.item]
else:
gameitem = 20
itemdict[gamelocation] = gameitem
print("Sending")
return json.dumps({
'cmd':"locations", 'items': itemdict, 'metroids': ctx.metroids_required
})
return json.dumps({
"cmd": "items", "items": items_to_give
})

def parse_payload(ctx: AM2RContext, data_decoded):
item_list = [game_id_to_location_id[int(location)] for location in data_decoded["Items"]]
item_set = set(item_list)
ctx.locations_checked = item_list
new_locations = [location for location in ctx.missing_locations if location in item_set]
if new_locations:
async_start(ctx.send_msgs([{"cmd": "LocationChecks", "locations": new_locations}]))


async def am2r_sync_task(ctx: AM2RContext):
logger.info("Starting AM2R connector, use /am2r for status information.")
while not ctx.exit_event.is_set():
error_status = None
if ctx.am2r_streams:
(reader, writer) = ctx.am2r_streams
msg = get_payload(ctx).encode()
writer.write(msg)
writer.write(b'\n')
try:
await asyncio.wait_for(writer.drain(), timeout=1.5)
try:
data = await asyncio.wait_for(reader.readline(), timeout=5)
data_decoded = json.loads(data.decode())
ctx.auth = data_decoded["SlotName"]
ctx.client_requesting_scouts = not bool(int(data_decoded["SeedReceived"]))
parse_payload(ctx, data_decoded)
except asyncio.TimeoutError:
logger.debug("Read Timed Out, Reconnecting")
error_status = CONNECTION_TIMING_OUT_STATUS
writer.close()
ctx.am2r_streams = None
except ConnectionResetError as e:
logger.debug("Read failed due to Connection Lost, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.am2r_streams = None
except TimeoutError:
logger.debug("Connection Timed Out, Reconnecting")
error_status = CONNECTION_TIMING_OUT_STATUS
writer.close()
ctx.am2r_streams = None
except ConnectionResetError:
logger.debug("Connection Lost, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.am2r_streams = None

if ctx.am2r_status == CONNECTION_TENTATIVE_STATUS:
if not error_status:
logger.info("Successfully Connected to AM2R")
ctx.am2r_status = CONNECTION_CONNECTED_STATUS
else:
ctx.am2r_status = f"Was tentatively connected but error occured: {error_status}"
elif error_status:
ctx.am2r_status = error_status
logger.info("Lost connection to AM2R and attempting to reconnect. Use /am2r for status updates")
else:
try:
logger.debug("Attempting to connect to AM2R")
ctx.am2r_streams = await asyncio.wait_for(asyncio.open_connection("127.0.0.1", 64197), timeout=10)
ctx.am2r_status = CONNECTION_TENTATIVE_STATUS
except TimeoutError:
logger.debug("Connection Timed Out, Trying Again")
ctx.am2r_status = CONNECTION_TIMING_OUT_STATUS
continue
except ConnectionRefusedError:
logger.debug("Connection Refused, Trying Again")
ctx.am2r_status = CONNECTION_REFUSED_STATUS
continue


if __name__ == '__main__':
# Text Mode to use !hint and such with games that have no text entry
Utils.init_logging("AM2RClient")

options = Utils.get_options()

async def main(args):
ctx = AM2RContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
ctx.am2r_sync_task = asyncio.create_task(am2r_sync_task(ctx), name="AM2R Sync")
await ctx.exit_event.wait()
ctx.server_address = None

await ctx.shutdown()

import colorama
from worlds.am2r.AM2RClient import launch
import Utils

parser = get_base_parser()
args = parser.parse_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()
if __name__ == "__main__":
Utils.init_logging("AM2RClient", exception_logger="Client")
launch()
Loading

0 comments on commit 431496a

Please sign in to comment.