Refactor backend to use commands

This commit is contained in:
Schmelczer András 2020-11-06 21:10:08 +01:00
parent 503c99cb1f
commit 7cf33b9f1a
18 changed files with 144 additions and 156 deletions

View 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();
}
}

View file

@ -0,0 +1,7 @@
import { Command, GameObject } from 'shared';
export class ReactToCollisionCommand extends Command {
public constructor(public readonly other: GameObject) {
super();
}
}

View 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();
}
}

View file

@ -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();

View file

@ -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;

View file

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

View file

@ -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;

View file

@ -1,5 +0,0 @@
export interface TimeDependent {
step(deltaTimeInSeconds: number): void;
}
export const timeDependent = (a: any): a is TimeDependent => a && 'step' in a;

View file

@ -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) {

View file

@ -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 {

View file

@ -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 {

View file

@ -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);
} }
} }

View file

@ -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),

View file

@ -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(),
); );

View file

@ -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(

View file

@ -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;

View file

@ -0,0 +1,7 @@
import { Command, GameObject } from 'shared';
export class ReactToCollisionCommand extends Command {
public constructor(public readonly other: GameObject) {
super();
}
}

View file

@ -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;