ai
This commit is contained in:
parent
b6db7e8dc7
commit
d9b80b92ca
22 changed files with 563 additions and 62 deletions
|
|
@ -15,10 +15,24 @@ export class RemoteCall {
|
|||
}
|
||||
}
|
||||
|
||||
// Every object property streamed via UpdatePropertyCommand. A single union means
|
||||
// a typo or rename on the producing (server *-physical) or consuming (client
|
||||
// *-view) side is a compile error instead of a silently dropped update that
|
||||
// just stops a body interpolating. The wire format is unchanged — these remain
|
||||
// the same strings, only now compiler-checked at both ends.
|
||||
export type SyncPropertyKey =
|
||||
| 'head'
|
||||
| 'leftFoot'
|
||||
| 'rightFoot'
|
||||
| 'strength'
|
||||
| 'center'
|
||||
| 'ownership'
|
||||
| 'rotation';
|
||||
|
||||
@serializable
|
||||
export class UpdatePropertyCommand extends Command {
|
||||
constructor(
|
||||
public readonly propertyKey: string,
|
||||
public readonly propertyKey: SyncPropertyKey,
|
||||
public readonly propertyValue: any,
|
||||
public readonly rateOfChange: any,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Id } from '../../communication/id';
|
||||
import { Circle } from '../../helper/circle';
|
||||
import { serializable } from '../../serialization/serializable';
|
||||
import { toArrayFromFields } from '../../serialization/serialized-fields';
|
||||
import { GameObject } from '../game-object';
|
||||
|
||||
export enum CharacterTeam {
|
||||
|
|
@ -11,6 +12,18 @@ export enum CharacterTeam {
|
|||
|
||||
@serializable
|
||||
export class CharacterBase extends GameObject {
|
||||
private static readonly serializedFields = [
|
||||
'id',
|
||||
'name',
|
||||
'killCount',
|
||||
'deathCount',
|
||||
'team',
|
||||
'health',
|
||||
'head',
|
||||
'leftFoot',
|
||||
'rightFoot',
|
||||
] as const;
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
public name: string,
|
||||
|
|
@ -26,37 +39,27 @@ export class CharacterBase extends GameObject {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onShoot(strength: number) { }
|
||||
public onShoot(strength: number) {}
|
||||
|
||||
public onLeap() { }
|
||||
public onLeap() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onHitConfirmed(charge?: number) { }
|
||||
public onHitConfirmed(charge?: number) {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onKillConfirmed(victimName?: string, streak?: number, charge?: number) { }
|
||||
public onKillConfirmed(victimName?: string, streak?: number, charge?: number) {}
|
||||
|
||||
public setHealth(health: number) {
|
||||
this.health = health;
|
||||
}
|
||||
|
||||
public onDie() { }
|
||||
public onDie() {}
|
||||
|
||||
public setKillCount(killCount: number) {
|
||||
this.killCount = killCount;
|
||||
}
|
||||
|
||||
public toArray(): Array<any> {
|
||||
return [
|
||||
this.id,
|
||||
this.name,
|
||||
this.killCount,
|
||||
this.deathCount,
|
||||
this.team,
|
||||
this.health,
|
||||
this.head,
|
||||
this.leftFoot,
|
||||
this.rightFoot,
|
||||
];
|
||||
return toArrayFromFields(this, CharacterBase.serializedFields);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { Id, serializable } from '../../main';
|
||||
import { toArrayFromFields } from '../../serialization/serialized-fields';
|
||||
import { GameObject } from '../game-object';
|
||||
|
||||
@serializable
|
||||
export class LampBase extends GameObject {
|
||||
private static readonly serializedFields = [
|
||||
'id',
|
||||
'center',
|
||||
'color',
|
||||
'lightness',
|
||||
] as const;
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
public center: vec2,
|
||||
|
|
@ -19,7 +27,6 @@ export class LampBase extends GameObject {
|
|||
public setLight(color: vec3, lightness: number) {}
|
||||
|
||||
public toArray(): Array<any> {
|
||||
const { id, center, color, lightness } = this;
|
||||
return [id, center, color, lightness];
|
||||
return toArrayFromFields(this, LampBase.serializedFields);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,22 @@ import { vec2 } from 'gl-matrix';
|
|||
import { Random } from '../../helper/random';
|
||||
import { settings } from '../../settings';
|
||||
import { serializable } from '../../serialization/serializable';
|
||||
import { toArrayFromFields } from '../../serialization/serialized-fields';
|
||||
import { GameObject } from '../game-object';
|
||||
import { Id } from '../../communication/id';
|
||||
import { CharacterTeam } from './character-base';
|
||||
|
||||
@serializable
|
||||
export class PlanetBase extends GameObject {
|
||||
// centre/radius are derived from vertices in the constructor, so they are not
|
||||
// serialized — only the constructor parameters are.
|
||||
private static readonly serializedFields = [
|
||||
'id',
|
||||
'vertices',
|
||||
'ownership',
|
||||
'isKeystone',
|
||||
] as const;
|
||||
|
||||
public readonly center: vec2;
|
||||
public readonly radius: number;
|
||||
|
||||
|
|
@ -57,6 +67,6 @@ export class PlanetBase extends GameObject {
|
|||
}
|
||||
|
||||
public toArray(): Array<any> {
|
||||
return [this.id, this.vertices, this.ownership, this.isKeystone];
|
||||
return toArrayFromFields(this, PlanetBase.serializedFields);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { settings } from '../../settings';
|
||||
import { serializable } from '../../serialization/serializable';
|
||||
import { toArrayFromFields } from '../../serialization/serialized-fields';
|
||||
import { GameObject } from '../game-object';
|
||||
import { Id } from '../../communication/id';
|
||||
import { CharacterTeam } from './character-base';
|
||||
|
||||
@serializable
|
||||
export class ProjectileBase extends GameObject {
|
||||
private static readonly serializedFields = [
|
||||
'id',
|
||||
'center',
|
||||
'radius',
|
||||
'team',
|
||||
'strength',
|
||||
] as const;
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
public center: vec2,
|
||||
|
|
@ -23,6 +32,6 @@ export class ProjectileBase extends GameObject {
|
|||
}
|
||||
|
||||
public toArray(): Array<any> {
|
||||
return [this.id, this.center, this.radius, this.team, this.strength];
|
||||
return toArrayFromFields(this, ProjectileBase.serializedFields);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,13 @@ const springMove = (
|
|||
|
||||
const keepPosture = (state: CharacterMovementState) => {
|
||||
const center = characterCenter(state);
|
||||
springMove(state, state.leftFoot, center, leftFootOffset, settings.postureFeetStiffness);
|
||||
springMove(
|
||||
state,
|
||||
state.leftFoot,
|
||||
center,
|
||||
leftFootOffset,
|
||||
settings.postureFeetStiffness,
|
||||
);
|
||||
springMove(
|
||||
state,
|
||||
state.rightFoot,
|
||||
|
|
@ -133,7 +139,12 @@ const carryWithRotatingPlanet = (
|
|||
const angle = -planet.angularVelocity * deltaTimeInSeconds;
|
||||
const center = planet.center;
|
||||
state.head.center = vec2.rotate(vec2.create(), state.head.center, center, angle);
|
||||
state.leftFoot.center = vec2.rotate(vec2.create(), state.leftFoot.center, center, angle);
|
||||
state.leftFoot.center = vec2.rotate(
|
||||
vec2.create(),
|
||||
state.leftFoot.center,
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
state.rightFoot.center = vec2.rotate(
|
||||
vec2.create(),
|
||||
state.rightFoot.center,
|
||||
|
|
@ -148,10 +159,7 @@ const carryWithRotatingPlanet = (
|
|||
// server's leap() and the client's prediction apply the exact same impulse.
|
||||
// The caller does the gating (strength, cooldown, alive); this is a no-op when
|
||||
// not on a surface.
|
||||
export const applyLeapImpulse = (
|
||||
state: CharacterMovementState,
|
||||
moveDirection: vec2,
|
||||
) => {
|
||||
export const applyLeapImpulse = (state: CharacterMovementState, moveDirection: vec2) => {
|
||||
const planet = state.currentPlanet;
|
||||
if (!planet) {
|
||||
return;
|
||||
|
|
@ -206,7 +214,9 @@ export const tickPlanetDetachment = (
|
|||
state: CharacterMovementState,
|
||||
deltaTimeInSeconds: number,
|
||||
) => {
|
||||
if ((state.secondsSinceOnSurface += deltaTimeInSeconds) > settings.planetDetachmentSeconds) {
|
||||
if (
|
||||
(state.secondsSinceOnSurface += deltaTimeInSeconds) > settings.planetDetachmentSeconds
|
||||
) {
|
||||
state.currentPlanet = undefined;
|
||||
}
|
||||
};
|
||||
|
|
@ -255,10 +265,7 @@ export const decayMomentum = (
|
|||
}
|
||||
};
|
||||
|
||||
const decayBodyMomentum = (
|
||||
state: CharacterMovementState,
|
||||
deltaTimeInSeconds: number,
|
||||
) => {
|
||||
const decayBodyMomentum = (state: CharacterMovementState, deltaTimeInSeconds: number) => {
|
||||
decayMomentum(state.bodyVelocity, !!state.currentPlanet, deltaTimeInSeconds);
|
||||
};
|
||||
|
||||
|
|
@ -293,7 +300,11 @@ export const stepCharacterMovement = (
|
|||
const center = characterCenter(state);
|
||||
const grounds = world.groundsNear(center, boundRadius + settings.maxGravityDistance);
|
||||
|
||||
const movementForce = vec2.scale(inputDirection, inputDirection, settings.maxAcceleration);
|
||||
const movementForce = vec2.scale(
|
||||
inputDirection,
|
||||
inputDirection,
|
||||
settings.maxAcceleration,
|
||||
);
|
||||
applyForce(state.leftFoot, movementForce, deltaTimeInSeconds);
|
||||
applyForce(state.rightFoot, movementForce, deltaTimeInSeconds);
|
||||
|
||||
|
|
|
|||
13
shared/src/serialization/serialized-fields.ts
Normal file
13
shared/src/serialization/serialized-fields.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Derive a serializable object's positional wire array from a single declared
|
||||
// list of field names, so toArray() and the constructor share ONE source of
|
||||
// truth for field order instead of a hand-written array that can silently drift
|
||||
// out of step with the parameters.
|
||||
//
|
||||
// deserialize reconstructs via `new Ctor(...array.slice(1))`, so the field list
|
||||
// MUST name the constructor's parameters in order. Fields a constructor
|
||||
// recomputes from others (e.g. a planet's centre/radius from its vertices) are
|
||||
// derived, not serialized, and so are deliberately absent from the list.
|
||||
export const toArrayFromFields = (
|
||||
object: any,
|
||||
fields: ReadonlyArray<string>,
|
||||
): Array<any> => fields.map((field) => object[field]);
|
||||
Loading…
Add table
Add a link
Reference in a new issue