diff --git a/AdventureClient.py b/AdventureClient.py
index 06e4d60d..7bfbd5ef 100644
--- a/AdventureClient.py
+++ b/AdventureClient.py
@@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
if ': !' not in msg:
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "ReceivedItems":
- msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}"
+ msg = f"Received {', '.join([self.item_names.lookup_in_slot(item.item) for item in args['items']])}"
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "Retrieved":
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
diff --git a/CommonClient.py b/CommonClient.py
index 63cac098..8af822cb 100644
--- a/CommonClient.py
+++ b/CommonClient.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import collections
import copy
import logging
import asyncio
@@ -8,6 +9,7 @@ import sys
import typing
import time
import functools
+import warnings
import ModuleUpdate
ModuleUpdate.update()
@@ -173,10 +175,74 @@ class CommonContext:
items_handling: typing.Optional[int] = None
want_slot_data: bool = True # should slot_data be retrieved via Connect
- # data package
- # Contents in flux until connection to server is made, to download correct data for this multiworld.
- item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
- location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
+ class NameLookupDict:
+ """A specialized dict, with helper methods, for id -> name item/location data package lookups by game."""
+ def __init__(self, ctx: CommonContext, lookup_type: typing.Literal["item", "location"]):
+ self.ctx: CommonContext = ctx
+ self.lookup_type: typing.Literal["item", "location"] = lookup_type
+ self._unknown_item: typing.Callable[[int], str] = lambda key: f"Unknown {lookup_type} (ID: {key})"
+ self._archipelago_lookup: typing.Dict[int, str] = {}
+ self._flat_store: typing.Dict[int, str] = Utils.KeyedDefaultDict(self._unknown_item)
+ self._game_store: typing.Dict[str, typing.ChainMap[int, str]] = collections.defaultdict(
+ lambda: collections.ChainMap(self._archipelago_lookup, Utils.KeyedDefaultDict(self._unknown_item)))
+ self.warned: bool = False
+
+ # noinspection PyTypeChecker
+ def __getitem__(self, key: str) -> typing.Mapping[int, str]:
+ # TODO: In a future version (0.6.0?) this should be simplified by removing implicit id lookups support.
+ if isinstance(key, int):
+ if not self.warned:
+ # Use warnings instead of logger to avoid deprecation message from appearing on user side.
+ self.warned = True
+ warnings.warn(f"Implicit name lookup by id only is deprecated and only supported to maintain "
+ f"backwards compatibility for now. If multiple games share the same id for a "
+ f"{self.lookup_type}, name could be incorrect. Please use "
+ f"`{self.lookup_type}_names.lookup_in_game()` or "
+ f"`{self.lookup_type}_names.lookup_in_slot()` instead.")
+ return self._flat_store[key] # type: ignore
+
+ return self._game_store[key]
+
+ def __len__(self) -> int:
+ return len(self._game_store)
+
+ def __iter__(self) -> typing.Iterator[str]:
+ return iter(self._game_store)
+
+ def __repr__(self) -> str:
+ return self._game_store.__repr__()
+
+ def lookup_in_game(self, code: int, game_name: typing.Optional[str] = None) -> str:
+ """Returns the name for an item/location id in the context of a specific game or own game if `game` is
+ omitted.
+ """
+ if game_name is None:
+ game_name = self.ctx.game
+ assert game_name is not None, f"Attempted to lookup {self.lookup_type} with no game name available."
+
+ return self._game_store[game_name][code]
+
+ def lookup_in_slot(self, code: int, slot: typing.Optional[int] = None) -> str:
+ """Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is
+ omitted.
+ """
+ if slot is None:
+ slot = self.ctx.slot
+ assert slot is not None, f"Attempted to lookup {self.lookup_type} with no slot info available."
+
+ return self.lookup_in_game(code, self.ctx.slot_info[slot].game)
+
+ def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int]) -> None:
+ """Overrides existing lookup tables for a particular game."""
+ id_to_name_lookup_table = Utils.KeyedDefaultDict(self._unknown_item)
+ id_to_name_lookup_table.update({code: name for name, code in name_to_id_lookup_table.items()})
+ self._game_store[game] = collections.ChainMap(self._archipelago_lookup, id_to_name_lookup_table)
+ self._flat_store.update(id_to_name_lookup_table) # Only needed for legacy lookup method.
+ if game == "Archipelago":
+ # Keep track of the Archipelago data package separately so if it gets updated in a custom datapackage,
+ # it updates in all chain maps automatically.
+ self._archipelago_lookup.clear()
+ self._archipelago_lookup.update(id_to_name_lookup_table)
# defaults
starting_reconnect_delay: int = 5
@@ -231,7 +297,7 @@ class CommonContext:
# message box reporting a loss of connection
_messagebox_connection_loss: typing.Optional["kvui.MessageBox"] = None
- def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None:
+ def __init__(self, server_address: typing.Optional[str] = None, password: typing.Optional[str] = None) -> None:
# server state
self.server_address = server_address
self.username = None
@@ -271,6 +337,9 @@ class CommonContext:
self.exit_event = asyncio.Event()
self.watcher_event = asyncio.Event()
+ self.item_names = self.NameLookupDict(self, "item")
+ self.location_names = self.NameLookupDict(self, "location")
+
self.jsontotextparser = JSONtoTextParser(self)
self.rawjsontotextparser = RawJSONtoTextParser(self)
self.update_data_package(network_data_package)
@@ -486,19 +555,17 @@ class CommonContext:
or remote_checksum != cache_checksum:
needed_updates.add(game)
else:
- self.update_game(cached_game)
+ self.update_game(cached_game, game)
if needed_updates:
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
- def update_game(self, game_package: dict):
- for item_name, item_id in game_package["item_name_to_id"].items():
- self.item_names[item_id] = item_name
- for location_name, location_id in game_package["location_name_to_id"].items():
- self.location_names[location_id] = location_name
+ def update_game(self, game_package: dict, game: str):
+ self.item_names.update_game(game, game_package["item_name_to_id"])
+ self.location_names.update_game(game, game_package["location_name_to_id"])
def update_data_package(self, data_package: dict):
for game, game_data in data_package["games"].items():
- self.update_game(game_data)
+ self.update_game(game_data, game)
def consume_network_data_package(self, data_package: dict):
self.update_data_package(data_package)
diff --git a/MultiServer.py b/MultiServer.py
index 4fb03732..2c08b0b4 100644
--- a/MultiServer.py
+++ b/MultiServer.py
@@ -168,9 +168,11 @@ class Context:
slot_info: typing.Dict[int, NetworkSlot]
generator_version = Version(0, 0, 0)
checksums: typing.Dict[str, str]
- item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
+ item_names: typing.Dict[str, typing.Dict[int, str]] = (
+ collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')))
item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
- location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
+ location_names: typing.Dict[str, typing.Dict[int, str]] = (
+ collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')))
location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
all_item_and_group_names: typing.Dict[str, typing.Set[str]]
all_location_and_group_names: typing.Dict[str, typing.Set[str]]
@@ -271,14 +273,21 @@ class Context:
if "checksum" in game_package:
self.checksums[game_name] = game_package["checksum"]
for item_name, item_id in game_package["item_name_to_id"].items():
- self.item_names[item_id] = item_name
+ self.item_names[game_name][item_id] = item_name
for location_name, location_id in game_package["location_name_to_id"].items():
- self.location_names[location_id] = location_name
+ self.location_names[game_name][location_id] = location_name
self.all_item_and_group_names[game_name] = \
set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name])
self.all_location_and_group_names[game_name] = \
set(game_package["location_name_to_id"]) | set(self.location_name_groups.get(game_name, []))
+ archipelago_item_names = self.item_names["Archipelago"]
+ archipelago_location_names = self.location_names["Archipelago"]
+ for game in [game_name for game_name in self.gamespackage if game_name != "Archipelago"]:
+ # Add Archipelago items and locations to each data package.
+ self.item_names[game].update(archipelago_item_names)
+ self.location_names[game].update(archipelago_location_names)
+
def item_names_for_game(self, game: str) -> typing.Optional[typing.Dict[str, int]]:
return self.gamespackage[game]["item_name_to_id"] if game in self.gamespackage else None
@@ -783,10 +792,7 @@ async def on_client_connected(ctx: Context, client: Client):
for slot, connected_clients in clients.items():
if connected_clients:
name = ctx.player_names[team, slot]
- players.append(
- NetworkPlayer(team, slot,
- ctx.name_aliases.get((team, slot), name), name)
- )
+ players.append(NetworkPlayer(team, slot, ctx.name_aliases.get((team, slot), name), name))
games = {ctx.games[x] for x in range(1, len(ctx.games) + 1)}
games.add("Archipelago")
await ctx.send_msgs(client, [{
@@ -801,8 +807,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_versions': {game: game_data["version"] for game, game_data
- in ctx.gamespackage.items() if game in games},
'datapackage_checksums': {game: game_data["checksum"] for game, game_data
in ctx.gamespackage.items() if game in games and "checksum" in game_data},
'seed_name': ctx.seed_name,
@@ -1006,8 +1010,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
send_items_to(ctx, team, target_player, new_item)
ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % (
- team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id],
- ctx.player_names[(team, target_player)], ctx.location_names[location]))
+ team + 1, ctx.player_names[(team, slot)], ctx.item_names[ctx.slot_info[target_player].game][item_id],
+ ctx.player_names[(team, target_player)], ctx.location_names[ctx.slot_info[slot].game][location]))
info_text = json_format_send_event(new_item, target_player)
ctx.broadcast_team(team, [info_text])
@@ -1061,8 +1065,8 @@ def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location
def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
- f"{ctx.item_names[hint.item]} is " \
- f"at {ctx.location_names[hint.location]} " \
+ f"{ctx.item_names[ctx.slot_info[hint.receiving_player].game][hint.item]} is " \
+ f"at {ctx.location_names[ctx.slot_info[hint.finding_player].game][hint.location]} " \
f"in {ctx.player_names[team, hint.finding_player]}'s World"
if hint.entrance:
@@ -1364,7 +1368,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
if self.ctx.remaining_mode == "enabled":
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
- self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
+ self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
@@ -1377,7 +1381,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
- self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
+ self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
@@ -1395,7 +1399,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
locations = get_missing_checks(self.ctx, self.client.team, self.client.slot)
if locations:
- names = [self.ctx.location_names[location] for location in locations]
+ game = self.ctx.slot_info[self.client.slot].game
+ names = [self.ctx.location_names[game][location] for location in locations]
if filter_text:
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
if filter_text in location_groups: # location group name
@@ -1420,7 +1425,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
locations = get_checked_checks(self.ctx, self.client.team, self.client.slot)
if locations:
- names = [self.ctx.location_names[location] for location in locations]
+ game = self.ctx.slot_info[self.client.slot].game
+ names = [self.ctx.location_names[game][location] for location in locations]
if filter_text:
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
if filter_text in location_groups: # location group name
@@ -1501,10 +1507,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
elif input_text.isnumeric():
game = self.ctx.games[self.client.slot]
hint_id = int(input_text)
- hint_name = self.ctx.item_names[hint_id] \
- if not for_location and hint_id in self.ctx.item_names \
- else self.ctx.location_names[hint_id] \
- if for_location and hint_id in self.ctx.location_names \
+ hint_name = self.ctx.item_names[game][hint_id] \
+ if not for_location and hint_id in self.ctx.item_names[game] \
+ else self.ctx.location_names[game][hint_id] \
+ if for_location and hint_id in self.ctx.location_names[game] \
else None
if hint_name in self.ctx.non_hintable_names[game]:
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
diff --git a/NetUtils.py b/NetUtils.py
index 8fc3929e..076fdc3b 100644
--- a/NetUtils.py
+++ b/NetUtils.py
@@ -247,7 +247,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
def _handle_item_id(self, node: JSONMessagePart):
item_id = int(node["text"])
- node["text"] = self.ctx.item_names[item_id]
+ node["text"] = self.ctx.item_names.lookup_in_slot(item_id, node["player"])
return self._handle_item_name(node)
def _handle_location_name(self, node: JSONMessagePart):
@@ -255,8 +255,8 @@ class JSONtoTextParser(metaclass=HandlerMeta):
return self._handle_color(node)
def _handle_location_id(self, node: JSONMessagePart):
- item_id = int(node["text"])
- node["text"] = self.ctx.location_names[item_id]
+ location_id = int(node["text"])
+ node["text"] = self.ctx.location_names.lookup_in_slot(location_id, node["player"])
return self._handle_location_name(node)
def _handle_entrance_name(self, node: JSONMessagePart):
diff --git a/UndertaleClient.py b/UndertaleClient.py
index e1538ce8..cdc21c56 100644
--- a/UndertaleClient.py
+++ b/UndertaleClient.py
@@ -247,8 +247,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
toDraw = ""
for i in range(20):
- if i < len(str(ctx.item_names[l.item])):
- toDraw += str(ctx.item_names[l.item])[i]
+ if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
+ toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
else:
break
f.write(toDraw)
diff --git a/Utils.py b/Utils.py
index 78027199..9f683721 100644
--- a/Utils.py
+++ b/Utils.py
@@ -46,7 +46,7 @@ class Version(typing.NamedTuple):
return ".".join(str(item) for item in self)
-__version__ = "0.4.6"
+__version__ = "0.5.0"
version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux")
@@ -458,6 +458,9 @@ class KeyedDefaultDict(collections.defaultdict):
"""defaultdict variant that uses the missing key as argument to default_factory"""
default_factory: typing.Callable[[typing.Any], typing.Any]
+ def __init__(self, default_factory: typing.Callable[[Any], Any] = None, **kwargs):
+ super().__init__(default_factory, **kwargs)
+
def __missing__(self, key):
self[key] = value = self.default_factory(key)
return value
diff --git a/WargrooveClient.py b/WargrooveClient.py
index 77180502..c5fdeb35 100644
--- a/WargrooveClient.py
+++ b/WargrooveClient.py
@@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
if not os.path.isfile(path):
open(path, 'w').close()
# Announcing commander unlocks
- item_name = self.item_names[network_item.item]
+ item_name = self.item_names.lookup_in_slot(network_item.item)
if item_name in faction_table.keys():
for commander in faction_table[item_name]:
logger.info(f"{commander.name} has been unlocked!")
@@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
open(print_path, 'w').close()
with open(print_path, 'w') as f:
f.write("Received " +
- self.item_names[network_item.item] +
+ self.item_names.lookup_in_slot(network_item.item) +
" from " +
self.player_names[network_item.player])
f.close()
@@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
faction_items = 0
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
for network_item in self.items_received:
- if self.item_names[network_item.item] in faction_item_names:
+ if self.item_names.lookup_in_slot(network_item.item) in faction_item_names:
faction_items += 1
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
# Must be an integer larger than 0
diff --git a/WebHostLib/api/__init__.py b/WebHostLib/api/__init__.py
index cfdbe25f..22d1f19f 100644
--- a/WebHostLib/api/__init__.py
+++ b/WebHostLib/api/__init__.py
@@ -56,15 +56,6 @@ def get_datapackage():
return network_data_package
-@api_endpoints.route('/datapackage_version')
-@cache.cached()
-def get_datapackage_versions():
- from worlds import AutoWorldRegister
-
- version_package = {game: world.data_version for game, world in AutoWorldRegister.world_types.items()}
- return version_package
-
-
@api_endpoints.route('/datapackage_checksum')
@cache.cached()
def get_datapackage_checksums():
diff --git a/WebHostLib/templates/ootTracker.html b/WebHostLib/templates/ootTracker.html
deleted file mode 100644
index ea7a6d5a..00000000
--- a/WebHostLib/templates/ootTracker.html
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-
- {{ player_name }}'s Tracker
-
-
-
-
-
-
-
-
-  |
-  |
-  |
-  |
-  |
-  |
-  |
-
-
-  |
-  |
-
-
- 
- {{ hookshot_length }}
-
- |
-  |
-  |
-  |
-  |
-
-
-  |
-  |
-  |
-  |
-  |
-  |
-  |
-
-
-
-
- 
- {{ bottle_count if bottle_count > 0 else '' }}
-
- |
-  |
-  |
-  |
-
-
- 
- {{ wallet_size }}
-
- |
-  |
-  |
-
-
-
-
- 
- Zelda
-
- |
-
-
- 
- Epona
-
- |
-
-
- 
- Saria
-
- |
-
-
- 
- Sun
-
- |
-
-
- 
- Time
-
- |
-
-
- 
- Storms
-
- |
-
-
- 
- {{ token_count }}
-
- |
-
-
-
-
- 
- Min
-
- |
-
-
- 
- Bol
-
- |
-
-
- 
- Ser
-
- |
-
-
- 
- Req
-
- |
-
-
- 
- Noc
-
- |
-
-
- 
- Pre
-
- |
-
-
- 
- {{ piece_count if piece_count > 0 else '' }}
-
- |
-
-
-
-
- |
-  |
-  |
- Items |
-
- {% for area in checks_done %}
-
-
- {% for location in location_info[area] %}
-
- {{ location }} |
- |
- |
- {{ '✔' if location_info[area][location] else '' }} |
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
diff --git a/Zelda1Client.py b/Zelda1Client.py
index cd76a0a5..6d7af0a9 100644
--- a/Zelda1Client.py
+++ b/Zelda1Client.py
@@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext):
def reconcile_shops(ctx: ZeldaContext):
- checked_location_names = [ctx.location_names[location] for location in ctx.checked_locations]
+ checked_location_names = [ctx.location_names.lookup_in_slot(location) for location in ctx.checked_locations]
shops = [location for location in checked_location_names if "Shop" in location]
left_slots = [shop for shop in shops if "Left" in shop]
middle_slots = [shop for shop in shops if "Middle" in shop]
@@ -190,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone=
locations_checked = []
location = None
for location in ctx.missing_locations:
- location_name = ctx.location_names[location]
+ location_name = ctx.location_names.lookup_in_slot(location)
if location_name in Locations.overworld_locations and zone == "overworld":
status = locations_array[Locations.major_location_offsets[location_name]]
diff --git a/docs/network protocol.md b/docs/network protocol.md
index 604ff670..da5c4143 100644
--- a/docs/network protocol.md
+++ b/docs/network protocol.md
@@ -53,7 +53,7 @@ Example:
```
## (Server -> Client)
-These packets are are sent from the multiworld server to the client. They are not messages which the server accepts.
+These packets are sent from the multiworld server to the client. They are not messages which the server accepts.
* [RoomInfo](#RoomInfo)
* [ConnectionRefused](#ConnectionRefused)
* [Connected](#Connected)
@@ -80,7 +80,6 @@ Sent to clients when they connect to an Archipelago server.
| hint_cost | int | The percentage of total locations that need to be checked 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_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). **Deprecated. Use `datapackage_checksums` instead.** |
| datapackage_checksums | dict[str, str] | Checksum hash of the individual games' data packages the server will send. Used by newer clients to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents) for more information. |
| 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. |
@@ -500,9 +499,9 @@ In JSON this may look like:
{"item": 3, "location": 3, "player": 3, "flags": 0}
]
```
-`item` is the item id of the item. Item ids are in the range of ± 253-1.
+`item` is the item id of the item. Item ids are only supported in the range of [-253, 253 - 1], with anything ≤ 0 reserved for Archipelago use.
-`location` is the location id of the item inside the world. Location ids are in the range of ± 253-1.
+`location` is the location id of the item inside the world. Location ids are only supported in the range of [-253, 253 - 1], with anything ≤ 0 reserved for Archipelago use.
`player` is the player slot of the world the item is located in, except when inside an [LocationInfo](#LocationInfo) Packet then it will be the slot of the player to receive the item
@@ -646,15 +645,47 @@ class Hint(typing.NamedTuple):
```
### Data Package Contents
-A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago server most easily. Currently, this package is used to send ID to name mappings so that clients need not maintain their own mappings.
+A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago
+server most easily and not maintain their own mappings. Some contents include:
-We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different version. A special case is datapackage version 0, where it is expected the package is custom and should not be cached.
+ - Name to ID mappings for items and locations.
+ - A checksum of each game's data package for clients to tell if a cached package is invalid.
-Note:
- * Any ID is unique to its type across AP: Item 56 only exists once and Location 56 only exists once.
- * Any Name is unique to its type across its own Game only: Single Arrow can exist in two games.
- * The IDs from the game "Archipelago" may be used in any other game.
- Especially Location ID -1: Cheat Console and -2: Server (typically Remote Start Inventory)
+We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know
+when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different checksum
+than any locally cached ones.
+
+**Important Notes about IDs and Names**:
+
+* IDs ≤ 0 are reserved for "Archipelago" and should not be used by other world implementations.
+* The IDs from the game "Archipelago" (in `worlds/generic`) may be used in any world.
+ * Especially Location ID `-1`: `Cheat Console` and `-2`: `Server` (typically Remote Start Inventory)
+* Any names and IDs are only unique in its own world data package, but different games may reuse these names or IDs.
+ * At runtime, you will need to look up the game of the player to know which item or location ID/Name to lookup in the
+ data package. This can be easily achieved by reviewing the `slot_info` for a particular player ID prior to lookup.
+ * For example, a data package like this is valid (Some properties such as `checksum` were omitted):
+ ```json
+ {
+ "games": {
+ "Game A": {
+ "location_name_to_id": {
+ "Boss Chest": 40
+ },
+ "item_name_to_id": {
+ "Item X": 12
+ }
+ },
+ "Game B": {
+ "location_name_to_id": {
+ "Minigame Prize": 40
+ },
+ "item_name_to_id": {
+ "Item X": 40
+ }
+ }
+ }
+ }
+ ```
#### Contents
| Name | Type | Notes |
@@ -668,7 +699,6 @@ GameData is a **dict** but contains these keys and values. It's broken out into
|---------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------|
| item_name_to_id | dict[str, int] | Mapping of all item names to their respective ID. |
| location_name_to_id | dict[str, int] | Mapping of all location names to their respective ID. |
-| version | int | Version number of this game's data. Deprecated. Used by older clients to request an updated datapackage if cache is outdated. |
| checksum | str | A checksum hash of this game's data. |
### Tags
diff --git a/kvui.py b/kvui.py
index a1663126..98aa9516 100644
--- a/kvui.py
+++ b/kvui.py
@@ -683,10 +683,18 @@ class HintLog(RecycleView):
for hint in hints:
data.append({
"receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
- "item": {"text": self.parser.handle_node(
- {"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})},
+ "item": {"text": self.parser.handle_node({
+ "type": "item_id",
+ "text": hint["item"],
+ "flags": hint["item_flags"],
+ "player": hint["receiving_player"],
+ })},
"finding": {"text": self.parser.handle_node({"type": "player_id", "text": hint["finding_player"]})},
- "location": {"text": self.parser.handle_node({"type": "location_id", "text": hint["location"]})},
+ "location": {"text": self.parser.handle_node({
+ "type": "location_id",
+ "text": hint["location"],
+ "player": hint["finding_player"],
+ })},
"entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text",
"color": "blue", "text": hint["entrance"]
if hint["entrance"] else "Vanilla"})},
diff --git a/test/general/test_ids.py b/test/general/test_ids.py
index 98c41b67..e4010af3 100644
--- a/test/general/test_ids.py
+++ b/test/general/test_ids.py
@@ -6,22 +6,6 @@ from . import setup_solo_multiworld
class TestIDs(unittest.TestCase):
- def test_unique_items(self):
- """Tests that every game has a unique ID per item in the datapackage"""
- known_item_ids = set()
- for gamename, world_type in AutoWorldRegister.world_types.items():
- current = len(known_item_ids)
- known_item_ids |= set(world_type.item_id_to_name)
- self.assertEqual(len(known_item_ids) - len(world_type.item_id_to_name), current)
-
- def test_unique_locations(self):
- """Tests that every game has a unique ID per location in the datapackage"""
- known_location_ids = set()
- for gamename, world_type in AutoWorldRegister.world_types.items():
- current = len(known_location_ids)
- known_location_ids |= set(world_type.location_id_to_name)
- self.assertEqual(len(known_location_ids) - len(world_type.location_id_to_name), current)
-
def test_range_items(self):
"""There are Javascript clients, which are limited to Number.MAX_SAFE_INTEGER due to 64bit float precision."""
for gamename, world_type in AutoWorldRegister.world_types.items():
diff --git a/test/programs/test_common_client.py b/test/programs/test_common_client.py
new file mode 100644
index 00000000..9936240d
--- /dev/null
+++ b/test/programs/test_common_client.py
@@ -0,0 +1,106 @@
+import unittest
+
+import NetUtils
+from CommonClient import CommonContext
+
+
+class TestCommonContext(unittest.IsolatedAsyncioTestCase):
+ async def asyncSetUp(self):
+ self.ctx = CommonContext()
+ self.ctx.slot = 1 # Pretend we're player 1 for this.
+ self.ctx.slot_info.update({
+ 1: NetUtils.NetworkSlot("Player 1", "__TestGame1", NetUtils.SlotType.player),
+ 2: NetUtils.NetworkSlot("Player 2", "__TestGame1", NetUtils.SlotType.player),
+ 3: NetUtils.NetworkSlot("Player 3", "__TestGame2", NetUtils.SlotType.player),
+ })
+ self.ctx.consume_players_package([
+ NetUtils.NetworkPlayer(1, 1, "Player 1", "Player 1"),
+ NetUtils.NetworkPlayer(1, 2, "Player 2", "Player 2"),
+ NetUtils.NetworkPlayer(1, 3, "Player 3", "Player 3"),
+ ])
+ # Using IDs outside the "safe range" for testing purposes only. If this fails unit tests, it's because
+ # another world is not following the spec for allowed ID ranges.
+ self.ctx.update_data_package({
+ "games": {
+ "__TestGame1": {
+ "location_name_to_id": {
+ "Test Location 1 - Safe": 2**54 + 1,
+ "Test Location 2 - Duplicate": 2**54 + 2,
+ },
+ "item_name_to_id": {
+ "Test Item 1 - Safe": 2**54 + 1,
+ "Test Item 2 - Duplicate": 2**54 + 2,
+ },
+ },
+ "__TestGame2": {
+ "location_name_to_id": {
+ "Test Location 3 - Duplicate": 2**54 + 2,
+ },
+ "item_name_to_id": {
+ "Test Item 3 - Duplicate": 2**54 + 2,
+ },
+ },
+ },
+ })
+
+ async def test_archipelago_datapackage_lookups_exist(self):
+ assert "Archipelago" in self.ctx.item_names, "Archipelago item names entry does not exist"
+ assert "Archipelago" in self.ctx.location_names, "Archipelago location names entry does not exist"
+
+ async def test_implicit_name_lookups(self):
+ # Items
+ assert self.ctx.item_names[2**54 + 1] == "Test Item 1 - Safe"
+ assert self.ctx.item_names[2**54 + 3] == f"Unknown item (ID: {2**54+3})"
+ assert self.ctx.item_names[-1] == "Nothing"
+
+ # Locations
+ assert self.ctx.location_names[2**54 + 1] == "Test Location 1 - Safe"
+ assert self.ctx.location_names[2**54 + 3] == f"Unknown location (ID: {2**54+3})"
+ assert self.ctx.location_names[-1] == "Cheat Console"
+
+ async def test_explicit_name_lookups(self):
+ # Items
+ assert self.ctx.item_names["__TestGame1"][2**54+1] == "Test Item 1 - Safe"
+ assert self.ctx.item_names["__TestGame1"][2**54+2] == "Test Item 2 - Duplicate"
+ assert self.ctx.item_names["__TestGame1"][2**54+3] == f"Unknown item (ID: {2**54+3})"
+ assert self.ctx.item_names["__TestGame1"][-1] == "Nothing"
+ assert self.ctx.item_names["__TestGame2"][2**54+1] == f"Unknown item (ID: {2**54+1})"
+ assert self.ctx.item_names["__TestGame2"][2**54+2] == "Test Item 3 - Duplicate"
+ assert self.ctx.item_names["__TestGame2"][2**54+3] == f"Unknown item (ID: {2**54+3})"
+ assert self.ctx.item_names["__TestGame2"][-1] == "Nothing"
+
+ # Locations
+ assert self.ctx.location_names["__TestGame1"][2**54+1] == "Test Location 1 - Safe"
+ assert self.ctx.location_names["__TestGame1"][2**54+2] == "Test Location 2 - Duplicate"
+ assert self.ctx.location_names["__TestGame1"][2**54+3] == f"Unknown location (ID: {2**54+3})"
+ assert self.ctx.location_names["__TestGame1"][-1] == "Cheat Console"
+ assert self.ctx.location_names["__TestGame2"][2**54+1] == f"Unknown location (ID: {2**54+1})"
+ assert self.ctx.location_names["__TestGame2"][2**54+2] == "Test Location 3 - Duplicate"
+ assert self.ctx.location_names["__TestGame2"][2**54+3] == f"Unknown location (ID: {2**54+3})"
+ assert self.ctx.location_names["__TestGame2"][-1] == "Cheat Console"
+
+ async def test_lookup_helper_functions(self):
+ # Checking own slot.
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1) == "Test Item 1 - Safe"
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2) == "Test Item 2 - Duplicate"
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 3) == f"Unknown item (ID: {2 ** 54 + 3})"
+ assert self.ctx.item_names.lookup_in_slot(-1) == f"Nothing"
+
+ # Checking others' slots.
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1, 2) == "Test Item 1 - Safe"
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2, 2) == "Test Item 2 - Duplicate"
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1, 3) == f"Unknown item (ID: {2 ** 54 + 1})"
+ assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2, 3) == "Test Item 3 - Duplicate"
+
+ # Checking by game.
+ assert self.ctx.item_names.lookup_in_game(2 ** 54 + 1, "__TestGame1") == "Test Item 1 - Safe"
+ assert self.ctx.item_names.lookup_in_game(2 ** 54 + 2, "__TestGame1") == "Test Item 2 - Duplicate"
+ assert self.ctx.item_names.lookup_in_game(2 ** 54 + 3, "__TestGame1") == f"Unknown item (ID: {2 ** 54 + 3})"
+ assert self.ctx.item_names.lookup_in_game(2 ** 54 + 1, "__TestGame2") == f"Unknown item (ID: {2 ** 54 + 1})"
+ assert self.ctx.item_names.lookup_in_game(2 ** 54 + 2, "__TestGame2") == "Test Item 3 - Duplicate"
+
+ # Checking with Archipelago ids are valid in any game package.
+ assert self.ctx.item_names.lookup_in_slot(-1, 2) == "Nothing"
+ assert self.ctx.item_names.lookup_in_slot(-1, 3) == "Nothing"
+ assert self.ctx.item_names.lookup_in_game(-1, "__TestGame1") == "Nothing"
+ assert self.ctx.item_names.lookup_in_game(-1, "__TestGame2") == "Nothing"
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py
index 5d674c0c..6e17f023 100644
--- a/worlds/AutoWorld.py
+++ b/worlds/AutoWorld.py
@@ -258,18 +258,6 @@ class World(metaclass=AutoWorldRegister):
location_name_groups: ClassVar[Dict[str, Set[str]]] = {}
"""maps location group names to sets of locations. Example: {"Sewer": {"Sewer Key Drop 1", "Sewer Key Drop 2"}}"""
- data_version: ClassVar[int] = 0
- """
- Increment this every time something in your world's names/id mappings changes.
-
- When this is set to 0, that world's DataPackage is considered in "testing mode", which signals to servers/clients
- that it should not be cached, and clients should request that world's DataPackage every connection. Not
- recommended for production-ready worlds.
-
- Deprecated. Clients should utilize `checksum` to determine if DataPackage has changed since last connection and
- request a new DataPackage, if necessary.
- """
-
required_client_version: Tuple[int, int, int] = (0, 1, 6)
"""
override this if changes to a world break forward-compatibility of the client
@@ -543,7 +531,6 @@ class World(metaclass=AutoWorldRegister):
"item_name_to_id": cls.item_name_to_id,
"location_name_groups": sorted_location_name_groups,
"location_name_to_id": cls.location_name_to_id,
- "version": cls.data_version,
}
res["checksum"] = data_package_checksum(res)
return res
diff --git a/worlds/__init__.py b/worlds/__init__.py
index 53b0c5ce..09f72882 100644
--- a/worlds/__init__.py
+++ b/worlds/__init__.py
@@ -33,7 +33,6 @@ class GamesPackage(TypedDict, total=False):
location_name_groups: Dict[str, List[str]]
location_name_to_id: Dict[str, int]
checksum: str
- version: int # TODO: Remove support after per game data packages API change.
class DataPackage(TypedDict):
diff --git a/worlds/adventure/__init__.py b/worlds/adventure/__init__.py
index 84caca82..1c2583b3 100644
--- a/worlds/adventure/__init__.py
+++ b/worlds/adventure/__init__.py
@@ -113,7 +113,6 @@ class AdventureWorld(World):
settings: ClassVar[AdventureSettings]
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
- data_version: ClassVar[int] = 1
required_client_version: Tuple[int, int, int] = (0, 3, 9)
def __init__(self, world: MultiWorld, player: int):
diff --git a/worlds/alttp/Client.py b/worlds/alttp/Client.py
index 5b27f559..db7555f2 100644
--- a/worlds/alttp/Client.py
+++ b/worlds/alttp/Client.py
@@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
def new_check(location_id):
new_locations.append(location_id)
ctx.locations_checked.add(location_id)
- location = ctx.location_names[location_id]
+ location = ctx.location_names.lookup_in_slot(location_id)
snes_logger.info(
f'New Check: {location} ' +
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
@@ -552,9 +552,9 @@ class ALTTPSNIClient(SNIClient):
item = ctx.items_received[recv_index]
recv_index += 1
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
- color(ctx.item_names[item.item], 'red', 'bold'),
+ color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
- ctx.location_names[item.location], recv_index, len(ctx.items_received)))
+ ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
snes_buffered_write(ctx, RECV_PROGRESS_ADDR,
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py
index ae3dfe9e..3176f7a7 100644
--- a/worlds/alttp/__init__.py
+++ b/worlds/alttp/__init__.py
@@ -213,7 +213,6 @@ class ALTTPWorld(World):
item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int}
location_name_to_id = lookup_name_to_id
- data_version = 9
required_client_version = (0, 4, 1)
web = ALTTPWeb()
diff --git a/worlds/bk_sudoku/__init__.py b/worlds/bk_sudoku/__init__.py
index 195339c3..2c57bc73 100644
--- a/worlds/bk_sudoku/__init__.py
+++ b/worlds/bk_sudoku/__init__.py
@@ -34,7 +34,6 @@ class Bk_SudokuWorld(World):
"""
game = "Sudoku"
web = Bk_SudokuWebWorld()
- data_version = 1
item_name_to_id: Dict[str, int] = {}
location_name_to_id: Dict[str, int] = {}
diff --git a/worlds/blasphemous/__init__.py b/worlds/blasphemous/__init__.py
index 9abcd81b..a46fb55b 100644
--- a/worlds/blasphemous/__init__.py
+++ b/worlds/blasphemous/__init__.py
@@ -32,7 +32,6 @@ class BlasphemousWorld(World):
game: str = "Blasphemous"
web = BlasphemousWeb()
- data_version = 2
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
diff --git a/worlds/bumpstik/__init__.py b/worlds/bumpstik/__init__.py
index d922c027..fe261dc9 100644
--- a/worlds/bumpstik/__init__.py
+++ b/worlds/bumpstik/__init__.py
@@ -39,8 +39,6 @@ class BumpStikWorld(World):
location_name_to_id = location_table
item_name_groups = item_groups
- data_version = 1
-
required_client_version = (0, 3, 8)
options: BumpstikOptions
diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py
index b70c65bb..c8b9587f 100644
--- a/worlds/checksfinder/__init__.py
+++ b/worlds/checksfinder/__init__.py
@@ -33,8 +33,6 @@ class ChecksFinderWorld(World):
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
- data_version = 4
-
def _get_checksfinder_data(self):
return {
'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32),
diff --git a/worlds/clique/__init__.py b/worlds/clique/__init__.py
index 30c0e47f..b5cc74d9 100644
--- a/worlds/clique/__init__.py
+++ b/worlds/clique/__init__.py
@@ -37,7 +37,6 @@ class CliqueWorld(World):
"""The greatest game of all time."""
game = "Clique"
- data_version = 3
web = CliqueWebWorld()
option_definitions = clique_options
location_name_to_id = location_table
diff --git a/worlds/cv64/__init__.py b/worlds/cv64/__init__.py
index 2f483cd4..0d384acc 100644
--- a/worlds/cv64/__init__.py
+++ b/worlds/cv64/__init__.py
@@ -64,7 +64,6 @@ class CV64World(World):
options: CV64Options
settings: typing.ClassVar[CV64Settings]
topology_present = True
- data_version = 1
item_name_to_id = get_item_names_to_ids()
location_name_to_id = get_location_names_to_ids()
diff --git a/worlds/cv64/client.py b/worlds/cv64/client.py
index ff9c79f5..bea8ce38 100644
--- a/worlds/cv64/client.py
+++ b/worlds/cv64/client.py
@@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
text_color = bytearray([0xA2, 0x0B])
else:
text_color = bytearray([0xA2, 0x02])
- received_text, num_lines = cv64_text_wrap(f"{ctx.item_names[next_item.item]}\n"
+ received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_slot(next_item.item)}\n"
f"from {ctx.player_names[next_item.player]}", 96)
await bizhawk.guarded_write(ctx.bizhawk_ctx,
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py
index c4b2232b..02001098 100644
--- a/worlds/dark_souls_3/__init__.py
+++ b/worlds/dark_souls_3/__init__.py
@@ -49,7 +49,6 @@ class DarkSouls3World(World):
option_definitions = dark_souls_options
topology_present: bool = True
web = DarkSouls3Web()
- data_version = 8
base_id = 100000
enabled_location_categories: Set[DS3LocationCategory]
required_client_version = (0, 4, 2)
diff --git a/worlds/dkc3/Client.py b/worlds/dkc3/Client.py
index efa199e1..8e4a1bf2 100644
--- a/worlds/dkc3/Client.py
+++ b/worlds/dkc3/Client.py
@@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
for new_check_id in new_checks:
ctx.locations_checked.add(new_check_id)
- location = ctx.location_names[new_check_id]
+ location = ctx.location_names.lookup_in_slot(new_check_id)
snes_logger.info(
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
@@ -99,9 +99,9 @@ class DKC3SNIClient(SNIClient):
item = ctx.items_received[recv_index]
recv_index += 1
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
- color(ctx.item_names[item.item], 'red', 'bold'),
+ color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
- ctx.location_names[item.location], recv_index, len(ctx.items_received)))
+ ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
if item.item in item_rom_data:
diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py
index f2981149..de6fb4a4 100644
--- a/worlds/dkc3/__init__.py
+++ b/worlds/dkc3/__init__.py
@@ -61,7 +61,6 @@ class DKC3World(World):
options: DKC3Options
topology_present = False
- data_version = 2
#hint_blacklist = {LocationName.rocket_rush_flag}
item_name_to_id = {name: data.code for name, data in item_table.items()}
diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py
index ca286211..a9dfcc50 100644
--- a/worlds/dlcquest/__init__.py
+++ b/worlds/dlcquest/__init__.py
@@ -43,8 +43,6 @@ class DLCqworld(World):
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = location_table
- data_version = 1
-
options_dataclass = DLCQuestOptions
options: DLCQuestOptions
diff --git a/worlds/doom_1993/__init__.py b/worlds/doom_1993/__init__.py
index ace33f99..b6138ae0 100644
--- a/worlds/doom_1993/__init__.py
+++ b/worlds/doom_1993/__init__.py
@@ -42,7 +42,6 @@ class DOOM1993World(World):
options: DOOM1993Options
game = "DOOM 1993"
web = DOOM1993Web()
- data_version = 3
required_client_version = (0, 3, 9)
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
diff --git a/worlds/doom_ii/__init__.py b/worlds/doom_ii/__init__.py
index daad9455..38840f55 100644
--- a/worlds/doom_ii/__init__.py
+++ b/worlds/doom_ii/__init__.py
@@ -43,7 +43,6 @@ class DOOM2World(World):
options: DOOM2Options
game = "DOOM II"
web = DOOM2Web()
- data_version = 3
required_client_version = (0, 3, 9)
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
diff --git a/worlds/factorio/Client.py b/worlds/factorio/Client.py
index d245e1bb..258a5445 100644
--- a/worlds/factorio/Client.py
+++ b/worlds/factorio/Client.py
@@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext):
if ctx.locations_checked != research_data:
bridge_logger.debug(
f"New researches done: "
- f"{[ctx.location_names[rid] for rid in research_data - ctx.locations_checked]}")
+ f"{[ctx.location_names.lookup_in_slot(rid) for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
death_link_tick = data.get("death_link_tick", 0)
@@ -360,7 +360,7 @@ async def factorio_server_watcher(ctx: FactorioContext):
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
item_id = transfer_item.item
player_name = ctx.player_names[transfer_item.player]
- item_name = ctx.item_names[item_id]
+ item_name = ctx.item_names.lookup_in_slot(item_id)
factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
ctx.send_index += 1
diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py
index 3b747573..1ea2f6e4 100644
--- a/worlds/factorio/__init__.py
+++ b/worlds/factorio/__init__.py
@@ -95,7 +95,6 @@ class Factorio(World):
item_name_groups = {
"Progressive": set(progressive_tech_table.keys()),
}
- data_version = 8
required_client_version = (0, 4, 2)
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
diff --git a/worlds/ff1/__init__.py b/worlds/ff1/__init__.py
index ce5519b1..3a504750 100644
--- a/worlds/ff1/__init__.py
+++ b/worlds/ff1/__init__.py
@@ -40,7 +40,6 @@ class FF1World(World):
settings_key = "ffr_options"
game = "Final Fantasy"
topology_present = False
- data_version = 2
ff1_items = FF1Items()
ff1_locations = FF1Locations()
diff --git a/worlds/ffmq/__init__.py b/worlds/ffmq/__init__.py
index b995cc42..ac3e9137 100644
--- a/worlds/ffmq/__init__.py
+++ b/worlds/ffmq/__init__.py
@@ -56,8 +56,6 @@ class FFMQWorld(World):
create_regions = create_regions
set_rules = set_rules
stage_set_rules = stage_set_rules
-
- data_version = 1
web = FFMQWebWorld()
# settings: FFMQSettings
@@ -216,4 +214,3 @@ class FFMQWorld(World):
hint_data[self.player][location.address] += f"/{hint}"
else:
hint_data[self.player][location.address] = hint
-
diff --git a/worlds/generic/__init__.py b/worlds/generic/__init__.py
index 6b2ffdfe..b88295b4 100644
--- a/worlds/generic/__init__.py
+++ b/worlds/generic/__init__.py
@@ -40,7 +40,6 @@ class GenericWorld(World):
}
hidden = True
web = GenericWeb()
- data_version = 1
def generate_early(self):
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
diff --git a/worlds/heretic/__init__.py b/worlds/heretic/__init__.py
index c83cdb94..fc5ffdd2 100644
--- a/worlds/heretic/__init__.py
+++ b/worlds/heretic/__init__.py
@@ -41,7 +41,6 @@ class HereticWorld(World):
options: HereticOptions
game = "Heretic"
web = HereticWeb()
- data_version = 3
required_client_version = (0, 3, 9)
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py
index 3530030f..fdaece8d 100644
--- a/worlds/hk/__init__.py
+++ b/worlds/hk/__init__.py
@@ -154,7 +154,6 @@ class HKWorld(World):
ranges: typing.Dict[str, typing.Tuple[int, int]]
charm_costs: typing.List[int]
cached_filler_items = {}
- data_version = 2
def __init__(self, world, player):
super(HKWorld, self).__init__(world, player)
diff --git a/worlds/hylics2/__init__.py b/worlds/hylics2/__init__.py
index be7ebf19..18bcb0ed 100644
--- a/worlds/hylics2/__init__.py
+++ b/worlds/hylics2/__init__.py
@@ -37,8 +37,6 @@ class Hylics2World(World):
options_dataclass = Hylics2Options
options: Hylics2Options
- data_version = 3
-
def set_rules(self):
Rules.set_rules(self)
diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py
index e33a680b..6faa8206 100644
--- a/worlds/kdl3/Client.py
+++ b/worlds/kdl3/Client.py
@@ -330,9 +330,9 @@ class KDL3SNIClient(SNIClient):
item = ctx.items_received[recv_amount]
recv_amount += 1
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
- color(ctx.item_names[item.item], 'red', 'bold'),
+ color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
- ctx.location_names[item.location], recv_amount, len(ctx.items_received)))
+ ctx.location_names.lookup_in_slot(item.location, item.player), recv_amount, len(ctx.items_received)))
snes_buffered_write(ctx, KDL3_RECV_COUNT, pack("H", recv_amount))
item_idx = item.item & 0x00000F
@@ -415,7 +415,7 @@ class KDL3SNIClient(SNIClient):
for new_check_id in new_checks:
ctx.locations_checked.add(new_check_id)
- location = ctx.location_names[new_check_id]
+ location = ctx.location_names.lookup_in_slot(new_check_id)
snes_logger.info(
f'New Check: {location} ({len(ctx.locations_checked)}/'
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py
index 6c7517f3..f7de0f41 100644
--- a/worlds/ladx/__init__.py
+++ b/worlds/ladx/__init__.py
@@ -78,11 +78,6 @@ class LinksAwakeningWorld(World):
settings: typing.ClassVar[LinksAwakeningSettings]
topology_present = True # show path to required location checks in spoiler
- # 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
- # cache any texts, then increase by 1 for each release that makes changes.
- data_version = 1
-
# ID of first item and location, could be hard-coded but code may be easier
# to read with this as a propery.
base_id = BASE_ID
diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py
index fa24fdc3..302e7e1d 100644
--- a/worlds/lingo/__init__.py
+++ b/worlds/lingo/__init__.py
@@ -37,7 +37,6 @@ class LingoWorld(World):
base_id = 444400
topology_present = True
- data_version = 1
options_dataclass = LingoOptions
options: LingoOptions
diff --git a/worlds/lufia2ac/Client.py b/worlds/lufia2ac/Client.py
index 3c05e639..1e8437d2 100644
--- a/worlds/lufia2ac/Client.py
+++ b/worlds/lufia2ac/Client.py
@@ -147,9 +147,9 @@ class L2ACSNIClient(SNIClient):
snes_items_received += 1
snes_logger.info("Received %s from %s (%s) (%d/%d in list)" % (
- ctx.item_names[item.item],
+ ctx.item_names.lookup_in_slot(item.item),
ctx.player_names[item.player],
- ctx.location_names[item.location],
+ ctx.location_names.lookup_in_slot(item.location, item.player),
snes_items_received, len(ctx.items_received)))
snes_buffered_write(ctx, L2AC_RX_ADDR + 2 * (snes_items_received + 1), item_code.to_bytes(2, "little"))
snes_buffered_write(ctx, L2AC_RX_ADDR, snes_items_received.to_bytes(2, "little"))
diff --git a/worlds/lufia2ac/__init__.py b/worlds/lufia2ac/__init__.py
index 561429c8..6433452c 100644
--- a/worlds/lufia2ac/__init__.py
+++ b/worlds/lufia2ac/__init__.py
@@ -65,7 +65,6 @@ class L2ACWorld(World):
"Iris treasures": {name for name, data in l2ac_item_table.items() if data.type is ItemType.IRIS_TREASURE},
"Party members": {name for name, data in l2ac_item_table.items() if data.type is ItemType.PARTY_MEMBER},
}
- data_version: ClassVar[int] = 2
required_client_version: Tuple[int, int, int] = (0, 4, 4)
# L2ACWorld specific properties
diff --git a/worlds/meritous/__init__.py b/worlds/meritous/__init__.py
index 728d7af8..7a21b19e 100644
--- a/worlds/meritous/__init__.py
+++ b/worlds/meritous/__init__.py
@@ -44,8 +44,6 @@ class MeritousWorld(World):
location_name_to_id = location_table
item_name_groups = item_groups
- data_version = 2
-
# NOTE: Remember to change this before this game goes live
required_client_version = (0, 2, 4)
diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py
index 343b9bad..75e043d0 100644
--- a/worlds/minecraft/__init__.py
+++ b/worlds/minecraft/__init__.py
@@ -92,8 +92,6 @@ class MinecraftWorld(World):
item_name_to_id = Constants.item_name_to_id
location_name_to_id = Constants.location_name_to_id
- data_version = 7
-
def _get_mc_data(self) -> Dict[str, Any]:
exits = [connection[0] for connection in Constants.region_info["default_connections"]]
return {
diff --git a/worlds/mmbn3/__init__.py b/worlds/mmbn3/__init__.py
index eac8a37b..97725e72 100644
--- a/worlds/mmbn3/__init__.py
+++ b/worlds/mmbn3/__init__.py
@@ -57,8 +57,6 @@ class MMBN3World(World):
settings: typing.ClassVar[MMBN3Settings]
topology_present = False
- data_version = 1
-
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
diff --git a/worlds/noita/__init__.py b/worlds/noita/__init__.py
index 43078c5e..af292176 100644
--- a/worlds/noita/__init__.py
+++ b/worlds/noita/__init__.py
@@ -34,14 +34,13 @@ class NoitaWorld(World):
item_name_groups = items.item_name_groups
location_name_groups = locations.location_name_groups
- data_version = 2
web = NoitaWeb()
def generate_early(self) -> None:
if not self.multiworld.get_player_name(self.player).isascii():
raise Exception("Noita yaml's slot name has invalid character(s).")
-
+
# Returned items will be sent over to the client
def fill_slot_data(self) -> Dict[str, Any]:
return self.options.as_dict("death_link", "victory_condition", "path_option", "hidden_chests",
diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py
index d9ee6385..9346ac55 100644
--- a/worlds/oot/__init__.py
+++ b/worlds/oot/__init__.py
@@ -150,8 +150,6 @@ class OOTWorld(World):
location_name_to_id = location_name_to_id
web = OOTWeb()
- data_version = 3
-
required_client_version = (0, 4, 0)
item_name_groups = {
diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py
index be66fa3a..44227d4b 100644
--- a/worlds/overcooked2/__init__.py
+++ b/worlds/overcooked2/__init__.py
@@ -48,7 +48,6 @@ class Overcooked2World(World):
web = Overcooked2Web()
required_client_version = (0, 3, 8)
topology_present: bool = False
- data_version = 3
item_name_to_id = item_name_to_id
item_id_to_name = item_id_to_name
diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py
index 6225350a..3e50f748 100644
--- a/worlds/pokemon_emerald/__init__.py
+++ b/worlds/pokemon_emerald/__init__.py
@@ -87,7 +87,6 @@ class PokemonEmeraldWorld(World):
item_name_groups = ITEM_GROUPS
location_name_groups = LOCATION_GROUPS
- data_version = 2
required_client_version = (0, 4, 6)
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py
index 79028a68..003e0a32 100644
--- a/worlds/pokemon_rb/__init__.py
+++ b/worlds/pokemon_rb/__init__.py
@@ -74,7 +74,6 @@ class PokemonRedBlueWorld(World):
option_definitions = pokemon_rb_options
settings: typing.ClassVar[PokemonSettings]
- data_version = 9
required_client_version = (0, 4, 2)
topology_present = True
diff --git a/worlds/raft/__init__.py b/worlds/raft/__init__.py
index 8e4eda09..e96cd447 100644
--- a/worlds/raft/__init__.py
+++ b/worlds/raft/__init__.py
@@ -39,7 +39,6 @@ class RaftWorld(World):
location_name_to_id = locations_lookup_name_to_id
option_definitions = raft_options
- data_version = 2
required_client_version = (0, 3, 4)
def create_items(self):
diff --git a/worlds/rogue_legacy/__init__.py b/worlds/rogue_legacy/__init__.py
index c5a8d71b..eb657699 100644
--- a/worlds/rogue_legacy/__init__.py
+++ b/worlds/rogue_legacy/__init__.py
@@ -35,7 +35,6 @@ class RLWorld(World):
game = "Rogue Legacy"
option_definitions = rl_options
topology_present = True
- data_version = 4
required_client_version = (0, 3, 5)
web = RLWeb()
diff --git a/worlds/ror2/__init__.py b/worlds/ror2/__init__.py
index 5afdb797..b6a1901a 100644
--- a/worlds/ror2/__init__.py
+++ b/worlds/ror2/__init__.py
@@ -44,7 +44,6 @@ class RiskOfRainWorld(World):
}
location_name_to_id = item_pickups
- data_version = 9
required_client_version = (0, 4, 5)
web = RiskOfWeb()
total_revivals: int
diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py
index 6279aa94..f7d1ca72 100644
--- a/worlds/sa2b/__init__.py
+++ b/worlds/sa2b/__init__.py
@@ -58,7 +58,6 @@ class SA2BWorld(World):
options_dataclass = SA2BOptions
options: SA2BOptions
topology_present = False
- data_version = 7
item_name_groups = item_groups
item_name_to_id = {name: data.code for name, data in item_table.items()}
diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py
index 4e55509d..ac9ccfff 100644
--- a/worlds/sc2/Client.py
+++ b/worlds/sc2/Client.py
@@ -244,10 +244,10 @@ class StarcraftClientProcessor(ClientCommandProcessor):
self.formatted_print(f" [u]{faction.name}[/u] ")
for item_id in categorized_items[faction]:
- item_name = self.ctx.item_names[item_id]
+ item_name = self.ctx.item_names.lookup_in_slot(item_id)
received_child_items = items_received_set.intersection(parent_to_child.get(item_id, []))
matching_children = [child for child in received_child_items
- if item_matches_filter(self.ctx.item_names[child])]
+ if item_matches_filter(self.ctx.item_names.lookup_in_slot(child))]
received_items_of_this_type = items_received.get(item_id, [])
item_is_match = item_matches_filter(item_name)
if item_is_match or len(matching_children) > 0:
@@ -1165,7 +1165,7 @@ def request_unfinished_missions(ctx: SC2Context) -> None:
objectives = set(ctx.locations_for_mission(mission))
if objectives:
remaining_objectives = objectives.difference(ctx.checked_locations)
- unfinished_locations[mission] = [ctx.location_names[location_id] for location_id in remaining_objectives]
+ unfinished_locations[mission] = [ctx.location_names.lookup_in_slot(location_id) for location_id in remaining_objectives]
else:
unfinished_locations[mission] = []
diff --git a/worlds/sc2/ClientGui.py b/worlds/sc2/ClientGui.py
index 167583fd..f9dcfc18 100644
--- a/worlds/sc2/ClientGui.py
+++ b/worlds/sc2/ClientGui.py
@@ -269,7 +269,7 @@ class SC2Manager(GameManager):
for loc in self.ctx.locations_for_mission(mission_name):
if loc in self.ctx.missing_locations:
count += 1
- locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names[loc])
+ locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_slot(loc))
plando_locations = []
for plando_loc in self.ctx.plando_locations:
diff --git a/worlds/shorthike/__init__.py b/worlds/shorthike/__init__.py
index 3e0430f0..470b061c 100644
--- a/worlds/shorthike/__init__.py
+++ b/worlds/shorthike/__init__.py
@@ -28,7 +28,6 @@ class ShortHikeWorld(World):
game = "A Short Hike"
web = ShortHikeWeb()
- data_version = 2
item_name_to_id = {item["name"]: item["id"] for item in item_table}
location_name_to_id = {loc["name"]: loc["id"] for loc in location_table}
diff --git a/worlds/sm/Client.py b/worlds/sm/Client.py
index 7c97f743..6d6dd08b 100644
--- a/worlds/sm/Client.py
+++ b/worlds/sm/Client.py
@@ -123,7 +123,7 @@ class SMSNIClient(SNIClient):
location_id = locations_start_id + item_index
ctx.locations_checked.add(location_id)
- location = ctx.location_names[location_id]
+ location = ctx.location_names.lookup_in_slot(location_id)
snes_logger.info(
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
@@ -151,9 +151,8 @@ class SMSNIClient(SNIClient):
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
- color(ctx.item_names[item.item], 'red', 'bold'),
+ color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
- ctx.location_names[item.location], item_out_ptr, len(ctx.items_received)))
+ ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
await snes_flush_writes(ctx)
-
diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py
index 7f12bf48..826b1447 100644
--- a/worlds/sm/__init__.py
+++ b/worlds/sm/__init__.py
@@ -99,7 +99,6 @@ class SMWorld(World):
game: str = "Super Metroid"
topology_present = True
- data_version = 3
option_definitions = sm_options
settings: typing.ClassVar[SMSettings]
diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py
index 0e944aa4..833ae56c 100644
--- a/worlds/sm64ex/__init__.py
+++ b/worlds/sm64ex/__init__.py
@@ -35,7 +35,6 @@ class SM64World(World):
item_name_to_id = item_table
location_name_to_id = location_table
- data_version = 9
required_client_version = (0, 3, 5)
area_connections: typing.Dict[int, int]
diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py
index 33a74b3d..85bb3fe1 100644
--- a/worlds/smw/Client.py
+++ b/worlds/smw/Client.py
@@ -448,7 +448,7 @@ class SMWSNIClient(SNIClient):
for new_check_id in new_checks:
ctx.locations_checked.add(new_check_id)
- location = ctx.location_names[new_check_id]
+ location = ctx.location_names.lookup_in_slot(new_check_id)
snes_logger.info(
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
@@ -499,15 +499,16 @@ class SMWSNIClient(SNIClient):
if recv_index < len(ctx.items_received):
item = ctx.items_received[recv_index]
recv_index += 1
+ sending_game = ctx.slot_info[item.player].game
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
- color(ctx.item_names[item.item], 'red', 'bold'),
+ color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
- ctx.location_names[item.location], recv_index, len(ctx.items_received)))
+ ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
if self.should_show_message(ctx, item):
if item.item != 0xBC0012 and item.item not in trap_rom_data:
# Don't send messages for Boss Tokens
- item_name = ctx.item_names[item.item]
+ item_name = ctx.item_names.lookup_in_slot(item.item)
player_name = ctx.player_names[item.player]
receive_message = generate_received_text(item_name, player_name)
@@ -515,7 +516,7 @@ class SMWSNIClient(SNIClient):
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
if item.item in trap_rom_data:
- item_name = ctx.item_names[item.item]
+ item_name = ctx.item_names.lookup_in_slot(item.item)
player_name = ctx.player_names[item.player]
receive_message = generate_received_text(item_name, player_name)
@@ -596,7 +597,7 @@ class SMWSNIClient(SNIClient):
for loc_id in ctx.checked_locations:
if loc_id not in ctx.locations_checked:
ctx.locations_checked.add(loc_id)
- loc_name = ctx.location_names[loc_id]
+ loc_name = ctx.location_names.lookup_in_slot(loc_id)
if loc_name not in location_id_to_level_id:
continue
diff --git a/worlds/smz3/Client.py b/worlds/smz3/Client.py
index 0a248aa5..3c90ead0 100644
--- a/worlds/smz3/Client.py
+++ b/worlds/smz3/Client.py
@@ -109,7 +109,7 @@ class SMZ3SNIClient(SNIClient):
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
ctx.locations_checked.add(location_id)
- location = ctx.location_names[location_id]
+ location = ctx.location_names.lookup_in_slot(location_id)
snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
@@ -132,8 +132,7 @@ class SMZ3SNIClient(SNIClient):
item_out_ptr += 1
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + recv_progress_addr_table_offset, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
- color(ctx.item_names[item.item], 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
- ctx.location_names[item.location], item_out_ptr, len(ctx.items_received)))
+ color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
+ ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
await snes_flush_writes(ctx)
-
diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py
index b030e3fa..6056a171 100644
--- a/worlds/smz3/__init__.py
+++ b/worlds/smz3/__init__.py
@@ -68,7 +68,6 @@ class SMZ3World(World):
"""
game: str = "SMZ3"
topology_present = False
- data_version = 3
option_definitions = smz3_options
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
location_names: Set[str]
diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py
index 06132265..3baed165 100644
--- a/worlds/soe/__init__.py
+++ b/worlds/soe/__init__.py
@@ -176,7 +176,6 @@ class SoEWorld(World):
options: SoEOptions
settings: typing.ClassVar[SoESettings]
topology_present = False
- data_version = 5
web = SoEWebWorld()
required_client_version = (0, 4, 4)
diff --git a/worlds/spire/__init__.py b/worlds/spire/__init__.py
index d8a9322a..5b0e1e17 100644
--- a/worlds/spire/__init__.py
+++ b/worlds/spire/__init__.py
@@ -30,7 +30,6 @@ class SpireWorld(World):
option_definitions = spire_options
game = "Slay the Spire"
topology_present = False
- data_version = 2
web = SpireWeb()
required_client_version = (0, 3, 7)
diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py
index dafb1c64..61c86663 100644
--- a/worlds/stardew_valley/__init__.py
+++ b/worlds/stardew_valley/__init__.py
@@ -73,7 +73,6 @@ class StardewValleyWorld(World):
[location.name for location in locations] for group, locations in locations_by_tag.items()
}
- data_version = 3
required_client_version = (0, 4, 0)
options_dataclass = StardewValleyOptions
diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py
index 08df70d7..85611746 100644
--- a/worlds/subnautica/__init__.py
+++ b/worlds/subnautica/__init__.py
@@ -44,7 +44,6 @@ class SubnauticaWorld(World):
location_name_to_id = all_locations
options_dataclass = options.SubnauticaOptions
options: options.SubnauticaOptions
- data_version = 10
required_client_version = (0, 4, 1)
creatures_to_scan: List[str]
diff --git a/worlds/terraria/__init__.py b/worlds/terraria/__init__.py
index ac6b25e5..abc10a7b 100644
--- a/worlds/terraria/__init__.py
+++ b/worlds/terraria/__init__.py
@@ -52,11 +52,6 @@ class TerrariaWorld(World):
options_dataclass = TerrariaOptions
options: TerrariaOptions
- # 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
- # cache any texts, then increase by 1 for each release that makes changes.
- data_version = 2
-
item_name_to_id = item_name_to_id
location_name_to_id = location_name_to_id
diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py
index ff7b3515..cab6fb64 100644
--- a/worlds/timespinner/__init__.py
+++ b/worlds/timespinner/__init__.py
@@ -39,7 +39,6 @@ class TimespinnerWorld(World):
option_definitions = timespinner_options
game = "Timespinner"
topology_present = True
- data_version = 12
web = TimespinnerWebWorld()
required_client_version = (0, 4, 2)
@@ -228,7 +227,7 @@ class TimespinnerWorld(World):
non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value
local_items: Set[str] = self.multiworld.local_items[self.player].value
- local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if
+ local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if
item in local_items or not item in non_local_items)
if not local_starter_melee_weapons:
if 'Plasma Orb' in non_local_items:
diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py
index 7565dc01..a1f90814 100644
--- a/worlds/tloz/__init__.py
+++ b/worlds/tloz/__init__.py
@@ -68,7 +68,6 @@ class TLoZWorld(World):
settings: typing.ClassVar[TLoZSettings]
game = "The Legend of Zelda"
topology_present = False
- data_version = 1
base_id = 7000
web = TLoZWeb()
diff --git a/worlds/undertale/__init__.py b/worlds/undertale/__init__.py
index 0694456a..b87d3ac0 100644
--- a/worlds/undertale/__init__.py
+++ b/worlds/undertale/__init__.py
@@ -52,8 +52,6 @@ class UndertaleWorld(World):
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
- data_version = 7
-
def _get_undertale_data(self):
return {
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
diff --git a/worlds/v6/__init__.py b/worlds/v6/__init__.py
index 30a76f82..3d3ee8cf 100644
--- a/worlds/v6/__init__.py
+++ b/worlds/v6/__init__.py
@@ -34,8 +34,6 @@ class V6World(World):
item_name_to_id = item_table
location_name_to_id = location_table
- data_version = 1
-
area_connections: typing.Dict[int, int]
area_cost_map: typing.Dict[int,int]
diff --git a/worlds/yoshisisland/Client.py b/worlds/yoshisisland/Client.py
index 1aff36c5..2a710b04 100644
--- a/worlds/yoshisisland/Client.py
+++ b/worlds/yoshisisland/Client.py
@@ -116,7 +116,7 @@ class YoshisIslandSNIClient(SNIClient):
for new_check_id in new_checks:
ctx.locations_checked.add(new_check_id)
- location = ctx.location_names[new_check_id]
+ location = ctx.location_names.lookup_in_slot(new_check_id)
total_locations = len(ctx.missing_locations) + len(ctx.checked_locations)
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
@@ -127,9 +127,9 @@ class YoshisIslandSNIClient(SNIClient):
item = ctx.items_received[recv_index]
recv_index += 1
logging.info("Received %s from %s (%s) (%d/%d in list)" % (
- color(ctx.item_names[item.item], "red", "bold"),
+ color(ctx.item_names.lookup_in_slot(item.item), "red", "bold"),
color(ctx.player_names[item.player], "yellow"),
- ctx.location_names[item.location], recv_index, len(ctx.items_received)))
+ ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
snes_buffered_write(ctx, ITEMQUEUE_HIGH, pack("H", recv_index))
if item.item in item_values:
diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py
index cce120d7..205cc9ad 100644
--- a/worlds/zillion/__init__.py
+++ b/worlds/zillion/__init__.py
@@ -86,11 +86,6 @@ class ZillionWorld(World):
item_name_to_id = _item_name_to_id
location_name_to_id = _loc_name_to_id
- # increment this every time something in your world's names/id mappings changes.
- # While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be
- # retrieved by clients on every connection.
- data_version = 1
-
logger: logging.Logger
class LogStreamInterface: