Skip to content

Commit

Permalink
Merge pull request #9 from Ehseezed/apworld-creation
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
add missing location IDs
  • Loading branch information
Ehseezed authored Feb 3, 2024
2 parents 61ae729 + 431496a commit f88904d
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 f88904d

Please sign in to comment.