-
Notifications
You must be signed in to change notification settings - Fork 2
/
net_data.py
138 lines (123 loc) · 4.25 KB
/
net_data.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""
Holds dataclasses for information to be sent and received over the network.
"""
from dataclasses import dataclass
from typing import ClassVar, Tuple
@dataclass
class Coords:
"""
Represents a set of coordinates.
"""
x_pos: float
y_pos: float
byte_size: ClassVar[int] = 8
def __bytes__(self) -> bytes:
"""
Get the list of bytes ready to be transmitted over the network.
"""
# Positions are sent as integers with 2 d.p of accuracy from the
# original float.
return (
int(self.x_pos * 100).to_bytes(4, "big", signed=True)
+ int(self.y_pos * 100).to_bytes(4, "big", signed=True)
)
@classmethod
def from_bytes(cls, coord_bytes: bytes) -> 'Coords':
"""
Get an instance of this class from bytes transmitted over the network.
"""
return cls(
int.from_bytes(coord_bytes[:4], "big", signed=True) / 100,
int.from_bytes(coord_bytes[4:8], "big", signed=True) / 100
)
def to_tuple(self) -> Tuple[float, float]:
"""
Convert Coords back to a tuple of 2 floats.
"""
return self.x_pos, self.y_pos
def to_int_tuple(self) -> Tuple[int, int]:
"""
Convert Coords back to a tuple of 2 integers.
"""
return self.x_pos.__trunc__(), self.y_pos.__trunc__()
@dataclass
class Player:
"""
Represents publicly known information about a player: their position and
skin.
"""
name: str
pos: Coords
grid_pos: Tuple[int, int]
skin: int
kills: int
deaths: int
byte_size: ClassVar[int] = Coords.byte_size + 29
def __bytes__(self) -> bytes:
"""
Get the list of bytes ready to be transmitted over the network.
"""
# Positions are sent as integers with 2 d.p of accuracy from the
# original float.
return (
bytes.ljust(self.name.encode('ascii', 'ignore')[:24], 24, b'\x00')
+ bytes(self.pos)
+ self.skin.to_bytes(1, "big")
+ self.kills.to_bytes(2, "big")
+ self.deaths.to_bytes(2, "big")
)
@classmethod
def from_bytes(cls, player_bytes: bytes) -> 'Player':
"""
Get an instance of this class from bytes transmitted over the network.
"""
name = player_bytes[:24].strip(b'\x00').decode('ascii', 'ignore')
coords = Coords.from_bytes(player_bytes[24:32])
return cls(
name, coords, (coords.x_pos.__trunc__(), coords.y_pos.__trunc__()),
int.from_bytes(player_bytes[32:33], "big"),
int.from_bytes(player_bytes[33:35], "big"),
int.from_bytes(player_bytes[35:37], "big")
)
@dataclass
class PrivatePlayer(Player):
"""
Extends Player, also containing private information known only to the
server and the player themselves.
"""
hits_remaining: int
last_killer_skin: int = 0
byte_size: ClassVar[int] = Player.byte_size + 2
def __bytes__(self) -> bytes:
"""
Get the list of bytes ready to be transmitted over the network.
"""
return (
super().__bytes__()
+ self.hits_remaining.to_bytes(1, "big")
+ self.last_killer_skin.to_bytes(1, "big")
)
@classmethod
def from_bytes(cls, player_bytes: bytes) -> 'PrivatePlayer':
"""
Get an instance of this class from bytes transmitted over the network.
"""
name = player_bytes[:24].strip(b'\x00').decode('ascii', 'ignore')
coords = Coords.from_bytes(player_bytes[24:32])
return cls(
name, coords, (coords.x_pos.__trunc__(), coords.y_pos.__trunc__()),
int.from_bytes(player_bytes[32:33], "big"),
int.from_bytes(player_bytes[33:35], "big"),
int.from_bytes(player_bytes[35:37], "big"),
int.from_bytes(player_bytes[37:38], "big"),
int.from_bytes(player_bytes[38:39], "big")
)
def strip_private_data(self) -> Player:
"""
Remove all private data from this class so that it is suitable to be
sent to all players.
"""
return Player(
self.name, self.pos, self.grid_pos, self.skin, self.kills,
self.deaths
)