Server: add slot_info key to Connected
This commit is contained in:
parent
ddd3073132
commit
0bd252e7f5
|
@ -12,6 +12,7 @@ import random
|
|||
|
||||
import Options
|
||||
import Utils
|
||||
import NetUtils
|
||||
|
||||
|
||||
class MultiWorld():
|
||||
|
@ -39,6 +40,7 @@ class MultiWorld():
|
|||
def __init__(self, players: int):
|
||||
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
||||
self.players = players
|
||||
self.player_types = {player: NetUtils.SlotType.player for player in self.player_ids}
|
||||
self.glitch_triforce = False
|
||||
self.algorithm = 'balanced'
|
||||
self.dungeons: Dict[Tuple[str, int], Dungeon] = {}
|
||||
|
|
12
Main.py
12
Main.py
|
@ -9,7 +9,7 @@ import tempfile
|
|||
import zipfile
|
||||
from typing import Dict, Tuple, Optional
|
||||
|
||||
from BaseClasses import Item, MultiWorld, CollectionState, Region, RegionType
|
||||
from BaseClasses import MultiWorld, CollectionState, Region, RegionType
|
||||
from worlds.alttp.Items import item_name_groups
|
||||
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
|
||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||
|
@ -250,11 +250,14 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
import NetUtils
|
||||
slot_data = {}
|
||||
client_versions = {}
|
||||
minimum_versions = {"server": (0, 1, 8), "clients": client_versions}
|
||||
games = {}
|
||||
minimum_versions = {"server": (0, 2, 4), "clients": client_versions}
|
||||
slot_info = {}
|
||||
names = [[name for player, name in sorted(world.player_name.items())]]
|
||||
for slot in world.player_ids:
|
||||
client_versions[slot] = world.worlds[slot].get_required_client_version()
|
||||
games[slot] = world.game[slot]
|
||||
slot_info[slot] = NetUtils.NetworkSlot(names[0][slot+1], world.game[slot], world.player_types[slot])
|
||||
precollected_items = {player: [item.code for item in world_precollected]
|
||||
for player, world_precollected in world.precollected_items.items()}
|
||||
precollected_hints = {player: set() for player in range(1, world.players + 1)}
|
||||
|
@ -288,8 +291,9 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
|
||||
multidata = {
|
||||
"slot_data": slot_data,
|
||||
"games": games,
|
||||
"names": [[name for player, name in sorted(world.player_name.items())]],
|
||||
"slot_info": slot_info,
|
||||
"names": names, # TODO: remove around 0.2.5 in favor of slot_info
|
||||
"games": games, # TODO: remove around 0.2.5 in favor of slot_info
|
||||
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
|
||||
"remote_items": {player for player in world.player_ids if
|
||||
world.worlds[player].remote_items},
|
||||
|
|
|
@ -33,7 +33,8 @@ from worlds import network_data_package, lookup_any_item_id_to_name, lookup_any_
|
|||
import Utils
|
||||
from Utils import get_item_name_from_id, get_location_name_from_id, \
|
||||
version_tuple, restricted_loads, Version
|
||||
from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission
|
||||
from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission, NetworkSlot, \
|
||||
SlotType
|
||||
|
||||
colorama.init()
|
||||
|
||||
|
@ -104,6 +105,7 @@ class Context:
|
|||
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
|
||||
log_network: bool = False):
|
||||
super(Context, self).__init__()
|
||||
self.slot_info: typing.Dict[int, NetworkSlot] = {}
|
||||
self.log_network = log_network
|
||||
self.endpoints = []
|
||||
self.clients = {}
|
||||
|
@ -292,18 +294,35 @@ class Context:
|
|||
self.slot_data = decoded_obj['slot_data']
|
||||
self.er_hint_data = {int(player): {int(address): name for address, name in loc_data.items()}
|
||||
for player, loc_data in decoded_obj["er_hint_data"].items()}
|
||||
self.games = decoded_obj["games"]
|
||||
|
||||
# load start inventory:
|
||||
for slot, item_codes in decoded_obj["precollected_items"].items():
|
||||
self.start_inventory[slot] = [NetworkItem(item_code, -2, 0) for item_code in item_codes]
|
||||
|
||||
for team in range(len(decoded_obj['names'])):
|
||||
for slot, hints in decoded_obj["precollected_hints"].items():
|
||||
self.hints[team, slot].update(hints)
|
||||
# declare slots without checks as done, as they're assumed to be spectators
|
||||
for slot, locations in self.locations.items():
|
||||
if not locations:
|
||||
if "slot_info" in decoded_obj:
|
||||
self.slot_info = decoded_obj["slot_info"]
|
||||
self.games = {slot: slot_info.game for slot, slot_info in self.slot_info.items()}
|
||||
|
||||
else:
|
||||
self.games = decoded_obj["games"]
|
||||
|
||||
self.slot_info = {
|
||||
slot: NetworkSlot(
|
||||
self.player_names[0, slot],
|
||||
self.games[slot],
|
||||
SlotType(int(bool(locations))))
|
||||
for slot, locations in self.locations.items()
|
||||
}
|
||||
|
||||
# declare slots that aren't players as done
|
||||
for slot, slot_info in self.slot_info.items():
|
||||
if slot_info.type.always_goal:
|
||||
for team in self.clients:
|
||||
self.client_game_state[team, slot] = ClientStatus.CLIENT_GOAL
|
||||
|
||||
if use_embedded_server_options:
|
||||
server_options = decoded_obj.get("server_options", {})
|
||||
self._set_options(server_options)
|
||||
|
@ -541,6 +560,8 @@ async def on_client_connected(ctx: Context, client: Client):
|
|||
'cmd': 'RoomInfo',
|
||||
'password': bool(ctx.password),
|
||||
'players': players,
|
||||
# TODO remove around 0.2.5 in favor of slot_info ?
|
||||
# Maybe convert into a list of games that are present to fetch relevant datapackage entries before Connect?
|
||||
'games': [ctx.games[x] for x in range(1, len(ctx.games) + 1)],
|
||||
# tags are for additional features in the communication.
|
||||
# Name them by feature or fork, as you feel is appropriate.
|
||||
|
@ -579,7 +600,7 @@ async def on_client_joined(ctx: Context, client: Client):
|
|||
f"{verb} {ctx.games[client.slot]} has joined. "
|
||||
f"Client({version_str}), {client.tags}).")
|
||||
ctx.notify_client(client, "Now that you are connected, "
|
||||
"you can use !help to list commands to run via the server."
|
||||
"you can use !help to list commands to run via the server. "
|
||||
"If your client supports it, "
|
||||
"you may have additional local commands you can list with /help.")
|
||||
ctx.client_connection_timers[client.team, client.slot] = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
@ -705,12 +726,7 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
|
|||
if count_activity:
|
||||
ctx.client_activity_timers[team, slot] = datetime.datetime.now(datetime.timezone.utc)
|
||||
for location in new_locations:
|
||||
if len(ctx.locations[slot][location]) == 3:
|
||||
item_id, target_player, flags = ctx.locations[slot][location]
|
||||
else:
|
||||
# TODO: remove around version 0.2.5
|
||||
item_id, target_player = ctx.locations[slot][location]
|
||||
flags = 0
|
||||
item_id, target_player, flags = ctx.locations[slot][location]
|
||||
|
||||
new_item = NetworkItem(item_id, location, slot, flags)
|
||||
if target_player != slot:
|
||||
|
@ -739,12 +755,7 @@ def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[
|
|||
seeked_item_id = proxy_worlds[ctx.games[slot]].item_name_to_id[item]
|
||||
for finding_player, check_data in ctx.locations.items():
|
||||
for location_id, result in check_data.items():
|
||||
if len(result) == 3:
|
||||
item_id, receiving_player, item_flags = result
|
||||
else:
|
||||
# TODO: remove around version 0.2.5
|
||||
item_id, receiving_player = result
|
||||
item_flags = 0
|
||||
item_id, receiving_player, item_flags = result
|
||||
|
||||
if receiving_player == slot and item_id == seeked_item_id:
|
||||
found = location_id in ctx.location_checks[team, finding_player]
|
||||
|
@ -759,12 +770,7 @@ def collect_hints_location(ctx: Context, team: int, slot: int, location: str) ->
|
|||
seeked_location: int = proxy_worlds[ctx.games[slot]].location_name_to_id[location]
|
||||
result = ctx.locations[slot].get(seeked_location, (None, None, None))
|
||||
if result:
|
||||
if len(result) == 3:
|
||||
item_id, receiving_player, item_flags = result
|
||||
else:
|
||||
# TODO: remove around version 0.2.5
|
||||
item_id, receiving_player = result
|
||||
item_flags = 0
|
||||
item_id, receiving_player, item_flags = result
|
||||
|
||||
found = seeked_location in ctx.location_checks[team, slot]
|
||||
entrance = ctx.er_hint_data.get(slot, {}).get(seeked_location, "")
|
||||
|
@ -1347,7 +1353,8 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
|||
"players": ctx.get_players_package(),
|
||||
"missing_locations": get_missing_checks(ctx, team, slot),
|
||||
"checked_locations": get_checked_checks(ctx, team, slot),
|
||||
"slot_data": ctx.slot_data[client.slot]
|
||||
"slot_data": ctx.slot_data[client.slot],
|
||||
"slot_info": ctx.slot_info
|
||||
}]
|
||||
start_inventory = get_start_inventory(ctx, team, slot, client.remote_start_inventory)
|
||||
items = get_received_items(ctx, client.team, client.slot, client.remote_items)
|
||||
|
@ -1431,13 +1438,8 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
|||
[{'cmd': 'InvalidPacket', "type": "arguments", "text": 'LocationScouts',
|
||||
"original_cmd": cmd}])
|
||||
return
|
||||
if len(ctx.locations[client.slot][location]) == 3:
|
||||
target_item, target_player, flags = ctx.locations[client.slot][location]
|
||||
else:
|
||||
# TODO: remove around version 0.2.5
|
||||
target_item, target_player = ctx.locations[client.slot][location]
|
||||
flags = 0
|
||||
|
||||
target_item, target_player, flags = ctx.locations[client.slot][location]
|
||||
locs.append(NetworkItem(target_item, location, target_player, flags))
|
||||
|
||||
await ctx.send_msgs(client, [{'cmd': 'LocationInfo', 'locations': locs}])
|
||||
|
|
25
NetUtils.py
25
NetUtils.py
|
@ -1,6 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import typing
|
||||
import enum
|
||||
from json import JSONEncoder, JSONDecoder
|
||||
|
@ -29,7 +28,18 @@ class ClientStatus(enum.IntEnum):
|
|||
CLIENT_GOAL = 30
|
||||
|
||||
|
||||
class Permission(enum.IntEnum):
|
||||
class SlotType(enum.IntFlag):
|
||||
spectator = 0b00
|
||||
player = 0b01
|
||||
group = 0b10
|
||||
|
||||
@property
|
||||
def always_goal(self) -> bool:
|
||||
"""Mark this slot has having reached its goal instantly."""
|
||||
return self.value != 0b01
|
||||
|
||||
|
||||
class Permission(enum.IntFlag):
|
||||
disabled = 0b000 # 0, completely disables access
|
||||
enabled = 0b001 # 1, allows manual use
|
||||
goal = 0b010 # 2, allows manual use after goal completion
|
||||
|
@ -49,12 +59,20 @@ class Permission(enum.IntEnum):
|
|||
|
||||
|
||||
class NetworkPlayer(typing.NamedTuple):
|
||||
"""Represents a particular player on a particular team."""
|
||||
team: int
|
||||
slot: int
|
||||
alias: str
|
||||
name: str
|
||||
|
||||
|
||||
class NetworkSlot(typing.NamedTuple):
|
||||
"""Represents a particular slot across teams."""
|
||||
name: str
|
||||
game: str
|
||||
type: SlotType
|
||||
|
||||
|
||||
class NetworkItem(typing.NamedTuple):
|
||||
item: int
|
||||
location: int
|
||||
|
@ -122,9 +140,6 @@ class Endpoint:
|
|||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
|
||||
async def disconnect(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class HandlerMeta(type):
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
|
|
2
Utils.py
2
Utils.py
|
@ -349,7 +349,7 @@ class RestrictedUnpickler(pickle.Unpickler):
|
|||
if module == "builtins" and name in safe_builtins:
|
||||
return getattr(builtins, name)
|
||||
# used by MultiServer -> savegame/multidata
|
||||
if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint"}:
|
||||
if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot"}:
|
||||
return getattr(self.net_utils_module, name)
|
||||
# Options and Plando are unpickled by WebHost -> Generate
|
||||
if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
|
||||
|
|
|
@ -18,6 +18,9 @@ class GenericWorld(World):
|
|||
}
|
||||
hidden = True
|
||||
|
||||
def generate_early(self):
|
||||
self.world.player_types[self.player] = 0 # mark as spectator
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
if name == "Nothing":
|
||||
return Item(name, False, -1, self.player)
|
||||
|
|
Loading…
Reference in New Issue