Skip to content

Commit

Permalink
[Network_Item] Add item flags to network item so client can distinct …
Browse files Browse the repository at this point in the history
…some details (ArchipelagoMW#210)
  • Loading branch information
Jarno458 authored Jan 18, 2022
1 parent 5d356d5 commit c9fa49d
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 29 deletions.
12 changes: 8 additions & 4 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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
Expand Down Expand Up @@ -266,18 +266,22 @@ def write_multidata():
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]:
Expand Down
45 changes: 33 additions & 12 deletions MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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 []


Expand All @@ -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)

Expand Down Expand Up @@ -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}])

Expand Down
24 changes: 18 additions & 6 deletions NetUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -194,7 +195,17 @@ def _handle_player_name(self, node: JSONMessagePart):
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):
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand All @@ -270,7 +282,7 @@ def as_network_message(self) -> dict:
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 ")
Expand All @@ -288,7 +300,7 @@ def as_network_message(self) -> dict:

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
Expand Down
23 changes: 17 additions & 6 deletions docs/network protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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).
Expand Down
5 changes: 4 additions & 1 deletion kvui.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def on_message(self, textinput: TextInput):
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)
Expand Down Expand Up @@ -420,6 +420,9 @@ class KivyJSONtoTextParser(JSONtoTextParser):
"blue": "6495ED",
"magenta": "EE00EE",
"cyan": "00EEEE",
"slateblue": "6D8BE8",
"plum": "AF99EF",
"salmon": "FA8072",
"white": "FFFFFF"
}

Expand Down

0 comments on commit c9fa49d

Please sign in to comment.