diff --git a/server/__pycache__/block.cpython-311.pyc b/server/__pycache__/block.cpython-311.pyc new file mode 100644 index 0000000..9b4b878 Binary files /dev/null and b/server/__pycache__/block.cpython-311.pyc differ diff --git a/server/__pycache__/config.cpython-311.pyc b/server/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000..2f4b86e Binary files /dev/null and b/server/__pycache__/config.cpython-311.pyc differ diff --git a/server/__pycache__/main.cpython-311.pyc b/server/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..fecc794 Binary files /dev/null and b/server/__pycache__/main.cpython-311.pyc differ diff --git a/server/__pycache__/network.cpython-311.pyc b/server/__pycache__/network.cpython-311.pyc new file mode 100644 index 0000000..763cf31 Binary files /dev/null and b/server/__pycache__/network.cpython-311.pyc differ diff --git a/server/__pycache__/player.cpython-311.pyc b/server/__pycache__/player.cpython-311.pyc new file mode 100644 index 0000000..ee8a16e Binary files /dev/null and b/server/__pycache__/player.cpython-311.pyc differ diff --git a/server/block.py b/server/block.py new file mode 100644 index 0000000..d49c593 --- /dev/null +++ b/server/block.py @@ -0,0 +1,35 @@ +from config import SPAWN + +class Block: + def __init__(self, x, y, block_type, color, collides): + self.x = x + self.y = y + self.type = block_type + self.color = color + self.collides = collides + + async def tick(self): + pass + + async def render(self): + pass + + async def onCollide(self, player, x, y): + if x != 0: player.vel_x = self.x + x - player.x + if y != 0: player.vel_y = self.y + y - player.y + + if x != 0 or y != 0: # special blocks + if self.type == "jump_boost": + player.setGravitySpeed(1.25) + player.setJumpSpeed(5) + player.on_ground = True + elif player.jump_speed != 2: + player.setGravitySpeed(0.5) + player.setJumpSpeed(2) + + if self.type == "killer": + await player.setPos(*SPAWN) + await player.sendToPlayers() + + def toStatement(self, add=True): + return f"B1{self.x},{self.y},{int(self.collides)},{self.type},{self.color}" if add else f"B0{self.x},{self.y}" \ No newline at end of file diff --git a/server/config.py b/server/config.py new file mode 100644 index 0000000..a2447d1 --- /dev/null +++ b/server/config.py @@ -0,0 +1,16 @@ +import sys + + +HOST,PORT = sys.argv[1].split(":") +PORT = int(PORT) + +COLORS = ["#d22", "#2d2", "#22d", "#dd2", "#2dd", "#d2d", "#ddd"] +SPAWN = (0, 0) + +REACH_DISTANCE = 15 + +BLOCK_TYPES = { + "normal": "#555", + "jump_boost": "#2d2", + "killer": "#d22", +} \ No newline at end of file diff --git a/server/main.py b/server/main.py index 7c66794..fd3c16c 100644 --- a/server/main.py +++ b/server/main.py @@ -1,190 +1,9 @@ -from websockets.server import serve, ServerConnection -import random, sys, asyncio, time -class Block: - def __init__(self, x, y, block_type, color, collides): - self.x = x - self.y = y - self.type = block_type - self.color = color - self.collides = collides - - async def tick(self): - pass - - async def render(self): - pass - - async def onCollide(self, player, x, y): - if x != 0: player.vel_x = self.x + x - player.x - if y != 0: player.vel_y = self.y + y - player.y - - if x != 0 or y != 0: # special blocks - if self.type == "jump_boost": - player.jump_speed = 5 - player.gravity_speed = 1.25 - player.on_ground = True - await writePacket(player.websocket, "S", ["J", "5"]) - await writePacket(player.websocket, "S", ["G", "1.25"]) - elif player.jump_speed != 2: - player.jump_speed = 2 - player.gravity_speed = 0.5 - await writePacket(player.websocket, "S", ["J", "2"]) - await writePacket(player.websocket, "S", ["G", "0.5"]) - - if self.type == "killer": - await player.setPos(*SPAWN) - await player.sendToPlayers() - - def toStatement(self, add=True): - return f"B1{self.x},{self.y},{int(self.collides)},{self.type},{self.color}" if add else f"B0{self.x},{self.y}" - -class Player(Block): - def __init__(self, websocket, x=None, y=None, name=None, color=None, vel_x=None, vel_y=None): - super().__init__(x, y, None, color, True) - self.x = x - self.y = y - - self.name = name - self.color = color - self.vel_x = vel_x - self.vel_y = vel_y - - self.websocket = websocket - - self.controls_x = 0 - self.controls_jump = False - - self.on_ground = False - - self.walk_speed = 1 - self.jump_speed = 2 - self.gravity_speed = 0.5 - - self.last_ - - async def setWalkSpeed(self, speed): - await writePacket(self.websocket, "S", ["W", str(speed)]) - self.walk_speed = speed - - async def setGravitySpeed(self, speed): - await writePacket(self.websocket, "S", ["G", str(speed)]) - self.gravity_speed = speed - - async def setJumpSpeed(self, speed): - await writePacket(self.websocket, "S", ["J", str(speed)]) - self.jump_speed = speed - - async def sendName(self, name): - await writePacket(self.websocket, "N", [name]) - - async def setName(self, name): - self.name = name - await self.sendName(name) - - async def setColor(self, color): - self.color = color - await writePacket(self.websocket, "C", [color]) - - async def setVel(self, x, y): - if x == self.vel_x and y == self.vel_y: return - - self.vel_x = x - self.vel_y = y - - await self.sendVel(x, y) - - async def setPos(self, x, y): - if x == self.x and y == self.y: return - - self.x = x - self.y = y - - await self.sendPos(x, y) - - async def sendVel(self, x, y): - await writePacket(self.websocket, "V", [str(x), str(y)]) - - async def sendPos(self, x, y): - await writePacket(self.websocket, "P", [str(x), str(y)]) - - async def sendMessage(self, message): - await writePacket(self.websocket, "M", message.split("\n")) - - async def sendWorld(self, statements): - if len(statements) == 0: return - await writePacket(self.websocket, "W", statements) - - async def sendBlockTypes(self, types): - await writePacket(self.websocket, "B", types) - - async def sendToPlayers(self): - for p in getPlayers(): - if p != self: - await p.sendWorld([self.toStatement()]) - - async def tick(self): - self.x = round(self.x * 100) / 100 - self.y = round(self.y * 100) / 100 - self.vel_x = round(self.vel_x * 100) / 100 - self.vel_y = round(self.vel_y * 100) / 100 - - if not self.on_ground: - self.vel_y -= self.gravity_speed - - await self.collide() - - async def collide(self): - global WORLD - - self.on_ground = False - - for block in WORLD: - if not block.collides: continue - if block == self: continue - - collide_x = 0 - collide_y = 0 - - if self.x > block.x-1 and self.x < block.x+1: - if self.y > block.y and self.y + self.vel_y - 1 < block.y: - self.on_ground = True - collide_y = 1 - if self.y < block.y and self.y + self.vel_y > block.y - 1: - collide_y = -1 - - if self.y > block.y-1 and self.y < block.y+1: - if self.x > block.x and self.x + self.vel_x - 1 < block.x: - collide_x = 1 - if self.x < block.x and self.x + self.vel_x > block.x - 1: - collide_x = -1 - - await block.onCollide(self, collide_x, collide_y) - - async def onCollide(self, player, x, y): - await super().onCollide(player, x, y) - # if x != 0: - # player.vel_x *= 0.5 - # self.vel_x = player.vel_x - # if y != 0: - # player.vel_y *= 0.5 - # self.vel_y = player.vel_y - # pass - - async def render(self): - self.vel_x *= 0.5 - self.vel_y *= 0.5 - self.x += self.vel_x - self.y += self.vel_y - # await self.setVel(self.vel_x * 0.5, self.vel_y * 0.5) - # await self.setPos(self.x + self.vel_x, self.y + self.vel_y) - return self.vel_x != 0 or self.vel_y != 0 - - async def keepAlive(self): - await writePacket(self.websocket, "R", [str(self.x), str(self.y), str(self.vel_x), str(self.vel_y)]) - - def toStatement(self, add=True): - return f"P1{self.name},{self.x},{self.y},{self.vel_x},{self.vel_y},{self.color}" if add else f"P0{self.name}" +import asyncio, time +from network import startServer +from player import Player +from block import Block +from config import * def getPlayers(): global WORLD @@ -202,140 +21,6 @@ def getPlayer(name): def current_milli_time(): return round(time.time() * 1000) -async def readPacket(websocket: ServerConnection) -> tuple[str, list[str]]: - data = await websocket.recv() - id,data = data[0], data[1:].splitlines() - print(id, data) - return id,data - -async def writePacket(websocket: ServerConnection, packet_id: str, packet_data: list[str]): - await websocket.send(packet_id + ("\n".join(packet_data))) - -async def handler(websocket: ServerConnection): - packet_id, packet_data = await readPacket(websocket) - - name = packet_data[0] - - if packet_id != "J": - await writePacket(websocket, "K", ["join packet is invalid"]) - return - if getPlayer(name) != None: - await writePacket(websocket, "K", ["this nickname is already in use"]) - return - - print(name, "joined to the server") - - try: - player = Player(websocket) - - await player.sendWorld([b.toStatement() for b in WORLD]) - - await player.sendBlockTypes(BLOCK_TYPES.keys()) - await player.setColor(random.choice(COLORS)) - await player.setPos(*SPAWN) - await player.setVel(0,0) - await player.setName(name) - await player.setWalkSpeed(0.5) - - await player.sendToPlayers() - - WORLD.append(player) - - while True: - packet_id, packet_data = await readPacket(websocket) - - if packet_id == "V": - vel_x, vel_y = float(packet_data[0]), float(packet_data[1]) - vel_x = max(min(vel_x, player.walk_speed), -player.walk_speed) - vel_y = max(min(vel_y, player.jump_speed), 0) - - player.vel_x += vel_x - - if player.on_ground: - player.vel_y += vel_y - player.on_ground = False - - await player.sendToPlayers() - - if packet_id == "K": - key,pressed = packet_data - pressed = pressed == "1" - - if key == "KeyR" and pressed: - await player.setPos(SPAWN[0], SPAWN[1]) - if key == "ShiftLeft": - if pressed: - await player.setWalkSpeed(1) - else: - await player.setWalkSpeed(0.5) - - if packet_id == "M": - message = packet_data[0] - message = f"{name} > {message}" - - for p in getPlayers(): - await p.sendMessage(message) - - print(message) - - if packet_id == "D": - x,y = packet_data - x,y = int(x),int(y) - - block = None - for i in WORLD: - if type(i) == Player: - continue - if i.x == x and i.y == y: - block = i - break - if not block: continue - - if block.type == "spawn": continue # spawn block protection - - if abs(x - player.x) ** 2 + abs(y - player.y) ** 2 > REACH_DISTANCE ** 2: - continue - - WORLD.remove(block) - - for p in getPlayers(): - await p.sendWorld([block.toStatement(False)]) - - if packet_id == "P": - x,y,block_type = packet_data - x,y = int(x),int(y) - - if block_type not in BLOCK_TYPES: - continue - - if abs(x - player.x) ** 2 + abs(y - player.y) ** 2 > REACH_DISTANCE ** 2: - continue - - found_block = False - for i in WORLD: - if type(i) == Player: - continue - if i.x == x and i.y == y: - found_block = True - break - if found_block: continue - - block = Block(x,y,block_type,BLOCK_TYPES[block_type],True) - - WORLD.append(block) - - for p in getPlayers(): - await p.sendWorld([block.toStatement()]) - except Exception as exc: - WORLD.remove(player) - - for p in getPlayers(): - await p.sendWorld([player.toStatement(False)]) - - await writePacket(websocket, "K", [str(exc)]) - - print(name, "left the server") - async def tickTimer(): while True: for b in WORLD: @@ -358,25 +43,7 @@ async def main(): asyncio.get_event_loop().create_task(tickTimer()) asyncio.get_event_loop().create_task(keepAliveTimer()) asyncio.get_event_loop().create_task(renderTimer()) - async with serve(handler, HOST, PORT) as server: - print(f"started server on {HOST}:{PORT}") - await asyncio.get_running_loop().create_future() - - - -HOST,PORT = sys.argv[1].split(":") -PORT = int(PORT) - -COLORS = ["#d22", "#2d2", "#22d", "#dd2", "#2dd", "#d2d", "#ddd"] -SPAWN = (0, 0) - -REACH_DISTANCE = 15 - -BLOCK_TYPES = { - "normal": "#555", - "jump_boost": "#2d2", - "killer": "#d22", -} + await startServer(HOST, PORT, getPlayer, getPlayers, WORLD) WORLD = [ Block(-1, -1, "normal", "#555", True), @@ -384,7 +51,5 @@ WORLD = [ Block(1, -1, "normal", "#555", True) ] - - if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file diff --git a/server/network.py b/server/network.py new file mode 100644 index 0000000..0044551 --- /dev/null +++ b/server/network.py @@ -0,0 +1,61 @@ +from websockets.server import serve, ServerConnection +import asyncio +from config import BLOCK_TYPES, COLORS, SPAWN +from main import WORLD, getPlayer, getPlayers +from player import Player +import random + +async def startServer(host, port): + async with serve(handler, host, port): + print(f"started server on {host}:{port}") + await asyncio.get_running_loop().create_future() + +async def readPacket(websocket: ServerConnection) -> tuple[str, list[str]]: + data = await websocket.recv() + return data[0], data[1:].splitlines() + +async def writePacket(websocket: ServerConnection, packet_id: str, packet_data: list[str]): + await websocket.send(packet_id + ("\n".join(packet_data))) + +async def handler(websocket: ServerConnection): + packet_id, packet_data = await readPacket(websocket) + + name = packet_data[0] + + if packet_id != "J": + await writePacket(websocket, "K", ["join packet is invalid"]) + return + if getPlayer(name) != None: + await writePacket(websocket, "K", ["this nickname is already in use"]) + return + + print(name, "joined to the server") + + try: + player = Player(websocket) + + await player.sendWorld([b.toStatement() for b in WORLD]) + + await player.sendBlockTypes(BLOCK_TYPES.keys()) + await player.setColor(random.choice(COLORS)) + await player.setPos(*SPAWN) + await player.setVel(0,0) + await player.setName(name) + await player.setWalkSpeed(0.5) + + await player.sendToPlayers() + + WORLD.append(player) + + while True: + packet_id, packet_data = await readPacket(websocket) + player.onPacket(packet_id, packet_data) + except Exception as exc: + WORLD.remove(player) + + for p in getPlayers(): + await p.sendWorld([player.toStatement(False)]) + + await writePacket(websocket, "K", [str(exc)]) + + print(name, "left the server") \ No newline at end of file diff --git a/server/player.py b/server/player.py new file mode 100644 index 0000000..19dd784 --- /dev/null +++ b/server/player.py @@ -0,0 +1,255 @@ +from block import Block +from network import writePacket +from config import SPAWN, REACH_DISTANCE, BLOCK_TYPES +from main import getPlayers, WORLD +import time + +class Player(Block): + def __init__(self, websocket, x=None, y=None, name=None, color=None, vel_x=None, vel_y=None): + super().__init__(x, y, None, color, True) + self.x = x + self.y = y + + self.name = name + self.color = color + self.vel_x = vel_x + self.vel_y = vel_y + + self.websocket = websocket + + self.on_ground = False + + self.walk_speed = 1 + self.jump_speed = 2 + self.gravity_speed = 0.5 + + self.lsd_pos = (x, y) + self.lsd_time = time.time() + + async def sendPacket(self, packet_id, packet_data): + await writePacket(self.websocket, packet_id, packet_data) + + async def onPacket(self, packet_id, packet_data): + if packet_id == "V": + vel_x, vel_y = float(packet_data[0]), float(packet_data[1]) + vel_x = max(min(vel_x, self.walk_speed), -self.walk_speed) + vel_y = max(min(vel_y, self.jump_speed), 0) + + self.vel_x += vel_x + + if self.on_ground: + self.vel_y += vel_y + self.on_ground = False + + await self.sendToPlayers() + + if packet_id == "X": + x, y = float(packet_data[0]), float(packet_data[1]) + + ticks = (time.time() - self.lsd_time) * 20 + rx, ry = abs(x - self.lsd_pos[0]), y - self.lsd_pos[1] + + if rx > self.walk_speed * ticks: return + if ry < 0 and abs(ry) > self.gravity_speed * ticks: return + if ry > 0 and ry > self.jump_speed * ticks: return + + self.x = x + self.y = y + + self.lsd_pos = (x, y) + self.lsd_time = time.time() + + await self.sendToPlayers() + + if packet_id == "K": + key,pressed = packet_data + pressed = pressed == "1" + + if key == "KeyR" and pressed: + await self.setPos(SPAWN[0], SPAWN[1]) + if key == "ShiftLeft": + if pressed: + await self.setWalkSpeed(1) + else: + await self.setWalkSpeed(0.5) + + if packet_id == "M": + message = packet_data[0] + message = f"{self.name} > {message}" + + for p in getPlayers(): + await p.sendMessage(message) + + print(message) + + if packet_id == "D": + x,y = packet_data + x,y = int(x),int(y) + + block = None + for i in WORLD: + if type(i) == Player: + continue + if i.x == x and i.y == y: + block = i + break + if not block: return + + if block.type == "spawn": return # spawn block protection + + if abs(x - self.x) ** 2 + abs(y - self.y) ** 2 > REACH_DISTANCE ** 2: + return + + WORLD.remove(block) + + for p in getPlayers(): + await p.sendWorld([block.toStatement(False)]) + + if packet_id == "P": + x,y,block_type = packet_data + x,y = int(x),int(y) + + if block_type not in BLOCK_TYPES: + return + + if abs(x - self.x) ** 2 + abs(y - self.y) ** 2 > REACH_DISTANCE ** 2: + return + + found_block = False + for i in WORLD: + if type(i) == Player: + continue + if i.x == x and i.y == y: + found_block = True + break + if found_block: return + + block = Block(x,y,block_type,BLOCK_TYPES[block_type],True) + + WORLD.append(block) + + for p in getPlayers(): + await p.sendWorld([block.toStatement()]) + + async def setWalkSpeed(self, speed): + await writePacket(self.websocket, "S", ["W", str(speed)]) + self.walk_speed = speed + + async def setGravitySpeed(self, speed): + await writePacket(self.websocket, "S", ["G", str(speed)]) + self.gravity_speed = speed + + async def setJumpSpeed(self, speed): + await writePacket(self.websocket, "S", ["J", str(speed)]) + self.jump_speed = speed + + async def sendName(self, name): + await writePacket(self.websocket, "N", [name]) + + async def setName(self, name): + self.name = name + await self.sendName(name) + + async def setColor(self, color): + self.color = color + await writePacket(self.websocket, "C", [color]) + + async def setVel(self, x, y): + if x == self.vel_x and y == self.vel_y: return + + self.vel_x = x + self.vel_y = y + + await self.sendVel(x, y) + + async def setPos(self, x, y): + if x == self.x and y == self.y: return + + self.x = x + self.y = y + + await self.sendPos(x, y) + + async def sendVel(self, x, y): + await writePacket(self.websocket, "V", [str(x), str(y)]) + + async def sendPos(self, x, y): + await writePacket(self.websocket, "P", [str(x), str(y)]) + + async def sendMessage(self, message): + await writePacket(self.websocket, "M", message.split("\n")) + + async def sendWorld(self, statements): + if len(statements) == 0: return + await writePacket(self.websocket, "W", statements) + + async def sendBlockTypes(self, types): + await writePacket(self.websocket, "B", types) + + async def sendToPlayers(self): + for p in getPlayers(): + if p != self: + await p.sendWorld([self.toStatement()]) + + async def tick(self): + self.x = round(self.x * 100) / 100 + self.y = round(self.y * 100) / 100 + self.vel_x = round(self.vel_x * 100) / 100 + self.vel_y = round(self.vel_y * 100) / 100 + + if not self.on_ground: + self.vel_y -= self.gravity_speed + + await self.collide() + + async def collide(self): + global WORLD + + self.on_ground = False + + for block in WORLD: + if not block.collides: continue + if block == self: continue + + collide_x = 0 + collide_y = 0 + + if self.x > block.x-1 and self.x < block.x+1: + if self.y > block.y and self.y + self.vel_y - 1 < block.y: + self.on_ground = True + collide_y = 1 + if self.y < block.y and self.y + self.vel_y > block.y - 1: + collide_y = -1 + + if self.y > block.y-1 and self.y < block.y+1: + if self.x > block.x and self.x + self.vel_x - 1 < block.x: + collide_x = 1 + if self.x < block.x and self.x + self.vel_x > block.x - 1: + collide_x = -1 + + await block.onCollide(self, collide_x, collide_y) + + async def onCollide(self, player, x, y): + await super().onCollide(player, x, y) + # if x != 0: + # player.vel_x *= 0.5 + # self.vel_x = player.vel_x + # if y != 0: + # player.vel_y *= 0.5 + # self.vel_y = player.vel_y + # pass + + async def render(self): + self.vel_x *= 0.5 + self.vel_y *= 0.5 + self.x += self.vel_x + self.y += self.vel_y + # await self.setVel(self.vel_x * 0.5, self.vel_y * 0.5) + # await self.setPos(self.x + self.vel_x, self.y + self.vel_y) + return self.vel_x != 0 or self.vel_y != 0 + + async def keepAlive(self): + await writePacket(self.websocket, "R", [str(self.x), str(self.y), str(self.vel_x), str(self.vel_y)]) + + def toStatement(self, add=True): + return f"P1{self.name},{self.x},{self.y},{self.vel_x},{self.vel_y},{self.color}" if add else f"P0{self.name}" diff --git a/server/src/block.py b/server/src/block.py new file mode 100644 index 0000000..d49c593 --- /dev/null +++ b/server/src/block.py @@ -0,0 +1,35 @@ +from config import SPAWN + +class Block: + def __init__(self, x, y, block_type, color, collides): + self.x = x + self.y = y + self.type = block_type + self.color = color + self.collides = collides + + async def tick(self): + pass + + async def render(self): + pass + + async def onCollide(self, player, x, y): + if x != 0: player.vel_x = self.x + x - player.x + if y != 0: player.vel_y = self.y + y - player.y + + if x != 0 or y != 0: # special blocks + if self.type == "jump_boost": + player.setGravitySpeed(1.25) + player.setJumpSpeed(5) + player.on_ground = True + elif player.jump_speed != 2: + player.setGravitySpeed(0.5) + player.setJumpSpeed(2) + + if self.type == "killer": + await player.setPos(*SPAWN) + await player.sendToPlayers() + + def toStatement(self, add=True): + return f"B1{self.x},{self.y},{int(self.collides)},{self.type},{self.color}" if add else f"B0{self.x},{self.y}" \ No newline at end of file diff --git a/server/src/config.py b/server/src/config.py new file mode 100644 index 0000000..a2447d1 --- /dev/null +++ b/server/src/config.py @@ -0,0 +1,16 @@ +import sys + + +HOST,PORT = sys.argv[1].split(":") +PORT = int(PORT) + +COLORS = ["#d22", "#2d2", "#22d", "#dd2", "#2dd", "#d2d", "#ddd"] +SPAWN = (0, 0) + +REACH_DISTANCE = 15 + +BLOCK_TYPES = { + "normal": "#555", + "jump_boost": "#2d2", + "killer": "#d22", +} \ No newline at end of file diff --git a/server/src/main.py b/server/src/main.py new file mode 100644 index 0000000..6526332 --- /dev/null +++ b/server/src/main.py @@ -0,0 +1,55 @@ + +import asyncio, time +from network import startServer +from player import Player +from block import Block +from config import * + +def getPlayers(): + global WORLD + for b in WORLD: + if type(b) == Player: + yield b + +def getPlayer(name): + global WORLD + for b in WORLD: + if type(b) == Player: + if b.name == name: + return b + +def current_milli_time(): + return round(time.time() * 1000) + +async def tickTimer(): + while True: + for b in WORLD: + await b.tick() + await asyncio.sleep(1/20) + +async def keepAliveTimer(): + while True: + for b in getPlayers(): + await b.keepAlive() + await asyncio.sleep(1) + +async def renderTimer(): + while True: + for p in getPlayers(): + await p.sendWorld([b.toStatement() for b in WORLD if await b.render() and b != p]) + await asyncio.sleep(1/60) + +async def main(): + asyncio.get_event_loop().create_task(tickTimer()) + asyncio.get_event_loop().create_task(keepAliveTimer()) + asyncio.get_event_loop().create_task(renderTimer()) + await startServer(HOST, PORT) + +WORLD = [ + Block(-1, -1, "normal", "#555", True), + Block(0, -1, "spawn", "#2ad", True), + Block(1, -1, "normal", "#555", True) +] + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/server/src/network.py b/server/src/network.py new file mode 100644 index 0000000..0044551 --- /dev/null +++ b/server/src/network.py @@ -0,0 +1,61 @@ +from websockets.server import serve, ServerConnection +import asyncio +from config import BLOCK_TYPES, COLORS, SPAWN +from main import WORLD, getPlayer, getPlayers +from player import Player +import random + +async def startServer(host, port): + async with serve(handler, host, port): + print(f"started server on {host}:{port}") + await asyncio.get_running_loop().create_future() + +async def readPacket(websocket: ServerConnection) -> tuple[str, list[str]]: + data = await websocket.recv() + return data[0], data[1:].splitlines() + +async def writePacket(websocket: ServerConnection, packet_id: str, packet_data: list[str]): + await websocket.send(packet_id + ("\n".join(packet_data))) + +async def handler(websocket: ServerConnection): + packet_id, packet_data = await readPacket(websocket) + + name = packet_data[0] + + if packet_id != "J": + await writePacket(websocket, "K", ["join packet is invalid"]) + return + if getPlayer(name) != None: + await writePacket(websocket, "K", ["this nickname is already in use"]) + return + + print(name, "joined to the server") + + try: + player = Player(websocket) + + await player.sendWorld([b.toStatement() for b in WORLD]) + + await player.sendBlockTypes(BLOCK_TYPES.keys()) + await player.setColor(random.choice(COLORS)) + await player.setPos(*SPAWN) + await player.setVel(0,0) + await player.setName(name) + await player.setWalkSpeed(0.5) + + await player.sendToPlayers() + + WORLD.append(player) + + while True: + packet_id, packet_data = await readPacket(websocket) + player.onPacket(packet_id, packet_data) + except Exception as exc: + WORLD.remove(player) + + for p in getPlayers(): + await p.sendWorld([player.toStatement(False)]) + + await writePacket(websocket, "K", [str(exc)]) + + print(name, "left the server") \ No newline at end of file diff --git a/server/src/player.py b/server/src/player.py new file mode 100644 index 0000000..19dd784 --- /dev/null +++ b/server/src/player.py @@ -0,0 +1,255 @@ +from block import Block +from network import writePacket +from config import SPAWN, REACH_DISTANCE, BLOCK_TYPES +from main import getPlayers, WORLD +import time + +class Player(Block): + def __init__(self, websocket, x=None, y=None, name=None, color=None, vel_x=None, vel_y=None): + super().__init__(x, y, None, color, True) + self.x = x + self.y = y + + self.name = name + self.color = color + self.vel_x = vel_x + self.vel_y = vel_y + + self.websocket = websocket + + self.on_ground = False + + self.walk_speed = 1 + self.jump_speed = 2 + self.gravity_speed = 0.5 + + self.lsd_pos = (x, y) + self.lsd_time = time.time() + + async def sendPacket(self, packet_id, packet_data): + await writePacket(self.websocket, packet_id, packet_data) + + async def onPacket(self, packet_id, packet_data): + if packet_id == "V": + vel_x, vel_y = float(packet_data[0]), float(packet_data[1]) + vel_x = max(min(vel_x, self.walk_speed), -self.walk_speed) + vel_y = max(min(vel_y, self.jump_speed), 0) + + self.vel_x += vel_x + + if self.on_ground: + self.vel_y += vel_y + self.on_ground = False + + await self.sendToPlayers() + + if packet_id == "X": + x, y = float(packet_data[0]), float(packet_data[1]) + + ticks = (time.time() - self.lsd_time) * 20 + rx, ry = abs(x - self.lsd_pos[0]), y - self.lsd_pos[1] + + if rx > self.walk_speed * ticks: return + if ry < 0 and abs(ry) > self.gravity_speed * ticks: return + if ry > 0 and ry > self.jump_speed * ticks: return + + self.x = x + self.y = y + + self.lsd_pos = (x, y) + self.lsd_time = time.time() + + await self.sendToPlayers() + + if packet_id == "K": + key,pressed = packet_data + pressed = pressed == "1" + + if key == "KeyR" and pressed: + await self.setPos(SPAWN[0], SPAWN[1]) + if key == "ShiftLeft": + if pressed: + await self.setWalkSpeed(1) + else: + await self.setWalkSpeed(0.5) + + if packet_id == "M": + message = packet_data[0] + message = f"{self.name} > {message}" + + for p in getPlayers(): + await p.sendMessage(message) + + print(message) + + if packet_id == "D": + x,y = packet_data + x,y = int(x),int(y) + + block = None + for i in WORLD: + if type(i) == Player: + continue + if i.x == x and i.y == y: + block = i + break + if not block: return + + if block.type == "spawn": return # spawn block protection + + if abs(x - self.x) ** 2 + abs(y - self.y) ** 2 > REACH_DISTANCE ** 2: + return + + WORLD.remove(block) + + for p in getPlayers(): + await p.sendWorld([block.toStatement(False)]) + + if packet_id == "P": + x,y,block_type = packet_data + x,y = int(x),int(y) + + if block_type not in BLOCK_TYPES: + return + + if abs(x - self.x) ** 2 + abs(y - self.y) ** 2 > REACH_DISTANCE ** 2: + return + + found_block = False + for i in WORLD: + if type(i) == Player: + continue + if i.x == x and i.y == y: + found_block = True + break + if found_block: return + + block = Block(x,y,block_type,BLOCK_TYPES[block_type],True) + + WORLD.append(block) + + for p in getPlayers(): + await p.sendWorld([block.toStatement()]) + + async def setWalkSpeed(self, speed): + await writePacket(self.websocket, "S", ["W", str(speed)]) + self.walk_speed = speed + + async def setGravitySpeed(self, speed): + await writePacket(self.websocket, "S", ["G", str(speed)]) + self.gravity_speed = speed + + async def setJumpSpeed(self, speed): + await writePacket(self.websocket, "S", ["J", str(speed)]) + self.jump_speed = speed + + async def sendName(self, name): + await writePacket(self.websocket, "N", [name]) + + async def setName(self, name): + self.name = name + await self.sendName(name) + + async def setColor(self, color): + self.color = color + await writePacket(self.websocket, "C", [color]) + + async def setVel(self, x, y): + if x == self.vel_x and y == self.vel_y: return + + self.vel_x = x + self.vel_y = y + + await self.sendVel(x, y) + + async def setPos(self, x, y): + if x == self.x and y == self.y: return + + self.x = x + self.y = y + + await self.sendPos(x, y) + + async def sendVel(self, x, y): + await writePacket(self.websocket, "V", [str(x), str(y)]) + + async def sendPos(self, x, y): + await writePacket(self.websocket, "P", [str(x), str(y)]) + + async def sendMessage(self, message): + await writePacket(self.websocket, "M", message.split("\n")) + + async def sendWorld(self, statements): + if len(statements) == 0: return + await writePacket(self.websocket, "W", statements) + + async def sendBlockTypes(self, types): + await writePacket(self.websocket, "B", types) + + async def sendToPlayers(self): + for p in getPlayers(): + if p != self: + await p.sendWorld([self.toStatement()]) + + async def tick(self): + self.x = round(self.x * 100) / 100 + self.y = round(self.y * 100) / 100 + self.vel_x = round(self.vel_x * 100) / 100 + self.vel_y = round(self.vel_y * 100) / 100 + + if not self.on_ground: + self.vel_y -= self.gravity_speed + + await self.collide() + + async def collide(self): + global WORLD + + self.on_ground = False + + for block in WORLD: + if not block.collides: continue + if block == self: continue + + collide_x = 0 + collide_y = 0 + + if self.x > block.x-1 and self.x < block.x+1: + if self.y > block.y and self.y + self.vel_y - 1 < block.y: + self.on_ground = True + collide_y = 1 + if self.y < block.y and self.y + self.vel_y > block.y - 1: + collide_y = -1 + + if self.y > block.y-1 and self.y < block.y+1: + if self.x > block.x and self.x + self.vel_x - 1 < block.x: + collide_x = 1 + if self.x < block.x and self.x + self.vel_x > block.x - 1: + collide_x = -1 + + await block.onCollide(self, collide_x, collide_y) + + async def onCollide(self, player, x, y): + await super().onCollide(player, x, y) + # if x != 0: + # player.vel_x *= 0.5 + # self.vel_x = player.vel_x + # if y != 0: + # player.vel_y *= 0.5 + # self.vel_y = player.vel_y + # pass + + async def render(self): + self.vel_x *= 0.5 + self.vel_y *= 0.5 + self.x += self.vel_x + self.y += self.vel_y + # await self.setVel(self.vel_x * 0.5, self.vel_y * 0.5) + # await self.setPos(self.x + self.vel_x, self.y + self.vel_y) + return self.vel_x != 0 or self.vel_y != 0 + + async def keepAlive(self): + await writePacket(self.websocket, "R", [str(self.x), str(self.y), str(self.vel_x), str(self.vel_y)]) + + def toStatement(self, add=True): + return f"P1{self.name},{self.x},{self.y},{self.vel_x},{self.vel_y},{self.color}" if add else f"P0{self.name}" diff --git a/server/start.sh b/server/start.sh new file mode 100755 index 0000000..26aae65 --- /dev/null +++ b/server/start.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +HOST=localhost +PORT=8000 + +python3 src/main.py $HOST:$PORT \ No newline at end of file