Network: implement 0.4 marked compatibility removals (#757)

* world remote items handling
* players list when connecting
This commit is contained in:
Fabian Dill 2022-12-11 02:59:17 +01:00 committed by GitHub
parent ce42fda85f
commit 2cdd03f786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 27 additions and 108 deletions

View File

@ -381,10 +381,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
"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,

View File

@ -148,8 +148,6 @@ class Context:
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
@ -366,8 +364,6 @@ class Context:
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']
for slot, data in self.slot_data.items():
@ -548,7 +544,7 @@ class Context:
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')
@ -708,10 +704,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,
@ -719,8 +712,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': sum(game_data["version"] for game_data in ctx.gamespackage.values())
if all(game_data["version"] for game_data in ctx.gamespackage.values()) else 0,
'datapackage_versions': {game: game_data["version"] for game, game_data
in ctx.gamespackage.items()},
'seed_name': ctx.seed_name,
@ -1557,20 +1548,10 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
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:

View File

@ -39,10 +39,11 @@ def get_datapackage():
@api_endpoints.route('/datapackage_version')
@cache.cached()
def get_datapackage_versions():
from worlds import network_data_package, 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

View File

@ -74,7 +74,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. |

View File

@ -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
@ -379,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
@ -415,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
@ -433,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

View File

@ -160,18 +160,6 @@ class World(metaclass=AutoWorldRegister):
hint_blacklist: ClassVar[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

View File

@ -81,13 +81,11 @@ for world_name, world in AutoWorldRegister.world_types.items():
lookup_any_location_id_to_name.update(world.location_id_to_name)
network_data_package: DataPackage = {
"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: "

View File

@ -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 patch_rom(world, rom, player, enemized):
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

View File

@ -121,8 +121,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()

View File

@ -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

View File

@ -30,9 +30,7 @@ class FF1World(World):
option_definitions = ff1_options
game = "Final Fantasy"
topology_present = False
remote_items = True
data_version = 2
remote_start_inventory = True
ff1_items = FF1Items()
ff1_locations = FF1Locations()

View File

@ -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

View File

@ -102,8 +102,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

View File

@ -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

View File

@ -38,9 +38,10 @@ class PokemonRedBlueWorld(World):
# -MuffinJets#4559
game = "Pokemon Red and Blue"
option_definitions = pokemon_rb_options
remote_items = False
data_version = 3
required_client_version = (0, 3, 7)
topology_present = False

View File

@ -93,9 +93,6 @@ class SMWorld(World):
location_name_to_id = {key: locations_start_id + value.Id for key, value in locationsDict.items() if value.Id != None}
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)

View File

@ -76,9 +76,6 @@ class SMZ3World(World):
for key, value in TotalSMZ3World(Config(), "", 0, "").locationLookup.items()}
web = SMZ3Web()
remote_items: bool = False
remote_start_inventory: bool = False
locationNamesGT: Set[str] = {loc.Name for loc in GanonsTower(None, None).Locations}
# first added for 0.2.6
@ -485,9 +482,9 @@ class SMZ3World(World):
return False
def create_item(self, name: str) -> Item:
return SMZ3Item(name,
return SMZ3Item(name,
ItemClassification.progression if SMZ3World.isProgression(TotalSMZ3Item.ItemType[name]) else ItemClassification.filler,
TotalSMZ3Item.ItemType[name], self.item_name_to_id[name],
TotalSMZ3Item.ItemType[name], self.item_name_to_id[name],
self.player,
TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[name], self))

View File

@ -153,7 +153,6 @@ class SoEWorld(World):
game: str = "Secret of Evermore"
option_definitions = soe_options
topology_present = False
remote_items = False
data_version = 4
web = SoEWebWorld()
required_client_version = (0, 3, 5)

View File

@ -43,7 +43,6 @@ class TimespinnerWorld(World):
option_definitions = timespinner_options
game = "Timespinner"
topology_present = True
remote_items = False
data_version = 10
web = TimespinnerWebWorld()

View File

@ -61,14 +61,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: