Core: Remove Universally Unique ID Requirements (Per-Game Data Packages) (#1933)
This commit is contained in:
parent
f3003ff147
commit
5aa6ad63ca
AdventureClient.pyCommonClient.pyMultiServer.pyNetUtils.pyUndertaleClient.pyUtils.pyWargrooveClient.py
WebHostLib
Zelda1Client.pydocs
kvui.pytest
worlds
AutoWorld.py__init__.py
adventure
alttp
bk_sudoku
blasphemous
bumpstik
checksfinder
clique
cv64
dark_souls_3
dkc3
dlcquest
doom_1993
doom_ii
factorio
ff1
ffmq
generic
heretic
hk
hylics2
kdl3
ladx
lingo
lufia2ac
meritous
minecraft
mmbn3
noita
oot
overcooked2
pokemon_emerald
pokemon_rb
raft
rogue_legacy
ror2
sa2b
sc2
shorthike
sm
sm64ex
smw
smz3
soe
spire
stardew_valley
subnautica
terraria
timespinner
tloz
undertale
v6
yoshisisland
zillion
|
@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
|
|||
if ': !' not in msg:
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "ReceivedItems":
|
||||
msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}"
|
||||
msg = f"Received {', '.join([self.item_names.lookup_in_slot(item.item) for item in args['items']])}"
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "Retrieved":
|
||||
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import logging
|
||||
import asyncio
|
||||
|
@ -8,6 +9,7 @@ import sys
|
|||
import typing
|
||||
import time
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
@ -173,10 +175,74 @@ class CommonContext:
|
|||
items_handling: typing.Optional[int] = None
|
||||
want_slot_data: bool = True # should slot_data be retrieved via Connect
|
||||
|
||||
# data package
|
||||
# Contents in flux until connection to server is made, to download correct data for this multiworld.
|
||||
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
|
||||
location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
|
||||
class NameLookupDict:
|
||||
"""A specialized dict, with helper methods, for id -> name item/location data package lookups by game."""
|
||||
def __init__(self, ctx: CommonContext, lookup_type: typing.Literal["item", "location"]):
|
||||
self.ctx: CommonContext = ctx
|
||||
self.lookup_type: typing.Literal["item", "location"] = lookup_type
|
||||
self._unknown_item: typing.Callable[[int], str] = lambda key: f"Unknown {lookup_type} (ID: {key})"
|
||||
self._archipelago_lookup: typing.Dict[int, str] = {}
|
||||
self._flat_store: typing.Dict[int, str] = Utils.KeyedDefaultDict(self._unknown_item)
|
||||
self._game_store: typing.Dict[str, typing.ChainMap[int, str]] = collections.defaultdict(
|
||||
lambda: collections.ChainMap(self._archipelago_lookup, Utils.KeyedDefaultDict(self._unknown_item)))
|
||||
self.warned: bool = False
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
def __getitem__(self, key: str) -> typing.Mapping[int, str]:
|
||||
# TODO: In a future version (0.6.0?) this should be simplified by removing implicit id lookups support.
|
||||
if isinstance(key, int):
|
||||
if not self.warned:
|
||||
# Use warnings instead of logger to avoid deprecation message from appearing on user side.
|
||||
self.warned = True
|
||||
warnings.warn(f"Implicit name lookup by id only is deprecated and only supported to maintain "
|
||||
f"backwards compatibility for now. If multiple games share the same id for a "
|
||||
f"{self.lookup_type}, name could be incorrect. Please use "
|
||||
f"`{self.lookup_type}_names.lookup_in_game()` or "
|
||||
f"`{self.lookup_type}_names.lookup_in_slot()` instead.")
|
||||
return self._flat_store[key] # type: ignore
|
||||
|
||||
return self._game_store[key]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._game_store)
|
||||
|
||||
def __iter__(self) -> typing.Iterator[str]:
|
||||
return iter(self._game_store)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self._game_store.__repr__()
|
||||
|
||||
def lookup_in_game(self, code: int, game_name: typing.Optional[str] = None) -> str:
|
||||
"""Returns the name for an item/location id in the context of a specific game or own game if `game` is
|
||||
omitted.
|
||||
"""
|
||||
if game_name is None:
|
||||
game_name = self.ctx.game
|
||||
assert game_name is not None, f"Attempted to lookup {self.lookup_type} with no game name available."
|
||||
|
||||
return self._game_store[game_name][code]
|
||||
|
||||
def lookup_in_slot(self, code: int, slot: typing.Optional[int] = None) -> str:
|
||||
"""Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is
|
||||
omitted.
|
||||
"""
|
||||
if slot is None:
|
||||
slot = self.ctx.slot
|
||||
assert slot is not None, f"Attempted to lookup {self.lookup_type} with no slot info available."
|
||||
|
||||
return self.lookup_in_game(code, self.ctx.slot_info[slot].game)
|
||||
|
||||
def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int]) -> None:
|
||||
"""Overrides existing lookup tables for a particular game."""
|
||||
id_to_name_lookup_table = Utils.KeyedDefaultDict(self._unknown_item)
|
||||
id_to_name_lookup_table.update({code: name for name, code in name_to_id_lookup_table.items()})
|
||||
self._game_store[game] = collections.ChainMap(self._archipelago_lookup, id_to_name_lookup_table)
|
||||
self._flat_store.update(id_to_name_lookup_table) # Only needed for legacy lookup method.
|
||||
if game == "Archipelago":
|
||||
# Keep track of the Archipelago data package separately so if it gets updated in a custom datapackage,
|
||||
# it updates in all chain maps automatically.
|
||||
self._archipelago_lookup.clear()
|
||||
self._archipelago_lookup.update(id_to_name_lookup_table)
|
||||
|
||||
# defaults
|
||||
starting_reconnect_delay: int = 5
|
||||
|
@ -231,7 +297,7 @@ class CommonContext:
|
|||
# message box reporting a loss of connection
|
||||
_messagebox_connection_loss: typing.Optional["kvui.MessageBox"] = None
|
||||
|
||||
def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None:
|
||||
def __init__(self, server_address: typing.Optional[str] = None, password: typing.Optional[str] = None) -> None:
|
||||
# server state
|
||||
self.server_address = server_address
|
||||
self.username = None
|
||||
|
@ -271,6 +337,9 @@ class CommonContext:
|
|||
self.exit_event = asyncio.Event()
|
||||
self.watcher_event = asyncio.Event()
|
||||
|
||||
self.item_names = self.NameLookupDict(self, "item")
|
||||
self.location_names = self.NameLookupDict(self, "location")
|
||||
|
||||
self.jsontotextparser = JSONtoTextParser(self)
|
||||
self.rawjsontotextparser = RawJSONtoTextParser(self)
|
||||
self.update_data_package(network_data_package)
|
||||
|
@ -486,19 +555,17 @@ class CommonContext:
|
|||
or remote_checksum != cache_checksum:
|
||||
needed_updates.add(game)
|
||||
else:
|
||||
self.update_game(cached_game)
|
||||
self.update_game(cached_game, game)
|
||||
if needed_updates:
|
||||
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
|
||||
|
||||
def update_game(self, game_package: dict):
|
||||
for item_name, item_id in game_package["item_name_to_id"].items():
|
||||
self.item_names[item_id] = item_name
|
||||
for location_name, location_id in game_package["location_name_to_id"].items():
|
||||
self.location_names[location_id] = location_name
|
||||
def update_game(self, game_package: dict, game: str):
|
||||
self.item_names.update_game(game, game_package["item_name_to_id"])
|
||||
self.location_names.update_game(game, game_package["location_name_to_id"])
|
||||
|
||||
def update_data_package(self, data_package: dict):
|
||||
for game, game_data in data_package["games"].items():
|
||||
self.update_game(game_data)
|
||||
self.update_game(game_data, game)
|
||||
|
||||
def consume_network_data_package(self, data_package: dict):
|
||||
self.update_data_package(data_package)
|
||||
|
|
|
@ -168,9 +168,11 @@ class Context:
|
|||
slot_info: typing.Dict[int, NetworkSlot]
|
||||
generator_version = Version(0, 0, 0)
|
||||
checksums: typing.Dict[str, str]
|
||||
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
|
||||
item_names: typing.Dict[str, typing.Dict[int, str]] = (
|
||||
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')))
|
||||
item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
|
||||
location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
|
||||
location_names: typing.Dict[str, typing.Dict[int, str]] = (
|
||||
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')))
|
||||
location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
|
||||
all_item_and_group_names: typing.Dict[str, typing.Set[str]]
|
||||
all_location_and_group_names: typing.Dict[str, typing.Set[str]]
|
||||
|
@ -271,14 +273,21 @@ class Context:
|
|||
if "checksum" in game_package:
|
||||
self.checksums[game_name] = game_package["checksum"]
|
||||
for item_name, item_id in game_package["item_name_to_id"].items():
|
||||
self.item_names[item_id] = item_name
|
||||
self.item_names[game_name][item_id] = item_name
|
||||
for location_name, location_id in game_package["location_name_to_id"].items():
|
||||
self.location_names[location_id] = location_name
|
||||
self.location_names[game_name][location_id] = location_name
|
||||
self.all_item_and_group_names[game_name] = \
|
||||
set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name])
|
||||
self.all_location_and_group_names[game_name] = \
|
||||
set(game_package["location_name_to_id"]) | set(self.location_name_groups.get(game_name, []))
|
||||
|
||||
archipelago_item_names = self.item_names["Archipelago"]
|
||||
archipelago_location_names = self.location_names["Archipelago"]
|
||||
for game in [game_name for game_name in self.gamespackage if game_name != "Archipelago"]:
|
||||
# Add Archipelago items and locations to each data package.
|
||||
self.item_names[game].update(archipelago_item_names)
|
||||
self.location_names[game].update(archipelago_location_names)
|
||||
|
||||
def item_names_for_game(self, game: str) -> typing.Optional[typing.Dict[str, int]]:
|
||||
return self.gamespackage[game]["item_name_to_id"] if game in self.gamespackage else None
|
||||
|
||||
|
@ -783,10 +792,7 @@ async def on_client_connected(ctx: Context, client: Client):
|
|||
for slot, connected_clients in clients.items():
|
||||
if connected_clients:
|
||||
name = ctx.player_names[team, slot]
|
||||
players.append(
|
||||
NetworkPlayer(team, slot,
|
||||
ctx.name_aliases.get((team, slot), name), name)
|
||||
)
|
||||
players.append(NetworkPlayer(team, slot, ctx.name_aliases.get((team, slot), name), name))
|
||||
games = {ctx.games[x] for x in range(1, len(ctx.games) + 1)}
|
||||
games.add("Archipelago")
|
||||
await ctx.send_msgs(client, [{
|
||||
|
@ -801,8 +807,6 @@ async def on_client_connected(ctx: Context, client: Client):
|
|||
'permissions': get_permissions(ctx),
|
||||
'hint_cost': ctx.hint_cost,
|
||||
'location_check_points': ctx.location_check_points,
|
||||
'datapackage_versions': {game: game_data["version"] for game, game_data
|
||||
in ctx.gamespackage.items() if game in games},
|
||||
'datapackage_checksums': {game: game_data["checksum"] for game, game_data
|
||||
in ctx.gamespackage.items() if game in games and "checksum" in game_data},
|
||||
'seed_name': ctx.seed_name,
|
||||
|
@ -1006,8 +1010,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
|
|||
send_items_to(ctx, team, target_player, new_item)
|
||||
|
||||
ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % (
|
||||
team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id],
|
||||
ctx.player_names[(team, target_player)], ctx.location_names[location]))
|
||||
team + 1, ctx.player_names[(team, slot)], ctx.item_names[ctx.slot_info[target_player].game][item_id],
|
||||
ctx.player_names[(team, target_player)], ctx.location_names[ctx.slot_info[slot].game][location]))
|
||||
info_text = json_format_send_event(new_item, target_player)
|
||||
ctx.broadcast_team(team, [info_text])
|
||||
|
||||
|
@ -1061,8 +1065,8 @@ def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location
|
|||
|
||||
def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
|
||||
text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
|
||||
f"{ctx.item_names[hint.item]} is " \
|
||||
f"at {ctx.location_names[hint.location]} " \
|
||||
f"{ctx.item_names[ctx.slot_info[hint.receiving_player].game][hint.item]} is " \
|
||||
f"at {ctx.location_names[ctx.slot_info[hint.finding_player].game][hint.location]} " \
|
||||
f"in {ctx.player_names[team, hint.finding_player]}'s World"
|
||||
|
||||
if hint.entrance:
|
||||
|
@ -1364,7 +1368,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
if self.ctx.remaining_mode == "enabled":
|
||||
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
||||
if remaining_item_ids:
|
||||
self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
|
||||
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
|
||||
for item_id in remaining_item_ids))
|
||||
else:
|
||||
self.output("No remaining items found.")
|
||||
|
@ -1377,7 +1381,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
|
||||
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
||||
if remaining_item_ids:
|
||||
self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
|
||||
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
|
||||
for item_id in remaining_item_ids))
|
||||
else:
|
||||
self.output("No remaining items found.")
|
||||
|
@ -1395,7 +1399,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
locations = get_missing_checks(self.ctx, self.client.team, self.client.slot)
|
||||
|
||||
if locations:
|
||||
names = [self.ctx.location_names[location] for location in locations]
|
||||
game = self.ctx.slot_info[self.client.slot].game
|
||||
names = [self.ctx.location_names[game][location] for location in locations]
|
||||
if filter_text:
|
||||
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
|
||||
if filter_text in location_groups: # location group name
|
||||
|
@ -1420,7 +1425,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
locations = get_checked_checks(self.ctx, self.client.team, self.client.slot)
|
||||
|
||||
if locations:
|
||||
names = [self.ctx.location_names[location] for location in locations]
|
||||
game = self.ctx.slot_info[self.client.slot].game
|
||||
names = [self.ctx.location_names[game][location] for location in locations]
|
||||
if filter_text:
|
||||
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
|
||||
if filter_text in location_groups: # location group name
|
||||
|
@ -1501,10 +1507,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
elif input_text.isnumeric():
|
||||
game = self.ctx.games[self.client.slot]
|
||||
hint_id = int(input_text)
|
||||
hint_name = self.ctx.item_names[hint_id] \
|
||||
if not for_location and hint_id in self.ctx.item_names \
|
||||
else self.ctx.location_names[hint_id] \
|
||||
if for_location and hint_id in self.ctx.location_names \
|
||||
hint_name = self.ctx.item_names[game][hint_id] \
|
||||
if not for_location and hint_id in self.ctx.item_names[game] \
|
||||
else self.ctx.location_names[game][hint_id] \
|
||||
if for_location and hint_id in self.ctx.location_names[game] \
|
||||
else None
|
||||
if hint_name in self.ctx.non_hintable_names[game]:
|
||||
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
|
||||
|
|
|
@ -247,7 +247,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
|
|||
|
||||
def _handle_item_id(self, node: JSONMessagePart):
|
||||
item_id = int(node["text"])
|
||||
node["text"] = self.ctx.item_names[item_id]
|
||||
node["text"] = self.ctx.item_names.lookup_in_slot(item_id, node["player"])
|
||||
return self._handle_item_name(node)
|
||||
|
||||
def _handle_location_name(self, node: JSONMessagePart):
|
||||
|
@ -255,8 +255,8 @@ class JSONtoTextParser(metaclass=HandlerMeta):
|
|||
return self._handle_color(node)
|
||||
|
||||
def _handle_location_id(self, node: JSONMessagePart):
|
||||
item_id = int(node["text"])
|
||||
node["text"] = self.ctx.location_names[item_id]
|
||||
location_id = int(node["text"])
|
||||
node["text"] = self.ctx.location_names.lookup_in_slot(location_id, node["player"])
|
||||
return self._handle_location_name(node)
|
||||
|
||||
def _handle_entrance_name(self, node: JSONMessagePart):
|
||||
|
|
|
@ -247,8 +247,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
|||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
toDraw = ""
|
||||
for i in range(20):
|
||||
if i < len(str(ctx.item_names[l.item])):
|
||||
toDraw += str(ctx.item_names[l.item])[i]
|
||||
if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
|
||||
toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
|
||||
else:
|
||||
break
|
||||
f.write(toDraw)
|
||||
|
|
5
Utils.py
5
Utils.py
|
@ -46,7 +46,7 @@ class Version(typing.NamedTuple):
|
|||
return ".".join(str(item) for item in self)
|
||||
|
||||
|
||||
__version__ = "0.4.6"
|
||||
__version__ = "0.5.0"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
|
@ -458,6 +458,9 @@ class KeyedDefaultDict(collections.defaultdict):
|
|||
"""defaultdict variant that uses the missing key as argument to default_factory"""
|
||||
default_factory: typing.Callable[[typing.Any], typing.Any]
|
||||
|
||||
def __init__(self, default_factory: typing.Callable[[Any], Any] = None, **kwargs):
|
||||
super().__init__(default_factory, **kwargs)
|
||||
|
||||
def __missing__(self, key):
|
||||
self[key] = value = self.default_factory(key)
|
||||
return value
|
||||
|
|
|
@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
|
|||
if not os.path.isfile(path):
|
||||
open(path, 'w').close()
|
||||
# Announcing commander unlocks
|
||||
item_name = self.item_names[network_item.item]
|
||||
item_name = self.item_names.lookup_in_slot(network_item.item)
|
||||
if item_name in faction_table.keys():
|
||||
for commander in faction_table[item_name]:
|
||||
logger.info(f"{commander.name} has been unlocked!")
|
||||
|
@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
|
|||
open(print_path, 'w').close()
|
||||
with open(print_path, 'w') as f:
|
||||
f.write("Received " +
|
||||
self.item_names[network_item.item] +
|
||||
self.item_names.lookup_in_slot(network_item.item) +
|
||||
" from " +
|
||||
self.player_names[network_item.player])
|
||||
f.close()
|
||||
|
@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
|
|||
faction_items = 0
|
||||
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
||||
for network_item in self.items_received:
|
||||
if self.item_names[network_item.item] in faction_item_names:
|
||||
if self.item_names.lookup_in_slot(network_item.item) in faction_item_names:
|
||||
faction_items += 1
|
||||
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
||||
# Must be an integer larger than 0
|
||||
|
|
|
@ -56,15 +56,6 @@ def get_datapackage():
|
|||
return network_data_package
|
||||
|
||||
|
||||
@api_endpoints.route('/datapackage_version')
|
||||
@cache.cached()
|
||||
def get_datapackage_versions():
|
||||
from worlds import AutoWorldRegister
|
||||
|
||||
version_package = {game: world.data_version for game, world in AutoWorldRegister.world_types.items()}
|
||||
return version_package
|
||||
|
||||
|
||||
@api_endpoints.route('/datapackage_checksum')
|
||||
@cache.cached()
|
||||
def get_datapackage_checksums():
|
||||
|
|
|
@ -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):
|
||||
checked_location_names = [ctx.location_names[location] for location in ctx.checked_locations]
|
||||
checked_location_names = [ctx.location_names.lookup_in_slot(location) for location in ctx.checked_locations]
|
||||
shops = [location for location in checked_location_names if "Shop" in location]
|
||||
left_slots = [shop for shop in shops if "Left" in shop]
|
||||
middle_slots = [shop for shop in shops if "Middle" in shop]
|
||||
|
@ -190,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone=
|
|||
locations_checked = []
|
||||
location = None
|
||||
for location in ctx.missing_locations:
|
||||
location_name = ctx.location_names[location]
|
||||
location_name = ctx.location_names.lookup_in_slot(location)
|
||||
|
||||
if location_name in Locations.overworld_locations and zone == "overworld":
|
||||
status = locations_array[Locations.major_location_offsets[location_name]]
|
||||
|
|
|
@ -53,7 +53,7 @@ Example:
|
|||
```
|
||||
|
||||
## (Server -> Client)
|
||||
These packets are are sent from the multiworld server to the client. They are not messages which the server accepts.
|
||||
These packets are sent from the multiworld server to the client. They are not messages which the server accepts.
|
||||
* [RoomInfo](#RoomInfo)
|
||||
* [ConnectionRefused](#ConnectionRefused)
|
||||
* [Connected](#Connected)
|
||||
|
@ -80,7 +80,6 @@ Sent to clients when they connect to an Archipelago server.
|
|||
| hint_cost | int | The percentage of total locations that need to be checked to receive a hint from the server. |
|
||||
| location_check_points | int | The amount of hint points you receive per item/location check completed. |
|
||||
| games | list\[str\] | List of games present in this multiworld. |
|
||||
| datapackage_versions | dict\[str, int\] | Data versions of the individual games' data packages the server will send. Used to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents). **Deprecated. Use `datapackage_checksums` instead.** |
|
||||
| datapackage_checksums | dict[str, str] | Checksum hash of the individual games' data packages the server will send. Used by newer clients to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents) for more information. |
|
||||
| seed_name | str | Uniquely identifying name of this generation |
|
||||
| time | float | Unix time stamp of "now". Send for time synchronization if wanted for things like the DeathLink Bounce. |
|
||||
|
@ -500,9 +499,9 @@ In JSON this may look like:
|
|||
{"item": 3, "location": 3, "player": 3, "flags": 0}
|
||||
]
|
||||
```
|
||||
`item` is the item id of the item. Item ids are in the range of ± 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
|
||||
|
||||
|
@ -646,15 +645,47 @@ class Hint(typing.NamedTuple):
|
|||
```
|
||||
|
||||
### Data Package Contents
|
||||
A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago server most easily. Currently, this package is used to send ID to name mappings so that clients need not maintain their own mappings.
|
||||
A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago
|
||||
server most easily and not maintain their own mappings. Some contents include:
|
||||
|
||||
We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different version. A special case is datapackage version 0, where it is expected the package is custom and should not be cached.
|
||||
- Name to ID mappings for items and locations.
|
||||
- A checksum of each game's data package for clients to tell if a cached package is invalid.
|
||||
|
||||
Note:
|
||||
* Any ID is unique to its type across AP: Item 56 only exists once and Location 56 only exists once.
|
||||
* Any Name is unique to its type across its own Game only: Single Arrow can exist in two games.
|
||||
* The IDs from the game "Archipelago" may be used in any other game.
|
||||
Especially Location ID -1: Cheat Console and -2: Server (typically Remote Start Inventory)
|
||||
We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know
|
||||
when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different checksum
|
||||
than any locally cached ones.
|
||||
|
||||
**Important Notes about IDs and Names**:
|
||||
|
||||
* IDs ≤ 0 are reserved for "Archipelago" and should not be used by other world implementations.
|
||||
* The IDs from the game "Archipelago" (in `worlds/generic`) may be used in any world.
|
||||
* Especially Location ID `-1`: `Cheat Console` and `-2`: `Server` (typically Remote Start Inventory)
|
||||
* Any names and IDs are only unique in its own world data package, but different games may reuse these names or IDs.
|
||||
* At runtime, you will need to look up the game of the player to know which item or location ID/Name to lookup in the
|
||||
data package. This can be easily achieved by reviewing the `slot_info` for a particular player ID prior to lookup.
|
||||
* For example, a data package like this is valid (Some properties such as `checksum` were omitted):
|
||||
```json
|
||||
{
|
||||
"games": {
|
||||
"Game A": {
|
||||
"location_name_to_id": {
|
||||
"Boss Chest": 40
|
||||
},
|
||||
"item_name_to_id": {
|
||||
"Item X": 12
|
||||
}
|
||||
},
|
||||
"Game B": {
|
||||
"location_name_to_id": {
|
||||
"Minigame Prize": 40
|
||||
},
|
||||
"item_name_to_id": {
|
||||
"Item X": 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Contents
|
||||
| Name | Type | Notes |
|
||||
|
@ -668,7 +699,6 @@ GameData is a **dict** but contains these keys and values. It's broken out into
|
|||
|---------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| item_name_to_id | dict[str, int] | Mapping of all item names to their respective ID. |
|
||||
| location_name_to_id | dict[str, int] | Mapping of all location names to their respective ID. |
|
||||
| version | int | Version number of this game's data. Deprecated. Used by older clients to request an updated datapackage if cache is outdated. |
|
||||
| checksum | str | A checksum hash of this game's data. |
|
||||
|
||||
### Tags
|
||||
|
|
14
kvui.py
14
kvui.py
|
@ -683,10 +683,18 @@ class HintLog(RecycleView):
|
|||
for hint in hints:
|
||||
data.append({
|
||||
"receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
|
||||
"item": {"text": self.parser.handle_node(
|
||||
{"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})},
|
||||
"item": {"text": self.parser.handle_node({
|
||||
"type": "item_id",
|
||||
"text": hint["item"],
|
||||
"flags": hint["item_flags"],
|
||||
"player": hint["receiving_player"],
|
||||
})},
|
||||
"finding": {"text": self.parser.handle_node({"type": "player_id", "text": hint["finding_player"]})},
|
||||
"location": {"text": self.parser.handle_node({"type": "location_id", "text": hint["location"]})},
|
||||
"location": {"text": self.parser.handle_node({
|
||||
"type": "location_id",
|
||||
"text": hint["location"],
|
||||
"player": hint["finding_player"],
|
||||
})},
|
||||
"entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text",
|
||||
"color": "blue", "text": hint["entrance"]
|
||||
if hint["entrance"] else "Vanilla"})},
|
||||
|
|
|
@ -6,22 +6,6 @@ from . import setup_solo_multiworld
|
|||
|
||||
|
||||
class TestIDs(unittest.TestCase):
|
||||
def test_unique_items(self):
|
||||
"""Tests that every game has a unique ID per item in the datapackage"""
|
||||
known_item_ids = set()
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
current = len(known_item_ids)
|
||||
known_item_ids |= set(world_type.item_id_to_name)
|
||||
self.assertEqual(len(known_item_ids) - len(world_type.item_id_to_name), current)
|
||||
|
||||
def test_unique_locations(self):
|
||||
"""Tests that every game has a unique ID per location in the datapackage"""
|
||||
known_location_ids = set()
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
current = len(known_location_ids)
|
||||
known_location_ids |= set(world_type.location_id_to_name)
|
||||
self.assertEqual(len(known_location_ids) - len(world_type.location_id_to_name), current)
|
||||
|
||||
def test_range_items(self):
|
||||
"""There are Javascript clients, which are limited to Number.MAX_SAFE_INTEGER due to 64bit float precision."""
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
|
|
|
@ -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]]] = {}
|
||||
"""maps location group names to sets of locations. Example: {"Sewer": {"Sewer Key Drop 1", "Sewer Key Drop 2"}}"""
|
||||
|
||||
data_version: ClassVar[int] = 0
|
||||
"""
|
||||
Increment this every time something in your world's names/id mappings changes.
|
||||
|
||||
When this is set to 0, that world's DataPackage is considered in "testing mode", which signals to servers/clients
|
||||
that it should not be cached, and clients should request that world's DataPackage every connection. Not
|
||||
recommended for production-ready worlds.
|
||||
|
||||
Deprecated. Clients should utilize `checksum` to determine if DataPackage has changed since last connection and
|
||||
request a new DataPackage, if necessary.
|
||||
"""
|
||||
|
||||
required_client_version: Tuple[int, int, int] = (0, 1, 6)
|
||||
"""
|
||||
override this if changes to a world break forward-compatibility of the client
|
||||
|
@ -543,7 +531,6 @@ class World(metaclass=AutoWorldRegister):
|
|||
"item_name_to_id": cls.item_name_to_id,
|
||||
"location_name_groups": sorted_location_name_groups,
|
||||
"location_name_to_id": cls.location_name_to_id,
|
||||
"version": cls.data_version,
|
||||
}
|
||||
res["checksum"] = data_package_checksum(res)
|
||||
return res
|
||||
|
|
|
@ -33,7 +33,6 @@ class GamesPackage(TypedDict, total=False):
|
|||
location_name_groups: Dict[str, List[str]]
|
||||
location_name_to_id: Dict[str, int]
|
||||
checksum: str
|
||||
version: int # TODO: Remove support after per game data packages API change.
|
||||
|
||||
|
||||
class DataPackage(TypedDict):
|
||||
|
|
|
@ -113,7 +113,6 @@ class AdventureWorld(World):
|
|||
settings: ClassVar[AdventureSettings]
|
||||
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
|
||||
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
||||
data_version: ClassVar[int] = 1
|
||||
required_client_version: Tuple[int, int, int] = (0, 3, 9)
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
|
|
|
@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
|
|||
def new_check(location_id):
|
||||
new_locations.append(location_id)
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names[location_id]
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ' +
|
||||
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
|
||||
|
@ -552,9 +552,9 @@ class ALTTPSNIClient(SNIClient):
|
|||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR,
|
||||
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
|
|
|
@ -213,7 +213,6 @@ class ALTTPWorld(World):
|
|||
item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int}
|
||||
location_name_to_id = lookup_name_to_id
|
||||
|
||||
data_version = 9
|
||||
required_client_version = (0, 4, 1)
|
||||
web = ALTTPWeb()
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ class Bk_SudokuWorld(World):
|
|||
"""
|
||||
game = "Sudoku"
|
||||
web = Bk_SudokuWebWorld()
|
||||
data_version = 1
|
||||
|
||||
item_name_to_id: Dict[str, int] = {}
|
||||
location_name_to_id: Dict[str, int] = {}
|
||||
|
|
|
@ -32,7 +32,6 @@ class BlasphemousWorld(World):
|
|||
|
||||
game: str = "Blasphemous"
|
||||
web = BlasphemousWeb()
|
||||
data_version = 2
|
||||
|
||||
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
|
||||
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
|
||||
|
|
|
@ -39,8 +39,6 @@ class BumpStikWorld(World):
|
|||
location_name_to_id = location_table
|
||||
item_name_groups = item_groups
|
||||
|
||||
data_version = 1
|
||||
|
||||
required_client_version = (0, 3, 8)
|
||||
|
||||
options: BumpstikOptions
|
||||
|
|
|
@ -33,8 +33,6 @@ class ChecksFinderWorld(World):
|
|||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
|
||||
|
||||
data_version = 4
|
||||
|
||||
def _get_checksfinder_data(self):
|
||||
return {
|
||||
'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
||||
|
|
|
@ -37,7 +37,6 @@ class CliqueWorld(World):
|
|||
"""The greatest game of all time."""
|
||||
|
||||
game = "Clique"
|
||||
data_version = 3
|
||||
web = CliqueWebWorld()
|
||||
option_definitions = clique_options
|
||||
location_name_to_id = location_table
|
||||
|
|
|
@ -64,7 +64,6 @@ class CV64World(World):
|
|||
options: CV64Options
|
||||
settings: typing.ClassVar[CV64Settings]
|
||||
topology_present = True
|
||||
data_version = 1
|
||||
|
||||
item_name_to_id = get_item_names_to_ids()
|
||||
location_name_to_id = get_location_names_to_ids()
|
||||
|
|
|
@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
|
|||
text_color = bytearray([0xA2, 0x0B])
|
||||
else:
|
||||
text_color = bytearray([0xA2, 0x02])
|
||||
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names[next_item.item]}\n"
|
||||
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_slot(next_item.item)}\n"
|
||||
f"from {ctx.player_names[next_item.player]}", 96)
|
||||
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
||||
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
||||
|
|
|
@ -49,7 +49,6 @@ class DarkSouls3World(World):
|
|||
option_definitions = dark_souls_options
|
||||
topology_present: bool = True
|
||||
web = DarkSouls3Web()
|
||||
data_version = 8
|
||||
base_id = 100000
|
||||
enabled_location_categories: Set[DS3LocationCategory]
|
||||
required_client_version = (0, 4, 2)
|
||||
|
|
|
@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
|
|||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names[new_check_id]
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
|
@ -99,9 +99,9 @@ class DKC3SNIClient(SNIClient):
|
|||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
|
||||
if item.item in item_rom_data:
|
||||
|
|
|
@ -61,7 +61,6 @@ class DKC3World(World):
|
|||
options: DKC3Options
|
||||
|
||||
topology_present = False
|
||||
data_version = 2
|
||||
#hint_blacklist = {LocationName.rocket_rush_flag}
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
|
|
|
@ -43,8 +43,6 @@ class DLCqworld(World):
|
|||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = location_table
|
||||
|
||||
data_version = 1
|
||||
|
||||
options_dataclass = DLCQuestOptions
|
||||
options: DLCQuestOptions
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@ class DOOM1993World(World):
|
|||
options: DOOM1993Options
|
||||
game = "DOOM 1993"
|
||||
web = DOOM1993Web()
|
||||
data_version = 3
|
||||
required_client_version = (0, 3, 9)
|
||||
|
||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||
|
|
|
@ -43,7 +43,6 @@ class DOOM2World(World):
|
|||
options: DOOM2Options
|
||||
game = "DOOM II"
|
||||
web = DOOM2Web()
|
||||
data_version = 3
|
||||
required_client_version = (0, 3, 9)
|
||||
|
||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||
|
|
|
@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext):
|
|||
if ctx.locations_checked != research_data:
|
||||
bridge_logger.debug(
|
||||
f"New researches done: "
|
||||
f"{[ctx.location_names[rid] for rid in research_data - ctx.locations_checked]}")
|
||||
f"{[ctx.location_names.lookup_in_slot(rid) for rid in research_data - ctx.locations_checked]}")
|
||||
ctx.locations_checked = research_data
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
||||
death_link_tick = data.get("death_link_tick", 0)
|
||||
|
@ -360,7 +360,7 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
|||
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
||||
item_id = transfer_item.item
|
||||
player_name = ctx.player_names[transfer_item.player]
|
||||
item_name = ctx.item_names[item_id]
|
||||
item_name = ctx.item_names.lookup_in_slot(item_id)
|
||||
factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
|
||||
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
|
||||
ctx.send_index += 1
|
||||
|
|
|
@ -95,7 +95,6 @@ class Factorio(World):
|
|||
item_name_groups = {
|
||||
"Progressive": set(progressive_tech_table.keys()),
|
||||
}
|
||||
data_version = 8
|
||||
required_client_version = (0, 4, 2)
|
||||
|
||||
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
|
||||
|
|
|
@ -40,7 +40,6 @@ class FF1World(World):
|
|||
settings_key = "ffr_options"
|
||||
game = "Final Fantasy"
|
||||
topology_present = False
|
||||
data_version = 2
|
||||
|
||||
ff1_items = FF1Items()
|
||||
ff1_locations = FF1Locations()
|
||||
|
|
|
@ -56,8 +56,6 @@ class FFMQWorld(World):
|
|||
create_regions = create_regions
|
||||
set_rules = set_rules
|
||||
stage_set_rules = stage_set_rules
|
||||
|
||||
data_version = 1
|
||||
|
||||
web = FFMQWebWorld()
|
||||
# settings: FFMQSettings
|
||||
|
@ -216,4 +214,3 @@ class FFMQWorld(World):
|
|||
hint_data[self.player][location.address] += f"/{hint}"
|
||||
else:
|
||||
hint_data[self.player][location.address] = hint
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ class GenericWorld(World):
|
|||
}
|
||||
hidden = True
|
||||
web = GenericWeb()
|
||||
data_version = 1
|
||||
|
||||
def generate_early(self):
|
||||
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
|
||||
|
|
|
@ -41,7 +41,6 @@ class HereticWorld(World):
|
|||
options: HereticOptions
|
||||
game = "Heretic"
|
||||
web = HereticWeb()
|
||||
data_version = 3
|
||||
required_client_version = (0, 3, 9)
|
||||
|
||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||
|
|
|
@ -154,7 +154,6 @@ class HKWorld(World):
|
|||
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
||||
charm_costs: typing.List[int]
|
||||
cached_filler_items = {}
|
||||
data_version = 2
|
||||
|
||||
def __init__(self, world, player):
|
||||
super(HKWorld, self).__init__(world, player)
|
||||
|
|
|
@ -37,8 +37,6 @@ class Hylics2World(World):
|
|||
options_dataclass = Hylics2Options
|
||||
options: Hylics2Options
|
||||
|
||||
data_version = 3
|
||||
|
||||
|
||||
def set_rules(self):
|
||||
Rules.set_rules(self)
|
||||
|
|
|
@ -330,9 +330,9 @@ class KDL3SNIClient(SNIClient):
|
|||
item = ctx.items_received[recv_amount]
|
||||
recv_amount += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], recv_amount, len(ctx.items_received)))
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_amount, len(ctx.items_received)))
|
||||
|
||||
snes_buffered_write(ctx, KDL3_RECV_COUNT, pack("H", recv_amount))
|
||||
item_idx = item.item & 0x00000F
|
||||
|
@ -415,7 +415,7 @@ class KDL3SNIClient(SNIClient):
|
|||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names[new_check_id]
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
||||
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
|
|
|
@ -78,11 +78,6 @@ class LinksAwakeningWorld(World):
|
|||
settings: typing.ClassVar[LinksAwakeningSettings]
|
||||
topology_present = True # show path to required location checks in spoiler
|
||||
|
||||
# data_version is used to signal that items, locations or their names
|
||||
# changed. Set this to 0 during development so other games' clients do not
|
||||
# cache any texts, then increase by 1 for each release that makes changes.
|
||||
data_version = 1
|
||||
|
||||
# ID of first item and location, could be hard-coded but code may be easier
|
||||
# to read with this as a propery.
|
||||
base_id = BASE_ID
|
||||
|
|
|
@ -37,7 +37,6 @@ class LingoWorld(World):
|
|||
|
||||
base_id = 444400
|
||||
topology_present = True
|
||||
data_version = 1
|
||||
|
||||
options_dataclass = LingoOptions
|
||||
options: LingoOptions
|
||||
|
|
|
@ -147,9 +147,9 @@ class L2ACSNIClient(SNIClient):
|
|||
snes_items_received += 1
|
||||
|
||||
snes_logger.info("Received %s from %s (%s) (%d/%d in list)" % (
|
||||
ctx.item_names[item.item],
|
||||
ctx.item_names.lookup_in_slot(item.item),
|
||||
ctx.player_names[item.player],
|
||||
ctx.location_names[item.location],
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player),
|
||||
snes_items_received, len(ctx.items_received)))
|
||||
snes_buffered_write(ctx, L2AC_RX_ADDR + 2 * (snes_items_received + 1), item_code.to_bytes(2, "little"))
|
||||
snes_buffered_write(ctx, L2AC_RX_ADDR, snes_items_received.to_bytes(2, "little"))
|
||||
|
|
|
@ -65,7 +65,6 @@ class L2ACWorld(World):
|
|||
"Iris treasures": {name for name, data in l2ac_item_table.items() if data.type is ItemType.IRIS_TREASURE},
|
||||
"Party members": {name for name, data in l2ac_item_table.items() if data.type is ItemType.PARTY_MEMBER},
|
||||
}
|
||||
data_version: ClassVar[int] = 2
|
||||
required_client_version: Tuple[int, int, int] = (0, 4, 4)
|
||||
|
||||
# L2ACWorld specific properties
|
||||
|
|
|
@ -44,8 +44,6 @@ class MeritousWorld(World):
|
|||
location_name_to_id = location_table
|
||||
item_name_groups = item_groups
|
||||
|
||||
data_version = 2
|
||||
|
||||
# NOTE: Remember to change this before this game goes live
|
||||
required_client_version = (0, 2, 4)
|
||||
|
||||
|
|
|
@ -92,8 +92,6 @@ class MinecraftWorld(World):
|
|||
item_name_to_id = Constants.item_name_to_id
|
||||
location_name_to_id = Constants.location_name_to_id
|
||||
|
||||
data_version = 7
|
||||
|
||||
def _get_mc_data(self) -> Dict[str, Any]:
|
||||
exits = [connection[0] for connection in Constants.region_info["default_connections"]]
|
||||
return {
|
||||
|
|
|
@ -57,8 +57,6 @@ class MMBN3World(World):
|
|||
settings: typing.ClassVar[MMBN3Settings]
|
||||
topology_present = False
|
||||
|
||||
data_version = 1
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
|
||||
|
||||
|
|
|
@ -34,14 +34,13 @@ class NoitaWorld(World):
|
|||
|
||||
item_name_groups = items.item_name_groups
|
||||
location_name_groups = locations.location_name_groups
|
||||
data_version = 2
|
||||
|
||||
web = NoitaWeb()
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if not self.multiworld.get_player_name(self.player).isascii():
|
||||
raise Exception("Noita yaml's slot name has invalid character(s).")
|
||||
|
||||
|
||||
# Returned items will be sent over to the client
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return self.options.as_dict("death_link", "victory_condition", "path_option", "hidden_chests",
|
||||
|
|
|
@ -150,8 +150,6 @@ class OOTWorld(World):
|
|||
location_name_to_id = location_name_to_id
|
||||
web = OOTWeb()
|
||||
|
||||
data_version = 3
|
||||
|
||||
required_client_version = (0, 4, 0)
|
||||
|
||||
item_name_groups = {
|
||||
|
|
|
@ -48,7 +48,6 @@ class Overcooked2World(World):
|
|||
web = Overcooked2Web()
|
||||
required_client_version = (0, 3, 8)
|
||||
topology_present: bool = False
|
||||
data_version = 3
|
||||
|
||||
item_name_to_id = item_name_to_id
|
||||
item_id_to_name = item_id_to_name
|
||||
|
|
|
@ -87,7 +87,6 @@ class PokemonEmeraldWorld(World):
|
|||
item_name_groups = ITEM_GROUPS
|
||||
location_name_groups = LOCATION_GROUPS
|
||||
|
||||
data_version = 2
|
||||
required_client_version = (0, 4, 6)
|
||||
|
||||
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]]
|
||||
|
|
|
@ -74,7 +74,6 @@ class PokemonRedBlueWorld(World):
|
|||
option_definitions = pokemon_rb_options
|
||||
settings: typing.ClassVar[PokemonSettings]
|
||||
|
||||
data_version = 9
|
||||
required_client_version = (0, 4, 2)
|
||||
|
||||
topology_present = True
|
||||
|
|
|
@ -39,7 +39,6 @@ class RaftWorld(World):
|
|||
location_name_to_id = locations_lookup_name_to_id
|
||||
option_definitions = raft_options
|
||||
|
||||
data_version = 2
|
||||
required_client_version = (0, 3, 4)
|
||||
|
||||
def create_items(self):
|
||||
|
|
|
@ -35,7 +35,6 @@ class RLWorld(World):
|
|||
game = "Rogue Legacy"
|
||||
option_definitions = rl_options
|
||||
topology_present = True
|
||||
data_version = 4
|
||||
required_client_version = (0, 3, 5)
|
||||
web = RLWeb()
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ class RiskOfRainWorld(World):
|
|||
}
|
||||
location_name_to_id = item_pickups
|
||||
|
||||
data_version = 9
|
||||
required_client_version = (0, 4, 5)
|
||||
web = RiskOfWeb()
|
||||
total_revivals: int
|
||||
|
|
|
@ -58,7 +58,6 @@ class SA2BWorld(World):
|
|||
options_dataclass = SA2BOptions
|
||||
options: SA2BOptions
|
||||
topology_present = False
|
||||
data_version = 7
|
||||
|
||||
item_name_groups = item_groups
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
|
|
|
@ -244,10 +244,10 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
|||
self.formatted_print(f" [u]{faction.name}[/u] ")
|
||||
|
||||
for item_id in categorized_items[faction]:
|
||||
item_name = self.ctx.item_names[item_id]
|
||||
item_name = self.ctx.item_names.lookup_in_slot(item_id)
|
||||
received_child_items = items_received_set.intersection(parent_to_child.get(item_id, []))
|
||||
matching_children = [child for child in received_child_items
|
||||
if item_matches_filter(self.ctx.item_names[child])]
|
||||
if item_matches_filter(self.ctx.item_names.lookup_in_slot(child))]
|
||||
received_items_of_this_type = items_received.get(item_id, [])
|
||||
item_is_match = item_matches_filter(item_name)
|
||||
if item_is_match or len(matching_children) > 0:
|
||||
|
@ -1165,7 +1165,7 @@ def request_unfinished_missions(ctx: SC2Context) -> None:
|
|||
objectives = set(ctx.locations_for_mission(mission))
|
||||
if objectives:
|
||||
remaining_objectives = objectives.difference(ctx.checked_locations)
|
||||
unfinished_locations[mission] = [ctx.location_names[location_id] for location_id in remaining_objectives]
|
||||
unfinished_locations[mission] = [ctx.location_names.lookup_in_slot(location_id) for location_id in remaining_objectives]
|
||||
else:
|
||||
unfinished_locations[mission] = []
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ class SC2Manager(GameManager):
|
|||
for loc in self.ctx.locations_for_mission(mission_name):
|
||||
if loc in self.ctx.missing_locations:
|
||||
count += 1
|
||||
locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names[loc])
|
||||
locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_slot(loc))
|
||||
|
||||
plando_locations = []
|
||||
for plando_loc in self.ctx.plando_locations:
|
||||
|
|
|
@ -28,7 +28,6 @@ class ShortHikeWorld(World):
|
|||
|
||||
game = "A Short Hike"
|
||||
web = ShortHikeWeb()
|
||||
data_version = 2
|
||||
|
||||
item_name_to_id = {item["name"]: item["id"] for item in item_table}
|
||||
location_name_to_id = {loc["name"]: loc["id"] for loc in location_table}
|
||||
|
|
|
@ -123,7 +123,7 @@ class SMSNIClient(SNIClient):
|
|||
location_id = locations_start_id + item_index
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names[location_id]
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
|
@ -151,9 +151,8 @@ class SMSNIClient(SNIClient):
|
|||
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
||||
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], item_out_ptr, len(ctx.items_received)))
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
|
|
|
@ -99,7 +99,6 @@ class SMWorld(World):
|
|||
|
||||
game: str = "Super Metroid"
|
||||
topology_present = True
|
||||
data_version = 3
|
||||
option_definitions = sm_options
|
||||
settings: typing.ClassVar[SMSettings]
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ class SM64World(World):
|
|||
item_name_to_id = item_table
|
||||
location_name_to_id = location_table
|
||||
|
||||
data_version = 9
|
||||
required_client_version = (0, 3, 5)
|
||||
|
||||
area_connections: typing.Dict[int, int]
|
||||
|
|
|
@ -448,7 +448,7 @@ class SMWSNIClient(SNIClient):
|
|||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names[new_check_id]
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
|
@ -499,15 +499,16 @@ class SMWSNIClient(SNIClient):
|
|||
if recv_index < len(ctx.items_received):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
sending_game = ctx.slot_info[item.player].game
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
if self.should_show_message(ctx, item):
|
||||
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
||||
# Don't send messages for Boss Tokens
|
||||
item_name = ctx.item_names[item.item]
|
||||
item_name = ctx.item_names.lookup_in_slot(item.item)
|
||||
player_name = ctx.player_names[item.player]
|
||||
|
||||
receive_message = generate_received_text(item_name, player_name)
|
||||
|
@ -515,7 +516,7 @@ class SMWSNIClient(SNIClient):
|
|||
|
||||
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
|
||||
if item.item in trap_rom_data:
|
||||
item_name = ctx.item_names[item.item]
|
||||
item_name = ctx.item_names.lookup_in_slot(item.item)
|
||||
player_name = ctx.player_names[item.player]
|
||||
|
||||
receive_message = generate_received_text(item_name, player_name)
|
||||
|
@ -596,7 +597,7 @@ class SMWSNIClient(SNIClient):
|
|||
for loc_id in ctx.checked_locations:
|
||||
if loc_id not in ctx.locations_checked:
|
||||
ctx.locations_checked.add(loc_id)
|
||||
loc_name = ctx.location_names[loc_id]
|
||||
loc_name = ctx.location_names.lookup_in_slot(loc_id)
|
||||
|
||||
if loc_name not in location_id_to_level_id:
|
||||
continue
|
||||
|
|
|
@ -109,7 +109,7 @@ class SMZ3SNIClient(SNIClient):
|
|||
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names[location_id]
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
|
||||
|
@ -132,8 +132,7 @@ class SMZ3SNIClient(SNIClient):
|
|||
item_out_ptr += 1
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + recv_progress_addr_table_offset, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], item_out_ptr, len(ctx.items_received)))
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
|
|
|
@ -68,7 +68,6 @@ class SMZ3World(World):
|
|||
"""
|
||||
game: str = "SMZ3"
|
||||
topology_present = False
|
||||
data_version = 3
|
||||
option_definitions = smz3_options
|
||||
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
|
||||
location_names: Set[str]
|
||||
|
|
|
@ -176,7 +176,6 @@ class SoEWorld(World):
|
|||
options: SoEOptions
|
||||
settings: typing.ClassVar[SoESettings]
|
||||
topology_present = False
|
||||
data_version = 5
|
||||
web = SoEWebWorld()
|
||||
required_client_version = (0, 4, 4)
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ class SpireWorld(World):
|
|||
option_definitions = spire_options
|
||||
game = "Slay the Spire"
|
||||
topology_present = False
|
||||
data_version = 2
|
||||
web = SpireWeb()
|
||||
required_client_version = (0, 3, 7)
|
||||
|
||||
|
|
|
@ -73,7 +73,6 @@ class StardewValleyWorld(World):
|
|||
[location.name for location in locations] for group, locations in locations_by_tag.items()
|
||||
}
|
||||
|
||||
data_version = 3
|
||||
required_client_version = (0, 4, 0)
|
||||
|
||||
options_dataclass = StardewValleyOptions
|
||||
|
|
|
@ -44,7 +44,6 @@ class SubnauticaWorld(World):
|
|||
location_name_to_id = all_locations
|
||||
options_dataclass = options.SubnauticaOptions
|
||||
options: options.SubnauticaOptions
|
||||
data_version = 10
|
||||
required_client_version = (0, 4, 1)
|
||||
|
||||
creatures_to_scan: List[str]
|
||||
|
|
|
@ -52,11 +52,6 @@ class TerrariaWorld(World):
|
|||
options_dataclass = TerrariaOptions
|
||||
options: TerrariaOptions
|
||||
|
||||
# data_version is used to signal that items, locations or their names
|
||||
# changed. Set this to 0 during development so other games' clients do not
|
||||
# cache any texts, then increase by 1 for each release that makes changes.
|
||||
data_version = 2
|
||||
|
||||
item_name_to_id = item_name_to_id
|
||||
location_name_to_id = location_name_to_id
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ class TimespinnerWorld(World):
|
|||
option_definitions = timespinner_options
|
||||
game = "Timespinner"
|
||||
topology_present = True
|
||||
data_version = 12
|
||||
web = TimespinnerWebWorld()
|
||||
required_client_version = (0, 4, 2)
|
||||
|
||||
|
@ -228,7 +227,7 @@ class TimespinnerWorld(World):
|
|||
non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value
|
||||
local_items: Set[str] = self.multiworld.local_items[self.player].value
|
||||
|
||||
local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if
|
||||
local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if
|
||||
item in local_items or not item in non_local_items)
|
||||
if not local_starter_melee_weapons:
|
||||
if 'Plasma Orb' in non_local_items:
|
||||
|
|
|
@ -68,7 +68,6 @@ class TLoZWorld(World):
|
|||
settings: typing.ClassVar[TLoZSettings]
|
||||
game = "The Legend of Zelda"
|
||||
topology_present = False
|
||||
data_version = 1
|
||||
base_id = 7000
|
||||
web = TLoZWeb()
|
||||
|
||||
|
|
|
@ -52,8 +52,6 @@ class UndertaleWorld(World):
|
|||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
|
||||
|
||||
data_version = 7
|
||||
|
||||
def _get_undertale_data(self):
|
||||
return {
|
||||
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
|
||||
|
|
|
@ -34,8 +34,6 @@ class V6World(World):
|
|||
item_name_to_id = item_table
|
||||
location_name_to_id = location_table
|
||||
|
||||
data_version = 1
|
||||
|
||||
area_connections: typing.Dict[int, int]
|
||||
area_cost_map: typing.Dict[int,int]
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ class YoshisIslandSNIClient(SNIClient):
|
|||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names[new_check_id]
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
total_locations = len(ctx.missing_locations) + len(ctx.checked_locations)
|
||||
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
||||
|
@ -127,9 +127,9 @@ class YoshisIslandSNIClient(SNIClient):
|
|||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info("Received %s from %s (%s) (%d/%d in list)" % (
|
||||
color(ctx.item_names[item.item], "red", "bold"),
|
||||
color(ctx.item_names.lookup_in_slot(item.item), "red", "bold"),
|
||||
color(ctx.player_names[item.player], "yellow"),
|
||||
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
snes_buffered_write(ctx, ITEMQUEUE_HIGH, pack("H", recv_index))
|
||||
if item.item in item_values:
|
||||
|
|
|
@ -86,11 +86,6 @@ class ZillionWorld(World):
|
|||
item_name_to_id = _item_name_to_id
|
||||
location_name_to_id = _loc_name_to_id
|
||||
|
||||
# increment this every time something in your world's names/id mappings changes.
|
||||
# While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be
|
||||
# retrieved by clients on every connection.
|
||||
data_version = 1
|
||||
|
||||
logger: logging.Logger
|
||||
|
||||
class LogStreamInterface:
|
||||
|
|
Loading…
Reference in New Issue