Skip to content

Commit

Permalink
Remade branches because I'm stupid
Browse files Browse the repository at this point in the history
  • Loading branch information
Ehseezed committed Aug 15, 2024
1 parent 56aabe5 commit b2131e4
Show file tree
Hide file tree
Showing 10 changed files with 2,557 additions and 0 deletions.
231 changes: 231 additions & 0 deletions worlds/am2r/Client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import asyncio
import copy
import json
import time
import random
from asyncio import StreamReader, StreamWriter
from typing import List
from worlds.am2r.items import item_table
from worlds.am2r.locations import get_location_datas

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}")

def _cmd_septoggs(self):
"""Septogg information"""
logger.info("Hi, messenger for the co-creator of the Septoggs here. The Septoggs were creatures found in the \
original MII as platforms to help samus with Space Jumping, we wanted to include them along with the Blob Throwers to \
complete the enemy roster from the original game, but had to come up with another purpose for them to work besides \
floating platforms. They do help the player, which is most noticeable in randomizer modes, but they also act as \
environmental story telling, akin to the Zebesian Roaches and Tatori from Super Metroid. This can be seen with the Baby \
Septoggs randomly appearing in certain areas with camouflage of that environment, more and more babies appearing by \
Metroid husks in the breeding grounds after more Metroids are killed in the area (to show how much damage the Metroids \
can cause to the ecosystem and establish that Septoggs are scavengers), and Baby Septoggs staying close to Elder \
Septoggs (as they feel safe next to the durable Elders)")

def _cmd_credits(self):
"""Huge thanks to all the people listed here"""
logger.info("AM2R Multiworld Randomizer brought to you by:")
logger.info("Programmers: Ehseezed DodoBirb")
logger.info("Sprite Artists: Abyssal Creature, Mimolette")
logger.info("Special Thanks to all the beta testers and the AM2R Community Updates Team")
logger.info("And Variable who was conned into becoming a programmer to fix issues he found")


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")

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 item.item in item_id_to_game_id]
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:
if netitem.flags & 0b100 != 0:
gameitem = random.randint(0, 20)
else:
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
})

async def parse_payload(ctx: AM2RContext, data_decoded):
item_list = [game_id_to_location_id[int(location)] for location in data_decoded["Items"]]
game_finished = bool(int(data_decoded["GameCompleted"]))
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:
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": new_locations}])
if game_finished and not ctx.finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": 30}])
ctx.finished_game = True

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.password = data_decoded["SlotPass"]
ctx.client_requesting_scouts = not bool(int(data_decoded["SeedReceived"]))
await 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


def launch():
# 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):
random.seed()
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

parser = get_base_parser()
args = parser.parse_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()
111 changes: 111 additions & 0 deletions worlds/am2r/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import types
from typing import Dict
from .items import item_table
from .locations import get_location_datas, EventId
from .regions import create_regions_and_locations
from BaseClasses import Tutorial, Item
from .options import AM2R_options, MetroidsAreChecks
from worlds.AutoWorld import World, WebWorld
from worlds.LauncherComponents import Component, components, Type, launch_subprocess


def launch_client():
from .Client import launch
launch_subprocess(launch, name="AM2RClient")


components.append(Component("AM2R Client", "AM2RClient", func=launch_client, component_type=Type.CLIENT))


class AM2RWeb(WebWorld):
theme = "partyTime"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Archipelago AM2R software on your computer. This guide covers single-player, multiworld, and related software.",
"English",
"setup_en.md",
"setup/en",
["Zed"]
)]


class AM2RWorld(World):
"""
AM2R is a remake of the classic Metroid 2 game for the Game Boy that tries its best to keep the feel
of the original as well as filling in some gaps to more closely tie into Metroid Fusion and brings some
items from there as well.
"""
game = "AM2R"
option_definitions = options.AM2R_options
web = AM2RWeb()

item_name_to_id = items.item_name_to_id
location_name_to_id = {location.name: location.code for location in get_location_datas(None, None)}

item_name_groups = items.item_name_groups
data_version = 1

def fill_slot_data(self) -> Dict[str, object]:
return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions}

def create_regions(self) -> None:
create_regions_and_locations(self.multiworld, self.player)

def create_item(self, name: str) -> Item:
return items.create_item(self.player, name)

def create_items(self) -> None:
if self.options.MetroidsAreChecks != MetroidsAreChecks.option_include_A6:
self.multiworld.get_location("Deep Caves: Lil\' Bro", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Deep Caves: Big Sis", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Omega Nest: SA-X Queen Lucina", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Omega Nest: Epsilon", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Omega Nest: Druid", self.player).place_locked_item(self.create_item("Metroid"))
if self.options.MetroidsAreChecks == MetroidsAreChecks.option_disabled:
self.multiworld.get_location("The Forgotten Alpha", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Golden Temple: Friendly Spider", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Golden Temple Nest: Moe", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Golden Temple Nest: Larry", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Golden Temple Nest: Curly", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Main Caves: Freddy Fazbear", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Station: Turbine Terror", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Station: The Lookout", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Station: Recent Guardian", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Nest: EnderMahan", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Nest: Carnage Awful", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Nest: Venom Awesome", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Hydro Nest: Something More, Something Awesome",self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Nest: Mimolette", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Nest: The Big Cheese", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Nest: Mohwir", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Nest: Chirn", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Nest: BHHarbinger", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Nest: The Abyssal Creature", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Complex: Sisyphus", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Industrial Complex: And then there\'s this Asshole",self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Inside Industrial: Guardian of Doom Treadmill",self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Inside Industrial: Rawsome1234 by the Lava Lake",self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Dual Alphas: Marco", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Dual Alphas: Polo", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Mines: Unga", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Mines: Gunga", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("The Tower: Patricia", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("The Tower: Variable \"GUH\"", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Ruler of The Tower: Slagathor", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("The Tower: Mr.Sandman", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("The Tower: Anakin", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("The Tower: Xander", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("EMP: Sir Zeta Commander of the Alpha Squadron", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Alpha Squadron: Timmy", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Alpha Squadron: Tommy", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Alpha Squadron: Terry", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Alpha Squadron: Telly", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Alpha Squadron: Martin", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Underwater: Gamma Bros Mario", self.player).place_locked_item(self.create_item("Metroid"))
self.multiworld.get_location("Underwater: Gamma Bros Luigi", self.player).place_locked_item(self.create_item("Metroid"))

self.multiworld.get_location("The Last Metroid is in Captivity", self.player).place_locked_item(self.create_item("The Galaxy is at Peace"))
items.create_all_items(self.multiworld, self.player)

def set_rules(self) -> None:
self.multiworld.completion_condition[self.player] = lambda state: state.has("The Galaxy is at Peace", self.player)
Loading

0 comments on commit b2131e4

Please sign in to comment.