From eb337473fd9443e05aa8561cc87174d4123f33a0 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 8 Jul 2022 03:17:29 +0200 Subject: [PATCH 1/7] Network: implement 0.4 marked compatibility removals --- CommonClient.py | 27 +++++++++++++++------------ Main.py | 4 ---- MultiServer.py | 32 +++++++------------------------- SNIClient.py | 2 +- worlds/AutoWorld.py | 12 ------------ worlds/alttp/__init__.py | 2 -- worlds/ff1/__init__.py | 2 -- worlds/oot/__init__.py | 2 -- worlds/sm/__init__.py | 3 --- worlds/smz3/__init__.py | 3 --- worlds/soe/__init__.py | 1 - worlds/timespinner/__init__.py | 1 - 12 files changed, 23 insertions(+), 68 deletions(-) diff --git a/CommonClient.py b/CommonClient.py index 76623ff3f2ce..e0a9c3078446 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -562,18 +562,21 @@ async def process_server_cmd(ctx: CommonContext, args: dict): f" for each location checked. Use !hint for more information.") ctx.hint_cost = int(args['hint_cost']) ctx.check_points = int(args['location_check_points']) - players = args.get("players", []) - if len(players) < 1: - logger.info('No player connected') - else: - players.sort() - current_team = -1 - logger.info('Connected Players:') - for network_player in players: - if network_player.team != current_team: - logger.info(f' Team #{network_player.team + 1}') - current_team = network_player.team - logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot)) + + if "players" in args: # TODO remove when servers sending this are outdated + players = args.get("players", []) + if len(players) < 1: + logger.info('No player connected') + else: + players.sort() + current_team = -1 + logger.info('Connected Players:') + for network_player in players: + if network_player.team != current_team: + logger.info(f' Team #{network_player.team + 1}') + current_team = network_player.team + logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot)) + # update datapackage await ctx.prepare_datapackage(set(args["games"]), args["datapackage_versions"]) diff --git a/Main.py b/Main.py index acbb4ad5cf20..9ebc3b1e2113 100644 --- a/Main.py +++ b/Main.py @@ -381,10 +381,6 @@ def precollect_hint(location): "names": names, # TODO: remove around 0.2.5 in favor of slot_info "games": games, # TODO: remove around 0.2.5 in favor of slot_info "connect_names": {name: (0, player) for player, name in world.player_name.items()}, - "remote_items": {player for player in world.player_ids if - world.worlds[player].remote_items}, - "remote_start_inventory": {player for player in world.player_ids if - world.worlds[player].remote_start_inventory}, "locations": locations_data, "checks_in_area": checks_in_area, "server_options": baked_server_options, diff --git a/MultiServer.py b/MultiServer.py index 06f9a9f9cda1..c09f14c88917 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -144,8 +144,6 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo self.player_name_lookup: typing.Dict[str, team_slot] = {} self.connect_names = {} # names of slots clients can connect to self.allow_forfeits = {} - self.remote_items = set() - self.remote_start_inventory = set() # player location_id item_id target_player_id self.locations = {} self.host = host @@ -318,8 +316,6 @@ def _load(self, decoded_obj: dict, use_embedded_server_options: bool): self.seed_name = decoded_obj["seed_name"] self.random.seed(self.seed_name) self.connect_names = decoded_obj['connect_names'] - self.remote_items = decoded_obj['remote_items'] - self.remote_start_inventory = decoded_obj.get('remote_start_inventory', decoded_obj['remote_items']) self.locations = decoded_obj['locations'] self.slot_data = decoded_obj['slot_data'] self.er_hint_data = {int(player): {int(address): name for address, name in loc_data.items()} @@ -488,7 +484,7 @@ def set_save(self, savedata: dict): if "stored_data" in savedata: self.stored_data = savedata["stored_data"] - # count items and slots from lists for item_handling = remote + # count items and slots from lists for items_handling = remote logging.info( f'Loaded save file with {sum([len(v) for k, v in self.received_items.items() if k[2]])} received items ' f'for {sum(k[2] for k in self.received_items)} players') @@ -631,10 +627,7 @@ async def on_client_connected(ctx: Context, client: Client): await ctx.send_msgs(client, [{ 'cmd': 'RoomInfo', 'password': bool(ctx.password), - # TODO remove around 0.4 - 'players': players, - # TODO convert to list of games present in 0.4 - 'games': [ctx.games[x] for x in range(1, len(ctx.games) + 1)], + 'games': {ctx.games[x] for x in range(1, len(ctx.games) + 1)}, # tags are for additional features in the communication. # Name them by feature or fork, as you feel is appropriate. 'tags': ctx.tags, @@ -1408,27 +1401,16 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict): else: team, slot = ctx.connect_names[args['name']] game = ctx.games[slot] - ignore_game = "IgnoreGame" in args["tags"] or ( # IgnoreGame is deprecated. TODO: remove after 0.3.3? - ("TextOnly" in args["tags"] or "Tracker" in args["tags"]) and not args.get("game")) + ignore_game = ("TextOnly" in args["tags"] or "Tracker" in args["tags"]) and not args.get("game") if not ignore_game and args['game'] != game: errors.add('InvalidGame') minver = min_client_version if ignore_game else 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 - await ctx.send_msgs(client, [{ - "cmd": "Print", "text": - "Warning: Client is not sending items_handling flags, " - "which will not be supported in the future."}]) - else: - try: - client.items_handling = args['items_handling'] - except (ValueError, TypeError): - errors.add('InvalidItemsHandling') + 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: diff --git a/SNIClient.py b/SNIClient.py index e313feff008c..44465ee0c1bb 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -1042,7 +1042,7 @@ async def game_watcher(ctx: Context): # versions lower than 0.3.0 dont have item handling flag nor remote item support romVersion = int(game_name[2:5].decode('UTF-8')) if romVersion < 30: - ctx.items_handling = 0b001 # full local + ctx.items_handling = 0b001 # full local else: item_handling = await snes_read(ctx, SM_REMOTE_ITEM_FLAG_ADDR, 1) ctx.items_handling = 0b001 if item_handling is None else item_handling[0] diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 5cc7d625905f..a5b8e7a5baae 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -140,18 +140,6 @@ class World(metaclass=AutoWorldRegister): hint_blacklist: FrozenSet[str] = frozenset() # any names that should not be hintable - # NOTE: remote_items and remote_start_inventory are now available in the network protocol for the client to set. - # These values will be removed. - # if a world is set to remote_items, then it just needs to send location checks to the server and the server - # sends back the items - # if a world is set to remote_items = False, then the server never sends an item where receiver == finder, - # the client finds its own items in its own world. - remote_items: bool = True - - # If remote_start_inventory is true, the start_inventory/world.precollected_items is sent on connection, - # otherwise the world implementation is in charge of writing the items to their output data. - remote_start_inventory: bool = True - # For games where after a victory it is impossible to go back in and get additional/remaining Locations checked. # this forces forfeit: auto for those games. forced_auto_forfeit: bool = False diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 8e4ec1c143d4..1d0d7cd45b68 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -119,8 +119,6 @@ class ALTTPWorld(World): location_name_to_id = lookup_name_to_id data_version = 8 - remote_items: bool = False - remote_start_inventory: bool = False required_client_version = (0, 3, 2) web = ALTTPWeb() diff --git a/worlds/ff1/__init__.py b/worlds/ff1/__init__.py index 761d9fbbe4cc..8d201690ee88 100644 --- a/worlds/ff1/__init__.py +++ b/worlds/ff1/__init__.py @@ -30,9 +30,7 @@ class FF1World(World): options = ff1_options game = "Final Fantasy" topology_present = False - remote_items = True data_version = 1 - remote_start_inventory = True ff1_items = FF1Items() ff1_locations = FF1Locations() diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index b640578c16ee..9d84d30b35d1 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -100,8 +100,6 @@ class OOTWorld(World): item_name_to_id = {item_name: oot_data_to_ap_id(data, False) for item_name, data in item_table.items() if data[2] is not None} location_name_to_id = location_name_to_id - remote_items: bool = False - remote_start_inventory: bool = False web = OOTWeb() data_version = 2 diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 29d428abc247..e3495964bc93 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -84,9 +84,6 @@ class SMWorld(World): location_name_to_id = locations_lookup_name_to_id web = SMWeb() - remote_items: bool = False - remote_start_inventory: bool = False - # changes to client DeathLink handling for 0.2.1 # changes to client Remote Item handling for 0.2.6 required_client_version = (0, 2, 6) diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index 7f05e0dfd529..7254451f8d17 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -61,9 +61,6 @@ class SMZ3World(World): location_name_to_id: Dict[str, int] = {key : locations_start_id + value.Id for key, value in TotalSMZ3World(Config({}), "", 0, "").locationLookup.items()} web = SMZ3Web() - remote_items: bool = False - remote_start_inventory: bool = False - # first added for 0.2.6 required_client_version = (0, 2, 6) diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index f00ebbe1434c..3f0713471b66 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -152,7 +152,6 @@ class SoEWorld(World): game: str = "Secret of Evermore" options = soe_options topology_present = False - remote_items = False data_version = 2 web = SoEWebWorld() required_client_version = (0, 2, 6) diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index d789e9ddef55..1d470466bc8d 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -43,7 +43,6 @@ class TimespinnerWorld(World): options = timespinner_options game = "Timespinner" topology_present = True - remote_items = False data_version = 10 web = TimespinnerWebWorld() From 24124055d5583131855806dcdbbd746b4ae78145 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 8 Jul 2022 16:08:28 +0200 Subject: [PATCH 2/7] Network: remove summed datapackage version --- MultiServer.py | 1 - WebHostLib/api/__init__.py | 3 +-- docs/network protocol.md | 1 - worlds/__init__.py | 2 -- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index c09f14c88917..88474823781c 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -635,7 +635,6 @@ async def on_client_connected(ctx: Context, client: Client): 'permissions': get_permissions(ctx), 'hint_cost': ctx.hint_cost, 'location_check_points': ctx.location_check_points, - 'datapackage_version': network_data_package["version"], 'datapackage_versions': {game: game_data["version"] for game, game_data in network_data_package["games"].items()}, 'seed_name': ctx.seed_name, diff --git a/WebHostLib/api/__init__.py b/WebHostLib/api/__init__.py index c2f9b3840f0b..7ac3bae8edf1 100644 --- a/WebHostLib/api/__init__.py +++ b/WebHostLib/api/__init__.py @@ -40,9 +40,8 @@ def get_datapackge(): @api_endpoints.route('/datapackage_version') @cache.cached() def get_datapackge_versions(): - from worlds import network_data_package, AutoWorldRegister + from worlds import AutoWorldRegister version_package = {game: world.data_version for game, world in AutoWorldRegister.world_types.items()} - version_package["version"] = network_data_package["version"] return version_package diff --git a/docs/network protocol.md b/docs/network protocol.md index b5553c372ce1..baad3c2b71a0 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -64,7 +64,6 @@ Sent to clients when they connect to an Archipelago server. | hint_cost | int | The amount of points it costs to receive a hint from the server. | | location_check_points | int | The amount of hint points you receive per item/location check completed. || | games | list\[str\] | List of games present in this multiworld. | -| datapackage_version | int | Sum of individual games' datapackage version. Deprecated. Use `datapackage_versions` instead. | | datapackage_versions | dict\[str, int\] | Data versions of the individual games' data packages the server will send. Used to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents). | | seed_name | str | uniquely identifying name of this generation | | time | float | Unix time stamp of "now". Send for time synchronization if wanted for things like the DeathLink Bounce. | diff --git a/worlds/__init__.py b/worlds/__init__.py index b9270836791b..9600fe60901c 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -33,13 +33,11 @@ lookup_any_location_id_to_name.update(world.location_id_to_name) network_data_package = { - "version": sum(world.data_version for world in AutoWorldRegister.world_types.values()), "games": games, } # Set entire datapackage to version 0 if any of them are set to 0 if any(not world.data_version for world in AutoWorldRegister.world_types.values()): - network_data_package["version"] = 0 import logging logging.warning(f"Datapackage is in custom mode. Custom Worlds: " f"{[world for world in AutoWorldRegister.world_types.values() if not world.data_version]}") From a7d0be6f8d94276508e55197e370f777b08adf03 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 27 Sep 2022 13:22:16 +0200 Subject: [PATCH 3/7] Update worlds/ff1/__init__.py --- worlds/ff1/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/worlds/ff1/__init__.py b/worlds/ff1/__init__.py index 45e72e761bee..3ac1f937b8a4 100644 --- a/worlds/ff1/__init__.py +++ b/worlds/ff1/__init__.py @@ -30,8 +30,6 @@ class FF1World(World): option_definitions = ff1_options game = "Final Fantasy" topology_present = False - - remote_items = True data_version = 2 remote_start_inventory = True From 86d10974b80f5974159fe25da8378909eaf3ff1a Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 27 Sep 2022 13:22:38 +0200 Subject: [PATCH 4/7] Update worlds/ff1/__init__.py --- worlds/ff1/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/ff1/__init__.py b/worlds/ff1/__init__.py index 3ac1f937b8a4..251671a26818 100644 --- a/worlds/ff1/__init__.py +++ b/worlds/ff1/__init__.py @@ -31,7 +31,6 @@ class FF1World(World): game = "Final Fantasy" topology_present = False data_version = 2 - remote_start_inventory = True ff1_items = FF1Items() ff1_locations = FF1Locations() From 849cfae6000e56afaf869e7f9263e57b6dac6f99 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Wed, 7 Dec 2022 07:13:05 +0100 Subject: [PATCH 5/7] Core: remove new remote_items instances --- worlds/alttp/Rom.py | 12 ++++++------ worlds/dark_souls_3/__init__.py | 2 -- worlds/hylics2/__init__.py | 2 -- worlds/overcooked2/__init__.py | 2 -- worlds/pokemon_rb/__init__.py | 1 - worlds/zillion/__init__.py | 8 -------- 6 files changed, 6 insertions(+), 21 deletions(-) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 18e09ab19407..268cb9ebffb9 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -795,11 +795,11 @@ def patch_rom(world, rom, player, enemized): itemid = 0x33 elif location.item.compass: itemid = 0x25 - if world.worlds[player].remote_items: # remote items does not currently work - itemid = list(location_table.keys()).index(location.name) + 1 - assert itemid < 0x100 - rom.write_byte(location.player_address, 0xFF) - elif location.item.player != player: + # if world.worlds[player].remote_items: # remote items does not currently work + # itemid = list(location_table.keys()).index(location.name) + 1 + # assert itemid < 0x100 + # rom.write_byte(location.player_address, 0xFF) + if location.item.player != player: if location.player_address is not None: rom.write_byte(location.player_address, min(location.item.player, ROM_PLAYER_LIMIT)) else: @@ -1654,7 +1654,7 @@ def get_reveal_bytes(itemName): write_strings(rom, world, player) # remote items flag, does not currently work - rom.write_byte(0x18637C, int(world.worlds[player].remote_items)) + rom.write_byte(0x18637C, 0) # set rom name # 21 bytes diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index b6979dfcb499..5239967037a7 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -51,8 +51,6 @@ class DarkSouls3World(World): game: str = "Dark Souls III" option_definitions = dark_souls_options topology_present: bool = True - remote_items: bool = False - remote_start_inventory: bool = False web = DarkSouls3Web() data_version = 4 base_id = 100000 diff --git a/worlds/hylics2/__init__.py b/worlds/hylics2/__init__.py index 26a1a1131b35..f26af4beac00 100644 --- a/worlds/hylics2/__init__.py +++ b/worlds/hylics2/__init__.py @@ -36,8 +36,6 @@ class Hylics2World(World): option_definitions = Options.hylics2_options topology_present: bool = True - remote_items: bool = True - remote_start_inventory: bool = True data_version: 1 diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py index 9b217e15366b..b58d8fccb0e5 100644 --- a/worlds/overcooked2/__init__.py +++ b/worlds/overcooked2/__init__.py @@ -49,8 +49,6 @@ class Overcooked2World(World): required_client_version = (0, 3, 4) option_definitions = overcooked_options topology_present: bool = False - remote_items: bool = True - remote_start_inventory: bool = False data_version = 2 item_name_to_id = item_name_to_id diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 8d31f6633b4c..b0c994db0eeb 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -38,7 +38,6 @@ class PokemonRedBlueWorld(World): # -MuffinJets#4559 game = "Pokemon Red and Blue" option_definitions = pokemon_rb_options - remote_items = False data_version = 1 topology_present = False diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index 0b9b0e51400c..d0b65671395f 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -60,14 +60,6 @@ class ZillionWorld(World): # retrieved by clients on every connection. data_version: int = 1 - # NOTE: remote_items and remote_start_inventory are now available in the network protocol for the client to set. - # These values will be removed. - # if a world is set to remote_items, then it just needs to send location checks to the server and the server - # sends back the items - # if a world is set to remote_items = False, then the server never sends an item where receiver == finder, - # the client finds its own items in its own world. - remote_items: bool = False - logger: logging.Logger class LogStreamInterface: From 561561bfa32a64a05932d7440ea52207e769d893 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 11 Dec 2022 02:43:55 +0100 Subject: [PATCH 6/7] Docs: no need to document what doesn't exist --- docs/world api.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/world api.md b/docs/world api.md index d95627ddb324..77b6d8cf230c 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -343,18 +343,6 @@ class MyGameWorld(World): option_definitions = mygame_options # assign the options dict to the world #... ``` - -### Local or Remote - -A world with `remote_items` set to `True` gets all items items from the server -and no item from the local game. So for an RPG opening a chest would not add -any item to your inventory, instead the server will send you what was in that -chest. The advantage is that a generic mod can be used that does not need to -know anything about the seed. - -A world with `remote_items` set to `False` will locally reward its local items. -For console games this can remove delay and make script/animation/dialog flow -more natural. These games typically have been edited to 'bake in' the items. ### A World Class Skeleton From 69e47466757f7b9a8ac4c7599a03def745b09fb9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 11 Dec 2022 02:54:34 +0100 Subject: [PATCH 7/7] Docs: clean up world api --- docs/world api.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/world api.md b/docs/world api.md index 77b6d8cf230c..9961ccb4e5f6 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -367,8 +367,6 @@ class MyGameWorld(World): game: str = "My Game" # name of the game/world option_definitions = mygame_options # options the player can set topology_present: bool = True # show path to required location checks in spoiler - remote_items: bool = False # True if all items come from the server - remote_start_inventory: bool = False # True if start inventory comes from the server # data_version is used to signal that items, locations or their names # changed. Set this to 0 during development so other games' clients do not @@ -403,17 +401,13 @@ The world has to provide the following things for generation * additions to the item pool * additions to the regions list: at least one called "Menu" * locations placed inside those regions -* a `def create_item(self, item: str) -> MyGameItem` for plando/manual placing -* applying `self.world.precollected_items` for plando/start inventory - if not using a `remote_start_inventory` +* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand +* applying `self.world.push_precollected` for start inventory * a `def generate_output(self, output_directory: str)` that creates the output - if there is output to be generated. If only items are randomized and - `remote_items = True` it is possible to have a generic mod and output - generation can be skipped. In all other cases this is required. When this is - called, `self.world.get_locations()` has all locations for all players, with - properties `item` pointing to the item and `player` identifying the player. - `self.world.get_filled_locations(self.player)` will filter for this world. - `item.player` can be used to see if it's a local item. + files if there is output to be generated. When this is + called, `self.world.get_locations(self.player)` has all locations for the player, with + attribute `item` pointing to the item. + `location.item.player` can be used to see if it's a local item. In addition, the following methods can be implemented and attributes can be set @@ -421,12 +415,13 @@ In addition, the following methods can be implemented and attributes can be set called per player before any items or locations are created. You can set properties on your world here. Already has access to player options and RNG. * `def create_regions(self)` - called to place player's regions into the MultiWorld's regions list. If it's + called to place player's regions and their locations into the MultiWorld's regions list. If it's hard to separate, this can be done during `generate_early` or `basic` as well. * `def create_items(self)` called to place player's items into the MultiWorld's itempool. * `def set_rules(self)` - called to set access and item rules on locations and entrances. + called to set access and item rules on locations and entrances. + Locations have to be defined before this, or rule application can miss them. * `def generate_basic(self)` called after the previous steps. Some placement and player specific randomizations can be done here. After this step all regions and items have