Network: implement 0.4 marked compatibility removals (#757)
* world remote items handling * players list when connecting
This commit is contained in:
parent
ce42fda85f
commit
2cdd03f786
4
Main.py
4
Main.py
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: "
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -43,7 +43,6 @@ class TimespinnerWorld(World):
|
|||
option_definitions = timespinner_options
|
||||
game = "Timespinner"
|
||||
topology_present = True
|
||||
remote_items = False
|
||||
data_version = 10
|
||||
web = TimespinnerWebWorld()
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue