Created
June 29, 2026 16:02
-
-
Save EncodeTheCode/714a984f4a8fb805ca235771c5692592 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import json | |
| import time | |
| # ========================= | |
| # CONFIG | |
| # ========================= | |
| HOST = "0.0.0.0" | |
| PORT = 4445 | |
| TICK_RATE = 20 | |
| PLAYER_TIMEOUT = 5.0 | |
| MAX_PACKET_SIZE = 1200 | |
| # ========================= | |
| # PLAYER CLASS | |
| # ========================= | |
| class Player: | |
| def __init__(self, addr): | |
| self.addr = addr | |
| # ------------------------- | |
| # SECURITY / IDENTITY | |
| # ------------------------- | |
| self.ip = addr[0] # SERVER ONLY (DO NOT BROADCAST) | |
| self.special_user_id = None # set after authentication | |
| self.authenticated = False | |
| # ------------------------- | |
| # NETWORK | |
| # ------------------------- | |
| self.last_seen = time.time() | |
| self.ping = 0.0 | |
| # ------------------------- | |
| # POSITION | |
| # ------------------------- | |
| self.x = self.y = self.z = 0.0 | |
| self.px = self.py = self.pz = 0.0 | |
| # ------------------------- | |
| # GAMEPLAY | |
| # ------------------------- | |
| self.health = 100 | |
| self.armor = 0 | |
| self.weapon_id = 0 | |
| self.primary_ammo = 30 | |
| self.secondary_ammo = 90 | |
| self.walking = False | |
| self.running = False | |
| # ========================= | |
| # AUTH HANDSHAKE | |
| # ========================= | |
| def authenticate(self, msg): | |
| """ | |
| Client must send: | |
| { | |
| "type": "auth", | |
| "user_id": "abc123", | |
| "token": "secure_token" | |
| } | |
| """ | |
| user_id = msg.get("user_id") | |
| token = msg.get("token") | |
| # Replace this with real auth (DB/JWT/etc.) | |
| if isinstance(user_id, str) and len(token) > 5: | |
| self.special_user_id = user_id | |
| self.authenticated = True | |
| return True | |
| return False | |
| # ========================= | |
| # MOVEMENT | |
| # ========================= | |
| def apply_movement(self, msg): | |
| self.px, self.py, self.pz = self.x, self.y, self.z | |
| self.x += float(msg.get("dx", 0)) | |
| self.y += float(msg.get("dy", 0)) | |
| self.z += float(msg.get("dz", 0)) | |
| self.walking = bool(msg.get("walking", False)) | |
| self.running = bool(msg.get("running", False)) | |
| # ========================= | |
| # COMBAT | |
| # ========================= | |
| def apply_combat(self, msg): | |
| self.weapon_id = int(msg.get("weapon_id", self.weapon_id)) | |
| self.primary_ammo = int(msg.get("primary_ammo", self.primary_ammo)) | |
| self.secondary_ammo = int(msg.get("secondary_ammo", self.secondary_ammo)) | |
| # ========================= | |
| # DAMAGE | |
| # ========================= | |
| def apply_damage(self, msg): | |
| dmg = int(msg.get("amount", 0)) | |
| absorbed = min(self.armor, dmg) | |
| self.armor -= absorbed | |
| dmg -= absorbed | |
| self.health = max(0, self.health - dmg) | |
| # ========================= | |
| # PING | |
| # ========================= | |
| def apply_ping(self, msg): | |
| client_time = float(msg.get("t", time.time())) | |
| self.ping = time.time() - client_time | |
| # ========================= | |
| # TOUCH (activity update) | |
| # ========================= | |
| def touch(self): | |
| self.last_seen = time.time() | |
| # ========================= | |
| # SAFE SERIALIZATION | |
| # (IMPORTANT: NO IP LEAK) | |
| # ========================= | |
| def serialize(self): | |
| return { | |
| "user_id": self.special_user_id, | |
| "x": self.x, | |
| "y": self.y, | |
| "z": self.z, | |
| "px": self.px, | |
| "py": self.py, | |
| "pz": self.pz, | |
| "health": self.health, | |
| "armor": self.armor, | |
| "weapon_id": self.weapon_id, | |
| "primary_ammo": self.primary_ammo, | |
| "secondary_ammo": self.secondary_ammo, | |
| "walking": self.walking, | |
| "running": self.running, | |
| "ping": round(self.ping, 3) | |
| } | |
| # ========================= | |
| # GAME SERVER | |
| # ========================= | |
| class GameServer(asyncio.DatagramProtocol): | |
| def __init__(self): | |
| self.players = {} | |
| # ------------------------- | |
| # GET OR CREATE PLAYER | |
| # ------------------------- | |
| def get_player(self, addr): | |
| if addr not in self.players: | |
| self.players[addr] = Player(addr) | |
| return self.players[addr] | |
| # ------------------------- | |
| # PARSE PACKET | |
| # ------------------------- | |
| def parse(self, data): | |
| if len(data) > MAX_PACKET_SIZE: | |
| return None | |
| try: | |
| return json.loads(data) | |
| except: | |
| return None | |
| # ------------------------- | |
| # HANDLE PACKETS | |
| # ------------------------- | |
| def datagram_received(self, data, addr): | |
| msg = self.parse(data) | |
| if not msg: | |
| return | |
| player = self.get_player(addr) | |
| player.touch() | |
| mtype = msg.get("type") | |
| # --------------------- | |
| # AUTH REQUIRED FIRST | |
| # --------------------- | |
| if not player.authenticated: | |
| if mtype == "auth": | |
| player.authenticate(msg) | |
| return | |
| # --------------------- | |
| # GAMEPLAY | |
| # --------------------- | |
| if mtype == "update": | |
| player.apply_movement(msg) | |
| elif mtype == "combat": | |
| player.apply_combat(msg) | |
| elif mtype == "damage": | |
| player.apply_damage(msg) | |
| elif mtype == "ping": | |
| player.apply_ping(msg) | |
| # ------------------------- | |
| # CLEANUP DISCONNECTED PLAYERS | |
| # ------------------------- | |
| def cleanup(self): | |
| now = time.time() | |
| dead = [] | |
| for addr, p in self.players.items(): | |
| if now - p.last_seen > PLAYER_TIMEOUT: | |
| dead.append(addr) | |
| for d in dead: | |
| del self.players[d] | |
| # ------------------------- | |
| # WORLD SNAPSHOT | |
| # ------------------------- | |
| def world(self): | |
| return { | |
| "type": "world", | |
| "players": { | |
| str(addr): p.serialize() | |
| for addr, p in self.players.items() | |
| } | |
| } | |
| # ========================= | |
| # BROADCAST LOOP | |
| # ========================= | |
| async def broadcast(server): | |
| interval = 1.0 / TICK_RATE | |
| while True: | |
| start = time.time() | |
| server.cleanup() | |
| packet = json.dumps(server.world()).encode() | |
| dead = [] | |
| for addr in server.players: | |
| try: | |
| server.transport.sendto(packet, addr) | |
| except: | |
| dead.append(addr) | |
| for d in dead: | |
| server.players.pop(d, None) | |
| elapsed = time.time() - start | |
| await asyncio.sleep(max(0, interval - elapsed)) | |
| # ========================= | |
| # MAIN | |
| # ========================= | |
| async def main(): | |
| loop = asyncio.get_running_loop() | |
| server = GameServer() | |
| transport, _ = await loop.create_datagram_endpoint( | |
| lambda: server, | |
| local_addr=(HOST, PORT) | |
| ) | |
| server.transport = transport | |
| print(f"Secure multiplayer server running on {HOST}:{PORT}") | |
| await broadcast(server) | |
| asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment