multiplayerlib 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multiplayerlib/EncryptionManager.py +27 -0
- multiplayerlib/NetworkConstants.py +9 -0
- multiplayerlib/NetworkLib.py +234 -0
- multiplayerlib/__init__.py +15 -0
- multiplayerlib-0.0.1.dist-info/METADATA +257 -0
- multiplayerlib-0.0.1.dist-info/RECORD +9 -0
- multiplayerlib-0.0.1.dist-info/WHEEL +5 -0
- multiplayerlib-0.0.1.dist-info/licenses/LICENSE +25 -0
- multiplayerlib-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# this file is part of MultiplayerLib, which is under an MIT license.
|
|
2
|
+
# see LICENSE file at root of this repository for details.
|
|
3
|
+
|
|
4
|
+
from multiplayerlib.NetworkConstants import *
|
|
5
|
+
from cryptography.fernet import Fernet
|
|
6
|
+
from cryptography.hazmat.primitives import hashes
|
|
7
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
8
|
+
import base64
|
|
9
|
+
import sys
|
|
10
|
+
import zlib
|
|
11
|
+
|
|
12
|
+
class EncryptionManager:
|
|
13
|
+
def __init__(self,key,salt):
|
|
14
|
+
kdf = PBKDF2HMAC(
|
|
15
|
+
algorithm=hashes.SHA256(),
|
|
16
|
+
length=32,
|
|
17
|
+
salt=salt.to_bytes(16,sys.byteorder),
|
|
18
|
+
iterations=1_000_000,
|
|
19
|
+
)
|
|
20
|
+
self.key = base64.urlsafe_b64encode(kdf.derive(key))
|
|
21
|
+
self.encrypter = Fernet(self.key)
|
|
22
|
+
|
|
23
|
+
def encrypt(self,bytesObject):
|
|
24
|
+
return self.encrypter.encrypt(bytesObject)
|
|
25
|
+
|
|
26
|
+
def decrypt(self,bytesObject):
|
|
27
|
+
return self.encrypter.decrypt(bytesObject)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# this file is part of MultiplayerLib, which is under an MIT license.
|
|
2
|
+
# see LICENSE file at root of this repository for details.
|
|
3
|
+
|
|
4
|
+
BUFFER_SIZE = 1024
|
|
5
|
+
DEFAULT_PORT = 5000
|
|
6
|
+
|
|
7
|
+
DEFAULT_KEY = "encryptionKeyForFernetEncryption"
|
|
8
|
+
DEFAULT_SALT = 2815
|
|
9
|
+
PEER_TIMEOUT = 5
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# this file is part of MultiplayerLib, which is under an MIT license.
|
|
2
|
+
# see LICENSE file at root of this repository for details.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Network utilities and a NetworkManager for peer-to-peer UDP communication.
|
|
6
|
+
|
|
7
|
+
Provides helpers for encoding/decoding messages, optional compression and
|
|
8
|
+
encryption, and a simple API for sending/receiving JSON-serializable packets.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import random
|
|
12
|
+
|
|
13
|
+
import orjson
|
|
14
|
+
import socket
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
from multiplayerlib.NetworkConstants import *
|
|
18
|
+
from multiplayerlib.EncryptionManager import EncryptionManager
|
|
19
|
+
import zlib
|
|
20
|
+
from queue import Queue
|
|
21
|
+
|
|
22
|
+
class Peer():
|
|
23
|
+
registry = {}
|
|
24
|
+
registry_lock = threading.Lock()
|
|
25
|
+
def __init__(self,address):
|
|
26
|
+
self.address = address
|
|
27
|
+
self.latestPacketTime = time.time()
|
|
28
|
+
|
|
29
|
+
def heartbeat(self):
|
|
30
|
+
self.latestPacketTime = time.time()
|
|
31
|
+
|
|
32
|
+
def get_is_alive(self):
|
|
33
|
+
return time.time()-self.latestPacketTime < PEER_TIMEOUT
|
|
34
|
+
|
|
35
|
+
def get_address(self):
|
|
36
|
+
return self.address
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def getInstance(cls, value):
|
|
40
|
+
with cls.registry_lock:
|
|
41
|
+
instance = cls.registry.get(value)
|
|
42
|
+
if instance is None:
|
|
43
|
+
instance = cls(value)
|
|
44
|
+
cls.registry[value] = instance
|
|
45
|
+
return instance
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def update_registry(cls):
|
|
49
|
+
with cls.registry_lock:
|
|
50
|
+
cls.registry = {
|
|
51
|
+
address: instance
|
|
52
|
+
for address, instance in cls.registry.items()
|
|
53
|
+
if instance.get_is_alive()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class NetworkManager:
|
|
57
|
+
"""
|
|
58
|
+
High-level manager for UDP networking.
|
|
59
|
+
|
|
60
|
+
Handles socket setup, optional compression and encryption of packets, and
|
|
61
|
+
provides a background receiving loop to accumulate incoming messages.
|
|
62
|
+
"""
|
|
63
|
+
def __init__(self, is_host=True, peer_ip=None, base_port=DEFAULT_PORT, use_compression=False, use_encryption=False, encryption_key=DEFAULT_KEY, encryption_salt=DEFAULT_SALT) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Network Manager
|
|
66
|
+
|
|
67
|
+
:param is_host: host server or connect to server
|
|
68
|
+
:param peer_ip: ip address of peer (only required if client is NOT the server host)
|
|
69
|
+
:param base_port: the base port for the server. server hosts recv on this port and client hosts recv on base_port + 1. client sends to base_port and server sends to base_port + 1
|
|
70
|
+
:param use_compression: if true, packets will be compressed. this allows for reduced size of packets. defaults to true. (most UDP packets should be small enough that this isnt needed)
|
|
71
|
+
:param use_encryption: if true, packets will be encrypted before sending.
|
|
72
|
+
"""
|
|
73
|
+
if use_encryption:
|
|
74
|
+
self.encryptionManager = EncryptionManager(encryption_key.encode(),encryption_salt)
|
|
75
|
+
originalMessage = getSamplePacketString()
|
|
76
|
+
encryptedTestMessage = self.encryptionManager.encrypt(zlib.compress(orjson.dumps(getSamplePacketString())))
|
|
77
|
+
decryptedTestMessage = orjson.loads(zlib.decompress(self.encryptionManager.decrypt(encryptedTestMessage)))
|
|
78
|
+
print(f"encryption is working: {originalMessage==decryptedTestMessage}")
|
|
79
|
+
|
|
80
|
+
self.compress_packets = use_compression
|
|
81
|
+
self.use_encryption = use_encryption
|
|
82
|
+
|
|
83
|
+
self.is_host = is_host
|
|
84
|
+
self.peer_ip = peer_ip
|
|
85
|
+
|
|
86
|
+
self.local_port = base_port if is_host else base_port + random.randint(1,10)
|
|
87
|
+
self.peer_port = base_port + 1 if is_host else base_port
|
|
88
|
+
|
|
89
|
+
self.peer_addr = None if is_host else (peer_ip,self.peer_port)
|
|
90
|
+
|
|
91
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
92
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
93
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32768)
|
|
94
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 32768)
|
|
95
|
+
self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 0x10)
|
|
96
|
+
self.sock.settimeout(0.5)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
self.sock.bind(("",self.local_port))
|
|
100
|
+
print(f"Socket bound to {socket.gethostbyname(socket.gethostname())}:{self.local_port}")
|
|
101
|
+
except OSError as e:
|
|
102
|
+
print(f"Socket bind failed on port {self.local_port}: {e}")
|
|
103
|
+
|
|
104
|
+
self.running = False
|
|
105
|
+
self.incoming_queue = Queue()
|
|
106
|
+
|
|
107
|
+
def start(self) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Start the receiver loop in a background thread.
|
|
110
|
+
"""
|
|
111
|
+
self.running = True
|
|
112
|
+
threading.Thread(
|
|
113
|
+
target=self.recv_loop,
|
|
114
|
+
daemon=True
|
|
115
|
+
).start()
|
|
116
|
+
|
|
117
|
+
threading.Thread(
|
|
118
|
+
target=self.peer_cleanup_loop,
|
|
119
|
+
daemon=True
|
|
120
|
+
).start()
|
|
121
|
+
|
|
122
|
+
def peer_cleanup_loop(self):
|
|
123
|
+
"""
|
|
124
|
+
Periodically remove timed-out peers.
|
|
125
|
+
"""
|
|
126
|
+
while self.running:
|
|
127
|
+
Peer.update_registry()
|
|
128
|
+
time.sleep(1)
|
|
129
|
+
|
|
130
|
+
def recv_loop(self) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Continuously receive packets and decode them into the incoming queue.
|
|
133
|
+
"""
|
|
134
|
+
while self.running:
|
|
135
|
+
try:
|
|
136
|
+
data,addr = self.sock.recvfrom(BUFFER_SIZE)
|
|
137
|
+
if self.is_host:
|
|
138
|
+
peer = Peer.getInstance(addr)
|
|
139
|
+
peer.heartbeat()
|
|
140
|
+
with Peer.registry_lock:
|
|
141
|
+
peers = list(Peer.registry.items())
|
|
142
|
+
|
|
143
|
+
for address, peer in peers:
|
|
144
|
+
if address != addr:
|
|
145
|
+
self.sock.sendto(data, address)
|
|
146
|
+
try:
|
|
147
|
+
msg = self.decode_message(data)
|
|
148
|
+
self.incoming_queue.put(msg)
|
|
149
|
+
except Exception:
|
|
150
|
+
pass
|
|
151
|
+
except Exception:
|
|
152
|
+
time.sleep(0.01)
|
|
153
|
+
|
|
154
|
+
def safe_receive(self) -> dict | None:
|
|
155
|
+
"""
|
|
156
|
+
Retrieve the next parsed packet from the incoming queue in a non-blocking way.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
return self.incoming_queue.get_nowait()
|
|
160
|
+
except:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def decode_message(self, message_encoded: bytes) -> dict:
|
|
164
|
+
"""
|
|
165
|
+
Decode a raw packet into a Python object.
|
|
166
|
+
"""
|
|
167
|
+
if self.use_encryption:
|
|
168
|
+
message_encoded = self.encryptionManager.decrypt(message_encoded)
|
|
169
|
+
if self.compress_packets:
|
|
170
|
+
message_encoded = zlib.decompress(message_encoded)
|
|
171
|
+
message = orjson.loads(message_encoded)
|
|
172
|
+
return message
|
|
173
|
+
|
|
174
|
+
def prepare_message(self, data: dict) -> bytes:
|
|
175
|
+
"""
|
|
176
|
+
Prepare a Python object for sending over the network.
|
|
177
|
+
"""
|
|
178
|
+
message_encoded = orjson.dumps(data)
|
|
179
|
+
if self.compress_packets:
|
|
180
|
+
message_encoded = zlib.compress(message_encoded)
|
|
181
|
+
if self.use_encryption:
|
|
182
|
+
message_encoded = self.encryptionManager.encrypt(message_encoded)
|
|
183
|
+
return message_encoded
|
|
184
|
+
|
|
185
|
+
def safe_send(self, data: dict) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Send a data packet to the currently known peer address.
|
|
188
|
+
"""
|
|
189
|
+
if self.is_host:
|
|
190
|
+
with Peer.registry_lock:
|
|
191
|
+
peers = list(Peer.registry.items())
|
|
192
|
+
|
|
193
|
+
for address, peer in peers:
|
|
194
|
+
try:
|
|
195
|
+
self.sock.sendto(self.prepare_message(data), address)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
print(e)
|
|
198
|
+
else:
|
|
199
|
+
if self.peer_addr:
|
|
200
|
+
try:
|
|
201
|
+
self.sock.sendto(self.prepare_message(data), self.peer_addr)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
print(e)
|
|
204
|
+
|
|
205
|
+
def close(self) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Stop the receive loop and close the underlying socket.
|
|
208
|
+
"""
|
|
209
|
+
self.running = False
|
|
210
|
+
try:
|
|
211
|
+
self.sock.close()
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def getSamplePacketString() -> dict:
|
|
217
|
+
"""
|
|
218
|
+
Returns a basic example of a packet in a dictionary structure.
|
|
219
|
+
"""
|
|
220
|
+
data = {
|
|
221
|
+
"type":"playerdata",
|
|
222
|
+
"packetNum":random.randint(0,100),
|
|
223
|
+
"position":{
|
|
224
|
+
"x":random.randint(0,600),
|
|
225
|
+
"y":random.randint(0,600),
|
|
226
|
+
"rotation":random.uniform(0,6.28)
|
|
227
|
+
},
|
|
228
|
+
"velocity":[random.randint(0,5),random.randint(0,5)],
|
|
229
|
+
"health":random.randint(0,100),
|
|
230
|
+
"timestamp":time.time(),
|
|
231
|
+
"deaths":random.randint(0,100),
|
|
232
|
+
"ID":random.randint(0,1000)
|
|
233
|
+
}
|
|
234
|
+
return data
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# this file is part of MultiplayerLib, which is under an MIT license.
|
|
2
|
+
# see LICENSE file at root of this repository for details.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
:MultiplayerLib:\n
|
|
6
|
+
Multiplayerlib is a library designed to allow for easy creation of multiplayer games with any game
|
|
7
|
+
module. It is currently limited to only 2 player games where one player is the host and one player
|
|
8
|
+
connects, although there are plans for adding support for more than just 1v1 games.
|
|
9
|
+
"""
|
|
10
|
+
from multiplayerlib.NetworkLib import *
|
|
11
|
+
from multiplayerlib.NetworkConstants import *
|
|
12
|
+
|
|
13
|
+
def help():
|
|
14
|
+
print("example packet:")
|
|
15
|
+
getSamplePacketString()
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multiplayerlib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: MultiplayerLib - A lightweight Python multiplayer networking library
|
|
5
|
+
Author: griffingreat1
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: cryptography==50.0.1
|
|
10
|
+
Requires-Dist: orjson==3.12.0
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# MultiplayerLib
|
|
14
|
+
|
|
15
|
+
MultiplayerLib is a lightweight UDP networking library designed for simple real-time multiplayer games. It provides a minimal, easy-to-integrate API for fast client-server communication with optional compression and encryption.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
- [Features](#-features)
|
|
21
|
+
- [Installation](#-installation)
|
|
22
|
+
- [Quick Start](#quick-start-example)
|
|
23
|
+
- [Architecture Overview](#architecture-overview)
|
|
24
|
+
- [API Reference](#api-reference)
|
|
25
|
+
- [Packet Examples](#packet-examples-and-typical-usage-in-a-game)
|
|
26
|
+
- [Troubleshooting](#troubleshooting--tips)
|
|
27
|
+
- [Security Notes](#security-notes)
|
|
28
|
+
- [Contributing](#contributing)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✨ Features
|
|
33
|
+
|
|
34
|
+
- 🚀 UDP networking for low-latency gameplay
|
|
35
|
+
- 👥 Multi-client server support
|
|
36
|
+
- 🔄 Real-time state synchronization
|
|
37
|
+
- 🗜️ Optional packet compression using zlib
|
|
38
|
+
- 🔐 Optional encryption using Fernet (AES) + PBKDF2 key derivation
|
|
39
|
+
- 📦 JSON-based packet formatting (human readable)
|
|
40
|
+
- 🧵 Background threaded receive loop
|
|
41
|
+
- 🛡️ Safe send/receive queue system
|
|
42
|
+
- 🔧 Minimal API surface: `NetworkManager`, `EncryptionManager`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📦 Installation
|
|
47
|
+
|
|
48
|
+
run the install command to install the package
|
|
49
|
+
|
|
50
|
+
```pip install multiplayerlib
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick Start Example
|
|
56
|
+
|
|
57
|
+
### Host (Server)
|
|
58
|
+
|
|
59
|
+
```py
|
|
60
|
+
mgr = NetworkManager(
|
|
61
|
+
is_host=True,
|
|
62
|
+
base_port=5000,
|
|
63
|
+
use_compression=True,
|
|
64
|
+
use_encryption=False
|
|
65
|
+
)
|
|
66
|
+
mgr.start()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Client
|
|
70
|
+
|
|
71
|
+
```py
|
|
72
|
+
mgr = NetworkManager(
|
|
73
|
+
is_host=False,
|
|
74
|
+
peer_ip="1.2.3.4",
|
|
75
|
+
base_port=5000,
|
|
76
|
+
use_compression=True,
|
|
77
|
+
use_encryption=False
|
|
78
|
+
)
|
|
79
|
+
mgr.start()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Sending / Receiving
|
|
83
|
+
|
|
84
|
+
```py
|
|
85
|
+
mgr.safe_send({
|
|
86
|
+
"type": "playerdata",
|
|
87
|
+
"position": {"x": 10, "y": 20, "rotation": 0},
|
|
88
|
+
"health": 100
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
msg = mgr.safe_receive()
|
|
92
|
+
if msg:
|
|
93
|
+
# handle decoded packet
|
|
94
|
+
pass
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Architecture Overview
|
|
100
|
+
|
|
101
|
+
- The system follows a **client-server UDP model**:
|
|
102
|
+
- One host acts as the server
|
|
103
|
+
- Multiple clients can connect and send packets
|
|
104
|
+
- The server broadcasts received packets to all connected clients
|
|
105
|
+
- Each client sends state updates to the server
|
|
106
|
+
|
|
107
|
+
### Networking Flow
|
|
108
|
+
|
|
109
|
+
1. Client sends packet → Server receives
|
|
110
|
+
2. Server updates peer registry (heartbeats)
|
|
111
|
+
3. Server broadcasts packet to all other clients
|
|
112
|
+
4. Clients receive and process updates via `incoming_queue`
|
|
113
|
+
|
|
114
|
+
### Design Notes
|
|
115
|
+
|
|
116
|
+
- UDP is used for low latency (no built-in reliability)
|
|
117
|
+
- Packet delivery is not guaranteed
|
|
118
|
+
- Ordering is not enforced
|
|
119
|
+
- Designed for frequent state updates (position, actions, etc.)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### NetworkManager
|
|
126
|
+
|
|
127
|
+
#### Constructor
|
|
128
|
+
```py
|
|
129
|
+
NetworkManager(
|
|
130
|
+
is_host=True,
|
|
131
|
+
peer_ip=None,
|
|
132
|
+
base_port=5000,
|
|
133
|
+
use_compression=True,
|
|
134
|
+
use_encryption=False,
|
|
135
|
+
encryption_key=DEFAULT_KEY,
|
|
136
|
+
encryption_salt=DEFAULT_SALT
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Methods
|
|
141
|
+
|
|
142
|
+
- `start()`
|
|
143
|
+
Starts background receive and peer cleanup threads.
|
|
144
|
+
|
|
145
|
+
- `safe_send(data: dict)`
|
|
146
|
+
Sends a packet to all connected peers (server) or to the host (client).
|
|
147
|
+
|
|
148
|
+
- `safe_receive() -> dict | None`
|
|
149
|
+
Non-blocking receive from internal queue.
|
|
150
|
+
|
|
151
|
+
- `close()`
|
|
152
|
+
Stops networking and closes socket.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### Packet Pipeline
|
|
157
|
+
|
|
158
|
+
Outgoing:
|
|
159
|
+
```
|
|
160
|
+
dict → JSON → compress (optional) → encrypt (optional) → UDP packet
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Incoming:
|
|
164
|
+
```
|
|
165
|
+
UDP packet → decrypt → decompress → JSON decode → dict
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### EncryptionManager
|
|
171
|
+
|
|
172
|
+
- Uses PBKDF2-derived key + Fernet encryption
|
|
173
|
+
- Requires identical key/salt on both sides
|
|
174
|
+
- Encrypt/decrypt operates on bytes
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Packet Examples
|
|
179
|
+
|
|
180
|
+
Common patterns used in games:
|
|
181
|
+
|
|
182
|
+
### Player State
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"type": "playerdata",
|
|
186
|
+
"position": {
|
|
187
|
+
"x": 640,
|
|
188
|
+
"y": 360,
|
|
189
|
+
"rotation": 1.57
|
|
190
|
+
},
|
|
191
|
+
"health": 100
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Action Event
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"type": "shoot",
|
|
199
|
+
"pos": [x, y],
|
|
200
|
+
"vec": [vx, vy],
|
|
201
|
+
"timestamp": 1234567890.0
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Disconnect
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"type": "quit"
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Important Constants
|
|
215
|
+
|
|
216
|
+
Defined in `NetworkConstants.py`:
|
|
217
|
+
|
|
218
|
+
- `BUFFER_SIZE = 4096`
|
|
219
|
+
- `DEFAULT_PORT = 5000`
|
|
220
|
+
- `POSITIONUPDATEINTERVAL = 1/30`
|
|
221
|
+
- `DEFAULT_KEY`, `DEFAULT_SALT`
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Troubleshooting & Tips
|
|
226
|
+
|
|
227
|
+
- If bind fails, ensure the port is not in use
|
|
228
|
+
- If packets are missing, remember UDP does not guarantee delivery
|
|
229
|
+
- If encryption fails, ensure:
|
|
230
|
+
- same key
|
|
231
|
+
- same salt
|
|
232
|
+
- same `use_encryption` setting
|
|
233
|
+
- Keep packets small (UDP safe size ~1–1.5KB recommended)
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Security Notes
|
|
238
|
+
|
|
239
|
+
- Default keys are for development only
|
|
240
|
+
- Always generate secure keys for real applications
|
|
241
|
+
- Treat UDP as **insecure transport unless encrypted**
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
Contributions welcome:
|
|
248
|
+
- new packet utilities
|
|
249
|
+
- performance improvements
|
|
250
|
+
- reliability features (ordering, ack system)
|
|
251
|
+
- better debugging/logging tools
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT License — see [LICENSE](../LICENSE)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
multiplayerlib/EncryptionManager.py,sha256=NaoG3jACJZ_8A2Zj8CGMILi2iTUjjyIPkzHGFlJjcn8,889
|
|
2
|
+
multiplayerlib/NetworkConstants.py,sha256=0FDlFsvTljl0dyNFzIk6GTa02MXCDzmCKMj0o1SRN1A,255
|
|
3
|
+
multiplayerlib/NetworkLib.py,sha256=G8XKuKJ40dF0oajX-JmjL2t_Y8OVf0VbY3amSRn29jA,8246
|
|
4
|
+
multiplayerlib/__init__.py,sha256=5c-oglX2MzAlXmul-59eFhBFl9nFtVlBthwXqv1VLU8,594
|
|
5
|
+
multiplayerlib-0.0.1.dist-info/licenses/LICENSE,sha256=48NxET-TydLhSStr1Xeps-BVoJrAHHEVDQAntTj2p7Q,1289
|
|
6
|
+
multiplayerlib-0.0.1.dist-info/METADATA,sha256=o4ZFocLapsk9qiiocHA9OB2QO_kG3fnmcVwa1KQjFCQ,4998
|
|
7
|
+
multiplayerlib-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
multiplayerlib-0.0.1.dist-info/top_level.txt,sha256=MYuxXjRQ_aoiHE7krXpIHPAYUW90v8iNEHQoRomvtFc,15
|
|
9
|
+
multiplayerlib-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 griffingreat1
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of the MultiplayerLib software and associated documentation files (the “Software”),
|
|
7
|
+
to deal in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
16
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
17
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
18
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
19
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
20
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Note: This license applies **only to the MultiplayerLib library**.
|
|
25
|
+
The included demo game code is **not covered by this license** and can be used, modified, or distributed freely.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
multiplayerlib
|