Improve netcode and simplify (#4)
- Simplify the logic wherever possible to remove overengineering and simplify the netcode - Actually improve the netcode - The new lines are mainly the package locks and tests Reviewed-on: https://home.schmelczer.dev/git/andras/decla-red/pulls/4
This commit is contained in:
parent
5d854440ec
commit
93a7fe01ab
1 changed files with 15073 additions and 4754 deletions
|
|
@ -1,56 +1,101 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import {
|
||||
CommandReceiver,
|
||||
Circle,
|
||||
PlayerInformation,
|
||||
CharacterTeam,
|
||||
Random,
|
||||
settings,
|
||||
sanitizeName,
|
||||
boundRadius,
|
||||
evaluateSdf,
|
||||
} from 'shared';
|
||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
|
||||
import { PhysicalContainer } from '../physics/physical-container';
|
||||
import { BoundingBox } from '../physics/bounding-box';
|
||||
import { CharacterPhysical } from '../objects/character-physical';
|
||||
import { PlanetPhysical } from '../objects/planet-physical';
|
||||
import { PlanetPhysical, planetsNear } from '../objects/planet-physical';
|
||||
import { PlayerContainer } from './player-container';
|
||||
|
||||
const maximumNameLength = 40;
|
||||
|
||||
export interface Score {
|
||||
kills: number;
|
||||
deaths: number;
|
||||
}
|
||||
|
||||
export abstract class PlayerBase extends CommandReceiver {
|
||||
public character?: CharacterPhysical | null;
|
||||
public character: CharacterPhysical | null = null;
|
||||
public center: vec2 = vec2.create();
|
||||
|
||||
protected sumKills = 0;
|
||||
protected sumDeaths = 0;
|
||||
protected lastCharacter?: CharacterPhysical;
|
||||
protected timeUntilRespawn = 0;
|
||||
private kills: number;
|
||||
private deaths: number;
|
||||
|
||||
constructor(
|
||||
protected readonly playerInfo: PlayerInformation,
|
||||
protected readonly playerContainer: PlayerContainer,
|
||||
protected readonly objectContainer: PhysicalContainer,
|
||||
public readonly team: CharacterTeam,
|
||||
score: Score = { kills: 0, deaths: 0 },
|
||||
) {
|
||||
super();
|
||||
this.kills = score.kills;
|
||||
this.deaths = score.deaths;
|
||||
}
|
||||
|
||||
protected createCharacter() {
|
||||
public get score(): Score {
|
||||
return {
|
||||
kills: (this.character ?? this.lastCharacter)?.killCount ?? this.kills,
|
||||
deaths: this.deaths,
|
||||
};
|
||||
}
|
||||
|
||||
protected createCharacter(): CharacterPhysical {
|
||||
this.kills = this.lastCharacter?.killCount ?? this.kills;
|
||||
this.character = new CharacterPhysical(
|
||||
this.playerInfo.name.slice(0, 20),
|
||||
this.sumKills,
|
||||
this.sumDeaths,
|
||||
sanitizeName(this.playerInfo.name, maximumNameLength),
|
||||
this.kills,
|
||||
this.deaths,
|
||||
this.team,
|
||||
this.objectContainer,
|
||||
this.findEmptyPositionForPlayer(this.findSpawnCenter()),
|
||||
);
|
||||
|
||||
this.objectContainer.addObject(this.character);
|
||||
this.center = this.character.center;
|
||||
return this.character;
|
||||
}
|
||||
|
||||
public abstract step(deltaTimeInSeconds: number): void;
|
||||
public step(deltaTimeInSeconds: number) {
|
||||
this.stepLifecycle(deltaTimeInSeconds);
|
||||
}
|
||||
|
||||
protected stepLifecycle(deltaTimeInSeconds: number): CharacterPhysical | undefined {
|
||||
if (this.character) {
|
||||
this.center = this.character.center;
|
||||
|
||||
if (this.character.isAlive) {
|
||||
return this.character;
|
||||
}
|
||||
|
||||
this.deaths++;
|
||||
this.lastCharacter = this.character;
|
||||
this.character = null;
|
||||
this.timeUntilRespawn = settings.playerDiedTimeout;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if ((this.timeUntilRespawn -= deltaTimeInSeconds) < 0) {
|
||||
this.createCharacter();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private findSpawnCenter(): vec2 {
|
||||
const planets = this.objectContainer
|
||||
.findIntersecting(
|
||||
getBoundingBoxOfCircle(new Circle(vec2.create(), settings.worldRadius * 2)),
|
||||
)
|
||||
.filter((o): o is PlanetPhysical => o instanceof PlanetPhysical);
|
||||
const planets = planetsNear(
|
||||
this.objectContainer,
|
||||
vec2.create(),
|
||||
settings.worldRadius * 2,
|
||||
);
|
||||
|
||||
const friendly = planets.filter((p) => p.team === this.team);
|
||||
const neutral = planets.filter((p) => p.team === CharacterTeam.neutral);
|
||||
|
|
@ -68,34 +113,39 @@ export abstract class PlayerBase extends CommandReceiver {
|
|||
);
|
||||
const safe = candidates.filter((p) => !isContested(p));
|
||||
|
||||
// candidates is non-empty here, so choose() always returns a planet.
|
||||
return vec2.clone(Random.choose(safe.length ? safe : candidates)!.center);
|
||||
}
|
||||
|
||||
protected findEmptyPositionForPlayer(preferredCenter: vec2): vec2 {
|
||||
private findEmptyPositionForPlayer(preferredCenter: vec2): vec2 {
|
||||
let rotation = 0;
|
||||
let radius = 0;
|
||||
for (;;) {
|
||||
const playerPosition = vec2.fromValues(
|
||||
radius * Math.cos(rotation) + preferredCenter.x,
|
||||
radius * Math.sin(rotation) + preferredCenter.y,
|
||||
let roomiestPosition = vec2.clone(preferredCenter);
|
||||
let roomiestClearance = -Infinity;
|
||||
for (let attempt = 0; attempt < 512; attempt++) {
|
||||
const position = vec2.fromValues(
|
||||
radius * Math.cos(rotation) + preferredCenter[0],
|
||||
radius * Math.sin(rotation) + preferredCenter[1],
|
||||
);
|
||||
|
||||
const playerBoundingCircle = new Circle(
|
||||
playerPosition,
|
||||
CharacterPhysical.boundRadius,
|
||||
const clearance = evaluateSdf(
|
||||
position,
|
||||
this.objectContainer.findIntersecting(
|
||||
BoundingBox.ofCircle(position, boundRadius),
|
||||
),
|
||||
);
|
||||
|
||||
const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle);
|
||||
const possibleIntersectors =
|
||||
this.objectContainer.findIntersecting(playerBoundingBox);
|
||||
if (!isCircleIntersecting(playerBoundingCircle, possibleIntersectors)) {
|
||||
return playerPosition;
|
||||
if (clearance >= boundRadius) {
|
||||
return position;
|
||||
}
|
||||
if (clearance > roomiestClearance) {
|
||||
roomiestClearance = clearance;
|
||||
roomiestPosition = position;
|
||||
}
|
||||
|
||||
rotation += Math.PI / 8;
|
||||
radius += 30;
|
||||
}
|
||||
|
||||
return roomiestPosition;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue