Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created June 29, 2026 16:02
Show Gist options
  • Select an option

  • Save EncodeTheCode/714a984f4a8fb805ca235771c5692592 to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/714a984f4a8fb805ca235771c5692592 to your computer and use it in GitHub Desktop.
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