multiplayerlib 0.0.1__tar.gz

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.
@@ -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,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,245 @@
1
+ # MultiplayerLib
2
+
3
+ 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.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+ - [Features](#-features)
9
+ - [Installation](#-installation)
10
+ - [Quick Start](#quick-start-example)
11
+ - [Architecture Overview](#architecture-overview)
12
+ - [API Reference](#api-reference)
13
+ - [Packet Examples](#packet-examples-and-typical-usage-in-a-game)
14
+ - [Troubleshooting](#troubleshooting--tips)
15
+ - [Security Notes](#security-notes)
16
+ - [Contributing](#contributing)
17
+
18
+ ---
19
+
20
+ ## ✨ Features
21
+
22
+ - 🚀 UDP networking for low-latency gameplay
23
+ - 👥 Multi-client server support
24
+ - 🔄 Real-time state synchronization
25
+ - 🗜️ Optional packet compression using zlib
26
+ - 🔐 Optional encryption using Fernet (AES) + PBKDF2 key derivation
27
+ - 📦 JSON-based packet formatting (human readable)
28
+ - 🧵 Background threaded receive loop
29
+ - 🛡️ Safe send/receive queue system
30
+ - 🔧 Minimal API surface: `NetworkManager`, `EncryptionManager`
31
+
32
+ ---
33
+
34
+ ## 📦 Installation
35
+
36
+ run the install command to install the package
37
+
38
+ ```pip install multiplayerlib
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Quick Start Example
44
+
45
+ ### Host (Server)
46
+
47
+ ```py
48
+ mgr = NetworkManager(
49
+ is_host=True,
50
+ base_port=5000,
51
+ use_compression=True,
52
+ use_encryption=False
53
+ )
54
+ mgr.start()
55
+ ```
56
+
57
+ ### Client
58
+
59
+ ```py
60
+ mgr = NetworkManager(
61
+ is_host=False,
62
+ peer_ip="1.2.3.4",
63
+ base_port=5000,
64
+ use_compression=True,
65
+ use_encryption=False
66
+ )
67
+ mgr.start()
68
+ ```
69
+
70
+ ### Sending / Receiving
71
+
72
+ ```py
73
+ mgr.safe_send({
74
+ "type": "playerdata",
75
+ "position": {"x": 10, "y": 20, "rotation": 0},
76
+ "health": 100
77
+ })
78
+
79
+ msg = mgr.safe_receive()
80
+ if msg:
81
+ # handle decoded packet
82
+ pass
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Architecture Overview
88
+
89
+ - The system follows a **client-server UDP model**:
90
+ - One host acts as the server
91
+ - Multiple clients can connect and send packets
92
+ - The server broadcasts received packets to all connected clients
93
+ - Each client sends state updates to the server
94
+
95
+ ### Networking Flow
96
+
97
+ 1. Client sends packet → Server receives
98
+ 2. Server updates peer registry (heartbeats)
99
+ 3. Server broadcasts packet to all other clients
100
+ 4. Clients receive and process updates via `incoming_queue`
101
+
102
+ ### Design Notes
103
+
104
+ - UDP is used for low latency (no built-in reliability)
105
+ - Packet delivery is not guaranteed
106
+ - Ordering is not enforced
107
+ - Designed for frequent state updates (position, actions, etc.)
108
+
109
+ ---
110
+
111
+ ## API Reference
112
+
113
+ ### NetworkManager
114
+
115
+ #### Constructor
116
+ ```py
117
+ NetworkManager(
118
+ is_host=True,
119
+ peer_ip=None,
120
+ base_port=5000,
121
+ use_compression=True,
122
+ use_encryption=False,
123
+ encryption_key=DEFAULT_KEY,
124
+ encryption_salt=DEFAULT_SALT
125
+ )
126
+ ```
127
+
128
+ #### Methods
129
+
130
+ - `start()`
131
+ Starts background receive and peer cleanup threads.
132
+
133
+ - `safe_send(data: dict)`
134
+ Sends a packet to all connected peers (server) or to the host (client).
135
+
136
+ - `safe_receive() -> dict | None`
137
+ Non-blocking receive from internal queue.
138
+
139
+ - `close()`
140
+ Stops networking and closes socket.
141
+
142
+ ---
143
+
144
+ ### Packet Pipeline
145
+
146
+ Outgoing:
147
+ ```
148
+ dict → JSON → compress (optional) → encrypt (optional) → UDP packet
149
+ ```
150
+
151
+ Incoming:
152
+ ```
153
+ UDP packet → decrypt → decompress → JSON decode → dict
154
+ ```
155
+
156
+ ---
157
+
158
+ ### EncryptionManager
159
+
160
+ - Uses PBKDF2-derived key + Fernet encryption
161
+ - Requires identical key/salt on both sides
162
+ - Encrypt/decrypt operates on bytes
163
+
164
+ ---
165
+
166
+ ## Packet Examples
167
+
168
+ Common patterns used in games:
169
+
170
+ ### Player State
171
+ ```json
172
+ {
173
+ "type": "playerdata",
174
+ "position": {
175
+ "x": 640,
176
+ "y": 360,
177
+ "rotation": 1.57
178
+ },
179
+ "health": 100
180
+ }
181
+ ```
182
+
183
+ ### Action Event
184
+ ```json
185
+ {
186
+ "type": "shoot",
187
+ "pos": [x, y],
188
+ "vec": [vx, vy],
189
+ "timestamp": 1234567890.0
190
+ }
191
+ ```
192
+
193
+ ### Disconnect
194
+ ```json
195
+ {
196
+ "type": "quit"
197
+ }
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Important Constants
203
+
204
+ Defined in `NetworkConstants.py`:
205
+
206
+ - `BUFFER_SIZE = 4096`
207
+ - `DEFAULT_PORT = 5000`
208
+ - `POSITIONUPDATEINTERVAL = 1/30`
209
+ - `DEFAULT_KEY`, `DEFAULT_SALT`
210
+
211
+ ---
212
+
213
+ ## Troubleshooting & Tips
214
+
215
+ - If bind fails, ensure the port is not in use
216
+ - If packets are missing, remember UDP does not guarantee delivery
217
+ - If encryption fails, ensure:
218
+ - same key
219
+ - same salt
220
+ - same `use_encryption` setting
221
+ - Keep packets small (UDP safe size ~1–1.5KB recommended)
222
+
223
+ ---
224
+
225
+ ## Security Notes
226
+
227
+ - Default keys are for development only
228
+ - Always generate secure keys for real applications
229
+ - Treat UDP as **insecure transport unless encrypted**
230
+
231
+ ---
232
+
233
+ ## Contributing
234
+
235
+ Contributions welcome:
236
+ - new packet utilities
237
+ - performance improvements
238
+ - reliability features (ordering, ack system)
239
+ - better debugging/logging tools
240
+
241
+ ---
242
+
243
+ ## License
244
+
245
+ MIT License — see [LICENSE](../LICENSE)
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "multiplayerlib"
3
+ version = "0.0.1"
4
+ description = "MultiplayerLib - A lightweight Python multiplayer networking library"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "cryptography==50.0.1",
8
+ "orjson==3.12.0",
9
+ ]
10
+ readme = "README.md"
11
+ authors = [
12
+ {name="griffingreat1"},
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["setuptools>=61.0"]
17
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/multiplayerlib/EncryptionManager.py
5
+ src/multiplayerlib/NetworkConstants.py
6
+ src/multiplayerlib/NetworkLib.py
7
+ src/multiplayerlib/__init__.py
8
+ src/multiplayerlib.egg-info/PKG-INFO
9
+ src/multiplayerlib.egg-info/SOURCES.txt
10
+ src/multiplayerlib.egg-info/dependency_links.txt
11
+ src/multiplayerlib.egg-info/requires.txt
12
+ src/multiplayerlib.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ cryptography==50.0.1
2
+ orjson==3.12.0
@@ -0,0 +1 @@
1
+ multiplayerlib