Added better Cleint Interpolation and made synced mouse positions
This commit is contained in:
parent
ca656d1ca8
commit
3aed5da246
216
client.py
216
client.py
@ -1,6 +1,6 @@
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import pickle
|
||||
import pygame
|
||||
import sys
|
||||
import time
|
||||
@ -8,64 +8,88 @@ import time
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 65432
|
||||
MOVE_SPEED = 200 # pixels per second
|
||||
FAKE_LATENCY = 0.1 # seconds of artificial latency for both sending and receiving
|
||||
|
||||
class Event:
|
||||
def __init__(self, name, payload):
|
||||
self.name = name
|
||||
self.payload = payload
|
||||
|
||||
def recvall(sock, n):
|
||||
data = b''
|
||||
while len(data) < n:
|
||||
packet = sock.recv(n - len(data))
|
||||
if not packet:
|
||||
return None
|
||||
data += packet
|
||||
return data
|
||||
|
||||
def send_event(sock, event):
|
||||
data = pickle.dumps(event)
|
||||
length = len(data)
|
||||
sock.sendall(length.to_bytes(4, byteorder='big') + data)
|
||||
|
||||
def recv_event(sock):
|
||||
raw_len = recvall(sock, 4)
|
||||
if not raw_len:
|
||||
return None
|
||||
msg_len = int.from_bytes(raw_len, byteorder='big')
|
||||
data = recvall(sock, msg_len)
|
||||
return pickle.loads(data)
|
||||
|
||||
# Global state
|
||||
players = {} # Server-authoritative state: client_id -> {"username", "x", "y"}
|
||||
players = {} # Server-authoritative state: client_id -> {"username", "x", "y", "mouse_x", "mouse_y"}
|
||||
my_client_id = None # Our unique client id from the server
|
||||
|
||||
# Local predicted state for the local player.
|
||||
predicted_state = {"x": 100, "y": 100, "keys": set(), "username": "Guest"}
|
||||
|
||||
ping = 0 # measured ping in seconds
|
||||
predicted_state = {
|
||||
"x": 100, "y": 100,
|
||||
"keys": set(),
|
||||
"username": "Guest",
|
||||
"mouse_x": 0,
|
||||
"mouse_y": 0
|
||||
}
|
||||
ping = 0 # measured ping in seconds
|
||||
server_details = {} # Updated from server_info and state_update events
|
||||
|
||||
def listen_to_server(sock):
|
||||
"""
|
||||
Listen for incoming messages from the server and update local state.
|
||||
Also performs reconciliation by snapping to the server state when no keys are pressed.
|
||||
Simulates network latency on incoming messages.
|
||||
"""
|
||||
global my_client_id, players, predicted_state, ping
|
||||
file_obj = sock.makefile('r')
|
||||
global my_client_id, players, predicted_state, ping, server_details
|
||||
while True:
|
||||
try:
|
||||
line = file_obj.readline()
|
||||
if not line:
|
||||
event = recv_event(sock)
|
||||
if event is None:
|
||||
break
|
||||
# Simulate fake latency for incoming messages.
|
||||
time.sleep(FAKE_LATENCY)
|
||||
msg = json.loads(line)
|
||||
event_name = msg.get('event')
|
||||
payload = msg.get('payload')
|
||||
if event_name == "self_id":
|
||||
if event.name == "self_id":
|
||||
if my_client_id is None:
|
||||
my_client_id = payload.get("client_id")
|
||||
my_client_id = event.payload.get("client_id")
|
||||
print(f"[INFO] My client ID: {my_client_id}")
|
||||
elif event_name == "client_connect":
|
||||
print(f"[INFO] Client connected: {payload.get('client_id')}")
|
||||
elif event_name == "client_disconnect":
|
||||
client_id = payload.get('client_id')
|
||||
print(f"[INFO] Client disconnected: {client_id}")
|
||||
if client_id in players:
|
||||
del players[client_id]
|
||||
elif event_name == "state_update":
|
||||
# Update our players dictionary from the server.
|
||||
players = payload.get("players", {})
|
||||
# If we have an update for our own player, reconcile.
|
||||
elif event.name == "server_info":
|
||||
server_details = event.payload
|
||||
print(f"[INFO] Received server info: {server_details}")
|
||||
elif event.name == "client_connect":
|
||||
print(f"[INFO] Client connected: {event.payload.get('client_id')}")
|
||||
elif event.name == "client_disconnect":
|
||||
cid = event.payload.get("client_id")
|
||||
print(f"[INFO] Client disconnected: {cid}")
|
||||
if cid in players:
|
||||
del players[cid]
|
||||
elif event.name == "state_update":
|
||||
players = event.payload.get("players", {})
|
||||
# Update dynamic server details.
|
||||
if "total_users" in event.payload:
|
||||
server_details["total_users"] = event.payload["total_users"]
|
||||
if "max_users" in event.payload:
|
||||
server_details["max_users"] = event.payload["max_users"]
|
||||
if my_client_id and my_client_id in players:
|
||||
server_x = players[my_client_id].get("x", 100)
|
||||
server_y = players[my_client_id].get("y", 100)
|
||||
tickrate = server_details.get("tickrate", 64)
|
||||
correction_factor = 0.1 * (64 / tickrate)
|
||||
if not predicted_state["keys"]:
|
||||
# Snap immediately if no movement is occurring.
|
||||
predicted_state["x"] = server_x
|
||||
predicted_state["y"] = server_y
|
||||
else:
|
||||
# If keys are pressed, nudge the predicted state gradually.
|
||||
correction_factor = 0.1 # 10% correction per update while moving.
|
||||
predicted_state["x"] += (server_x - predicted_state["x"]) * correction_factor
|
||||
predicted_state["y"] += (server_y - predicted_state["y"]) * correction_factor
|
||||
elif event_name == "pong":
|
||||
sent_time = payload.get('timestamp')
|
||||
elif event.name == "pong":
|
||||
sent_time = event.payload.get("timestamp")
|
||||
if sent_time:
|
||||
round_trip = time.time() - sent_time
|
||||
ping = round_trip
|
||||
@ -74,21 +98,13 @@ def listen_to_server(sock):
|
||||
print(f"[ERROR] {e}")
|
||||
break
|
||||
|
||||
def send_message(sock, msg_dict):
|
||||
"""
|
||||
Send a JSON message to the server with fake latency.
|
||||
"""
|
||||
time.sleep(FAKE_LATENCY) # simulate network latency on outgoing messages
|
||||
message = json.dumps(msg_dict) + "\n"
|
||||
sock.sendall(message.encode())
|
||||
def send_message(sock, event):
|
||||
send_event(sock, event)
|
||||
|
||||
def ping_loop(sock):
|
||||
"""
|
||||
Periodically send ping messages to measure latency.
|
||||
"""
|
||||
while True:
|
||||
ts = time.time()
|
||||
send_message(sock, {'type': 'ping', 'timestamp': ts})
|
||||
send_message(sock, Event("ping", {"timestamp": ts}))
|
||||
time.sleep(1)
|
||||
|
||||
def main():
|
||||
@ -96,74 +112,72 @@ def main():
|
||||
username = input("Enter your username: ")
|
||||
predicted_state["username"] = username
|
||||
|
||||
# Connect to the server.
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((HOST, PORT))
|
||||
|
||||
# Start threads to listen to the server and to send pings.
|
||||
threading.Thread(target=listen_to_server, args=(sock,), daemon=True).start()
|
||||
threading.Thread(target=ping_loop, args=(sock,), daemon=True).start()
|
||||
|
||||
# Register for needed events.
|
||||
default_events = ["client_connect", "client_disconnect", "state_update"]
|
||||
for event in default_events:
|
||||
send_message(sock, {'type': 'register_event', 'event': event})
|
||||
print(f"[INFO] Registered for event: {event}")
|
||||
# Register for events.
|
||||
for event_name in ["client_connect", "client_disconnect", "state_update"]:
|
||||
send_message(sock, Event("register_event", {"event": event_name}))
|
||||
print(f"[INFO] Registered for event: {event_name}")
|
||||
|
||||
# Inform the server of our chosen username.
|
||||
send_message(sock, {'type': 'set_username', 'username': username})
|
||||
send_message(sock, Event("set_username", {"username": username}))
|
||||
|
||||
# Initialize Pygame.
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((800, 600))
|
||||
pygame.display.set_caption("Pygame Client - Multiplayer (Ghost vs Client)")
|
||||
pygame.display.set_caption("Pygame Client - Multiplayer (Real vs Fake)")
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
# Define colors.
|
||||
ghost_color = (0, 255, 0) # Green for the server (ghost) state.
|
||||
client_color = (0, 0, 255) # Blue for the client (predicted) state.
|
||||
other_color = (255, 0, 0) # Red for other players.
|
||||
|
||||
# Local set to track pressed keys.
|
||||
ghost_color = (0, 255, 0) # Ghost: server state (outlined)
|
||||
client_color = (0, 0, 255) # Client: predicted state (filled)
|
||||
other_color = (255, 0, 0) # Remote players
|
||||
remote_mouse_color = (255, 255, 0) # Color for remote mouse pointer (yellow)
|
||||
local_keys = set()
|
||||
|
||||
while True:
|
||||
dt = clock.tick(60) / 1000.0 # Frame time (aiming for 60 FPS)
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
dt = clock.tick(60) / 1000.0
|
||||
for ev in pygame.event.get():
|
||||
if ev.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
elif ev.type == pygame.KEYDOWN:
|
||||
key_name = None
|
||||
if event.key == pygame.K_LEFT:
|
||||
if ev.key == pygame.K_LEFT:
|
||||
key_name = "left"
|
||||
elif event.key == pygame.K_RIGHT:
|
||||
elif ev.key == pygame.K_RIGHT:
|
||||
key_name = "right"
|
||||
elif event.key == pygame.K_UP:
|
||||
elif ev.key == pygame.K_UP:
|
||||
key_name = "up"
|
||||
elif event.key == pygame.K_DOWN:
|
||||
elif ev.key == pygame.K_DOWN:
|
||||
key_name = "down"
|
||||
if key_name and key_name not in local_keys:
|
||||
local_keys.add(key_name)
|
||||
predicted_state["keys"].add(key_name)
|
||||
send_message(sock, {'type': 'keydown', 'key': key_name})
|
||||
elif event.type == pygame.KEYUP:
|
||||
send_message(sock, Event("keydown", {"key": key_name}))
|
||||
elif ev.type == pygame.KEYUP:
|
||||
key_name = None
|
||||
if event.key == pygame.K_LEFT:
|
||||
if ev.key == pygame.K_LEFT:
|
||||
key_name = "left"
|
||||
elif event.key == pygame.K_RIGHT:
|
||||
elif ev.key == pygame.K_RIGHT:
|
||||
key_name = "right"
|
||||
elif event.key == pygame.K_UP:
|
||||
elif ev.key == pygame.K_UP:
|
||||
key_name = "up"
|
||||
elif event.key == pygame.K_DOWN:
|
||||
elif ev.key == pygame.K_DOWN:
|
||||
key_name = "down"
|
||||
if key_name and key_name in local_keys:
|
||||
local_keys.remove(key_name)
|
||||
if key_name in predicted_state["keys"]:
|
||||
predicted_state["keys"].remove(key_name)
|
||||
send_message(sock, {'type': 'keyup', 'key': key_name})
|
||||
|
||||
# Update the predicted state based on locally pressed keys.
|
||||
send_message(sock, Event("keyup", {"key": key_name}))
|
||||
elif ev.type == pygame.MOUSEMOTION:
|
||||
mouse_x, mouse_y = ev.pos
|
||||
predicted_state["mouse_x"] = mouse_x
|
||||
predicted_state["mouse_y"] = mouse_y
|
||||
send_message(sock, Event("mouse_move", {"x": mouse_x, "y": mouse_y}))
|
||||
|
||||
# Update predicted state locally.
|
||||
dx, dy = 0, 0
|
||||
if "left" in predicted_state["keys"]:
|
||||
dx -= MOVE_SPEED * dt
|
||||
@ -176,17 +190,15 @@ def main():
|
||||
predicted_state["x"] += dx
|
||||
predicted_state["y"] += dy
|
||||
|
||||
# Get ghost (server) position without interpolation.
|
||||
# For our own player, the ghost (server) position is used as the "real" state.
|
||||
if my_client_id and my_client_id in players:
|
||||
ghost_x = players[my_client_id].get("x", 100)
|
||||
ghost_y = players[my_client_id].get("y", 100)
|
||||
else:
|
||||
ghost_x, ghost_y = predicted_state["x"], predicted_state["y"]
|
||||
|
||||
# Render the scene.
|
||||
screen.fill((0, 0, 0))
|
||||
|
||||
# Draw remote players (using server state).
|
||||
# Draw remote players (only real state).
|
||||
for cid, data in players.items():
|
||||
if cid == my_client_id:
|
||||
continue
|
||||
@ -198,15 +210,18 @@ def main():
|
||||
text_surface = font.render(uname, True, (255, 255, 255))
|
||||
text_rect = text_surface.get_rect(center=(x, y - 30))
|
||||
screen.blit(text_surface, text_rect)
|
||||
|
||||
# Draw the ghost (server-authoritative) position as an outlined circle.
|
||||
# Draw remote mouse pointer if available.
|
||||
if "mouse_x" in data and "mouse_y" in data:
|
||||
mx = int(data.get("mouse_x", 0))
|
||||
my = int(data.get("mouse_y", 0))
|
||||
pygame.draw.circle(screen, remote_mouse_color, (mx, my), 5)
|
||||
# Draw our ghost (server-authoritative) as an outlined circle.
|
||||
font = pygame.font.SysFont(None, 24)
|
||||
ghost_text = font.render("Ghost", True, (255, 255, 255))
|
||||
ghost_rect = ghost_text.get_rect(center=(int(ghost_x), int(ghost_y) - 30))
|
||||
pygame.draw.circle(screen, ghost_color, (int(ghost_x), int(ghost_y)), 20, 2)
|
||||
screen.blit(ghost_text, ghost_rect)
|
||||
|
||||
# Draw the client-predicted position as the actual player (filled circle).
|
||||
# Draw our client-predicted player as a filled circle.
|
||||
pred_x = int(predicted_state.get("x", 100))
|
||||
pred_y = int(predicted_state.get("y", 100))
|
||||
font = pygame.font.SysFont(None, 24)
|
||||
@ -214,12 +229,21 @@ def main():
|
||||
client_rect = client_text.get_rect(center=(pred_x, pred_y - 30))
|
||||
pygame.draw.circle(screen, client_color, (pred_x, pred_y), 20)
|
||||
screen.blit(client_text, client_rect)
|
||||
|
||||
# Display ping on the screen.
|
||||
# Display ping.
|
||||
ping_font = pygame.font.SysFont(None, 24)
|
||||
ping_surface = ping_font.render(f"Ping: {int(ping*1000)} ms", True, (255, 255, 0))
|
||||
screen.blit(ping_surface, (10, 10))
|
||||
|
||||
# Display HUD info: total users, client ID, and tickrate.
|
||||
hud_font = pygame.font.SysFont(None, 24)
|
||||
total_users = server_details.get("total_users", len(players))
|
||||
max_users = server_details.get("max_users", "?")
|
||||
tickrate = server_details.get("tickrate", 64)
|
||||
hud_text = hud_font.render(
|
||||
f"Users: {total_users}/{max_users} | Client ID: {my_client_id or 'N/A'} | Tickrate: {tickrate} tps",
|
||||
True,
|
||||
(255, 255, 255)
|
||||
)
|
||||
screen.blit(hud_text, (10, 40))
|
||||
pygame.display.flip()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
191
server.py
191
server.py
@ -1,78 +1,124 @@
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import pickle
|
||||
import time
|
||||
import uuid
|
||||
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 65432
|
||||
|
||||
# Global dictionaries for event subscriptions, client IDs, and game state.
|
||||
event_subscriptions = {} # event_name -> list of client sockets
|
||||
client_ids = {} # conn -> client_id
|
||||
client_states = {} # conn -> {"client_id", "username", "x", "y", "keys"}
|
||||
lock = threading.Lock()
|
||||
|
||||
MOVE_SPEED = 200 # pixels per second
|
||||
TICKRATE = 64 # ticks per second
|
||||
MAX_USERS = 100
|
||||
|
||||
class Event:
|
||||
def __init__(self, name, payload):
|
||||
self.name = name
|
||||
self.payload = payload
|
||||
|
||||
def recvall(sock, n):
|
||||
data = b''
|
||||
while len(data) < n:
|
||||
packet = sock.recv(n - len(data))
|
||||
if not packet:
|
||||
return None
|
||||
data += packet
|
||||
return data
|
||||
|
||||
def send_event(sock, event):
|
||||
data = pickle.dumps(event)
|
||||
length = len(data)
|
||||
sock.sendall(length.to_bytes(4, byteorder='big') + data)
|
||||
|
||||
def recv_event(sock):
|
||||
raw_len = recvall(sock, 4)
|
||||
if not raw_len:
|
||||
return None
|
||||
msg_len = int.from_bytes(raw_len, byteorder='big')
|
||||
data = recvall(sock, msg_len)
|
||||
return pickle.loads(data)
|
||||
|
||||
# Global dictionaries
|
||||
event_subscriptions = {} # event name -> list of client sockets
|
||||
client_ids = {} # conn -> client_id
|
||||
client_states = {} # conn -> {"client_id", "username", "x", "y", "keys", "mouse_x", "mouse_y"}
|
||||
lock = threading.Lock()
|
||||
|
||||
def handle_client(conn, addr):
|
||||
client_id = str(uuid.uuid4())
|
||||
with lock:
|
||||
client_ids[conn] = client_id
|
||||
# Initialize state: default username "Guest", starting position, and an empty set of pressed keys.
|
||||
client_states[conn] = {"client_id": client_id, "username": "Guest", "x": 100, "y": 100, "keys": set()}
|
||||
# Initialize state for new client.
|
||||
client_states[conn] = {
|
||||
"client_id": client_id,
|
||||
"username": "Guest",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"keys": set(),
|
||||
"mouse_x": 0,
|
||||
"mouse_y": 0
|
||||
}
|
||||
print(f"[NEW CONNECTION] {addr} connected as {client_id}")
|
||||
|
||||
# Send a dedicated "self_id" event so the client knows its unique id.
|
||||
try:
|
||||
msg = json.dumps({'event': 'self_id', 'payload': {'client_id': client_id}}) + "\n"
|
||||
conn.sendall(msg.encode())
|
||||
# Send self_id so the client knows its unique id.
|
||||
send_event(conn, Event("self_id", {"client_id": client_id}))
|
||||
# Send server info.
|
||||
server_info = {
|
||||
"tickrate": TICKRATE,
|
||||
"total_users": len(client_states),
|
||||
"max_users": MAX_USERS,
|
||||
"server_ip": HOST,
|
||||
"server_port": PORT,
|
||||
"server_name": "My Multiplayer Server"
|
||||
}
|
||||
send_event(conn, Event("server_info", server_info))
|
||||
except Exception as e:
|
||||
print(f"[ERROR] sending self_id: {e}")
|
||||
print(f"[ERROR] sending self_id/server_info: {e}")
|
||||
|
||||
# Broadcast that a new client connected.
|
||||
send_event("client_connect", {"client_id": client_id})
|
||||
broadcast_event("client_connect", {"client_id": client_id})
|
||||
|
||||
try:
|
||||
file_obj = conn.makefile('r')
|
||||
for line in file_obj:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
msg_type = msg.get('type')
|
||||
if msg_type == 'register_event':
|
||||
event_name = msg.get('event')
|
||||
with lock:
|
||||
event_subscriptions.setdefault(event_name, []).append(conn)
|
||||
print(f"[REGISTER] {client_id} subscribed to '{event_name}'")
|
||||
elif msg_type == 'set_username':
|
||||
username = msg.get('username', 'Guest')
|
||||
with lock:
|
||||
if conn in client_states:
|
||||
client_states[conn]['username'] = username
|
||||
print(f"[USERNAME] {client_id} set username to '{username}'")
|
||||
elif msg_type == 'keydown':
|
||||
key = msg.get('key')
|
||||
with lock:
|
||||
if conn in client_states:
|
||||
client_states[conn]['keys'].add(key)
|
||||
print(f"[KEYDOWN] {client_id} key '{key}' pressed")
|
||||
elif msg_type == 'keyup':
|
||||
key = msg.get('key')
|
||||
with lock:
|
||||
if conn in client_states and key in client_states[conn]['keys']:
|
||||
client_states[conn]['keys'].remove(key)
|
||||
print(f"[KEYUP] {client_id} key '{key}' released")
|
||||
elif msg_type == 'ping':
|
||||
# Respond immediately with a pong, echoing the timestamp.
|
||||
timestamp = msg.get('timestamp')
|
||||
response = json.dumps({'event': 'pong', 'payload': {'timestamp': timestamp}}) + "\n"
|
||||
conn.sendall(response.encode())
|
||||
# Additional message types can be handled here.
|
||||
except json.JSONDecodeError:
|
||||
print(f"[ERROR] Invalid JSON from {client_id}: {line}")
|
||||
while True:
|
||||
event = recv_event(conn)
|
||||
if event is None:
|
||||
break
|
||||
if event.name == "register_event":
|
||||
event_name = event.payload.get('event')
|
||||
with lock:
|
||||
event_subscriptions.setdefault(event_name, []).append(conn)
|
||||
print(f"[REGISTER] {client_id} subscribed to '{event_name}'")
|
||||
elif event.name == "set_username":
|
||||
username = event.payload.get('username', 'Guest')
|
||||
with lock:
|
||||
if conn in client_states:
|
||||
client_states[conn]['username'] = username
|
||||
print(f"[USERNAME] {client_id} set username to '{username}'")
|
||||
elif event.name == "keydown":
|
||||
key = event.payload.get('key')
|
||||
with lock:
|
||||
if conn in client_states:
|
||||
client_states[conn]['keys'].add(key)
|
||||
print(f"[KEYDOWN] {client_id} key '{key}' pressed")
|
||||
elif event.name == "keyup":
|
||||
key = event.payload.get('key')
|
||||
with lock:
|
||||
if conn in client_states and key in client_states[conn]['keys']:
|
||||
client_states[conn]['keys'].remove(key)
|
||||
print(f"[KEYUP] {client_id} key '{key}' released")
|
||||
elif event.name == "mouse_move":
|
||||
x = event.payload.get('x', 0)
|
||||
y = event.payload.get('y', 0)
|
||||
with lock:
|
||||
if conn in client_states:
|
||||
client_states[conn]['mouse_x'] = x
|
||||
client_states[conn]['mouse_y'] = y
|
||||
# Optionally log mouse moves.
|
||||
# print(f"[MOUSE_MOVE] {client_id} moved mouse to ({x}, {y})")
|
||||
elif event.name == "ping":
|
||||
timestamp = event.payload.get('timestamp')
|
||||
send_event(conn, Event("pong", {"timestamp": timestamp}))
|
||||
# You can handle additional events here.
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Exception with {client_id}: {e}")
|
||||
finally:
|
||||
@ -83,34 +129,26 @@ def handle_client(conn, addr):
|
||||
client_ids.pop(conn, None)
|
||||
client_states.pop(conn, None)
|
||||
print(f"[DISCONNECT] {client_id} disconnected.")
|
||||
send_event("client_disconnect", {"client_id": client_id})
|
||||
broadcast_event("client_disconnect", {"client_id": client_id})
|
||||
conn.close()
|
||||
|
||||
def send_event(event_name, payload):
|
||||
"""
|
||||
Broadcast an event to all clients subscribed to the given event.
|
||||
"""
|
||||
message = json.dumps({'event': event_name, 'payload': payload}) + "\n"
|
||||
def broadcast_event(event_name, payload):
|
||||
event = Event(event_name, payload)
|
||||
with lock:
|
||||
receivers = event_subscriptions.get(event_name, [])
|
||||
for client in receivers[:]: # iterate over a copy to allow removals
|
||||
for client in receivers[:]:
|
||||
try:
|
||||
client.sendall(message.encode())
|
||||
send_event(client, event)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to send to client, removing: {e}")
|
||||
receivers.remove(client)
|
||||
|
||||
def game_loop():
|
||||
"""
|
||||
Main game loop: update each client’s position based on currently pressed keys,
|
||||
and broadcast the overall game state (including server time) to all clients.
|
||||
"""
|
||||
dt = 0.1 #100 ms;
|
||||
|
||||
dt = 1 / TICKRATE # Tick interval
|
||||
while True:
|
||||
time.sleep(dt)
|
||||
with lock:
|
||||
# Update each client's position.
|
||||
# Update each client's position based on pressed keys.
|
||||
for state in client_states.values():
|
||||
dx, dy = 0, 0
|
||||
if 'left' in state['keys']:
|
||||
@ -123,16 +161,23 @@ def game_loop():
|
||||
dy += MOVE_SPEED * dt
|
||||
state['x'] += dx
|
||||
state['y'] += dy
|
||||
# Build payload: include current server time and each client’s state.
|
||||
# Build payload for state_update.
|
||||
players_payload = {}
|
||||
for state in client_states.values():
|
||||
players_payload[state['client_id']] = {
|
||||
"username": state['username'],
|
||||
"x": state['x'],
|
||||
"y": state['y']
|
||||
"y": state['y'],
|
||||
"mouse_x": state.get('mouse_x', 0),
|
||||
"mouse_y": state.get('mouse_y', 0)
|
||||
}
|
||||
payload = {"server_time": time.time(), "players": players_payload}
|
||||
send_event("state_update", payload)
|
||||
total_users = len(client_states)
|
||||
payload = {
|
||||
"players": players_payload,
|
||||
"total_users": total_users,
|
||||
"max_users": MAX_USERS
|
||||
}
|
||||
broadcast_event("state_update", payload)
|
||||
|
||||
def start_server():
|
||||
print("[STARTING] Server is starting...")
|
||||
|
Loading…
Reference in New Issue
Block a user