Created
June 29, 2026 16:08
-
-
Save EncodeTheCode/fc261a1cc7e308b88688dcb9ecfd0993 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 websockets | |
| import json | |
| import time | |
| # ========================= | |
| # CONFIG | |
| # ========================= | |
| PORT = 4445 | |
| TICK_RATE = 20 | |
| TIMEOUT = 5.0 | |
| # ========================= | |
| # PLAYER CLASS | |
| # ========================= | |
| class Player: | |
| def __init__(self, ws): | |
| self.ws = ws | |
| self.user_id = None | |
| self.x = self.y = self.z = 0.0 | |
| self.px = self.py = self.pz = 0.0 | |
| self.health = 100 | |
| self.armor = 0 | |
| self.weapon_id = 0 | |
| self.primary_ammo = 30 | |
| self.secondary_ammo = 90 | |
| self.walking = False | |
| self.running = False | |
| self.ping = 0.0 | |
| self.last_seen = time.time() | |
| self.authenticated = False | |
| def serialize(self): | |
| return { | |
| "user_id": self.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: | |
| def __init__(self): | |
| self.players = {} # websocket -> Player | |
| # ------------------------- | |
| # AUTH | |
| # ------------------------- | |
| async def handle_auth(self, player, msg): | |
| player.user_id = msg.get("user_id") | |
| token = msg.get("token") | |
| if player.user_id and len(token) > 5: | |
| player.authenticated = True | |
| await player.ws.send(json.dumps({ | |
| "type": "auth_ok", | |
| "user_id": player.user_id | |
| })) | |
| # ------------------------- | |
| # HANDLE MESSAGE | |
| # ------------------------- | |
| async def handle_message(self, ws, msg): | |
| player = self.players.get(ws) | |
| if not player: | |
| player = Player(ws) | |
| self.players[ws] = player | |
| player.last_seen = time.time() | |
| mtype = msg.get("type") | |
| # AUTH FIRST | |
| if not player.authenticated: | |
| if mtype == "auth": | |
| await self.handle_auth(player, msg) | |
| return | |
| # --------------------- | |
| # MOVEMENT | |
| # --------------------- | |
| if mtype == "update": | |
| player.px, player.py, player.pz = player.x, player.y, player.z | |
| player.x += float(msg.get("dx", 0)) | |
| player.y += float(msg.get("dy", 0)) | |
| player.z += float(msg.get("dz", 0)) | |
| player.walking = msg.get("walking", False) | |
| player.running = msg.get("running", False) | |
| # --------------------- | |
| # COMBAT | |
| # --------------------- | |
| elif mtype == "combat": | |
| player.weapon_id = msg.get("weapon_id", player.weapon_id) | |
| player.primary_ammo = msg.get("primary_ammo", player.primary_ammo) | |
| player.secondary_ammo = msg.get("secondary_ammo", player.secondary_ammo) | |
| # --------------------- | |
| # PING | |
| # --------------------- | |
| elif mtype == "ping": | |
| client_time = msg.get("t", time.time()) | |
| player.ping = time.time() - client_time | |
| # ------------------------- | |
| # WORLD STATE | |
| # ------------------------- | |
| def world(self): | |
| return { | |
| "type": "world", | |
| "players": { | |
| id(p.ws): p.serialize() | |
| for p in self.players.values() | |
| } | |
| } | |
| # ------------------------- | |
| # CLEANUP | |
| # ------------------------- | |
| def cleanup(self): | |
| now = time.time() | |
| dead = [] | |
| for ws, p in self.players.items(): | |
| if now - p.last_seen > TIMEOUT: | |
| dead.append(ws) | |
| for ws in dead: | |
| del self.players[ws] | |
| # ------------------------- | |
| # BROADCAST LOOP | |
| # ------------------------- | |
| async def broadcast(self): | |
| interval = 1 / TICK_RATE | |
| while True: | |
| self.cleanup() | |
| packet = json.dumps(self.world()) | |
| dead = [] | |
| for ws, player in self.players.items(): | |
| try: | |
| await ws.send(packet) | |
| except: | |
| dead.append(ws) | |
| for ws in dead: | |
| self.players.pop(ws, None) | |
| await asyncio.sleep(interval) | |
| # ========================= | |
| # SERVER ENTRY | |
| # ========================= | |
| server = GameServer() | |
| async def handler(ws): | |
| try: | |
| async for message in ws: | |
| msg = json.loads(message) | |
| await server.handle_message(ws, msg) | |
| except: | |
| pass | |
| finally: | |
| server.players.pop(ws, None) | |
| async def main(): | |
| async with websockets.serve(handler, "0.0.0.0", PORT): | |
| print(f"WebSocket server running on ws://0.0.0.0:{PORT}") | |
| await server.broadcast() | |
| asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment