From c9fa49d40f58d167f705a30620a96a3ec1dc53bc Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Tue, 18 Jan 2022 05:52:29 +0100 Subject: [PATCH] [Network_Item] Add item flags to network item so client can distinct some details (#210) --- Main.py | 12 +++++++---- MultiServer.py | 45 +++++++++++++++++++++++++++++----------- NetUtils.py | 24 +++++++++++++++------ docs/network protocol.md | 23 ++++++++++++++------ kvui.py | 5 ++++- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/Main.py b/Main.py index cc851ed6..02f67e7c 100644 --- a/Main.py +++ b/Main.py @@ -9,7 +9,7 @@ import tempfile import zipfile from typing import Dict, Tuple, Optional -from BaseClasses import MultiWorld, CollectionState, Region, RegionType +from BaseClasses import Item, 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 @@ -266,18 +266,22 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No if world.worlds[slot].sending_visible: sending_visible_players.add(slot) + def get_item_flags(item: Item) -> int: + return item.advancement + (item.never_exclude << 1) + (item.trap << 2) + def precollect_hint(location): hint = NetUtils.Hint(location.item.player, location.player, location.address, - location.item.code, False) + location.item.code, False, "", get_item_flags(location.item)) precollected_hints[location.player].add(hint) precollected_hints[location.item.player].add(hint) - locations_data: Dict[int, Dict[int, Tuple[int, int]]] = {player: {} for player in world.player_ids} + locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids} for location in world.get_filled_locations(): if type(location.address) == int: # item code None should be event, location.address should then also be None assert location.item.code is not None - locations_data[location.player][location.address] = location.item.code, location.item.player + locations_data[location.player][location.address] = \ + location.item.code, location.item.player, get_item_flags(location.item) if location.player in sending_visible_players: precollect_hint(location) elif location.name in world.start_location_hints[location.player]: diff --git a/MultiServer.py b/MultiServer.py index e431f8b5..9f166d5b 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -78,7 +78,7 @@ class Context: "compatibility": int} # team -> slot id -> list of clients authenticated to slot. clients: typing.Dict[int, typing.Dict[int, typing.List[Client]]] - locations: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int]]] + locations: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int]]] def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int, hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", collect_mode="disabled", @@ -662,8 +662,13 @@ 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: - item_id, target_player = ctx.locations[slot][location] - new_item = NetworkItem(item_id, location, slot) + if len(ctx.locations[slot][location]) == 3: + item_id, target_player, flags = ctx.locations[slot][location] + else: + item_id, target_player = ctx.locations[slot][location] + flags = 0 + + new_item = NetworkItem(item_id, location, slot, flags) if target_player != slot or slot in ctx.remote_items: get_received_items(ctx, team, target_player).append(new_item) @@ -694,22 +699,33 @@ 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(): - item_id, receiving_player = result + if len(result) == 3: + item_id, receiving_player, item_flags = result + else: + item_id, receiving_player = result + item_flags = 0 + if receiving_player == slot and item_id == seeked_item_id: found = location_id in ctx.location_checks[team, finding_player] entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "") - hints.append(NetUtils.Hint(receiving_player, finding_player, location_id, item_id, found, entrance)) + hints.append(NetUtils.Hint(receiving_player, finding_player, location_id, item_id, found, entrance, item_flags)) return hints def collect_hints_location(ctx: Context, team: int, slot: int, location: str) -> typing.List[NetUtils.Hint]: seeked_location: int = proxy_worlds[ctx.games[slot]].location_name_to_id[location] - item_id, receiving_player = ctx.locations[slot].get(seeked_location, (None, None)) - if item_id: + result = ctx.locations[slot].get(seeked_location, (None, None, None)) + if result: + if len(result) == 3: + item_id, receiving_player, item_flags = result + else: + item_id, receiving_player = result + item_flags = 0 + found = seeked_location in ctx.location_checks[team, slot] entrance = ctx.er_hint_data.get(slot, {}).get(seeked_location, "") - return [NetUtils.Hint(receiving_player, slot, seeked_location, item_id, found, entrance)] + return [NetUtils.Hint(receiving_player, slot, seeked_location, item_id, found, entrance, item_flags)] return [] @@ -729,10 +745,10 @@ def json_format_send_event(net_item: NetworkItem, receiving_player: int): NetUtils.add_json_text(parts, net_item.player, type=NetUtils.JSONTypes.player_id) if net_item.player == receiving_player: NetUtils.add_json_text(parts, " found their ") - NetUtils.add_json_item(parts, net_item.item, net_item.player) + NetUtils.add_json_item(parts, net_item.item, net_item.player, net_item.flags) else: NetUtils.add_json_text(parts, " sent ") - NetUtils.add_json_item(parts, net_item.item, receiving_player) + NetUtils.add_json_item(parts, net_item.item, receiving_player, net_item.flags) NetUtils.add_json_text(parts, " to ") NetUtils.add_json_text(parts, receiving_player, type=NetUtils.JSONTypes.player_id) @@ -1342,8 +1358,13 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict): [{'cmd': 'InvalidPacket', "type": "arguments", "text": 'LocationScouts', "original_cmd": cmd}]) return - target_item, target_player = ctx.locations[client.slot][location] - locs.append(NetworkItem(target_item, location, target_player)) + if len(ctx.locations[client.slot][location]) == 3: + target_item, target_player, flags = ctx.locations[client.slot][location] + else: + target_item, target_player = ctx.locations[client.slot][location] + flags = 0 + + locs.append(NetworkItem(target_item, location, target_player, flags)) await ctx.send_msgs(client, [{'cmd': 'LocationInfo', 'locations': locs}]) diff --git a/NetUtils.py b/NetUtils.py index ff49b8b8..b01ef58d 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -57,6 +57,7 @@ class NetworkItem(typing.NamedTuple): item: int location: int player: int + flags: int = 0 def _scan_for_TypedTuples(obj: typing.Any) -> typing.Any: @@ -194,7 +195,17 @@ class JSONtoTextParser(metaclass=HandlerMeta): return self._handle_color(node) def _handle_item_name(self, node: JSONMessagePart): - node["color"] = 'cyan' + flags = node.get("item_flags", 0) + if flags == 0: + node["color"] = 'cyan' + elif flags & 1 << 0: # advancement + node["color"] = 'plum' + elif flags & 1 << 1: # never_eclude + node["color"] = 'slateblue' + elif flags & 1 << 2: # trap + node["color"] = 'salmon' + else: + node["color"] = 'cyan' return self._handle_color(node) def _handle_item_id(self, node: JSONMessagePart): @@ -238,8 +249,8 @@ def add_json_text(parts: list, text: typing.Any, **kwargs) -> None: parts.append({"text": str(text), **kwargs}) -def add_json_item(parts: list, item_id: int, player: int = 0, **kwargs) -> None: - parts.append({"text": str(item_id), "player": player, "type": JSONTypes.item_id, **kwargs}) +def add_json_item(parts: list, item_id: int, player: int = 0, item_flags: int = 0, **kwargs) -> None: + parts.append({"text": str(item_id), "player": player, "item_flags": item_flags, "type": JSONTypes.item_id, **kwargs}) def add_json_location(parts: list, item_id: int, player: int = 0, **kwargs) -> None: @@ -253,13 +264,14 @@ class Hint(typing.NamedTuple): item: int found: bool entrance: str = "" + item_flags: int = 0 def re_check(self, ctx, team) -> Hint: if self.found: return self found = self.location in ctx.location_checks[team, self.finding_player] if found: - return Hint(self.receiving_player, self.finding_player, self.location, self.item, found, self.entrance) + return Hint(self.receiving_player, self.finding_player, self.location, self.item, found, self.entrance, self.item_flags) return self def __hash__(self): @@ -270,7 +282,7 @@ class Hint(typing.NamedTuple): add_json_text(parts, "[Hint]: ") add_json_text(parts, self.receiving_player, type="player_id") add_json_text(parts, "'s ") - add_json_item(parts, self.item, self.receiving_player) + add_json_item(parts, self.item, self.receiving_player, self.item_flags) add_json_text(parts, " is at ") add_json_location(parts, self.location, self.finding_player) add_json_text(parts, " in ") @@ -288,7 +300,7 @@ class Hint(typing.NamedTuple): return {"cmd": "PrintJSON", "data": parts, "type": "Hint", "receiving": self.receiving_player, - "item": NetworkItem(self.item, self.location, self.finding_player), + "item": NetworkItem(self.item, self.location, self.finding_player, self.item_flags), "found": self.found} @property diff --git a/docs/network protocol.md b/docs/network protocol.md index 7919a34e..906097a8 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -164,7 +164,7 @@ Sent to clients purely to display a message to the player. This packet differs f | data | list\[[JSONMessagePart](#JSONMessagePart)\] | Type of this part of the message. | | type | str | May be present to indicate the nature of this message. Known types are Hint and ItemSend. | | receiving | int | Is present if type is Hint or ItemSend and marks the destination player's ID. | -| item | [NetworkItem](#NetworkItem) | Is present if type is Hint or ItemSend and marks the source player id, location id and item id. | +| item | [NetworkItem](#NetworkItem) | Is present if type is Hint or ItemSend and marks the source player id, location id, item id and item flags. | | found | bool | Is present if type is Hint, denotes whether the location hinted for was checked. | ### DataPackage @@ -329,15 +329,23 @@ class NetworkItem(NamedTuple): item: int location: int player: int + flags: int ``` In JSON this may look like: ```js [ - {"item": 1, "location": 1, "player": 0}, - {"item": 2, "location": 2, "player": 0}, - {"item": 3, "location": 3, "player": 0} + {"item": 1, "location": 1, "player": 0, "flags": 1}, + {"item": 2, "location": 2, "player": 0, "flags": 2}, + {"item": 3, "location": 3, "player": 0, "flags": 0} ] ``` +Flags are bit flags: +| Flag | Meaning | +| ----- | ----- | +| 0 | Indicates, indicates the item had no specail use in generation | +| 1 << 0 | If set, indicates the item can unlock advancement | +| 1 << 1 | If set, indicates the item is important but not necessarily unlocks advancement | +| 1 << 2 | If set, indicates the item is an trap | ### JSONMessagePart Message nodes sent along with [PrintJSON](#PrintJSON) packet to be reconstructed into a legible message. The nodes are intended to be read in the order they are listed in the packet. @@ -346,9 +354,10 @@ Message nodes sent along with [PrintJSON](#PrintJSON) packet to be reconstructed from typing import TypedDict, Optional class JSONMessagePart(TypedDict): type: Optional[str] - color: Optional[str] text: Optional[str] - player: Optional[int] # marks owning player id for location/item + color: Optional[str] # only available if type is an color + item_flags: Optional[int] # only available if type is an item + player: Optional[int] # only available if type is either item or location ``` `type` is used to denote the intent of the message part. This can be used to indicate special information which may be rendered differently depending on client. How these types are displayed in Archipelago's ALttP client is not the end-all be-all. Other clients may choose to interpret and display these messages differently. @@ -390,6 +399,8 @@ Color options: * white_bg `text` is the content of the message part to be displayed. +`player` marks owning player id for location/item, +`item_flags` contains the [NetworkItem](#NetworkItem) flags that belong to the item ### Client States An enumeration containing the possible client states that may be used to inform the server in [StatusUpdate](#StatusUpdate). diff --git a/kvui.py b/kvui.py index cd8da14d..f403b826 100644 --- a/kvui.py +++ b/kvui.py @@ -340,7 +340,7 @@ class GameManager(App): except Exception as e: logging.getLogger("Client").exception(e) - def print_json(self, data): + def print_json(self, data: typing.List[JSONMessagePart]): text = self.json_to_kivy_parser(data) self.log_panels["Archipelago"].on_message_markup(text) self.log_panels["All"].on_message_markup(text) @@ -420,6 +420,9 @@ class KivyJSONtoTextParser(JSONtoTextParser): "blue": "6495ED", "magenta": "EE00EE", "cyan": "00EEEE", + "slateblue": "6D8BE8", + "plum": "AF99EF", + "salmon": "FA8072", "white": "FFFFFF" }