This commit is contained in:
schmelczerandras 2020-11-04 16:01:17 +01:00
parent 1ce961d6c2
commit 99cdb62928
76 changed files with 340 additions and 433 deletions

View file

@ -1,15 +0,0 @@
name: decla-red-server
region: fra
services:
- dockerfile_path: Dockerfile
github:
branch: main
deploy_on_push: true
repo: schmelczerandras/decla.red
http_port: 3000
instance_count: 1
instance_size_slug: basic-xxs
run_command: ' declared-server --name=Frankfurt --seed=102'
name: decla-red-server
routes:
- path: /

View file

@ -1,6 +0,0 @@
.dockerignore
Dockerfile
**/node_modules
node_modules
**/package-lock.json
package-lock.json

View file

@ -1,6 +1,9 @@
{ {
"cSpell.words": [ "cSpell.words": [
"Deserializable",
"Deserialization",
"decla", "decla",
"overridable",
"serializable" "serializable"
] ]
} }

View file

@ -1,8 +0,0 @@
FROM node:14.13.0-alpine3.10
RUN npm i -g declared-server
EXPOSE 3000
CMD ["--port=3000", "--name=Docker server", "--seed=500"]
ENTRYPOINT [ "NODE_ENV=production node", "declared-server" ]

View file

@ -1,6 +1,6 @@
{ {
"name": "declared-server", "name": "declared-server",
"version": "0.0.11", "version": "0.0.12",
"description": "Game server for decla.red", "description": "Game server for decla.red",
"keywords": [], "keywords": [],
"author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)", "author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)",

View file

@ -1,11 +1,10 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Random, PlanetBase, hsl, settings } from 'shared'; import { Random, PlanetBase, hsl, settings } from 'shared';
import { LampPhysical } from '../objects/lamp-physical'; import { LampPhysical } from './objects/lamp-physical';
import { PlanetPhysical } from './objects/planet-physical';
import { PlanetPhysical } from '../objects/planet-physical'; import { PhysicalContainer } from './physics/containers/physical-container';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { evaluateSdf } from './physics/functions/evaluate-sdf';
import { evaluateSdf } from '../physics/functions/evaluate-sdf'; import { Physical } from './physics/physicals/physical';
import { Physical } from '../physics/physicals/physical';
export const createWorld = (objectContainer: PhysicalContainer, worldRadius: number) => { export const createWorld = (objectContainer: PhysicalContainer, worldRadius: number) => {
const objects: Array<Physical> = []; const objects: Array<Physical> = [];

View file

@ -7,14 +7,12 @@ import {
ServerInformation, ServerInformation,
PlayerInformation, PlayerInformation,
UpdateGameState, UpdateGameState,
serialize,
CharacterTeam, CharacterTeam,
GameEnd, GameEndCommand,
GameStart, GameStartCommand,
Command, Command,
last,
} from 'shared'; } from 'shared';
import { createWorld } from './map/create-world'; import { createWorld } from './create-world';
import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { Options } from './options'; import { Options } from './options';
import { PlayerContainer } from './players/player-container'; import { PlayerContainer } from './players/player-container';
@ -52,7 +50,7 @@ export class GameServer {
this.redPoints = 0; this.redPoints = 0;
this.isInEndGame = false; this.isInEndGame = false;
this.timeScaling = 1; this.timeScaling = 1;
previousPlayers?.queueCommandForEachClient(new GameStart()); previousPlayers?.queueCommandForEachClient(new GameStartCommand());
previousPlayers?.sendQueuedCommands(); previousPlayers?.sendQueuedCommands();
} }
@ -119,7 +117,7 @@ export class GameServer {
this.isInEndGame = true; this.isInEndGame = true;
const endTitleLength = 6000; const endTitleLength = 6000;
this.players.queueCommandForEachClient( this.players.queueCommandForEachClient(
new GameEnd(winningTeam, endTitleLength / 1000, true), new GameEndCommand(winningTeam, endTitleLength / 1000, true),
); );
setTimeout(() => this.destroy(), endTitleLength * 1.1); setTimeout(() => this.destroy(), endTitleLength * 1.1);
} }

View file

@ -1,5 +0,0 @@
export const getTimeInMilliseconds = (): number => {
const [seconds, nanoSeconds] = process.hrtime();
return seconds * 1000 + nanoSeconds / 1000 / 1000;
};

View file

@ -1,9 +1,6 @@
const shortAngleDist = (a0: number, a1: number): number => { export const interpolateAngles = (from: number, to: number, q: number) => {
const max = Math.PI * 2; const max = Math.PI * 2;
const da = (a1 - a0) % max; const possibleDistance = (to - from) % max;
return ((2 * da) % max) - da; const shortedDistance = ((2 * possibleDistance) % max) - possibleDistance;
}; return from + shortedDistance * q;
export const interpolateAngles = (a0: number, a1: number, t: number) => {
return a0 + shortAngleDist(a0, a1) * t;
}; };

View file

@ -9,7 +9,7 @@ import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
import { import {
ReactsToCollision, ReactsToCollision,
reactsToCollision, reactsToCollision,
} from '../physics/physicals/reacts-to-collision'; } from '../physics/functions/reacts-to-collision';
@serializesTo(Circle) @serializesTo(Circle)
export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollision { export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollision {

View file

@ -21,7 +21,7 @@ import { interpolateAngles } from '../helper/interpolate-angles';
import { forceAtPosition } from '../physics/functions/force-at-position'; import { forceAtPosition } from '../physics/functions/force-at-position';
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle'; import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
import { PlanetPhysical } from './planet-physical'; import { PlanetPhysical } from './planet-physical';
import { ReactsToCollision } from '../physics/physicals/reacts-to-collision'; import { ReactsToCollision } from '../physics/functions/reacts-to-collision';
@serializesTo(PlayerCharacterBase) @serializesTo(PlayerCharacterBase)
export class PlayerCharacterPhysical export class PlayerCharacterPhysical
@ -479,6 +479,7 @@ export class PlayerCharacterPhysical
public kill() { public kill() {
this.isDestroyed = true; this.isDestroyed = true;
this.remoteCall('kill');
} }
private destroy() { private destroy() {

View file

@ -14,7 +14,7 @@ import { CirclePhysical } from './circle-physical';
import { DynamicPhysical } from '../physics/physicals/dynamic-physical'; import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { PlanetPhysical } from './planet-physical'; import { PlanetPhysical } from './planet-physical';
import { ReactsToCollision } from '../physics/physicals/reacts-to-collision'; import { ReactsToCollision } from '../physics/functions/reacts-to-collision';
import { PlayerCharacterPhysical } from './player-character-physical'; import { PlayerCharacterPhysical } from './player-character-physical';
import { moveCircle } from '../physics/functions/move-circle'; import { moveCircle } from '../physics/functions/move-circle';
@ -114,24 +114,6 @@ export class ProjectilePhysical
return; return;
} }
const gravity = vec2.create();
const intersecting = this.container.findIntersecting(this.boundingBox);
intersecting.forEach((i) => {
if (i instanceof PlanetPhysical) {
vec2.add(gravity, gravity, i.getForce(this.center));
}
});
vec2.add(
this.velocity,
this.velocity,
vec2.scale(
vec2.create(),
gravity,
deltaTime * settings.gravityScalingForProjectiles,
),
);
vec2.copy(this.object.velocity, this.velocity); vec2.copy(this.object.velocity, this.velocity);
const { velocity } = this.object.step2(deltaTime); const { velocity } = this.object.step2(deltaTime);
vec2.copy(this.velocity, velocity); vec2.copy(this.velocity, velocity);

View file

@ -1,6 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
export class BoundingBoxBase { // axes aligned
export abstract class BoundingBoxBase {
constructor( constructor(
protected _xMin: number = 0, protected _xMin: number = 0,
protected _xMax: number = 0, protected _xMax: number = 0,

View file

@ -1,7 +1,7 @@
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
import { StaticPhysical } from '../physicals/static-physical'; import { StaticPhysical } from '../physicals/static-physical';
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
class Node { class Node {
public left: Node | null = null; public left: Node | null = null;
public right: Node | null = null; public right: Node | null = null;

View file

@ -1,8 +1,9 @@
import { Circle } from 'shared'; import { Circle } from 'shared';
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box';
export const getBoundingBoxOfCircle = (circle: Circle): BoundingBoxBase => export const getBoundingBoxOfCircle = (circle: Circle): BoundingBoxBase =>
new BoundingBoxBase( new ImmutableBoundingBox(
circle.center.x - circle.radius, circle.center.x - circle.radius,
circle.center.x + circle.radius, circle.center.x + circle.radius,
circle.center.y - circle.radius, circle.center.y - circle.radius,

View file

@ -1,7 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Circle, GameObject, rotate90Deg } from 'shared'; import { Circle, GameObject, rotate90Deg } from 'shared';
import { CirclePhysical } from '../../objects/circle-physical'; import { CirclePhysical } from '../../objects/circle-physical';
import { reactsToCollision } from '../physicals/reacts-to-collision'; import { reactsToCollision } from './reacts-to-collision';
import { evaluateSdf } from './evaluate-sdf'; import { evaluateSdf } from './evaluate-sdf';
import { Physical } from '../physicals/physical'; import { Physical } from '../physicals/physical';

View file

@ -10,7 +10,6 @@ import {
SetAspectRatioActionCommand, SetAspectRatioActionCommand,
calculateViewArea, calculateViewArea,
SecondaryActionCommand, SecondaryActionCommand,
PlayerDiedCommand,
settings, settings,
PlayerInformation, PlayerInformation,
CharacterTeam, CharacterTeam,
@ -23,21 +22,21 @@ import {
ServerAnnouncement, ServerAnnouncement,
PropertyUpdatesForObjects, PropertyUpdatesForObjects,
PropertyUpdatesForObject, PropertyUpdatesForObject,
PrimaryActionCommand,
} from 'shared'; } from 'shared';
import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { PlayerCharacterPhysical } from '../objects/player-character-physical'; import { PlayerCharacterPhysical } from '../objects/player-character-physical';
import { PlayerContainer } from './player-container'; import { PlayerContainer } from './player-container';
import { PlayerBase } from './player-base'; import { PlayerBase } from './player-base';
import { DeltaTimeCalculator } from '../helper/delta-time-calculator';
export class Player extends PlayerBase { export class Player extends PlayerBase {
private aspectRatio: number = 16 / 9; private aspectRatio: number = 16 / 9;
private isActive = true; private isActive = true;
private objectsPreviouslyInViewArea: Array<GameObject> = []; private objectsPreviouslyInViewArea: Array<GameObject> = [];
private pingDeltaTime = new DeltaTimeCalculator();
private pingTime?: number;
private _latency?: number; private _latency?: number;
protected commandExecutors: CommandExecutors = { protected commandExecutors: CommandExecutors = {
@ -45,7 +44,7 @@ export class Player extends PlayerBase {
(this.aspectRatio = v.aspectRatio), (this.aspectRatio = v.aspectRatio),
[MoveActionCommand.type]: (c: MoveActionCommand) => [MoveActionCommand.type]: (c: MoveActionCommand) =>
this.character?.handleMovementAction(c), this.character?.handleMovementAction(c),
[SecondaryActionCommand.type]: (c: SecondaryActionCommand) => { [PrimaryActionCommand.type]: (c: PrimaryActionCommand) => {
this.character?.shootTowards(c.position); this.character?.shootTowards(c.position);
}, },
}; };
@ -63,7 +62,7 @@ export class Player extends PlayerBase {
socket.on( socket.on(
TransportEvents.Pong, TransportEvents.Pong,
() => (this._latency = getTimeInMilliseconds() - this.pingTime!), () => (this._latency = this.pingDeltaTime.getNextDeltaTimeInSeconds()),
); );
this.measureLatency(); this.measureLatency();
@ -71,8 +70,9 @@ export class Player extends PlayerBase {
} }
public measureLatency() { public measureLatency() {
this.pingTime = getTimeInMilliseconds(); this.pingDeltaTime.getNextDeltaTimeInSeconds(true);
this.socket.emit(TransportEvents.Ping); this.socket.emit(TransportEvents.Ping);
if (this.isActive) { if (this.isActive) {
setTimeout(this.measureLatency.bind(this), 10000); setTimeout(this.measureLatency.bind(this), 10000);
} }
@ -106,7 +106,6 @@ export class Player extends PlayerBase {
this.sumDeaths++; this.sumDeaths++;
this.sumKills = this.character.killCount; this.sumKills = this.character.killCount;
this.queueCommandSend(new PlayerDiedCommand(settings.playerDiedTimeout));
this.character = null; this.character = null;
this.timeUntilRespawn = settings.playerDiedTimeout; this.timeUntilRespawn = settings.playerDiedTimeout;
} }

View file

@ -1,6 +1,6 @@
import { Command, CommandReceiver, serialize, TransportEvents } from 'shared'; import { Command, CommandReceiver, serialize, TransportEvents } from 'shared';
export class CommandReceiverSocket extends CommandReceiver { export class CommandSocket extends CommandReceiver {
constructor(private readonly socket: SocketIOClient.Socket) { constructor(private readonly socket: SocketIOClient.Socket) {
super(); super();
} }

View file

@ -1,29 +0,0 @@
import { vec2 } from 'gl-matrix';
import { CommandGenerator, SecondaryActionCommand, TernaryActionCommand } from 'shared';
import { Game } from '../../game';
export class MouseListener extends CommandGenerator {
constructor(target: HTMLElement, private readonly game: Game) {
super();
target.addEventListener('mousedown', (event: MouseEvent) => {
const position = this.positionFromEvent(event);
if (event.button == 0) {
this.sendCommandToSubscribers(new SecondaryActionCommand(position));
}
});
target.addEventListener('contextmenu', (event: MouseEvent) => {
event.preventDefault();
const position = this.positionFromEvent(event);
this.sendCommandToSubscribers(new TernaryActionCommand(position));
});
}
private positionFromEvent(event: MouseEvent): vec2 {
return this.game.displayToWorldCoordinates(
vec2.fromValues(event.clientX, event.clientY),
);
}
}

View file

@ -1,101 +0,0 @@
import { vec2 } from 'gl-matrix';
import {
CommandGenerator,
SecondaryActionCommand,
MoveActionCommand,
last,
} from 'shared';
import { Game } from '../../game';
export class TouchJoystickListener extends CommandGenerator {
private readonly deadZone = 8;
private readonly deltaScaling = 0.4;
private joystick: HTMLElement;
private joystickButton: HTMLElement;
private isJoystickActive = false;
private touchStartPosition!: vec2;
constructor(
target: HTMLElement,
private overlay: HTMLElement,
private readonly game: Game,
) {
super();
this.joystick = document.createElement('div');
this.joystick.className = 'joystick';
this.joystickButton = document.createElement('div');
this.joystick.appendChild(this.joystickButton);
target.addEventListener('touchstart', (event: TouchEvent) => {
event.preventDefault();
if (this.isJoystickActive) {
const center = vec2.fromValues(
last(event.touches)!.clientX,
last(event.touches)!.clientY,
);
this.sendCommandToSubscribers(
new SecondaryActionCommand(this.game.displayToWorldCoordinates(center)),
);
} else {
this.touchStartPosition = vec2.fromValues(
event.touches[0].clientX,
event.touches[0].clientY,
);
}
});
target.addEventListener('touchmove', (event: TouchEvent) => {
event.preventDefault();
const touchPosition = vec2.fromValues(
event.touches[0].clientX,
event.touches[0].clientY,
);
const delta = vec2.subtract(vec2.create(), touchPosition, this.touchStartPosition);
vec2.scale(delta, delta, this.deltaScaling);
const deltaLength = vec2.length(delta);
if (!this.isJoystickActive && deltaLength > this.deadZone) {
this.isJoystickActive = true;
this.overlay.appendChild(this.joystick);
this.joystickButton.style.transform = `translateX(-50%) translateY(-50%)`;
this.joystick.style.transform = `translateX(${this.touchStartPosition.x}px) translateY(${this.touchStartPosition.y}px) translateX(-50%) translateY(-50%)`;
}
const maxLength = 20;
vec2.scale(delta, delta, Math.min(1, maxLength / deltaLength));
this.joystickButton.style.transform = `translateX(${delta.x}px) translateY(${delta.y}px) translateX(-50%) translateY(-50%)`;
vec2.set(delta, delta.x, -delta.y);
if (deltaLength > this.deadZone) {
this.sendCommandToSubscribers(
new MoveActionCommand(vec2.normalize(delta, delta)),
);
} else {
this.sendCommandToSubscribers(new MoveActionCommand(vec2.create()));
}
});
target.addEventListener('touchend', (event: TouchEvent) => {
event.preventDefault();
if (!this.isJoystickActive) {
const center = vec2.fromValues(
event.changedTouches[0].clientX,
event.changedTouches[0].clientY,
);
this.sendCommandToSubscribers(
new SecondaryActionCommand(this.game.displayToWorldCoordinates(center)),
);
} else if (event.touches.length === 0) {
this.isJoystickActive = false;
this.joystick.parentElement?.removeChild(this.joystick);
this.sendCommandToSubscribers(new MoveActionCommand(vec2.create()));
}
});
}
}

View file

@ -4,22 +4,29 @@ import { CommandGenerator, MoveActionCommand } from 'shared';
export class KeyboardListener extends CommandGenerator { export class KeyboardListener extends CommandGenerator {
private keysDown: Set<string> = new Set(); private keysDown: Set<string> = new Set();
constructor(target: HTMLElement) { constructor() {
super(); super();
target.addEventListener('keydown', (event: KeyboardEvent) => { addEventListener('keydown', this.keyDownListener);
const key = this.normalize(event.key); addEventListener('keyup', this.keyUpListener);
this.keysDown.add(key); addEventListener('blur', this.blurListener);
this.generateCommands();
});
target.addEventListener('keyup', (event: KeyboardEvent) => {
const key = this.normalize(event.key);
this.keysDown.delete(key);
this.generateCommands();
});
} }
private keyDownListener = (event: KeyboardEvent) => {
this.keysDown.add(event.key.toLowerCase());
this.generateCommands();
};
private keyUpListener = (event: KeyboardEvent) => {
this.keysDown.delete(event.key.toLowerCase());
this.generateCommands();
};
private blurListener = () => {
this.keysDown.clear();
this.generateCommands();
};
private generateCommands() { private generateCommands() {
const up = ~~( const up = ~~(
this.keysDown.has('w') || this.keysDown.has('w') ||
@ -34,10 +41,13 @@ export class KeyboardListener extends CommandGenerator {
if (vec2.squaredLength(movement) > 0) { if (vec2.squaredLength(movement) > 0) {
vec2.normalize(movement, movement); vec2.normalize(movement, movement);
} }
this.sendCommandToSubscribers(new MoveActionCommand(movement)); this.sendCommandToSubscribers(new MoveActionCommand(movement));
} }
private normalize(key: string): string { public destroy() {
return key.toLowerCase(); removeEventListener('keydown', this.keyDownListener);
removeEventListener('keyup', this.keyUpListener);
removeEventListener('blur', this.blurListener);
} }
} }

View file

@ -0,0 +1,38 @@
import { vec2 } from 'gl-matrix';
import { CommandGenerator, PrimaryActionCommand, SecondaryActionCommand } from 'shared';
import { Game } from '../game';
export class MouseListener extends CommandGenerator {
constructor(private readonly game: Game) {
super();
addEventListener('mousedown', this.mouseDownListener);
addEventListener('contextmenu', this.contextMenuListener);
}
private mouseDownListener = (event: MouseEvent) => {
const position = this.positionFromEvent(event);
if (event.button === 0) {
this.sendCommandToSubscribers(new PrimaryActionCommand(position));
}
};
private contextMenuListener = (event: MouseEvent) => {
event.preventDefault();
const position = this.positionFromEvent(event);
this.sendCommandToSubscribers(new SecondaryActionCommand(position));
};
private positionFromEvent(event: MouseEvent): vec2 {
return this.game.displayToWorldCoordinates(
vec2.fromValues(event.clientX, event.clientY),
);
}
public destroy() {
removeEventListener('mousedown', this.mouseDownListener);
removeEventListener('contextmenu', this.contextMenuListener);
}
}

View file

@ -0,0 +1,104 @@
import { vec2 } from 'gl-matrix';
import {
CommandGenerator,
SecondaryActionCommand,
MoveActionCommand,
last,
} from 'shared';
import { Game } from '../game';
export class TouchListener extends CommandGenerator {
private static readonly deadZone = 8;
private static readonly deltaScaling = 0.4;
private joystick: HTMLElement;
private joystickButton: HTMLElement;
private isJoystickActive = false;
private touchStartPosition!: vec2;
constructor(private overlay: HTMLElement, private readonly game: Game) {
super();
this.joystick = document.createElement('div');
this.joystick.className = 'joystick';
this.joystickButton = document.createElement('div');
this.joystick.appendChild(this.joystickButton);
addEventListener('touchstart', this.touchStartListener);
addEventListener('touchmove', this.touchMoveListener);
addEventListener('touchend', this.touchEndListener);
}
private touchStartListener = (event: TouchEvent) => {
event.preventDefault();
if (this.isJoystickActive) {
const center = vec2.fromValues(
last(event.touches)!.clientX,
last(event.touches)!.clientY,
);
this.sendCommandToSubscribers(
new SecondaryActionCommand(this.game.displayToWorldCoordinates(center)),
);
} else {
this.touchStartPosition = vec2.fromValues(
event.touches[0].clientX,
event.touches[0].clientY,
);
}
};
private touchMoveListener = (event: TouchEvent) => {
event.preventDefault();
const touchPosition = vec2.fromValues(
event.touches[0].clientX,
event.touches[0].clientY,
);
const delta = vec2.subtract(vec2.create(), touchPosition, this.touchStartPosition);
vec2.scale(delta, delta, TouchListener.deltaScaling);
const deltaLength = vec2.length(delta);
if (!this.isJoystickActive && deltaLength > TouchListener.deadZone) {
this.isJoystickActive = true;
this.overlay.appendChild(this.joystick);
this.joystickButton.style.transform = `translateX(-50%) translateY(-50%)`;
this.joystick.style.transform = `translateX(${this.touchStartPosition.x}px) translateY(${this.touchStartPosition.y}px) translateX(-50%) translateY(-50%)`;
}
const maxLength = 20;
vec2.scale(delta, delta, Math.min(1, maxLength / deltaLength));
this.joystickButton.style.transform = `translateX(${delta.x}px) translateY(${delta.y}px) translateX(-50%) translateY(-50%)`;
vec2.set(delta, delta.x, -delta.y);
if (deltaLength > TouchListener.deadZone) {
this.sendCommandToSubscribers(new MoveActionCommand(vec2.normalize(delta, delta)));
} else {
this.sendCommandToSubscribers(new MoveActionCommand(vec2.create()));
}
};
private touchEndListener = (event: TouchEvent) => {
event.preventDefault();
if (!this.isJoystickActive) {
const center = vec2.fromValues(
event.changedTouches[0].clientX,
event.changedTouches[0].clientY,
);
this.sendCommandToSubscribers(
new SecondaryActionCommand(this.game.displayToWorldCoordinates(center)),
);
} else if (event.touches.length === 0) {
this.isJoystickActive = false;
this.joystick.parentElement?.removeChild(this.joystick);
this.sendCommandToSubscribers(new MoveActionCommand(vec2.create()));
}
};
public destroy() {
removeEventListener('touchstart', this.touchStartListener);
removeEventListener('touchmove', this.touchMoveListener);
removeEventListener('touchend', this.touchEndListener);
}
}

View file

@ -1,8 +0,0 @@
import { vec2 } from 'gl-matrix';
import { Command } from 'shared';
export class MoveToCommand extends Command {
public constructor(public readonly position: vec2) {
super();
}
}

View file

@ -1,8 +0,0 @@
import { Renderer } from 'sdf-2d';
import { Command } from 'shared';
export class RenderCommand extends Command {
public constructor(public readonly renderer: Renderer) {
super();
}
}

View file

@ -1,28 +1,26 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Renderer, renderNoise } from 'sdf-2d'; import { Renderer, renderNoise } from 'sdf-2d';
import { import {
broadcastCommands,
deserialize, deserialize,
TransportEvents, TransportEvents,
SetAspectRatioActionCommand, SetAspectRatioActionCommand,
PlayerInformation, PlayerInformation,
PlayerDiedCommand,
UpdateOtherPlayerDirections, UpdateOtherPlayerDirections,
clamp, clamp,
UpdateGameState, UpdateGameState,
GameEnd, GameEndCommand,
CharacterTeam, CharacterTeam,
ServerAnnouncement, ServerAnnouncement,
GameStart, GameStartCommand,
CommandReceiver, CommandReceiver,
CommandExecutors, CommandExecutors,
Command, Command,
} from 'shared'; } from 'shared';
import io from 'socket.io-client'; import io from 'socket.io-client';
import { KeyboardListener } from './commands/generators/keyboard-listener'; import { KeyboardListener } from './commands/keyboard-listener';
import { MouseListener } from './commands/generators/mouse-listener'; import { MouseListener } from './commands/mouse-listener';
import { TouchJoystickListener } from './commands/generators/touch-joystick-listener'; import { TouchListener } from './commands/touch-listener';
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket'; import { CommandSocket } from './commands/command-socket';
import { startAnimation } from './start-animation'; import { startAnimation } from './start-animation';
import { PlayerDecision } from './join-form-handler'; import { PlayerDecision } from './join-form-handler';
import { GameObjectContainer } from './objects/game-object-container'; import { GameObjectContainer } from './objects/game-object-container';
@ -38,12 +36,16 @@ export class Game extends CommandReceiver {
public started: Promise<void>; public started: Promise<void>;
private resolveStarted!: () => unknown; private resolveStarted!: () => unknown;
private keyboardListener: KeyboardListener;
private mouseListener: MouseListener;
private touchListener: TouchListener;
private declaPlanetCountElement = document.createElement('div'); private declaPlanetCountElement = document.createElement('div');
private redPlanetCountElement = document.createElement('div'); private redPlanetCountElement = document.createElement('div');
private announcementText = document.createElement('h2'); private announcementText = document.createElement('h2');
private progressBar = document.createElement('div'); private progressBar = document.createElement('div');
private arrows: { [id: number]: HTMLElement } = {}; private arrows: { [id: number]: HTMLElement } = {};
private socketReceiver!: CommandReceiverSocket; private socketReceiver!: CommandSocket;
constructor( constructor(
private readonly playerDecision: PlayerDecision, private readonly playerDecision: PlayerDecision,
@ -56,6 +58,10 @@ export class Game extends CommandReceiver {
this.progressBar.className = 'planet-progress'; this.progressBar.className = 'planet-progress';
this.progressBar.appendChild(this.declaPlanetCountElement); this.progressBar.appendChild(this.declaPlanetCountElement);
this.progressBar.appendChild(this.redPlanetCountElement); this.progressBar.appendChild(this.redPlanetCountElement);
this.keyboardListener = new KeyboardListener();
this.mouseListener = new MouseListener(this);
this.touchListener = new TouchListener(this.overlay, this);
} }
private initialize() { private initialize() {
@ -96,15 +102,13 @@ export class Game extends CommandReceiver {
commands.forEach((c) => this.sendCommand(c)); commands.forEach((c) => this.sendCommand(c));
}); });
this.socketReceiver = new CommandReceiverSocket(this.socket); this.socketReceiver = new CommandSocket(this.socket);
broadcastCommands( this.keyboardListener.clearSubscribers();
[ this.keyboardListener.subscribe(this.socketReceiver);
new KeyboardListener(document.body), this.mouseListener.clearSubscribers();
new MouseListener(this.canvas, this), this.mouseListener.subscribe(this.socketReceiver);
new TouchJoystickListener(this.canvas, this.overlay, this), this.touchListener.clearSubscribers();
], this.touchListener.subscribe(this.socketReceiver);
[this.socketReceiver],
);
this.isBetweenGames = false; this.isBetweenGames = false;
@ -123,16 +127,15 @@ export class Game extends CommandReceiver {
protected commandExecutors: CommandExecutors = { protected commandExecutors: CommandExecutors = {
[ServerAnnouncement.type]: (c: ServerAnnouncement) => [ServerAnnouncement.type]: (c: ServerAnnouncement) =>
(this.lastAnnouncementText = c.text), (this.lastAnnouncementText = c.text),
[PlayerDiedCommand.type]: (c: PlayerDiedCommand) => VibrationHandler.vibrate(150),
[UpdateGameState.type]: (c: UpdateGameState) => (this.lastGameState = c), [UpdateGameState.type]: (c: UpdateGameState) => (this.lastGameState = c),
[GameEnd.type]: (c: GameEnd) => { [GameEndCommand.type]: (c: GameEndCommand) => {
const team = `<span class="${c.winningTeam}">${c.winningTeam}</span>`; const team = `<span class="${c.winningTeam}">${c.winningTeam}</span>`;
this.lastAnnouncementText = `Team ${team} won 🎉`; this.lastAnnouncementText = `Team ${team} won 🎉`;
this.isEnding = true; this.isEnding = true;
}, },
[UpdateOtherPlayerDirections.type]: (c: UpdateOtherPlayerDirections) => [UpdateOtherPlayerDirections.type]: (c: UpdateOtherPlayerDirections) =>
(this.lastOtherPlayerDirections = c), (this.lastOtherPlayerDirections = c),
[GameStart.type]: this.initialize.bind(this), [GameStartCommand.type]: this.initialize.bind(this),
}; };
private lastOtherPlayerDirections?: UpdateOtherPlayerDirections; private lastOtherPlayerDirections?: UpdateOtherPlayerDirections;
@ -195,18 +198,19 @@ export class Game extends CommandReceiver {
public async start(): Promise<void> { public async start(): Promise<void> {
const noiseTexture = await renderNoise([256, 256], 2, 1); const noiseTexture = await renderNoise([256, 256], 2, 1);
this.initialize(); this.initialize();
await startAnimation(this.canvas, this.gameLoop.bind(this), noiseTexture); await startAnimation(this.canvas, this.gameLoop.bind(this), noiseTexture);
this.socket.close(); this.socket.close();
this.overlay.innerHTML = ''; this.overlay.innerHTML = '';
this.keyboardListener.destroy();
this.mouseListener.destroy();
this.touchListener.destroy();
} }
public displayToWorldCoordinates(p: vec2): vec2 { public displayToWorldCoordinates(p: vec2): vec2 {
const result = this.renderer?.displayToWorldCoordinates(p); return this.renderer?.displayToWorldCoordinates(p) ?? vec2.create();
if (!result) {
return vec2.create();
}
return result;
} }
public aspectRatioChanged(aspectRatio: number) { public aspectRatioChanged(aspectRatio: number) {

View file

@ -1,6 +1,6 @@
import { ServerInformation, serverInformationEndpoint, TransportEvents } from 'shared'; import { ServerInformation, serverInformationEndpoint, TransportEvents } from 'shared';
import io from 'socket.io-client'; import io from 'socket.io-client';
import { Configuration } from './config/configuration'; import { Configuration } from './configuration';
import parser from 'socket.io-msgpack-parser'; import parser from 'socket.io-msgpack-parser';
import { SoundHandler, Sounds } from './sound-handler'; import { SoundHandler, Sounds } from './sound-handler';

View file

@ -1,16 +1,11 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { CircleLight, Renderer } from 'sdf-2d'; import { CircleLight, Renderer } from 'sdf-2d';
import { CommandExecutors, Id, LampBase, UpdateProperty } from 'shared'; import { CommandExecutors, Id, LampBase, UpdateProperty } from 'shared';
import { RenderCommand } from '../commands/types/render';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
export class LampView extends LampBase implements ViewObject { export class LampView extends LampBase implements ViewObject {
private light: CircleLight; private light: CircleLight;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: (c: RenderCommand) => c.renderer.addDrawable(this.light),
};
constructor(id: Id, center: vec2, color: vec3, lightness: number) { constructor(id: Id, center: vec2, color: vec3, lightness: number) {
super(id, center, color, lightness); super(id, center, color, lightness);
this.light = new CircleLight(center, color, lightness); this.light = new CircleLight(center, color, lightness);

View file

@ -1,7 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { CommandExecutors, Id, Random, PlanetBase, UpdateProperty } from 'shared'; import { Id, Random, PlanetBase, UpdateProperty } from 'shared';
import { RenderCommand } from '../commands/types/render';
import { PlanetShape } from '../shapes/planet-shape'; import { PlanetShape } from '../shapes/planet-shape';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -17,10 +16,6 @@ export class PlanetView extends PlanetBase implements ViewObject {
private shape: PlanetShape; private shape: PlanetShape;
private ownershipProgess: HTMLElement; private ownershipProgess: HTMLElement;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: (c: RenderCommand) => c.renderer.addDrawable(this.shape),
};
constructor(id: Id, vertices: Array<vec2>, ownership: number) { constructor(id: Id, vertices: Array<vec2>, ownership: number) {
super(id, vertices); super(id, vertices);
this.shape = new PlanetShape(vertices, ownership); this.shape = new PlanetShape(vertices, ownership);

View file

@ -19,7 +19,6 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
private nameElement: HTMLElement = document.createElement('div'); private nameElement: HTMLElement = document.createElement('div');
private statsElement: HTMLElement = document.createElement('div'); private statsElement: HTMLElement = document.createElement('div');
private healthElement: HTMLElement = document.createElement('div'); private healthElement: HTMLElement = document.createElement('div');
private previousHealth;
public isMainCharacter = false; public isMainCharacter = false;
@ -45,7 +44,6 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
this.rightFootExtrapolator = new CircleExtrapolator(this.rightFoot!); this.rightFootExtrapolator = new CircleExtrapolator(this.rightFoot!);
this.headExtrapolator = new CircleExtrapolator(this.head!); this.headExtrapolator = new CircleExtrapolator(this.head!);
this.previousHealth = this.health;
this.nameElement.className = 'player-tag ' + this.team; this.nameElement.className = 'player-tag ' + this.team;
this.nameElement.innerText = this.name; this.nameElement.innerText = this.name;
this.nameElement.appendChild(this.healthElement); this.nameElement.appendChild(this.healthElement);
@ -77,17 +75,19 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
Sounds.hit, Sounds.hit,
(0.4 * 2 * (previousHealth - health)) / settings.playerMaxStrength, (0.4 * 2 * (previousHealth - health)) / settings.playerMaxStrength,
); );
if (this.isMainCharacter) {
VibrationHandler.vibrate(Math.min(200, (previousHealth - this.health) * 4));
}
}
public kill() {
if (this.isMainCharacter) {
VibrationHandler.vibrate(150);
}
} }
public step(deltaTimeInSeconds: number): void { public step(deltaTimeInSeconds: number): void {
if (this.previousHealth > this.health) {
if (this.isMainCharacter) {
VibrationHandler.vibrate(Math.min(200, (this.previousHealth - this.health) * 4));
}
this.previousHealth = this.health;
}
this.head! = this.headExtrapolator.getValue(deltaTimeInSeconds); this.head! = this.headExtrapolator.getValue(deltaTimeInSeconds);
this.leftFoot! = this.leftFootExtrapolator.getValue(deltaTimeInSeconds); this.leftFoot! = this.leftFootExtrapolator.getValue(deltaTimeInSeconds);
this.rightFoot! = this.rightFootExtrapolator.getValue(deltaTimeInSeconds); this.rightFoot! = this.rightFootExtrapolator.getValue(deltaTimeInSeconds);

View file

@ -1,7 +0,0 @@
import { CommandReceiver } from './command-receiver';
import { CommandGenerator } from './command-generator';
export const broadcastCommands = (
commandGenerators: Array<CommandGenerator>,
commandReceivers: Array<CommandReceiver>,
) => commandReceivers.forEach((r) => commandGenerators.forEach((g) => g.subscribe(r)));

View file

@ -1,13 +1,17 @@
import { CommandReceiver } from './command-receiver'; import { CommandReceiver } from './command-receiver';
import { Command } from './command'; import { Command } from './command';
export class CommandGenerator { export abstract class CommandGenerator {
private subscribers: Array<CommandReceiver> = []; private subscribers: Array<CommandReceiver> = [];
public subscribe(subscriber: CommandReceiver): void { public subscribe(subscriber: CommandReceiver): void {
this.subscribers.push(subscriber); this.subscribers.push(subscriber);
} }
public clearSubscribers(): void {
this.subscribers = [];
}
protected sendCommandToSubscribers(command: Command): void { protected sendCommandToSubscribers(command: Command): void {
this.subscribers.forEach((s) => s.sendCommand(command)); this.subscribers.forEach((s) => s.sendCommand(command));
} }

View file

@ -14,7 +14,7 @@ export abstract class CommandReceiver {
const commandType = command.type; const commandType = command.type;
if (Object.prototype.hasOwnProperty.call(this.commandExecutors, commandType)) { if (Object.prototype.hasOwnProperty.call(this.commandExecutors, commandType)) {
this.commandExecutors[commandType](command); this.commandExecutors[commandType]!(command);
} else { } else {
this.defaultCommandExecutor(command); this.defaultCommandExecutor(command);
} }

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../../command';
@serializable @serializable
export class MoveActionCommand extends Command { export class MoveActionCommand extends Command {

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../../command';
@serializable @serializable
export class PrimaryActionCommand extends Command { export class PrimaryActionCommand extends Command {

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../../command';
@serializable @serializable
export class SecondaryActionCommand extends Command { export class SecondaryActionCommand extends Command {

View file

@ -1,5 +1,5 @@
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../../command';
@serializable @serializable
export class SetAspectRatioActionCommand extends Command { export class SetAspectRatioActionCommand extends Command {

View file

@ -1,5 +1,5 @@
import { GameObject } from '../../objects/game-object'; import { GameObject } from '../../objects/game-object';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable

View file

@ -1,5 +1,5 @@
import { PlayerCharacterBase } from '../../objects/types/player-character-base'; import { PlayerCharacterBase } from '../../objects/types/player-character-base';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable

View file

@ -1,5 +1,5 @@
import { Id } from '../../transport/identity'; import { Id } from '../../communication/id';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable

View file

@ -1,9 +1,9 @@
import { CharacterTeam } from '../../objects/types/character-team'; import { CharacterTeam } from '../../objects/types/character-team';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable
export class GameEnd extends Command { export class GameEndCommand extends Command {
constructor( constructor(
public readonly winningTeam: CharacterTeam, public readonly winningTeam: CharacterTeam,
public readonly endCardLengthInSeconds: number, public readonly endCardLengthInSeconds: number,

View file

@ -1,8 +1,8 @@
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable
export class GameStart extends Command { export class GameStartCommand extends Command {
constructor() { constructor() {
super(); super();
} }

View file

@ -1,14 +0,0 @@
import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class PlayerDiedCommand extends Command {
public constructor(public readonly timeout: number) {
super();
}
public toArray(): Array<any> {
return [this.timeout];
}
}

View file

@ -0,0 +1,14 @@
import { PropertyUpdatesForObject } from '../../objects/game-object';
import { serializable } from '../../serialization/serializable';
import { Command } from '../command';
@serializable
export class PropertyUpdatesForObjects extends Command {
constructor(public readonly updates: Array<PropertyUpdatesForObject>) {
super();
}
public toArray(): Array<any> {
return [this.updates];
}
}

View file

@ -1,14 +0,0 @@
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
import { RemoteCallsForObject } from './remote-calls-for-objects';
@serializable
export class RemoteCallsForObjects extends Command {
constructor(public readonly callsForObjects: Array<RemoteCallsForObject>) {
super();
}
public toArray(): Array<any> {
return [this.callsForObjects];
}
}

View file

@ -1,15 +1,23 @@
import { Id } from '../../main'; import { Id, RemoteCall } from '../../main';
import { RemoteCall } from '../../objects/game-object'; import { serializable } from '../../serialization/serializable';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable
export class RemoteCallsForObject extends Command { export class RemoteCallsForObject {
constructor(public readonly id: Id, public readonly calls: Array<RemoteCall>) { constructor(public readonly id: Id, public readonly calls: Array<RemoteCall>) {}
super();
}
public toArray(): Array<any> { public toArray(): Array<any> {
return [this.id, this.calls]; return [this.id, this.calls];
} }
} }
@serializable
export class RemoteCallsForObjects extends Command {
constructor(public readonly callsForObjects: Array<RemoteCallsForObject>) {
super();
}
public toArray(): Array<any> {
return [this.callsForObjects];
}
}

View file

@ -1,4 +1,4 @@
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable

View file

@ -1,14 +0,0 @@
import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class TernaryActionCommand extends Command {
public constructor(public readonly position: vec2) {
super();
}
public toArray(): Array<any> {
return [this.position];
}
}

View file

@ -1,4 +1,4 @@
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable

View file

@ -1,7 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Id } from '../../communication/id';
import { CharacterTeam } from '../../objects/types/character-team'; import { CharacterTeam } from '../../objects/types/character-team';
import { Id } from '../../transport/identity'; import { serializable } from '../../serialization/serializable';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable

View file

@ -1,3 +1,5 @@
export type Id = number | null;
let currentId = 0; let currentId = 0;
export const id = (): number => { export const id = (): number => {

View file

@ -1,5 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../transport/serialization/serializable'; import { serializable } from '../serialization/serializable';
@serializable @serializable
export class Circle { export class Circle {

View file

@ -1,6 +1,7 @@
import { vec3 } from 'gl-matrix'; import { vec3 } from 'gl-matrix';
import { rgb } from './rgb'; import { rgb } from './rgb';
// source: https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
export const hsl = (hue: number, saturation: number, lightness: number): vec3 => { export const hsl = (hue: number, saturation: number, lightness: number): vec3 => {
hue /= 360; hue /= 360;
saturation /= 100; saturation /= 100;

View file

@ -1,4 +0,0 @@
export const prettyPrint = (o: any): string =>
JSON.stringify(o, (_, v) => (v.toFixed ? Number(v.toFixed(3)) : v), ' ')
.replace(/("|,|{|^\n)/g, '')
.replace(/(\W*}\n?)+/g, '\n\n');

View file

@ -1,4 +1,4 @@
// src // source
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
// Mulberry32 // Mulberry32

View file

@ -1,5 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../transport/serialization/serializable'; import { serializable } from '../serialization/serializable';
@serializable @serializable
export class Rectangle { export class Rectangle {

View file

@ -1 +0,0 @@
export const toPercent = (value: number) => `${Math.round(value * 100)}%`;

View file

@ -2,24 +2,21 @@ export * from './commands/command';
export * from './commands/types/create-objects'; export * from './commands/types/create-objects';
export * from './commands/types/create-player'; export * from './commands/types/create-player';
export * from './commands/types/delete-objects'; export * from './commands/types/delete-objects';
export * from './commands/types/ternary-action';
export * from './commands/types/move-action';
export * from './commands/types/primary-action';
export * from './commands/types/remote-calls-for-objects'; export * from './commands/types/remote-calls-for-objects';
export * from './commands/types/remote-calls-for-object';
export * from './commands/types/player-died';
export * from './commands/types/server-announcement'; export * from './commands/types/server-announcement';
export * from './commands/types/property-updates-for-objects';
export * from './commands/types/game-end'; export * from './commands/types/game-end';
export * from './commands/types/game-start'; export * from './commands/types/game-start';
export * from './commands/types/update-other-player-directions'; export * from './commands/types/update-other-player-directions';
export * from './commands/types/secondary-action';
export * from './commands/types/update-game-state'; export * from './commands/types/update-game-state';
export * from './commands/command-receiver'; export * from './commands/command-receiver';
export * from './commands/types/update-other-player-directions'; export * from './commands/types/update-other-player-directions';
export * from './commands/command-executors'; export * from './commands/command-executors';
export * from './commands/command-generator'; export * from './commands/command-generator';
export * from './commands/broadcast-commands'; export * from './commands/types/actions/move-action';
export * from './commands/types/set-aspect-ratio-action'; export * from './commands/types/actions/primary-action';
export * from './commands/types/actions/set-aspect-ratio-action';
export * from './commands/types/actions/secondary-action';
export * from './helper/array'; export * from './helper/array';
export * from './helper/last'; export * from './helper/last';
export * from './helper/rgb'; export * from './helper/rgb';
@ -28,27 +25,25 @@ export * from './helper/rgb255';
export * from './helper/mix-rgb'; export * from './helper/mix-rgb';
export * from './helper/clamp'; export * from './helper/clamp';
export * from './helper/calculate-view-area'; export * from './helper/calculate-view-area';
export * from './helper/pretty-print';
export * from './helper/circle'; export * from './helper/circle';
export * from './helper/rectangle'; export * from './helper/rectangle';
export * from './helper/mix'; export * from './helper/mix';
export * from './helper/random'; export * from './helper/random';
export * from './helper/id'; export * from './communication/id';
export * from './communication/server-information'; export * from './communication/server-information';
export * from './communication/player-information'; export * from './communication/player-information';
export * from './helper/rotate-90-deg'; export * from './helper/rotate-90-deg';
export * from './helper/rotate-minus-90-deg'; export * from './helper/rotate-minus-90-deg';
export * from './objects/game-object'; export * from './objects/game-object';
export * from './transport/serialization/deserialize'; export * from './serialization/deserialize';
export * from './transport/serialization/serialize'; export * from './serialization/serialize';
export * from './transport/serialization/serializes-to'; export * from './serialization/serializes-to';
export * from './transport/serialization/serializable'; export * from './serialization/serializable';
export * from './transport/serialization/override-deserialization'; export * from './serialization/override-deserialization';
export * from './objects/types/character-team'; export * from './objects/types/character-team';
export * from './objects/types/player-character-base'; export * from './objects/types/player-character-base';
export * from './objects/types/lamp-base'; export * from './objects/types/lamp-base';
export * from './objects/types/planet-base'; export * from './objects/types/planet-base';
export * from './objects/types/projectile-base'; export * from './objects/types/projectile-base';
export * from './settings'; export * from './settings';
export * from './transport/transport-events'; export * from './communication/transport-events';
export * from './transport/identity';

View file

@ -1,6 +1,6 @@
import { Command } from '../commands/command'; import { Command } from '../commands/command';
import { Id } from '../transport/identity'; import { Id } from '../communication/id';
import { serializable } from '../transport/serialization/serializable'; import { serializable } from '../serialization/serializable';
@serializable @serializable
export class RemoteCall { export class RemoteCall {
@ -33,17 +33,6 @@ export class PropertyUpdatesForObject {
} }
} }
@serializable
export class PropertyUpdatesForObjects extends Command {
constructor(public readonly updates: Array<PropertyUpdatesForObject>) {
super();
}
public toArray(): Array<any> {
return [this.updates];
}
}
export abstract class GameObject { export abstract class GameObject {
private remoteCalls: Array<RemoteCall> = []; private remoteCalls: Array<RemoteCall> = [];

View file

@ -1,9 +1,9 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Random } from '../../helper/random'; import { Random } from '../../helper/random';
import { settings } from '../../settings'; import { settings } from '../../settings';
import { Id } from '../../transport/identity'; import { serializable } from '../../serialization/serializable';
import { serializable } from '../../transport/serialization/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
import { Id } from '../../communication/id';
@serializable @serializable
export class PlanetBase extends GameObject { export class PlanetBase extends GameObject {

View file

@ -1,6 +1,6 @@
import { Id } from '../../communication/id';
import { Circle } from '../../helper/circle'; import { Circle } from '../../helper/circle';
import { Id } from '../../transport/identity'; import { serializable } from '../../serialization/serializable';
import { serializable } from '../../transport/serialization/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
import { CharacterTeam } from './character-team'; import { CharacterTeam } from './character-team';
@ -26,6 +26,8 @@ export class PlayerCharacterBase extends GameObject {
this.health = health; this.health = health;
} }
public kill() {}
public setKillCount(killCount: number) { public setKillCount(killCount: number) {
this.killCount = killCount; this.killCount = killCount;
} }

View file

@ -1,9 +1,9 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { settings } from '../../settings'; import { settings } from '../../settings';
import { Id } from '../../transport/identity'; import { serializable } from '../../serialization/serializable';
import { serializable } from '../../transport/serialization/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
import { CharacterTeam } from './character-team'; import { CharacterTeam } from './character-team';
import { Id } from '../../communication/id';
@serializable @serializable
export class ProjectileBase extends GameObject { export class ProjectileBase extends GameObject {

View file

@ -6,8 +6,7 @@ export const deserialize = (json: string): any => {
const possibleType = v[0]; const possibleType = v[0];
const overridableConstructor = serializableMapping.get(possibleType); const overridableConstructor = serializableMapping.get(possibleType);
if (overridableConstructor) { if (overridableConstructor) {
v.shift(); return new overridableConstructor.constructor(...v.slice(1));
return new overridableConstructor.constructor(...v);
} }
return v; return v;
} }

View file

@ -0,0 +1 @@
export const mangledTypeKey = '__serializable_type';

View file

@ -1,8 +1,5 @@
import { DeserializableClass } from './deserializable-class'; import { DeserializableClass } from './deserializable-class';
/**
* @internal
*/
export const serializableMapping = new Map< export const serializableMapping = new Map<
string, string,
{ {

View file

@ -1,3 +1,4 @@
import { mangledTypeKey } from './mangled-type-key';
import { SerializableClass } from './serializable-class'; import { SerializableClass } from './serializable-class';
import { serializableMapping } from './serializable-mapping'; import { serializableMapping } from './serializable-mapping';
@ -9,8 +10,8 @@ export const serializable = (type: SerializableClass): any => {
}); });
} }
Object.defineProperty(type, '__serializable_type', { value: type.name }); Object.defineProperty(type, mangledTypeKey, { value: type.name });
Object.defineProperty(type.prototype, '__serializable_type', { value: type.name }); Object.defineProperty(type.prototype, mangledTypeKey, { value: type.name });
return type; return type;
}; };

View file

@ -1,8 +1,10 @@
import { mangledTypeKey } from './mangled-type-key';
export const serialize = (object: any): string => { export const serialize = (object: any): string => {
return JSON.stringify(object, (_, value) => { return JSON.stringify(object, (_, value) => {
if (value?.__serializable_type) { if (value && value[mangledTypeKey]) {
const props = value.toArray() as Array<any>; const props = value.toArray() as Array<any>;
props.unshift(value.__serializable_type); props.unshift(value[mangledTypeKey]);
return props; return props;
} }
return value?.toFixed ? Number(value.toFixed(3)) : value; return value?.toFixed ? Number(value.toFixed(3)) : value;

View file

@ -1,3 +1,4 @@
import { mangledTypeKey } from './mangled-type-key';
import { SerializableClass } from './serializable-class'; import { SerializableClass } from './serializable-class';
import { serializableMapping } from './serializable-mapping'; import { serializableMapping } from './serializable-mapping';
@ -10,8 +11,8 @@ export const serializesTo = (target: SerializableClass) => {
}); });
} }
Object.defineProperty(actual, '__serializable_type', { value: target.name }); Object.defineProperty(actual, mangledTypeKey, { value: target.name });
Object.defineProperty(actual.prototype, '__serializable_type', { Object.defineProperty(actual.prototype, mangledTypeKey, {
value: target.name, value: target.name,
}); });

View file

@ -15,7 +15,6 @@ export const settings = {
maxVelocityX: 1000, maxVelocityX: 1000,
maxVelocityY: 1000, maxVelocityY: 1000,
radiusSteps: 500, radiusSteps: 500,
gravityScalingForProjectiles: 5,
spawnDespawnTime: 0.7, spawnDespawnTime: 0.7,
worldRadius: 10000, worldRadius: 10000,
objectsOnCircleLength: 0.002, objectsOnCircleLength: 0.002,
@ -112,5 +111,5 @@ export const settings = {
palette: [declaColor, neutralColor, redColor], palette: [declaColor, neutralColor, redColor],
paletteDim: [declaColorDim, neutralColor, redColorDim], paletteDim: [declaColorDim, neutralColor, redColorDim],
targetPhysicsDeltaTimeInSeconds: 1 / 100, targetPhysicsDeltaTimeInSeconds: 1 / 100,
inViewAreaSize: 1920 * 1080 * 3, inViewAreaSize: 1920 * 1080 * 4,
}; };

View file

@ -1 +0,0 @@
export type Id = number | null;