Change gameplay

This commit is contained in:
schmelczerandras 2020-10-24 22:24:27 +02:00
parent d79900e3ea
commit 34dae300da
56 changed files with 906 additions and 400 deletions

View file

@ -4,6 +4,7 @@ export const defaultOptions: Options = {
port: 3000, port: 3000,
name: 'Localhost', name: 'Localhost',
playerLimit: 16, playerLimit: 16,
seed: 51, seed: Math.random(),
scoreLimit: 500,
worldSize: 10000, worldSize: 10000,
}; };

View file

@ -6,6 +6,12 @@ import {
settings, settings,
ServerInformation, ServerInformation,
PlayerInformation, PlayerInformation,
serializable,
UpdateGameState,
serialize,
CharacterTeam,
GameEnd,
GameStart,
} from 'shared'; } from 'shared';
import { createWorld } from './map/create-world'; import { createWorld } from './map/create-world';
import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DeltaTimeCalculator } from './helper/delta-time-calculator';
@ -15,22 +21,40 @@ import { PlayerContainer } from './players/player-container';
const playerCountSubscribedRoom = 'playerCountUpdates'; const playerCountSubscribedRoom = 'playerCountUpdates';
export class GameServer { export class GameServer {
private objects = new PhysicalContainer(); private objects!: PhysicalContainer;
private players: PlayerContainer; private players!: PlayerContainer;
private deltaTimes: Array<number> = []; private deltaTimes!: Array<number>;
private deltaTimeCalculator = new DeltaTimeCalculator(); private deltaTimeCalculator!: DeltaTimeCalculator;
private declaPoints = 0;
private redPoints = 0;
private isInEndGame = false;
private timeScaling = 1;
private serverName: string; private serverName: string;
private playerLimit: number; private playerLimit: number;
constructor(private readonly io: ioserver.Server, options: Options) { private initialize() {
const previousPlayers = this.players;
this.objects = new PhysicalContainer();
this.players = new PlayerContainer(this.objects); this.players = new PlayerContainer(this.objects);
this.deltaTimeCalculator = new DeltaTimeCalculator();
this.deltaTimes = [];
this.declaPoints = 0;
this.redPoints = 0;
this.isInEndGame = false;
this.timeScaling = 1;
createWorld(this.objects, this.options.worldSize);
this.objects.initialize();
previousPlayers?.sendOnSocket(serialize(new GameStart()));
}
constructor(private readonly io: ioserver.Server, private options: Options) {
this.serverName = options.name; this.serverName = options.name;
this.playerLimit = options.playerLimit; this.playerLimit = options.playerLimit;
createWorld(this.objects, options.worldSize); this.initialize();
this.objects.initialize();
io.on('connection', (socket: SocketIO.Socket) => { io.on('connection', (socket: SocketIO.Socket) => {
socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => { socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => {
@ -40,33 +64,61 @@ export class GameServer {
player.sendCommand(command); player.sendCommand(command);
}); });
this.sendPlayerCountUpdate(); this.sendServerStateUpdate();
socket.on('disconnect', () => { socket.on('disconnect', () => {
player.destroy(); player.destroy();
this.players.deletePlayer(player); this.players.deletePlayer(player);
this.sendPlayerCountUpdate(); this.sendServerStateUpdate();
}); });
}); });
socket.on(TransportEvents.SubscribeForPlayerCount, () => { socket.on(TransportEvents.SubscribeForServerInfoUpdates, () => {
socket.join(playerCountSubscribedRoom); socket.join(playerCountSubscribedRoom);
}); });
}); });
} }
public sendPlayerCountUpdate() { private timeSinceLastServerStateUpdate = 0;
public sendServerStateUpdate() {
this.io this.io
.to(playerCountSubscribedRoom) .to(playerCountSubscribedRoom)
.emit(TransportEvents.PlayerCountUpdate, this.players.count); .emit(TransportEvents.ServerInfoUpdate, [this.players.count, this.gameProgress]);
} }
public start() { public start() {
this.handlePhysics(); this.handlePhysics();
} }
private updatePoints() {
if (this.isInEndGame) {
return;
}
const { decla, red } = this.objects.getPointsGenerated();
this.declaPoints += decla;
this.redPoints += red;
if (this.declaPoints >= this.options.scoreLimit) {
this.endGame(CharacterTeam.decla);
} else if (this.redPoints >= this.options.scoreLimit) {
this.endGame(CharacterTeam.red);
}
}
private endGame(winningTeam: CharacterTeam) {
this.isInEndGame = true;
const endTitleLength = 6000;
this.players.sendOnSocket(
serialize(new GameEnd(winningTeam, endTitleLength / 1000, true)),
);
setTimeout(() => this.destroy(), endTitleLength * 1.1);
}
private destroy() {
this.initialize();
}
private handlePhysics() { private handlePhysics() {
const delta = this.deltaTimeCalculator.getNextDeltaTimeInSeconds(); let delta = this.deltaTimeCalculator.getNextDeltaTimeInSeconds();
const framesBetweenDeltaTimeCalculation = 1000; const framesBetweenDeltaTimeCalculation = 1000;
if (this.deltaTimes.length > framesBetweenDeltaTimeCalculation) { if (this.deltaTimes.length > framesBetweenDeltaTimeCalculation) {
@ -82,8 +134,25 @@ export class GameServer {
this.deltaTimes = []; this.deltaTimes = [];
} }
if ((this.timeSinceLastServerStateUpdate += delta) > 5) {
this.timeSinceLastServerStateUpdate = 0;
this.sendServerStateUpdate();
}
if (this.isInEndGame) {
this.timeScaling *= Math.pow(settings.endGameDeltaScaling, delta);
delta /= this.timeScaling;
}
this.objects.stepObjects(delta); this.objects.stepObjects(delta);
this.updatePoints();
this.players.sendOnSocket(
serialize(
new UpdateGameState(this.declaPoints, this.redPoints, this.options.scoreLimit),
),
);
this.players.step(delta); this.players.step(delta);
this.objects.resetRemoteCalls();
const physicsDelta = this.deltaTimeCalculator.getDeltaTimeInSeconds() * 1000; const physicsDelta = this.deltaTimeCalculator.getDeltaTimeInSeconds() * 1000;
this.deltaTimes.push(physicsDelta); this.deltaTimes.push(physicsDelta);
@ -96,11 +165,16 @@ export class GameServer {
} }
} }
private get gameProgress(): number {
return (Math.max(this.declaPoints, this.redPoints) / this.options.scoreLimit) * 100;
}
public get serverInfo(): ServerInformation { public get serverInfo(): ServerInformation {
return { return {
serverName: this.serverName, serverName: this.serverName,
playerCount: this.players.count, playerCount: this.players.count,
playerLimit: this.playerLimit, playerLimit: this.playerLimit,
gameStatePercent: this.gameProgress,
}; };
} }
} }

View file

@ -4,11 +4,8 @@ import { Server } from 'http';
import cors from 'cors'; import cors from 'cors';
import { applyArrayPlugins, Random, serverInformationEndpoint } from 'shared'; import { applyArrayPlugins, Random, serverInformationEndpoint } from 'shared';
import minimist from 'minimist'; import minimist from 'minimist';
import { glMatrix } from 'gl-matrix'; import { glMatrix } from 'gl-matrix';
import { GameServer } from './game-server'; import { GameServer } from './game-server';
import { defaultOptions } from './default-options'; import { defaultOptions } from './default-options';
glMatrix.setMatrixArrayType(Array); glMatrix.setMatrixArrayType(Array);

View file

@ -1,5 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Circle, GameObject, serializesTo, settings, UpdateObjectMessage } from 'shared'; import { Circle, GameObject, serializesTo, settings } from 'shared';
import { PhysicalBase } from '../physics/physicals/physical-base'; import { PhysicalBase } from '../physics/physicals/physical-base';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';

View file

@ -0,0 +1,8 @@
export interface GeneratesPoints {
getPoints(): {
decla: number;
red: number;
};
}
export const generatesPoints = (a: any): a is GeneratesPoints => a && 'getPoints' in a;

View file

@ -36,6 +36,8 @@ export class LampPhysical extends LampBase implements StaticPhysical {
return vec2.create(); return vec2.create();
} }
public step(deltaTime: number): void {}
public distance(target: vec2): number { public distance(target: vec2): number {
return vec2.distance(this.center, target); return vec2.distance(this.center, target);
} }

View file

@ -10,15 +10,16 @@ import {
settings, settings,
PlanetBase, PlanetBase,
CharacterTeam, CharacterTeam,
UpdateObjectMessage,
} from 'shared'; } from 'shared';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
import { StaticPhysical } from '../physics/physicals/static-physical'; import { StaticPhysical } from '../physics/physicals/static-physical';
import { UpdateGameObjectMessage } from '../update-game-object-message'; import { GeneratesPoints } from './generates-points';
@serializesTo(PlanetBase) @serializesTo(PlanetBase)
export class PlanetPhysical extends PlanetBase implements StaticPhysical { export class PlanetPhysical
extends PlanetBase
implements StaticPhysical, GeneratesPoints {
public readonly canCollide = true; public readonly canCollide = true;
public readonly canMove = false; public readonly canMove = false;
@ -67,41 +68,62 @@ export class PlanetPhysical extends PlanetBase implements StaticPhysical {
return sign * d; return sign * d;
} }
public calculateUpdates(): UpdateObjectMessage { public get team(): CharacterTeam {
return new UpdateGameObjectMessage(this, ['ownership']); return this.ownership === 0.5
? CharacterTeam.neutral
: this.ownership < 0.5
? CharacterTeam.decla
: CharacterTeam.red;
}
private timeSinceLastPointGeneration = 0;
public getPoints(): {
decla: number;
red: number;
} {
if (this.timeSinceLastPointGeneration > settings.planetPointGenerationInterval) {
this.timeSinceLastPointGeneration = 0;
if (this.team !== CharacterTeam.neutral) {
this.remoteCall('generatedPoints', settings.planetPointGenerationValue);
}
return {
decla:
this.team === CharacterTeam.decla ? settings.planetPointGenerationValue : 0,
red: this.team === CharacterTeam.red ? settings.planetPointGenerationValue : 0,
};
}
return {
decla: 0,
red: 0,
};
}
public step(deltaTime: number): void {
this.timeSinceLastPointGeneration += deltaTime;
// In reverse order, so that teams can achieve a 100% control.
this.remoteCall('setOwnership', this.ownership);
this.takeControl(CharacterTeam.neutral, deltaTime);
} }
public takeControl(team: CharacterTeam, deltaTime: number) { public takeControl(team: CharacterTeam, deltaTime: number) {
const previousOwnership = this.ownership;
if (team === CharacterTeam.decla) { if (team === CharacterTeam.decla) {
this.ownership -= (0.5 / settings.takeControlTimeInSeconds) * deltaTime; this.ownership -= (0.5 / settings.takeControlTimeInSeconds) * deltaTime;
if ( } else if (team === CharacterTeam.red) {
previousOwnership >= 0.5 - settings.planetControlThreshold &&
this.ownership < 0.5 - settings.planetControlThreshold
) {
PlanetPhysical.declaPlanetCount++;
PlanetPhysical.neutralPlanetCount--;
} else if (
previousOwnership > 0.5 + settings.planetControlThreshold &&
previousOwnership <= 0.5 + settings.planetControlThreshold
) {
PlanetPhysical.redPlanetCount--;
PlanetPhysical.neutralPlanetCount++;
}
} else {
this.ownership += (0.5 / settings.takeControlTimeInSeconds) * deltaTime; this.ownership += (0.5 / settings.takeControlTimeInSeconds) * deltaTime;
} else {
const previous = this.ownership;
this.ownership +=
-Math.sign(this.ownership - 0.5) *
(0.5 / settings.loseControlTimeInSeconds) *
deltaTime;
if ( if (
previousOwnership < 0.5 + settings.planetControlThreshold && (previous < 0.5 && this.ownership > 0.5) ||
this.ownership >= 0.5 + settings.planetControlThreshold (previous > 0.5 && this.ownership < 0.5)
) { ) {
PlanetPhysical.redPlanetCount++; this.ownership = 0.5;
PlanetPhysical.neutralPlanetCount--;
} else if (
previousOwnership <= 0.5 - settings.planetControlThreshold &&
previousOwnership > 0.5 + settings.planetControlThreshold
) {
PlanetPhysical.declaPlanetCount--;
PlanetPhysical.neutralPlanetCount++;
} }
} }

View file

@ -21,8 +21,6 @@ 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/physicals/reacts-to-collision';
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
import { UpdateGameObjectMessage } from '../update-game-object-message';
@serializesTo(PlayerCharacterBase) @serializesTo(PlayerCharacterBase)
export class PlayerCharacterPhysical export class PlayerCharacterPhysical
@ -133,20 +131,15 @@ export class PlayerCharacterPhysical
); );
} }
public calculateUpdates(): UpdateObjectMessage {
return new UpdateGameObjectMessage(this, [
'head',
'leftFoot',
'rightFoot',
'health',
'killCount',
]);
}
public handleMovementAction(c: MoveActionCommand) { public handleMovementAction(c: MoveActionCommand) {
this.movementActions.push(c); this.movementActions.push(c);
} }
private addKill() {
this.killCount++;
this.remoteCall('setKillCount', this.killCount);
}
public onCollision(other: GameObject) { public onCollision(other: GameObject) {
if ( if (
other instanceof ProjectilePhysical && other instanceof ProjectilePhysical &&
@ -155,9 +148,10 @@ export class PlayerCharacterPhysical
) { ) {
other.destroy(); other.destroy();
this.health -= other.strength; this.health -= other.strength;
this.remoteCall('setHealth', this.health);
if (this.health <= 0) { if (this.health <= 0) {
this.destroy(); this.destroy();
other.originator.killCount++; other.originator.addKill();
} }
} }
} }
@ -182,6 +176,8 @@ export class PlayerCharacterPhysical
this.container, this.container,
); );
this.container.addObject(projectile); this.container.addObject(projectile);
this.remoteCall('onShoot', strength);
} }
public get boundingBox(): BoundingBoxBase { public get boundingBox(): BoundingBoxBase {
@ -299,6 +295,7 @@ export class PlayerCharacterPhysical
this.stepBodyPart(this.leftFoot, deltaTime); this.stepBodyPart(this.leftFoot, deltaTime);
this.stepBodyPart(this.rightFoot, deltaTime); this.stepBodyPart(this.rightFoot, deltaTime);
this.keepPosture(); this.keepPosture();
this.remoteCall('updateCircles', this.head, this.leftFoot, this.rightFoot);
} }
private setDirection(direction: vec2, deltaTime: number) { private setDirection(direction: vec2, deltaTime: number) {

View file

@ -13,8 +13,6 @@ 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/physicals/reacts-to-collision';
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
import { UpdateGameObjectMessage } from '../update-game-object-message';
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';
@ -68,10 +66,6 @@ export class ProjectilePhysical
vec2.add(this.center, this.center, delta); vec2.add(this.center, this.center, delta);
} }
public calculateUpdates(): UpdateObjectMessage {
return new UpdateGameObjectMessage(this, ['center', 'strength']);
}
public get boundingBox(): ImmutableBoundingBox { public get boundingBox(): ImmutableBoundingBox {
if (!this._boundingBox) { if (!this._boundingBox) {
this._boundingBox = (this.object as CirclePhysical).boundingBox; this._boundingBox = (this.object as CirclePhysical).boundingBox;
@ -105,7 +99,9 @@ export class ProjectilePhysical
} }
public step(deltaTime: number) { public step(deltaTime: number) {
if ((this.strength -= settings.projectileFadeSpeed * deltaTime) < 0) { super.step(deltaTime);
if (this.strength <= 0) {
this.destroy(); this.destroy();
return; return;
} }
@ -122,5 +118,7 @@ export class ProjectilePhysical
this.object.velocity = this.velocity; this.object.velocity = this.velocity;
this.object.step2(deltaTime); this.object.step2(deltaTime);
this.remoteCall('setCenter', this.center);
} }
} }

View file

@ -2,6 +2,7 @@ export interface Options {
port: number; port: number;
name: string; name: string;
playerLimit: number; playerLimit: number;
scoreLimit: number;
seed: number; seed: number;
worldSize: number; worldSize: number;
} }

View file

@ -6,12 +6,14 @@ import { BoundingBoxTree } from './bounding-box-tree';
import { Physical } from '../physicals/physical'; import { Physical } from '../physicals/physical';
import { StaticPhysical } from '../physicals/static-physical'; import { StaticPhysical } from '../physicals/static-physical';
import { DynamicPhysical } from '../physicals/dynamic-physical'; import { DynamicPhysical } from '../physicals/dynamic-physical';
import { GeneratesPoints, generatesPoints } from '../../objects/generates-points';
export class PhysicalContainer { export class PhysicalContainer {
private isTreeInitialized = false; private isTreeInitialized = false;
private staticBoundingBoxesWaitList: Array<StaticPhysical> = []; private staticBoundingBoxesWaitList: Array<StaticPhysical> = [];
private staticBoundingBoxes = new BoundingBoxTree(); private staticBoundingBoxes = new BoundingBoxTree();
private dynamicBoundingBoxes = new BoundingBoxList(); private dynamicBoundingBoxes = new BoundingBoxList();
private objects: Array<Physical> = [];
public initialize() { public initialize() {
this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList); this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList);
@ -19,6 +21,8 @@ export class PhysicalContainer {
} }
public addObject(physical: Physical) { public addObject(physical: Physical) {
this.objects.push(physical);
if (physical.canMove) { if (physical.canMove) {
this.dynamicBoundingBoxes.insert(physical); this.dynamicBoundingBoxes.insert(physical);
} else { } else {
@ -31,11 +35,31 @@ export class PhysicalContainer {
} }
public removeObject(object: DynamicPhysical) { public removeObject(object: DynamicPhysical) {
this.objects = this.objects.filter((p) => p !== object);
this.dynamicBoundingBoxes.remove(object); this.dynamicBoundingBoxes.remove(object);
} }
public stepObjects(deltaTime: number) { public stepObjects(deltaTime: number) {
this.dynamicBoundingBoxes.forEach((o) => o.step(deltaTime)); this.objects.forEach((o) => o.step(deltaTime));
}
public resetRemoteCalls() {
this.objects.forEach((o) => o.gameObject.resetRemoteCalls());
}
public getPointsGenerated(): { decla: number; red: number } {
return this.objects
.filter((o) => generatesPoints(o))
.reduce(
(sum, o) => {
const { decla, red } = ((o as unknown) as GeneratesPoints).getPoints();
return {
decla: sum.decla + decla,
red: sum.red + red,
};
},
{ decla: 0, red: 0 },
);
} }
public findIntersecting(box: BoundingBoxBase): Array<Physical> { public findIntersecting(box: BoundingBoxBase): Array<Physical> {

View file

@ -2,5 +2,4 @@ import { PhysicalBase } from './physical-base';
export interface DynamicPhysical extends PhysicalBase { export interface DynamicPhysical extends PhysicalBase {
readonly canMove: true; readonly canMove: true;
step(deltaTimeInMilliseconds: number): void;
} }

View file

@ -9,5 +9,5 @@ export interface PhysicalBase {
readonly gameObject: GameObject; readonly gameObject: GameObject;
distance(target: vec2): number; distance(target: vec2): number;
step(deltaTimeInMilliseconds: number): void; step(deltaTime: number): void;
} }

View file

@ -1,4 +1,4 @@
import { CharacterTeam, PlayerInformation, Random } from 'shared'; import { CharacterTeam, PlayerInformation, Random, TransportEvents } from 'shared';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { Player } from './player'; import { Player } from './player';
@ -32,6 +32,10 @@ export class PlayerContainer {
this.players.forEach((p) => p.step(deltaTimeInSeconds)); this.players.forEach((p) => p.step(deltaTimeInSeconds));
} }
public sendOnSocket(message: any) {
this.players.forEach((p) => p.socket.emit(TransportEvents.ServerToPlayer, message));
}
private getTeamOfNextPlayer(): CharacterTeam { private getTeamOfNextPlayer(): CharacterTeam {
const declaCount = this._players.filter((p) => p.team === CharacterTeam.decla).length; const declaCount = this._players.filter((p) => p.team === CharacterTeam.decla).length;
const redCount = this._players.filter((p) => p.team === CharacterTeam.red).length; const redCount = this._players.filter((p) => p.team === CharacterTeam.red).length;

View file

@ -8,7 +8,6 @@ import {
MoveActionCommand, MoveActionCommand,
serialize, serialize,
TransportEvents, TransportEvents,
UpdateObjectsCommand,
SetAspectRatioActionCommand, SetAspectRatioActionCommand,
calculateViewArea, calculateViewArea,
SecondaryActionCommand, SecondaryActionCommand,
@ -17,11 +16,13 @@ import {
Circle, Circle,
PlayerInformation, PlayerInformation,
CharacterTeam, CharacterTeam,
UpdateGameState, UpdateOtherPlayerDirections,
GameObject, GameObject,
Command, Command,
UpdateObjectMessage,
OtherPlayerDirection, OtherPlayerDirection,
RemoteCallsForObject,
RemoteCallsForObjects,
ServerAnnouncement,
} from 'shared'; } from 'shared';
import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds'; import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
@ -70,7 +71,7 @@ export class Player extends CommandReceiver {
private readonly playerInfo: PlayerInformation, private readonly playerInfo: PlayerInformation,
private readonly playerContainer: PlayerContainer, private readonly playerContainer: PlayerContainer,
private readonly objectContainer: PhysicalContainer, private readonly objectContainer: PhysicalContainer,
private readonly socket: SocketIO.Socket, public readonly socket: SocketIO.Socket,
public readonly team: CharacterTeam, public readonly team: CharacterTeam,
) { ) {
super(); super();
@ -123,8 +124,15 @@ export class Player extends CommandReceiver {
this.character = null; this.character = null;
this.timeUntilRespawn = settings.playerDiedTimeout; this.timeUntilRespawn = settings.playerDiedTimeout;
} }
} else if ((this.timeUntilRespawn -= deltaTimeInSeconds) < 0) { } else {
this.sendToPlayer(
new ServerAnnouncement(`Reviving in ${Math.round(this.timeUntilRespawn)}`),
);
if ((this.timeUntilRespawn -= deltaTimeInSeconds) < 0) {
this.createCharacter(); this.createCharacter();
this.center = this.character!.center;
this.sendToPlayer(new ServerAnnouncement(''));
}
} }
const viewArea = calculateViewArea(this.center, this.aspectRatio, 1.5); const viewArea = calculateViewArea(this.center, this.aspectRatio, 1.5);
@ -155,21 +163,14 @@ export class Player extends CommandReceiver {
} }
this.sendToPlayer( this.sendToPlayer(
new UpdateObjectsCommand( new RemoteCallsForObjects(
this.objectsPreviouslyInViewArea this.objectsPreviouslyInViewArea.map(
.map((g) => g.calculateUpdates()) (g) => new RemoteCallsForObject(g.id, g.getRemoteCalls()),
.filter((u) => u) as Array<UpdateObjectMessage>, ),
), ),
); );
this.sendToPlayer( this.sendToPlayer(new UpdateOtherPlayerDirections(this.getOtherPlayers()));
new UpdateGameState(
PlanetPhysical.declaPlanetCount,
PlanetPhysical.redPlanetCount,
PlanetPhysical.neutralPlanetCount,
this.getOtherPlayers(),
),
);
} }
private findEmptyPositionForPlayer(): vec2 { private findEmptyPositionForPlayer(): vec2 {
@ -223,7 +224,7 @@ export class Player extends CommandReceiver {
.filter((g) => g instanceof PlayerCharacterPhysical); .filter((g) => g instanceof PlayerCharacterPhysical);
const otherPlayers = this.playerContainer.players.filter( const otherPlayers = this.playerContainer.players.filter(
(p) => playersInViewArea.indexOf(p.character!) < 0, (p) => p.character?.isAlive && playersInViewArea.indexOf(p.character!) < 0,
); );
return otherPlayers.map( return otherPlayers.map(
@ -244,7 +245,6 @@ export class Player extends CommandReceiver {
public destroy() { public destroy() {
this.isActive = false; this.isActive = false;
this.character?.destroy(); this.character?.destroy();
} }
} }

View file

@ -1,11 +0,0 @@
import { GameObject, serializesTo, UpdateMessage, UpdateObjectMessage } from 'shared';
@serializesTo(UpdateObjectMessage)
export class UpdateGameObjectMessage<T extends GameObject> extends UpdateObjectMessage {
constructor(object: T, keys: Array<string & keyof T>) {
super(
object.id,
keys.map((k) => new UpdateMessage(k, object[k])),
);
}
}

View file

@ -108,7 +108,12 @@ const applyServerContainerShadows = () => {
const main = async () => { const main = async () => {
try { try {
let game: Game; let game: Game;
const firstClickListener = () => {
SoundHandler.initialize(); SoundHandler.initialize();
document.removeEventListener('click', firstClickListener);
};
document.addEventListener('click', firstClickListener);
handleFullScreen(minimize, maximize); handleFullScreen(minimize, maximize);
toggleSettingsButton.addEventListener('click', toggleSettings); toggleSettingsButton.addEventListener('click', toggleSettings);
@ -134,7 +139,7 @@ const main = async () => {
for (;;) { for (;;) {
show(spinner); show(spinner);
hide(logoutButton); hide(logoutButton, true);
show(landingUI, true, 'flex'); show(landingUI, true, 'flex');
const background = new LandingPageBackground(canvas); const background = new LandingPageBackground(canvas);
@ -156,7 +161,7 @@ const main = async () => {
await game.started; await game.started;
isInGame = true; isInGame = true;
hide(spinner); hide(spinner);
show(logoutButton); show(logoutButton, true, 'block');
await gameOver; await gameOver;
isInGame = false; isInGame = false;
} }

View file

@ -37,7 +37,9 @@ h1 {
font-size: 6rem; font-size: 6rem;
text-align: center; text-align: center;
padding-bottom: $medium-padding; padding-bottom: $medium-padding;
@media (max-width: $height-breakpoint) {
font-size: 4.5rem;
}
@media (max-height: $height-breakpoint) { @media (max-height: $height-breakpoint) {
display: none; display: none;
} }
@ -163,11 +165,23 @@ body {
font-size: 0; font-size: 0;
transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%);
box-shadow: inset 0 0 3px 0px rgba(0, 0, 0, 0.2);
@include square(50px); @include square(50px);
border-radius: 1000px; border-radius: 1000px;
mask-image: url('../static/mask.svg'); mask-image: url('../static/mask.svg');
} }
.falling-point {
font-size: 1.2em;
&.decla {
color: $bright-decla;
}
&.red {
color: $bright-red;
}
}
.other-player-arrow { .other-player-arrow {
@include square($large-icon); @include square($large-icon);
@media (max-width: $breakpoint) { @media (max-width: $breakpoint) {
@ -205,18 +219,29 @@ body {
} }
.announcement { .announcement {
top: 50%; top: 25%;
left: 50%; transform: translateX(calc(-50% + 50vw)) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
font-size: 3rem; font-size: 3rem;
@include background; @include background;
z-index: 1000; z-index: 1000;
padding: $medium-padding; padding: $medium-padding;
border-radius: 16px; border-radius: 16px;
&:empty {
display: none;
}
.decla {
color: $bright-decla;
}
.red {
color: $bright-red;
}
} }
.planet-progress { .planet-progress {
position: absolute;
top: $small-padding; top: $small-padding;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
@ -225,26 +250,50 @@ body {
$height: 8px; $height: 8px;
height: $height; height: $height;
justify-content: space-between;
box-shadow: inset 0 0 3px 0px rgba(0, 0, 0, 0.2);
border-radius: 4px; border-radius: 4px;
z-index: 100; z-index: 100;
div { &::before {
height: $height; content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
background-color: #888;
height: 24px;
width: 4px;
border-radius: 1000px;
} }
border-radius: 100px; &::after {
overflow: hidden; content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(50%);
@include square(24px);
background-image: url('../static/flag.svg');
background-size: contain;
}
div {
height: $height;
box-shadow: inset 0 0 3px 0px rgba(0, 0, 0, 0.2);
}
div:nth-child(1) { div:nth-child(1) {
background: $bright-decla; background: $bright-decla;
border-radius: 100px 0 0 100px;
} }
div:nth-child(2) { div:nth-child(2) {
background: $bright-neutral;
}
div:nth-child(3) {
background: $bright-red; background: $bright-red;
border-radius: 0 100px 100px 0;
} }
} }
} }

View file

@ -1,100 +1,140 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { import { Renderer, renderNoise } from 'sdf-2d';
CircleLight,
ColorfulCircle,
FilteringOptions,
Flashlight,
Renderer,
renderNoise,
runAnimation,
WrapOptions,
} from 'sdf-2d';
import { import {
broadcastCommands, broadcastCommands,
deserialize, deserialize,
serialize, serialize,
settings,
TransportEvents, TransportEvents,
SetAspectRatioActionCommand, SetAspectRatioActionCommand,
PlayerInformation, PlayerInformation,
PlayerDiedCommand, PlayerDiedCommand,
UpdateGameState, UpdateOtherPlayerDirections,
clamp, clamp,
UpdateGameState,
GameEnd,
CharacterTeam,
ServerAnnouncement,
GameStart,
CommandReceiver,
CommandExecutors,
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/generators/keyboard-listener';
import { MouseListener } from './commands/generators/mouse-listener'; import { MouseListener } from './commands/generators/mouse-listener';
import { TouchJoystickListener } from './commands/generators/touch-joystick-listener'; import { TouchJoystickListener } from './commands/generators/touch-joystick-listener';
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket'; import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
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';
import { OptionsHandler } from './options-handler'; import { OptionsHandler } from './options-handler';
import { BlobShape } from './shapes/blob-shape';
import { PlanetShape } from './shapes/planet-shape';
export class Game { export class Game extends CommandReceiver {
public readonly gameObjects = new GameObjectContainer(this); public gameObjects = new GameObjectContainer(this);
public renderer?: Renderer; public renderer?: Renderer;
private socket!: SocketIOClient.Socket; private socket!: SocketIOClient.Socket;
private deadTimeout = 0; private isBetweenGames = false;
public started: Promise<void>;
private resolveStarted!: () => unknown;
private declaPlanetCountElement = document.createElement('div'); private declaPlanetCountElement = document.createElement('div');
private redPlanetCountElement = document.createElement('div'); private redPlanetCountElement = document.createElement('div');
private neutralPlanetCountElement = document.createElement('div');
private announcementText = document.createElement('h2'); private announcementText = document.createElement('h2');
private progressBar = document.createElement('div');
private arrowElements: Array<HTMLElement> = [];
constructor( constructor(
private readonly playerDecision: PlayerDecision, private readonly playerDecision: PlayerDecision,
private readonly canvas: HTMLCanvasElement, private readonly canvas: HTMLCanvasElement,
private readonly overlay: HTMLElement, private readonly overlay: HTMLElement,
) { ) {
super();
this.started = new Promise((r) => (this.resolveStarted = r));
this.announcementText.className = 'announcement'; this.announcementText.className = 'announcement';
const progressBar = document.createElement('div'); this.progressBar.className = 'planet-progress';
progressBar.className = 'planet-progress'; this.progressBar.appendChild(this.declaPlanetCountElement);
overlay.appendChild(progressBar); this.progressBar.appendChild(this.redPlanetCountElement);
progressBar.appendChild(this.declaPlanetCountElement);
progressBar.appendChild(this.neutralPlanetCountElement);
progressBar.appendChild(this.redPlanetCountElement);
} }
private arrowElements: Array<HTMLElement> = []; private initialize() {
private async setupCommunication(serverUrl: string): Promise<void> { this.isBetweenGames = true;
this.socket = io(serverUrl, {
this.socket?.close();
this.gameObjects = new GameObjectContainer(this);
this.overlay.innerHTML = '';
this.overlay.appendChild(this.progressBar);
this.announcementText.innerText = '';
this.overlay.appendChild(this.announcementText);
this.socket = io(this.playerDecision.server, {
reconnectionDelayMax: 10000, reconnectionDelayMax: 10000,
transports: ['websocket'], transports: ['websocket'],
forceNew: true,
}); });
this.socket.on('reconnect_attempt', () => { this.socket.on('reconnect_attempt', () => {
this.socket.io.opts.transports = ['polling', 'websocket']; this.socket.io.opts.transports = ['polling', 'websocket'];
}); });
this.socket.on('disconnect', this.destroy.bind(this)); this.socket.on('disconnect', () => {
if (!this.isBetweenGames) {
this.destroy();
}
});
this.socket.on(TransportEvents.ServerToPlayer, (serialized: string) => { this.socket.on(TransportEvents.Ping, () => {
const command = deserialize(serialized); this.socket.emit(TransportEvents.Pong);
if (command instanceof PlayerDiedCommand) { });
this.deadTimeout = command.timeout;
this.socket.on(TransportEvents.ServerToPlayer, (serialized: string) =>
this.sendCommand(deserialize(serialized)),
);
broadcastCommands(
[
new KeyboardListener(document.body),
new MouseListener(this.canvas, this),
new TouchJoystickListener(this.canvas, this.overlay, this),
],
[this.gameObjects, new CommandReceiverSocket(this.socket)],
);
this.isBetweenGames = false;
this.socket.emit(TransportEvents.PlayerJoining, {
name: this.playerDecision.playerName,
} as PlayerInformation);
}
protected defaultCommandExecutor(c: Command) {
this.gameObjects.sendCommand(c);
}
protected commandExecutors: CommandExecutors = {
[ServerAnnouncement.type]: (c: ServerAnnouncement) =>
(this.announcementText.innerText = c.text),
[PlayerDiedCommand.type]: (c: PlayerDiedCommand) => {
if (OptionsHandler.options.vibrationEnabled) { if (OptionsHandler.options.vibrationEnabled) {
navigator.vibrate(150); navigator.vibrate(150);
} }
this.overlay.appendChild(this.announcementText); },
} else if (command instanceof UpdateGameState) { [UpdateGameState.type]: (c: UpdateGameState) => {
const all = command.declaCount + command.redCount + command.neutralCount; this.declaPlanetCountElement.style.width = (c.declaCount / c.limit) * 50 + '%';
this.declaPlanetCountElement.style.width = (command.declaCount / all) * 100 + '%'; this.redPlanetCountElement.style.width = (c.redCount / c.limit) * 50 + '%';
this.neutralPlanetCountElement.style.width = },
(command.neutralCount / all) * 100 + '%'; [GameEnd.type]: (c: GameEnd) => {
this.redPlanetCountElement.style.width = (command.redCount / all) * 100 + '%'; const team =
c.winningTeam === CharacterTeam.decla
if (command.declaCount > all * 0.5) { ? '<span class="decla">decla</span>'
this.overlay.appendChild(this.announcementText); : '<span class="red">red</span>';
this.announcementText.innerText = 'Decla team won 🎉'; this.announcementText.innerHTML = `Team ${team} won 🎉`;
} },
[UpdateOtherPlayerDirections.type]: this.handleOtherPlayerDirections.bind(this),
if (command.redCount > all * 0.5) { [GameStart.type]: this.initialize.bind(this),
this.overlay.appendChild(this.announcementText); };
this.announcementText.innerText = 'Red team won 🎉';
}
private handleOtherPlayerDirections(command: UpdateOtherPlayerDirections) {
this.arrowElements this.arrowElements
.splice(command.otherPlayerDirections.length, this.arrowElements.length) .splice(command.otherPlayerDirections.length, this.arrowElements.length)
.forEach((e) => e.parentElement?.removeChild(e)); .forEach((e) => e.parentElement?.removeChild(e));
@ -141,76 +181,12 @@ export class Game {
p.y p.y
}px) translateX(-50%) translateY(-50%) rotate(${-angle + Math.PI / 2}rad) `; }px) translateX(-50%) translateY(-50%) rotate(${-angle + Math.PI / 2}rad) `;
}); });
} else this.gameObjects.sendCommand(command);
});
this.socket.on(TransportEvents.Ping, () => {
this.socket.emit(TransportEvents.Pong);
});
this.socket.emit(TransportEvents.PlayerJoining, {
name: this.playerDecision.playerName,
} as PlayerInformation);
broadcastCommands(
[
new KeyboardListener(document.body),
new MouseListener(this.canvas, this),
new TouchJoystickListener(this.canvas, this.overlay, this),
],
[this.gameObjects, new CommandReceiverSocket(this.socket)],
);
} }
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.setupCommunication(this.playerDecision.server); this.initialize();
await runAnimation( await startAnimation(this.canvas, this.gameLoop.bind(this), noiseTexture);
this.canvas,
[
{
...PlanetShape.descriptor,
shaderCombinationSteps: [0, 1, 2, 3],
},
{
...BlobShape.descriptor,
shaderCombinationSteps: [0, 1, 2, 8],
},
{
...ColorfulCircle.descriptor,
shaderCombinationSteps: [0, 2, 16],
},
{
...CircleLight.descriptor,
shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
},
{
...Flashlight.descriptor,
shaderCombinationSteps: [0],
},
],
this.gameLoop.bind(this),
{
shadowTraceCount: 16,
paletteSize: settings.palette.length,
//enableStopwatch: true,
},
{
colorPalette: settings.palette,
enableHighDpiRendering: true,
lightCutoffDistance: settings.lightCutoffDistance,
textures: {
noiseTexture: {
source: noiseTexture,
overrides: {
maxFilter: FilteringOptions.LINEAR,
wrapS: WrapOptions.MIRRORED_REPEAT,
wrapT: WrapOptions.MIRRORED_REPEAT,
},
},
},
},
);
this.socket.close(); this.socket.close();
this.overlay.innerHTML = ''; this.overlay.innerHTML = '';
} }
@ -237,17 +213,13 @@ export class Game {
private gameLoop( private gameLoop(
renderer: Renderer, renderer: Renderer,
currentTime: DOMHighResTimeStamp, _: DOMHighResTimeStamp,
deltaTime: DOMHighResTimeStamp, deltaTime: DOMHighResTimeStamp,
): boolean { ): boolean {
this.resolveStarted();
deltaTime /= 1000;
this.renderer = renderer; this.renderer = renderer;
if ((this.deadTimeout -= deltaTime / 1000) > 0) {
this.announcementText.innerText = `Reviving in ${Math.floor(this.deadTimeout)}`;
} else {
this.announcementText.parentElement?.removeChild(this.announcementText);
}
this.gameObjects.stepObjects(deltaTime); this.gameObjects.stepObjects(deltaTime);
this.gameObjects.drawObjects(this.renderer, this.overlay); this.gameObjects.drawObjects(this.renderer, this.overlay);

View file

@ -95,6 +95,9 @@ class ServerChooserOption {
private divElement = document.createElement('div'); private divElement = document.createElement('div');
private inputElement = document.createElement('input'); private inputElement = document.createElement('input');
private labelElement = document.createElement('label'); private labelElement = document.createElement('label');
private serverNameElement = document.createElement('span');
private completionElement = document.createElement('span');
private socket: SocketIOClient.Socket; private socket: SocketIOClient.Socket;
constructor( constructor(
@ -111,7 +114,11 @@ class ServerChooserOption {
this.labelElement.htmlFor = url; this.labelElement.htmlFor = url;
this.divElement.appendChild(this.inputElement); this.divElement.appendChild(this.inputElement);
this.divElement.appendChild(this.labelElement); this.divElement.appendChild(this.labelElement);
this.setPlayerLabelText(); this.labelElement.appendChild(this.serverNameElement);
this.labelElement.appendChild(document.createElement('br'));
this.labelElement.appendChild(this.completionElement);
this.completionElement.className = 'completion';
this.setServerInfoLabelText();
this.socket = io(url, { this.socket = io(url, {
reconnection: false, reconnection: false,
@ -121,11 +128,15 @@ class ServerChooserOption {
this.socket.on('connect_error', this.destroy.bind(this)); this.socket.on('connect_error', this.destroy.bind(this));
this.socket.on('connect_timeout', this.destroy.bind(this)); this.socket.on('connect_timeout', this.destroy.bind(this));
this.socket.on('disconnect', this.destroy.bind(this)); this.socket.on('disconnect', this.destroy.bind(this));
this.socket.emit(TransportEvents.SubscribeForPlayerCount); this.socket.emit(TransportEvents.SubscribeForServerInfoUpdates);
this.socket.on(TransportEvents.PlayerCountUpdate, (v: number) => { this.socket.on(
this.content.playerCount = v; TransportEvents.ServerInfoUpdate,
this.setPlayerLabelText(); ([playerCount, gameState]: [number, number]) => {
}); this.content.playerCount = playerCount;
this.content.gameStatePercent = gameState;
this.setServerInfoLabelText();
},
);
} }
public destroy() { public destroy() {
@ -134,8 +145,25 @@ class ServerChooserOption {
this.onDestroy(this); this.onDestroy(this);
} }
private setPlayerLabelText() { private getRoundCompletionText(percent: number): string {
this.labelElement.innerText = `${this.content.serverName} - ${this.content.playerCount}/${this.content.playerLimit} players`; const texts = [
'Just started',
'Just started',
'Ongoing',
'Halfway through',
'Nearly over',
'About to finish',
'Game is over',
];
return texts[Math.floor((percent / 100) * (texts.length - 1))];
}
private setServerInfoLabelText() {
this.serverNameElement.innerText = `${this.content.serverName} - ${this.content.playerCount}/${this.content.playerLimit} players`;
this.completionElement.innerText = this.getRoundCompletionText(
this.content.gameStatePercent,
);
} }
public get element(): HTMLElement { public get element(): HTMLElement {

View file

@ -16,7 +16,7 @@ export class Camera extends GameObject implements ViewObject {
public beforeDestroy(): void {} public beforeDestroy(): void {}
public step(deltaTimeInMilliseconds: number): void {} public step(deltaTimeInSeconds: number): void {}
public draw(renderer: Renderer, overlay: HTMLElement) { public draw(renderer: Renderer, overlay: HTMLElement) {
const canvasAspectRatio = renderer.canvasSize.x / renderer.canvasSize.y; const canvasAspectRatio = renderer.canvasSize.x / renderer.canvasSize.y;

View file

@ -33,7 +33,7 @@ export class CharacterView extends CharacterBase implements ViewObject {
public beforeDestroy(): void {} public beforeDestroy(): void {}
public step(deltaTimeInMilliseconds: number): void {} public step(deltaTimeInSeconds: number): void {}
public draw(renderer: Renderer, overlay: HTMLElement): void { public draw(renderer: Renderer, overlay: HTMLElement): void {
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]); this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);

View file

@ -5,9 +5,8 @@ import {
CreateObjectsCommand, CreateObjectsCommand,
CreatePlayerCommand, CreatePlayerCommand,
DeleteObjectsCommand, DeleteObjectsCommand,
GameObject,
Id, Id,
UpdateObjectsCommand, RemoteCallsForObjects,
} from 'shared'; } from 'shared';
import { Game } from '../game'; import { Game } from '../game';
import { Camera } from './camera'; import { Camera } from './camera';
@ -24,8 +23,11 @@ export class GameObjectContainer extends CommandReceiver {
if (this.camera) { if (this.camera) {
this.deleteObject(this.camera.id); this.deleteObject(this.camera.id);
} }
this.player = c.character as PlayerCharacterView; this.player = c.character as PlayerCharacterView;
this.camera = new Camera(this.game); this.camera = new Camera(this.game);
this.addObject(this.player); this.addObject(this.player);
this.addObject(this.camera); this.addObject(this.camera);
}, },
@ -33,24 +35,25 @@ export class GameObjectContainer extends CommandReceiver {
[CreateObjectsCommand.type]: (c: CreateObjectsCommand) => [CreateObjectsCommand.type]: (c: CreateObjectsCommand) =>
c.objects.forEach((o) => this.addObject(o as ViewObject)), c.objects.forEach((o) => this.addObject(o as ViewObject)),
[RemoteCallsForObjects.type]: (c: RemoteCallsForObjects) =>
c.callsForObjects.forEach((c) =>
this.objects.get(c.id)?.processRemoteCalls(c.calls),
),
[DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) => [DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) =>
c.ids.forEach((id: Id) => this.deleteObject(id)), c.ids.forEach((id: Id) => this.deleteObject(id)),
[UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => {
c.updates.forEach((u) => this.objects.get(u.id)?.update(u.updates));
},
}; };
constructor(private game: Game) { constructor(private game: Game) {
super(); super();
} }
public stepObjects(delta: number) { public stepObjects(deltaTimeInSeconds: number) {
if (this.player) { if (this.player) {
this.camera.center = this.player.position; this.camera.center = this.player.position;
} }
this.objects.forEach((o) => o.step(delta)); this.objects.forEach((o) => o.step(deltaTimeInSeconds));
} }
public drawObjects(renderer: Renderer, overlay: HTMLElement) { public drawObjects(renderer: Renderer, overlay: HTMLElement) {

View file

@ -16,7 +16,7 @@ export class LampView extends LampBase implements ViewObject {
this.light = new CircleLight(center, color, lightness); this.light = new CircleLight(center, color, lightness);
} }
public step(deltaTimeInMilliseconds: number): void {} public step(deltaTimeInSeconds: number): void {}
public beforeDestroy(): void {} public beforeDestroy(): void {}

View file

@ -5,6 +5,14 @@ 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';
type FallingPoint = {
velocity: vec2;
position: vec2;
element: HTMLElement;
addedToOverlay: boolean;
timeToLive: number;
};
export class PlanetView extends PlanetBase implements ViewObject { export class PlanetView extends PlanetBase implements ViewObject {
private shape: PlanetShape; private shape: PlanetShape;
private ownershipProgess: HTMLElement; private ownershipProgess: HTMLElement;
@ -22,9 +30,48 @@ export class PlanetView extends PlanetBase implements ViewObject {
this.ownershipProgess.className = 'ownership'; this.ownershipProgess.className = 'ownership';
} }
public step(deltaTimeInMilliseconds: number): void { public step(deltaTimeInSeconds: number): void {
this.shape.randomOffset += deltaTimeInMilliseconds / 4000; this.shape.randomOffset += deltaTimeInSeconds / 4;
this.shape.colorMixQ = this.ownership; this.shape.colorMixQ = this.ownership;
this.generatedPointElements.forEach((p) => {
vec2.add(
p.velocity,
p.velocity,
vec2.scale(vec2.create(), vec2.fromValues(0, 50), deltaTimeInSeconds),
);
vec2.add(
p.position,
p.position,
vec2.scale(vec2.create(), p.velocity, deltaTimeInSeconds),
);
if ((p.timeToLive -= deltaTimeInSeconds) <= 0) {
p.element.parentElement?.removeChild(p.element);
} else {
p.element.style.opacity = Math.min(1, p.timeToLive).toString();
}
});
this.generatedPointElements = this.generatedPointElements.filter(
(p) => p.timeToLive > 0,
);
}
private generatedPointElements: Array<FallingPoint> = [];
public generatedPoints(value: number) {
const element = document.createElement('div');
element.className = 'falling-point ' + (this.ownership < 0.5 ? 'decla' : 'red');
element.innerText = '+' + value;
this.generatedPointElements.push({
element,
addedToOverlay: false,
timeToLive: Random.getRandomInRange(2, 3),
position: vec2.create(),
velocity: vec2.fromValues(Random.getRandomInRange(-30, 30), 0),
});
} }
private getGradient(): string { private getGradient(): string {
@ -34,18 +81,21 @@ export class PlanetView extends PlanetBase implements ViewObject {
? `conic-gradient( ? `conic-gradient(
var(--bright-decla) ${sidePercent}%, var(--bright-decla) ${sidePercent}%,
var(--bright-decla) ${sidePercent}%, var(--bright-decla) ${sidePercent}%,
var(--bright-neutral) ${sidePercent}%, rgba(0, 0, 0, 0) ${sidePercent}%,
var(--bright-neutral) 100% rgba(0, 0, 0, 0) 100%
)` )`
: `conic-gradient( : `conic-gradient(
var(--bright-neutral) 0%, rgba(0, 0, 0, 0) 0%,
var(--bright-neutral) ${100 - sidePercent}%, rgba(0, 0, 0, 0) ${100 - sidePercent}%,
var(--bright-red) ${100 - sidePercent}%, var(--bright-red) ${100 - sidePercent}%,
var(--bright-red) 100% var(--bright-red) 100%
)`; )`;
} }
public beforeDestroy(): void { public beforeDestroy(): void {
this.ownershipProgess.parentElement?.removeChild(this.ownershipProgess); this.ownershipProgess.parentElement?.removeChild(this.ownershipProgess);
this.generatedPointElements.forEach((p) =>
p.element.parentElement?.removeChild(p.element),
);
} }
public draw(renderer: Renderer, overlay: HTMLElement): void { public draw(renderer: Renderer, overlay: HTMLElement): void {
@ -54,6 +104,16 @@ export class PlanetView extends PlanetBase implements ViewObject {
} }
const screenPosition = renderer.worldToDisplayCoordinates(this.center); const screenPosition = renderer.worldToDisplayCoordinates(this.center);
this.generatedPointElements.forEach((p) => {
if (!p.addedToOverlay) {
overlay.appendChild(p.element);
}
p.element.style.left = screenPosition.x + p.position.x + 'px';
p.element.style.top = screenPosition.y + p.position.y + 'px';
});
this.ownershipProgess.style.left = screenPosition.x + 'px'; this.ownershipProgess.style.left = screenPosition.x + 'px';
this.ownershipProgess.style.top = screenPosition.y + 'px'; this.ownershipProgess.style.top = screenPosition.y + 'px';
this.ownershipProgess.style.background = this.getGradient(); this.ownershipProgess.style.background = this.getGradient();

View file

@ -4,6 +4,7 @@ import { Circle, Id, PlayerCharacterBase, CharacterTeam, settings } from 'shared
import { OptionsHandler } from '../options-handler'; import { OptionsHandler } from '../options-handler';
import { BlobShape } from '../shapes/blob-shape'; import { BlobShape } from '../shapes/blob-shape';
import { SoundHandler, Sounds } from '../sound-handler';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
export class PlayerCharacterView extends PlayerCharacterBase implements ViewObject { export class PlayerCharacterView extends PlayerCharacterBase implements ViewObject {
@ -39,8 +40,17 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
return this.head!.center; return this.head!.center;
} }
public step(deltaTimeInMilliseconds: number): void { public setHealth(health: number) {
this.timeSinceLastNameElementUpdate += deltaTimeInMilliseconds; const previousHealth = this.health;
super.setHealth(health);
SoundHandler.play(
Sounds.hit,
(0.4 * 2 * (previousHealth - health)) / settings.playerMaxStrength,
);
}
public step(deltaTimeInSeconds: number): void {
this.timeSinceLastNameElementUpdate += deltaTimeInSeconds;
this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px'; this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px';
this.statsElement.innerText = this.getStatsText(); this.statsElement.innerText = this.getStatsText();
if (this.previousHealth > this.health) { if (this.previousHealth > this.health) {
@ -52,6 +62,10 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
this.previousHealth = this.health; this.previousHealth = this.health;
} }
public onShoot(strength: number) {
SoundHandler.play(Sounds.shoot, (0.3 * 2 * strength) / settings.playerMaxStrength);
}
public beforeDestroy(): void { public beforeDestroy(): void {
this.nameElement.parentElement?.removeChild(this.nameElement); this.nameElement.parentElement?.removeChild(this.nameElement);
} }

View file

@ -23,7 +23,9 @@ export class ProjectileView extends ProjectileBase implements ViewObject {
); );
} }
public step(deltaTimeInMilliseconds: number): void { public step(deltaTimeInSeconds: number): void {
super.step(deltaTimeInSeconds);
this.circle.center = this.center; this.circle.center = this.center;
this.light.center = this.center; this.light.center = this.center;
this.light.intensity = (0.15 * this.strength) / settings.projectileMaxStrength; this.light.intensity = (0.15 * this.strength) / settings.projectileMaxStrength;

View file

@ -29,11 +29,7 @@ export abstract class OptionsHandler {
}; };
if (this._options.musicEnabled) { if (this._options.musicEnabled) {
const firstClickListener = () => {
document.removeEventListener('click', firstClickListener);
SoundHandler.playAmbient(); SoundHandler.playAmbient();
};
document.addEventListener('click', firstClickListener);
} }
} }

View file

@ -8,37 +8,71 @@ export enum Sounds {
hit = 'hit', hit = 'hit',
shoot = 'shoot', shoot = 'shoot',
click = 'click', click = 'click',
ambient = 'ambient',
} }
const concurrencyScale = 5;
export abstract class SoundHandler { export abstract class SoundHandler {
private static sounds: { [key in Sounds]: HTMLAudioElement }; private static sounds: { [key in Sounds]: HTMLAudioElement };
private static isAmbientPlaying = false;
public static initialize() { private static ambientSound = new Audio(ambientSound);
private static initialized = false;
public static async initialize() {
this.sounds = { this.sounds = {
[Sounds.hit]: new Audio(hitSound), [Sounds.hit]: await this.initializeSound(hitSound),
[Sounds.shoot]: new Audio(shootSound), [Sounds.shoot]: await this.initializeSound(shootSound),
[Sounds.click]: new Audio(clickSound), [Sounds.click]: await this.initializeSound(clickSound),
[Sounds.ambient]: new Audio(ambientSound),
}; };
this.sounds.ambient.volume = 0.5;
this.ambientSound.play();
this.ambientSound.muted = true;
this.initialized = true;
setTimeout(() => {
this.ambientSound.muted = false;
this.ambientSound.volume = 0.5;
if (!this.isAmbientPlaying) {
this.ambientSound.pause();
}
}, 100);
} }
public static play(sound: Sounds) { private static async initializeSound(hitSound: string): Promise<HTMLAudioElement> {
if (OptionsHandler.options.soundsEnabled) { const sound = new Audio(hitSound);
if (this.sounds[sound].currentTime > 0) { sound.muted = true;
(this.sounds[sound].cloneNode() as HTMLAudioElement).play(); await sound.play();
} else { sound.pause();
this.sounds[sound].play(); sound.muted = false;
sound.currentTime = 0;
return sound;
} }
public static play(sound: Sounds, volume: number = 1) {
if (!this.initialized || !OptionsHandler.options.soundsEnabled) {
return;
} }
const audio =
this.sounds[sound].currentTime > 0
? (this.sounds[sound].cloneNode(true) as HTMLAudioElement)
: this.sounds[sound];
audio.volume = volume;
audio.play();
} }
public static playAmbient() { public static playAmbient() {
this.sounds.ambient.play(); this.isAmbientPlaying = true;
if (this.initialized) {
this.ambientSound.play();
}
} }
public static stopAmbient() { public static stopAmbient() {
this.sounds.ambient.pause(); this.isAmbientPlaying = false;
if (this.initialized) {
this.ambientSound.pause();
}
} }
} }

View file

@ -0,0 +1,64 @@
import {
CircleLight,
ColorfulCircle,
FilteringOptions,
Flashlight,
Renderer,
runAnimation,
WrapOptions,
} from 'sdf-2d';
import { settings } from 'shared';
import { BlobShape } from './shapes/blob-shape';
import { PlanetShape } from './shapes/planet-shape';
export const startAnimation = async (
canvas: HTMLCanvasElement,
draw: (r: Renderer, current: number, delta: number) => boolean,
noiseTexture: TexImageSource,
): Promise<void> =>
await runAnimation(
canvas,
[
{
...PlanetShape.descriptor,
shaderCombinationSteps: [0, 1, 2, 3],
},
{
...BlobShape.descriptor,
shaderCombinationSteps: [0, 1, 2, 8],
},
{
...ColorfulCircle.descriptor,
shaderCombinationSteps: [0, 2, 16],
},
{
...CircleLight.descriptor,
shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
},
{
...Flashlight.descriptor,
shaderCombinationSteps: [0],
},
],
draw,
{
shadowTraceCount: 16,
paletteSize: settings.palette.length,
//enableStopwatch: true,
},
{
colorPalette: settings.palette,
enableHighDpiRendering: true,
lightCutoffDistance: settings.lightCutoffDistance,
textures: {
noiseTexture: {
source: noiseTexture,
overrides: {
maxFilter: FilteringOptions.LINEAR,
wrapS: WrapOptions.MIRRORED_REPEAT,
wrapT: WrapOptions.MIRRORED_REPEAT,
},
},
},
},
);

View file

@ -13,7 +13,7 @@ $large-icon: 48px;
$small-icon: 32px; $small-icon: 32px;
$bright-decla: #4069a5; $bright-decla: #4069a5;
$bright-red: #d15652; $bright-red: #d15652;
$bright-neutral: #88888877; $bright-neutral: #ccc;
:root { :root {
--bright-decla: #{$bright-decla}; --bright-decla: #{$bright-decla};

View file

@ -96,9 +96,15 @@ form {
border-radius: $border-radius; border-radius: $border-radius;
padding: $small-padding; padding: $small-padding;
border: $border; border: $border;
cursor: pointer; cursor: pointer;
margin: $border-width-focused - $border-width $border-width-focused - margin: $border-width-focused - $border-width $border-width-focused -
$border-width + $small-padding / 2; $border-width + $small-padding / 2;
.completion {
font-size: 0.7em;
color: $bright-neutral;
}
} }
&:focus { &:focus {

View file

@ -50,13 +50,18 @@
#settings { #settings {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 $medium-padding $medium-padding $medium-padding; @include background;
border-radius: 12px;
z-index: 100;
margin-right: $medium-padding - $small-padding;
padding: $small-padding;
transform: translateY(-100%); transform: translateY(-100%);
@media (max-width: $breakpoint) { @media (max-width: $breakpoint) {
flex-direction: row-reverse; flex-direction: row-reverse;
transform: translateX(100%); transform: translateX(100%);
padding: $medium-padding 0 $medium-padding $medium-padding; margin-top: $medium-padding - $small-padding;
margin-right: 0;
} }
opacity: 0; opacity: 0;
@ -72,13 +77,9 @@
svg { svg {
cursor: pointer; cursor: pointer;
transition: opacity $animation-time; transition: opacity $animation-time;
margin-bottom: $small-padding;
margin-left: 0;
@include square($large-icon); @include square($large-icon);
@media (max-width: $breakpoint) { @media (max-width: $breakpoint) {
@include square($small-icon); @include square($small-icon);
margin-bottom: 0;
margin-left: $small-padding;
} }
} }
@ -130,4 +131,33 @@
} }
} }
} }
img,
svg {
margin-top: $small-padding;
@media (max-width: $breakpoint) {
margin-top: 0;
margin-right: $small-padding;
}
}
label:first-child {
img,
svg {
margin-top: 0;
margin-right: 0;
}
}
label:not(:first-child) {
input[type='checkbox']:after {
$height: 4px;
top: $height / 2 + $small-padding;
@media (max-width: $breakpoint) {
right: $small-padding;
top: $height / 2;
}
}
}
} }

7
frontend/static/flag.svg Normal file
View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="5" y1="5" x2="5" y2="21" />
<line x1="19" y1="5" x2="19" y2="14" />
<path d="M5 5a5 5 0 0 1 7 0a5 5 0 0 0 7 0" />
<path d="M5 14a5 5 0 0 1 7 0a5 5 0 0 0 7 0" />
</svg>

After

Width:  |  Height:  |  Size: 418 B

BIN
frontend/static/shoot2.mp3 Normal file

Binary file not shown.

BIN
frontend/static/shoot3.mp3 Normal file

Binary file not shown.

BIN
frontend/static/shoot4.mp3 Normal file

Binary file not shown.

BIN
frontend/static/shoot5.mp3 Normal file

Binary file not shown.

BIN
frontend/static/shoot6.mp3 Normal file

Binary file not shown.

View file

@ -0,0 +1,18 @@
import { CharacterTeam } from '../../objects/types/character-team';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class GameEnd extends Command {
constructor(
public readonly winningTeam: CharacterTeam,
public readonly endCardLengthInSeconds: number,
public readonly shouldReconnect: boolean,
) {
super();
}
public toArray(): Array<any> {
return [this.winningTeam, this.endCardLengthInSeconds, this.shouldReconnect];
}
}

View file

@ -0,0 +1,13 @@
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class GameStart extends Command {
constructor() {
super();
}
public toArray(): Array<any> {
return [];
}
}

View file

@ -0,0 +1,14 @@
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

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

View file

@ -0,0 +1,13 @@
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class ServerAnnouncement extends Command {
constructor(public readonly text: string) {
super();
}
public toArray(): Array<any> {
return [this.text];
}
}

View file

@ -1,37 +1,17 @@
import { vec2 } from 'gl-matrix';
import { CharacterTeam } from '../../objects/types/character-team';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable
export class OtherPlayerDirection {
public constructor(
public readonly direction: vec2,
public readonly team: CharacterTeam,
) {}
public toArray(): Array<any> {
return [this.direction, this.team];
}
}
@serializable @serializable
export class UpdateGameState extends Command { export class UpdateGameState extends Command {
public constructor( public constructor(
public readonly declaCount: number, public readonly declaCount: number,
public readonly redCount: number, public readonly redCount: number,
public readonly neutralCount: number, public readonly limit: number,
public readonly otherPlayerDirections: Array<OtherPlayerDirection>,
) { ) {
super(); super();
} }
public toArray(): Array<any> { public toArray(): Array<any> {
return [ return [this.declaCount, this.redCount, this.limit];
this.declaCount,
this.redCount,
this.neutralCount,
this.otherPlayerDirections,
];
} }
} }

View file

@ -1,15 +0,0 @@
import { vec2 } from 'gl-matrix';
import { UpdateObjectMessage } from '../../objects/update-object-message';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class UpdateObjectsCommand extends Command {
public constructor(public readonly updates: Array<UpdateObjectMessage>) {
super();
}
public toArray(): Array<any> {
return [this.updates];
}
}

View file

@ -0,0 +1,27 @@
import { vec2 } from 'gl-matrix';
import { CharacterTeam } from '../../objects/types/character-team';
import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command';
@serializable
export class OtherPlayerDirection {
public constructor(
public readonly direction: vec2,
public readonly team: CharacterTeam,
) {}
public toArray(): Array<any> {
return [this.direction, this.team];
}
}
@serializable
export class UpdateOtherPlayerDirections extends Command {
public constructor(public readonly otherPlayerDirections: Array<OtherPlayerDirection>) {
super();
}
public toArray(): Array<any> {
return [this.otherPlayerDirections];
}
}

View file

@ -2,6 +2,7 @@ export interface ServerInformation {
playerLimit: number; playerLimit: number;
playerCount: number; playerCount: number;
serverName: string; serverName: string;
gameStatePercent: number;
} }
export const serverInformationEndpoint = '/stats'; export const serverInformationEndpoint = '/state';

View file

@ -5,12 +5,17 @@ export * from './commands/types/delete-objects';
export * from './commands/types/ternary-action'; export * from './commands/types/ternary-action';
export * from './commands/types/move-action'; export * from './commands/types/move-action';
export * from './commands/types/primary-action'; export * from './commands/types/primary-action';
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/player-died';
export * from './commands/types/update-objects'; export * from './commands/types/server-announcement';
export * from './commands/types/update-game-state'; export * from './commands/types/game-end';
export * from './commands/types/game-start';
export * from './commands/types/update-other-player-directions';
export * from './commands/types/secondary-action'; export * from './commands/types/secondary-action';
export * from './commands/command-receiver';
export * from './commands/types/update-game-state'; export * from './commands/types/update-game-state';
export * from './commands/command-receiver';
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/broadcast-commands';
@ -46,7 +51,6 @@ 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 './objects/update-object-message';
export * from './settings'; export * from './settings';
export * from './transport/transport-events'; export * from './transport/transport-events';
export * from './transport/identity'; export * from './transport/identity';

View file

@ -1,14 +1,37 @@
import { UpdateMessage, UpdateObjectMessage } from '../main';
import { Id } from '../transport/identity'; import { Id } from '../transport/identity';
import { serializable } from '../transport/serialization/serializable';
export abstract class GameObject { @serializable
constructor(public readonly id: Id) {} export class RemoteCall {
constructor(public readonly functionName: string, public readonly args: Array<any>) {}
public calculateUpdates(): UpdateObjectMessage | undefined { public toArray(): Array<any> {
return; return [this.functionName, this.args];
} }
}
update(updates: Array<UpdateMessage>): void {
updates.forEach((u) => ((this as any)[u.key] = u.value)); export abstract class GameObject {
private remoteCalls: Array<RemoteCall> = [];
constructor(public readonly id: Id) {}
public processRemoteCalls(remoteCalls: Array<RemoteCall>) {
remoteCalls.forEach((r) =>
((this[r.functionName as keyof this] as unknown) as (
...args: Array<any>
) => unknown)(...r.args),
);
}
public getRemoteCalls(): Array<RemoteCall> {
return this.remoteCalls;
}
public resetRemoteCalls() {
this.remoteCalls = [];
}
protected remoteCall(name: string & keyof this, ...args: Array<any>) {
this.remoteCalls.push(new RemoteCall(name, args));
} }
} }

View file

@ -19,6 +19,12 @@ export class PlanetBase extends GameObject {
vec2.scale(this.center, this.center, 1 / vertices.length); vec2.scale(this.center, this.center, 1 / vertices.length);
} }
public setOwnership(value: number) {
this.ownership = value;
}
public generatedPoints(value: number) {}
public static createPlanetVertices( public static createPlanetVertices(
center: vec2, center: vec2,
width: number, width: number,

View file

@ -20,6 +20,25 @@ export class PlayerCharacterBase extends CharacterBase {
super(id, team, health, head, leftFoot, rightFoot); super(id, team, health, head, leftFoot, rightFoot);
} }
public onShoot(strength: number) {}
public updateCircles(head: Circle, leftFoot: Circle, rightFoot: Circle) {
this.head!.center = head.center;
this.head!.radius = head.radius;
this.leftFoot!.center = leftFoot.center;
this.leftFoot!.radius = leftFoot.radius;
this.rightFoot!.center = rightFoot.center;
this.rightFoot!.radius = rightFoot.radius;
}
public setHealth(health: number) {
this.health = health;
}
public setKillCount(killCount: number) {
this.killCount = killCount;
}
public toArray(): Array<any> { public toArray(): Array<any> {
return [ return [
this.id, this.id,

View file

@ -1,4 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { settings } from '../../settings';
import { Id } from '../../transport/identity'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
@ -16,6 +17,15 @@ export class ProjectileBase extends GameObject {
super(id); super(id);
} }
public setCenter(center: vec2) {
this.center = center;
}
public step(deltaTimeInSeconds: number) {
this.strength -= settings.projectileFadeSpeed * deltaTimeInSeconds;
this.strength = Math.max(0, this.strength);
}
public toArray(): Array<any> { public toArray(): Array<any> {
return [this.id, this.center, this.radius, this.team, this.strength]; return [this.id, this.center, this.radius, this.team, this.strength];
} }

View file

@ -1,12 +0,0 @@
import { Id } from '../main';
import { serializable } from '../transport/serialization/serializable';
import { UpdateMessage } from './update-message';
@serializable
export class UpdateObjectMessage {
constructor(public id: Id, public updates: Array<UpdateMessage>) {}
public toArray(): Array<any> {
return [this.id, this.updates];
}
}

View file

@ -21,6 +21,9 @@ export const settings = {
objectsOnCircleLength: 0.002, objectsOnCircleLength: 0.002,
planetEdgeCount: 7, planetEdgeCount: 7,
takeControlTimeInSeconds: 4, takeControlTimeInSeconds: 4,
loseControlTimeInSeconds: 24,
planetPointGenerationInterval: 1.5,
planetPointGenerationValue: 1,
maxGravityDistance: 700, maxGravityDistance: 700,
maxGravityQ: 180, maxGravityQ: 180,
planetControlThreshold: 0.2, planetControlThreshold: 0.2,
@ -28,6 +31,7 @@ export const settings = {
maxGravityStrength: 10000, maxGravityStrength: 10000,
maxAcceleration: 10000, maxAcceleration: 10000,
playerMaxStrength: 80, playerMaxStrength: 80,
endGameDeltaScaling: 4,
playerDiedTimeout: 5, playerDiedTimeout: 5,
playerStrengthRegenerationPerSeconds: 40, playerStrengthRegenerationPerSeconds: 40,
projectileMaxStrength: 40, projectileMaxStrength: 40,
@ -37,7 +41,7 @@ export const settings = {
projectileFadeSpeed: 20, projectileFadeSpeed: 20,
projectileCreationInterval: 0.1, projectileCreationInterval: 0.1,
playerColorIndexOffset: 3, playerColorIndexOffset: 3,
backgroundGradient: [rgb255(90, 38, 43), rgb255(0, 0, 0), rgb255(43, 39, 73)], backgroundGradient: [rgb255(90, 38, 43), rgb255(43, 39, 73)],
declaColor, declaColor,
declaPlanetColor, declaPlanetColor,
redColor, redColor,

View file

@ -2,8 +2,8 @@ export enum TransportEvents {
PlayerJoining = 'PlayerJoining', PlayerJoining = 'PlayerJoining',
PlayerToServer = 'PlayerToServer', PlayerToServer = 'PlayerToServer',
ServerToPlayer = 'ServerToPlayer', ServerToPlayer = 'ServerToPlayer',
SubscribeForPlayerCount = 'SubscribeForPlayerCount', SubscribeForServerInfoUpdates = 'SubscribeForServerInfoUpdates',
PlayerCountUpdate = 'PlayerCountUpdate', ServerInfoUpdate = 'ServerInfoUpdate',
Ping = 'Ping', Ping = 'Ping',
Pong = 'Pong', Pong = 'Pong',
} }