Move remote_items and _start_inventory from world to client (#227)
This commit is contained in:
parent
219bcb3521
commit
f0cfe30a36
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}])
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue