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
 | 
			
		||||
    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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue