Refactor
This commit is contained in:
parent
b774357807
commit
57d7009342
39 changed files with 204 additions and 251 deletions
|
|
@ -67,8 +67,8 @@ export const createWorld = (objectContainer: PhysicalContainer, worldRadius: num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Generated planet count', objects.length);
|
console.info('Generated planet count', objects.length);
|
||||||
console.log('Generated light count', lights.length);
|
console.info('Generated light count', lights.length);
|
||||||
|
|
||||||
[...objects, ...lights].forEach((o) => objectContainer.addObject(o));
|
[...objects, ...lights].forEach((o) => objectContainer.addObject(o));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export class GameServer {
|
||||||
const commands: Array<Command> = deserialize(json);
|
const commands: Array<Command> = deserialize(json);
|
||||||
commands.forEach((c) => player.sendCommand(c));
|
commands.forEach((c) => player.sendCommand(c));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Error while processing command', e);
|
console.error('Error while processing command', e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -169,16 +169,16 @@ export class GameServer {
|
||||||
|
|
||||||
if (this.deltaTimes.length > framesBetweenDeltaTimeCalculation) {
|
if (this.deltaTimes.length > framesBetweenDeltaTimeCalculation) {
|
||||||
this.deltaTimes.sort((a, b) => a - b);
|
this.deltaTimes.sort((a, b) => a - b);
|
||||||
console.log(
|
console.info(
|
||||||
`Median physics time: ${this.deltaTimes[
|
`Median physics time: ${this.deltaTimes[
|
||||||
Math.floor(framesBetweenDeltaTimeCalculation / 2)
|
Math.floor(framesBetweenDeltaTimeCalculation / 2)
|
||||||
].toFixed(2)} ms`,
|
].toFixed(2)} ms`,
|
||||||
);
|
);
|
||||||
console.log(
|
console.info(
|
||||||
'Tail times: ',
|
'Tail times: ',
|
||||||
this.deltaTimes.slice(-20).map((v) => `${(v * 1000).toFixed(2)} ms`),
|
this.deltaTimes.slice(-20).map((v) => `${(v * 1000).toFixed(2)} ms`),
|
||||||
);
|
);
|
||||||
console.log(
|
console.info(
|
||||||
`Memory used: ${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`,
|
`Memory used: ${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`,
|
||||||
);
|
);
|
||||||
this.deltaTimes = [];
|
this.deltaTimes = [];
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,19 @@ const gameServer = new GameServer(io, options);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
origin: (origin, callback) => {
|
origin: (_, callback) => {
|
||||||
callback(null, true);
|
callback(null, true);
|
||||||
},
|
},
|
||||||
credentials: true,
|
credentials: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(serverInformationEndpoint, (req, res) => {
|
app.get(serverInformationEndpoint, (_, res) => {
|
||||||
res.json(gameServer.serverInfo);
|
res.json(gameServer.serverInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(options.port, () => {
|
server.listen(options.port, () => {
|
||||||
console.log(`server started at http://localhost:${options.port}`);
|
console.info(`Server started on port ${options.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
gameServer.start();
|
gameServer.start();
|
||||||
|
|
|
||||||
7
backend/src/objects/capabilities/exerts-force.ts
Normal file
7
backend/src/objects/capabilities/exerts-force.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export interface ExertsForce {
|
||||||
|
getForce(target: vec2): vec2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exertsForce = (a: any): a is ExertsForce => a && 'getForce' in a;
|
||||||
8
backend/src/objects/capabilities/generates-points.ts
Normal file
8
backend/src/objects/capabilities/generates-points.ts
Normal 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;
|
||||||
8
backend/src/objects/capabilities/reacts-to-collision.ts
Normal file
8
backend/src/objects/capabilities/reacts-to-collision.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { GameObject } from 'shared';
|
||||||
|
|
||||||
|
export interface ReactsToCollision {
|
||||||
|
onCollision(other: GameObject): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reactsToCollision = (a: any): a is ReactsToCollision =>
|
||||||
|
a && 'onCollision' in a;
|
||||||
5
backend/src/objects/capabilities/time-dependent.ts
Normal file
5
backend/src/objects/capabilities/time-dependent.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface TimeDependent {
|
||||||
|
step(deltaTimeInSeconds: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const timeDependent = (a: any): a is TimeDependent => a && 'step' in a;
|
||||||
504
backend/src/objects/character-physical.ts
Normal file
504
backend/src/objects/character-physical.ts
Normal file
|
|
@ -0,0 +1,504 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import {
|
||||||
|
id,
|
||||||
|
settings,
|
||||||
|
MoveActionCommand,
|
||||||
|
serializesTo,
|
||||||
|
last,
|
||||||
|
GameObject,
|
||||||
|
Circle,
|
||||||
|
CharacterBase,
|
||||||
|
CharacterTeam,
|
||||||
|
PropertyUpdatesForObject,
|
||||||
|
UpdateProperty,
|
||||||
|
} from 'shared';
|
||||||
|
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
|
||||||
|
import { CirclePhysical } from './circle-physical';
|
||||||
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
|
import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
|
||||||
|
import { ProjectilePhysical } from './projectile-physical';
|
||||||
|
import { interpolateAngles } from '../helper/interpolate-angles';
|
||||||
|
import { forceAtPosition } from '../physics/functions/force-at-position';
|
||||||
|
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||||
|
import { PlanetPhysical } from './planet-physical';
|
||||||
|
import { ReactsToCollision } from './capabilities/reacts-to-collision';
|
||||||
|
import { TimeDependent } from './capabilities/time-dependent';
|
||||||
|
import { GeneratesPoints } from './capabilities/generates-points';
|
||||||
|
|
||||||
|
@serializesTo(CharacterBase)
|
||||||
|
export class CharacterPhysical
|
||||||
|
extends CharacterBase
|
||||||
|
implements DynamicPhysical, ReactsToCollision, TimeDependent, GeneratesPoints {
|
||||||
|
public readonly canCollide = true;
|
||||||
|
public readonly canMove = true;
|
||||||
|
|
||||||
|
private static readonly headRadius = 50;
|
||||||
|
private static readonly feetRadius = 20;
|
||||||
|
private projectileStrength = settings.playerMaxStrength;
|
||||||
|
|
||||||
|
// offsets are measured from (0, 0)
|
||||||
|
private static readonly desiredHeadOffset = vec2.fromValues(0, 65);
|
||||||
|
private static readonly desiredLeftFootOffset = vec2.fromValues(-20, 0);
|
||||||
|
private static readonly desiredRightFootOffset = vec2.fromValues(20, 0);
|
||||||
|
private static readonly centerOfMass = vec2.scale(
|
||||||
|
vec2.create(),
|
||||||
|
vec2.add(
|
||||||
|
vec2.create(),
|
||||||
|
vec2.add(
|
||||||
|
vec2.create(),
|
||||||
|
CharacterPhysical.desiredHeadOffset,
|
||||||
|
CharacterPhysical.desiredLeftFootOffset,
|
||||||
|
),
|
||||||
|
CharacterPhysical.desiredRightFootOffset,
|
||||||
|
),
|
||||||
|
1 / 3,
|
||||||
|
);
|
||||||
|
|
||||||
|
private static readonly headOffset = vec2.subtract(
|
||||||
|
vec2.create(),
|
||||||
|
CharacterPhysical.desiredHeadOffset,
|
||||||
|
CharacterPhysical.centerOfMass,
|
||||||
|
);
|
||||||
|
private static readonly leftFootOffset = vec2.subtract(
|
||||||
|
vec2.create(),
|
||||||
|
CharacterPhysical.desiredLeftFootOffset,
|
||||||
|
CharacterPhysical.centerOfMass,
|
||||||
|
);
|
||||||
|
private static readonly rightFootOffset = vec2.subtract(
|
||||||
|
vec2.create(),
|
||||||
|
CharacterPhysical.desiredRightFootOffset,
|
||||||
|
CharacterPhysical.centerOfMass,
|
||||||
|
);
|
||||||
|
|
||||||
|
public static readonly boundRadius =
|
||||||
|
(CharacterPhysical.headRadius + CharacterPhysical.feetRadius * 2) * 2;
|
||||||
|
|
||||||
|
private timeSinceDying = 0;
|
||||||
|
private isDestroyed = false;
|
||||||
|
private timeSinceBorn = 0;
|
||||||
|
private hasJustBorn = true;
|
||||||
|
|
||||||
|
private direction = 0;
|
||||||
|
private currentPlanet?: PlanetPhysical;
|
||||||
|
private secondsSinceOnSurface = 1000;
|
||||||
|
|
||||||
|
public head: CirclePhysical;
|
||||||
|
public leftFoot: CirclePhysical;
|
||||||
|
public rightFoot: CirclePhysical;
|
||||||
|
public bound: CirclePhysical;
|
||||||
|
|
||||||
|
private movementActions: Array<MoveActionCommand> = [];
|
||||||
|
private lastMovementAction: MoveActionCommand = new MoveActionCommand(vec2.create());
|
||||||
|
|
||||||
|
private headVelocity = new Circle(vec2.create(), 0);
|
||||||
|
private leftFootVelocity = new Circle(vec2.create(), 0);
|
||||||
|
private rightFootVelocity = new Circle(vec2.create(), 0);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
name: string,
|
||||||
|
killCount: number,
|
||||||
|
deathCount: number,
|
||||||
|
team: CharacterTeam,
|
||||||
|
private readonly container: PhysicalContainer,
|
||||||
|
startPosition: vec2,
|
||||||
|
) {
|
||||||
|
super(id(), name, killCount, deathCount, team, settings.playerMaxHealth);
|
||||||
|
this.head = new CirclePhysical(
|
||||||
|
vec2.add(vec2.create(), startPosition, CharacterPhysical.headOffset),
|
||||||
|
CharacterPhysical.headRadius,
|
||||||
|
this,
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
this.leftFoot = new CirclePhysical(
|
||||||
|
vec2.add(vec2.create(), startPosition, CharacterPhysical.leftFootOffset),
|
||||||
|
CharacterPhysical.feetRadius,
|
||||||
|
this,
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
this.rightFoot = new CirclePhysical(
|
||||||
|
vec2.add(vec2.create(), startPosition, CharacterPhysical.rightFootOffset),
|
||||||
|
CharacterPhysical.feetRadius,
|
||||||
|
this,
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
container.addObject(this.head);
|
||||||
|
container.addObject(this.leftFoot);
|
||||||
|
container.addObject(this.rightFoot);
|
||||||
|
|
||||||
|
this.bound = new CirclePhysical(
|
||||||
|
vec2.create(),
|
||||||
|
CharacterPhysical.boundRadius,
|
||||||
|
this,
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasGeneratedPoints = false;
|
||||||
|
public getPoints(): { decla: number; red: number } {
|
||||||
|
if (!this.isAlive && !this.hasGeneratedPoints) {
|
||||||
|
this.hasGeneratedPoints = true;
|
||||||
|
const decla = this.team === CharacterTeam.decla ? 0 : settings.playerKillPoint;
|
||||||
|
const red = this.team === CharacterTeam.red ? 0 : settings.playerKillPoint;
|
||||||
|
return {
|
||||||
|
decla,
|
||||||
|
red,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
decla: 0,
|
||||||
|
red: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isAlive(): boolean {
|
||||||
|
return !this.isDestroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleMovementAction(c: MoveActionCommand) {
|
||||||
|
this.movementActions.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addKill() {
|
||||||
|
this.killCount++;
|
||||||
|
this.remoteCall('setKillCount', this.killCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCollision(other: GameObject) {
|
||||||
|
if (
|
||||||
|
other instanceof ProjectilePhysical &&
|
||||||
|
other.team !== this.team &&
|
||||||
|
other.isAlive
|
||||||
|
) {
|
||||||
|
other.destroy();
|
||||||
|
this.health -= other.strength;
|
||||||
|
this.remoteCall('setHealth', this.health);
|
||||||
|
if (this.health <= 0 && this.isAlive) {
|
||||||
|
this.onDie();
|
||||||
|
other.originator.addKill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public shootTowards(position: vec2) {
|
||||||
|
if (!this.isAlive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const direction = vec2.subtract(vec2.create(), position, this.center);
|
||||||
|
vec2.normalize(direction, direction);
|
||||||
|
const velocity = vec2.scale(direction, direction, settings.projectileSpeed);
|
||||||
|
const strength = this.projectileStrength / 2;
|
||||||
|
this.projectileStrength -= strength;
|
||||||
|
const projectile = new ProjectilePhysical(
|
||||||
|
vec2.clone(this.center),
|
||||||
|
20,
|
||||||
|
strength,
|
||||||
|
this.team,
|
||||||
|
velocity,
|
||||||
|
this,
|
||||||
|
this.container,
|
||||||
|
);
|
||||||
|
this.container.addObject(projectile);
|
||||||
|
|
||||||
|
this.remoteCall('onShoot', strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get boundingBox(): BoundingBoxBase {
|
||||||
|
this.bound.center = this.head.center;
|
||||||
|
return this.bound.boundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get gameObject(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get center(): vec2 {
|
||||||
|
const bodyCenter = vec2.add(vec2.create(), this.head.center, this.leftFoot.center);
|
||||||
|
vec2.add(bodyCenter, bodyCenter, this.rightFoot.center);
|
||||||
|
return vec2.scale(bodyCenter, bodyCenter, 1 / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public distance(target: vec2): number {
|
||||||
|
return (
|
||||||
|
Math.min(
|
||||||
|
this.head.distance(target),
|
||||||
|
this.leftFoot.distance(target),
|
||||||
|
this.rightFoot.distance(target),
|
||||||
|
) - 5
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private averageAndResetMovementActions(): vec2 {
|
||||||
|
let direction: vec2;
|
||||||
|
if (this.movementActions.length === 0) {
|
||||||
|
direction = vec2.clone(this.lastMovementAction.direction);
|
||||||
|
} else {
|
||||||
|
direction = this.movementActions.reduce(
|
||||||
|
(sum, current) => vec2.add(sum, sum, current.direction),
|
||||||
|
vec2.create(),
|
||||||
|
);
|
||||||
|
|
||||||
|
vec2.scale(direction, direction, 1 / this.movementActions.length);
|
||||||
|
|
||||||
|
this.lastMovementAction = last(this.movementActions)!;
|
||||||
|
this.movementActions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec2.length(direction) > 0
|
||||||
|
? vec2.normalize(direction, direction)
|
||||||
|
: vec2.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private animateScaling(q: number) {
|
||||||
|
this.head.radius = CharacterPhysical.headRadius * q;
|
||||||
|
this.leftFoot.radius = this.rightFoot.radius = CharacterPhysical.feetRadius * q;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPropertyUpdates(): PropertyUpdatesForObject {
|
||||||
|
return new PropertyUpdatesForObject(this.id, [
|
||||||
|
new UpdateProperty('head', this.head, this.headVelocity),
|
||||||
|
new UpdateProperty('leftFoot', this.leftFoot, this.leftFootVelocity),
|
||||||
|
new UpdateProperty('rightFoot', this.rightFoot, this.rightFootVelocity),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setPropertyUpdates(
|
||||||
|
oldHead: Circle,
|
||||||
|
oldLeftFoot: Circle,
|
||||||
|
oldRightFoot: Circle,
|
||||||
|
deltaTime: number,
|
||||||
|
) {
|
||||||
|
this.headVelocity = new Circle(
|
||||||
|
vec2.scale(
|
||||||
|
oldHead.center,
|
||||||
|
vec2.subtract(oldHead.center, this.head.center, oldHead.center),
|
||||||
|
1 / deltaTime,
|
||||||
|
),
|
||||||
|
(this.head.radius - oldHead.radius) / deltaTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.leftFootVelocity = new Circle(
|
||||||
|
vec2.scale(
|
||||||
|
oldLeftFoot.center,
|
||||||
|
vec2.subtract(oldLeftFoot.center, this.leftFoot.center, oldLeftFoot.center),
|
||||||
|
1 / deltaTime,
|
||||||
|
),
|
||||||
|
(this.leftFoot.radius - oldLeftFoot.radius) / deltaTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.rightFootVelocity = new Circle(
|
||||||
|
vec2.scale(
|
||||||
|
oldRightFoot.center,
|
||||||
|
vec2.subtract(oldRightFoot.center, this.rightFoot.center, oldRightFoot.center),
|
||||||
|
1 / deltaTime,
|
||||||
|
),
|
||||||
|
(this.rightFoot.radius - oldRightFoot.radius) / deltaTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.animateScaling(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public step(deltaTime: number) {
|
||||||
|
const oldHead = new Circle(vec2.clone(this.head.center), this.head.radius);
|
||||||
|
const oldLeftFoot = new Circle(
|
||||||
|
vec2.clone(this.leftFoot.center),
|
||||||
|
this.leftFoot.radius,
|
||||||
|
);
|
||||||
|
const oldRightFoot = new Circle(
|
||||||
|
vec2.clone(this.rightFoot.center),
|
||||||
|
this.rightFoot.radius,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isDestroyed) {
|
||||||
|
if ((this.timeSinceDying += deltaTime) > settings.spawnDespawnTime) {
|
||||||
|
this.destroy();
|
||||||
|
} else {
|
||||||
|
this.animateScaling(1 - this.timeSinceDying / settings.spawnDespawnTime);
|
||||||
|
}
|
||||||
|
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasJustBorn) {
|
||||||
|
if ((this.timeSinceBorn += deltaTime) > settings.spawnDespawnTime) {
|
||||||
|
this.hasJustBorn = false;
|
||||||
|
} else {
|
||||||
|
this.animateScaling(this.timeSinceBorn / settings.spawnDespawnTime);
|
||||||
|
}
|
||||||
|
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.secondsSinceOnSurface += deltaTime) > 0.5) {
|
||||||
|
this.currentPlanet = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.projectileStrength = Math.min(
|
||||||
|
settings.playerMaxStrength,
|
||||||
|
this.projectileStrength + settings.playerStrengthRegenerationPerSeconds * deltaTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.currentPlanet?.takeControl(this.team, deltaTime);
|
||||||
|
|
||||||
|
const intersectingWithForceField = this.container.findIntersecting(
|
||||||
|
getBoundingBoxOfCircle(
|
||||||
|
new Circle(
|
||||||
|
this.center,
|
||||||
|
CharacterPhysical.boundRadius + settings.maxGravityDistance,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const direction = this.averageAndResetMovementActions();
|
||||||
|
const movementForce = vec2.scale(direction, direction, settings.maxAcceleration);
|
||||||
|
this.applyForce(this.leftFoot, movementForce, deltaTime);
|
||||||
|
this.applyForce(this.rightFoot, movementForce, deltaTime);
|
||||||
|
|
||||||
|
if (!this.currentPlanet) {
|
||||||
|
const leftFootGravity = forceAtPosition(
|
||||||
|
this.leftFoot.center,
|
||||||
|
intersectingWithForceField,
|
||||||
|
);
|
||||||
|
const rightFootGravity = forceAtPosition(
|
||||||
|
this.rightFoot.center,
|
||||||
|
intersectingWithForceField,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.applyForce(this.leftFoot, leftFootGravity, deltaTime);
|
||||||
|
this.applyForce(this.rightFoot, rightFootGravity, deltaTime);
|
||||||
|
|
||||||
|
const sumForce = vec2.subtract(vec2.create(), leftFootGravity, movementForce);
|
||||||
|
|
||||||
|
this.setDirection(
|
||||||
|
vec2.length(sumForce) === 0 ? vec2.fromValues(0, -1) : sumForce,
|
||||||
|
deltaTime,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const leftFootGravity = this.currentPlanet!.getForce(this.leftFoot.center);
|
||||||
|
const rightFootGravity = this.currentPlanet!.getForce(this.rightFoot.center);
|
||||||
|
|
||||||
|
vec2.add(leftFootGravity, leftFootGravity, rightFootGravity);
|
||||||
|
const gravity = vec2.scale(leftFootGravity, leftFootGravity, 0.5);
|
||||||
|
|
||||||
|
if (vec2.dot(movementForce, gravity) < -vec2.length(movementForce) * 0.8) {
|
||||||
|
vec2.scale(gravity, gravity, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scaledLeftFootGravity = vec2.scale(
|
||||||
|
vec2.create(),
|
||||||
|
this.leftFoot.lastNormal,
|
||||||
|
vec2.dot(this.leftFoot.lastNormal, gravity),
|
||||||
|
);
|
||||||
|
this.applyForce(this.leftFoot, scaledLeftFootGravity, deltaTime);
|
||||||
|
|
||||||
|
const scaledRightFootGravity = vec2.scale(
|
||||||
|
vec2.create(),
|
||||||
|
this.rightFoot.lastNormal,
|
||||||
|
vec2.dot(this.rightFoot.lastNormal, gravity),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.applyForce(this.rightFoot, scaledRightFootGravity, deltaTime);
|
||||||
|
|
||||||
|
if (vec2.length(gravity) <= 100) {
|
||||||
|
this.currentPlanet = undefined;
|
||||||
|
}
|
||||||
|
this.setDirection(gravity, deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keepPosture(deltaTime);
|
||||||
|
this.stepBodyPart(this.leftFoot, deltaTime);
|
||||||
|
this.stepBodyPart(this.rightFoot, deltaTime);
|
||||||
|
this.stepBodyPart(this.head, deltaTime);
|
||||||
|
|
||||||
|
this.setPropertyUpdates(oldHead, oldLeftFoot, oldRightFoot, deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDirection(direction: vec2, deltaTime: number) {
|
||||||
|
this.direction = interpolateAngles(
|
||||||
|
this.direction,
|
||||||
|
Math.atan2(direction.y, direction.x) + Math.PI / 2,
|
||||||
|
0.2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private keepPosture(deltaTime: number) {
|
||||||
|
let center = this.center;
|
||||||
|
this.springMove(
|
||||||
|
this.leftFoot,
|
||||||
|
center,
|
||||||
|
CharacterPhysical.leftFootOffset,
|
||||||
|
deltaTime,
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
this.springMove(
|
||||||
|
this.rightFoot,
|
||||||
|
center,
|
||||||
|
CharacterPhysical.rightFootOffset,
|
||||||
|
deltaTime,
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.springMove(this.head, center, CharacterPhysical.headOffset, deltaTime, 7000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private springMove(
|
||||||
|
object: CirclePhysical,
|
||||||
|
center: vec2,
|
||||||
|
offset: vec2,
|
||||||
|
deltaTime: number,
|
||||||
|
strength: number,
|
||||||
|
) {
|
||||||
|
const desiredPosition = vec2.add(vec2.create(), center, offset);
|
||||||
|
vec2.rotate(desiredPosition, desiredPosition, center, this.direction);
|
||||||
|
const positionDelta = vec2.subtract(vec2.create(), desiredPosition, object.center);
|
||||||
|
|
||||||
|
const positionDeltaLength = vec2.length(positionDelta);
|
||||||
|
|
||||||
|
if (positionDeltaLength > 0) {
|
||||||
|
const positionDeltaDirection = vec2.normalize(vec2.create(), positionDelta);
|
||||||
|
vec2.scale(
|
||||||
|
positionDelta,
|
||||||
|
positionDeltaDirection,
|
||||||
|
positionDeltaLength ** 2 * deltaTime * strength,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (vec2.length(positionDelta) * deltaTime * deltaTime > positionDeltaLength) {
|
||||||
|
vec2.scale(
|
||||||
|
positionDelta,
|
||||||
|
positionDelta,
|
||||||
|
positionDeltaLength / (vec2.length(positionDelta) * deltaTime * deltaTime),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
object.applyForce(positionDelta, deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private stepBodyPart(part: CirclePhysical, deltaTime: number) {
|
||||||
|
const { hitObject } = part.stepManually(deltaTime);
|
||||||
|
if (hitObject instanceof PlanetPhysical) {
|
||||||
|
this.secondsSinceOnSurface = 0;
|
||||||
|
this.currentPlanet = hitObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyForce(circle: CirclePhysical, force: vec2, timeInSeconds: number) {
|
||||||
|
vec2.add(
|
||||||
|
circle.velocity,
|
||||||
|
circle.velocity,
|
||||||
|
vec2.scale(vec2.create(), force, timeInSeconds),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDie() {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.remoteCall('onDie');
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroy() {
|
||||||
|
this.container.removeObject(this);
|
||||||
|
this.container.removeObject(this.head);
|
||||||
|
this.container.removeObject(this.leftFoot);
|
||||||
|
this.container.removeObject(this.rightFoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Circle, GameObject, serializesTo, settings } from 'shared';
|
import { Circle, GameObject, serializesTo } from 'shared';
|
||||||
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';
|
||||||
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 {
|
import { ReactsToCollision, reactsToCollision } from './capabilities/reacts-to-collision';
|
||||||
ReactsToCollision,
|
|
||||||
reactsToCollision,
|
|
||||||
} from '../physics/functions/reacts-to-collision';
|
|
||||||
|
|
||||||
@serializesTo(Circle)
|
@serializesTo(Circle)
|
||||||
export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollision {
|
export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollision {
|
||||||
|
|
@ -17,8 +13,9 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
|
||||||
readonly canMove = true;
|
readonly canMove = true;
|
||||||
|
|
||||||
public velocity = vec2.create();
|
public velocity = vec2.create();
|
||||||
|
public lastNormal = vec2.fromValues(0, 1);
|
||||||
|
|
||||||
private _boundingBox: BoundingBox;
|
private _boundingBox: BoundingBox;
|
||||||
public lastNormal = vec2.fromValues(1, 0);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _center: vec2,
|
private _center: vec2,
|
||||||
|
|
@ -67,18 +64,6 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
|
||||||
return vec2.distance(target, this.center) - this.radius;
|
return vec2.distance(target, this.center) - this.radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
public distanceBetween(target: Circle): number {
|
|
||||||
return vec2.distance(target.center, this.center) - this.radius - target.radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
public areIntersecting(other: PhysicalBase): boolean {
|
|
||||||
return other.distance(this.center) < this.radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isInside(other: PhysicalBase): boolean {
|
|
||||||
return other.distance(this.center) < -this.radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
private recalculateBoundingBox() {
|
private recalculateBoundingBox() {
|
||||||
this._boundingBox.xMin = this.center.x - this._radius;
|
this._boundingBox.xMin = this.center.x - this._radius;
|
||||||
this._boundingBox.xMax = this.center.x + this._radius;
|
this._boundingBox.xMax = this.center.x + this._radius;
|
||||||
|
|
@ -94,9 +79,7 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(_: number) {}
|
public stepManually(
|
||||||
|
|
||||||
public step2(
|
|
||||||
deltaTimeInSeconds: number,
|
deltaTimeInSeconds: number,
|
||||||
): { hitObject: GameObject | undefined; velocity: vec2 } {
|
): { hitObject: GameObject | undefined; velocity: vec2 } {
|
||||||
let delta = vec2.scale(vec2.create(), this.velocity, deltaTimeInSeconds);
|
let delta = vec2.scale(vec2.create(), this.velocity, deltaTimeInSeconds);
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,6 @@ export class LampPhysical extends LampBase implements StaticPhysical {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getForce(_: vec2): vec2 {
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import {
|
||||||
clamp,
|
clamp,
|
||||||
clamp01,
|
clamp01,
|
||||||
id,
|
id,
|
||||||
rotate90Deg,
|
|
||||||
rotateMinus90Deg,
|
|
||||||
serializesTo,
|
serializesTo,
|
||||||
settings,
|
settings,
|
||||||
PlanetBase,
|
PlanetBase,
|
||||||
|
|
@ -14,12 +12,14 @@ import {
|
||||||
|
|
||||||
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 { GeneratesPoints } from './generates-points';
|
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
|
extends PlanetBase
|
||||||
implements StaticPhysical, GeneratesPoints {
|
implements StaticPhysical, GeneratesPoints, ExertsForce, TimeDependent {
|
||||||
public readonly canCollide = true;
|
public readonly canCollide = true;
|
||||||
public readonly canMove = false;
|
public readonly canMove = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,15 @@ import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-boundi
|
||||||
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 { PlanetPhysical } from './planet-physical';
|
import { ReactsToCollision } from './capabilities/reacts-to-collision';
|
||||||
import { ReactsToCollision } from '../physics/functions/reacts-to-collision';
|
import { CharacterPhysical } from './character-physical';
|
||||||
import { PlayerCharacterPhysical } from './player-character-physical';
|
|
||||||
import { moveCircle } from '../physics/functions/move-circle';
|
import { moveCircle } from '../physics/functions/move-circle';
|
||||||
|
import { TimeDependent } from './capabilities/time-dependent';
|
||||||
|
|
||||||
@serializesTo(ProjectileBase)
|
@serializesTo(ProjectileBase)
|
||||||
export class ProjectilePhysical
|
export class ProjectilePhysical
|
||||||
extends ProjectileBase
|
extends ProjectileBase
|
||||||
implements DynamicPhysical, ReactsToCollision {
|
implements DynamicPhysical, ReactsToCollision, TimeDependent {
|
||||||
public readonly canCollide = true;
|
public readonly canCollide = true;
|
||||||
public readonly canMove = true;
|
public readonly canMove = true;
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class ProjectilePhysical
|
||||||
public strength: number,
|
public strength: number,
|
||||||
team: CharacterTeam,
|
team: CharacterTeam,
|
||||||
private velocity: vec2,
|
private velocity: vec2,
|
||||||
public readonly originator: PlayerCharacterPhysical,
|
public readonly originator: CharacterPhysical,
|
||||||
readonly container: PhysicalContainer,
|
readonly container: PhysicalContainer,
|
||||||
) {
|
) {
|
||||||
super(id(), center, radius, team, strength);
|
super(id(), center, radius, team, strength);
|
||||||
|
|
@ -60,7 +60,7 @@ export class ProjectilePhysical
|
||||||
while (wasCollision) {
|
while (wasCollision) {
|
||||||
const intersecting = this.container
|
const intersecting = this.container
|
||||||
.findIntersecting(this.boundingBox)
|
.findIntersecting(this.boundingBox)
|
||||||
.filter((g) => g instanceof PlayerCharacterPhysical && g.team === this.team);
|
.filter((g) => g instanceof CharacterPhysical && g.team === this.team);
|
||||||
const { hitSurface } = moveCircle(this.object, delta, intersecting, true);
|
const { hitSurface } = moveCircle(this.object, delta, intersecting, true);
|
||||||
wasCollision = hitSurface;
|
wasCollision = hitSurface;
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,7 @@ export class ProjectilePhysical
|
||||||
|
|
||||||
public onCollision(other: GameObject) {
|
public onCollision(other: GameObject) {
|
||||||
if (
|
if (
|
||||||
!(other instanceof PlayerCharacterPhysical && other.team === this.team) &&
|
!(other instanceof CharacterPhysical && other.team === this.team) &&
|
||||||
this.bounceCount++ === settings.projectileMaxBounceCount
|
this.bounceCount++ === settings.projectileMaxBounceCount
|
||||||
) {
|
) {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
|
@ -115,7 +115,7 @@ export class ProjectilePhysical
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2.copy(this.object.velocity, this.velocity);
|
vec2.copy(this.object.velocity, this.velocity);
|
||||||
const { velocity } = this.object.step2(deltaTime);
|
const { velocity } = this.object.stepManually(deltaTime);
|
||||||
vec2.copy(this.velocity, velocity);
|
vec2.copy(this.velocity, velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||||
|
|
||||||
import { BoundingBoxList } from './bounding-box-list';
|
import { BoundingBoxList } from './bounding-box-list';
|
||||||
import { BoundingBoxTree } from './bounding-box-tree';
|
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';
|
import {
|
||||||
|
GeneratesPoints,
|
||||||
|
generatesPoints,
|
||||||
|
} from '../../objects/capabilities/generates-points';
|
||||||
|
import { timeDependent } from '../../objects/capabilities/time-dependent';
|
||||||
|
|
||||||
export class PhysicalContainer {
|
export class PhysicalContainer {
|
||||||
private isTreeInitialized = false;
|
private isTreeInitialized = false;
|
||||||
|
|
@ -40,7 +42,7 @@ export class PhysicalContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public stepObjects(deltaTime: number) {
|
public stepObjects(deltaTime: number) {
|
||||||
this.objects.forEach((o) => o.step(deltaTime));
|
this.objects.forEach((o) => timeDependent(o) && o.step(deltaTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetRemoteCalls() {
|
public resetRemoteCalls() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { exertsForce } from '../../objects/capabilities/exerts-force';
|
||||||
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.reduce(
|
||||||
(sum: vec2, o: Physical) =>
|
(sum: vec2, o: Physical) =>
|
||||||
!o.canMove ? vec2.add(sum, sum, o.getForce(position)) : sum,
|
exertsForce(o) ? vec2.add(sum, sum, o.getForce(position)) : sum,
|
||||||
vec2.create(),
|
vec2.create(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Circle, GameObject, rotate90Deg } from 'shared';
|
import { Circle, GameObject, rotate90Deg } from 'shared';
|
||||||
import { CirclePhysical } from '../../objects/circle-physical';
|
import { CirclePhysical } from '../../objects/circle-physical';
|
||||||
import { reactsToCollision } from './reacts-to-collision';
|
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';
|
||||||
|
|
||||||
|
|
@ -11,10 +11,8 @@ export const moveCircle = (
|
||||||
possibleIntersectors: Array<Physical>,
|
possibleIntersectors: Array<Physical>,
|
||||||
ignoreCollision = false,
|
ignoreCollision = false,
|
||||||
): {
|
): {
|
||||||
realDelta: vec2;
|
|
||||||
hitSurface: boolean;
|
hitSurface: boolean;
|
||||||
normal?: vec2;
|
normal?: vec2;
|
||||||
tangent?: vec2;
|
|
||||||
hitObject?: GameObject;
|
hitObject?: GameObject;
|
||||||
} => {
|
} => {
|
||||||
const direction = vec2.clone(delta);
|
const direction = vec2.clone(delta);
|
||||||
|
|
@ -78,16 +76,10 @@ export const moveCircle = (
|
||||||
]);
|
]);
|
||||||
const normal = vec2.fromValues(dx, dy);
|
const normal = vec2.fromValues(dx, dy);
|
||||||
vec2.normalize(normal, normal);
|
vec2.normalize(normal, normal);
|
||||||
const rotatedNormal = rotate90Deg(normal);
|
|
||||||
return {
|
return {
|
||||||
realDelta: delta,
|
|
||||||
hitSurface: true,
|
hitSurface: true,
|
||||||
normal,
|
normal,
|
||||||
hitObject: intersecting?.gameObject,
|
hitObject: intersecting?.gameObject,
|
||||||
tangent:
|
|
||||||
vec2.dot(rotatedNormal, delta) < 0
|
|
||||||
? vec2.scale(rotatedNormal, rotatedNormal, -1)
|
|
||||||
: rotatedNormal,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +89,6 @@ export const moveCircle = (
|
||||||
vec2.add(circle.center, circle.center, delta);
|
vec2.add(circle.center, circle.center, delta);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
realDelta: delta,
|
|
||||||
hitSurface: false,
|
hitSurface: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,4 @@ export interface PhysicalBase {
|
||||||
readonly gameObject: GameObject;
|
readonly gameObject: GameObject;
|
||||||
|
|
||||||
distance(target: vec2): number;
|
distance(target: vec2): number;
|
||||||
step(deltaTime: number): void;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import { PhysicalBase } from './physical-base';
|
import { PhysicalBase } from './physical-base';
|
||||||
import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box';
|
import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box';
|
||||||
import { vec2 } from 'gl-matrix';
|
|
||||||
|
|
||||||
export interface StaticPhysical extends PhysicalBase {
|
export interface StaticPhysical extends PhysicalBase {
|
||||||
readonly canMove: false;
|
readonly canMove: false;
|
||||||
readonly boundingBox: ImmutableBoundingBox;
|
readonly boundingBox: ImmutableBoundingBox;
|
||||||
|
|
||||||
getForce(target: vec2): vec2;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import {
|
import {
|
||||||
PlayerInformation,
|
PlayerInformation,
|
||||||
CharacterTeam,
|
|
||||||
settings,
|
settings,
|
||||||
Circle,
|
Circle,
|
||||||
Random,
|
Random,
|
||||||
MoveActionCommand,
|
MoveActionCommand,
|
||||||
|
CharacterTeam,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
import { PlayerContainer } from './player-container';
|
import { PlayerContainer } from './player-container';
|
||||||
import { PlayerBase } from './player-base';
|
import { PlayerBase } from './player-base';
|
||||||
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||||
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
import { CharacterPhysical } from '../objects/character-physical';
|
||||||
import { PlanetPhysical } from '../objects/planet-physical';
|
import { PlanetPhysical } from '../objects/planet-physical';
|
||||||
import { Physical } from '../physics/physicals/physical';
|
import { Physical } from '../physics/physicals/physical';
|
||||||
|
|
||||||
|
|
@ -98,22 +98,18 @@ export class NPC extends PlayerBase {
|
||||||
|
|
||||||
private findNearCharactersSorted(
|
private findNearCharactersSorted(
|
||||||
nearObjects: Array<Physical>,
|
nearObjects: Array<Physical>,
|
||||||
): Array<{ character: PlayerCharacterPhysical; distance: number }> {
|
): Array<{ character: CharacterPhysical; distance: number }> {
|
||||||
const characters = Array.from(
|
const characters = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
nearObjects.filter(
|
nearObjects.filter(
|
||||||
(o) =>
|
(o) =>
|
||||||
o.gameObject instanceof PlayerCharacterPhysical &&
|
o.gameObject instanceof CharacterPhysical && o.gameObject !== this.character,
|
||||||
o.gameObject !== this.character,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).map((c) => ({
|
).map((c) => ({
|
||||||
character: c.gameObject,
|
character: c.gameObject,
|
||||||
distance: vec2.distance(
|
distance: vec2.distance(this.center, (c.gameObject as CharacterPhysical).center),
|
||||||
this.center,
|
})) as Array<{ character: CharacterPhysical; distance: number }>;
|
||||||
(c.gameObject as PlayerCharacterPhysical).center,
|
|
||||||
),
|
|
||||||
})) as Array<{ character: PlayerCharacterPhysical; distance: number }>;
|
|
||||||
|
|
||||||
characters.sort((a, b) => a.distance - b.distance);
|
characters.sort((a, b) => a.distance - b.distance);
|
||||||
|
|
||||||
|
|
@ -139,12 +135,10 @@ export class NPC extends PlayerBase {
|
||||||
const nearObjects = this.objectContainer.findIntersecting(observableArea);
|
const nearObjects = this.objectContainer.findIntersecting(observableArea);
|
||||||
|
|
||||||
const enemies = nearObjects.filter(
|
const enemies = nearObjects.filter(
|
||||||
(o) =>
|
(o) => o.gameObject instanceof CharacterPhysical && o.gameObject.team !== this.team,
|
||||||
o.gameObject instanceof PlayerCharacterPhysical &&
|
|
||||||
o.gameObject.team !== this.team,
|
|
||||||
);
|
);
|
||||||
if (enemies.length > 0) {
|
if (enemies.length > 0) {
|
||||||
return (enemies[0].gameObject as PlayerCharacterPhysical).center;
|
return (enemies[0].gameObject as CharacterPhysical).center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ import { CommandReceiver, Circle, PlayerInformation, CharacterTeam } from 'share
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||||
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
|
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
|
||||||
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
import { CharacterPhysical } from '../objects/character-physical';
|
||||||
import { PlayerContainer } from './player-container';
|
import { PlayerContainer } from './player-container';
|
||||||
|
|
||||||
export abstract class PlayerBase extends CommandReceiver {
|
export abstract class PlayerBase extends CommandReceiver {
|
||||||
public character?: PlayerCharacterPhysical | null;
|
public character?: CharacterPhysical | null;
|
||||||
public center: vec2 = vec2.create();
|
public center: vec2 = vec2.create();
|
||||||
|
|
||||||
protected sumKills = 0;
|
protected sumKills = 0;
|
||||||
|
|
@ -23,7 +23,7 @@ export abstract class PlayerBase extends CommandReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createCharacter(preferredCenter: vec2) {
|
protected createCharacter(preferredCenter: vec2) {
|
||||||
this.character = new PlayerCharacterPhysical(
|
this.character = new CharacterPhysical(
|
||||||
this.playerInfo.name.slice(0, 20),
|
this.playerInfo.name.slice(0, 20),
|
||||||
this.sumKills,
|
this.sumKills,
|
||||||
this.sumDeaths,
|
this.sumDeaths,
|
||||||
|
|
@ -52,7 +52,7 @@ export abstract class PlayerBase extends CommandReceiver {
|
||||||
|
|
||||||
const playerBoundingCircle = new Circle(
|
const playerBoundingCircle = new Circle(
|
||||||
playerPosition,
|
playerPosition,
|
||||||
PlayerCharacterPhysical.boundRadius,
|
CharacterPhysical.boundRadius,
|
||||||
);
|
);
|
||||||
|
|
||||||
const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle);
|
const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle);
|
||||||
|
|
@ -69,6 +69,6 @@ export abstract class PlayerBase extends CommandReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.character?.kill();
|
this.character?.onDie();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
TransportEvents,
|
TransportEvents,
|
||||||
SetAspectRatioActionCommand,
|
SetAspectRatioActionCommand,
|
||||||
calculateViewArea,
|
calculateViewArea,
|
||||||
SecondaryActionCommand,
|
|
||||||
settings,
|
settings,
|
||||||
PlayerInformation,
|
PlayerInformation,
|
||||||
CharacterTeam,
|
CharacterTeam,
|
||||||
|
|
@ -26,18 +25,15 @@ import {
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
|
import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
import { CharacterPhysical } from '../objects/character-physical';
|
||||||
import { PlayerContainer } from './player-container';
|
import { PlayerContainer } from './player-container';
|
||||||
import { PlayerBase } from './player-base';
|
import { PlayerBase } from './player-base';
|
||||||
import { DeltaTimeCalculator } from '../helper/delta-time-calculator';
|
|
||||||
|
|
||||||
export class Player extends PlayerBase {
|
export class Player extends PlayerBase {
|
||||||
|
// default, until the clients sends its real value
|
||||||
private aspectRatio: number = 16 / 9;
|
private aspectRatio: number = 16 / 9;
|
||||||
private isActive = true;
|
|
||||||
|
|
||||||
private objectsPreviouslyInViewArea: Array<GameObject> = [];
|
private objectsPreviouslyInViewArea: Array<GameObject> = [];
|
||||||
private pingDeltaTime = new DeltaTimeCalculator();
|
|
||||||
private _latency?: number;
|
|
||||||
|
|
||||||
protected commandExecutors: CommandExecutors = {
|
protected commandExecutors: CommandExecutors = {
|
||||||
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) =>
|
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) =>
|
||||||
|
|
@ -57,31 +53,10 @@ export class Player extends PlayerBase {
|
||||||
private readonly socket: SocketIO.Socket,
|
private readonly socket: SocketIO.Socket,
|
||||||
) {
|
) {
|
||||||
super(playerInfo, playerContainer, objectContainer, team);
|
super(playerInfo, playerContainer, objectContainer, team);
|
||||||
|
|
||||||
this.createCharacter();
|
this.createCharacter();
|
||||||
|
|
||||||
socket.on(
|
|
||||||
TransportEvents.Pong,
|
|
||||||
() => (this._latency = this.pingDeltaTime.getNextDeltaTimeInSeconds()),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.measureLatency();
|
|
||||||
this.step(0);
|
this.step(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public measureLatency() {
|
|
||||||
this.pingDeltaTime.getNextDeltaTimeInSeconds(true);
|
|
||||||
this.socket.emit(TransportEvents.Ping);
|
|
||||||
|
|
||||||
if (this.isActive) {
|
|
||||||
setTimeout(this.measureLatency.bind(this), 10000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get latency(): number | undefined {
|
|
||||||
return this._latency;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createCharacter() {
|
protected createCharacter() {
|
||||||
const preferredCenter = this.playerContainer.players.find(
|
const preferredCenter = this.playerContainer.players.find(
|
||||||
(p) => p.character?.isAlive && p.team === this.team,
|
(p) => p.character?.isAlive && p.team === this.team,
|
||||||
|
|
@ -191,7 +166,7 @@ export class Player extends PlayerBase {
|
||||||
const playersInViewArea = this.objectContainer
|
const playersInViewArea = this.objectContainer
|
||||||
.findIntersecting(bb)
|
.findIntersecting(bb)
|
||||||
.map((o) => o.gameObject)
|
.map((o) => o.gameObject)
|
||||||
.filter((g) => g instanceof PlayerCharacterPhysical);
|
.filter((g) => g instanceof CharacterPhysical);
|
||||||
|
|
||||||
const otherPlayers = this.playerContainer.players.filter(
|
const otherPlayers = this.playerContainer.players.filter(
|
||||||
(p) => p.character?.isAlive && playersInViewArea.indexOf(p.character!) < 0,
|
(p) => p.character?.isAlive && playersInViewArea.indexOf(p.character!) < 0,
|
||||||
|
|
@ -222,6 +197,5 @@ export class Player extends PlayerBase {
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
this.isActive = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue