Move remote_items and _start_inventory from world to client (#227)

This commit is contained in:
black-sliver 2022-01-23 06:38:46 +01:00 committed by GitHub
parent 219bcb3521
commit f0cfe30a36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 21 deletions

View File

@ -118,6 +118,7 @@ class CommonContext():
game = None
ui = None
keep_alive_task = None
items_handling: typing.Optional[int] = None
def __init__(self, server_address, password):
# server state
@ -247,9 +248,9 @@ class CommonContext():
async def send_connect(self, **kwargs):
payload = {
"cmd": 'Connect',
'cmd': 'Connect',
'password': self.password, 'name': self.auth, 'version': Utils.version_tuple,
'tags': self.tags,
'tags': self.tags, 'items_handling': self.items_handling,
'uuid': Utils.get_unique_identifier(), 'game': self.game
}
if kwargs:
@ -469,6 +470,8 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
logger.error('Invalid password')
ctx.password = None
await ctx.server_auth(True)
elif 'InvalidItemsHandling' in errors:
raise Exception('The item handling flags requested by the client are not supported')
elif errors:
raise Exception("Unknown connection errors: " + str(errors))
else:
@ -589,6 +592,7 @@ if __name__ == '__main__':
class TextContext(CommonContext):
tags = {"AP", "IgnoreGame", "TextOnly"}
game = "Archipelago"
items_handling = 0 # don't receive any NetworkItems
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:

View File

@ -30,6 +30,9 @@ class FF1CommandProcessor(ClientCommandProcessor):
class FF1Context(CommonContext):
command_processor = FF1CommandProcessor
items_handling = 0b111 # full remote
def __init__(self, server_address, password):
super().__init__(server_address, password)
self.nes_streams: (StreamReader, StreamWriter) = None
@ -40,8 +43,6 @@ class FF1Context(CommonContext):
self.game = 'Final Fantasy'
self.awaiting_rom = False
command_processor = FF1CommandProcessor
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(FF1Context, self).server_auth(password_requested)

View File

@ -49,6 +49,7 @@ class FactorioCommandProcessor(ClientCommandProcessor):
class FactorioContext(CommonContext):
command_processor = FactorioCommandProcessor
game = "Factorio"
items_handling = 0b111 # full remote
# updated by spinup server
mod_version: Utils.Version = Utils.Version(0, 0, 0)

View File

@ -56,11 +56,19 @@ class Client(Endpoint):
self.messageprocessor = client_message_processor(ctx, self)
self.ctx = weakref.ref(ctx)
def parse_tags(self, ctx: Context):
self.remote_items = 'Tracker' in self.tags or self.slot in ctx.remote_items
self.remote_start_inventory = 'Tracker' in self.tags or self.slot in ctx.remote_start_inventory
self.no_items = 'TextOnly' in self.tags
self.no_locations = 'TextOnly' in self.tags or 'Tracker' in self.tags
@property
def items_handling(self):
if self.no_items:
return 0
return 1 + (self.remote_items << 1) + (self.remote_start_inventory << 2)
@items_handling.setter
def items_handling(self, value: int):
if not (value & 0b001) and (value & 0b110):
raise ValueError("Invalid flag combination")
self.no_items = not (value & 0b001)
self.remote_items = bool(value & 0b010)
self.remote_start_inventory = bool(value & 0b100)
@property
def name(self) -> str:
@ -1307,6 +1315,16 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
minver = ctx.minimum_client_versions[slot]
if minver > args['version']:
errors.add('IncompatibleVersion')
if args.get('items_handling', None) is None:
# fall back to load from multidata
client.no_items = False
client.remote_items = slot in ctx.remote_items
client.remote_start_inventory = slot in ctx.remote_start_inventory
else:
try:
client.items_handling = args['items_handling']
except (ValueError, TypeError):
errors.add('InvalidItemsHandling')
# only exact version match allowed
if ctx.compatibility == 0 and args['version'] != version_tuple:
@ -1327,7 +1345,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
ctx.clients[team][slot].append(client)
client.version = args['version']
client.tags = args['tags']
client.parse_tags(ctx)
client.no_locations = 'TextOnly' in client.tags or 'Tracker' in client.tags
reply = [{
"cmd": "Connected",
"team": client.team, "slot": client.slot,
@ -1338,7 +1356,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
}]
start_inventory = get_start_inventory(ctx, team, slot, client.remote_start_inventory)
items = get_received_items(ctx, client.team, client.slot, client.remote_items)
if start_inventory or items:
if (start_inventory or items) and not client.no_items:
reply.append({"cmd": 'ReceivedItems', "index": 0, "items": start_inventory + items})
client.send_index = len(start_inventory) + len(items)
if not client.auth: # if this was a Re-Connect, don't print to console
@ -1368,21 +1386,28 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
"original_cmd": cmd}])
return
if "tags" in args:
old_tags = client.tags
client.tags = args["tags"]
client.parse_tags(ctx)
if "Tracker" in old_tags != "Tracker" in client.tags \
or "TextOnly" in old_tags != "TextOnly" in client.tags:
if args.get('items_handling', None) is not None and client.items_handling != args['items_handling']:
try:
client.items_handling = args['items_handling']
start_inventory = get_start_inventory(ctx, client.team, client.slot, client.remote_start_inventory)
items = get_received_items(ctx, client.team, client.slot, client.remote_items)
if start_inventory or items:
if (items or start_inventory) and not client.no_items:
client.send_index = len(start_inventory) + len(items)
await ctx.send_msgs(client, [{"cmd": "ReceivedItems", "index": 0,
"items": start_inventory + items}])
else:
client.send_index = 0
except (ValueError, TypeError) as err:
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', 'type': 'arguments',
'text': f'Invalid items_handling: {err}',
'original_cmd': cmd}])
return
if "tags" in args:
old_tags = client.tags
client.tags = args["tags"]
if set(old_tags) != set(client.tags):
client.no_locations = 'TextOnly' in client.tags or 'Tracker' in client.tags
ctx.notify_all(
f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has changed tags "
f"from {old_tags} to {client.tags}.")
@ -1390,7 +1415,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
elif cmd == 'Sync':
start_inventory = get_start_inventory(ctx, client.team, client.slot, client.remote_start_inventory)
items = get_received_items(ctx, client.team, client.slot, client.remote_items)
if start_inventory or items:
if (start_inventory or items) and not client.no_items:
client.send_index = len(start_inventory) + len(items)
await ctx.send_msgs(client, [{"cmd": "ReceivedItems", "index": 0,
"items": start_inventory + items}])

View File

@ -102,6 +102,7 @@ class LttPCommandProcessor(ClientCommandProcessor):
class Context(CommonContext):
command_processor = LttPCommandProcessor
game = "A Link to the Past"
items_handling = None # set in game_watcher
def __init__(self, snes_address, server_address, password):
super(Context, self).__init__(server_address, password)
@ -901,8 +902,10 @@ async def game_watcher(ctx: Context):
continue
elif game_name == b"SM":
ctx.game = GAME_SM
ctx.items_handling = 0b001 # full local
else:
ctx.game = GAME_ALTTP
ctx.items_handling = 0b001 # full local
rom = await snes_read(ctx, SM_ROMNAME_START if ctx.game == GAME_SM else ROMNAME_START, ROMNAME_SIZE)
if rom is None or rom == bytes([0] * ROMNAME_SIZE):

View File

@ -99,13 +99,14 @@ Sent to clients when the server refuses connection. This is sent during the init
#### Arguments
| Name | Type | Notes |
| ---- | ---- | ----- |
| errors | list\[str\] | Optional. When provided, should contain any one of: `InvalidSlot`, `InvalidGame`, `SlotAlreadyTaken`, `IncompatibleVersion`, or `InvalidPassword`. |
| errors | list\[str\] | Optional. When provided, should contain any one of: `InvalidSlot`, `InvalidGame`, `SlotAlreadyTaken`, `IncompatibleVersion`, `InvalidPassword`, or `InvalidItemsHandling`. |
InvalidSlot indicates that the sent 'name' field did not match any auth entry on the server.
InvalidGame indicates that a correctly named slot was found, but the game for it mismatched.
SlotAlreadyTaken indicates a connection with a different uuid is already established.
IncompatibleVersion indicates a version mismatch.
InvalidPassword indicates the wrong, or no password when it was required, was sent.
InvalidItemsHandling indicates a wrong value type or flag combination was sent.
### Connected
Sent to clients when the connection handshake is successfully completed.
@ -214,17 +215,28 @@ Sent by the client to initiate a connection to an Archipelago game session.
| name | str | The player name for this client. |
| uuid | str | Unique identifier for player client. |
| version | [NetworkVersion](#NetworkVersion) | An object representing the Archipelago version this client supports. |
| items_handling | int | Flags configuring which items should be sent by the server. Read below for individual flags.
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. [Tags](#Tags) |
#### items_handling flags
| Value | Meaning |
| ----- | ------- |
| 0b000 | No ReceivedItems is sent to you, ever. |
| 0b001 | Indicates you get items sent from other worlds. |
| 0b010 | Indicates you get items sent from your own world. Requires 0b001 to be set. |
| 0b100 | Indicates you get your starting inventory sent. Requires 0b001 to be set. |
| null | Null or undefined loads settings from world definition for backwards compatibility. This is deprecated. |
#### Authentication
Many, if not all, other packets require a successfully authenticated client. This is described in more detail in [Archipelago Connection Handshake](#Archipelago-Connection-Handshake).
### ConnectUpdate
Update arguments from the Connect package, currently only updating tags is supported.
Update arguments from the Connect package, currently only updating tags and items_handling is supported.
#### Arguments
| Name | Type | Notes |
| ---- | ---- | ----- |
| items_handling | int | Flags configuring which items should be sent by the server.
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. [Tags](#Tags) |
### Sync