GLM
This commit is contained in:
parent
b9274a42c7
commit
5017ff168a
1 changed files with 1004 additions and 2007 deletions
|
|
@ -32,14 +32,7 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
public camera: Camera = new Camera(this.game);
|
||||
private wasLocalPlayerAlive = false;
|
||||
|
||||
// Create and delete arrive on the wire, but positions are drawn
|
||||
// interpolationDelaySeconds in the past — so applying them the instant a
|
||||
// packet lands made every projectile pop into existence and hang at its muzzle
|
||||
// for 100 ms, then disappear a few hundred units before its rendered impact.
|
||||
// Objects are therefore admitted and retired on the render timeline instead:
|
||||
// they exist (and accumulate interpolation frames) from the moment they
|
||||
// arrive, but only draw once the cursor reaches the snapshot that introduced
|
||||
// them, and keep drawing until it reaches the one that removed them.
|
||||
// Create/delete are admitted/retired on the render timeline (not the wire) so interpolated objects don't pop at the muzzle.
|
||||
private visibleFrom = new Map<Id, number>();
|
||||
private deleteAt = new Map<Id, number>();
|
||||
private awaitingCreateStamp: Array<Id> = [];
|
||||
|
|
@ -50,10 +43,7 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
this.player = c.character as CharacterView;
|
||||
this.player.isMainCharacter = true;
|
||||
this.addObject(this.player);
|
||||
// Fresh character (first spawn or respawn at a far planet): drop any
|
||||
// prediction state so it snaps to the new body instead of gliding across.
|
||||
localCharacterPredictor.reset();
|
||||
// Respawned — clear the elimination overlay.
|
||||
FeedbackHud.hideElimination();
|
||||
this.wasLocalPlayerAlive = true;
|
||||
},
|
||||
|
|
@ -65,32 +55,22 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
}),
|
||||
|
||||
[StepCommand.type]: (c: StepCommand) => {
|
||||
// A batch carrying no property updates (a bare announcement, say) leaves
|
||||
// entries unstamped; fall back to the newest known snapshot so nothing is
|
||||
// stranded invisible.
|
||||
// Fall back to newest snapshot if a batch had no property updates, so nothing is stranded.
|
||||
this.stampPending(serverTimeline.snapshotTime);
|
||||
this.retireDueObjects();
|
||||
this.defaultCommandExecutor(c);
|
||||
|
||||
// The local body is alive only while its object still exists (the server
|
||||
// deletes it on death) and its health is above zero — `player` keeps
|
||||
// pointing at the now-stale view after death, so both checks are needed.
|
||||
// Alive = object exists AND health > 0 (player pointer outlives the body).
|
||||
const bodyPresent = !!this.player && this.objects.has(this.player.id);
|
||||
const alive = bodyPresent && this.player.health > 0;
|
||||
|
||||
// Show the elimination overlay on the alive→dead edge; CreatePlayerCommand
|
||||
// clears it on respawn.
|
||||
if (this.wasLocalPlayerAlive && !alive) {
|
||||
FeedbackHud.showElimination();
|
||||
}
|
||||
this.wasLocalPlayerAlive = alive;
|
||||
|
||||
if (bodyPresent) {
|
||||
// Override the interpolated pose of the local player with the predicted
|
||||
// one so it responds to input immediately. Suppressed while dead so the
|
||||
// corpse can't be walked around (the server ignores a dead player's
|
||||
// input — a moving predicted body would be a pure client-side desync).
|
||||
// A large correction (respawn / death) snaps inside the predictor.
|
||||
// Override the local player's interpolated pose with the predicted one; suppressed while dead (server ignores dead input).
|
||||
localCharacterPredictor.setAlive(alive);
|
||||
localCharacterPredictor.setStrength(
|
||||
this.player.strengthFraction * settings.playerMaxStrength,
|
||||
|
|
@ -113,8 +93,7 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
|
||||
[PropertyUpdatesForObjects.type]: (c: PropertyUpdatesForObjects) => {
|
||||
serverTimeline.onSnapshot(c.timestamp);
|
||||
// Create/delete precede the property updates inside a batch, so this is
|
||||
// where the batch's server timestamp becomes known to them.
|
||||
// Create/delete precede property updates in a batch — timestamp is known here.
|
||||
this.stampPending(c.timestamp);
|
||||
c.updates.forEach((u) => {
|
||||
u.updates.forEach((au) => this.objects.get(u.id)?.handleCommand(au));
|
||||
|
|
@ -133,17 +112,10 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Forget everything the previous connection was told about.
|
||||
*
|
||||
* A reconnect is a brand-new server-side player whose view-area bookkeeping
|
||||
* starts empty, so it re-announces what is in view NOW and never sends a
|
||||
* delete for anything the dropped session held. Without this, every object
|
||||
* that has since moved out of view — the old character included — stays in the
|
||||
* map and on screen forever, as a frozen ghost.
|
||||
*
|
||||
* `player` is deliberately left pointing at the stale view: every read of it
|
||||
* is gated on the object still being in `objects`, so clearing the map is what
|
||||
* makes it inert, and the next CreatePlayerCommand replaces it.
|
||||
* A reconnect is a new server-side player that never sends deletes for the
|
||||
* old session's objects — clear them or they stay as frozen ghosts. `player`
|
||||
* is left stale: reads are gated on `objects`, and the next CreatePlayerCommand
|
||||
* replaces it.
|
||||
*/
|
||||
public reset() {
|
||||
this.objects.forEach((o) => o.handleCommand(new BeforeDestroyCommand()));
|
||||
|
|
@ -174,8 +146,7 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
protected defaultCommandExecutor(c: Command) {
|
||||
const isRender = c.type === RenderCommand.type;
|
||||
this.objects.forEach((o) => {
|
||||
// Stepping always runs, so an object that has not appeared yet is still
|
||||
// accumulating interpolation frames and enters the scene already moving.
|
||||
// Stepping runs even for not-yet-visible objects so they enter the scene already moving.
|
||||
if (isRender && !this.isVisible(o.id)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -214,9 +185,7 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
// Hand the local player's raw authoritative pose to the predictor (the
|
||||
// interpolated pose would already be ~100 ms stale). The three body parts
|
||||
// arrive together in one snapshot.
|
||||
// Feed the predictor the raw (not interpolated) authoritative pose.
|
||||
private feedPredictor(updates: Array<UpdatePropertyCommand>) {
|
||||
let head: Circle | undefined;
|
||||
let leftFoot: Circle | undefined;
|
||||
|
|
|
|||
Loading…
Reference in a new issue