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

View File

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

View File

@ -49,6 +49,7 @@ class FactorioCommandProcessor(ClientCommandProcessor):
class FactorioContext(CommonContext): class FactorioContext(CommonContext):
command_processor = FactorioCommandProcessor command_processor = FactorioCommandProcessor
game = "Factorio" game = "Factorio"
items_handling = 0b111 # full remote
# updated by spinup server # updated by spinup server
mod_version: Utils.Version = Utils.Version(0, 0, 0) 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.messageprocessor = client_message_processor(ctx, self)
self.ctx = weakref.ref(ctx) self.ctx = weakref.ref(ctx)
def parse_tags(self, ctx: Context): @property
self.remote_items = 'Tracker' in self.tags or self.slot in ctx.remote_items def items_handling(self):
self.remote_start_inventory = 'Tracker' in self.tags or self.slot in ctx.remote_start_inventory if self.no_items:
self.no_items = 'TextOnly' in self.tags return 0
self.no_locations = 'TextOnly' in self.tags or 'Tracker' in self.tags 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 @property
def name(self) -> str: 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] minver = ctx.minimum_client_versions[slot]
if minver > args['version']: if minver > args['version']:
errors.add('IncompatibleVersion') 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 # only exact version match allowed
if ctx.compatibility == 0 and args['version'] != version_tuple: 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) ctx.clients[team][slot].append(client)
client.version = args['version'] client.version = args['version']
client.tags = args['tags'] client.tags = args['tags']
client.parse_tags(ctx) client.no_locations = 'TextOnly' in client.tags or 'Tracker' in client.tags
reply = [{ reply = [{
"cmd": "Connected", "cmd": "Connected",
"team": client.team, "slot": client.slot, "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) start_inventory = get_start_inventory(ctx, team, slot, client.remote_start_inventory)
items = get_received_items(ctx, client.team, client.slot, client.remote_items) 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}) reply.append({"cmd": 'ReceivedItems', "index": 0, "items": start_inventory + items})
client.send_index = len(start_inventory) + len(items) client.send_index = len(start_inventory) + len(items)
if not client.auth: # if this was a Re-Connect, don't print to console 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}]) "original_cmd": cmd}])
return return
if "tags" in args: if args.get('items_handling', None) is not None and client.items_handling != args['items_handling']:
old_tags = client.tags try:
client.tags = args["tags"] client.items_handling = args['items_handling']
client.parse_tags(ctx)
if "Tracker" in old_tags != "Tracker" in client.tags \
or "TextOnly" in old_tags != "TextOnly" in client.tags:
start_inventory = get_start_inventory(ctx, client.team, client.slot, client.remote_start_inventory) 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) 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) client.send_index = len(start_inventory) + len(items)
await ctx.send_msgs(client, [{"cmd": "ReceivedItems", "index": 0, await ctx.send_msgs(client, [{"cmd": "ReceivedItems", "index": 0,
"items": start_inventory + items}]) "items": start_inventory + items}])
else: else:
client.send_index = 0 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): if set(old_tags) != set(client.tags):
client.no_locations = 'TextOnly' in client.tags or 'Tracker' in client.tags
ctx.notify_all( ctx.notify_all(
f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has changed tags " f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has changed tags "
f"from {old_tags} to {client.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': elif cmd == 'Sync':
start_inventory = get_start_inventory(ctx, client.team, client.slot, client.remote_start_inventory) 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) 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) client.send_index = len(start_inventory) + len(items)
await ctx.send_msgs(client, [{"cmd": "ReceivedItems", "index": 0, await ctx.send_msgs(client, [{"cmd": "ReceivedItems", "index": 0,
"items": start_inventory + items}]) "items": start_inventory + items}])

View File

@ -102,6 +102,7 @@ class LttPCommandProcessor(ClientCommandProcessor):
class Context(CommonContext): class Context(CommonContext):
command_processor = LttPCommandProcessor command_processor = LttPCommandProcessor
game = "A Link to the Past" game = "A Link to the Past"
items_handling = None # set in game_watcher
def __init__(self, snes_address, server_address, password): def __init__(self, snes_address, server_address, password):
super(Context, self).__init__(server_address, password) super(Context, self).__init__(server_address, password)
@ -901,8 +902,10 @@ async def game_watcher(ctx: Context):
continue continue
elif game_name == b"SM": elif game_name == b"SM":
ctx.game = GAME_SM ctx.game = GAME_SM
ctx.items_handling = 0b001 # full local
else: else:
ctx.game = GAME_ALTTP 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) 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): 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 #### Arguments
| Name | Type | Notes | | 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. 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. 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. SlotAlreadyTaken indicates a connection with a different uuid is already established.
IncompatibleVersion indicates a version mismatch. IncompatibleVersion indicates a version mismatch.
InvalidPassword indicates the wrong, or no password when it was required, was sent. 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 ### Connected
Sent to clients when the connection handshake is successfully completed. 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. | | name | str | The player name for this client. |
| uuid | str | Unique identifier for player client. | | uuid | str | Unique identifier for player client. |
| version | [NetworkVersion](#NetworkVersion) | An object representing the Archipelago version this client supports. | | 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) | | 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 #### 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). 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 ### 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 #### Arguments
| Name | Type | Notes | | 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) | | tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. [Tags](#Tags) |
### Sync ### Sync