Fix net code

This commit is contained in:
Andras Schmelczer 2026-06-14 15:01:36 +01:00
parent 1f10b9c750
commit a1fb6755c7
23 changed files with 408 additions and 236 deletions

View file

@ -13,6 +13,7 @@ import {
import { BeforeDestroyCommand } from '../commands/types/before-destroy';
import { StepCommand } from '../commands/types/step';
import { Game } from '../game';
import { serverTimeline } from '../helper/server-timeline';
import { Camera } from './types/camera';
import { CharacterView } from './types/character-view';
import { PlanetView } from './types/planet-view';
@ -36,7 +37,7 @@ export class GameObjectContainer extends CommandReceiver {
this.defaultCommandExecutor(c);
if (this.player) {
this.camera.center = this.player.position;
this.camera.follow(this.player.position, c.deltaTimeInSeconds);
}
},
@ -45,10 +46,12 @@ export class GameObjectContainer extends CommandReceiver {
this.objects.get(c.id)?.processRemoteCalls(c.calls),
),
[PropertyUpdatesForObjects.type]: (c: PropertyUpdatesForObjects) =>
[PropertyUpdatesForObjects.type]: (c: PropertyUpdatesForObjects) => {
serverTimeline.onSnapshot(c.timestamp);
c.updates.forEach((u) =>
u.updates.forEach((au) => this.objects.get(u.id)?.handleCommand(au)),
),
);
},
[DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) =>
c.ids.forEach((id: Id) => this.deleteObject(id)),

View file

@ -13,10 +13,28 @@ export class Camera extends CommandReceiver {
public center: vec2 = vec2.create();
private aspectRatio?: number;
// A short exponential lag masks any residual stepping in the followed
// position without the camera noticeably trailing during normal movement.
private static readonly followSeconds = 0.08;
// Swooshing across half the map after a respawn would be disorienting;
// beyond this distance the camera cuts instead.
private static readonly snapDistance = 1500;
constructor(private game: Game) {
super();
}
public follow(target: vec2, deltaTimeInSeconds: number) {
if (vec2.distance(target, this.center) > Camera.snapDistance) {
vec2.copy(this.center, target);
return;
}
const q = 1 - Math.exp(-deltaTimeInSeconds / Camera.followSeconds);
vec2.lerp(this.center, this.center, target, q);
}
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: this.draw.bind(this),
};

View file

