prettier formatting

This commit is contained in:
Elizabeth Hunt 2023-08-25 16:48:17 -06:00
parent dec7b614d8
commit 773ce84f4b
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
65 changed files with 429 additions and 391 deletions

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "none"
}

View File

@ -1,11 +1,11 @@
module.exports = { module.exports = {
extends: [ extends: [
// add more generic rule sets here, such as: // add more generic rule sets here, such as:
"eslint:recommended", 'eslint:recommended',
"plugin:svelte/recommended", 'plugin:svelte/recommended'
], ],
rules: { rules: {
// override/add rules settings here, such as: // override/add rules settings here, such as:
// 'svelte/rule-name': 'error' // 'svelte/rule-name': 'error'
}, }
}; };

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

View File

@ -10,7 +10,7 @@
--orange: #af3a03; --orange: #af3a03;
} }
[data-theme="dark"] { [data-theme='dark'] {
--bg: #282828; --bg: #282828;
--text: #f9f5d7; --text: #f9f5d7;
--red: #fb4934; --red: #fb4934;

View File

@ -1,15 +1,15 @@
@import url("./theme.css"); @import url('./theme.css');
@import url("./tf.css"); @import url('./tf.css');
@font-face { @font-face {
font-family: "scientifica"; font-family: 'scientifica';
src: url("/fonts/scientifica.ttf"); src: url('/fonts/scientifica.ttf');
} }
* { * {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-family: "scientifica", monospace; font-family: 'scientifica', monospace;
transition: background 0.2s ease-in-out; transition: background 0.2s ease-in-out;
font-smooth: never; font-smooth: never;
} }

View File

@ -17,7 +17,7 @@
rgba(162, 254, 254, 1) 100% rgba(162, 254, 254, 1) 100%
); );
content: ""; content: '';
width: 100%; width: 100%;
height: 100%; height: 100%;
top: 0; top: 0;

View File

@ -1,4 +1,4 @@
@import url("./colors.css"); @import url('./colors.css');
.primary { .primary {
color: var(--aqua); color: var(--aqua);

View File

@ -1,6 +1,6 @@
import { Game } from "@engine/Game"; import { Game } from '@engine/Game';
import { Entity } from "@engine/entities"; import { Entity, Floor } from '@engine/entities';
import { Grid } from "@engine/structures"; import { Grid } from '@engine/structures';
import { import {
WallBounds, WallBounds,
FacingDirection, FacingDirection,
@ -8,17 +8,19 @@ import {
Physics, Physics,
Input, Input,
Collision, Collision,
NetworkUpdate, NetworkUpdate
} from "@engine/systems"; } from '@engine/systems';
import { import {
type MessageQueueProvider, type MessageQueueProvider,
type MessagePublisher, type MessagePublisher,
type MessageProcessor, type MessageProcessor,
type Message, type Message,
type EntityAddBody, type EntityAddBody,
MessageType, MessageType
} from "@engine/network"; } from '@engine/network';
import { stringify, parse } from "@engine/utils"; import { stringify, parse } from '@engine/utils';
import { BoundingBox, Sprite } from '@engine/components';
import { Miscellaneous } from '@engine/config';
class ClientMessageProcessor implements MessageProcessor { class ClientMessageProcessor implements MessageProcessor {
private game: Game; private game: Game;
@ -29,14 +31,19 @@ class ClientMessageProcessor implements MessageProcessor {
public process(message: Message) { public process(message: Message) {
switch (message.type) { switch (message.type) {
case MessageType.NEW_ENTITY: case MessageType.NEW_ENTITIES:
const entityAddBody = message.body as unknown as EntityAddBody; const entityAdditions = message.body as unknown as EntityAddBody[];
this.game.addEntity( entityAdditions.forEach((addBody) =>
Entity.from(entityAddBody.entityName, entityAddBody.args), this.game.addEntity(Entity.from(addBody.entityName, addBody.args))
); );
break; break;
case MessageType.REMOVE_ENTITIES:
const ids = message.body as unknown as string[];
ids.forEach((id) => this.game.removeEntity(id));
break;
default:
break;
} }
console.log(message); console.log(message);
} }
} }
@ -49,9 +56,9 @@ class ClientSocketMessageQueueProvider implements MessageQueueProvider {
this.socket = socket; this.socket = socket;
this.messages = []; this.messages = [];
this.socket.addEventListener("message", (e) => { this.socket.addEventListener('message', (e) => {
const message = parse<Message>(e.data); const messages = parse<Message[]>(e.data);
this.messages.push(message); this.messages = this.messages.concat(messages);
}); });
} }
@ -79,7 +86,7 @@ class ClientSocketMessagePublisher implements MessagePublisher {
public publish() { public publish() {
this.messages.forEach((message: Message) => this.messages.forEach((message: Message) =>
this.socket.send(stringify(message)), this.socket.send(stringify(message))
); );
} }
} }
@ -96,7 +103,7 @@ export class JumpStorm {
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
httpMethod: string, httpMethod: string,
wsMethod: string, wsMethod: string,
host: string, host: string
) { ) {
await fetch(`${httpMethod}://${host}/assign`) await fetch(`${httpMethod}://${host}/assign`)
.then((resp) => { .then((resp) => {
@ -115,7 +122,7 @@ export class JumpStorm {
const clientSocketMessageQueueProvider = const clientSocketMessageQueueProvider =
new ClientSocketMessageQueueProvider(socket); new ClientSocketMessageQueueProvider(socket);
const clientSocketMessagePublisher = new ClientSocketMessagePublisher( const clientSocketMessagePublisher = new ClientSocketMessagePublisher(
socket, socket
); );
const clientMessageProcessor = new ClientMessageProcessor(this.game); const clientMessageProcessor = new ClientMessageProcessor(this.game);
[ [
@ -123,14 +130,28 @@ export class JumpStorm {
new FacingDirection(), new FacingDirection(),
new Physics(), new Physics(),
new Collision(grid), new Collision(grid),
new WallBounds(ctx.canvas.width), new WallBounds(),
new NetworkUpdate( new NetworkUpdate(
clientSocketMessageQueueProvider, clientSocketMessageQueueProvider,
clientSocketMessagePublisher, clientSocketMessagePublisher,
clientMessageProcessor, clientMessageProcessor
), ),
new Render(ctx), new Render(ctx)
].forEach((system) => this.game.addSystem(system)); ].forEach((system) => this.game.addSystem(system));
const floor = new Floor(160);
const floorHeight = 40;
floor.addComponent(
new BoundingBox(
{
x: Miscellaneous.WIDTH / 2,
y: Miscellaneous.HEIGHT - floorHeight / 2
},
{ width: Miscellaneous.WIDTH, height: floorHeight }
)
);
this.game.addEntity(floor);
} }
public play() { public play() {
@ -146,13 +167,13 @@ export class JumpStorm {
private createInputSystem(): Input { private createInputSystem(): Input {
const inputSystem = new Input(this.clientId); const inputSystem = new Input(this.clientId);
window.addEventListener("keydown", (e) => { window.addEventListener('keydown', (e) => {
if (!e.repeat) { if (!e.repeat) {
inputSystem.keyPressed(e.key); inputSystem.keyPressed(e.key);
} }
}); });
window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key)); window.addEventListener('keyup', (e) => inputSystem.keyReleased(e.key));
return inputSystem; return inputSystem;
} }

View File

@ -19,8 +19,7 @@
const game = new Game(); const game = new Game();
const jumpStorm = new JumpStorm(game); const jumpStorm = new JumpStorm(game);
const url = new URL(document.location); await jumpStorm.init(ctx, "http", "ws", document.location.host + "/api");
await jumpStorm.init(ctx, "http", "ws", url.host + "/api");
jumpStorm.play(); jumpStorm.play();
}); });
</script> </script>

View File

@ -3,7 +3,7 @@
const MAX_ENTRIES = 8; const MAX_ENTRIES = 8;
export let entries: { name: string, score: number }[] = []; export let entries: { name: string; score: number }[] = [];
</script> </script>
<div class="leaderboard"> <div class="leaderboard">

View File

@ -1,7 +1,7 @@
import App from "./App.svelte"; import App from './App.svelte';
const app = new App({ const app = new App({
target: document.getElementById("app"), target: document.getElementById('app')
}); });
export default app; export default app;

View File

@ -6,7 +6,6 @@
let width: number = Miscellaneous.WIDTH; let width: number = Miscellaneous.WIDTH;
let height: number = Miscellaneous.HEIGHT; let height: number = Miscellaneous.HEIGHT;
</script> </script>
<div class="centered-game"> <div class="centered-game">

View File

@ -1,7 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default { export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess()
} };

View File

@ -1,23 +1,23 @@
import { defineConfig } from "vite"; import { defineConfig } from 'vite';
import { svelte } from "@sveltejs/vite-plugin-svelte"; import { svelte } from '@sveltejs/vite-plugin-svelte';
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from 'node:url';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
server: { server: {
proxy: { proxy: {
"/api": { '/api': {
target: "http://localhost:8080", target: 'http://localhost:8080',
ws: true, ws: true,
rewrite: (path) => path.replace(/^\/api/, ""), rewrite: (path) => path.replace(/^\/api/, '')
}, }
}, }
}, },
cors: true, cors: true,
plugins: [svelte()], plugins: [svelte()],
resolve: { resolve: {
alias: { alias: {
"@engine": fileURLToPath(new URL("../engine", import.meta.url)), '@engine': fileURLToPath(new URL('../engine', import.meta.url))
}, }
}, }
}); });

View File

@ -1,5 +1,5 @@
import { Entity } from "./entities"; import { Entity } from './entities';
import { System } from "./systems"; import { System } from './systems';
export class Game { export class Game {
private systemOrder: string[]; private systemOrder: string[];
@ -39,7 +39,7 @@ export class Game {
public forEachEntityWithComponent( public forEachEntityWithComponent(
componentName: string, componentName: string,
callback: (entity: Entity) => void, callback: (entity: Entity) => void
) { ) {
this.componentEntities.get(componentName)?.forEach((entityId) => { this.componentEntities.get(componentName)?.forEach((entityId) => {
const entity = this.getEntity(entityId); const entity = this.getEntity(entityId);
@ -75,12 +75,12 @@ export class Game {
if (!this.componentEntities.has(component.name)) { if (!this.componentEntities.has(component.name)) {
this.componentEntities.set( this.componentEntities.set(
component.name, component.name,
new Set<string>([entity.id]), new Set<string>([entity.id])
); );
return; return;
} }
this.componentEntities.get(component.name)?.add(entity.id); this.componentEntities.get(component.name)?.add(entity.id);
}), })
); );
this.systemOrder.forEach((systemName) => { this.systemOrder.forEach((systemName) => {

View File

@ -1,6 +1,6 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
import type { Coord2D, Dimension2D } from "../interfaces"; import type { Coord2D, Dimension2D } from '../interfaces';
import { dotProduct, rotateVector } from "../utils"; import { dotProduct, rotateVector } from '../utils';
export class BoundingBox extends Component { export class BoundingBox extends Component {
public center: Coord2D; public center: Coord2D;
@ -48,8 +48,8 @@ export class BoundingBox extends Component {
const projection = dotProduct(normal, vertex); const projection = dotProduct(normal, vertex);
return [Math.min(min, projection), Math.max(max, projection)]; return [Math.min(min, projection), Math.max(max, projection)];
}, },
[Infinity, -Infinity], [Infinity, -Infinity]
), )
); );
if (maxThis < minBox || maxBox < minThis) return false; if (maxThis < minBox || maxBox < minThis) return false;
@ -64,14 +64,14 @@ export class BoundingBox extends Component {
{ x: -this.dimension.width / 2, y: -this.dimension.height / 2 }, { x: -this.dimension.width / 2, y: -this.dimension.height / 2 },
{ x: -this.dimension.width / 2, y: this.dimension.height / 2 }, { x: -this.dimension.width / 2, y: this.dimension.height / 2 },
{ x: this.dimension.width / 2, y: this.dimension.height / 2 }, { x: this.dimension.width / 2, y: this.dimension.height / 2 },
{ x: this.dimension.width / 2, y: -this.dimension.height / 2 }, { x: this.dimension.width / 2, y: -this.dimension.height / 2 }
] ]
.map((vertex) => rotateVector(vertex, this.rotation)) // rotate .map((vertex) => rotateVector(vertex, this.rotation)) // rotate
.map((vertex) => { .map((vertex) => {
// translate // translate
return { return {
x: vertex.x + this.center.x, x: vertex.x + this.center.x,
y: vertex.y + this.center.y, y: vertex.y + this.center.y
}; };
}); });
} }
@ -92,28 +92,28 @@ export class BoundingBox extends Component {
if (rads <= Math.PI / 2) { if (rads <= Math.PI / 2) {
return { return {
width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)), width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)),
height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads)), height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads))
}; };
} }
rads -= Math.PI / 2; rads -= Math.PI / 2;
return { return {
width: Math.abs(height * Math.cos(rads) + width * Math.sin(rads)), width: Math.abs(height * Math.cos(rads) + width * Math.sin(rads)),
height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads)), height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads))
}; };
} }
public getTopLeft(): Coord2D { public getTopLeft(): Coord2D {
return { return {
x: this.center.x - this.dimension.width / 2, x: this.center.x - this.dimension.width / 2,
y: this.center.y - this.dimension.height / 2, y: this.center.y - this.dimension.height / 2
}; };
} }
public getBottomRight(): Coord2D { public getBottomRight(): Coord2D {
return { return {
x: this.center.x + this.dimension.width / 2, x: this.center.x + this.dimension.width / 2,
y: this.center.y + this.dimension.height / 2, y: this.center.y + this.dimension.height / 2
}; };
} }
} }

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class Collide extends Component { export class Collide extends Component {
constructor() { constructor() {

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames, Velocity } from "."; import { Component, ComponentNames, Velocity } from '.';
export class Control extends Component { export class Control extends Component {
public controlVelocityComponent: Velocity; public controlVelocityComponent: Velocity;
@ -6,7 +6,7 @@ export class Control extends Component {
constructor( constructor(
controllableBy: string, controllableBy: string,
controlVelocityComponent: Velocity = new Velocity(), controlVelocityComponent: Velocity = new Velocity()
) { ) {
super(ComponentNames.Control); super(ComponentNames.Control);

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames, Sprite } from "."; import { Component, ComponentNames, Sprite } from '.';
export class FacingDirection extends Component { export class FacingDirection extends Component {
public readonly facingLeftSprite: Sprite; public readonly facingLeftSprite: Sprite;

View File

@ -1,6 +1,6 @@
import type { Force2D } from "../interfaces"; import type { Force2D } from '../interfaces';
import { Component } from "./Component"; import { Component } from './Component';
import { ComponentNames } from "."; import { ComponentNames } from '.';
/** /**
* A list of forces and torque, (in newtons, and newton-meters respectively) * A list of forces and torque, (in newtons, and newton-meters respectively)

View File

@ -1,7 +1,7 @@
import { ComponentNames, Component } from "."; import { ComponentNames, Component } from '.';
export class Gravity extends Component { export class Gravity extends Component {
private static DEFAULT_TERMINAL_VELOCITY = 5; private static DEFAULT_TERMINAL_VELOCITY = 4.5;
public terminalVelocity: number; public terminalVelocity: number;

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class Jump extends Component { export class Jump extends Component {
public canJump: boolean; public canJump: boolean;

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class Mass extends Component { export class Mass extends Component {
public mass: number; public mass: number;

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class Moment extends Component { export class Moment extends Component {
public inertia: number; public inertia: number;

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class NetworkUpdateable extends Component { export class NetworkUpdateable extends Component {
public isPublish: boolean; public isPublish: boolean;

View File

@ -1,5 +1,5 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
import type { Dimension2D, DrawArgs, Coord2D } from "../interfaces"; import type { Dimension2D, DrawArgs, Coord2D } from '../interfaces';
export class Sprite extends Component { export class Sprite extends Component {
private sheet: HTMLImageElement; private sheet: HTMLImageElement;
@ -17,7 +17,7 @@ export class Sprite extends Component {
spriteImgPos: Coord2D, spriteImgPos: Coord2D,
spriteImgDimensions: Dimension2D, spriteImgDimensions: Dimension2D,
msPerFrame: number, msPerFrame: number,
numFrames: number, numFrames: number
) { ) {
super(ComponentNames.Sprite); super(ComponentNames.Sprite);
@ -56,12 +56,12 @@ export class Sprite extends Component {
ctx.drawImage( ctx.drawImage(
this.sheet, this.sheet,
...this.getSpriteArgs(), ...this.getSpriteArgs(),
...this.getDrawArgs(drawArgs), ...this.getDrawArgs(drawArgs)
); );
if (tint) { if (tint) {
ctx.globalAlpha = 0.5; ctx.globalAlpha = 0.5;
ctx.globalCompositeOperation = "source-atop"; ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = tint; ctx.fillStyle = tint;
ctx.fillRect(...this.getDrawArgs(drawArgs)); ctx.fillRect(...this.getDrawArgs(drawArgs));
} }
@ -74,19 +74,23 @@ export class Sprite extends Component {
this.spriteImgPos.x + this.currentFrame * this.spriteImgDimensions.width, this.spriteImgPos.x + this.currentFrame * this.spriteImgDimensions.width,
this.spriteImgPos.y, this.spriteImgPos.y,
this.spriteImgDimensions.width, this.spriteImgDimensions.width,
this.spriteImgDimensions.height, this.spriteImgDimensions.height
]; ];
} }
private getDrawArgs({ private getDrawArgs({
center, center,
dimension, dimension
}: DrawArgs): [dx: number, dy: number, dw: number, dh: number] { }: DrawArgs): [dx: number, dy: number, dw: number, dh: number] {
return [ return [
center.x - dimension.width / 2, center.x - dimension.width / 2,
center.y - dimension.height / 2, center.y - dimension.height / 2,
dimension.width, dimension.width,
dimension.height, dimension.height
]; ];
} }
public getSpriteDimensions() {
return this.spriteImgDimensions;
}
} }

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class TopCollidable extends Component { export class TopCollidable extends Component {
constructor() { constructor() {

View File

@ -1,12 +1,12 @@
import type { Velocity2D } from "../interfaces"; import type { Velocity2D } from '../interfaces';
import { Component } from "./Component"; import { Component } from './Component';
import { ComponentNames } from "."; import { ComponentNames } from '.';
export class Velocity extends Component { export class Velocity extends Component {
public velocity: Velocity2D; public velocity: Velocity2D;
constructor( constructor(
velocity: Velocity2D = { dCartesian: { dx: 0, dy: 0 }, dTheta: 0 }, velocity: Velocity2D = { dCartesian: { dx: 0, dy: 0 }, dTheta: 0 }
) { ) {
super(ComponentNames.Velocity); super(ComponentNames.Velocity);

View File

@ -1,4 +1,4 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from '.';
export class WallBounded extends Component { export class WallBounded extends Component {
constructor() { constructor() {

View File

@ -1,16 +1,16 @@
export * from "./Component"; export * from './Component';
export * from "./BoundingBox"; export * from './BoundingBox';
export * from "./Velocity"; export * from './Velocity';
export * from "./Forces"; export * from './Forces';
export * from "./Sprite"; export * from './Sprite';
export * from "./FacingDirection"; export * from './FacingDirection';
export * from "./Jump"; export * from './Jump';
export * from "./TopCollidable"; export * from './TopCollidable';
export * from "./Collide"; export * from './Collide';
export * from "./Control"; export * from './Control';
export * from "./WallBounded"; export * from './WallBounded';
export * from "./Gravity"; export * from './Gravity';
export * from "./Mass"; export * from './Mass';
export * from "./Moment"; export * from './Moment';
export * from "./NetworkUpdateable"; export * from './NetworkUpdateable';
export * from "./names"; export * from './names';

View File

@ -1,16 +1,16 @@
export namespace ComponentNames { export namespace ComponentNames {
export const Sprite = "Sprite"; export const Sprite = 'Sprite';
export const BoundingBox = "BoundingBox"; export const BoundingBox = 'BoundingBox';
export const Velocity = "Velocity"; export const Velocity = 'Velocity';
export const FacingDirection = "FacingDirection"; export const FacingDirection = 'FacingDirection';
export const Control = "Control"; export const Control = 'Control';
export const Jump = "Jump"; export const Jump = 'Jump';
export const TopCollidable = "TopCollidable"; export const TopCollidable = 'TopCollidable';
export const Collide = "Collide"; export const Collide = 'Collide';
export const WallBounded = "WallBounded"; export const WallBounded = 'WallBounded';
export const Gravity = "Gravity"; export const Gravity = 'Gravity';
export const Forces = "Forces"; export const Forces = 'Forces';
export const Mass = "Mass"; export const Mass = 'Mass';
export const Moment = "Moment"; export const Moment = 'Moment';
export const NetworkUpdateable = "NetworkUpdateable"; export const NetworkUpdateable = 'NetworkUpdateable';
} }

View File

@ -1,10 +1,10 @@
import type { SpriteSpec } from "./sprites"; import type { SpriteSpec } from './sprites';
import { SPRITE_SPECS } from "./sprites"; import { SPRITE_SPECS } from './sprites';
export const IMAGES = new Map<string, HTMLImageElement>(); export const IMAGES = new Map<string, HTMLImageElement>();
export const loadSpritesIntoImageElements = ( export const loadSpritesIntoImageElements = (
spriteSpecs: Partial<SpriteSpec>[], spriteSpecs: Partial<SpriteSpec>[]
): Promise<void>[] => { ): Promise<void>[] => {
const spritePromises: Promise<void>[] = []; const spritePromises: Promise<void>[] = [];
@ -17,13 +17,13 @@ export const loadSpritesIntoImageElements = (
spritePromises.push( spritePromises.push(
new Promise((resolve) => { new Promise((resolve) => {
img.onload = () => resolve(); img.onload = () => resolve();
}), })
); );
} }
if (spriteSpec.states) { if (spriteSpec.states) {
spritePromises.push( spritePromises.push(
...loadSpritesIntoImageElements(Array.from(spriteSpec.states.values())), ...loadSpritesIntoImageElements(Array.from(spriteSpec.states.values()))
); );
} }
} }
@ -35,8 +35,8 @@ export const loadAssets = () =>
Promise.all([ Promise.all([
...loadSpritesIntoImageElements( ...loadSpritesIntoImageElements(
Array.from(SPRITE_SPECS.keys()).map( Array.from(SPRITE_SPECS.keys()).map(
(key) => SPRITE_SPECS.get(key) as SpriteSpec, (key) => SPRITE_SPECS.get(key) as SpriteSpec
), )
), )
// TODO: Sound // TODO: Sound
]); ]);

View File

@ -1,4 +1,4 @@
import { Action } from "../interfaces"; import { Action } from '../interfaces';
export namespace KeyConstants { export namespace KeyConstants {
export const KeyActions: Record<string, Action> = { export const KeyActions: Record<string, Action> = {
@ -10,11 +10,13 @@ export namespace KeyConstants {
w: Action.JUMP, w: Action.JUMP,
ArrowUp: Action.JUMP, ArrowUp: Action.JUMP,
' ': Action.JUMP
}; };
// value -> [key] from KeyActions // value -> [key] from KeyActions
export const ActionKeys: Map<Action, string[]> = Object.keys( export const ActionKeys: Map<Action, string[]> = Object.keys(
KeyActions, KeyActions
).reduce((acc: Map<Action, string[]>, key) => { ).reduce((acc: Map<Action, string[]>, key) => {
const action = KeyActions[key]; const action = KeyActions[key];

View File

@ -1,3 +1,3 @@
export * from "./constants"; export * from './constants';
export * from "./assets.ts"; export * from './assets.ts';
export * from "./sprites.ts"; export * from './sprites.ts';

View File

@ -1,7 +1,7 @@
export enum Sprites { export enum Sprites {
FLOOR, FLOOR,
TRAMPOLINE, TRAMPOLINE,
COFFEE, COFFEE
} }
export interface SpriteSpec { export interface SpriteSpec {
@ -22,12 +22,12 @@ const floorSpriteSpec = {
height: 40, height: 40,
frames: 3, frames: 3,
msPerFrame: 125, msPerFrame: 125,
states: new Map<number, Partial<SpriteSpec>>(), states: new Map<number, Partial<SpriteSpec>>()
}; };
[40, 80, 120, 160].forEach((width) => { [40, 80, 120, 160].forEach((width) => {
floorSpriteSpec.states.set(width, { floorSpriteSpec.states.set(width, {
width, width,
sheet: `/assets/floor_tile_${width}.png`, sheet: `/assets/floor_tile_${width}.png`
}); });
}); });
SPRITE_SPECS.set(Sprites.FLOOR, floorSpriteSpec); SPRITE_SPECS.set(Sprites.FLOOR, floorSpriteSpec);
@ -37,12 +37,12 @@ const coffeeSpriteSpec = {
width: 60, width: 60,
height: 45, height: 45,
frames: 3, frames: 3,
states: new Map<string, Partial<SpriteSpec>>(), states: new Map<string, Partial<SpriteSpec>>()
}; };
coffeeSpriteSpec.states.set("LEFT", { coffeeSpriteSpec.states.set('LEFT', {
sheet: "/assets/coffee_left.png", sheet: '/assets/coffee_left.png'
}); });
coffeeSpriteSpec.states.set("RIGHT", { coffeeSpriteSpec.states.set('RIGHT', {
sheet: "/assets/coffee_right.png", sheet: '/assets/coffee_right.png'
}); });
SPRITE_SPECS.set(Sprites.COFFEE, coffeeSpriteSpec); SPRITE_SPECS.set(Sprites.COFFEE, coffeeSpriteSpec);

View File

@ -1,5 +1,5 @@
import { EntityNames, Player } from "."; import { EntityNames, Player } from '.';
import type { Component } from "../components"; import type { Component } from '../components';
export abstract class Entity { export abstract class Entity {
public id: string; public id: string;
@ -18,7 +18,7 @@ export abstract class Entity {
public getComponent<T extends Component>(name: string): T { public getComponent<T extends Component>(name: string): T {
if (!this.hasComponent(name)) { if (!this.hasComponent(name)) {
throw new Error("Entity does not have component " + name); throw new Error('Entity does not have component ' + name);
} }
return this.components.get(name) as T; return this.components.get(name) as T;
} }
@ -34,9 +34,11 @@ export abstract class Entity {
static from(entityName: string, args: any): Entity { static from(entityName: string, args: any): Entity {
switch (entityName) { switch (entityName) {
case EntityNames.Player: case EntityNames.Player:
return new Player(args.playerId); const player = new Player(args.playerId);
player.id = args.id;
return player;
default: default:
throw new Error(".from() Entity type not implemented: " + entityName); throw new Error('.from() Entity type not implemented: ' + entityName);
} }
} }
} }

View File

@ -1,11 +1,11 @@
import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config"; import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from '../config';
import { BoundingBox, Sprite } from "../components"; import { BoundingBox, Sprite } from '../components';
import { TopCollidable } from "../components/TopCollidable"; import { TopCollidable } from '../components/TopCollidable';
import { Entity, EntityNames } from "../entities"; import { Entity, EntityNames } from '../entities';
export class Floor extends Entity { export class Floor extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.FLOOR, Sprites.FLOOR
) as SpriteSpec; ) as SpriteSpec;
constructor(width: number) { constructor(width: number) {
@ -17,18 +17,8 @@ export class Floor extends Entity {
{ x: 0, y: 0 }, { x: 0, y: 0 },
{ width, height: Floor.spriteSpec.height }, { width, height: Floor.spriteSpec.height },
Floor.spriteSpec.msPerFrame, Floor.spriteSpec.msPerFrame,
Floor.spriteSpec.frames, Floor.spriteSpec.frames
), )
);
this.addComponent(
new BoundingBox(
{
x: 300,
y: 300,
},
{ width, height: Floor.spriteSpec.height },
),
); );
this.addComponent(new TopCollidable()); this.addComponent(new TopCollidable());

View File

@ -1,5 +1,5 @@
import { Entity, EntityNames } from "."; import { Entity, EntityNames } from '.';
import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config"; import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from '../config';
import { import {
Jump, Jump,
FacingDirection, FacingDirection,
@ -12,16 +12,16 @@ import {
Collide, Collide,
Control, Control,
Mass, Mass,
Moment, Moment
} from "../components"; } from '../components';
import { Direction } from "../interfaces"; import { Direction } from '../interfaces';
export class Player extends Entity { export class Player extends Entity {
private static MASS: number = 10; private static MASS: number = 10;
private static MOI: number = 100; private static MOI: number = 100;
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.COFFEE, Sprites.COFFEE
) as SpriteSpec; ) as SpriteSpec;
constructor(playerId: string) { constructor(playerId: string) {
@ -31,15 +31,15 @@ export class Player extends Entity {
new BoundingBox( new BoundingBox(
{ {
x: 300, x: 300,
y: 100, y: 100
}, },
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height }, { width: Player.spriteSpec.width, height: Player.spriteSpec.height },
0, 0
), )
); );
this.addComponent( this.addComponent(
new Velocity({ dCartesian: { dx: 0, dy: 0 }, dTheta: 0 }), new Velocity({ dCartesian: { dx: 0, dy: 0 }, dTheta: 0 })
); );
this.addComponent(new Mass(Player.MASS)); this.addComponent(new Mass(Player.MASS));
@ -64,8 +64,8 @@ export class Player extends Entity {
{ x: 0, y: 0 }, { x: 0, y: 0 },
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height }, { width: Player.spriteSpec.width, height: Player.spriteSpec.height },
Player.spriteSpec.msPerFrame, Player.spriteSpec.msPerFrame,
Player.spriteSpec.frames, Player.spriteSpec.frames
), )
); );
this.addComponent(new FacingDirection(leftSprite, rightSprite)); this.addComponent(new FacingDirection(leftSprite, rightSprite));

View File

@ -1,4 +1,4 @@
export * from "./Entity"; export * from './Entity';
export * from "./Floor"; export * from './Floor';
export * from "./Player"; export * from './Player';
export * from "./names"; export * from './names';

View File

@ -1,4 +1,4 @@
export namespace EntityNames { export namespace EntityNames {
export const Player = "Player"; export const Player = 'Player';
export const Floor = "Floor"; export const Floor = 'Floor';
} }

View File

@ -1,5 +1,5 @@
export enum Action { export enum Action {
MOVE_LEFT, MOVE_LEFT,
MOVE_RIGHT, MOVE_RIGHT,
JUMP, JUMP
} }

View File

@ -1,6 +1,6 @@
export enum Direction { export enum Direction {
UP = "UP", UP = 'UP',
DOWN = "DOWN", DOWN = 'DOWN',
LEFT = "LEFT", LEFT = 'LEFT',
RIGHT = "RIGHT", RIGHT = 'RIGHT'
} }

View File

@ -1,4 +1,4 @@
import type { Coord2D, Dimension2D } from "./"; import type { Coord2D, Dimension2D } from './';
export interface DrawArgs { export interface DrawArgs {
center: Coord2D; center: Coord2D;

View File

@ -1,4 +1,4 @@
export * from "./Vec2"; export * from './Vec2';
export * from "./Draw"; export * from './Draw';
export * from "./Direction"; export * from './Direction';
export * from "./Action"; export * from './Action';

View File

@ -1,7 +1,7 @@
export enum MessageType { export enum MessageType {
NEW_ENTITY = "NEW_ENTITY", NEW_ENTITIES = 'NEW_ENTITIES',
REMOVE_ENTITY = "REMOVE_ENTITY", REMOVE_ENTITIES = 'REMOVE_ENTITIES',
UPDATE_ENTITY = "UPDATE_ENTITY", UPDATE_ENTITY = 'UPDATE_ENTITY'
} }
export type EntityAddBody = { export type EntityAddBody = {

View File

@ -1,6 +1,6 @@
import type { Coord2D, Dimension2D } from "../interfaces"; import type { Coord2D, Dimension2D } from '../interfaces';
import type { BoxedEntry, RefreshingCollisionFinderBehavior } from "."; import type { BoxedEntry, RefreshingCollisionFinderBehavior } from '.';
import { Miscellaneous } from "../config/constants"; import { Miscellaneous } from '../config/constants';
export class Grid implements RefreshingCollisionFinderBehavior { export class Grid implements RefreshingCollisionFinderBehavior {
private cellEntities: Map<number, string[]>; private cellEntities: Map<number, string[]>;
@ -12,11 +12,11 @@ export class Grid implements RefreshingCollisionFinderBehavior {
constructor( constructor(
gridDimension: Dimension2D = { gridDimension: Dimension2D = {
width: Miscellaneous.WIDTH, width: Miscellaneous.WIDTH,
height: Miscellaneous.HEIGHT, height: Miscellaneous.HEIGHT
}, },
cellDimension: Dimension2D = { cellDimension: Dimension2D = {
width: Miscellaneous.DEFAULT_GRID_WIDTH, width: Miscellaneous.DEFAULT_GRID_WIDTH,
height: Miscellaneous.DEFAULT_GRID_HEIGHT, height: Miscellaneous.DEFAULT_GRID_HEIGHT
}, },
topLeft = { x: 0, y: 0 } topLeft = { x: 0, y: 0 }
) { ) {
@ -73,7 +73,7 @@ export class Grid implements RefreshingCollisionFinderBehavior {
const translated: Coord2D = { const translated: Coord2D = {
y: center.y - this.topLeft.y, y: center.y - this.topLeft.y,
x: center.x - this.topLeft.x, x: center.x - this.topLeft.x
}; };
const topLeftBox = { const topLeftBox = {
@ -82,7 +82,7 @@ export class Grid implements RefreshingCollisionFinderBehavior {
), ),
y: Math.floor( y: Math.floor(
(translated.y - dimension.height / 2) / this.cellDimension.height (translated.y - dimension.height / 2) / this.cellDimension.height
), )
}; };
const bottomRightBox = { const bottomRightBox = {
x: Math.floor( x: Math.floor(
@ -90,7 +90,7 @@ export class Grid implements RefreshingCollisionFinderBehavior {
), ),
y: Math.floor( y: Math.floor(
(translated.y + dimension.height / 2) / this.cellDimension.height (translated.y + dimension.height / 2) / this.cellDimension.height
), )
}; };
const cells: number[] = []; const cells: number[] = [];

View File

@ -1,11 +1,11 @@
import type { Coord2D, Dimension2D } from "../interfaces"; import type { Coord2D, Dimension2D } from '../interfaces';
import type { BoxedEntry, RefreshingCollisionFinderBehavior } from "."; import type { BoxedEntry, RefreshingCollisionFinderBehavior } from '.';
enum Quadrant { enum Quadrant {
I, I,
II, II,
III, III,
IV, IV
} }
/* /*
@ -102,8 +102,8 @@ export class QuadTree implements RefreshingCollisionFinderBehavior {
[Quadrant.III, { x: this.topLeft.x, y: this.topLeft.y + halfHeight }], [Quadrant.III, { x: this.topLeft.x, y: this.topLeft.y + halfHeight }],
[ [
Quadrant.IV, Quadrant.IV,
{ x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight }, { x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight }
], ]
] as [Quadrant, Coord2D][] ] as [Quadrant, Coord2D][]
).forEach(([quadrant, pos]) => { ).forEach(([quadrant, pos]) => {
this.children.set( this.children.set(
@ -122,27 +122,27 @@ export class QuadTree implements RefreshingCollisionFinderBehavior {
private getQuadrants(boxedEntry: BoxedEntry): Quadrant[] { private getQuadrants(boxedEntry: BoxedEntry): Quadrant[] {
const treeCenter: Coord2D = { const treeCenter: Coord2D = {
x: this.topLeft.x + this.dimension.width / 2, x: this.topLeft.x + this.dimension.width / 2,
y: this.topLeft.y + this.dimension.height / 2, y: this.topLeft.y + this.dimension.height / 2
}; };
return ( return (
[ [
[ [
Quadrant.I, Quadrant.I,
(x: number, y: number) => x >= treeCenter.x && y < treeCenter.y, (x: number, y: number) => x >= treeCenter.x && y < treeCenter.y
], ],
[ [
Quadrant.II, Quadrant.II,
(x: number, y: number) => x < treeCenter.x && y < treeCenter.y, (x: number, y: number) => x < treeCenter.x && y < treeCenter.y
], ],
[ [
Quadrant.III, Quadrant.III,
(x: number, y: number) => x < treeCenter.x && y >= treeCenter.y, (x: number, y: number) => x < treeCenter.x && y >= treeCenter.y
], ],
[ [
Quadrant.IV, Quadrant.IV,
(x: number, y: number) => x >= treeCenter.x && y >= treeCenter.y, (x: number, y: number) => x >= treeCenter.x && y >= treeCenter.y
], ]
] as [Quadrant, (x: number, y: number) => boolean][] ] as [Quadrant, (x: number, y: number) => boolean][]
) )
.filter( .filter(

View File

@ -1,4 +1,4 @@
import type { Coord2D, Dimension2D } from "../interfaces"; import type { Coord2D, Dimension2D } from '../interfaces';
export interface BoxedEntry { export interface BoxedEntry {
id: string; id: string;
@ -7,8 +7,8 @@ export interface BoxedEntry {
} }
export interface RefreshingCollisionFinderBehavior { export interface RefreshingCollisionFinderBehavior {
public clear(): void; clear(): void;
public insert(boxedEntry: BoxedEntry): void; insert(boxedEntry: BoxedEntry): void;
public getNeighborIds(boxedEntry: BoxedEntry): Set<string>; getNeighborIds(boxedEntry: BoxedEntry): Set<string>;
public setTopLeft(topLeft: Coord2d): void; setTopLeft(topLeft: Coord2D): void;
} }

View File

@ -1,3 +1,3 @@
export * from "./RefreshingCollisionFinderBehavior"; export * from './RefreshingCollisionFinderBehavior';
export * from "./QuadTree"; export * from './QuadTree';
export * from "./Grid"; export * from './Grid';

View File

@ -1,22 +1,22 @@
import { SystemNames, System } from "."; import { SystemNames, System } from '.';
import { import {
Mass, Mass,
BoundingBox, BoundingBox,
ComponentNames, ComponentNames,
Jump, Jump,
Velocity, Velocity,
Forces, Forces
} from "../components"; } from '../components';
import { Game } from "../Game"; import { Game } from '../Game';
import { Miscellaneous, PhysicsConstants } from "../config"; import { Miscellaneous, PhysicsConstants } from '../config';
import { Entity } from "../entities"; import { Entity } from '../entities';
import type { Coord2D, Dimension2D, Velocity2D } from "../interfaces"; import type { Coord2D, Dimension2D, Velocity2D } from '../interfaces';
import { BoxedEntry, RefreshingCollisionFinderBehavior } from "../structures"; import { BoxedEntry, RefreshingCollisionFinderBehavior } from '../structures';
export class Collision extends System { export class Collision extends System {
private static readonly COLLIDABLE_COMPONENT_NAMES = [ private static readonly COLLIDABLE_COMPONENT_NAMES = [
ComponentNames.Collide, ComponentNames.Collide,
ComponentNames.TopCollidable, ComponentNames.TopCollidable
]; ];
private collisionFinder: RefreshingCollisionFinderBehavior; private collisionFinder: RefreshingCollisionFinderBehavior;
@ -38,7 +38,7 @@ export class Collision extends System {
return; return;
} }
entitiesToAddToCollisionFinder.push(entity); entitiesToAddToCollisionFinder.push(entity);
}), })
); );
this.insertEntitiesAndUpdateBounds(entitiesToAddToCollisionFinder); this.insertEntitiesAndUpdateBounds(entitiesToAddToCollisionFinder);
@ -53,7 +53,7 @@ export class Collision extends System {
entities.forEach((entity) => { entities.forEach((entity) => {
const boundingBox = entity.getComponent<BoundingBox>( const boundingBox = entity.getComponent<BoundingBox>(
ComponentNames.BoundingBox, ComponentNames.BoundingBox
); );
let dimension = { ...boundingBox.dimension }; let dimension = { ...boundingBox.dimension };
@ -73,7 +73,7 @@ export class Collision extends System {
collisionFinderInsertions.push({ collisionFinderInsertions.push({
id: entity.id, id: entity.id,
dimension, dimension,
center, center
}); });
}); });
@ -82,13 +82,13 @@ export class Collision extends System {
this.collisionFinder.setTopLeft(topLeft); this.collisionFinder.setTopLeft(topLeft);
this.collisionFinder.setDimension({ this.collisionFinder.setDimension({
width: bottomRight.x - topLeft.x, width: bottomRight.x - topLeft.x,
height: bottomRight.y - topLeft.y, height: bottomRight.y - topLeft.y
}); });
} }
// then, begin insertions // then, begin insertions
collisionFinderInsertions.forEach((boxedEntry: BoxedEntry) => collisionFinderInsertions.forEach((boxedEntry: BoxedEntry) =>
this.collisionFinder.insert(boxedEntry), this.collisionFinder.insert(boxedEntry)
); );
} }
@ -97,7 +97,7 @@ export class Collision extends System {
collidingEntities.forEach(([entityAId, entityBId]) => { collidingEntities.forEach(([entityAId, entityBId]) => {
const [entityA, entityB] = [entityAId, entityBId].map((id) => const [entityA, entityB] = [entityAId, entityBId].map((id) =>
game.entities.get(id), game.entities.get(id)
); );
if (entityA && entityB) { if (entityA && entityB) {
this.performCollision(entityA, entityB); this.performCollision(entityA, entityB);
@ -107,13 +107,13 @@ export class Collision extends System {
private performCollision(entityA: Entity, entityB: Entity) { private performCollision(entityA: Entity, entityB: Entity) {
const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map( const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map(
(entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox), (entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox)
); );
let velocity: Velocity2D = { dCartesian: { dx: 0, dy: 0 }, dTheta: 0 }; let velocity: Velocity2D = { dCartesian: { dx: 0, dy: 0 }, dTheta: 0 };
if (entityA.hasComponent(ComponentNames.Velocity)) { if (entityA.hasComponent(ComponentNames.Velocity)) {
velocity = entityA.getComponent<Velocity>( velocity = entityA.getComponent<Velocity>(
ComponentNames.Velocity, ComponentNames.Velocity
).velocity; ).velocity;
} }
@ -125,7 +125,7 @@ export class Collision extends System {
) { ) {
if (entityBBoundingBox.rotation != 0) { if (entityBBoundingBox.rotation != 0) {
throw new Error( throw new Error(
`entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.`, `entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.`
); );
} }
@ -139,7 +139,7 @@ export class Collision extends System {
entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({ entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({
fCartesian: { fy: F_n, fx: 0 }, fCartesian: { fy: F_n, fx: 0 },
torque: 0, torque: 0
}); });
} }
@ -157,19 +157,19 @@ export class Collision extends System {
private getCollidingEntities( private getCollidingEntities(
collidableEntities: Entity[], collidableEntities: Entity[],
game: Game, game: Game
): [string, string][] { ): [string, string][] {
const collidingEntityIds: [string, string][] = []; const collidingEntityIds: [string, string][] = [];
for (const entity of collidableEntities) { for (const entity of collidableEntities) {
const boundingBox = entity.getComponent<BoundingBox>( const boundingBox = entity.getComponent<BoundingBox>(
ComponentNames.BoundingBox, ComponentNames.BoundingBox
); );
const neighborIds = this.collisionFinder.getNeighborIds({ const neighborIds = this.collisionFinder.getNeighborIds({
id: entity.id, id: entity.id,
dimension: boundingBox.dimension, dimension: boundingBox.dimension,
center: boundingBox.center, center: boundingBox.center
}); });
for (const neighborId of neighborIds) { for (const neighborId of neighborIds) {
@ -177,7 +177,7 @@ export class Collision extends System {
if (!neighbor) return; if (!neighbor) return;
const neighborBoundingBox = neighbor.getComponent<BoundingBox>( const neighborBoundingBox = neighbor.getComponent<BoundingBox>(
ComponentNames.BoundingBox, ComponentNames.BoundingBox
); );
if (boundingBox.isCollidingWith(neighborBoundingBox)) { if (boundingBox.isCollidingWith(neighborBoundingBox)) {
@ -192,11 +192,11 @@ export class Collision extends System {
// ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ // ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ
private getDyToPushOutOfFloor( private getDyToPushOutOfFloor(
entityBoundingBox: BoundingBox, entityBoundingBox: BoundingBox,
floorBoundingBox: BoundingBox, floorBoundingBox: BoundingBox
): number { ): number {
const { const {
dimension: { width, height }, dimension: { width, height },
center: { x }, center: { x }
} = entityBoundingBox; } = entityBoundingBox;
const outScribedRectangle = entityBoundingBox.getOutscribedBoxDims(); const outScribedRectangle = entityBoundingBox.getOutscribedBoxDims();
@ -215,7 +215,7 @@ export class Collision extends System {
if (x >= floorBoundingBox.center.x) { if (x >= floorBoundingBox.center.x) {
boundedCollisionX = Math.min( boundedCollisionX = Math.min(
floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2, floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2,
clippedX, clippedX
); );
return ( return (
outScribedRectangle.height / 2 - outScribedRectangle.height / 2 -
@ -225,7 +225,7 @@ export class Collision extends System {
boundedCollisionX = Math.max( boundedCollisionX = Math.max(
floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2, floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2,
clippedX, clippedX
); );
return ( return (

View File

@ -2,10 +2,10 @@ import {
ComponentNames, ComponentNames,
Velocity, Velocity,
FacingDirection as FacingDirectionComponent, FacingDirection as FacingDirectionComponent,
Control, Control
} from "../components"; } from '../components';
import { Game } from "../Game"; import { Game } from '../Game';
import { System, SystemNames } from "./"; import { System, SystemNames } from './';
export class FacingDirection extends System { export class FacingDirection extends System {
constructor() { constructor() {
@ -23,7 +23,7 @@ export class FacingDirection extends System {
const totalVelocityComponent = new Velocity(); const totalVelocityComponent = new Velocity();
const control = entity.getComponent<Control>(ComponentNames.Control); const control = entity.getComponent<Control>(ComponentNames.Control);
const velocity = entity.getComponent<Velocity>( const velocity = entity.getComponent<Velocity>(
ComponentNames.Velocity, ComponentNames.Velocity
).velocity; ).velocity;
totalVelocityComponent.add(velocity); totalVelocityComponent.add(velocity);
@ -32,7 +32,7 @@ export class FacingDirection extends System {
} }
const facingDirection = entity.getComponent<FacingDirectionComponent>( const facingDirection = entity.getComponent<FacingDirectionComponent>(
ComponentNames.FacingDirection, ComponentNames.FacingDirection
); );
if (totalVelocityComponent.velocity.dCartesian.dx > 0) { if (totalVelocityComponent.velocity.dCartesian.dx > 0) {
@ -40,7 +40,7 @@ export class FacingDirection extends System {
} else if (totalVelocityComponent.velocity.dCartesian.dx < 0) { } else if (totalVelocityComponent.velocity.dCartesian.dx < 0) {
entity.addComponent(facingDirection.facingLeftSprite); entity.addComponent(facingDirection.facingLeftSprite);
} }
}, }
); );
} }
} }

View File

@ -4,12 +4,12 @@ import {
ComponentNames, ComponentNames,
Velocity, Velocity,
Mass, Mass,
Control, Control
} from "../components"; } from '../components';
import { Game } from "../Game"; import { Game } from '../Game';
import { KeyConstants, PhysicsConstants } from "../config"; import { KeyConstants, PhysicsConstants } from '../config';
import { Action } from "../interfaces"; import { Action } from '../interfaces';
import { System, SystemNames } from "."; import { System, SystemNames } from '.';
export class Input extends System { export class Input extends System {
public clientId: string; public clientId: string;
@ -42,7 +42,7 @@ export class Input extends System {
public update(_dt: number, game: Game) { public update(_dt: number, game: Game) {
game.forEachEntityWithComponent(ComponentNames.Control, (entity) => { game.forEachEntityWithComponent(ComponentNames.Control, (entity) => {
const controlComponent = entity.getComponent<Control>( const controlComponent = entity.getComponent<Control>(
ComponentNames.Control, ComponentNames.Control
); );
if (controlComponent.controllableBy != this.clientId) return; if (controlComponent.controllableBy != this.clientId) return;
@ -58,7 +58,7 @@ export class Input extends System {
if (entity.hasComponent(ComponentNames.Jump)) { if (entity.hasComponent(ComponentNames.Jump)) {
const velocity = entity.getComponent<Velocity>( const velocity = entity.getComponent<Velocity>(
ComponentNames.Velocity, ComponentNames.Velocity
).velocity; ).velocity;
const jump = entity.getComponent<Jump>(ComponentNames.Jump); const jump = entity.getComponent<Jump>(ComponentNames.Jump);
@ -78,9 +78,9 @@ export class Input extends System {
entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({ entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({
fCartesian: { fCartesian: {
fy: mass * PhysicsConstants.PLAYER_JUMP_ACC, fy: mass * PhysicsConstants.PLAYER_JUMP_ACC,
fx: 0, fx: 0
}, },
torque: 0, torque: 0
}); });
} }
} }

View File

@ -1,11 +1,11 @@
import { System, SystemNames } from "."; import { System, SystemNames } from '.';
import { Game } from "../Game"; import { Game } from '../Game';
import { ComponentNames, NetworkUpdateable } from "../components"; import { ComponentNames, NetworkUpdateable } from '../components';
import { import {
type MessageQueueProvider, type MessageQueueProvider,
type MessagePublisher, type MessagePublisher,
type MessageProcessor, type MessageProcessor
} from "../network"; } from '../network';
export class NetworkUpdate extends System { export class NetworkUpdate extends System {
private queueProvider: MessageQueueProvider; private queueProvider: MessageQueueProvider;
@ -15,7 +15,7 @@ export class NetworkUpdate extends System {
constructor( constructor(
queueProvider: MessageQueueProvider, queueProvider: MessageQueueProvider,
publisher: MessagePublisher, publisher: MessagePublisher,
messageProcessor: MessageProcessor, messageProcessor: MessageProcessor
) { ) {
super(SystemNames.NetworkUpdate); super(SystemNames.NetworkUpdate);
@ -34,9 +34,9 @@ export class NetworkUpdate extends System {
ComponentNames.NetworkUpdateable, ComponentNames.NetworkUpdateable,
(entity) => { (entity) => {
const networkUpdateComponent = entity.getComponent<NetworkUpdateable>( const networkUpdateComponent = entity.getComponent<NetworkUpdateable>(
ComponentNames.NetworkUpdateable, ComponentNames.NetworkUpdateable
); );
}, }
); );
this.publisher.publish(); this.publisher.publish();

View File

@ -1,4 +1,4 @@
import { System, SystemNames } from "."; import { System, SystemNames } from '.';
import { import {
BoundingBox, BoundingBox,
ComponentNames, ComponentNames,
@ -8,11 +8,11 @@ import {
Mass, Mass,
Jump, Jump,
Moment, Moment,
Control, Control
} from "../components"; } from '../components';
import { PhysicsConstants } from "../config"; import { PhysicsConstants } from '../config';
import type { Force2D, Velocity2D } from "../interfaces"; import type { Force2D, Velocity2D } from '../interfaces';
import { Game } from "../Game"; import { Game } from '../Game';
export class Physics extends System { export class Physics extends System {
constructor() { constructor() {
@ -24,10 +24,10 @@ export class Physics extends System {
const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass; const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces; const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces;
const velocity = entity.getComponent<Velocity>( const velocity = entity.getComponent<Velocity>(
ComponentNames.Velocity, ComponentNames.Velocity
).velocity; ).velocity;
const inertia = entity.getComponent<Moment>( const inertia = entity.getComponent<Moment>(
ComponentNames.Moment, ComponentNames.Moment
).inertia; ).inertia;
// F_g = mg, applied only until terminal velocity is reached // F_g = mg, applied only until terminal velocity is reached
@ -37,9 +37,9 @@ export class Physics extends System {
forces.push({ forces.push({
fCartesian: { fCartesian: {
fy: mass * PhysicsConstants.GRAVITY, fy: mass * PhysicsConstants.GRAVITY,
fx: 0, fx: 0
}, },
torque: 0, torque: 0
}); });
} }
} }
@ -49,17 +49,17 @@ export class Physics extends System {
(accum: Force2D, { fCartesian, torque }: Force2D) => ({ (accum: Force2D, { fCartesian, torque }: Force2D) => ({
fCartesian: { fCartesian: {
fx: accum.fCartesian.fx + (fCartesian?.fx ?? 0), fx: accum.fCartesian.fx + (fCartesian?.fx ?? 0),
fy: accum.fCartesian.fy + (fCartesian?.fy ?? 0), fy: accum.fCartesian.fy + (fCartesian?.fy ?? 0)
}, },
torque: accum.torque + (torque ?? 0), torque: accum.torque + (torque ?? 0)
}), }),
{ fCartesian: { fx: 0, fy: 0 }, torque: 0 }, { fCartesian: { fx: 0, fy: 0 }, torque: 0 }
); );
// integrate accelerations // integrate accelerations
const [ddy, ddx] = [ const [ddy, ddx] = [
sumOfForces.fCartesian.fy, sumOfForces.fCartesian.fy,
sumOfForces.fCartesian.fx, sumOfForces.fCartesian.fx
].map((x) => x / mass); ].map((x) => x / mass);
velocity.dCartesian.dx += ddx * dt; velocity.dCartesian.dx += ddx * dt;
velocity.dCartesian.dy += ddy * dt; velocity.dCartesian.dy += ddy * dt;
@ -79,14 +79,14 @@ export class Physics extends System {
const control = entity.getComponent<Control>(ComponentNames.Control); const control = entity.getComponent<Control>(ComponentNames.Control);
velocityComponent.add( velocityComponent.add(
entity.getComponent<Velocity>(ComponentNames.Velocity).velocity, entity.getComponent<Velocity>(ComponentNames.Velocity).velocity
); );
if (control) { if (control) {
velocityComponent.add(control.controlVelocityComponent.velocity); velocityComponent.add(control.controlVelocityComponent.velocity);
} }
const boundingBox = entity.getComponent<BoundingBox>( const boundingBox = entity.getComponent<BoundingBox>(
ComponentNames.BoundingBox, ComponentNames.BoundingBox
); );
// integrate velocity // integrate velocity

View File

@ -1,7 +1,7 @@
import { System, SystemNames } from "."; import { System, SystemNames } from '.';
import { BoundingBox, ComponentNames, Sprite } from "../components"; import { BoundingBox, ComponentNames, Sprite } from '../components';
import { Game } from "../Game"; import { Game } from '../Game';
import { clamp } from "../utils"; import { clamp } from '../utils';
export class Render extends System { export class Render extends System {
private ctx: CanvasRenderingContext2D; private ctx: CanvasRenderingContext2D;
@ -19,7 +19,7 @@ export class Render extends System {
sprite.update(dt); sprite.update(dt);
const boundingBox = entity.getComponent<BoundingBox>( const boundingBox = entity.getComponent<BoundingBox>(
ComponentNames.BoundingBox, ComponentNames.BoundingBox
); );
// don't render if we're outside the screen // don't render if we're outside the screen
@ -27,12 +27,12 @@ export class Render extends System {
clamp( clamp(
boundingBox.center.y, boundingBox.center.y,
-boundingBox.dimension.height / 2, -boundingBox.dimension.height / 2,
this.ctx.canvas.height + boundingBox.dimension.height / 2, this.ctx.canvas.height + boundingBox.dimension.height / 2
) != boundingBox.center.y || ) != boundingBox.center.y ||
clamp( clamp(
boundingBox.center.x, boundingBox.center.x,
-boundingBox.dimension.width / 2, -boundingBox.dimension.width / 2,
this.ctx.canvas.width + boundingBox.dimension.width / 2, this.ctx.canvas.width + boundingBox.dimension.width / 2
) != boundingBox.center.x ) != boundingBox.center.x
) { ) {
return; return;
@ -41,7 +41,7 @@ export class Render extends System {
const drawArgs = { const drawArgs = {
center: boundingBox.center, center: boundingBox.center,
dimension: boundingBox.dimension, dimension: boundingBox.dimension,
rotation: boundingBox.rotation, rotation: boundingBox.rotation
}; };
sprite.draw(this.ctx, drawArgs); sprite.draw(this.ctx, drawArgs);

View File

@ -1,4 +1,4 @@
import { Game } from "../Game"; import { Game } from '../Game';
export abstract class System { export abstract class System {
public readonly name: string; public readonly name: string;

View File

@ -1,28 +1,24 @@
import { System, SystemNames } from "."; import { System, SystemNames } from '.';
import { BoundingBox, ComponentNames } from "../components"; import { BoundingBox, ComponentNames } from '../components';
import { Game } from "../Game"; import { Game } from '../Game';
import type { Entity } from "../entities"; import { clamp } from '../utils';
import { clamp } from "../utils"; import { Miscellaneous } from '../config';
export class WallBounds extends System { export class WallBounds extends System {
private screenWidth: number; constructor() {
constructor(screenWidth: number) {
super(SystemNames.WallBounds); super(SystemNames.WallBounds);
this.screenWidth = screenWidth;
} }
public update(_dt: number, game: Game) { public update(_dt: number, game: Game) {
game.forEachEntityWithComponent(ComponentNames.WallBounded, (entity) => { game.forEachEntityWithComponent(ComponentNames.WallBounded, (entity) => {
const boundingBox = entity.getComponent<BoundingBox>( const boundingBox = entity.getComponent<BoundingBox>(
ComponentNames.BoundingBox, ComponentNames.BoundingBox
); );
boundingBox.center.x = clamp( boundingBox.center.x = clamp(
boundingBox.center.x, boundingBox.center.x,
boundingBox.dimension.width / 2, boundingBox.dimension.width / 2,
this.screenWidth - boundingBox.dimension.width / 2, Miscellaneous.WIDTH - boundingBox.dimension.width / 2
); );
}); });
} }

View File

@ -1,9 +1,9 @@
export * from "./names"; export * from './names';
export * from "./System"; export * from './System';
export * from "./Render"; export * from './Render';
export * from "./Physics"; export * from './Physics';
export * from "./Input"; export * from './Input';
export * from "./FacingDirection"; export * from './FacingDirection';
export * from "./Collision"; export * from './Collision';
export * from "./WallBounds"; export * from './WallBounds';
export * from "./NetworkUpdate"; export * from './NetworkUpdate';

View File

@ -1,9 +1,9 @@
export namespace SystemNames { export namespace SystemNames {
export const Render = "Render"; export const Render = 'Render';
export const Physics = "Physics"; export const Physics = 'Physics';
export const FacingDirection = "FacingDirection"; export const FacingDirection = 'FacingDirection';
export const Input = "Input"; export const Input = 'Input';
export const Collision = "Collision"; export const Collision = 'Collision';
export const WallBounds = "WallBounds"; export const WallBounds = 'WallBounds';
export const NetworkUpdate = "NetworkUpdate"; export const NetworkUpdate = 'NetworkUpdate';
} }

View File

@ -1,8 +1,8 @@
const replacer = (_key: any, value: any) => { const replacer = (_key: any, value: any) => {
if (value instanceof Map) { if (value instanceof Map) {
return { return {
dataType: "Map", dataType: 'Map',
value: Array.from(value.entries()), value: Array.from(value.entries())
}; };
} else { } else {
return value; return value;
@ -10,8 +10,8 @@ const replacer = (_key: any, value: any) => {
}; };
const reviver = (_key: any, value: any) => { const reviver = (_key: any, value: any) => {
if (typeof value === "object" && value !== null) { if (typeof value === 'object' && value !== null) {
if (value.dataType === "Map") { if (value.dataType === 'Map') {
return new Map(value.value); return new Map(value.value);
} }
} }

View File

@ -1,4 +1,4 @@
import type { Coord2D } from "../interfaces"; import type { Coord2D } from '../interfaces';
export const dotProduct = (vector1: Coord2D, vector2: Coord2D): number => export const dotProduct = (vector1: Coord2D, vector2: Coord2D): number =>
vector1.x * vector2.x + vector1.y * vector2.y; vector1.x * vector2.x + vector1.y * vector2.y;

View File

@ -1,4 +1,4 @@
export * from "./rotateVector"; export * from './rotateVector';
export * from "./dotProduct"; export * from './dotProduct';
export * from "./clamp"; export * from './clamp';
export * from "./coding"; export * from './coding';

View File

@ -1,4 +1,4 @@
import type { Coord2D } from "../interfaces"; import type { Coord2D } from '../interfaces';
/** /**
* ([[cos(θ), -sin(θ),]) ([x,) * ([[cos(θ), -sin(θ),]) ([x,)
@ -10,6 +10,6 @@ export const rotateVector = (vector: Coord2D, theta: number): Coord2D => {
return { return {
x: vector.x * cos - vector.y * sin, x: vector.x * cos - vector.y * sin,
y: vector.x * sin + vector.y * cos, y: vector.x * sin + vector.y * cos
}; };
}; };

View File

@ -8,6 +8,5 @@
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"
}, },
"dependencies": { "dependencies": {}
}
} }

View File

@ -1,21 +1,22 @@
import { Game } from "@engine/Game"; import { Game } from '@engine/Game';
import { EntityNames, Player } from "@engine/entities"; import { EntityNames, Player } from '@engine/entities';
import { WallBounds, Physics, Collision, NetworkUpdate } from "@engine/systems"; import { WallBounds, Physics, Collision, NetworkUpdate } from '@engine/systems';
import { import {
type MessageQueueProvider, type MessageQueueProvider,
type MessagePublisher, type MessagePublisher,
MessageType, MessageType,
type MessageProcessor, type MessageProcessor,
type Message, type Message
} from "@engine/network"; } from '@engine/network';
import { stringify, parse } from "@engine/utils"; import { stringify, parse } from '@engine/utils';
import { Grid } from "@engine/structures"; import { Grid } from '@engine/structures';
import { Miscellaneous } from "@engine/config"; import { Miscellaneous } from '@engine/config';
import { Server } from "bun"; import { Server } from 'bun';
const SERVER_PORT = 8080; const SERVER_PORT = 8080;
const SERVER_TICK_RATE = (1 / 100) * 1000; const SERVER_TICK_RATE = (1 / 60) * 1000;
const GAME_TOPIC = "game"; const GAME_TOPIC = 'game';
const MAX_PLAYERS = 8;
type SessionData = { sessionId: string }; type SessionData = { sessionId: string };
@ -70,11 +71,11 @@ class ServerSocketMessagePublisher implements MessagePublisher {
} }
public publish() { public publish() {
this.messages.forEach( if (this.messages.length) {
(message) => this.server?.publish(GAME_TOPIC, stringify(message)), this.server?.publish(GAME_TOPIC, stringify(this.messages));
);
this.messages = []; this.messages = [];
}
} }
} }
@ -85,81 +86,102 @@ const messagePublisher = new ServerSocketMessagePublisher();
const messageProcessor = new ServerMessageProcessor(); const messageProcessor = new ServerMessageProcessor();
const sessionControllableEntities: Map<string, Set<string>> = new Map(); const sessionControllableEntities: Map<string, Set<string>> = new Map();
const sessions = new Set<string>();
const server = Bun.serve<SessionData>({ const server = Bun.serve<SessionData>({
port: SERVER_PORT, port: SERVER_PORT,
fetch: async (req, server): Promise<Response> => { fetch: async (req, server): Promise<Response> => {
const url = new URL(req.url); const url = new URL(req.url);
const headers = new Headers(); const headers = new Headers();
headers.set("Access-Control-Allow-Origin", "*"); headers.set('Access-Control-Allow-Origin', '*');
if (url.pathname == '/assign') {
if (sessions.size > MAX_PLAYERS)
return new Response('too many players', { headers, status: 400 });
if (url.pathname == "/assign") {
const sessionId = crypto.randomUUID(); const sessionId = crypto.randomUUID();
headers.set("Set-Cookie", `SessionId=${sessionId};`); headers.set('Set-Cookie', `SessionId=${sessionId};`);
sessions.add(sessionId);
return new Response(sessionId, { headers }); return new Response(sessionId, { headers });
} }
const cookie = req.headers.get("cookie"); const cookie = req.headers.get('cookie');
if (!cookie) { if (!cookie) {
return new Response("No session", { headers, status: 401 }); return new Response('No session', { headers, status: 401 });
} }
const sessionId = cookie.split(";").at(0)!.split("SessionId=").at(1); const sessionId = cookie.split(';').at(0)!.split('SessionId=').at(1);
if (url.pathname == "/game") { if (url.pathname == '/game') {
headers.set( headers.set(
"Set-Cookie", 'Set-Cookie',
`SessionId=${sessionId}; HttpOnly; SameSite=Strict;`, `SessionId=${sessionId}; HttpOnly; SameSite=Strict;`
); );
server.upgrade(req, { server.upgrade(req, {
headers, headers,
data: { data: {
sessionId, sessionId
}, }
}); });
return new Response("upgraded", { headers }); return new Response('upgraded', { headers });
} }
if (url.pathname == "/me") {
if (url.pathname == '/me') {
return new Response(sessionId, { headers }); return new Response(sessionId, { headers });
} }
return new Response("Not found", { headers, status: 404 }); return new Response('Not found', { headers, status: 404 });
}, },
websocket: { websocket: {
open(ws) { open(ws) {
const { sessionId } = ws.data; const { sessionId } = ws.data;
if (sessionControllableEntities.has(sessionId)) { if (sessionControllableEntities.has(sessionId)) {
// no need to add player
return; return;
} }
const player = new Player(sessionId); const player = new Player(sessionId);
game.addEntity(player); game.addEntity(player);
sessionControllableEntities.set(sessionId, new Set(player.id)); sessionControllableEntities.set(sessionId, new Set([player.id]));
messagePublisher.addMessage({ messagePublisher.addMessage({
type: MessageType.NEW_ENTITY, type: MessageType.NEW_ENTITIES,
body: { body: [
entityName: EntityNames.Player, {
args: { playerId: sessionId }, entityName: EntityNames.Player,
}, args: { playerId: sessionId, id: player.id }
}
]
}); });
ws.subscribe(GAME_TOPIC); ws.subscribe(GAME_TOPIC);
}, },
message(ws, message) { message(ws, message) {
if (typeof message == "string") { if (typeof message == 'string') {
const receivedMessage = parse<ServerMessage>(message); const receivedMessage = parse<ServerMessage>(message);
receivedMessage.sessionData = ws.data; receivedMessage.sessionData = ws.data;
messageReceiver.addMessage(receivedMessage); messageReceiver.addMessage(receivedMessage);
} }
}, },
close(_ws) {}, close(ws) {
}, const { sessionId } = ws.data;
sessions.delete(sessionId);
const sessionEntities = sessionControllableEntities.get(sessionId);
if (!sessionEntities) return;
messagePublisher.addMessage({
type: MessageType.REMOVE_ENTITIES,
body: Array.from(sessionEntities)
});
}
}
}); });
messagePublisher.setServer(server); messagePublisher.setServer(server);
@ -167,8 +189,8 @@ messagePublisher.setServer(server);
[ [
new Physics(), new Physics(),
new Collision(new Grid()), new Collision(new Grid()),
new WallBounds(Miscellaneous.WIDTH), new WallBounds(),
new NetworkUpdate(messageReceiver, messagePublisher, messageProcessor), new NetworkUpdate(messageReceiver, messagePublisher, messageProcessor)
].forEach((system) => game.addSystem(system)); ].forEach((system) => game.addSystem(system));
game.start(); game.start();