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 Options
|
||||||
import Utils
|
import Utils
|
||||||
|
import NetUtils
|
||||||
|
|
||||||
|
|
||||||
class MultiWorld():
|
class MultiWorld():
|
||||||
|
@ -39,6 +40,7 @@ class MultiWorld():
|
||||||
def __init__(self, players: int):
|
def __init__(self, players: int):
|
||||||
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
||||||
self.players = players
|
self.players = players
|
||||||
|
self.player_types = {player: NetUtils.SlotType.player for player in self.player_ids}
|
||||||
self.glitch_triforce = False
|
self.glitch_triforce = False
|
||||||
self.algorithm = 'balanced'
|
self.algorithm = 'balanced'
|
||||||
self.dungeons: Dict[Tuple[str, int], Dungeon] = {}
|
self.dungeons: Dict[Tuple[str, int], Dungeon] = {}
|
||||||
|
|
12
Main.py
12
Main.py
|
@ -9,7 +9,7 @@ import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import Dict, Tuple, Optional
|
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.Items import item_name_groups
|
||||||
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
|
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
|
||||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
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
|
import NetUtils
|
||||||
slot_data = {}
|
slot_data = {}
|
||||||
client_versions = {}
|
client_versions = {}
|
||||||
minimum_versions = {"server": (0, 1, 8), "clients": client_versions}
|
|
||||||
games = {}
|
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:
|
for slot in world.player_ids:
|
||||||
client_versions[slot] = world.worlds[slot].get_required_client_version()
|
client_versions[slot] = world.worlds[slot].get_required_client_version()
|
||||||
games[slot] = world.game[slot]
|
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]
|
precollected_items = {player: [item.code for item in world_precollected]
|
||||||
for player, world_precollected in world.precollected_items.items()}
|
for player, world_precollected in world.precollected_items.items()}
|
||||||
precollected_hints = {player: set() for player in range(1, world.players + 1)}
|
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 = {
|
multidata = {
|
||||||
"slot_data": slot_data,
|
"slot_data": slot_data,
|
||||||
"games": games,
|
"slot_info": slot_info,
|
||||||
"names": [[name for player, name in sorted(world.player_name.items())]],
|
"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()},
|
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
|
||||||
"remote_items": {player for player in world.player_ids if
|
"remote_items": {player for player in world.player_ids if
|
||||||
world.worlds[player].remote_items},
|
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
|
import Utils
|
||||||
from Utils import get_item_name_from_id, get_location_name_from_id, \
|
from Utils import get_item_name_from_id, get_location_name_from_id, \
|
||||||
version_tuple, restricted_loads, Version
|
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()
|
colorama.init()
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@ class Context:
|
||||||
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
|
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
|
||||||
log_network: bool = False):
|
log_network: bool = False):
|
||||||
super(Context, self).__init__()
|
super(Context, self).__init__()
|
||||||
|
self.slot_info: typing.Dict[int, NetworkSlot] = {}
|
||||||
self.log_network = log_network
|
self.log_network = log_network
|
||||||
self.endpoints = []
|
self.endpoints = []
|
||||||
self.clients = {}
|
self.clients = {}
|
||||||
|
@ -292,18 +294,35 @@ class Context:
|
||||||
self.slot_data = decoded_obj['slot_data']
|
self.slot_data = decoded_obj['slot_data']
|
||||||
self.er_hint_data = {int(player): {int(address): name for address, name in loc_data.items()}
|
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()}
|
for player, loc_data in decoded_obj["er_hint_data"].items()}
|
||||||
self.games = decoded_obj["games"]
|
|
||||||
# load start inventory:
|
# load start inventory:
|
||||||
for slot, item_codes in decoded_obj["precollected_items"].items():
|
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]
|
self.start_inventory[slot] = [NetworkItem(item_code, -2, 0) for item_code in item_codes]
|
||||||
|
|
||||||
for team in range(len(decoded_obj['names'])):
|
for team in range(len(decoded_obj['names'])):
|
||||||
for slot, hints in decoded_obj["precollected_hints"].items():
|
for slot, hints in decoded_obj["precollected_hints"].items():
|
||||||
self.hints[team, slot].update(hints)
|
self.hints[team, slot].update(hints)
|
||||||
# declare slots without checks as done, as they're assumed to be spectators
|
if "slot_info" in decoded_obj:
|
||||||
for slot, locations in self.locations.items():
|
self.slot_info = decoded_obj["slot_info"]
|
||||||
if not locations:
|
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:
|
for team in self.clients:
|
||||||
self.client_game_state[team, slot] = ClientStatus.CLIENT_GOAL
|
self.client_game_state[team, slot] = ClientStatus.CLIENT_GOAL
|
||||||
|
|
||||||
if use_embedded_server_options:
|
if use_embedded_server_options:
|
||||||
server_options = decoded_obj.get("server_options", {})
|
server_options = decoded_obj.get("server_options", {})
|
||||||
self._set_options(server_options)
|
self._set_options(server_options)
|
||||||
|
@ -541,6 +560,8 @@ async def on_client_connected(ctx: Context, client: Client):
|
||||||
'cmd': 'RoomInfo',
|
'cmd': 'RoomInfo',
|
||||||
'password': bool(ctx.password),
|
'password': bool(ctx.password),
|
||||||
'players': players,
|
'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)],
|
'games': [ctx.games[x] for x in range(1, len(ctx.games) + 1)],
|
||||||
# tags are for additional features in the communication.
|
# tags are for additional features in the communication.
|
||||||
# Name them by feature or fork, as you feel is appropriate.
|
# 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"{verb} {ctx.games[client.slot]} has joined. "
|
||||||
f"Client({version_str}), {client.tags}).")
|
f"Client({version_str}), {client.tags}).")
|
||||||
ctx.notify_client(client, "Now that you are connected, "
|
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, "
|
"If your client supports it, "
|
||||||
"you may have additional local commands you can list with /help.")
|
"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)
|
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:
|
if count_activity:
|
||||||
ctx.client_activity_timers[team, slot] = datetime.datetime.now(datetime.timezone.utc)
|
ctx.client_activity_timers[team, slot] = datetime.datetime.now(datetime.timezone.utc)
|
||||||
for location in new_locations:
|
for location in new_locations:
|
||||||
if len(ctx.locations[slot][location]) == 3:
|
item_id, target_player, flags = ctx.locations[slot][location]
|
||||||
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
|
|
||||||
|
|
||||||
new_item = NetworkItem(item_id, location, slot, flags)
|
new_item = NetworkItem(item_id, location, slot, flags)
|
||||||
if target_player != slot:
|
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]
|
seeked_item_id = proxy_worlds[ctx.games[slot]].item_name_to_id[item]
|
||||||
for finding_player, check_data in ctx.locations.items():
|
for finding_player, check_data in ctx.locations.items():
|
||||||
for location_id, result in check_data.items():
|
for location_id, result in check_data.items():
|
||||||
if len(result) == 3:
|
item_id, receiving_player, item_flags = result
|
||||||
item_id, receiving_player, item_flags = result
|
|
||||||
else:
|
|
||||||
# TODO: remove around version 0.2.5
|
|
||||||
item_id, receiving_player = result
|
|
||||||
item_flags = 0
|
|
||||||
|
|
||||||
if receiving_player == slot and item_id == seeked_item_id:
|
if receiving_player == slot and item_id == seeked_item_id:
|
||||||
found = location_id in ctx.location_checks[team, finding_player]
|
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]
|
seeked_location: int = proxy_worlds[ctx.games[slot]].location_name_to_id[location]
|
||||||
result = ctx.locations[slot].get(seeked_location, (None, None, None))
|
result = ctx.locations[slot].get(seeked_location, (None, None, None))
|
||||||
if result:
|
if result:
|
||||||
if len(result) == 3:
|
item_id, receiving_player, item_flags = result
|
||||||
item_id, receiving_player, item_flags = result
|
|
||||||
else:
|
|
||||||
# TODO: remove around version 0.2.5
|
|
||||||
item_id, receiving_player = result
|
|
||||||
item_flags = 0
|
|
||||||
|
|
||||||
found = seeked_location in ctx.location_checks[team, slot]
|
found = seeked_location in ctx.location_checks[team, slot]
|
||||||
entrance = ctx.er_hint_data.get(slot, {}).get(seeked_location, "")
|
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(),
|
"players": ctx.get_players_package(),
|
||||||
"missing_locations": get_missing_checks(ctx, team, slot),
|
"missing_locations": get_missing_checks(ctx, team, slot),
|
||||||
"checked_locations": get_checked_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)
|
start_inventory = get_start_inventory(ctx, team, slot, client.remote_start_inventory)
|
||||||
items = get_received_items(ctx, client.team, client.slot, client.remote_items)
|
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',
|
[{'cmd': 'InvalidPacket', "type": "arguments", "text": 'LocationScouts',
|
||||||
"original_cmd": cmd}])
|
"original_cmd": cmd}])
|
||||||
return
|
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))
|
locs.append(NetworkItem(target_item, location, target_player, flags))
|
||||||
|
|
||||||
await ctx.send_msgs(client, [{'cmd': 'LocationInfo', 'locations': locs}])
|
await ctx.send_msgs(client, [{'cmd': 'LocationInfo', 'locations': locs}])
|
||||||
|
|
25
NetUtils.py
25
NetUtils.py
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
import typing
|
import typing
|
||||||
import enum
|
import enum
|
||||||
from json import JSONEncoder, JSONDecoder
|
from json import JSONEncoder, JSONDecoder
|
||||||
|
@ -29,7 +28,18 @@ class ClientStatus(enum.IntEnum):
|
||||||
CLIENT_GOAL = 30
|
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
|
disabled = 0b000 # 0, completely disables access
|
||||||
enabled = 0b001 # 1, allows manual use
|
enabled = 0b001 # 1, allows manual use
|
||||||
goal = 0b010 # 2, allows manual use after goal completion
|
goal = 0b010 # 2, allows manual use after goal completion
|
||||||
|
@ -49,12 +59,20 @@ class Permission(enum.IntEnum):
|
||||||
|
|
||||||
|
|
||||||
class NetworkPlayer(typing.NamedTuple):
|
class NetworkPlayer(typing.NamedTuple):
|
||||||
|
"""Represents a particular player on a particular team."""
|
||||||
team: int
|
team: int
|
||||||
slot: int
|
slot: int
|
||||||
alias: str
|
alias: str
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkSlot(typing.NamedTuple):
|
||||||
|
"""Represents a particular slot across teams."""
|
||||||
|
name: str
|
||||||
|
game: str
|
||||||
|
type: SlotType
|
||||||
|
|
||||||
|
|
||||||
class NetworkItem(typing.NamedTuple):
|
class NetworkItem(typing.NamedTuple):
|
||||||
item: int
|
item: int
|
||||||
location: int
|
location: int
|
||||||
|
@ -122,9 +140,6 @@ class Endpoint:
|
||||||
def __init__(self, socket):
|
def __init__(self, socket):
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
|
|
||||||
async def disconnect(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class HandlerMeta(type):
|
class HandlerMeta(type):
|
||||||
def __new__(mcs, name, bases, attrs):
|
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:
|
if module == "builtins" and name in safe_builtins:
|
||||||
return getattr(builtins, name)
|
return getattr(builtins, name)
|
||||||
# used by MultiServer -> savegame/multidata
|
# 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)
|
return getattr(self.net_utils_module, name)
|
||||||
# Options and Plando are unpickled by WebHost -> Generate
|
# Options and Plando are unpickled by WebHost -> Generate
|
||||||
if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
|
if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
|
||||||
|
|
|
@ -18,6 +18,9 @@ class GenericWorld(World):
|
||||||
}
|
}
|
||||||
hidden = True
|
hidden = True
|
||||||
|
|
||||||
|
def generate_early(self):
|
||||||
|
self.world.player_types[self.player] = 0 # mark as spectator
|
||||||
|
|
||||||
def create_item(self, name: str) -> Item:
|
def create_item(self, name: str) -> Item:
|
||||||
if name == "Nothing":
|
if name == "Nothing":
|
||||||
return Item(name, False, -1, self.player)
|
return Item(name, False, -1, self.player)
|
||||||
|
|
Loading…
Reference in New Issue