Refactor frontend to use commands
This commit is contained in:
parent
be26ab422c
commit
503c99cb1f
25 changed files with 11098 additions and 26151 deletions
|
|
@ -42,11 +42,11 @@
|
|||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
|
||||
"workspaceFolder": "/workspace",
|
||||
"mounts": [
|
||||
"source=decla-red-root-node_modules-volume3,target=/workspace/node_modules,type=volume",
|
||||
"source=decla-red-frontend-node_modules-volume3,target=/workspace/frontend/node_modules,type=volume",
|
||||
"source=decla-red-backend-node_modules-volume3,target=/workspace/backend/node_modules,type=volume",
|
||||
"source=decla-red-shared-node_modules-volume3,target=/workspace/shared/node_modules,type=volume"
|
||||
"source=decla-red-root-node_modules-volume4,target=/workspace/node_modules,type=volume",
|
||||
"source=decla-red-frontend-node_modules-volume4,target=/workspace/frontend/node_modules,type=volume",
|
||||
"source=decla-red-backend-node_modules-volume4,target=/workspace/backend/node_modules,type=volume",
|
||||
"source=decla-red-shared-node_modules-volume4,target=/workspace/shared/node_modules,type=volume"
|
||||
],
|
||||
"forwardPorts": [3000, 8080],
|
||||
"postCreateCommand": "chown node:node ./**/node_modules && yarn"
|
||||
"postCreateCommand": "chown node:node ./**/node_modules && npm run build"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "npx webpack --mode production",
|
||||
"start": "npx concurrently --kill-others-on-fail \"npx webpack --mode development -w\" \"npx nodemon --legacy-watch dist/main.js\"",
|
||||
"start": "concurrently --kill-others-on-fail \"webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"",
|
||||
"try-build": "npm run build && cd dist && node main.js && cd -"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export class GameServer {
|
|||
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
||||
try {
|
||||
const commands: Array<Command> = deserialize(json);
|
||||
commands.forEach((c) => player.sendCommand(c));
|
||||
commands.forEach((c) => player.handleCommand(c));
|
||||
} catch (e) {
|
||||
console.error('Error while processing command', e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
CharacterBase,
|
||||
CharacterTeam,
|
||||
PropertyUpdatesForObject,
|
||||
UpdateProperty,
|
||||
UpdatePropertyCommand,
|
||||
} from 'shared';
|
||||
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
|
||||
import { CirclePhysical } from './circle-physical';
|
||||
|
|
@ -256,9 +256,9 @@ export class CharacterPhysical
|
|||
|
||||
public getPropertyUpdates(): PropertyUpdatesForObject {
|
||||
return new PropertyUpdatesForObject(this.id, [
|
||||
new UpdateProperty('head', this.head, this.headVelocity),
|
||||
new UpdateProperty('leftFoot', this.leftFoot, this.leftFootVelocity),
|
||||
new UpdateProperty('rightFoot', this.rightFoot, this.rightFootVelocity),
|
||||
new UpdatePropertyCommand('head', this.head, this.headVelocity),
|
||||
new UpdatePropertyCommand('leftFoot', this.leftFoot, this.leftFootVelocity),
|
||||
new UpdatePropertyCommand('rightFoot', this.rightFoot, this.rightFootVelocity),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
PlanetBase,
|
||||
CharacterTeam,
|
||||
PropertyUpdatesForObject,
|
||||
UpdateProperty,
|
||||
UpdatePropertyCommand,
|
||||
} from 'shared';
|
||||
|
||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||
|
|
@ -110,7 +110,7 @@ export class PlanetPhysical
|
|||
|
||||
public getPropertyUpdates(): PropertyUpdatesForObject {
|
||||
return new PropertyUpdatesForObject(this.id, [
|
||||
new UpdateProperty('ownership', this.ownership, 0),
|
||||
new UpdatePropertyCommand('ownership', this.ownership, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
GameObject,
|
||||
CharacterTeam,
|
||||
PropertyUpdatesForObject,
|
||||
UpdateProperty,
|
||||
UpdatePropertyCommand,
|
||||
} from 'shared';
|
||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||
import { CirclePhysical } from './circle-physical';
|
||||
|
|
@ -102,7 +102,7 @@ export class ProjectilePhysical
|
|||
|
||||
public getPropertyUpdates(): PropertyUpdatesForObject {
|
||||
return new PropertyUpdatesForObject(this.id, [
|
||||
new UpdateProperty('center', this.center, this.velocity),
|
||||
new UpdatePropertyCommand('center', this.center, this.velocity),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
30679
frontend/package-lock.json
generated
30679
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,9 +6,6 @@ import {
|
|||
CharacterBase,
|
||||
ProjectileBase,
|
||||
} from 'shared';
|
||||
import { LampView } from './scripts/objects/lamp-view';
|
||||
import { ProjectileView } from './scripts/objects/projectile-view';
|
||||
import { PlanetView } from './scripts/objects/planet-view';
|
||||
import './main.scss';
|
||||
import '../static/og-image.png';
|
||||
import '../static/favicons/apple-touch-icon.png';
|
||||
|
|
@ -19,7 +16,6 @@ import { LandingPageBackground } from './scripts/landing-page-background';
|
|||
import { JoinFormHandler } from './scripts/join-form-handler';
|
||||
import { handleFullScreen } from './scripts/helper/handle-full-screen';
|
||||
import { Game } from './scripts/game';
|
||||
import { CharacterView } from './scripts/objects/character-view';
|
||||
import { handleInsights } from './scripts/handle-insights';
|
||||
import { getInsightsFromRenderer } from './scripts/get-insights-from-renderer';
|
||||
import { Renderer } from 'sdf-2d';
|
||||
|
|
@ -29,6 +25,10 @@ import { hide } from './scripts/helper/hide';
|
|||
import { show } from './scripts/helper/show';
|
||||
import { SoundHandler, Sounds } from './scripts/sound-handler';
|
||||
import { VibrationHandler } from './scripts/vibration-handler';
|
||||
import { CharacterView } from './scripts/objects/types/character-view';
|
||||
import { LampView } from './scripts/objects/types/lamp-view';
|
||||
import { PlanetView } from './scripts/objects/types/planet-view';
|
||||
import { ProjectileView } from './scripts/objects/types/projectile-view';
|
||||
|
||||
glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
|
|
|
|||
7
frontend/src/scripts/commands/types/before-destroy.ts
Normal file
7
frontend/src/scripts/commands/types/before-destroy.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Command } from 'shared';
|
||||
|
||||
export class BeforeDestroyCommand extends Command {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
13
frontend/src/scripts/commands/types/render.ts
Normal file
13
frontend/src/scripts/commands/types/render.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Renderer } from 'sdf-2d';
|
||||
import { Command } from 'shared';
|
||||
|
||||
export class RenderCommand extends Command {
|
||||
public constructor(
|
||||
public readonly renderer: Renderer,
|
||||
public readonly overlay: HTMLElement,
|
||||
public readonly shouldChangeLayout: boolean,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
7
frontend/src/scripts/commands/types/step.ts
Normal file
7
frontend/src/scripts/commands/types/step.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Command } from 'shared';
|
||||
|
||||
export class StepCommand extends Command {
|
||||
public constructor(public readonly deltaTimeInSeconds: number) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,8 @@ import { GameObjectContainer } from './objects/game-object-container';
|
|||
import parser from 'socket.io-msgpack-parser';
|
||||
import { BlobShape } from './shapes/blob-shape';
|
||||
import { PlanetShape } from './shapes/planet-shape';
|
||||
import { RenderCommand } from './commands/types/render';
|
||||
import { StepCommand } from './commands/types/step';
|
||||
|
||||
export class Game extends CommandReceiver {
|
||||
public gameObjects = new GameObjectContainer(this);
|
||||
|
|
@ -107,7 +109,7 @@ export class Game extends CommandReceiver {
|
|||
|
||||
this.socket.on(TransportEvents.ServerToPlayer, (serializedCommands: string) => {
|
||||
const commands: Array<Command> = deserialize(serializedCommands);
|
||||
commands.forEach((c) => this.sendCommand(c));
|
||||
commands.forEach((c) => this.handleCommand(c));
|
||||
});
|
||||
|
||||
this.socketReceiver = new CommandSocket(this.socket);
|
||||
|
|
@ -124,7 +126,7 @@ export class Game extends CommandReceiver {
|
|||
}
|
||||
|
||||
protected defaultCommandExecutor(c: Command) {
|
||||
this.gameObjects.sendCommand(c);
|
||||
this.gameObjects.handleCommand(c);
|
||||
}
|
||||
|
||||
private lastGameState?: UpdateGameState;
|
||||
|
|
@ -248,7 +250,7 @@ export class Game extends CommandReceiver {
|
|||
}
|
||||
|
||||
public aspectRatioChanged(aspectRatio: number) {
|
||||
this.socketReceiver.sendCommand(new SetAspectRatioActionCommand(aspectRatio));
|
||||
this.socketReceiver.handleCommand(new SetAspectRatioActionCommand(aspectRatio));
|
||||
}
|
||||
|
||||
private isActive = true;
|
||||
|
|
@ -284,9 +286,12 @@ export class Game extends CommandReceiver {
|
|||
|
||||
this.renderer = renderer;
|
||||
|
||||
this.gameObjects.handleCommand(new StepCommand(deltaTime));
|
||||
this.gameObjects.handleCommand(
|
||||
new RenderCommand(this.renderer, this.overlay, shouldChangeLayout),
|
||||
);
|
||||
|
||||
this.socketReceiver.sendQueuedCommands();
|
||||
this.gameObjects.stepObjects(deltaTime);
|
||||
this.gameObjects.drawObjects(this.renderer, this.overlay, shouldChangeLayout);
|
||||
|
||||
return this.isActive;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
import { Renderer } from 'sdf-2d';
|
||||
import {
|
||||
Command,
|
||||
CommandExecutors,
|
||||
CommandReceiver,
|
||||
CreateObjectsCommand,
|
||||
CreatePlayerCommand,
|
||||
DeleteObjectsCommand,
|
||||
GameObject,
|
||||
Id,
|
||||
PropertyUpdatesForObjects,
|
||||
RemoteCallsForObjects,
|
||||
} from 'shared';
|
||||
import { BeforeDestroyCommand } from '../commands/types/before-destroy';
|
||||
import { StepCommand } from '../commands/types/step';
|
||||
import { Game } from '../game';
|
||||
import { Camera } from './camera';
|
||||
import { CharacterView } from './character-view';
|
||||
import { ViewObject } from './view-object';
|
||||
import { Camera } from './types/camera';
|
||||
import { CharacterView } from './types/character-view';
|
||||
|
||||
export class GameObjectContainer extends CommandReceiver {
|
||||
protected objects: Map<Id, ViewObject> = new Map();
|
||||
protected objects: Map<Id, GameObject> = new Map();
|
||||
public player!: CharacterView;
|
||||
public camera!: Camera;
|
||||
|
||||
|
|
@ -35,7 +38,15 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
},
|
||||
|
||||
[CreateObjectsCommand.type]: (c: CreateObjectsCommand) =>
|
||||
c.objects.forEach((o) => this.addObject(o as ViewObject)),
|
||||
c.objects.forEach((o) => this.addObject(o as GameObject)),
|
||||
|
||||
[StepCommand.type]: (c: StepCommand) => {
|
||||
this.objects.forEach((o) => o.handleCommand(c));
|
||||
|
||||
if (this.player) {
|
||||
this.camera.center = this.player.position;
|
||||
}
|
||||
},
|
||||
|
||||
[RemoteCallsForObjects.type]: (c: RemoteCallsForObjects) =>
|
||||
c.callsForObjects.forEach((c) =>
|
||||
|
|
@ -43,7 +54,9 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
),
|
||||
|
||||
[PropertyUpdatesForObjects.type]: (c: PropertyUpdatesForObjects) =>
|
||||
c.updates.forEach((c) => this.objects.get(c.id)?.updateProperties(c.updates)),
|
||||
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)),
|
||||
|
|
@ -53,29 +66,17 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
super();
|
||||
}
|
||||
|
||||
public stepObjects(deltaTimeInSeconds: number) {
|
||||
this.objects.forEach((o) => o.step(deltaTimeInSeconds));
|
||||
|
||||
if (this.player) {
|
||||
this.camera.center = this.player.position;
|
||||
}
|
||||
protected defaultCommandExecutor(c: Command) {
|
||||
this.objects.forEach((o) => o.handleCommand(c));
|
||||
}
|
||||
|
||||
public drawObjects(
|
||||
renderer: Renderer,
|
||||
overlay: HTMLElement,
|
||||
shouldChangeLayout: boolean,
|
||||
) {
|
||||
this.objects.forEach((o) => o.draw(renderer, overlay, shouldChangeLayout));
|
||||
}
|
||||
|
||||
private addObject(object: ViewObject) {
|
||||
private addObject(object: GameObject) {
|
||||
this.objects.set(object.id, object);
|
||||
}
|
||||
|
||||
private deleteObject(id: Id) {
|
||||
const object = this.objects.get(id);
|
||||
object?.beforeDestroy();
|
||||
object?.handleCommand(new BeforeDestroyCommand());
|
||||
this.objects.delete(id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { CircleLight, Renderer } from 'sdf-2d';
|
||||
import { CommandExecutors, Id, LampBase, UpdateProperty } from 'shared';
|
||||
import { ViewObject } from './view-object';
|
||||
|
||||
export class LampView extends LampBase implements ViewObject {
|
||||
private light: CircleLight;
|
||||
|
||||
constructor(id: Id, center: vec2, color: vec3, lightness: number) {
|
||||
super(id, center, color, lightness);
|
||||
this.light = new CircleLight(center, color, lightness);
|
||||
}
|
||||
|
||||
public updateProperties(update: UpdateProperty[]): void {}
|
||||
|
||||
public step(deltaTimeInSeconds: number): void {}
|
||||
|
||||
public beforeDestroy(): void {}
|
||||
|
||||
public draw(
|
||||
renderer: Renderer,
|
||||
overlay: HTMLElement,
|
||||
shouldChangeLayout: boolean,
|
||||
): void {
|
||||
renderer.addDrawable(this.light);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { CircleLight, Renderer } from 'sdf-2d';
|
||||
import { CharacterTeam, Id, ProjectileBase, settings, UpdateProperty } from 'shared';
|
||||
import { Vec2Extrapolator } from '../helper/vec2-extrapolator';
|
||||
import { ViewObject } from './view-object';
|
||||
|
||||
export class ProjectileView extends ProjectileBase implements ViewObject {
|
||||
private light: CircleLight;
|
||||
|
||||
private centerExtrapolator: Vec2Extrapolator;
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
center: vec2,
|
||||
radius: number,
|
||||
team: CharacterTeam,
|
||||
strength: number,
|
||||
) {
|
||||
super(id, center, radius, team, strength);
|
||||
this.light = new CircleLight(
|
||||
center,
|
||||
settings.paletteDim[settings.colorIndices[team]],
|
||||
0,
|
||||
);
|
||||
this.centerExtrapolator = new Vec2Extrapolator(center);
|
||||
}
|
||||
|
||||
public updateProperties(update: UpdateProperty[]): void {
|
||||
update.forEach((u) => {
|
||||
this.centerExtrapolator.addFrame(u.propertyValue, u.rateOfChange);
|
||||
});
|
||||
}
|
||||
|
||||
public step(deltaTimeInSeconds: number): void {
|
||||
super.step(deltaTimeInSeconds);
|
||||
|
||||
this.center = this.centerExtrapolator.getValue(deltaTimeInSeconds);
|
||||
this.light.center = this.center;
|
||||
this.light.intensity = (0.15 * this.strength) / settings.projectileMaxStrength;
|
||||
}
|
||||
|
||||
public beforeDestroy(): void {}
|
||||
|
||||
public draw(
|
||||
renderer: Renderer,
|
||||
overlay: HTMLElement,
|
||||
shouldChangeLayout: boolean,
|
||||
): void {
|
||||
renderer.addDrawable(this.light);
|
||||
}
|
||||
}
|
||||
42
frontend/src/scripts/objects/types/camera.ts
Normal file
42
frontend/src/scripts/objects/types/camera.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import {
|
||||
calculateViewArea,
|
||||
CommandExecutors,
|
||||
GameObject,
|
||||
mixRgb,
|
||||
settings,
|
||||
} from 'shared';
|
||||
import { RenderCommand } from '../../commands/types/render';
|
||||
import { Game } from '../../game';
|
||||
|
||||
export class Camera extends GameObject {
|
||||
public center: vec2 = vec2.create();
|
||||
private aspectRatio?: number;
|
||||
|
||||
constructor(private game: Game) {
|
||||
super(null);
|
||||
}
|
||||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[RenderCommand.type]: this.draw.bind(this),
|
||||
};
|
||||
|
||||
private draw({ renderer }: RenderCommand) {
|
||||
const canvasAspectRatio = renderer.canvasSize.x / renderer.canvasSize.y;
|
||||
if (canvasAspectRatio !== this.aspectRatio) {
|
||||
this.aspectRatio = canvasAspectRatio;
|
||||
this.game.aspectRatioChanged(canvasAspectRatio);
|
||||
}
|
||||
|
||||
const viewArea = calculateViewArea(this.center, canvasAspectRatio);
|
||||
renderer.setViewArea(viewArea.topLeft, viewArea.size);
|
||||
|
||||
renderer.setRuntimeSettings({
|
||||
ambientLight: mixRgb(
|
||||
settings.backgroundGradient[0],
|
||||
settings.backgroundGradient[1],
|
||||
vec2.length(this.center) / settings.worldRadius,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
154
frontend/src/scripts/objects/types/character-view.ts
Normal file
154
frontend/src/scripts/objects/types/character-view.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Renderer } from 'sdf-2d';
|
||||
import {
|
||||
Circle,
|
||||
Id,
|
||||
CharacterBase,
|
||||
CharacterTeam,
|
||||
settings,
|
||||
CommandExecutors,
|
||||
UpdatePropertyCommand,
|
||||
} from 'shared';
|
||||
import { BeforeDestroyCommand } from '../../commands/types/before-destroy';
|
||||
import { RenderCommand } from '../../commands/types/render';
|
||||
import { StepCommand } from '../../commands/types/step';
|
||||
import { CircleExtrapolator } from '../../helper/circle-extrapolator';
|
||||
import { BlobShape } from '../../shapes/blob-shape';
|
||||
import { SoundHandler, Sounds } from '../../sound-handler';
|
||||
import { VibrationHandler } from '../../vibration-handler';
|
||||
|
||||
export class CharacterView extends CharacterBase {
|
||||
private shape: BlobShape;
|
||||
private nameElement: HTMLElement = document.createElement('div');
|
||||
private statsElement: HTMLElement = document.createElement('div');
|
||||
private healthElement: HTMLElement = document.createElement('div');
|
||||
|
||||
public isMainCharacter = false;
|
||||
|
||||
private leftFootExtrapolator: CircleExtrapolator;
|
||||
private rightFootExtrapolator: CircleExtrapolator;
|
||||
private headExtrapolator: CircleExtrapolator;
|
||||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[RenderCommand.type]: this.draw.bind(this),
|
||||
[StepCommand.type]: this.step.bind(this),
|
||||
[BeforeDestroyCommand.type]: this.beforeDestroy.bind(this),
|
||||
[UpdatePropertyCommand.type]: this.updateProperty.bind(this),
|
||||
};
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
name: string,
|
||||
killCount: number,
|
||||
deathCount: number,
|
||||
team: CharacterTeam,
|
||||
health: number,
|
||||
head?: Circle,
|
||||
leftFoot?: Circle,
|
||||
rightFoot?: Circle,
|
||||
) {
|
||||
super(id, name, killCount, deathCount, team, health, head, leftFoot, rightFoot);
|
||||
this.shape = new BlobShape(settings.colorIndices[team]);
|
||||
|
||||
this.leftFootExtrapolator = new CircleExtrapolator(this.leftFoot!);
|
||||
this.rightFootExtrapolator = new CircleExtrapolator(this.rightFoot!);
|
||||
this.headExtrapolator = new CircleExtrapolator(this.head!);
|
||||
|
||||
this.nameElement.className = 'player-tag ' + this.team;
|
||||
this.nameElement.innerText = this.name;
|
||||
this.nameElement.appendChild(this.healthElement);
|
||||
this.nameElement.appendChild(this.statsElement);
|
||||
}
|
||||
|
||||
public get position(): vec2 {
|
||||
return this.head!.center;
|
||||
}
|
||||
|
||||
private updateProperty({
|
||||
propertyKey,
|
||||
propertyValue,
|
||||
rateOfChange,
|
||||
}: UpdatePropertyCommand) {
|
||||
if (propertyKey === 'head') {
|
||||
this.headExtrapolator.addFrame(propertyValue, rateOfChange);
|
||||
}
|
||||
if (propertyKey === 'leftFoot') {
|
||||
this.leftFootExtrapolator.addFrame(propertyValue, rateOfChange);
|
||||
}
|
||||
if (propertyKey === 'rightFoot') {
|
||||
this.rightFootExtrapolator.addFrame(propertyValue, rateOfChange);
|
||||
}
|
||||
}
|
||||
|
||||
public setHealth(health: number) {
|
||||
const previousHealth = this.health;
|
||||
super.setHealth(health);
|
||||
SoundHandler.play(
|
||||
Sounds.hit,
|
||||
(0.4 * 2 * (previousHealth - health)) / settings.playerMaxStrength,
|
||||
);
|
||||
|
||||
if (this.isMainCharacter) {
|
||||
VibrationHandler.vibrate(Math.min(200, (previousHealth - this.health) * 4));
|
||||
}
|
||||
}
|
||||
|
||||
public onDie() {
|
||||
if (this.isMainCharacter) {
|
||||
VibrationHandler.vibrate(150);
|
||||
}
|
||||
}
|
||||
|
||||
private step({ deltaTimeInSeconds }: StepCommand): void {
|
||||
this.head! = this.headExtrapolator.getValue(deltaTimeInSeconds);
|
||||
this.leftFoot! = this.leftFootExtrapolator.getValue(deltaTimeInSeconds);
|
||||
this.rightFoot! = this.rightFootExtrapolator.getValue(deltaTimeInSeconds);
|
||||
}
|
||||
|
||||
public onShoot(strength: number) {
|
||||
SoundHandler.play(Sounds.shoot, (0.6 * strength) / settings.playerMaxStrength);
|
||||
}
|
||||
|
||||
private beforeDestroy(): void {
|
||||
this.nameElement.parentElement?.removeChild(this.nameElement);
|
||||
}
|
||||
|
||||
private draw({ renderer, overlay, shouldChangeLayout }: RenderCommand): void {
|
||||
if (shouldChangeLayout) {
|
||||
if (!this.nameElement.parentElement) {
|
||||
overlay.appendChild(this.nameElement);
|
||||
}
|
||||
|
||||
const screenPosition = renderer.worldToDisplayCoordinates(
|
||||
this.calculateTextPosition(),
|
||||
);
|
||||
|
||||
this.nameElement.style.transform = `translateX(${screenPosition.x}px) translateY(${screenPosition.y}px) translateX(-50%) translateY(-50%) rotate(-15deg)`;
|
||||
|
||||
this.healthElement.style.width =
|
||||
(50 * this.health) / settings.playerMaxHealth + 'px';
|
||||
this.statsElement.innerText = this.getStatsText();
|
||||
}
|
||||
|
||||
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
|
||||
renderer.addDrawable(this.shape);
|
||||
}
|
||||
|
||||
private getStatsText(): string {
|
||||
return `${this.killCount}⚔/${this.deathCount}☠`;
|
||||
}
|
||||
|
||||
private calculateTextPosition(): vec2 {
|
||||
const footAverage = vec2.add(
|
||||
vec2.create(),
|
||||
this.leftFoot!.center,
|
||||
this.rightFoot!.center,
|
||||
);
|
||||
vec2.scale(footAverage, footAverage, 0.5);
|
||||
|
||||
const headFeetDelta = vec2.subtract(footAverage, this.head!.center, footAverage);
|
||||
vec2.normalize(headFeetDelta, headFeetDelta);
|
||||
const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 80);
|
||||
return vec2.add(textOffset, this.head!.center, textOffset);
|
||||
}
|
||||
}
|
||||
21
frontend/src/scripts/objects/types/lamp-view.ts
Normal file
21
frontend/src/scripts/objects/types/lamp-view.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { CircleLight, Renderer } from 'sdf-2d';
|
||||
import { CommandExecutors, Id, LampBase, UpdatePropertyCommand } from 'shared';
|
||||
import { RenderCommand } from '../../commands/types/render';
|
||||
|
||||
export class LampView extends LampBase {
|
||||
private light: CircleLight;
|
||||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[RenderCommand.type]: this.draw.bind(this),
|
||||
};
|
||||
|
||||
constructor(id: Id, center: vec2, color: vec3, lightness: number) {
|
||||
super(id, center, color, lightness);
|
||||
this.light = new CircleLight(center, color, lightness);
|
||||
}
|
||||
|
||||
private draw({ renderer }: RenderCommand): void {
|
||||
renderer.addDrawable(this.light);
|
||||
}
|
||||
}
|
||||
142
frontend/src/scripts/objects/types/planet-view.ts
Normal file
142
frontend/src/scripts/objects/types/planet-view.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Id, Random, PlanetBase, UpdatePropertyCommand, CommandExecutors } from 'shared';
|
||||
import { BeforeDestroyCommand } from '../../commands/types/before-destroy';
|
||||
import { RenderCommand } from '../../commands/types/render';
|
||||
import { StepCommand } from '../../commands/types/step';
|
||||
import { PlanetShape } from '../../shapes/planet-shape';
|
||||
|
||||
type FallingPoint = {
|
||||
velocity: vec2;
|
||||
position: vec2;
|
||||
element: HTMLElement;
|
||||
addedToOverlay: boolean;
|
||||
timeToLive: number;
|
||||
};
|
||||
|
||||
export class PlanetView extends PlanetBase {
|
||||
private shape: PlanetShape;
|
||||
private ownershipProgress: HTMLElement;
|
||||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[RenderCommand.type]: this.draw.bind(this),
|
||||
[StepCommand.type]: this.step.bind(this),
|
||||
[BeforeDestroyCommand.type]: this.beforeDestroy.bind(this),
|
||||
[UpdatePropertyCommand.type]: this.updateProperty.bind(this),
|
||||
};
|
||||
|
||||
constructor(id: Id, vertices: Array<vec2>, ownership: number) {
|
||||
super(id, vertices);
|
||||
this.shape = new PlanetShape(vertices, ownership);
|
||||
(this.shape as any).randomOffset = Random.getRandom();
|
||||
|
||||
this.ownershipProgress = document.createElement('div');
|
||||
this.ownershipProgress.className = 'ownership';
|
||||
}
|
||||
|
||||
private step(deltaTimeInSeconds: number): void {
|
||||
this.shape.randomOffset += deltaTimeInSeconds / 4;
|
||||
this.shape.colorMixQ = this.ownership;
|
||||
|
||||
this.generatedPointElements.forEach((p) => {
|
||||
vec2.add(
|
||||
p.velocity,
|
||||
p.velocity,
|
||||
vec2.scale(vec2.create(), vec2.fromValues(0, 50), deltaTimeInSeconds),
|
||||
);
|
||||
|
||||
vec2.add(
|
||||
p.position,
|
||||
p.position,
|
||||
vec2.scale(vec2.create(), p.velocity, deltaTimeInSeconds),
|
||||
);
|
||||
|
||||
p.timeToLive -= deltaTimeInSeconds;
|
||||
});
|
||||
}
|
||||
|
||||
private generatedPointElements: Array<FallingPoint> = [];
|
||||
|
||||
private lastGeneratedPoint?: number;
|
||||
public generatedPoints(value: number) {
|
||||
this.lastGeneratedPoint = value;
|
||||
}
|
||||
|
||||
private beforeDestroy(): void {
|
||||
this.ownershipProgress.parentElement?.removeChild(this.ownershipProgress);
|
||||
this.generatedPointElements.forEach((p) =>
|
||||
p.element.parentElement?.removeChild(p.element),
|
||||
);
|
||||
}
|
||||
|
||||
private updateProperty({ propertyValue }: UpdatePropertyCommand): void {
|
||||
this.ownership = propertyValue;
|
||||
}
|
||||
|
||||
private draw({ renderer, overlay, shouldChangeLayout }: RenderCommand): void {
|
||||
if (shouldChangeLayout) {
|
||||
if (!this.ownershipProgress.parentElement) {
|
||||
overlay.appendChild(this.ownershipProgress);
|
||||
}
|
||||
|
||||
const screenPosition = renderer.worldToDisplayCoordinates(this.center);
|
||||
|
||||
this.generatedPointElements.forEach((p) => {
|
||||
if (!p.addedToOverlay) {
|
||||
overlay.appendChild(p.element);
|
||||
}
|
||||
|
||||
p.element.style.transform = `translateX(${
|
||||
screenPosition.x + p.position.x
|
||||
}px) translateY(${screenPosition.y + p.position.y}px)`;
|
||||
|
||||
if (p.timeToLive <= 0) {
|
||||
p.element.parentElement?.removeChild(p.element);
|
||||
} else {
|
||||
p.element.style.opacity = Math.min(1, p.timeToLive).toString();
|
||||
}
|
||||
});
|
||||
|
||||
this.generatedPointElements = this.generatedPointElements.filter(
|
||||
(p) => p.timeToLive > 0,
|
||||
);
|
||||
|
||||
this.ownershipProgress.style.transform = `translateX(${screenPosition.x}px) translateY(${screenPosition.y}px) translateX(-50%) translateY(-50%)`;
|
||||
this.ownershipProgress.style.background = this.getGradient();
|
||||
|
||||
if (this.lastGeneratedPoint !== undefined) {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'falling-point ' + (this.ownership < 0.5 ? 'decla' : 'red');
|
||||
element.innerText = '+' + this.lastGeneratedPoint;
|
||||
this.generatedPointElements.push({
|
||||
element,
|
||||
addedToOverlay: false,
|
||||
timeToLive: Random.getRandomInRange(2, 3),
|
||||
position: vec2.create(),
|
||||
velocity: vec2.fromValues(Random.getRandomInRange(-30, 30), 0),
|
||||
});
|
||||
|
||||
this.lastGeneratedPoint = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
renderer.addDrawable(this.shape);
|
||||
}
|
||||
|
||||
private getGradient(): string {
|
||||
const sideDecla = this.ownership < 0.5;
|
||||
const sidePercent = (Math.abs(this.ownership - 0.5) / 0.5) * 100;
|
||||
return sideDecla
|
||||
? `conic-gradient(
|
||||
var(--bright-decla) ${sidePercent}%,
|
||||
var(--bright-decla) ${sidePercent}%,
|
||||
rgba(0, 0, 0, 0) ${sidePercent}%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
)`
|
||||
: `conic-gradient(
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0) ${100 - sidePercent}%,
|
||||
var(--bright-red) ${100 - sidePercent}%,
|
||||
var(--bright-red) 100%
|
||||
)`;
|
||||
}
|
||||
}
|
||||
57
frontend/src/scripts/objects/types/projectile-view.ts
Normal file
57
frontend/src/scripts/objects/types/projectile-view.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { CircleLight, Renderer } from 'sdf-2d';
|
||||
import {
|
||||
CharacterTeam,
|
||||
CommandExecutors,
|
||||
Id,
|
||||
ProjectileBase,
|
||||
settings,
|
||||
UpdatePropertyCommand,
|
||||
} from 'shared';
|
||||
import { RenderCommand } from '../../commands/types/render';
|
||||
import { StepCommand } from '../../commands/types/step';
|
||||
import { Vec2Extrapolator } from '../../helper/vec2-extrapolator';
|
||||
|
||||
export class ProjectileView extends ProjectileBase {
|
||||
private light: CircleLight;
|
||||
|
||||
private centerExtrapolator: Vec2Extrapolator;
|
||||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[RenderCommand.type]: this.draw.bind(this),
|
||||
[StepCommand.type]: this.handleStep.bind(this),
|
||||
[UpdatePropertyCommand.type]: this.updateProperty.bind(this),
|
||||
};
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
center: vec2,
|
||||
radius: number,
|
||||
team: CharacterTeam,
|
||||
strength: number,
|
||||
) {
|
||||
super(id, center, radius, team, strength);
|
||||
this.light = new CircleLight(
|
||||
center,
|
||||
settings.paletteDim[settings.colorIndices[team]],
|
||||
0,
|
||||
);
|
||||
this.centerExtrapolator = new Vec2Extrapolator(center);
|
||||
}
|
||||
|
||||
private updateProperty({ propertyValue, rateOfChange }: UpdatePropertyCommand): void {
|
||||
this.centerExtrapolator.addFrame(propertyValue, rateOfChange);
|
||||
}
|
||||
|
||||
private handleStep({ deltaTimeInSeconds }: StepCommand): void {
|
||||
this.step(deltaTimeInSeconds);
|
||||
|
||||
this.center = this.centerExtrapolator.getValue(deltaTimeInSeconds);
|
||||
this.light.center = this.center;
|
||||
this.light.intensity = (0.15 * this.strength) / settings.projectileMaxStrength;
|
||||
}
|
||||
|
||||
private draw({ renderer }: RenderCommand): void {
|
||||
renderer.addDrawable(this.light);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue