claude
This commit is contained in:
parent
47603c058e
commit
c074bc2645
39 changed files with 178 additions and 148 deletions
|
|
@ -1,4 +1,17 @@
|
|||
import { CharacterTeam } from 'shared';
|
||||
|
||||
export interface GameEvents {
|
||||
addPoints(blue: number, red: number): void;
|
||||
announce(text: string): void;
|
||||
}
|
||||
|
||||
export const addPointsForTeam = (
|
||||
game: GameEvents,
|
||||
team: CharacterTeam,
|
||||
points: number,
|
||||
): void => {
|
||||
game.addPoints(
|
||||
team === CharacterTeam.blue ? points : 0,
|
||||
team === CharacterTeam.red ? points : 0,
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,14 +32,7 @@ const io = new IoServer(server, {
|
|||
|
||||
const gameServer = new GameServer(io, options);
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: (_, callback) => {
|
||||
callback(null, true);
|
||||
},
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
app.use(cors({ origin: true, credentials: true }));
|
||||
|
||||
app.get(serverInformationEndpoint, (_, res) => {
|
||||
res.json(gameServer.serverInfo);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
clamp01,
|
||||
strengthToCharge,
|
||||
stepCharacterMovement,
|
||||
applyForce,
|
||||
applyLeapImpulse,
|
||||
decayMomentum,
|
||||
tickPlanetDetachment,
|
||||
|
|
@ -27,12 +28,13 @@ import {
|
|||
rightFootOffset,
|
||||
boundRadius,
|
||||
} from 'shared';
|
||||
import { addPointsForTeam } from '../game-events';
|
||||
import { Physical } from '../physics/physical';
|
||||
import { CirclePhysical } from './circle-physical';
|
||||
import { PhysicalContainer } from '../physics/physical-container';
|
||||
import { BoundingBox } from '../physics/bounding-box';
|
||||
import { ProjectilePhysical } from './projectile-physical';
|
||||
import { PlanetPhysical, planetsIn } from './planet-physical';
|
||||
import { PlanetPhysical, planetsNear } from './planet-physical';
|
||||
|
||||
const placeholder = new Circle(vec2.create(), 0);
|
||||
|
||||
|
|
@ -99,8 +101,7 @@ export class CharacterPhysical extends CharacterBase implements Physical {
|
|||
}
|
||||
|
||||
private readonly movementWorld: CharacterWorld = {
|
||||
groundsNear: (center, radius) =>
|
||||
planetsIn(this.container.findIntersecting(BoundingBox.ofCircle(center, radius))),
|
||||
groundsNear: (center, radius) => planetsNear(this.container, center, radius),
|
||||
stepBody: (body, deltaTimeInSeconds) => {
|
||||
const hit = (body as CirclePhysical).stepManually(deltaTimeInSeconds);
|
||||
return hit instanceof PlanetPhysical ? hit : undefined;
|
||||
|
|
@ -139,12 +140,7 @@ export class CharacterPhysical extends CharacterBase implements Physical {
|
|||
}
|
||||
|
||||
public get boundingBox(): BoundingBox {
|
||||
const center = this.center;
|
||||
this.box.xMin = center[0] - boundRadius;
|
||||
this.box.xMax = center[0] + boundRadius;
|
||||
this.box.yMin = center[1] - boundRadius;
|
||||
this.box.yMax = center[1] + boundRadius;
|
||||
return this.box;
|
||||
return this.box.setCircle(this.center, boundRadius);
|
||||
}
|
||||
|
||||
public get gameObject(): this {
|
||||
|
|
@ -309,11 +305,9 @@ export class CharacterPhysical extends CharacterBase implements Physical {
|
|||
this.remoteCall('onDie');
|
||||
|
||||
if (killedInCombat) {
|
||||
const points = settings.playerKillPoint;
|
||||
this.container.game.addPoints(
|
||||
this.team === CharacterTeam.blue ? 0 : points,
|
||||
this.team === CharacterTeam.red ? 0 : points,
|
||||
);
|
||||
const opposingTeam =
|
||||
this.team === CharacterTeam.blue ? CharacterTeam.red : CharacterTeam.blue;
|
||||
addPointsForTeam(this.container.game, opposingTeam, settings.playerKillPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +421,7 @@ export class CharacterPhysical extends CharacterBase implements Physical {
|
|||
let grounded = false;
|
||||
for (const part of this.parts) {
|
||||
vec2.copy(part.velocity, this.bodyVelocity);
|
||||
part.applyForce(sumGravity(planets, part.center), deltaTime);
|
||||
applyForce(part, sumGravity(planets, part.center), deltaTime);
|
||||
if (part.stepManually(deltaTime) instanceof PlanetPhysical) {
|
||||
grounded = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { applyForce, Circle, GameObject, resolveCircleMovement } from 'shared';
|
||||
import { Circle, GameObject, resolveCircleMovement } from 'shared';
|
||||
import { BoundingBox } from '../physics/bounding-box';
|
||||
import { PhysicalContainer } from '../physics/physical-container';
|
||||
import { Physical } from '../physics/physical';
|
||||
|
|
@ -22,11 +22,7 @@ export class CirclePhysical extends Circle implements Physical {
|
|||
}
|
||||
|
||||
public get boundingBox(): BoundingBox {
|
||||
this.box.xMin = this.center[0] - this.radius;
|
||||
this.box.xMax = this.center[0] + this.radius;
|
||||
this.box.yMin = this.center[1] - this.radius;
|
||||
this.box.yMax = this.center[1] + this.radius;
|
||||
return this.box;
|
||||
return this.box.setCircle(this.center, this.radius);
|
||||
}
|
||||
|
||||
public get gameObject(): GameObject {
|
||||
|
|
@ -37,10 +33,6 @@ export class CirclePhysical extends Circle implements Physical {
|
|||
this.owner.onCollision?.(other);
|
||||
}
|
||||
|
||||
public applyForce(force: vec2, timeInSeconds: number) {
|
||||
applyForce(this, force, timeInSeconds);
|
||||
}
|
||||
|
||||
public stepManually(
|
||||
deltaTimeInSeconds: number,
|
||||
possibleIntersectors?: Array<Physical>,
|
||||
|
|
|
|||
|
|
@ -10,15 +10,23 @@ import {
|
|||
PropertyUpdatesForObject,
|
||||
UpdatePropertyCommand,
|
||||
} from 'shared';
|
||||
import { GameEvents } from '../game-events';
|
||||
import { addPointsForTeam, GameEvents } from '../game-events';
|
||||
import { BoundingBox } from '../physics/bounding-box';
|
||||
import { Physical } from '../physics/physical';
|
||||
import { PhysicalContainer } from '../physics/physical-container';
|
||||
import { LampPhysical } from './lamp-physical';
|
||||
import type { CharacterPhysical } from './character-physical';
|
||||
|
||||
export const planetsIn = (objects: Array<Physical>): Array<PlanetPhysical> =>
|
||||
objects.filter((o): o is PlanetPhysical => o instanceof PlanetPhysical);
|
||||
|
||||
export const planetsNear = (
|
||||
container: PhysicalContainer,
|
||||
center: vec2,
|
||||
radius: number,
|
||||
): Array<PlanetPhysical> =>
|
||||
planetsIn(container.findIntersecting(BoundingBox.ofCircle(center, radius)));
|
||||
|
||||
export class PlanetPhysical extends PlanetBase implements Physical {
|
||||
public readonly boundingBox: BoundingBox;
|
||||
public readonly sizePointMultiplier: number;
|
||||
|
|
@ -80,11 +88,7 @@ export class PlanetPhysical extends PlanetBase implements Physical {
|
|||
}
|
||||
|
||||
private awardToOwner(base: number) {
|
||||
const value = Math.round(base * this.sizePointMultiplier);
|
||||
this.game.addPoints(
|
||||
this.team === CharacterTeam.blue ? value : 0,
|
||||
this.team === CharacterTeam.red ? value : 0,
|
||||
);
|
||||
addPointsForTeam(this.game, this.team, Math.round(base * this.sizePointMultiplier));
|
||||
}
|
||||
|
||||
private resolveCapture(deltaTime: number) {
|
||||
|
|
|
|||
|
|
@ -84,10 +84,11 @@ export class ProjectilePhysical extends ProjectileBase implements Physical {
|
|||
}
|
||||
|
||||
public onCollision(other: GameObject) {
|
||||
if (
|
||||
!(other instanceof CharacterPhysical && other.team === this.team) &&
|
||||
this.bounceCount++ === settings.projectileMaxBounceCount
|
||||
) {
|
||||
if (other instanceof CharacterPhysical && other.team === this.team) {
|
||||
return;
|
||||
}
|
||||
this.bounceCount++;
|
||||
if (this.bounceCount > settings.projectileMaxBounceCount) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ export class BoundingBox {
|
|||
);
|
||||
}
|
||||
|
||||
public setCircle(center: vec2, radius: number): BoundingBox {
|
||||
this.xMin = center[0] - radius;
|
||||
this.xMax = center[0] + radius;
|
||||
this.yMin = center[1] - radius;
|
||||
this.yMax = center[1] + radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public intersects(other: BoundingBox): boolean {
|
||||
return (
|
||||
this.xMin < other.xMax &&
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { GameObject } from 'shared';
|
||||
import { GameObject, Sdf } from 'shared';
|
||||
import { BoundingBox } from './bounding-box';
|
||||
|
||||
export interface Physical {
|
||||
readonly canCollide: boolean;
|
||||
export interface Physical extends Sdf {
|
||||
readonly boundingBox: BoundingBox;
|
||||
readonly gameObject: GameObject;
|
||||
|
||||
distance(target: vec2): number;
|
||||
step?(deltaTimeInSeconds: number): void;
|
||||
onCollision?(other: GameObject): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { PlayerInformation, settings, Random, CharacterTeam, Id } from 'shared';
|
||||
import {
|
||||
PlayerInformation,
|
||||
settings,
|
||||
Random,
|
||||
CharacterTeam,
|
||||
Id,
|
||||
evaluateSdf,
|
||||
} from 'shared';
|
||||
import { PhysicalContainer } from '../physics/physical-container';
|
||||
import { BoundingBox } from '../physics/bounding-box';
|
||||
import { PlayerContainer } from './player-container';
|
||||
|
|
@ -321,10 +328,7 @@ export class NPC extends PlayerBase {
|
|||
let traveled = tuning.lineOfSightStartOffset;
|
||||
const position = vec2.scaleAndAdd(vec2.create(), this.center, direction, traveled);
|
||||
while (traveled < totalDistance) {
|
||||
let sdf = Infinity;
|
||||
for (const planet of planets) {
|
||||
sdf = Math.min(sdf, planet.distance(position));
|
||||
}
|
||||
const sdf = evaluateSdf(position, planets);
|
||||
if (sdf < tuning.lineOfSightClearance) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { PhysicalContainer } from '../physics/physical-container';
|
||||
import { BoundingBox } from '../physics/bounding-box';
|
||||
import { CharacterPhysical } from '../objects/character-physical';
|
||||
import { PlanetPhysical, planetsIn } from '../objects/planet-physical';
|
||||
import { PlanetPhysical, planetsNear } from '../objects/planet-physical';
|
||||
import { PlayerContainer } from './player-container';
|
||||
|
||||
const maximumNameLength = 40;
|
||||
|
|
@ -65,7 +65,9 @@ export abstract class PlayerBase extends CommandReceiver {
|
|||
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) {
|
||||
|
|
@ -89,10 +91,10 @@ export abstract class PlayerBase extends CommandReceiver {
|
|||
}
|
||||
|
||||
private findSpawnCenter(): vec2 {
|
||||
const planets = planetsIn(
|
||||
this.objectContainer.findIntersecting(
|
||||
BoundingBox.ofCircle(vec2.create(), settings.worldRadius * 2),
|
||||
),
|
||||
const planets = planetsNear(
|
||||
this.objectContainer,
|
||||
vec2.create(),
|
||||
settings.worldRadius * 2,
|
||||
);
|
||||
|
||||
const friendly = planets.filter((p) => p.team === this.team);
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ export class PlayerContainer {
|
|||
);
|
||||
for (let i = 0; i < newNpcCount; i++) {
|
||||
const name = `🤖 ${Random.choose(settings.npcNames)}`;
|
||||
this.npcs.push(
|
||||
new NPC({ name }, this, this.objects, this.getTeamOfNextPlayer(true)),
|
||||
);
|
||||
this.npcs.push(new NPC({ name }, this, this.objects, this.getTeamOfNextNpc()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,8 +100,15 @@ export class PlayerContainer {
|
|||
this._players.forEach((p) => p.sendQueuedCommandsToClient());
|
||||
}
|
||||
|
||||
private getTeamOfNextPlayer(isNpc = false): CharacterTeam {
|
||||
const players = isNpc ? this.players : this._players;
|
||||
private getTeamOfNextPlayer(): CharacterTeam {
|
||||
return this.teamOfNext(this._players);
|
||||
}
|
||||
|
||||
private getTeamOfNextNpc(): CharacterTeam {
|
||||
return this.teamOfNext(this.players);
|
||||
}
|
||||
|
||||
private teamOfNext(players: Array<PlayerBase>): CharacterTeam {
|
||||
const blueCount = players.filter((p) => p.team === CharacterTeam.blue).length;
|
||||
const redCount = players.filter((p) => p.team === CharacterTeam.red).length;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ const minimumAspectRatio = 0.2;
|
|||
const maximumAspectRatio = 8;
|
||||
|
||||
const sheddableWhenBackedUp: ReadonlyArray<string> = [
|
||||
PropertyUpdatesForObjects.type,
|
||||
UpdateMinimap.type,
|
||||
InputAcknowledgement.type,
|
||||
PropertyUpdatesForObjects.name,
|
||||
UpdateMinimap.name,
|
||||
InputAcknowledgement.name,
|
||||
];
|
||||
|
||||
export class Player extends PlayerBase {
|
||||
|
|
@ -68,7 +68,7 @@ export class Player extends PlayerBase {
|
|||
|
||||
// Input is untrusted: one non-finite value reaching the simulation leaves a character that never dies.
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) => {
|
||||
[SetAspectRatioActionCommand.name]: (v: SetAspectRatioActionCommand) => {
|
||||
this.aspectRatio = finiteInRange(
|
||||
v.aspectRatio,
|
||||
minimumAspectRatio,
|
||||
|
|
@ -76,7 +76,7 @@ export class Player extends PlayerBase {
|
|||
this.aspectRatio,
|
||||
);
|
||||
},
|
||||
[MoveActionCommand.type]: (c: MoveActionCommand) => {
|
||||
[MoveActionCommand.name]: (c: MoveActionCommand) => {
|
||||
const direction = finiteVec2(c.direction, 1);
|
||||
if (!direction) {
|
||||
return;
|
||||
|
|
@ -84,7 +84,7 @@ export class Player extends PlayerBase {
|
|||
this.observeClientTime(c.clientTimeMs);
|
||||
this.character?.setMoveDirection(direction);
|
||||
},
|
||||
[PrimaryActionCommand.type]: (c: PrimaryActionCommand) => {
|
||||
[PrimaryActionCommand.name]: (c: PrimaryActionCommand) => {
|
||||
const position = finiteVec2(c.position, settings.maxClientPositionMagnitude);
|
||||
if (!position) {
|
||||
return;
|
||||
|
|
@ -92,7 +92,7 @@ export class Player extends PlayerBase {
|
|||
this.observeClientTime(c.clientTimeMs);
|
||||
this.character?.shootTowards(position, finiteInRange(c.charge, 0, 1, 0));
|
||||
},
|
||||
[LeapActionCommand.type]: (c: LeapActionCommand) => {
|
||||
[LeapActionCommand.name]: (c: LeapActionCommand) => {
|
||||
if (!isFiniteNumber(c.clientTimeMs)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ export class Player extends PlayerBase {
|
|||
this.observeClientTime(c.clientTimeMs);
|
||||
this.character?.leap();
|
||||
},
|
||||
[ClientHeartbeatCommand.type]: (c: ClientHeartbeatCommand) =>
|
||||
[ClientHeartbeatCommand.name]: (c: ClientHeartbeatCommand) =>
|
||||
this.observeClientTime(c.clientTimeMs),
|
||||
};
|
||||
|
||||
|
|
@ -150,10 +150,6 @@ export class Player extends PlayerBase {
|
|||
this.winnerTeam = winnerTeam;
|
||||
}
|
||||
|
||||
public step(deltaTimeInSeconds: number) {
|
||||
this.stepLifecycle(deltaTimeInSeconds);
|
||||
}
|
||||
|
||||
public queueCommandSend(command: Command) {
|
||||
this.commandsToBeSent.push(command);
|
||||
}
|
||||
|
|
@ -273,7 +269,7 @@ export class Player extends PlayerBase {
|
|||
// Only state the next snapshot regenerates may be dropped; create/delete are one-shot.
|
||||
if (this.bufferedBytes > settings.maxBufferedBytesPerClient) {
|
||||
this.commandsToBeSent = this.commandsToBeSent.filter(
|
||||
(c) => !sheddableWhenBackedUp.includes(c.type),
|
||||
(c) => !sheddableWhenBackedUp.includes(c.constructor.name),
|
||||
);
|
||||
}
|
||||
if (this.commandsToBeSent.length === 0) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { chargeHeldSince } from 'shared';
|
||||
import { pointer } from './helper/pointer';
|
||||
import { getDisplayPosition } from './helper/pointer';
|
||||
|
||||
let element: HTMLElement | undefined;
|
||||
let heldSince = 0;
|
||||
|
|
@ -42,7 +42,7 @@ function updateChargeIndicator() {
|
|||
element.classList.toggle('full', charge >= 1);
|
||||
|
||||
if (isFollowingPointer) {
|
||||
const cursor = pointer.displayPosition;
|
||||
const cursor = getDisplayPosition();
|
||||
if (cursor) {
|
||||
element.style.left = `${cursor[0]}px`;
|
||||
element.style.top = `${cursor[1]}px`;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
localCharacterPredictor,
|
||||
} from '../helper/prediction/local-character-predictor';
|
||||
|
||||
export abstract class InputGenerator {
|
||||
export class InputGenerator {
|
||||
constructor(protected readonly onCommand: (command: Command) => void) {}
|
||||
|
||||
protected sendMove(direction: vec2) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import type { Command } from 'shared';
|
||||
import { InputGenerator } from './input-generator';
|
||||
|
||||
export class KeyboardListener extends InputGenerator {
|
||||
private keysDown: Set<string> = new Set();
|
||||
|
||||
constructor(onCommand: ConstructorParameters<typeof InputGenerator>[0]) {
|
||||
constructor(onCommand: (command: Command) => void) {
|
||||
super(onCommand);
|
||||
addEventListener('keydown', this.keyDownListener);
|
||||
addEventListener('keyup', this.keyUpListener);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { chargeHeldSince, Command } from 'shared';
|
||||
import { Game } from '../game';
|
||||
import { chargeHeldSince } from 'shared';
|
||||
import type { Command } from 'shared';
|
||||
import type { Game } from '../game';
|
||||
import { ChargeIndicator } from '../charge-indicator';
|
||||
import { pointer } from '../helper/pointer';
|
||||
import { setDisplayPosition } from '../helper/pointer';
|
||||
import { InputGenerator } from './input-generator';
|
||||
|
||||
export class MouseListener extends InputGenerator {
|
||||
|
|
@ -22,7 +23,7 @@ export class MouseListener extends InputGenerator {
|
|||
}
|
||||
|
||||
private mouseMoveListener = (event: MouseEvent) => {
|
||||
pointer.displayPosition = vec2.fromValues(event.clientX, event.clientY);
|
||||
setDisplayPosition(vec2.fromValues(event.clientX, event.clientY));
|
||||
};
|
||||
|
||||
private mouseDownListener = (event: MouseEvent) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { chargeHeldSince, Command, settings } from 'shared';
|
||||
import { Game } from '../game';
|
||||
import { chargeHeldSince, settings } from 'shared';
|
||||
import type { Command } from 'shared';
|
||||
import type { Game } from '../game';
|
||||
import { ChargeIndicator } from '../charge-indicator';
|
||||
import { centeredTransform } from '../helper/centered-transform';
|
||||
import { InputGenerator } from './input-generator';
|
||||
|
||||
const deadZone = 8;
|
||||
|
|
@ -104,11 +106,14 @@ export class TouchListener extends InputGenerator {
|
|||
this.primaryDownAt = null;
|
||||
ChargeIndicator.end();
|
||||
this.overlay.appendChild(this.joystick);
|
||||
this.joystick.style.transform = `translateX(${this.touchStartPosition[0]}px) translateY(${this.touchStartPosition[1]}px) translateX(-50%) translateY(-50%)`;
|
||||
this.joystick.style.transform = centeredTransform(
|
||||
this.touchStartPosition[0],
|
||||
this.touchStartPosition[1],
|
||||
);
|
||||
}
|
||||
|
||||
vec2.scale(delta, delta, Math.min(1, joystickMaxLength / deltaLength));
|
||||
this.joystickButton.style.transform = `translateX(${delta[0]}px) translateY(${delta[1]}px) translateX(-50%) translateY(-50%)`;
|
||||
this.joystickButton.style.transform = centeredTransform(delta[0], delta[1]);
|
||||
|
||||
if (deltaLength > deadZone) {
|
||||
vec2.set(delta, delta[0], -delta[1]);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { settings } from 'shared';
|
||||
import { pointer } from './helper/pointer';
|
||||
import { getDisplayPosition } from './helper/pointer';
|
||||
|
||||
let root: HTMLElement | undefined;
|
||||
let killfeed: HTMLElement | undefined;
|
||||
|
|
@ -20,7 +20,7 @@ function ensureRoot(): { root: HTMLElement; killfeed: HTMLElement } {
|
|||
}
|
||||
|
||||
function focusPoint(): { x: number; y: number } {
|
||||
const cursor = pointer.displayPosition;
|
||||
const cursor = getDisplayPosition();
|
||||
if (cursor) {
|
||||
return { x: cursor[0], y: cursor[1] };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import parser from 'socket.io-msgpack-parser';
|
|||
import { CharacterShape } from './shapes/character-shape';
|
||||
import { PlanetShape } from './shapes/planet-shape';
|
||||
import { serverTimeline } from './helper/server-timeline';
|
||||
import { centeredTransform } from './helper/centered-transform';
|
||||
import {
|
||||
localCharacterPredictor,
|
||||
setFrameTimeMs,
|
||||
|
|
@ -79,21 +80,21 @@ export class Game extends CommandReceiver {
|
|||
private framesSinceLastLayoutUpdate = 0;
|
||||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[ServerAnnouncement.type]: (c: ServerAnnouncement) => {
|
||||
[ServerAnnouncement.name]: (c: ServerAnnouncement) => {
|
||||
this.lastAnnouncementText = c.text;
|
||||
this.timeSinceLastAnnouncement = 0;
|
||||
},
|
||||
[UpdateGameState.type]: (c: UpdateGameState) => (this.lastGameState = c),
|
||||
[InputAcknowledgement.type]: (c: InputAcknowledgement) =>
|
||||
[UpdateGameState.name]: (c: UpdateGameState) => (this.lastGameState = c),
|
||||
[InputAcknowledgement.name]: (c: InputAcknowledgement) =>
|
||||
localCharacterPredictor.acknowledge(
|
||||
c.clientTimeMs,
|
||||
c.movement,
|
||||
c.lastLeapClientTimeMs,
|
||||
c.ackAgeMs,
|
||||
),
|
||||
[GameEndCommand.type]: () => (this.isEnding = true),
|
||||
[UpdateMinimap.type]: (c: UpdateMinimap) => (this.lastMinimap = c),
|
||||
[GameStartCommand.type]: () => this.initialize(),
|
||||
[GameEndCommand.name]: () => (this.isEnding = true),
|
||||
[UpdateMinimap.name]: (c: UpdateMinimap) => (this.lastMinimap = c),
|
||||
[GameStartCommand.name]: () => this.initialize(),
|
||||
};
|
||||
|
||||
constructor(
|
||||
|
|
@ -220,6 +221,7 @@ export class Game extends CommandReceiver {
|
|||
|
||||
public async start(): Promise<void> {
|
||||
this.initialize();
|
||||
this.resolveStarted();
|
||||
const noiseTexture = await renderNoise([256, 256], 2, 1);
|
||||
|
||||
await runAnimation(
|
||||
|
|
@ -311,7 +313,6 @@ export class Game extends CommandReceiver {
|
|||
deltaTime: DOMHighResTimeStamp,
|
||||
): boolean {
|
||||
this.renderer = renderer;
|
||||
this.resolveStarted();
|
||||
// The frame timestamp, not performance.now(): dispatch jitter would land in the replay window.
|
||||
setFrameTimeMs(currentTime);
|
||||
deltaTime /= 1000;
|
||||
|
|
@ -408,6 +409,10 @@ export class Game extends CommandReceiver {
|
|||
deltaX = (deltaY * dx) / dy;
|
||||
}
|
||||
|
||||
this.keystoneArrow.style.transform = `translateX(${width / 2 + deltaX}px) translateY(${height / 2 + deltaY}px) translateX(-50%) translateY(-50%) rotate(${angle + Math.PI / 2}rad)`;
|
||||
this.keystoneArrow.style.transform = centeredTransform(
|
||||
width / 2 + deltaX,
|
||||
height / 2 + deltaY,
|
||||
` rotate(${angle + Math.PI / 2}rad)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
frontend/src/scripts/helper/centered-transform.ts
Normal file
2
frontend/src/scripts/helper/centered-transform.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const centeredTransform = (x: number, y: number, extra = ''): string =>
|
||||
`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)${extra}`;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue