Refactor backend to use commands
This commit is contained in:
parent
503c99cb1f
commit
7cf33b9f1a
18 changed files with 144 additions and 156 deletions
7
backend/src/commands/generate-points.ts
Normal file
7
backend/src/commands/generate-points.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Command, GameObject } from 'shared';
|
||||||
|
|
||||||
|
export class GeneratePointsCommand extends Command {
|
||||||
|
public constructor(public readonly decla: number, public readonly red: number) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
backend/src/commands/react-to-collision.ts
Normal file
7
backend/src/commands/react-to-collision.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Command, GameObject } from 'shared';
|
||||||
|
|
||||||
|
export class ReactToCollisionCommand extends Command {
|
||||||
|
public constructor(public readonly other: GameObject) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
backend/src/commands/step.ts
Normal file
10
backend/src/commands/step.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Command, CommandReceiver } from 'shared';
|
||||||
|
|
||||||
|
export class StepCommand extends Command {
|
||||||
|
public constructor(
|
||||||
|
public readonly deltaTimeInSeconds: number,
|
||||||
|
public readonly game: CommandReceiver,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,15 +11,19 @@ import {
|
||||||
GameEndCommand,
|
GameEndCommand,
|
||||||
GameStartCommand,
|
GameStartCommand,
|
||||||
Command,
|
Command,
|
||||||
|
CommandReceiver,
|
||||||
|
CommandExecutors,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { createWorld } from './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';
|
||||||
|
import { StepCommand } from './commands/step';
|
||||||
|
import { GeneratePointsCommand } from './commands/generate-points';
|
||||||
|
|
||||||
const gameStateSubscribedRoom = 'gameStateSubscribedRoom';
|
const gameStateSubscribedRoom = 'gameStateSubscribedRoom';
|
||||||
|
|
||||||
export class GameServer {
|
export class GameServer extends CommandReceiver {
|
||||||
private objects!: PhysicalContainer;
|
private objects!: PhysicalContainer;
|
||||||
private players!: PlayerContainer;
|
private players!: PlayerContainer;
|
||||||
private deltaTimes!: Array<number>;
|
private deltaTimes!: Array<number>;
|
||||||
|
|
@ -54,7 +58,13 @@ export class GameServer {
|
||||||
previousPlayers?.sendQueuedCommands();
|
previousPlayers?.sendQueuedCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected commandExecutors: CommandExecutors = {
|
||||||
|
[GeneratePointsCommand.type]: this.addPoints.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
constructor(private readonly io: ioserver.Server, private options: Options) {
|
constructor(private readonly io: ioserver.Server, private options: Options) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.serverName = options.name;
|
this.serverName = options.name;
|
||||||
this.playerLimit = options.playerLimit;
|
this.playerLimit = options.playerLimit;
|
||||||
|
|
||||||
|
|
@ -102,8 +112,11 @@ export class GameServer {
|
||||||
this.handlePhysics();
|
this.handlePhysics();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePoints() {
|
private addPoints({ decla, red }: GeneratePointsCommand) {
|
||||||
const { decla, red } = this.objects.getPointsGenerated();
|
if (this.isInEndGame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.declaPoints += decla;
|
this.declaPoints += decla;
|
||||||
this.redPoints += red;
|
this.redPoints += red;
|
||||||
if (this.declaPoints >= this.options.scoreLimit) {
|
if (this.declaPoints >= this.options.scoreLimit) {
|
||||||
|
|
@ -152,11 +165,9 @@ export class GameServer {
|
||||||
if (this.isInEndGame) {
|
if (this.isInEndGame) {
|
||||||
this.timeScaling *= Math.pow(settings.endGameDeltaScaling, delta);
|
this.timeScaling *= Math.pow(settings.endGameDeltaScaling, delta);
|
||||||
scaledDelta /= this.timeScaling;
|
scaledDelta /= this.timeScaling;
|
||||||
} else {
|
|
||||||
this.updatePoints();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.objects.stepObjects(scaledDelta);
|
this.objects.handleCommand(new StepCommand(scaledDelta, this));
|
||||||
this.players.step(scaledDelta);
|
this.players.step(scaledDelta);
|
||||||
this.players.stepCommunication(delta);
|
this.players.stepCommunication(delta);
|
||||||
this.objects.resetRemoteCalls();
|
this.objects.resetRemoteCalls();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
|
||||||
|
|
||||||
export interface ExertsForce {
|
|
||||||
getForce(target: vec2): vec2;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const exertsForce = (a: any): a is ExertsForce => a && 'getForce' in a;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
export interface GeneratesPoints {
|
|
||||||
getPoints(): {
|
|
||||||
decla: number;
|
|
||||||
red: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generatesPoints = (a: any): a is GeneratesPoints => a && 'getPoints' in a;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { GameObject } from 'shared';
|
|
||||||
|
|
||||||
export interface ReactsToCollision {
|
|
||||||
onCollision(other: GameObject): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const reactsToCollision = (a: any): a is ReactsToCollision =>
|
|
||||||
a && 'onCollision' in a;
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export interface TimeDependent {
|
|
||||||
step(deltaTimeInSeconds: number): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const timeDependent = (a: any): a is TimeDependent => a && 'step' in a;
|
|
||||||
|
|
@ -11,6 +11,8 @@ import {
|
||||||
CharacterTeam,
|
CharacterTeam,
|
||||||
PropertyUpdatesForObject,
|
PropertyUpdatesForObject,
|
||||||
UpdatePropertyCommand,
|
UpdatePropertyCommand,
|
||||||
|
CommandExecutors,
|
||||||
|
CommandReceiver,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
|
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
|
||||||
import { CirclePhysical } from './circle-physical';
|
import { CirclePhysical } from './circle-physical';
|
||||||
|
|
@ -21,14 +23,12 @@ 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 './capabilities/reacts-to-collision';
|
import { StepCommand } from '../commands/step';
|
||||||
import { TimeDependent } from './capabilities/time-dependent';
|
import { ReactToCollisionCommand } from '../commands/react-to-collision';
|
||||||
import { GeneratesPoints } from './capabilities/generates-points';
|
import { GeneratePointsCommand } from '../commands/generate-points';
|
||||||
|
|
||||||
@serializesTo(CharacterBase)
|
@serializesTo(CharacterBase)
|
||||||
export class CharacterPhysical
|
export class CharacterPhysical extends CharacterBase implements DynamicPhysical {
|
||||||
extends CharacterBase
|
|
||||||
implements DynamicPhysical, ReactsToCollision, TimeDependent, GeneratesPoints {
|
|
||||||
public readonly canCollide = true;
|
public readonly canCollide = true;
|
||||||
public readonly canMove = true;
|
public readonly canMove = true;
|
||||||
|
|
||||||
|
|
@ -94,6 +94,11 @@ export class CharacterPhysical
|
||||||
private leftFootVelocity = new Circle(vec2.create(), 0);
|
private leftFootVelocity = new Circle(vec2.create(), 0);
|
||||||
private rightFootVelocity = new Circle(vec2.create(), 0);
|
private rightFootVelocity = new Circle(vec2.create(), 0);
|
||||||
|
|
||||||
|
protected commandExecutors: CommandExecutors = {
|
||||||
|
[StepCommand.type]: this.step.bind(this),
|
||||||
|
[ReactToCollisionCommand.type]: this.onCollision.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
name: string,
|
name: string,
|
||||||
killCount: number,
|
killCount: number,
|
||||||
|
|
@ -134,20 +139,14 @@ export class CharacterPhysical
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasGeneratedPoints = false;
|
private hasGeneratedPoints = false;
|
||||||
public getPoints(): { decla: number; red: number } {
|
private getPoints(game: CommandReceiver) {
|
||||||
if (!this.isAlive && !this.hasGeneratedPoints) {
|
if (!this.isAlive && !this.hasGeneratedPoints) {
|
||||||
this.hasGeneratedPoints = true;
|
this.hasGeneratedPoints = true;
|
||||||
const decla = this.team === CharacterTeam.decla ? 0 : settings.playerKillPoint;
|
const decla = this.team === CharacterTeam.decla ? 0 : settings.playerKillPoint;
|
||||||
const red = this.team === CharacterTeam.red ? 0 : settings.playerKillPoint;
|
const red = this.team === CharacterTeam.red ? 0 : settings.playerKillPoint;
|
||||||
return {
|
|
||||||
decla,
|
game.handleCommand(new GeneratePointsCommand(decla, red));
|
||||||
red,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
decla: 0,
|
|
||||||
red: 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isAlive(): boolean {
|
public get isAlive(): boolean {
|
||||||
|
|
@ -163,7 +162,7 @@ export class CharacterPhysical
|
||||||
this.remoteCall('setKillCount', this.killCount);
|
this.remoteCall('setKillCount', this.killCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCollision(other: GameObject) {
|
public onCollision({ other }: ReactToCollisionCommand) {
|
||||||
if (
|
if (
|
||||||
other instanceof ProjectilePhysical &&
|
other instanceof ProjectilePhysical &&
|
||||||
other.team !== this.team &&
|
other.team !== this.team &&
|
||||||
|
|
@ -298,7 +297,8 @@ export class CharacterPhysical
|
||||||
this.animateScaling(1);
|
this.animateScaling(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(deltaTime: number) {
|
private step({ deltaTimeInSeconds, game }: StepCommand) {
|
||||||
|
this.getPoints(game);
|
||||||
const oldHead = new Circle(vec2.clone(this.head.center), this.head.radius);
|
const oldHead = new Circle(vec2.clone(this.head.center), this.head.radius);
|
||||||
const oldLeftFoot = new Circle(
|
const oldLeftFoot = new Circle(
|
||||||
vec2.clone(this.leftFoot.center),
|
vec2.clone(this.leftFoot.center),
|
||||||
|
|
@ -310,35 +310,36 @@ export class CharacterPhysical
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.isDestroyed) {
|
if (this.isDestroyed) {
|
||||||
if ((this.timeSinceDying += deltaTime) > settings.spawnDespawnTime) {
|
if ((this.timeSinceDying += deltaTimeInSeconds) > settings.spawnDespawnTime) {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
} else {
|
} else {
|
||||||
this.animateScaling(1 - this.timeSinceDying / settings.spawnDespawnTime);
|
this.animateScaling(1 - this.timeSinceDying / settings.spawnDespawnTime);
|
||||||
}
|
}
|
||||||
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTime);
|
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTimeInSeconds);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasJustBorn) {
|
if (this.hasJustBorn) {
|
||||||
if ((this.timeSinceBorn += deltaTime) > settings.spawnDespawnTime) {
|
if ((this.timeSinceBorn += deltaTimeInSeconds) > settings.spawnDespawnTime) {
|
||||||
this.hasJustBorn = false;
|
this.hasJustBorn = false;
|
||||||
} else {
|
} else {
|
||||||
this.animateScaling(this.timeSinceBorn / settings.spawnDespawnTime);
|
this.animateScaling(this.timeSinceBorn / settings.spawnDespawnTime);
|
||||||
}
|
}
|
||||||
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTime);
|
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTimeInSeconds);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.secondsSinceOnSurface += deltaTime) > 0.5) {
|
if ((this.secondsSinceOnSurface += deltaTimeInSeconds) > 0.5) {
|
||||||
this.currentPlanet = undefined;
|
this.currentPlanet = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.projectileStrength = Math.min(
|
this.projectileStrength = Math.min(
|
||||||
settings.playerMaxStrength,
|
settings.playerMaxStrength,
|
||||||
this.projectileStrength + settings.playerStrengthRegenerationPerSeconds * deltaTime,
|
this.projectileStrength +
|
||||||
|
settings.playerStrengthRegenerationPerSeconds * deltaTimeInSeconds,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.currentPlanet?.takeControl(this.team, deltaTime);
|
this.currentPlanet?.takeControl(this.team, deltaTimeInSeconds);
|
||||||
|
|
||||||
const intersectingWithForceField = this.container.findIntersecting(
|
const intersectingWithForceField = this.container.findIntersecting(
|
||||||
getBoundingBoxOfCircle(
|
getBoundingBoxOfCircle(
|
||||||
|
|
@ -351,8 +352,8 @@ export class CharacterPhysical
|
||||||
|
|
||||||
const direction = this.averageAndResetMovementActions();
|
const direction = this.averageAndResetMovementActions();
|
||||||
const movementForce = vec2.scale(direction, direction, settings.maxAcceleration);
|
const movementForce = vec2.scale(direction, direction, settings.maxAcceleration);
|
||||||
this.applyForce(this.leftFoot, movementForce, deltaTime);
|
this.applyForce(this.leftFoot, movementForce, deltaTimeInSeconds);
|
||||||
this.applyForce(this.rightFoot, movementForce, deltaTime);
|
this.applyForce(this.rightFoot, movementForce, deltaTimeInSeconds);
|
||||||
|
|
||||||
if (!this.currentPlanet) {
|
if (!this.currentPlanet) {
|
||||||
const leftFootGravity = forceAtPosition(
|
const leftFootGravity = forceAtPosition(
|
||||||
|
|
@ -364,14 +365,14 @@ export class CharacterPhysical
|
||||||
intersectingWithForceField,
|
intersectingWithForceField,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.applyForce(this.leftFoot, leftFootGravity, deltaTime);
|
this.applyForce(this.leftFoot, leftFootGravity, deltaTimeInSeconds);
|
||||||
this.applyForce(this.rightFoot, rightFootGravity, deltaTime);
|
this.applyForce(this.rightFoot, rightFootGravity, deltaTimeInSeconds);
|
||||||
|
|
||||||
const sumForce = vec2.subtract(vec2.create(), leftFootGravity, movementForce);
|
const sumForce = vec2.subtract(vec2.create(), leftFootGravity, movementForce);
|
||||||
|
|
||||||
this.setDirection(
|
this.setDirection(
|
||||||
vec2.length(sumForce) === 0 ? vec2.fromValues(0, -1) : sumForce,
|
vec2.length(sumForce) === 0 ? vec2.fromValues(0, -1) : sumForce,
|
||||||
deltaTime,
|
deltaTimeInSeconds,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const leftFootGravity = this.currentPlanet!.getForce(this.leftFoot.center);
|
const leftFootGravity = this.currentPlanet!.getForce(this.leftFoot.center);
|
||||||
|
|
@ -389,7 +390,7 @@ export class CharacterPhysical
|
||||||
this.leftFoot.lastNormal,
|
this.leftFoot.lastNormal,
|
||||||
vec2.dot(this.leftFoot.lastNormal, gravity),
|
vec2.dot(this.leftFoot.lastNormal, gravity),
|
||||||
);
|
);
|
||||||
this.applyForce(this.leftFoot, scaledLeftFootGravity, deltaTime);
|
this.applyForce(this.leftFoot, scaledLeftFootGravity, deltaTimeInSeconds);
|
||||||
|
|
||||||
const scaledRightFootGravity = vec2.scale(
|
const scaledRightFootGravity = vec2.scale(
|
||||||
vec2.create(),
|
vec2.create(),
|
||||||
|
|
@ -397,20 +398,20 @@ export class CharacterPhysical
|
||||||
vec2.dot(this.rightFoot.lastNormal, gravity),
|
vec2.dot(this.rightFoot.lastNormal, gravity),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.applyForce(this.rightFoot, scaledRightFootGravity, deltaTime);
|
this.applyForce(this.rightFoot, scaledRightFootGravity, deltaTimeInSeconds);
|
||||||
|
|
||||||
if (vec2.length(gravity) <= 100) {
|
if (vec2.length(gravity) <= 100) {
|
||||||
this.currentPlanet = undefined;
|
this.currentPlanet = undefined;
|
||||||
}
|
}
|
||||||
this.setDirection(gravity, deltaTime);
|
this.setDirection(gravity, deltaTimeInSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keepPosture(deltaTime);
|
this.keepPosture(deltaTimeInSeconds);
|
||||||
this.stepBodyPart(this.leftFoot, deltaTime);
|
this.stepBodyPart(this.leftFoot, deltaTimeInSeconds);
|
||||||
this.stepBodyPart(this.rightFoot, deltaTime);
|
this.stepBodyPart(this.rightFoot, deltaTimeInSeconds);
|
||||||
this.stepBodyPart(this.head, deltaTime);
|
this.stepBodyPart(this.head, deltaTimeInSeconds);
|
||||||
|
|
||||||
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTime);
|
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTimeInSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDirection(direction: vec2, deltaTime: number) {
|
private setDirection(direction: vec2, deltaTime: number) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Circle, GameObject, serializesTo } from 'shared';
|
import { Circle, CommandReceiver, GameObject, serializesTo } from 'shared';
|
||||||
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';
|
||||||
import { moveCircle } from '../physics/functions/move-circle';
|
import { moveCircle } from '../physics/functions/move-circle';
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
|
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
|
||||||
import { ReactsToCollision, reactsToCollision } from './capabilities/reacts-to-collision';
|
import { ReactToCollisionCommand } from '../commands/react-to-collision';
|
||||||
|
|
||||||
@serializesTo(Circle)
|
@serializesTo(Circle)
|
||||||
export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollision {
|
export class CirclePhysical extends CommandReceiver implements Circle, DynamicPhysical {
|
||||||
readonly canCollide = true;
|
readonly canCollide = true;
|
||||||
readonly canMove = true;
|
readonly canMove = true;
|
||||||
|
|
||||||
|
|
@ -24,6 +24,7 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
|
||||||
private readonly container: PhysicalContainer,
|
private readonly container: PhysicalContainer,
|
||||||
private restitution = 0,
|
private restitution = 0,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
this._boundingBox = new BoundingBox();
|
this._boundingBox = new BoundingBox();
|
||||||
this.recalculateBoundingBox();
|
this.recalculateBoundingBox();
|
||||||
}
|
}
|
||||||
|
|
@ -41,10 +42,8 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
|
||||||
this.recalculateBoundingBox();
|
this.recalculateBoundingBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCollision(other: GameObject) {
|
public onCollision(c: ReactToCollisionCommand) {
|
||||||
if (reactsToCollision(this.owner)) {
|
this.owner.handleCommand(c);
|
||||||
this.owner.onCollision(other);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get gameObject(): GameObject {
|
public get gameObject(): GameObject {
|
||||||
|
|
|
||||||
|
|
@ -10,30 +10,28 @@ import {
|
||||||
CharacterTeam,
|
CharacterTeam,
|
||||||
PropertyUpdatesForObject,
|
PropertyUpdatesForObject,
|
||||||
UpdatePropertyCommand,
|
UpdatePropertyCommand,
|
||||||
|
CommandExecutors,
|
||||||
|
CommandReceiver,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
|
import { GeneratePointsCommand } from '../commands/generate-points';
|
||||||
|
import { StepCommand } from '../commands/step';
|
||||||
|
|
||||||
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 { ExertsForce } from './capabilities/exerts-force';
|
|
||||||
import { GeneratesPoints } from './capabilities/generates-points';
|
|
||||||
import { TimeDependent } from './capabilities/time-dependent';
|
|
||||||
|
|
||||||
@serializesTo(PlanetBase)
|
@serializesTo(PlanetBase)
|
||||||
export class PlanetPhysical
|
export class PlanetPhysical extends PlanetBase implements StaticPhysical {
|
||||||
extends PlanetBase
|
|
||||||
implements StaticPhysical, GeneratesPoints, ExertsForce, TimeDependent {
|
|
||||||
public readonly canCollide = true;
|
public readonly canCollide = true;
|
||||||
public readonly canMove = false;
|
public readonly canMove = false;
|
||||||
|
|
||||||
public static neutralPlanetCount = 0;
|
|
||||||
public static declaPlanetCount = 0;
|
|
||||||
public static redPlanetCount = 0;
|
|
||||||
|
|
||||||
private _boundingBox?: ImmutableBoundingBox;
|
private _boundingBox?: ImmutableBoundingBox;
|
||||||
|
|
||||||
|
protected commandExecutors: CommandExecutors = {
|
||||||
|
[StepCommand.type]: this.step.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
constructor(vertices: Array<vec2>) {
|
constructor(vertices: Array<vec2>) {
|
||||||
super(id(), vertices);
|
super(id(), vertices);
|
||||||
PlanetPhysical.neutralPlanetCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public distance(target: vec2): number {
|
public distance(target: vec2): number {
|
||||||
|
|
@ -79,33 +77,27 @@ export class PlanetPhysical
|
||||||
}
|
}
|
||||||
|
|
||||||
private timeSinceLastPointGeneration = 0;
|
private timeSinceLastPointGeneration = 0;
|
||||||
public getPoints(): {
|
private getPoints(game: CommandReceiver) {
|
||||||
decla: number;
|
|
||||||
red: number;
|
|
||||||
} {
|
|
||||||
if (this.timeSinceLastPointGeneration > settings.planetPointGenerationInterval) {
|
if (this.timeSinceLastPointGeneration > settings.planetPointGenerationInterval) {
|
||||||
this.timeSinceLastPointGeneration = 0;
|
this.timeSinceLastPointGeneration = 0;
|
||||||
if (this.team !== CharacterTeam.neutral) {
|
if (this.team !== CharacterTeam.neutral) {
|
||||||
this.remoteCall('generatedPoints', settings.planetPointGenerationValue);
|
this.remoteCall('generatedPoints', settings.planetPointGenerationValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
game.handleCommand(
|
||||||
decla:
|
new GeneratePointsCommand(
|
||||||
this.team === CharacterTeam.decla ? settings.planetPointGenerationValue : 0,
|
this.team === CharacterTeam.decla ? settings.planetPointGenerationValue : 0,
|
||||||
red: this.team === CharacterTeam.red ? settings.planetPointGenerationValue : 0,
|
this.team === CharacterTeam.red ? settings.planetPointGenerationValue : 0,
|
||||||
};
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
private step({ deltaTimeInSeconds, game }: StepCommand) {
|
||||||
decla: 0,
|
this.timeSinceLastPointGeneration += deltaTimeInSeconds;
|
||||||
red: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public step(deltaTime: number): void {
|
|
||||||
this.timeSinceLastPointGeneration += deltaTime;
|
|
||||||
// In reverse order, so that teams can achieve a 100% control.
|
// In reverse order, so that teams can achieve a 100% control.
|
||||||
this.takeControl(CharacterTeam.neutral, deltaTime);
|
this.getPoints(game);
|
||||||
|
this.takeControl(CharacterTeam.neutral, deltaTimeInSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPropertyUpdates(): PropertyUpdatesForObject {
|
public getPropertyUpdates(): PropertyUpdatesForObject {
|
||||||
|
|
|
||||||
|
|
@ -8,20 +8,19 @@ import {
|
||||||
CharacterTeam,
|
CharacterTeam,
|
||||||
PropertyUpdatesForObject,
|
PropertyUpdatesForObject,
|
||||||
UpdatePropertyCommand,
|
UpdatePropertyCommand,
|
||||||
|
CommandExecutors,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||||
import { CirclePhysical } from './circle-physical';
|
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 { ReactsToCollision } from './capabilities/reacts-to-collision';
|
|
||||||
import { CharacterPhysical } from './character-physical';
|
import { CharacterPhysical } from './character-physical';
|
||||||
import { moveCircle } from '../physics/functions/move-circle';
|
import { moveCircle } from '../physics/functions/move-circle';
|
||||||
import { TimeDependent } from './capabilities/time-dependent';
|
import { StepCommand } from '../commands/step';
|
||||||
|
import { ReactToCollisionCommand } from '../commands/react-to-collision';
|
||||||
|
|
||||||
@serializesTo(ProjectileBase)
|
@serializesTo(ProjectileBase)
|
||||||
export class ProjectilePhysical
|
export class ProjectilePhysical extends ProjectileBase implements DynamicPhysical {
|
||||||
extends ProjectileBase
|
|
||||||
implements DynamicPhysical, ReactsToCollision, TimeDependent {
|
|
||||||
public readonly canCollide = true;
|
public readonly canCollide = true;
|
||||||
public readonly canMove = true;
|
public readonly canMove = true;
|
||||||
|
|
||||||
|
|
@ -31,6 +30,11 @@ export class ProjectilePhysical
|
||||||
|
|
||||||
public object: CirclePhysical;
|
public object: CirclePhysical;
|
||||||
|
|
||||||
|
protected commandExecutors: CommandExecutors = {
|
||||||
|
[StepCommand.type]: this.handleStep.bind(this),
|
||||||
|
[ReactToCollisionCommand.type]: this.onCollision.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
center: vec2,
|
center: vec2,
|
||||||
radius: number,
|
radius: number,
|
||||||
|
|
@ -91,7 +95,7 @@ export class ProjectilePhysical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCollision(other: GameObject) {
|
public onCollision({ other }: ReactToCollisionCommand) {
|
||||||
if (
|
if (
|
||||||
!(other instanceof CharacterPhysical && other.team === this.team) &&
|
!(other instanceof CharacterPhysical && other.team === this.team) &&
|
||||||
this.bounceCount++ === settings.projectileMaxBounceCount
|
this.bounceCount++ === settings.projectileMaxBounceCount
|
||||||
|
|
@ -106,8 +110,8 @@ export class ProjectilePhysical
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(deltaTime: number) {
|
private handleStep({ deltaTimeInSeconds }: StepCommand) {
|
||||||
super.step(deltaTime);
|
super.step(deltaTimeInSeconds);
|
||||||
|
|
||||||
if (this.strength <= 0) {
|
if (this.strength <= 0) {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
|
@ -115,7 +119,7 @@ export class ProjectilePhysical
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2.copy(this.object.velocity, this.velocity);
|
vec2.copy(this.object.velocity, this.velocity);
|
||||||
const { velocity } = this.object.stepManually(deltaTime);
|
const { velocity } = this.object.stepManually(deltaTimeInSeconds);
|
||||||
vec2.copy(this.velocity, velocity);
|
vec2.copy(this.velocity, velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,19 @@ 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 {
|
import { Command, CommandReceiver } from 'shared';
|
||||||
GeneratesPoints,
|
|
||||||
generatesPoints,
|
|
||||||
} from '../../objects/capabilities/generates-points';
|
|
||||||
import { timeDependent } from '../../objects/capabilities/time-dependent';
|
|
||||||
|
|
||||||
export class PhysicalContainer {
|
export class PhysicalContainer extends CommandReceiver {
|
||||||
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> = [];
|
private objects: Array<Physical> = [];
|
||||||
|
|
||||||
|
protected defaultCommandExecutor(c: Command) {
|
||||||
|
this.objects.forEach((o) => o.handleCommand(c));
|
||||||
|
}
|
||||||
|
|
||||||
public initialize() {
|
public initialize() {
|
||||||
this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList);
|
this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList);
|
||||||
this.isTreeInitialized = true;
|
this.isTreeInitialized = true;
|
||||||
|
|
@ -41,29 +41,10 @@ export class PhysicalContainer {
|
||||||
this.dynamicBoundingBoxes.remove(object);
|
this.dynamicBoundingBoxes.remove(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public stepObjects(deltaTime: number) {
|
|
||||||
this.objects.forEach((o) => timeDependent(o) && o.step(deltaTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetRemoteCalls() {
|
public resetRemoteCalls() {
|
||||||
this.objects.forEach((o) => o.gameObject.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> {
|
||||||
return [
|
return [
|
||||||
...this.staticBoundingBoxes.findIntersecting(box),
|
...this.staticBoundingBoxes.findIntersecting(box),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { exertsForce } from '../../objects/capabilities/exerts-force';
|
import { PlanetPhysical } from '../../objects/planet-physical';
|
||||||
import { Physical } from '../physicals/physical';
|
import { Physical } from '../physicals/physical';
|
||||||
|
|
||||||
export const forceAtPosition = (position: vec2, objects: Array<Physical>) =>
|
export const forceAtPosition = (position: vec2, objects: Array<Physical>) =>
|
||||||
objects.reduce(
|
objects
|
||||||
|
.filter((o) => o instanceof PlanetPhysical)
|
||||||
|
.reduce(
|
||||||
(sum: vec2, o: Physical) =>
|
(sum: vec2, o: Physical) =>
|
||||||
exertsForce(o) ? vec2.add(sum, sum, o.getForce(position)) : sum,
|
vec2.add(sum, sum, (o as PlanetPhysical).getForce(position)),
|
||||||
vec2.create(),
|
vec2.create(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
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 '../../objects/capabilities/reacts-to-collision';
|
|
||||||
import { evaluateSdf } from './evaluate-sdf';
|
import { evaluateSdf } from './evaluate-sdf';
|
||||||
import { Physical } from '../physicals/physical';
|
import { Physical } from '../physicals/physical';
|
||||||
|
import { ReactToCollisionCommand } from '../../commands/react-to-collision';
|
||||||
|
|
||||||
export const moveCircle = (
|
export const moveCircle = (
|
||||||
circle: CirclePhysical,
|
circle: CirclePhysical,
|
||||||
|
|
@ -43,13 +43,8 @@ export const moveCircle = (
|
||||||
if (ignoreCollision) {
|
if (ignoreCollision) {
|
||||||
circle.center = vec2.add(circle.center, circle.center, delta);
|
circle.center = vec2.add(circle.center, circle.center, delta);
|
||||||
} else {
|
} else {
|
||||||
if (reactsToCollision(intersecting)) {
|
intersecting.handleCommand(new ReactToCollisionCommand(circle.gameObject));
|
||||||
intersecting.onCollision(circle.gameObject);
|
circle.handleCommand(new ReactToCollisionCommand(intersecting.gameObject));
|
||||||
}
|
|
||||||
|
|
||||||
if (reactsToCollision(circle)) {
|
|
||||||
circle.onCollision(intersecting.gameObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2.add(
|
vec2.add(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { GameObject } from 'shared';
|
import { CommandReceiver, GameObject } from 'shared';
|
||||||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||||
|
|
||||||
export interface PhysicalBase {
|
export interface PhysicalBase extends CommandReceiver {
|
||||||
readonly canCollide: boolean;
|
readonly canCollide: boolean;
|
||||||
readonly canMove: boolean;
|
readonly canMove: boolean;
|
||||||
readonly boundingBox: BoundingBoxBase;
|
readonly boundingBox: BoundingBoxBase;
|
||||||
|
|
|
||||||
7
backend/src/react-to-collision.ts
Normal file
7
backend/src/react-to-collision.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Command, GameObject } from 'shared';
|
||||||
|
|
||||||
|
export class ReactToCollisionCommand extends Command {
|
||||||
|
public constructor(public readonly other: GameObject) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ export class PlanetView extends PlanetBase {
|
||||||
this.ownershipProgress.className = 'ownership';
|
this.ownershipProgress.className = 'ownership';
|
||||||
}
|
}
|
||||||
|
|
||||||
private step(deltaTimeInSeconds: number): void {
|
private step({ deltaTimeInSeconds }: StepCommand): void {
|
||||||
this.shape.randomOffset += deltaTimeInSeconds / 4;
|
this.shape.randomOffset += deltaTimeInSeconds / 4;
|
||||||
this.shape.colorMixQ = this.ownership;
|
this.shape.colorMixQ = this.ownership;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue