summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/src/main.ts41
-rw-r--r--server/src/network/MessageProcessor.ts36
-rw-r--r--server/src/network/SessionInputSystem.ts32
-rw-r--r--server/src/network/SessionManager.ts33
-rw-r--r--server/src/network/index.ts13
-rw-r--r--server/src/server.ts76
6 files changed, 194 insertions, 37 deletions
diff --git a/server/src/main.ts b/server/src/main.ts
index 965e0d7..0e47491 100644
--- a/server/src/main.ts
+++ b/server/src/main.ts
@@ -2,28 +2,55 @@ import { Grid } from '@engine/structures';
import {
ServerMessageProcessor,
ServerSocketMessagePublisher,
- ServerSocketMessageReceiver
+ ServerSocketMessageReceiver,
+ MemorySessionManager,
+ SessionInputSystem
} from './network';
import { Collision, NetworkUpdate, Physics, WallBounds } from '@engine/systems';
import { Game } from '@engine/Game';
import { Constants } from './constants';
import { GameServer } from './server';
+import { Floor } from '@engine/entities';
+import { BoundingBox } from '@engine/components';
+import { Miscellaneous } from '@engine/config';
+
+const game = new Game();
+
+const sessionManager = new MemorySessionManager();
const messageReceiver = new ServerSocketMessageReceiver();
const messagePublisher = new ServerSocketMessagePublisher();
-const messageProcessor = new ServerMessageProcessor();
+const messageProcessor = new ServerMessageProcessor(game, sessionManager);
-const game = new Game();
-
-const server = new GameServer(game, messageReceiver, messagePublisher);
+const server = new GameServer(
+ game,
+ messageReceiver,
+ messagePublisher,
+ sessionManager
+);
[
+ new SessionInputSystem(sessionManager),
+ new NetworkUpdate(messageReceiver, messagePublisher, messageProcessor),
new Physics(),
new Collision(new Grid()),
- new WallBounds(),
- new NetworkUpdate(messageReceiver, messagePublisher, messageProcessor)
+ new WallBounds()
].forEach((system) => game.addSystem(system));
+const floor = new Floor(160);
+const floorHeight = 200;
+
+floor.addComponent(
+ new BoundingBox(
+ {
+ x: Miscellaneous.WIDTH / 2,
+ y: Miscellaneous.HEIGHT + floorHeight / 2
+ },
+ { width: Miscellaneous.WIDTH, height: floorHeight }
+ )
+);
+game.addEntity(floor);
+
game.start();
setInterval(() => {
game.doGameLoop(performance.now());
diff --git a/server/src/network/MessageProcessor.ts b/server/src/network/MessageProcessor.ts
index de42459..2d9f11f 100644
--- a/server/src/network/MessageProcessor.ts
+++ b/server/src/network/MessageProcessor.ts
@@ -1,8 +1,36 @@
-import { MessageProcessor } from '@engine/network';
-import { ServerMessage } from '.';
+import {
+ EntityUpdateBody,
+ MessageProcessor,
+ MessageType
+} from '@engine/network';
+import { ServerMessage, SessionManager } from '.';
+import { Game } from '@engine/Game';
export class ServerMessageProcessor implements MessageProcessor {
- constructor() {}
+ private game: Game;
+ private sessionManager: SessionManager;
- public process(_message: ServerMessage) {}
+ constructor(game: Game, sessionManager: SessionManager) {
+ this.game = game;
+ this.sessionManager = sessionManager;
+ }
+
+ public process(message: ServerMessage) {
+ switch (message.type) {
+ case MessageType.NEW_INPUT: {
+ const { sessionId } = message.sessionData;
+ const session = this.sessionManager.getSession(sessionId);
+ session?.inputSystem.keyPressed(message.body as string);
+ break;
+ }
+ case MessageType.REMOVE_INPUT: {
+ const { sessionId } = message.sessionData;
+ const session = this.sessionManager.getSession(sessionId);
+ session?.inputSystem.keyReleased(message.body as string);
+ break;
+ }
+ default:
+ break;
+ }
+ }
}
diff --git a/server/src/network/SessionInputSystem.ts b/server/src/network/SessionInputSystem.ts
new file mode 100644
index 0000000..44fba54
--- /dev/null
+++ b/server/src/network/SessionInputSystem.ts
@@ -0,0 +1,32 @@
+import { Game } from '@engine/Game';
+import { SessionManager } from '.';
+import { System } from '@engine/systems';
+import { BoundingBox, ComponentNames, Control } from '@engine/components';
+
+export class SessionInputSystem extends System {
+ private sessionManager: SessionManager;
+
+ constructor(sessionManager: SessionManager) {
+ super('SessionInputSystem');
+
+ this.sessionManager = sessionManager;
+ }
+
+ public update(_dt: number, game: Game) {
+ this.sessionManager.getSessions().forEach((sessionId) => {
+ const session = this.sessionManager.getSession(sessionId);
+
+ if (!session) return;
+
+ const { inputSystem } = session;
+ session.controllableEntities.forEach((entityId) => {
+ const entity = game.getEntity(entityId);
+ if (!entity) return;
+
+ if (entity.hasComponent(ComponentNames.Control)) {
+ inputSystem.handleInput(entity);
+ }
+ });
+ });
+ }
+}
diff --git a/server/src/network/SessionManager.ts b/server/src/network/SessionManager.ts
new file mode 100644
index 0000000..dbd4364
--- /dev/null
+++ b/server/src/network/SessionManager.ts
@@ -0,0 +1,33 @@
+import { Session, SessionManager } from '.';
+
+export class MemorySessionManager implements SessionManager {
+ private sessions: Map<string, Session>;
+
+ constructor() {
+ this.sessions = new Map();
+ }
+
+ public getSessions() {
+ return Array.from(this.sessions.keys());
+ }
+
+ public uniqueSessionId() {
+ return crypto.randomUUID();
+ }
+
+ public getSession(id: string) {
+ return this.sessions.get(id);
+ }
+
+ public putSession(id: string, session: Session) {
+ return this.sessions.set(id, session);
+ }
+
+ public numSessions() {
+ return this.sessions.size;
+ }
+
+ public removeSession(id: string) {
+ this.sessions.delete(id);
+ }
+}
diff --git a/server/src/network/index.ts b/server/src/network/index.ts
index 8ffa689..3cbf0ac 100644
--- a/server/src/network/index.ts
+++ b/server/src/network/index.ts
@@ -1,16 +1,29 @@
import { Message } from '@engine/network';
+import { Input } from '@engine/systems';
export * from './MessageProcessor';
export * from './MessagePublisher';
export * from './MessageReceiver';
+export * from './SessionManager';
+export * from './SessionInputSystem';
export type SessionData = { sessionId: string };
export type Session = {
sessionId: string;
controllableEntities: Set<string>;
+ inputSystem: Input;
};
export interface ServerMessage extends Message {
sessionData: SessionData;
}
+
+export interface SessionManager {
+ uniqueSessionId(): string;
+ getSession(id: string): Session | undefined;
+ getSessions(): string[];
+ putSession(id: string, session: Session): void;
+ removeSession(id: string): void;
+ numSessions(): number;
+}
diff --git a/server/src/server.ts b/server/src/server.ts
index 303d2b5..575e916 100644
--- a/server/src/server.ts
+++ b/server/src/server.ts
@@ -1,35 +1,38 @@
import { Game } from '@engine/Game';
-import { EntityNames, Player } from '@engine/entities';
-import { MessageType } from '@engine/network';
+import { Player } from '@engine/entities';
+import { Message, MessageType } from '@engine/network';
import { Constants } from './constants';
import {
ServerSocketMessageReceiver,
ServerSocketMessagePublisher,
SessionData,
ServerMessage,
- Session
+ Session,
+ SessionManager
} from './network';
import { parse } from '@engine/utils';
import { Server, ServerWebSocket } from 'bun';
+import { Input } from '@engine/systems';
+import { Control, NetworkUpdateable } from '@engine/components';
+import { stringify } from '@engine/utils';
export class GameServer {
- private sessions: Map<string, Session>;
-
private server?: Server;
private game: Game;
private messageReceiver: ServerSocketMessageReceiver;
private messagePublisher: ServerSocketMessagePublisher;
+ private sessionManager: SessionManager;
constructor(
game: Game,
messageReceiver: ServerSocketMessageReceiver,
- messagePublisher: ServerSocketMessagePublisher
+ messagePublisher: ServerSocketMessagePublisher,
+ sessionManager: SessionManager
) {
- this.sessions = new Map();
-
this.game = game;
this.messageReceiver = messageReceiver;
this.messagePublisher = messagePublisher;
+ this.sessionManager = sessionManager;
}
public serve() {
@@ -64,10 +67,12 @@ export class GameServer {
private closeWebsocket(websocket: ServerWebSocket<SessionData>) {
const { sessionId } = websocket.data;
- const sessionEntities = this.sessions.get(sessionId)!.controllableEntities;
- this.sessions.delete(sessionId);
+ const sessionEntities =
+ this.sessionManager.getSession(sessionId)!.controllableEntities;
+ this.sessionManager.removeSession(sessionId);
if (!sessionEntities) return;
+ sessionEntities.forEach((id) => this.game.removeEntity(id));
this.messagePublisher.addMessage({
type: MessageType.REMOVE_ENTITIES,
@@ -79,28 +84,51 @@ export class GameServer {
websocket.subscribe(Constants.GAME_TOPIC);
const { sessionId } = websocket.data;
- if (this.sessions.has(sessionId)) {
+ if (this.sessionManager.getSession(sessionId)) {
return;
}
- this.sessions.set(sessionId, {
+ const newSession: Session = {
sessionId,
- controllableEntities: new Set()
- });
+ controllableEntities: new Set(),
+ inputSystem: new Input(sessionId)
+ };
- const player = new Player(sessionId);
+ const player = new Player();
+ player.addComponent(new Control(sessionId));
+ player.addComponent(new NetworkUpdateable());
this.game.addEntity(player);
- this.messagePublisher.addMessage({
+
+ newSession.controllableEntities.add(player.id);
+ this.sessionManager.putSession(sessionId, newSession);
+
+ const addCurrentEntities: Message[] = [
+ {
+ type: MessageType.NEW_ENTITIES,
+ body: Array.from(this.game.entities.values())
+ .filter((entity) => entity.id != player.id)
+ .map((entity) => {
+ return {
+ id: entity.id,
+ entityName: entity.name,
+ args: entity.serialize()
+ };
+ })
+ }
+ ];
+ websocket.sendText(stringify(addCurrentEntities));
+
+ const addNewPlayer: Message = {
type: MessageType.NEW_ENTITIES,
body: [
{
- entityName: EntityNames.Player,
- args: { playerId: sessionId, id: player.id }
+ id: player.id,
+ entityName: player.name,
+ args: player.serialize()
}
]
- });
-
- this.sessions.get(sessionId)!.controllableEntities.add(player.id);
+ };
+ this.messagePublisher.addMessage(addNewPlayer);
}
private fetchHandler(req: Request, server: Server): Response {
@@ -110,7 +138,7 @@ export class GameServer {
headers.set('Access-Control-Allow-Origin', '*');
if (url.pathname == '/assign') {
- if (this.sessions.size > Constants.MAX_PLAYERS)
+ if (this.sessionManager.numSessions() > Constants.MAX_PLAYERS)
return new Response('too many players', { headers, status: 400 });
const sessionId = crypto.randomUUID();
@@ -127,10 +155,6 @@ export class GameServer {
const sessionId = cookie.split(';').at(0)!.split('SessionId=').at(1);
if (url.pathname == '/game') {
- headers.set(
- 'Set-Cookie',
- `SessionId=${sessionId}; HttpOnly; SameSite=Strict;`
- );
server.upgrade(req, {
headers,
data: {