Deprecate data_version and introduce checksum for DataPackages. (#684)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
@@ -1,20 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import sys
|
||||
import pathlib
|
||||
from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Type, Union, TYPE_CHECKING, \
|
||||
ClassVar
|
||||
import sys
|
||||
from typing import Any, Callable, ClassVar, Dict, FrozenSet, List, Optional, Set, TYPE_CHECKING, TextIO, Tuple, Type, \
|
||||
Union
|
||||
|
||||
from Options import AssembleOptions
|
||||
from BaseClasses import CollectionState
|
||||
from Options import AssembleOptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from BaseClasses import MultiWorld, Item, Location, Tutorial
|
||||
from . import GamesPackage
|
||||
|
||||
|
||||
class AutoWorldRegister(type):
|
||||
world_types: Dict[str, Type[World]] = {}
|
||||
__file__: str
|
||||
zip_path: Optional[str]
|
||||
|
||||
def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister:
|
||||
if "web" in dct:
|
||||
@@ -154,9 +158,14 @@ class World(metaclass=AutoWorldRegister):
|
||||
|
||||
data_version: ClassVar[int] = 1
|
||||
"""
|
||||
increment this every time something in your world's names/id mappings changes.
|
||||
While this is set to 0, this world's DataPackage is considered in testing mode and will be inserted to the multidata
|
||||
and retrieved by clients on every connection.
|
||||
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)
|
||||
@@ -343,8 +352,35 @@ class World(metaclass=AutoWorldRegister):
|
||||
def create_filler(self) -> "Item":
|
||||
return self.create_item(self.get_filler_item_name())
|
||||
|
||||
@classmethod
|
||||
def get_data_package_data(cls) -> "GamesPackage":
|
||||
sorted_item_name_groups = {
|
||||
name: sorted(cls.item_name_groups[name]) for name in sorted(cls.item_name_groups)
|
||||
}
|
||||
sorted_location_name_groups = {
|
||||
name: sorted(cls.location_name_groups[name]) for name in sorted(cls.location_name_groups)
|
||||
}
|
||||
res: "GamesPackage" = {
|
||||
# sorted alphabetically
|
||||
"item_name_groups": sorted_item_name_groups,
|
||||
"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
|
||||
|
||||
|
||||
# any methods attached to this can be used as part of CollectionState,
|
||||
# please use a prefix as all of them get clobbered together
|
||||
class LogicMixin(metaclass=AutoLogicRegister):
|
||||
pass
|
||||
|
||||
|
||||
def data_package_checksum(data: "GamesPackage") -> str:
|
||||
"""Calculates the data package checksum for a game from a dict"""
|
||||
assert "checksum" not in data, "Checksum already in data"
|
||||
assert sorted(data) == list(data), "Data not ordered"
|
||||
from NetUtils import encode
|
||||
return hashlib.sha1(encode(data).encode()).hexdigest()
|
||||
|
||||
@@ -20,14 +20,19 @@ if typing.TYPE_CHECKING:
|
||||
from .AutoWorld import World
|
||||
|
||||
|
||||
class GamesPackage(typing.TypedDict):
|
||||
class GamesData(typing.TypedDict):
|
||||
item_name_groups: typing.Dict[str, typing.List[str]]
|
||||
item_name_to_id: typing.Dict[str, int]
|
||||
location_name_groups: typing.Dict[str, typing.List[str]]
|
||||
location_name_to_id: typing.Dict[str, int]
|
||||
version: int
|
||||
|
||||
|
||||
class GamesPackage(GamesData, total=False):
|
||||
checksum: str
|
||||
|
||||
|
||||
class DataPackage(typing.TypedDict):
|
||||
version: int
|
||||
games: typing.Dict[str, GamesPackage]
|
||||
|
||||
|
||||
@@ -75,14 +80,9 @@ games: typing.Dict[str, GamesPackage] = {}
|
||||
|
||||
from .AutoWorld import AutoWorldRegister
|
||||
|
||||
# Build the data package for each game.
|
||||
for world_name, world in AutoWorldRegister.world_types.items():
|
||||
games[world_name] = {
|
||||
"item_name_to_id": world.item_name_to_id,
|
||||
"location_name_to_id": world.location_name_to_id,
|
||||
"version": world.data_version,
|
||||
# seems clients don't actually want this. Keeping it here in case someone changes their mind.
|
||||
# "item_name_groups": {name: tuple(items) for name, items in world.item_name_groups.items()}
|
||||
}
|
||||
games[world_name] = world.get_data_package_data()
|
||||
lookup_any_item_id_to_name.update(world.item_id_to_name)
|
||||
lookup_any_location_id_to_name.update(world.location_id_to_name)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from BaseClasses import Tutorial
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from typing import Dict
|
||||
|
||||
from BaseClasses import Tutorial
|
||||
from ..AutoWorld import WebWorld, World
|
||||
|
||||
|
||||
class Bk_SudokuWebWorld(WebWorld):
|
||||
settings_page = "games/Sudoku/info/en"
|
||||
@@ -24,6 +25,7 @@ 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] = {}
|
||||
|
||||
@@ -42,6 +42,7 @@ 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
|
||||
|
||||
@@ -2,8 +2,8 @@ import random
|
||||
from typing import Dict, Any
|
||||
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
|
||||
from worlds.generic.Rules import set_rule
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from . import Items, Locations, Options, Rules, Exits
|
||||
from . import Exits, Items, Locations, Options, Rules
|
||||
from ..AutoWorld import WebWorld, World
|
||||
|
||||
|
||||
class Hylics2Web(WebWorld):
|
||||
@@ -20,13 +20,13 @@ class Hylics2Web(WebWorld):
|
||||
|
||||
class Hylics2World(World):
|
||||
"""
|
||||
Hylics 2 is a surreal and unusual RPG, with a bizarre yet unique visual style. Play as Wayne,
|
||||
Hylics 2 is a surreal and unusual RPG, with a bizarre yet unique visual style. Play as Wayne,
|
||||
travel the world, and gather your allies to defeat the nefarious Gibby in his Hylemxylem!
|
||||
"""
|
||||
game: str = "Hylics 2"
|
||||
web = Hylics2Web()
|
||||
|
||||
all_items = {**Items.item_table, **Items.gesture_item_table, **Items.party_item_table,
|
||||
all_items = {**Items.item_table, **Items.gesture_item_table, **Items.party_item_table,
|
||||
**Items.medallion_item_table}
|
||||
all_locations = {**Locations.location_table, **Locations.tv_location_table, **Locations.party_location_table,
|
||||
**Locations.medallion_location_table}
|
||||
@@ -37,7 +37,7 @@ class Hylics2World(World):
|
||||
|
||||
topology_present: bool = True
|
||||
|
||||
data_version: 1
|
||||
data_version = 1
|
||||
|
||||
start_location = "Waynehouse"
|
||||
|
||||
@@ -59,7 +59,7 @@ class Hylics2World(World):
|
||||
def create_event(self, event: str):
|
||||
return Hylics2Item(event, ItemClassification.progression_skip_balancing, None, self.player)
|
||||
|
||||
|
||||
|
||||
# set random starting location if option is enabled
|
||||
def generate_early(self):
|
||||
if self.multiworld.random_start[self.player]:
|
||||
@@ -76,7 +76,7 @@ class Hylics2World(World):
|
||||
def generate_basic(self):
|
||||
# create item pool
|
||||
pool = []
|
||||
|
||||
|
||||
# add regular items
|
||||
for i, data in Items.item_table.items():
|
||||
if data["count"] > 0:
|
||||
@@ -114,7 +114,7 @@ class Hylics2World(World):
|
||||
gestures = list(Items.gesture_item_table.items())
|
||||
tvs = list(Locations.tv_location_table.items())
|
||||
|
||||
# if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get
|
||||
# if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get
|
||||
# placed at Sage Airship: TV
|
||||
if self.multiworld.extra_items_in_logic[self.player]:
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
@@ -122,7 +122,7 @@ class Hylics2World(World):
|
||||
while tv[1]["name"] == "Sage Airship: TV":
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
self.multiworld.get_location(tv[1]["name"], self.player)\
|
||||
.place_locked_item(self.add_item(gestures[gest][1]["name"], gestures[gest][1]["classification"],
|
||||
.place_locked_item(self.add_item(gestures[gest][1]["name"], gestures[gest][1]["classification"],
|
||||
gestures[gest]))
|
||||
gestures.remove(gestures[gest])
|
||||
tvs.remove(tv)
|
||||
@@ -182,7 +182,7 @@ class Hylics2World(World):
|
||||
16: Region("Sage Airship", self.player, self.multiworld),
|
||||
17: Region("Hylemxylem", self.player, self.multiworld)
|
||||
}
|
||||
|
||||
|
||||
# create regions from table
|
||||
for i, reg in region_table.items():
|
||||
self.multiworld.regions.append(reg)
|
||||
@@ -214,7 +214,7 @@ class Hylics2World(World):
|
||||
for i, data in Locations.tv_location_table.items():
|
||||
region_table[data["region"]].locations\
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
|
||||
# add party member locations if option is enabled
|
||||
if self.multiworld.party_shuffle[self.player]:
|
||||
for i, data in Locations.party_location_table.items():
|
||||
@@ -241,4 +241,4 @@ class Hylics2Location(Location):
|
||||
|
||||
|
||||
class Hylics2Item(Item):
|
||||
game: str = "Hylics 2"
|
||||
game: str = "Hylics 2"
|
||||
|
||||
@@ -13,6 +13,7 @@ class OriBlindForest(World):
|
||||
game: str = "Ori and the Blind Forest"
|
||||
|
||||
topology_present = True
|
||||
data_version = 1
|
||||
|
||||
item_name_to_id = item_table
|
||||
location_name_to_id = lookup_name_to_id
|
||||
|
||||
Reference in New Issue
Block a user