@ -16,8 +16,8 @@ import {
import { BeforeDestroyCommand } from '../../commands/types/before-destroy';
import { RenderCommand } from '../../commands/types/render';
import { StepCommand } from '../../commands/types/step';
import { CircleExtrapolator } from '../../helper/extrapolators/circle-extrapolator';
import { LinearExtrapolator } from '../../helper/extrapolators/linear-extrapolator';
import { CircleInterpolator } from '../../helper/interpolators/circle-interpolator';
import { LinearInterpolator } from '../../helper/interpolators/linear-interpolator';
import { Pointer } from '../../helper/pointer';
import { CharacterShape } from '../../shapes/character-shape';
import { SoundHandler, Sounds } from '../../sound-handler';
@ -40,7 +40,7 @@ export class CharacterView extends CharacterBase {
private muzzleFlashIntensity = 0;
private hitFlashIntensity = 0;
private strength = settings.playerMaxStrength;
private strengthExtrapolator = new LinearExtrapolator(settings.playerMaxStrength);
private strengthInterpolator = new LinearInterpolator(settings.playerMaxStrength);
private nameElement: HTMLElement = document.createElement('div');
private statsElement: HTMLElement = document.createElement('div');
private killCountElement: HTMLElement = document.createElement('span');
@ -50,9 +50,9 @@ export class CharacterView extends CharacterBase {
public isMainCharacter = false;
private leftFootExtrapolator: CircleExtrapolator;
private rightFootExtrapolator: CircleExtrapolator;
private headExtrapolator: CircleExtrapolator;
private leftFootInterpolator: CircleInterpolator;
private rightFootInterpolator: CircleInterpolator;
private headInterpolator: CircleInterpolator;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: this.draw.bind(this),
@ -80,9 +80,9 @@ export class CharacterView extends CharacterBase {
0,
);
this.leftFootExtrapolator = new CircleExtrapolator(this.leftFoot!);
this.rightFootExtrapolator = new CircleExtrapolator(this.rightFoot!);
this.headExtrapolator = new CircleExtrapolator(this.head!);
this.leftFootInterpolator = new CircleInterpolator(this.leftFoot!);
this.rightFootInterpolator = new CircleInterpolator(this.rightFoot!);
this.headInterpolator = new CircleInterpolator(this.head!);
this.nameElement.className = 'player-tag ' + this.team;
this.nameElement.innerText = this.name;
@ -140,16 +140,16 @@ export class CharacterView extends CharacterBase {
rateOfChange,
}: UpdatePropertyCommand) {
if (propertyKey === 'head') {
this.headExtrapolator.addFrame(propertyValue, rateOfChange);
this.headInterpolator.addFrame(propertyValue, rateOfChange);
}
if (propertyKey === 'leftFoot') {
this.leftFootExtrapolator.addFrame(propertyValue, rateOfChange);
this.leftFootInterpolator.addFrame(propertyValue, rateOfChange);
}
if (propertyKey === 'rightFoot') {
this.rightFootExtrapolator.addFrame(propertyValue, rateOfChange);
this.rightFootInterpolator.addFrame(propertyValue, rateOfChange);
}
if (propertyKey === 'strength') {
this.strengthExtrapolator.addFrame(propertyValue, rateOfChange);
this.strengthInterpolator.addFrame(propertyValue, rateOfChange);
}
}
@ -194,12 +194,12 @@ export class CharacterView extends CharacterBase {
}
private step({ deltaTimeInSeconds }: StepCommand): void {
this.head! = this.headExtrapolator.getValue(deltaTimeInSeconds);
this.leftFoot! = this.leftFootExtrapolator.getValue(deltaTimeInSeconds);
this.rightFoot! = this.rightFootExtrapolator.getValue(deltaTimeInSeconds);
this.head! = this.headInterpolator.getValue(deltaTimeInSeconds);
this.leftFoot! = this.leftFootInterpolator.getValue(deltaTimeInSeconds);
this.rightFoot! = this.rightFootInterpolator.getValue(deltaTimeInSeconds);
this.strength = clamp(
this.strengthExtrapolator.getValue(deltaTimeInSeconds),
this.strengthInterpolator.getValue(deltaTimeInSeconds),
0,
settings.playerMaxStrength,
);
@ -224,7 +224,7 @@ export class CharacterView extends CharacterBase {
public onShoot(strength: number) {
const q = clamp01(
(strength - settings.chargeShotStrengthMin) /
(settings.chargeShotStrengthMax - settings.chargeShotStrengthMin),
(settings.chargeShotStrengthMax - settings.chargeShotStrengthMin),
);
SoundHandler.play(Sounds.shoot, mix(0.55, 1, q), mix(1.15, 0.8, q));
this.muzzleFlashIntensity = mix(0.35, 1, q);

View file

@ -12,6 +12,7 @@ import {
import { BeforeDestroyCommand } from '../../commands/types/before-destroy';
import { RenderCommand } from '../../commands/types/render';
import { StepCommand } from '../../commands/types/step';
import { LinearInterpolator } from '../../helper/interpolators/linear-interpolator';
import { PlanetShape } from '../../shapes/planet-shape';
const fallingPointLifetimeMs = 2000;
@ -39,7 +40,10 @@ abstract class FlareBudget {
export class PlanetView extends PlanetBase {
private shape: PlanetShape;
private ownershipProgress: HTMLElement;
private readonly rotationSpeed: number;
// Rotation is owned by the backend (it drives the collision polygon too) and
// streamed in; the interpolator replays the angle on the shared snapshot
// timeline, in sync with the characters standing on the surface.
private readonly rotationInterpolator = new LinearInterpolator(0);
private flareLight?: CircleLight;
private flareIntensity = 0;
@ -56,15 +60,13 @@ export class PlanetView extends PlanetBase {
super(id, vertices);
this.shape = new PlanetShape(vertices, ownership);
this.shape.randomOffset = Random.getRandom();
this.rotationSpeed =
(0.05 + Random.getRandom() * 0.07) * (Random.getRandom() < 0.5 ? -1 : 1);
this.ownershipProgress = document.createElement('div');
this.ownershipProgress.className = 'ownership';
}
private step({ deltaTimeInSeconds }: StepCommand): void {
this.shape.rotation += deltaTimeInSeconds * this.rotationSpeed;
this.shape.rotation = this.rotationInterpolator.getValue(deltaTimeInSeconds);
this.shape.colorMixQ = this.ownership;
if (this.flareIntensity > 0) {
@ -120,8 +122,16 @@ export class PlanetView extends PlanetBase {
this.releaseFlareSlot();
}
private updateProperty({ propertyValue }: UpdatePropertyCommand): void {
this.ownership = propertyValue;
private updateProperty({
propertyKey,
propertyValue,
rateOfChange,
}: UpdatePropertyCommand): void {
if (propertyKey === 'rotation') {
this.rotationInterpolator.addFrame(propertyValue, rateOfChange);
} else {
this.ownership = propertyValue;
}
}
private draw({ renderer, overlay, shouldChangeLayout }: RenderCommand): void {
@ -137,7 +147,7 @@ export class PlanetView extends PlanetBase {
if (this.lastGeneratedPoint !== undefined) {
const element = document.createElement('div');
element.className = 'falling-point ' + (this.ownership < 0.5 ? 'decla' : 'red');
element.className = 'falling-point ' + (this.ownership < 0.5 ? 'blue' : 'red');
element.innerText = '+' + this.lastGeneratedPoint;
element.style.left = `${screenPosition.x}px`;
element.style.top = `${screenPosition.y}px`;
@ -159,12 +169,12 @@ export class PlanetView extends PlanetBase {
}
private getGradient(): string {
const sideDecla = this.ownership < 0.5;
const sideBlue = this.ownership < 0.5;
const sidePercent = (Math.abs(this.ownership - 0.5) / 0.5) * 100;
return sideDecla
return sideBlue
? `conic-gradient(
var(--bright-decla) ${sidePercent}%,
var(--bright-decla) ${sidePercent}%,
var(--bright-blue) ${sidePercent}%,
var(--bright-blue) ${sidePercent}%,
rgba(0, 0, 0, 0) ${sidePercent}%,
rgba(0, 0, 0, 0) 100%
)`

View file

@ -10,12 +10,12 @@ import {
} from 'shared';
import { RenderCommand } from '../../commands/types/render';
import { StepCommand } from '../../commands/types/step';
import { Vec2Extrapolator } from '../../helper/extrapolators/vec2-extrapolator';
import { Vec2Interpolator } from '../../helper/interpolators/vec2-interpolator';
export class ProjectileView extends ProjectileBase {
private light: CircleLight;
private centerExtrapolator: Vec2Extrapolator;
private centerInterpolator: Vec2Interpolator;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: this.draw.bind(this),
@ -36,17 +36,17 @@ export class ProjectileView extends ProjectileBase {
settings.paletteDim[settings.colorIndices[team]],
0,
);
this.centerExtrapolator = new Vec2Extrapolator(center);
this.centerInterpolator = new Vec2Interpolator(center);
}
private updateProperty({ propertyValue, rateOfChange }: UpdatePropertyCommand): void {
this.centerExtrapolator.addFrame(propertyValue, rateOfChange);
this.centerInterpolator.addFrame(propertyValue, rateOfChange);
}
private handleStep({ deltaTimeInSeconds }: StepCommand): void {
this.step(deltaTimeInSeconds);
this.center = this.centerExtrapolator.getValue(deltaTimeInSeconds);
this.center = this.centerInterpolator.getValue(deltaTimeInSeconds);
this.light.center = this.center;
this.light.intensity = Math.min(
0.1,