Core: Remove Universally Unique ID Requirements (Per-Game Data Packages) (#1933)
This commit is contained in:
parent
f3003ff147
commit
5aa6ad63ca
|
@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
|
||||||
if ': !' not in msg:
|
if ': !' not in msg:
|
||||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||||
elif cmd == "ReceivedItems":
|
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)
|
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||||
elif cmd == "Retrieved":
|
elif cmd == "Retrieved":
|
||||||
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
|
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -8,6 +9,7 @@ import sys
|
||||||
import typing
|
import typing
|
||||||
import time
|
import time
|
||||||
import functools
|
import functools
|
||||||
|
import warnings
|
||||||
|
|
||||||
import ModuleUpdate
|
import ModuleUpdate
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
@ -173,10 +175,74 @@ class CommonContext:
|
||||||
items_handling: typing.Optional[int] = None
|
items_handling: typing.Optional[int] = None
|
||||||
want_slot_data: bool = True # should slot_data be retrieved via Connect
|
want_slot_data: bool = True # should slot_data be retrieved via Connect
|
||||||
|
|
||||||
# data package
|
class NameLookupDict:
|
||||||
# Contents in flux until connection to server is made, to download correct data for this multiworld.
|
"""A specialized dict, with helper methods, for id -> name item/location data package lookups by game."""
|
||||||
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
|
def __init__(self, ctx: CommonContext, lookup_type: typing.Literal["item", "location"]):
|
||||||
location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
|
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
|
# defaults
|
||||||
starting_reconnect_delay: int = 5
|
starting_reconnect_delay: int = 5
|
||||||
|
@ -231,7 +297,7 @@ class CommonContext:
|
||||||
# message box reporting a loss of connection
|
# message box reporting a loss of connection
|
||||||
_messagebox_connection_loss: typing.Optional["kvui.MessageBox"] = None
|
_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
|
# server state
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
self.username = None
|
self.username = None
|
||||||
|
@ -271,6 +337,9 @@ class CommonContext:
|
||||||
self.exit_event = asyncio.Event()
|
self.exit_event = asyncio.Event()
|
||||||
self.watcher_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.jsontotextparser = JSONtoTextParser(self)
|
||||||
self.rawjsontotextparser = RawJSONtoTextParser(self)
|
self.rawjsontotextparser = RawJSONtoTextParser(self)
|
||||||
self.update_data_package(network_data_package)
|
self.update_data_package(network_data_package)
|
||||||
|
@ -486,19 +555,17 @@ class CommonContext:
|
||||||
or remote_checksum != cache_checksum:
|
or remote_checksum != cache_checksum:
|
||||||
needed_updates.add(game)
|
needed_updates.add(game)
|
||||||
else:
|
else:
|
||||||
self.update_game(cached_game)
|
self.update_game(cached_game, game)
|
||||||
if needed_updates:
|
if needed_updates:
|
||||||
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
|
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
|
||||||
|
|
||||||
def update_game(self, game_package: dict):
|
def update_game(self, game_package: dict, game: str):
|
||||||
for item_name, item_id in game_package["item_name_to_id"].items():
|
self.item_names.update_game(game, game_package["item_name_to_id"])
|
||||||
self.item_names[item_id] = item_name
|
self.location_names.update_game(game, game_package["location_name_to_id"])
|
||||||
for location_name, location_id in game_package["location_name_to_id"].items():
|
|
||||||
self.location_names[location_id] = location_name
|
|
||||||
|
|
||||||
def update_data_package(self, data_package: dict):
|
def update_data_package(self, data_package: dict):
|
||||||
for game, game_data in data_package["games"].items():
|
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):
|
def consume_network_data_package(self, data_package: dict):
|
||||||
self.update_data_package(data_package)
|
self.update_data_package(data_package)
|
||||||
|
|
|
@ -168,9 +168,11 @@ class Context:
|
||||||
slot_info: typing.Dict[int, NetworkSlot]
|
slot_info: typing.Dict[int, NetworkSlot]
|
||||||
generator_version = Version(0, 0, 0)
|
generator_version = Version(0, 0, 0)
|
||||||
checksums: typing.Dict[str, str]
|
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]]]
|
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]]]
|
location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
|
||||||
all_item_and_group_names: 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]]
|
all_location_and_group_names: typing.Dict[str, typing.Set[str]]
|
||||||
|
@ -271,14 +273,21 @@ class Context:
|
||||||
if "checksum" in game_package:
|
if "checksum" in game_package:
|
||||||
self.checksums[game_name] = game_package["checksum"]
|
self.checksums[game_name] = game_package["checksum"]
|
||||||
for item_name, item_id in game_package["item_name_to_id"].items():
|
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():
|
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] = \
|
self.all_item_and_group_names[game_name] = \
|
||||||
set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name])
|
set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name])
|
||||||
self.all_location_and_group_names[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, []))
|
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]]:
|
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
|
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():
|
for slot, connected_clients in clients.items():
|
||||||
if connected_clients:
|
if connected_clients:
|
||||||
name = ctx.player_names[team, slot]
|
name = ctx.player_names[team, slot]
|
||||||
players.append(
|
players.append(NetworkPlayer(team, slot, ctx.name_aliases.get((team, slot), name), name))
|
||||||
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 = {ctx.games[x] for x in range(1, len(ctx.games) + 1)}
|
||||||
games.add("Archipelago")
|
games.add("Archipelago")
|
||||||
await ctx.send_msgs(client, [{
|
await ctx.send_msgs(client, [{
|
||||||
|
@ -801,8 +807,6 @@ async def on_client_connected(ctx: Context, client: Client):
|
||||||
'permissions': get_permissions(ctx),
|
'permissions': get_permissions(ctx),
|
||||||
'hint_cost': ctx.hint_cost,
|
'hint_cost': ctx.hint_cost,
|
||||||
'location_check_points': ctx.location_check_points,
|
'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
|
'datapackage_checksums': {game: game_data["checksum"] for game, game_data
|
||||||
in ctx.gamespackage.items() if game in games and "checksum" in game_data},
|
in ctx.gamespackage.items() if game in games and "checksum" in game_data},
|
||||||
'seed_name': ctx.seed_name,
|
'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)
|
send_items_to(ctx, team, target_player, new_item)
|
||||||
|
|
||||||
ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % (
|
ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % (
|
||||||
team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id],
|
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[location]))
|
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)
|
info_text = json_format_send_event(new_item, target_player)
|
||||||
ctx.broadcast_team(team, [info_text])
|
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:
|
def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
|
||||||
text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
|
text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
|
||||||
f"{ctx.item_names[hint.item]} is " \
|
f"{ctx.item_names[ctx.slot_info[hint.receiving_player].game][hint.item]} is " \
|
||||||
f"at {ctx.location_names[hint.location]} " \
|
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"
|
f"in {ctx.player_names[team, hint.finding_player]}'s World"
|
||||||
|
|
||||||
if hint.entrance:
|
if hint.entrance:
|
||||||
|
@ -1364,7 +1368,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||||
if self.ctx.remaining_mode == "enabled":
|
if self.ctx.remaining_mode == "enabled":
|
||||||
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
||||||
if remaining_item_ids:
|
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))
|
for item_id in remaining_item_ids))
|
||||||
else:
|
else:
|
||||||
self.output("No remaining items found.")
|
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:
|
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)
|
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
||||||
if remaining_item_ids:
|
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))
|
for item_id in remaining_item_ids))
|
||||||
else:
|
else:
|
||||||
self.output("No remaining items found.")
|
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)
|
locations = get_missing_checks(self.ctx, self.client.team, self.client.slot)
|
||||||
|
|
||||||
if locations:
|
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:
|
if filter_text:
|
||||||
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
|
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
|
||||||
if filter_text in location_groups: # location group name
|
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)
|
locations = get_checked_checks(self.ctx, self.client.team, self.client.slot)
|
||||||
|
|
||||||
if locations:
|
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:
|
if filter_text:
|
||||||
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
|
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
|
||||||
if filter_text in location_groups: # location group name
|
if filter_text in location_groups: # location group name
|
||||||
|
@ -1501,10 +1507,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||||
elif input_text.isnumeric():
|
elif input_text.isnumeric():
|
||||||
game = self.ctx.games[self.client.slot]
|
game = self.ctx.games[self.client.slot]
|
||||||
hint_id = int(input_text)
|
hint_id = int(input_text)
|
||||||
hint_name = self.ctx.item_names[hint_id] \
|
hint_name = self.ctx.item_names[game][hint_id] \
|
||||||
if not for_location and hint_id in self.ctx.item_names \
|
if not for_location and hint_id in self.ctx.item_names[game] \
|
||||||
else self.ctx.location_names[hint_id] \
|
else self.ctx.location_names[game][hint_id] \
|
||||||
if for_location and hint_id in self.ctx.location_names \
|
if for_location and hint_id in self.ctx.location_names[game] \
|
||||||
else None
|
else None
|
||||||
if hint_name in self.ctx.non_hintable_names[game]:
|
if hint_name in self.ctx.non_hintable_names[game]:
|
||||||
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
|
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
|
||||||
|
|
|
@ -247,7 +247,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
|
||||||
|
|
||||||
def _handle_item_id(self, node: JSONMessagePart):
|
def _handle_item_id(self, node: JSONMessagePart):
|
||||||
item_id = int(node["text"])
|
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)
|
return self._handle_item_name(node)
|
||||||
|
|
||||||
def _handle_location_name(self, node: JSONMessagePart):
|
def _handle_location_name(self, node: JSONMessagePart):
|
||||||
|
@ -255,8 +255,8 @@ class JSONtoTextParser(metaclass=HandlerMeta):
|
||||||
return self._handle_color(node)
|
return self._handle_color(node)
|
||||||
|
|
||||||
def _handle_location_id(self, node: JSONMessagePart):
|
def _handle_location_id(self, node: JSONMessagePart):
|
||||||
item_id = int(node["text"])
|
location_id = int(node["text"])
|
||||||
node["text"] = self.ctx.location_names[item_id]
|
node["text"] = self.ctx.location_names.lookup_in_slot(location_id, node["player"])
|
||||||
return self._handle_location_name(node)
|
return self._handle_location_name(node)
|
||||||
|
|
||||||
def _handle_entrance_name(self, node: JSONMessagePart):
|
def _handle_entrance_name(self, node: JSONMessagePart):
|
||||||
|
|
|
@ -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:
|
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||||
toDraw = ""
|
toDraw = ""
|
||||||
for i in range(20):
|
for i in range(20):
|
||||||
if i < len(str(ctx.item_names[l.item])):
|
if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
|
||||||
toDraw += str(ctx.item_names[l.item])[i]
|
toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
f.write(toDraw)
|
f.write(toDraw)
|
||||||
|
|
5
Utils.py
5
Utils.py
|
@ -46,7 +46,7 @@ class Version(typing.NamedTuple):
|
||||||
return ".".join(str(item) for item in self)
|
return ".".join(str(item) for item in self)
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.4.6"
|
__version__ = "0.5.0"
|
||||||
version_tuple = tuplize_version(__version__)
|
version_tuple = tuplize_version(__version__)
|
||||||
|
|
||||||
is_linux = sys.platform.startswith("linux")
|
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"""
|
"""defaultdict variant that uses the missing key as argument to default_factory"""
|
||||||
default_factory: typing.Callable[[typing.Any], typing.Any]
|
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):
|
def __missing__(self, key):
|
||||||
self[key] = value = self.default_factory(key)
|
self[key] = value = self.default_factory(key)
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
open(path, 'w').close()
|
open(path, 'w').close()
|
||||||
# Announcing commander unlocks
|
# 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():
|
if item_name in faction_table.keys():
|
||||||
for commander in faction_table[item_name]:
|
for commander in faction_table[item_name]:
|
||||||
logger.info(f"{commander.name} has been unlocked!")
|
logger.info(f"{commander.name} has been unlocked!")
|
||||||
|
@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
|
||||||
open(print_path, 'w').close()
|
open(print_path, 'w').close()
|
||||||
with open(print_path, 'w') as f:
|
with open(print_path, 'w') as f:
|
||||||
f.write("Received " +
|
f.write("Received " +
|
||||||
self.item_names[network_item.item] +
|
self.item_names.lookup_in_slot(network_item.item) +
|
||||||
" from " +
|
" from " +
|
||||||
self.player_names[network_item.player])
|
self.player_names[network_item.player])
|
||||||
f.close()
|
f.close()
|
||||||
|
@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
|
||||||
faction_items = 0
|
faction_items = 0
|
||||||
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
||||||
for network_item in self.items_received:
|
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
|
faction_items += 1
|
||||||
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
||||||
# Must be an integer larger than 0
|
# Must be an integer larger than 0
|
||||||
|
|
|
@ -56,15 +56,6 @@ def get_datapackage():
|
||||||
return network_data_package
|
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')
|
@api_endpoints.route('/datapackage_checksum')
|
||||||
@cache.cached()
|
@cache.cached()
|
||||||
def get_datapackage_checksums():
|
def get_datapackage_checksums():
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>{{ player_name }}'s Tracker</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/ootTracker.css') }}"/>
|
|
||||||
<script type="application/ecmascript" src="{{ url_for('static', filename='assets/ootTracker.js') }}"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
|
|
||||||
<table id="inventory-table">
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ ocarina_url }}" class="{{ 'acquired' if 'Ocarina' in acquired_items }}" title="Ocarina" /></td>
|
|
||||||
<td><img src="{{ icons['Bombs'] }}" class="{{ 'acquired' if 'Bomb Bag' in acquired_items }}" title="Bombs" /></td>
|
|
||||||
<td><img src="{{ icons['Bow'] }}" class="{{ 'acquired' if 'Bow' in acquired_items }}" title="Fairy Bow" /></td>
|
|
||||||
<td><img src="{{ icons['Fire Arrows'] }}" class="{{ 'acquired' if 'Fire Arrows' in acquired_items }}" title="Fire Arrows" /></td>
|
|
||||||
<td><img src="{{ icons['Kokiri Sword'] }}" class="{{ 'acquired' if 'Kokiri Sword' in acquired_items }}" title="Kokiri Sword" /></td>
|
|
||||||
<td><img src="{{ icons['Biggoron Sword'] }}" class="{{ 'acquired' if 'Biggoron Sword' in acquired_items }}" title="Biggoron's Sword" /></td>
|
|
||||||
<td><img src="{{ icons['Mirror Shield'] }}" class="{{ 'acquired' if 'Mirror Shield' in acquired_items }}" title="Mirror Shield" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Slingshot'] }}" class="{{ 'acquired' if 'Slingshot' in acquired_items }}" title="Slingshot" /></td>
|
|
||||||
<td><img src="{{ icons['Bombchus'] }}" class="{{ 'acquired' if has_bombchus }}" title="Bombchus" /></td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ hookshot_url }}" class="{{ 'acquired' if 'Progressive Hookshot' in acquired_items }}" title="Progressive Hookshot" />
|
|
||||||
<div class="item-count">{{ hookshot_length }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td><img src="{{ icons['Ice Arrows'] }}" class="{{ 'acquired' if 'Ice Arrows' in acquired_items }}" title="Ice Arrows" /></td>
|
|
||||||
<td><img src="{{ strength_upgrade_url }}" class="{{ 'acquired' if 'Progressive Strength Upgrade' in acquired_items }}" title="Progressive Strength Upgrade" /></td>
|
|
||||||
<td><img src="{{ icons['Goron Tunic'] }}" class="{{ 'acquired' if 'Goron Tunic' in acquired_items }}" title="Goron Tunic" /></td>
|
|
||||||
<td><img src="{{ icons['Zora Tunic'] }}" class="{{ 'acquired' if 'Zora Tunic' in acquired_items }}" title="Zora Tunic" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Boomerang'] }}" class="{{ 'acquired' if 'Boomerang' in acquired_items }}" title="Boomerang" /></td>
|
|
||||||
<td><img src="{{ icons['Lens of Truth'] }}" class="{{ 'acquired' if 'Lens of Truth' in acquired_items }}" title="Lens of Truth" /></td>
|
|
||||||
<td><img src="{{ icons['Megaton Hammer'] }}" class="{{ 'acquired' if 'Megaton Hammer' in acquired_items }}" title="Megaton Hammer" /></td>
|
|
||||||
<td><img src="{{ icons['Light Arrows'] }}" class="{{ 'acquired' if 'Light Arrows' in acquired_items }}" title="Light Arrows" /></td>
|
|
||||||
<td><img src="{{ scale_url }}" class="{{ 'acquired' if 'Progressive Scale' in acquired_items }}" title="Progressive Scale" /></td>
|
|
||||||
<td><img src="{{ icons['Iron Boots'] }}" class="{{ 'acquired' if 'Iron Boots' in acquired_items }}" title="Iron Boots" /></td>
|
|
||||||
<td><img src="{{ icons['Hover Boots'] }}" class="{{ 'acquired' if 'Hover Boots' in acquired_items }}" title="Hover Boots" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ bottle_url }}" class="{{ 'acquired' if bottle_count > 0 }}" title="Bottles" />
|
|
||||||
<div class="item-count">{{ bottle_count if bottle_count > 0 else '' }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td><img src="{{ icons['Dins Fire'] }}" class="{{ 'acquired' if 'Dins Fire' in acquired_items }}" title="Din's Fire" /></td>
|
|
||||||
<td><img src="{{ icons['Farores Wind'] }}" class="{{ 'acquired' if 'Farores Wind' in acquired_items }}" title="Farore's Wind" /></td>
|
|
||||||
<td><img src="{{ icons['Nayrus Love'] }}" class="{{ 'acquired' if 'Nayrus Love' in acquired_items }}" title="Nayru's Love" /></td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ wallet_url }}" class="{{ 'acquired' if 'Progressive Wallet' in acquired_items }}" title="Progressive Wallet" />
|
|
||||||
<div class="item-count">{{ wallet_size }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td><img src="{{ magic_meter_url }}" class="{{ 'acquired' if 'Magic Meter' in acquired_items }}" title="Magic Meter" /></td>
|
|
||||||
<td><img src="{{ icons['Gerudo Membership Card'] }}" class="{{ 'acquired' if 'Gerudo Membership Card' in acquired_items }}" title="Gerudo Membership Card" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Zeldas Lullaby'] }}" class="{{ 'acquired' if 'Zeldas Lullaby' in acquired_items }}" title="Zelda's Lullaby" id="lullaby"/>
|
|
||||||
<div class="item-count">Zelda</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Eponas Song'] }}" class="{{ 'acquired' if 'Eponas Song' in acquired_items }}" title="Epona's Song" id="epona" />
|
|
||||||
<div class="item-count">Epona</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Sarias Song'] }}" class="{{ 'acquired' if 'Sarias Song' in acquired_items }}" title="Saria's Song" id="saria"/>
|
|
||||||
<div class="item-count">Saria</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Suns Song'] }}" class="{{ 'acquired' if 'Suns Song' in acquired_items }}" title="Sun's Song" id="sun"/>
|
|
||||||
<div class="item-count">Sun</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Song of Time'] }}" class="{{ 'acquired' if 'Song of Time' in acquired_items }}" title="Song of Time" id="time"/>
|
|
||||||
<div class="item-count">Time</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Song of Storms'] }}" class="{{ 'acquired' if 'Song of Storms' in acquired_items }}" title="Song of Storms" />
|
|
||||||
<div class="item-count">Storms</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Gold Skulltula Token'] }}" class="{{ 'acquired' if token_count > 0 }}" title="Gold Skulltula Tokens" />
|
|
||||||
<div class="item-count">{{ token_count }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Minuet of Forest'] }}" class="{{ 'acquired' if 'Minuet of Forest' in acquired_items }}" title="Minuet of Forest" />
|
|
||||||
<div class="item-count">Min</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Bolero of Fire'] }}" class="{{ 'acquired' if 'Bolero of Fire' in acquired_items }}" title="Bolero of Fire" />
|
|
||||||
<div class="item-count">Bol</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Serenade of Water'] }}" class="{{ 'acquired' if 'Serenade of Water' in acquired_items }}" title="Serenade of Water" />
|
|
||||||
<div class="item-count">Ser</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Requiem of Spirit'] }}" class="{{ 'acquired' if 'Requiem of Spirit' in acquired_items }}" title="Requiem of Spirit" />
|
|
||||||
<div class="item-count">Req</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Nocturne of Shadow'] }}" class="{{ 'acquired' if 'Nocturne of Shadow' in acquired_items }}" title="Nocturne of Shadow" />
|
|
||||||
<div class="item-count">Noc</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Prelude of Light'] }}" class="{{ 'acquired' if 'Prelude of Light' in acquired_items }}" title="Prelude of Light" />
|
|
||||||
<div class="item-count">Pre</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="counted-item">
|
|
||||||
<img src="{{ icons['Triforce'] if game_finished else icons['Triforce Piece'] }}" class="{{ 'acquired' if game_finished or piece_count > 0 }}" title="{{ 'Triforce' if game_finished else 'Triforce Pieces' }}" id=triforce />
|
|
||||||
<div class="item-count">{{ piece_count if piece_count > 0 else '' }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table id="location-table">
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td><img src="{{ icons['Small Key'] }}" title="Small Keys" /></td>
|
|
||||||
<td><img src="{{ icons['Boss Key'] }}" title="Boss Key" /></td>
|
|
||||||
<td class="right-align">Items</td>
|
|
||||||
</tr>
|
|
||||||
{% for area in checks_done %}
|
|
||||||
<tr class="location-category" id="{{area}}-header">
|
|
||||||
<td>{{ area }} {{'▼' if area != 'Total'}}</td>
|
|
||||||
<td class="smallkeys">{{ small_key_counts.get(area, '-') }}</td>
|
|
||||||
<td class="bosskeys">{{ boss_key_counts.get(area, '-') }}</td>
|
|
||||||
<td class="counter">{{ checks_done[area] }} / {{ checks_in_area[area] }}</td>
|
|
||||||
</tr>
|
|
||||||
<tbody class="locations hide" id="{{area}}">
|
|
||||||
{% for location in location_info[area] %}
|
|
||||||
<tr>
|
|
||||||
<td class="location-name">{{ location }}</td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td class="counter">{{ '✔' if location_info[area][location] else '' }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext):
|
||||||
|
|
||||||
|
|
||||||
def reconcile_shops(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]
|
shops = [location for location in checked_location_names if "Shop" in location]
|
||||||
left_slots = [shop for shop in shops if "Left" in shop]
|
left_slots = [shop for shop in shops if "Left" in shop]
|
||||||
middle_slots = [shop for shop in shops if "Middle" 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 = []
|
locations_checked = []
|
||||||
location = None
|
location = None
|
||||||
for location in ctx.missing_locations:
|
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":
|
if location_name in Locations.overworld_locations and zone == "overworld":
|
||||||
status = locations_array[Locations.major_location_offsets[location_name]]
|
status = locations_array[Locations.major_location_offsets[location_name]]
|
||||||
|
|
|
@ -53,7 +53,7 @@ Example:
|
||||||
```
|
```
|
||||||
|
|
||||||
## (Server -> Client)
|
## (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)
|
* [RoomInfo](#RoomInfo)
|
||||||
* [ConnectionRefused](#ConnectionRefused)
|
* [ConnectionRefused](#ConnectionRefused)
|
||||||
* [Connected](#Connected)
|
* [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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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 |
|
| 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. |
|
| 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": 3, "location": 3, "player": 3, "flags": 0}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
`item` is the item id of the item. Item ids are in the range of ± 2<sup>53</sup>-1.
|
`item` is the item id of the item. Item ids are only supported in the range of [-2<sup>53</sup>, 2<sup>53</sup> - 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 ± 2<sup>53</sup>-1.
|
`location` is the location id of the item inside the world. Location ids are only supported in the range of [-2<sup>53</sup>, 2<sup>53</sup> - 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
|
`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
|
### 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:
|
We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know
|
||||||
* Any ID is unique to its type across AP: Item 56 only exists once and Location 56 only exists once.
|
when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different checksum
|
||||||
* Any Name is unique to its type across its own Game only: Single Arrow can exist in two games.
|
than any locally cached ones.
|
||||||
* 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)
|
**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
|
#### Contents
|
||||||
| Name | Type | Notes |
|
| 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. |
|
| 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. |
|
| 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. |
|
| checksum | str | A checksum hash of this game's data. |
|
||||||
|
|
||||||
### Tags
|
### Tags
|
||||||
|
|
14
kvui.py
14
kvui.py
|
@ -683,10 +683,18 @@ class HintLog(RecycleView):
|
||||||
for hint in hints:
|
for hint in hints:
|
||||||
data.append({
|
data.append({
|
||||||
"receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
|
"receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
|
||||||
"item": {"text": self.parser.handle_node(
|
"item": {"text": self.parser.handle_node({
|
||||||
{"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})},
|
"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"]})},
|
"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",
|
"entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text",
|
||||||
"color": "blue", "text": hint["entrance"]
|
"color": "blue", "text": hint["entrance"]
|
||||||
if hint["entrance"] else "Vanilla"})},
|
if hint["entrance"] else "Vanilla"})},
|
||||||
|
|
|
@ -6,22 +6,6 @@ from . import setup_solo_multiworld
|
||||||
|
|
||||||
|
|
||||||
class TestIDs(unittest.TestCase):
|
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):
|
def test_range_items(self):
|
||||||
"""There are Javascript clients, which are limited to Number.MAX_SAFE_INTEGER due to 64bit float precision."""
|
"""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():
|
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||||
|
|
|
@ -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"
|
|
@ -258,18 +258,6 @@ class World(metaclass=AutoWorldRegister):
|
||||||
location_name_groups: ClassVar[Dict[str, Set[str]]] = {}
|
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"}}"""
|
"""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)
|
required_client_version: Tuple[int, int, int] = (0, 1, 6)
|
||||||
"""
|
"""
|
||||||
override this if changes to a world break forward-compatibility of the client
|
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,
|
"item_name_to_id": cls.item_name_to_id,
|
||||||
"location_name_groups": sorted_location_name_groups,
|
"location_name_groups": sorted_location_name_groups,
|
||||||
"location_name_to_id": cls.location_name_to_id,
|
"location_name_to_id": cls.location_name_to_id,
|
||||||
"version": cls.data_version,
|
|
||||||
}
|
}
|
||||||
res["checksum"] = data_package_checksum(res)
|
res["checksum"] = data_package_checksum(res)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -33,7 +33,6 @@ class GamesPackage(TypedDict, total=False):
|
||||||
location_name_groups: Dict[str, List[str]]
|
location_name_groups: Dict[str, List[str]]
|
||||||
location_name_to_id: Dict[str, int]
|
location_name_to_id: Dict[str, int]
|
||||||
checksum: str
|
checksum: str
|
||||||
version: int # TODO: Remove support after per game data packages API change.
|
|
||||||
|
|
||||||
|
|
||||||
class DataPackage(TypedDict):
|
class DataPackage(TypedDict):
|
||||||
|
|
|
@ -113,7 +113,6 @@ class AdventureWorld(World):
|
||||||
settings: ClassVar[AdventureSettings]
|
settings: ClassVar[AdventureSettings]
|
||||||
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
|
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()}
|
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)
|
required_client_version: Tuple[int, int, int] = (0, 3, 9)
|
||||||
|
|
||||||
def __init__(self, world: MultiWorld, player: int):
|
def __init__(self, world: MultiWorld, player: int):
|
||||||
|
|
|
@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
|
||||||
def new_check(location_id):
|
def new_check(location_id):
|
||||||
new_locations.append(location_id)
|
new_locations.append(location_id)
|
||||||
ctx.locations_checked.add(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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ' +
|
f'New Check: {location} ' +
|
||||||
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
|
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]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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,
|
snes_buffered_write(ctx, RECV_PROGRESS_ADDR,
|
||||||
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||||
|
|
|
@ -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}
|
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
|
location_name_to_id = lookup_name_to_id
|
||||||
|
|
||||||
data_version = 9
|
|
||||||
required_client_version = (0, 4, 1)
|
required_client_version = (0, 4, 1)
|
||||||
web = ALTTPWeb()
|
web = ALTTPWeb()
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ class Bk_SudokuWorld(World):
|
||||||
"""
|
"""
|
||||||
game = "Sudoku"
|
game = "Sudoku"
|
||||||
web = Bk_SudokuWebWorld()
|
web = Bk_SudokuWebWorld()
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
item_name_to_id: Dict[str, int] = {}
|
item_name_to_id: Dict[str, int] = {}
|
||||||
location_name_to_id: Dict[str, int] = {}
|
location_name_to_id: Dict[str, int] = {}
|
||||||
|
|
|
@ -32,7 +32,6 @@ class BlasphemousWorld(World):
|
||||||
|
|
||||||
game: str = "Blasphemous"
|
game: str = "Blasphemous"
|
||||||
web = BlasphemousWeb()
|
web = BlasphemousWeb()
|
||||||
data_version = 2
|
|
||||||
|
|
||||||
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
|
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)}
|
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
|
||||||
|
|
|
@ -39,8 +39,6 @@ class BumpStikWorld(World):
|
||||||
location_name_to_id = location_table
|
location_name_to_id = location_table
|
||||||
item_name_groups = item_groups
|
item_name_groups = item_groups
|
||||||
|
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
required_client_version = (0, 3, 8)
|
required_client_version = (0, 3, 8)
|
||||||
|
|
||||||
options: BumpstikOptions
|
options: BumpstikOptions
|
||||||
|
|
|
@ -33,8 +33,6 @@ class ChecksFinderWorld(World):
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
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()}
|
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
|
||||||
|
|
||||||
data_version = 4
|
|
||||||
|
|
||||||
def _get_checksfinder_data(self):
|
def _get_checksfinder_data(self):
|
||||||
return {
|
return {
|
||||||
'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
||||||
|
|
|
@ -37,7 +37,6 @@ class CliqueWorld(World):
|
||||||
"""The greatest game of all time."""
|
"""The greatest game of all time."""
|
||||||
|
|
||||||
game = "Clique"
|
game = "Clique"
|
||||||
data_version = 3
|
|
||||||
web = CliqueWebWorld()
|
web = CliqueWebWorld()
|
||||||
option_definitions = clique_options
|
option_definitions = clique_options
|
||||||
location_name_to_id = location_table
|
location_name_to_id = location_table
|
||||||
|
|
|
@ -64,7 +64,6 @@ class CV64World(World):
|
||||||
options: CV64Options
|
options: CV64Options
|
||||||
settings: typing.ClassVar[CV64Settings]
|
settings: typing.ClassVar[CV64Settings]
|
||||||
topology_present = True
|
topology_present = True
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
item_name_to_id = get_item_names_to_ids()
|
item_name_to_id = get_item_names_to_ids()
|
||||||
location_name_to_id = get_location_names_to_ids()
|
location_name_to_id = get_location_names_to_ids()
|
||||||
|
|
|
@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
|
||||||
text_color = bytearray([0xA2, 0x0B])
|
text_color = bytearray([0xA2, 0x0B])
|
||||||
else:
|
else:
|
||||||
text_color = bytearray([0xA2, 0x02])
|
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)
|
f"from {ctx.player_names[next_item.player]}", 96)
|
||||||
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
||||||
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
||||||
|
|
|
@ -49,7 +49,6 @@ class DarkSouls3World(World):
|
||||||
option_definitions = dark_souls_options
|
option_definitions = dark_souls_options
|
||||||
topology_present: bool = True
|
topology_present: bool = True
|
||||||
web = DarkSouls3Web()
|
web = DarkSouls3Web()
|
||||||
data_version = 8
|
|
||||||
base_id = 100000
|
base_id = 100000
|
||||||
enabled_location_categories: Set[DS3LocationCategory]
|
enabled_location_categories: Set[DS3LocationCategory]
|
||||||
required_client_version = (0, 4, 2)
|
required_client_version = (0, 4, 2)
|
||||||
|
|
|
@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
|
||||||
|
|
||||||
for new_check_id in new_checks:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||||
|
@ -99,9 +99,9 @@ class DKC3SNIClient(SNIClient):
|
||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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]))
|
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
|
||||||
if item.item in item_rom_data:
|
if item.item in item_rom_data:
|
||||||
|
|
|
@ -61,7 +61,6 @@ class DKC3World(World):
|
||||||
options: DKC3Options
|
options: DKC3Options
|
||||||
|
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 2
|
|
||||||
#hint_blacklist = {LocationName.rocket_rush_flag}
|
#hint_blacklist = {LocationName.rocket_rush_flag}
|
||||||
|
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||||
|
|
|
@ -43,8 +43,6 @@ class DLCqworld(World):
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||||
location_name_to_id = location_table
|
location_name_to_id = location_table
|
||||||
|
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
options_dataclass = DLCQuestOptions
|
options_dataclass = DLCQuestOptions
|
||||||
options: DLCQuestOptions
|
options: DLCQuestOptions
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@ class DOOM1993World(World):
|
||||||
options: DOOM1993Options
|
options: DOOM1993Options
|
||||||
game = "DOOM 1993"
|
game = "DOOM 1993"
|
||||||
web = DOOM1993Web()
|
web = DOOM1993Web()
|
||||||
data_version = 3
|
|
||||||
required_client_version = (0, 3, 9)
|
required_client_version = (0, 3, 9)
|
||||||
|
|
||||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||||
|
|
|
@ -43,7 +43,6 @@ class DOOM2World(World):
|
||||||
options: DOOM2Options
|
options: DOOM2Options
|
||||||
game = "DOOM II"
|
game = "DOOM II"
|
||||||
web = DOOM2Web()
|
web = DOOM2Web()
|
||||||
data_version = 3
|
|
||||||
required_client_version = (0, 3, 9)
|
required_client_version = (0, 3, 9)
|
||||||
|
|
||||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||||
|
|
|
@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext):
|
||||||
if ctx.locations_checked != research_data:
|
if ctx.locations_checked != research_data:
|
||||||
bridge_logger.debug(
|
bridge_logger.debug(
|
||||||
f"New researches done: "
|
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
|
ctx.locations_checked = research_data
|
||||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
||||||
death_link_tick = data.get("death_link_tick", 0)
|
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]
|
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
||||||
item_id = transfer_item.item
|
item_id = transfer_item.item
|
||||||
player_name = ctx.player_names[transfer_item.player]
|
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}.")
|
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}"
|
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
|
||||||
ctx.send_index += 1
|
ctx.send_index += 1
|
||||||
|
|
|
@ -95,7 +95,6 @@ class Factorio(World):
|
||||||
item_name_groups = {
|
item_name_groups = {
|
||||||
"Progressive": set(progressive_tech_table.keys()),
|
"Progressive": set(progressive_tech_table.keys()),
|
||||||
}
|
}
|
||||||
data_version = 8
|
|
||||||
required_client_version = (0, 4, 2)
|
required_client_version = (0, 4, 2)
|
||||||
|
|
||||||
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
|
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
|
||||||
|
|
|
@ -40,7 +40,6 @@ class FF1World(World):
|
||||||
settings_key = "ffr_options"
|
settings_key = "ffr_options"
|
||||||
game = "Final Fantasy"
|
game = "Final Fantasy"
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 2
|
|
||||||
|
|
||||||
ff1_items = FF1Items()
|
ff1_items = FF1Items()
|
||||||
ff1_locations = FF1Locations()
|
ff1_locations = FF1Locations()
|
||||||
|
|
|
@ -57,8 +57,6 @@ class FFMQWorld(World):
|
||||||
set_rules = set_rules
|
set_rules = set_rules
|
||||||
stage_set_rules = stage_set_rules
|
stage_set_rules = stage_set_rules
|
||||||
|
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
web = FFMQWebWorld()
|
web = FFMQWebWorld()
|
||||||
# settings: FFMQSettings
|
# settings: FFMQSettings
|
||||||
|
|
||||||
|
@ -216,4 +214,3 @@ class FFMQWorld(World):
|
||||||
hint_data[self.player][location.address] += f"/{hint}"
|
hint_data[self.player][location.address] += f"/{hint}"
|
||||||
else:
|
else:
|
||||||
hint_data[self.player][location.address] = hint
|
hint_data[self.player][location.address] = hint
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ class GenericWorld(World):
|
||||||
}
|
}
|
||||||
hidden = True
|
hidden = True
|
||||||
web = GenericWeb()
|
web = GenericWeb()
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
def generate_early(self):
|
def generate_early(self):
|
||||||
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
|
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
|
||||||
|
|
|
@ -41,7 +41,6 @@ class HereticWorld(World):
|
||||||
options: HereticOptions
|
options: HereticOptions
|
||||||
game = "Heretic"
|
game = "Heretic"
|
||||||
web = HereticWeb()
|
web = HereticWeb()
|
||||||
data_version = 3
|
|
||||||
required_client_version = (0, 3, 9)
|
required_client_version = (0, 3, 9)
|
||||||
|
|
||||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||||
|
|
|
@ -154,7 +154,6 @@ class HKWorld(World):
|
||||||
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
||||||
charm_costs: typing.List[int]
|
charm_costs: typing.List[int]
|
||||||
cached_filler_items = {}
|
cached_filler_items = {}
|
||||||
data_version = 2
|
|
||||||
|
|
||||||
def __init__(self, world, player):
|
def __init__(self, world, player):
|
||||||
super(HKWorld, self).__init__(world, player)
|
super(HKWorld, self).__init__(world, player)
|
||||||
|
|
|
@ -37,8 +37,6 @@ class Hylics2World(World):
|
||||||
options_dataclass = Hylics2Options
|
options_dataclass = Hylics2Options
|
||||||
options: Hylics2Options
|
options: Hylics2Options
|
||||||
|
|
||||||
data_version = 3
|
|
||||||
|
|
||||||
|
|
||||||
def set_rules(self):
|
def set_rules(self):
|
||||||
Rules.set_rules(self)
|
Rules.set_rules(self)
|
||||||
|
|
|
@ -330,9 +330,9 @@ class KDL3SNIClient(SNIClient):
|
||||||
item = ctx.items_received[recv_amount]
|
item = ctx.items_received[recv_amount]
|
||||||
recv_amount += 1
|
recv_amount += 1
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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))
|
snes_buffered_write(ctx, KDL3_RECV_COUNT, pack("H", recv_amount))
|
||||||
item_idx = item.item & 0x00000F
|
item_idx = item.item & 0x00000F
|
||||||
|
@ -415,7 +415,7 @@ class KDL3SNIClient(SNIClient):
|
||||||
|
|
||||||
for new_check_id in new_checks:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
||||||
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||||
|
|
|
@ -78,11 +78,6 @@ class LinksAwakeningWorld(World):
|
||||||
settings: typing.ClassVar[LinksAwakeningSettings]
|
settings: typing.ClassVar[LinksAwakeningSettings]
|
||||||
topology_present = True # show path to required location checks in spoiler
|
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
|
# ID of first item and location, could be hard-coded but code may be easier
|
||||||
# to read with this as a propery.
|
# to read with this as a propery.
|
||||||
base_id = BASE_ID
|
base_id = BASE_ID
|
||||||
|
|
|
@ -37,7 +37,6 @@ class LingoWorld(World):
|
||||||
|
|
||||||
base_id = 444400
|
base_id = 444400
|
||||||
topology_present = True
|
topology_present = True
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
options_dataclass = LingoOptions
|
options_dataclass = LingoOptions
|
||||||
options: LingoOptions
|
options: LingoOptions
|
||||||
|
|
|
@ -147,9 +147,9 @@ class L2ACSNIClient(SNIClient):
|
||||||
snes_items_received += 1
|
snes_items_received += 1
|
||||||
|
|
||||||
snes_logger.info("Received %s from %s (%s) (%d/%d in list)" % (
|
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.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_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 + 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"))
|
snes_buffered_write(ctx, L2AC_RX_ADDR, snes_items_received.to_bytes(2, "little"))
|
||||||
|
|
|
@ -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},
|
"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},
|
"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)
|
required_client_version: Tuple[int, int, int] = (0, 4, 4)
|
||||||
|
|
||||||
# L2ACWorld specific properties
|
# L2ACWorld specific properties
|
||||||
|
|
|
@ -44,8 +44,6 @@ class MeritousWorld(World):
|
||||||
location_name_to_id = location_table
|
location_name_to_id = location_table
|
||||||
item_name_groups = item_groups
|
item_name_groups = item_groups
|
||||||
|
|
||||||
data_version = 2
|
|
||||||
|
|
||||||
# NOTE: Remember to change this before this game goes live
|
# NOTE: Remember to change this before this game goes live
|
||||||
required_client_version = (0, 2, 4)
|
required_client_version = (0, 2, 4)
|
||||||
|
|
||||||
|
|
|
@ -92,8 +92,6 @@ class MinecraftWorld(World):
|
||||||
item_name_to_id = Constants.item_name_to_id
|
item_name_to_id = Constants.item_name_to_id
|
||||||
location_name_to_id = Constants.location_name_to_id
|
location_name_to_id = Constants.location_name_to_id
|
||||||
|
|
||||||
data_version = 7
|
|
||||||
|
|
||||||
def _get_mc_data(self) -> Dict[str, Any]:
|
def _get_mc_data(self) -> Dict[str, Any]:
|
||||||
exits = [connection[0] for connection in Constants.region_info["default_connections"]]
|
exits = [connection[0] for connection in Constants.region_info["default_connections"]]
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -57,8 +57,6 @@ class MMBN3World(World):
|
||||||
settings: typing.ClassVar[MMBN3Settings]
|
settings: typing.ClassVar[MMBN3Settings]
|
||||||
topology_present = False
|
topology_present = False
|
||||||
|
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
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}
|
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ class NoitaWorld(World):
|
||||||
|
|
||||||
item_name_groups = items.item_name_groups
|
item_name_groups = items.item_name_groups
|
||||||
location_name_groups = locations.location_name_groups
|
location_name_groups = locations.location_name_groups
|
||||||
data_version = 2
|
|
||||||
|
|
||||||
web = NoitaWeb()
|
web = NoitaWeb()
|
||||||
|
|
||||||
|
|
|
@ -150,8 +150,6 @@ class OOTWorld(World):
|
||||||
location_name_to_id = location_name_to_id
|
location_name_to_id = location_name_to_id
|
||||||
web = OOTWeb()
|
web = OOTWeb()
|
||||||
|
|
||||||
data_version = 3
|
|
||||||
|
|
||||||
required_client_version = (0, 4, 0)
|
required_client_version = (0, 4, 0)
|
||||||
|
|
||||||
item_name_groups = {
|
item_name_groups = {
|
||||||
|
|
|
@ -48,7 +48,6 @@ class Overcooked2World(World):
|
||||||
web = Overcooked2Web()
|
web = Overcooked2Web()
|
||||||
required_client_version = (0, 3, 8)
|
required_client_version = (0, 3, 8)
|
||||||
topology_present: bool = False
|
topology_present: bool = False
|
||||||
data_version = 3
|
|
||||||
|
|
||||||
item_name_to_id = item_name_to_id
|
item_name_to_id = item_name_to_id
|
||||||
item_id_to_name = item_id_to_name
|
item_id_to_name = item_id_to_name
|
||||||
|
|
|
@ -87,7 +87,6 @@ class PokemonEmeraldWorld(World):
|
||||||
item_name_groups = ITEM_GROUPS
|
item_name_groups = ITEM_GROUPS
|
||||||
location_name_groups = LOCATION_GROUPS
|
location_name_groups = LOCATION_GROUPS
|
||||||
|
|
||||||
data_version = 2
|
|
||||||
required_client_version = (0, 4, 6)
|
required_client_version = (0, 4, 6)
|
||||||
|
|
||||||
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
|
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
|
||||||
|
|
|
@ -74,7 +74,6 @@ class PokemonRedBlueWorld(World):
|
||||||
option_definitions = pokemon_rb_options
|
option_definitions = pokemon_rb_options
|
||||||
settings: typing.ClassVar[PokemonSettings]
|
settings: typing.ClassVar[PokemonSettings]
|
||||||
|
|
||||||
data_version = 9
|
|
||||||
required_client_version = (0, 4, 2)
|
required_client_version = (0, 4, 2)
|
||||||
|
|
||||||
topology_present = True
|
topology_present = True
|
||||||
|
|
|
@ -39,7 +39,6 @@ class RaftWorld(World):
|
||||||
location_name_to_id = locations_lookup_name_to_id
|
location_name_to_id = locations_lookup_name_to_id
|
||||||
option_definitions = raft_options
|
option_definitions = raft_options
|
||||||
|
|
||||||
data_version = 2
|
|
||||||
required_client_version = (0, 3, 4)
|
required_client_version = (0, 3, 4)
|
||||||
|
|
||||||
def create_items(self):
|
def create_items(self):
|
||||||
|
|
|
@ -35,7 +35,6 @@ class RLWorld(World):
|
||||||
game = "Rogue Legacy"
|
game = "Rogue Legacy"
|
||||||
option_definitions = rl_options
|
option_definitions = rl_options
|
||||||
topology_present = True
|
topology_present = True
|
||||||
data_version = 4
|
|
||||||
required_client_version = (0, 3, 5)
|
required_client_version = (0, 3, 5)
|
||||||
web = RLWeb()
|
web = RLWeb()
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ class RiskOfRainWorld(World):
|
||||||
}
|
}
|
||||||
location_name_to_id = item_pickups
|
location_name_to_id = item_pickups
|
||||||
|
|
||||||
data_version = 9
|
|
||||||
required_client_version = (0, 4, 5)
|
required_client_version = (0, 4, 5)
|
||||||
web = RiskOfWeb()
|
web = RiskOfWeb()
|
||||||
total_revivals: int
|
total_revivals: int
|
||||||
|
|
|
@ -58,7 +58,6 @@ class SA2BWorld(World):
|
||||||
options_dataclass = SA2BOptions
|
options_dataclass = SA2BOptions
|
||||||
options: SA2BOptions
|
options: SA2BOptions
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 7
|
|
||||||
|
|
||||||
item_name_groups = item_groups
|
item_name_groups = item_groups
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||||
|
|
|
@ -244,10 +244,10 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||||
self.formatted_print(f" [u]{faction.name}[/u] ")
|
self.formatted_print(f" [u]{faction.name}[/u] ")
|
||||||
|
|
||||||
for item_id in categorized_items[faction]:
|
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, []))
|
received_child_items = items_received_set.intersection(parent_to_child.get(item_id, []))
|
||||||
matching_children = [child for child in received_child_items
|
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, [])
|
received_items_of_this_type = items_received.get(item_id, [])
|
||||||
item_is_match = item_matches_filter(item_name)
|
item_is_match = item_matches_filter(item_name)
|
||||||
if item_is_match or len(matching_children) > 0:
|
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))
|
objectives = set(ctx.locations_for_mission(mission))
|
||||||
if objectives:
|
if objectives:
|
||||||
remaining_objectives = objectives.difference(ctx.checked_locations)
|
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:
|
else:
|
||||||
unfinished_locations[mission] = []
|
unfinished_locations[mission] = []
|
||||||
|
|
||||||
|
|
|
@ -269,7 +269,7 @@ class SC2Manager(GameManager):
|
||||||
for loc in self.ctx.locations_for_mission(mission_name):
|
for loc in self.ctx.locations_for_mission(mission_name):
|
||||||
if loc in self.ctx.missing_locations:
|
if loc in self.ctx.missing_locations:
|
||||||
count += 1
|
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 = []
|
plando_locations = []
|
||||||
for plando_loc in self.ctx.plando_locations:
|
for plando_loc in self.ctx.plando_locations:
|
||||||
|
|
|
@ -28,7 +28,6 @@ class ShortHikeWorld(World):
|
||||||
|
|
||||||
game = "A Short Hike"
|
game = "A Short Hike"
|
||||||
web = ShortHikeWeb()
|
web = ShortHikeWeb()
|
||||||
data_version = 2
|
|
||||||
|
|
||||||
item_name_to_id = {item["name"]: item["id"] for item in item_table}
|
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}
|
location_name_to_id = {loc["name"]: loc["id"] for loc in location_table}
|
||||||
|
|
|
@ -123,7 +123,7 @@ class SMSNIClient(SNIClient):
|
||||||
location_id = locations_start_id + item_index
|
location_id = locations_start_id + item_index
|
||||||
|
|
||||||
ctx.locations_checked.add(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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||||
|
@ -151,9 +151,8 @@ class SMSNIClient(SNIClient):
|
||||||
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
||||||
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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)
|
await snes_flush_writes(ctx)
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,6 @@ class SMWorld(World):
|
||||||
|
|
||||||
game: str = "Super Metroid"
|
game: str = "Super Metroid"
|
||||||
topology_present = True
|
topology_present = True
|
||||||
data_version = 3
|
|
||||||
option_definitions = sm_options
|
option_definitions = sm_options
|
||||||
settings: typing.ClassVar[SMSettings]
|
settings: typing.ClassVar[SMSettings]
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ class SM64World(World):
|
||||||
item_name_to_id = item_table
|
item_name_to_id = item_table
|
||||||
location_name_to_id = location_table
|
location_name_to_id = location_table
|
||||||
|
|
||||||
data_version = 9
|
|
||||||
required_client_version = (0, 3, 5)
|
required_client_version = (0, 3, 5)
|
||||||
|
|
||||||
area_connections: typing.Dict[int, int]
|
area_connections: typing.Dict[int, int]
|
||||||
|
|
|
@ -448,7 +448,7 @@ class SMWSNIClient(SNIClient):
|
||||||
|
|
||||||
for new_check_id in new_checks:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||||
|
@ -499,15 +499,16 @@ class SMWSNIClient(SNIClient):
|
||||||
if recv_index < len(ctx.items_received):
|
if recv_index < len(ctx.items_received):
|
||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
|
sending_game = ctx.slot_info[item.player].game
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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 self.should_show_message(ctx, item):
|
||||||
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
||||||
# Don't send messages for Boss Tokens
|
# 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]
|
player_name = ctx.player_names[item.player]
|
||||||
|
|
||||||
receive_message = generate_received_text(item_name, player_name)
|
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]))
|
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
|
||||||
if item.item in trap_rom_data:
|
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]
|
player_name = ctx.player_names[item.player]
|
||||||
|
|
||||||
receive_message = generate_received_text(item_name, player_name)
|
receive_message = generate_received_text(item_name, player_name)
|
||||||
|
@ -596,7 +597,7 @@ class SMWSNIClient(SNIClient):
|
||||||
for loc_id in ctx.checked_locations:
|
for loc_id in ctx.checked_locations:
|
||||||
if loc_id not in ctx.locations_checked:
|
if loc_id not in ctx.locations_checked:
|
||||||
ctx.locations_checked.add(loc_id)
|
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:
|
if loc_name not in location_id_to_level_id:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -109,7 +109,7 @@ class SMZ3SNIClient(SNIClient):
|
||||||
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
||||||
|
|
||||||
ctx.locations_checked.add(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} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||||
|
|
||||||
|
@ -132,8 +132,7 @@ class SMZ3SNIClient(SNIClient):
|
||||||
item_out_ptr += 1
|
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]))
|
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)' % (
|
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'),
|
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)
|
await snes_flush_writes(ctx)
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@ class SMZ3World(World):
|
||||||
"""
|
"""
|
||||||
game: str = "SMZ3"
|
game: str = "SMZ3"
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 3
|
|
||||||
option_definitions = smz3_options
|
option_definitions = smz3_options
|
||||||
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
|
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
|
||||||
location_names: Set[str]
|
location_names: Set[str]
|
||||||
|
|
|
@ -176,7 +176,6 @@ class SoEWorld(World):
|
||||||
options: SoEOptions
|
options: SoEOptions
|
||||||
settings: typing.ClassVar[SoESettings]
|
settings: typing.ClassVar[SoESettings]
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 5
|
|
||||||
web = SoEWebWorld()
|
web = SoEWebWorld()
|
||||||
required_client_version = (0, 4, 4)
|
required_client_version = (0, 4, 4)
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ class SpireWorld(World):
|
||||||
option_definitions = spire_options
|
option_definitions = spire_options
|
||||||
game = "Slay the Spire"
|
game = "Slay the Spire"
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 2
|
|
||||||
web = SpireWeb()
|
web = SpireWeb()
|
||||||
required_client_version = (0, 3, 7)
|
required_client_version = (0, 3, 7)
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,6 @@ class StardewValleyWorld(World):
|
||||||
[location.name for location in locations] for group, locations in locations_by_tag.items()
|
[location.name for location in locations] for group, locations in locations_by_tag.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
data_version = 3
|
|
||||||
required_client_version = (0, 4, 0)
|
required_client_version = (0, 4, 0)
|
||||||
|
|
||||||
options_dataclass = StardewValleyOptions
|
options_dataclass = StardewValleyOptions
|
||||||
|
|
|
@ -44,7 +44,6 @@ class SubnauticaWorld(World):
|
||||||
location_name_to_id = all_locations
|
location_name_to_id = all_locations
|
||||||
options_dataclass = options.SubnauticaOptions
|
options_dataclass = options.SubnauticaOptions
|
||||||
options: options.SubnauticaOptions
|
options: options.SubnauticaOptions
|
||||||
data_version = 10
|
|
||||||
required_client_version = (0, 4, 1)
|
required_client_version = (0, 4, 1)
|
||||||
|
|
||||||
creatures_to_scan: List[str]
|
creatures_to_scan: List[str]
|
||||||
|
|
|
@ -52,11 +52,6 @@ class TerrariaWorld(World):
|
||||||
options_dataclass = TerrariaOptions
|
options_dataclass = TerrariaOptions
|
||||||
options: 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
|
item_name_to_id = item_name_to_id
|
||||||
location_name_to_id = location_name_to_id
|
location_name_to_id = location_name_to_id
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ class TimespinnerWorld(World):
|
||||||
option_definitions = timespinner_options
|
option_definitions = timespinner_options
|
||||||
game = "Timespinner"
|
game = "Timespinner"
|
||||||
topology_present = True
|
topology_present = True
|
||||||
data_version = 12
|
|
||||||
web = TimespinnerWebWorld()
|
web = TimespinnerWebWorld()
|
||||||
required_client_version = (0, 4, 2)
|
required_client_version = (0, 4, 2)
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@ class TLoZWorld(World):
|
||||||
settings: typing.ClassVar[TLoZSettings]
|
settings: typing.ClassVar[TLoZSettings]
|
||||||
game = "The Legend of Zelda"
|
game = "The Legend of Zelda"
|
||||||
topology_present = False
|
topology_present = False
|
||||||
data_version = 1
|
|
||||||
base_id = 7000
|
base_id = 7000
|
||||||
web = TLoZWeb()
|
web = TLoZWeb()
|
||||||
|
|
||||||
|
|
|
@ -52,8 +52,6 @@ class UndertaleWorld(World):
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
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()}
|
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
|
||||||
|
|
||||||
data_version = 7
|
|
||||||
|
|
||||||
def _get_undertale_data(self):
|
def _get_undertale_data(self):
|
||||||
return {
|
return {
|
||||||
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
||||||
|
|
|
@ -34,8 +34,6 @@ class V6World(World):
|
||||||
item_name_to_id = item_table
|
item_name_to_id = item_table
|
||||||
location_name_to_id = location_table
|
location_name_to_id = location_table
|
||||||
|
|
||||||
data_version = 1
|
|
||||||
|
|
||||||
area_connections: typing.Dict[int, int]
|
area_connections: typing.Dict[int, int]
|
||||||
area_cost_map: typing.Dict[int,int]
|
area_cost_map: typing.Dict[int,int]
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ class YoshisIslandSNIClient(SNIClient):
|
||||||
|
|
||||||
for new_check_id in new_checks:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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)
|
total_locations = len(ctx.missing_locations) + len(ctx.checked_locations)
|
||||||
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
||||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
||||||
|
@ -127,9 +127,9 @@ class YoshisIslandSNIClient(SNIClient):
|
||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
logging.info("Received %s from %s (%s) (%d/%d in list)" % (
|
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"),
|
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))
|
snes_buffered_write(ctx, ITEMQUEUE_HIGH, pack("H", recv_index))
|
||||||
if item.item in item_values:
|
if item.item in item_values:
|
||||||
|
|
|
@ -86,11 +86,6 @@ class ZillionWorld(World):
|
||||||
item_name_to_id = _item_name_to_id
|
item_name_to_id = _item_name_to_id
|
||||||
location_name_to_id = _loc_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
|
logger: logging.Logger
|
||||||
|
|
||||||
class LogStreamInterface:
|
class LogStreamInterface:
|
||||||
|
|
Loading…
Reference in New Issue