woaj
continuous-integration/drone/pr Build is passing
Details
continuous-integration/drone/pr Build is passing
Details
This commit is contained in:
parent
e4e31978ba
commit
e47d451426
|
@ -9,7 +9,14 @@
|
|||
<title>the kennel.</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<noscript>
|
||||
<div style="text-align: center">
|
||||
<h1>yeah, unfortunately you need javascript ;3</h1>
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<canvas id="gamecanvas"></canvas>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"name": "kennel",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"jquery": "^3.7.1"
|
||||
"jquery": "^3.7.1",
|
||||
"laser-pen": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-inject": "^5.0.5",
|
||||
|
@ -1230,6 +1231,12 @@
|
|||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
|
||||
},
|
||||
"node_modules/laser-pen": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/laser-pen/-/laser-pen-1.0.1.tgz",
|
||||
"integrity": "sha512-+K3CQK5ryLDDjX0pEdSIRXh89F6KSpm225DEymWMheg2/umhUO+na/4l8MGs2gee7VO2Mx3kyG288WGrofasoA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.11",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"vite-plugin-dynamic-base": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"jquery": "^3.7.1"
|
||||
"jquery": "^3.7.1",
|
||||
"laser-pen": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export enum ComponentType {
|
||||
POSITION = "POSITION",
|
||||
RENDERABLE = "RENDERABLE",
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
name: string;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
export enum ComponentType {
|
||||
POSITION = "POSITION",
|
||||
RENDERABLE = "RENDERABLE",
|
||||
TRAILING_POSITION = "TRAILING_POSITION",
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
name: ComponentType;
|
||||
}
|
||||
|
||||
export interface PositionComponent extends Component {
|
||||
name: ComponentType.POSITION;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface TrailingPositionComponent extends Component {
|
||||
name: ComponentType.TRAILING_POSITION;
|
||||
trails: Array<{ x: number; y: number; time: number }>;
|
||||
}
|
||||
|
||||
export interface RenderableComponent extends Component {
|
||||
name: ComponentType.RENDERABLE;
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
import { Vec2 } from "./vector";
|
||||
export class MouseController {
|
||||
export class DebouncePublisher<T> {
|
||||
private last_event_time = Date.now();
|
||||
private last_movement: Vec2 | undefined;
|
||||
private unpublished_data: T | undefined;
|
||||
private interval_id: number | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly publisher: (new_movement: Vec2) => void | Promise<void>,
|
||||
private readonly debounce_ms = 200,
|
||||
private readonly publisher: (data: T) => void | Promise<void>,
|
||||
private readonly debounce_ms = 100,
|
||||
) {}
|
||||
|
||||
public start() {
|
||||
|
@ -14,7 +13,7 @@ export class MouseController {
|
|||
return;
|
||||
}
|
||||
this.interval_id = setInterval(
|
||||
() => this.publish_movement(),
|
||||
() => this.debounce_publish(),
|
||||
this.debounce_ms,
|
||||
);
|
||||
}
|
||||
|
@ -27,21 +26,21 @@ export class MouseController {
|
|||
delete this.interval_id;
|
||||
}
|
||||
|
||||
public move(x: number, y: number) {
|
||||
this.last_movement = new Vec2(x, y);
|
||||
this.publish_movement();
|
||||
public update(data: T) {
|
||||
this.unpublished_data = data;
|
||||
this.debounce_publish();
|
||||
}
|
||||
|
||||
private publish_movement() {
|
||||
private debounce_publish() {
|
||||
if (
|
||||
Date.now() - this.last_event_time < this.debounce_ms ||
|
||||
typeof this.last_movement === "undefined"
|
||||
typeof this.unpublished_data === "undefined"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.last_event_time = Date.now();
|
||||
this.publisher(this.last_movement.copy());
|
||||
delete this.last_movement;
|
||||
this.publisher(this.unpublished_data);
|
||||
this.unpublished_data = undefined;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import {
|
||||
Component,
|
||||
ComponentType,
|
||||
RenderableComponent,
|
||||
TrailingPositionComponent,
|
||||
} from "./component";
|
||||
|
||||
export enum EntityType {
|
||||
LASER = "LASER",
|
||||
}
|
||||
|
||||
export interface Entity {
|
||||
entity_type: EntityType;
|
||||
id: string;
|
||||
components: Record<ComponentType, Component>;
|
||||
}
|
||||
|
||||
export const create_laser = (base: Entity) => {
|
||||
const trailing_position: TrailingPositionComponent = {
|
||||
name: ComponentType.TRAILING_POSITION,
|
||||
trails: [],
|
||||
};
|
||||
base.components[ComponentType.TRAILING_POSITION] = trailing_position;
|
||||
|
||||
const renderable: RenderableComponent = {
|
||||
name: ComponentType.RENDERABLE,
|
||||
};
|
||||
base.components[ComponentType.RENDERABLE] = renderable;
|
||||
return base;
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import { Entity } from "./entity";
|
||||
export enum EventType {
|
||||
INITIAL_STATE = "INITIAL_STATE",
|
||||
SET_CONTROLLABLE = "SET_CONTROLLABLE",
|
||||
|
@ -26,7 +27,7 @@ export interface InitialStateEvent extends Event {
|
|||
event_type: EventType.INITIAL_STATE;
|
||||
data: {
|
||||
world: { width: number; height: number };
|
||||
entities: any[];
|
||||
entities: Record<string, Entity>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -38,6 +39,20 @@ export interface SetControllableEvent extends Event {
|
|||
};
|
||||
}
|
||||
|
||||
export interface EntityBornEvent extends Event {
|
||||
event_type: EventType.ENTITY_BORN;
|
||||
data: {
|
||||
entity: Entity;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EntityDeathEvent extends Event {
|
||||
event_type: EventType.ENTITY_DEATH;
|
||||
data: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EventQueue {
|
||||
peek(): Event[];
|
||||
clear(): void;
|
|
@ -0,0 +1,112 @@
|
|||
import { System, SystemType } from "./system";
|
||||
import { Entity } from "./entity";
|
||||
import { ComponentType } from "./component";
|
||||
|
||||
export class Game {
|
||||
private running: boolean;
|
||||
private last_update: number;
|
||||
|
||||
private readonly entities: Map<string, Entity> = new Map();
|
||||
private readonly component_entities: Map<ComponentType, Set<string>> =
|
||||
new Map();
|
||||
private readonly systems: Map<SystemType, System> = new Map();
|
||||
private readonly system_order: SystemType[];
|
||||
|
||||
constructor(
|
||||
public readonly client_id: string,
|
||||
systems: System[],
|
||||
) {
|
||||
this.last_update = performance.now();
|
||||
this.running = false;
|
||||
|
||||
systems.forEach((system) => this.systems.set(system.system_type, system));
|
||||
this.system_order = systems.map(({ system_type }) => system_type);
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (this.running) return;
|
||||
|
||||
console.log("starting game");
|
||||
this.running = true;
|
||||
this.last_update = performance.now();
|
||||
|
||||
const game_loop = (timestamp: number) => {
|
||||
if (!this.running) return;
|
||||
|
||||
// rebuild component -> { entity } map
|
||||
this.component_entities.clear();
|
||||
Array.from(this.entities.values()).forEach((entity) =>
|
||||
Object.values(entity.components).forEach((component) => {
|
||||
const set =
|
||||
this.component_entities.get(component.name) ?? new Set<string>();
|
||||
set.add(entity.id);
|
||||
this.component_entities.set(component.name, set);
|
||||
}),
|
||||
);
|
||||
|
||||
const dt = timestamp - this.last_update;
|
||||
|
||||
this.system_order.forEach((system_type) =>
|
||||
this.systems.get(system_type)!.update(dt, this),
|
||||
);
|
||||
|
||||
this.last_update = timestamp;
|
||||
requestAnimationFrame(game_loop); // tail call recursion! /s
|
||||
};
|
||||
requestAnimationFrame(game_loop);
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (!this.running) return;
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
public for_each_entity_with_component(
|
||||
component: ComponentType,
|
||||
callback: (entity: Entity) => void,
|
||||
) {
|
||||
this.component_entities.get(component)?.forEach((entity_id) => {
|
||||
const entity = this.entities.get(entity_id);
|
||||
if (!entity) return;
|
||||
|
||||
callback(entity);
|
||||
});
|
||||
}
|
||||
|
||||
public get_entity(id: string) {
|
||||
return this.entities.get(id);
|
||||
}
|
||||
|
||||
public put_entity(entity: Entity) {
|
||||
const old_entity = this.entities.get(entity.id);
|
||||
if (old_entity) this.clear_entity_components(old_entity);
|
||||
|
||||
Object.values(entity.components).forEach((component) => {
|
||||
const set =
|
||||
this.component_entities.get(component.name) ?? new Set<string>();
|
||||
set.add(entity.id);
|
||||
this.component_entities.set(component.name, set);
|
||||
});
|
||||
this.entities.set(entity.id, entity);
|
||||
}
|
||||
|
||||
public remove_entity(id: string) {
|
||||
const entity = this.entities.get(id);
|
||||
if (typeof entity === "undefined") return;
|
||||
|
||||
this.clear_entity_components(entity);
|
||||
this.entities.delete(id);
|
||||
}
|
||||
|
||||
private clear_entity_components(entity: Entity) {
|
||||
Object.values(entity.components).forEach((component) => {
|
||||
const set = this.component_entities.get(component.name);
|
||||
if (typeof set === "undefined") return;
|
||||
set.delete(entity.id);
|
||||
});
|
||||
}
|
||||
|
||||
public get_system<T extends System>(system_type: SystemType): T | undefined {
|
||||
return this.systems.get(system_type) as T;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { DebouncePublisher } from "./debounce_publisher";
|
||||
import { EntityPositionUpdateEvent, EventPublisher, EventType } from "./events";
|
||||
import { Game } from "./game";
|
||||
import { System, SystemType } from "./system";
|
||||
|
||||
export class InputSystem extends System {
|
||||
private readonly controllable_entities: Set<string> = new Set();
|
||||
private readonly mouse_movement_debouncer: DebouncePublisher<{
|
||||
x: number;
|
||||
y: number;
|
||||
}>;
|
||||
|
||||
constructor(
|
||||
private readonly message_publisher: EventPublisher,
|
||||
target: HTMLElement,
|
||||
) {
|
||||
super(SystemType.INPUT);
|
||||
|
||||
this.mouse_movement_debouncer = new DebouncePublisher((data) =>
|
||||
this.publish_mouse_movement(data),
|
||||
);
|
||||
|
||||
target.addEventListener("mousemove", (event) => {
|
||||
this.mouse_movement_debouncer.update({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private publish_mouse_movement({ x, y }: { x: number; y: number }) {
|
||||
console.log(`publishing mouse movement at (${x}, ${y})`);
|
||||
for (const entity_id of this.controllable_entities) {
|
||||
this.message_publisher.add({
|
||||
event_type: EventType.ENTITY_POSITION_UPDATE,
|
||||
data: {
|
||||
id: entity_id,
|
||||
position: { x, y },
|
||||
},
|
||||
} as EntityPositionUpdateEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public add_controllable_entity(entity_id: string) {
|
||||
this.controllable_entities.add(entity_id);
|
||||
}
|
||||
|
||||
public update(_dt: number, _game: Game) {}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import {
|
||||
ComponentType,
|
||||
PositionComponent,
|
||||
TrailingPositionComponent,
|
||||
} from "./component";
|
||||
import { create_laser, Entity, EntityType } from "./entity";
|
||||
import {
|
||||
EntityBornEvent,
|
||||
EntityDeathEvent,
|
||||
EntityPositionUpdateEvent,
|
||||
EventPublisher,
|
||||
EventQueue,
|
||||
EventType,
|
||||
InitialStateEvent,
|
||||
SetControllableEvent,
|
||||
} from "./events";
|
||||
import { Game } from "./game";
|
||||
import { InputSystem } from "./input";
|
||||
import { RenderSystem } from "./render";
|
||||
import { System, SystemType } from "./system";
|
||||
|
||||
export class NetworkSystem extends System {
|
||||
constructor(
|
||||
private readonly event_queue: EventQueue,
|
||||
private readonly event_publisher: EventPublisher,
|
||||
) {
|
||||
super(SystemType.NETWORK);
|
||||
}
|
||||
|
||||
public update(_dt: number, game: Game) {
|
||||
const events = this.event_queue.peek();
|
||||
for (const event of events) {
|
||||
switch (event.event_type) {
|
||||
case EventType.INITIAL_STATE:
|
||||
this.process_initial_state_event(event as InitialStateEvent, game);
|
||||
break;
|
||||
case EventType.SET_CONTROLLABLE:
|
||||
this.process_set_controllable_event(
|
||||
event as SetControllableEvent,
|
||||
game,
|
||||
);
|
||||
break;
|
||||
case EventType.ENTITY_BORN:
|
||||
this.process_entity_born_event(event as EntityBornEvent, game);
|
||||
break;
|
||||
case EventType.ENTITY_POSITION_UPDATE:
|
||||
this.process_entity_position_update_event(
|
||||
event as EntityPositionUpdateEvent,
|
||||
game,
|
||||
);
|
||||
break;
|
||||
case EventType.ENTITY_DEATH:
|
||||
this.process_entity_death_event(event as EntityDeathEvent, game);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.event_queue.clear();
|
||||
this.event_publisher.publish();
|
||||
}
|
||||
|
||||
private process_initial_state_event(event: InitialStateEvent, game: Game) {
|
||||
console.log("received initial state", event);
|
||||
const { world, entities } = event.data;
|
||||
const render_system = game.get_system<RenderSystem>(SystemType.RENDER);
|
||||
if (!render_system) {
|
||||
console.error("render system not found");
|
||||
return;
|
||||
}
|
||||
render_system.set_world_dimensions(world.width, world.height);
|
||||
Object.values(entities).forEach((entity) =>
|
||||
game.put_entity(this.process_new_entity(entity)),
|
||||
);
|
||||
}
|
||||
|
||||
private process_entity_position_update_event(
|
||||
event: EntityPositionUpdateEvent,
|
||||
game: Game,
|
||||
) {
|
||||
console.log("received entity position update", event);
|
||||
const { position, id } = event.data;
|
||||
const entity = game.get_entity(id);
|
||||
if (typeof entity === "undefined") return;
|
||||
|
||||
const position_component = entity.components[
|
||||
ComponentType.POSITION
|
||||
] as PositionComponent;
|
||||
position_component.x = position.x;
|
||||
position_component.y = position.y;
|
||||
|
||||
if (ComponentType.TRAILING_POSITION in entity.components) {
|
||||
const trailing_position = entity.components[
|
||||
ComponentType.TRAILING_POSITION
|
||||
] as TrailingPositionComponent;
|
||||
trailing_position.trails.push({
|
||||
x: position_component.x,
|
||||
y: position_component.y,
|
||||
time: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private process_entity_born_event(event: EntityBornEvent, game: Game) {
|
||||
console.log("received a new entity", event);
|
||||
const { entity } = event.data;
|
||||
game.put_entity(this.process_new_entity(entity));
|
||||
}
|
||||
|
||||
private process_new_entity(entity: Entity): Entity {
|
||||
if (entity.entity_type === EntityType.LASER) {
|
||||
return create_laser(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private process_entity_death_event(event: EntityDeathEvent, game: Game) {
|
||||
console.log("an entity died D:", event);
|
||||
const { id } = event.data;
|
||||
game.remove_entity(id);
|
||||
}
|
||||
|
||||
private process_set_controllable_event(
|
||||
event: SetControllableEvent,
|
||||
game: Game,
|
||||
) {
|
||||
console.log("got a controllable event", event);
|
||||
if (event.data.client_id !== game.client_id) {
|
||||
console.warn("got controllable event for client that is not us");
|
||||
return;
|
||||
}
|
||||
|
||||
const input_system = game.get_system<InputSystem>(SystemType.INPUT)!;
|
||||
input_system.add_controllable_entity(event.data.id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { ComponentType, TrailingPositionComponent } from "./component";
|
||||
import { Game } from "./game";
|
||||
import { System, SystemType } from "./system";
|
||||
import { drawLaserPen } from "laser-pen";
|
||||
|
||||
export class RenderSystem extends System {
|
||||
constructor(private readonly canvas: HTMLCanvasElement) {
|
||||
super(SystemType.RENDER);
|
||||
}
|
||||
|
||||
public set_world_dimensions(width: number, height: number) {
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
}
|
||||
|
||||
public update(_dt: number, game: Game) {
|
||||
const ctx = this.canvas.getContext("2d");
|
||||
if (ctx === null) return;
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
game.for_each_entity_with_component(ComponentType.RENDERABLE, (entity) => {
|
||||
if (ComponentType.TRAILING_POSITION in entity.components) {
|
||||
const trailing_position = entity.components[
|
||||
ComponentType.TRAILING_POSITION
|
||||
] as TrailingPositionComponent;
|
||||
if (trailing_position.trails.length < 3) return;
|
||||
drawLaserPen(ctx, trailing_position.trails);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { Game } from "./game";
|
||||
|
||||
export enum SystemType {
|
||||
INPUT = "INPUT",
|
||||
NETWORK = "NETWORK",
|
||||
RENDER = "RENDER",
|
||||
TRAILING_POSITION = "TRAILING_POSITION",
|
||||
}
|
||||
|
||||
export abstract class System {
|
||||
constructor(public readonly system_type: SystemType) {}
|
||||
|
||||
abstract update(dt: number, game: Game): void;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { ComponentType, TrailingPositionComponent } from "./component";
|
||||
import { Game } from "./game";
|
||||
import { System, SystemType } from "./system";
|
||||
|
||||
export class TrailingPositionSystem extends System {
|
||||
constructor(
|
||||
private readonly point_filter: (
|
||||
trail_point: {
|
||||
x: number;
|
||||
y: number;
|
||||
time: number;
|
||||
}[],
|
||||
) => {
|
||||
x: number;
|
||||
y: number;
|
||||
time: number;
|
||||
}[],
|
||||
) {
|
||||
super(SystemType.TRAILING_POSITION);
|
||||
}
|
||||
|
||||
public update(_dt: number, game: Game) {
|
||||
game.for_each_entity_with_component(
|
||||
ComponentType.TRAILING_POSITION,
|
||||
(entity) => {
|
||||
const trailing_position = entity.components[
|
||||
ComponentType.TRAILING_POSITION
|
||||
] as TrailingPositionComponent;
|
||||
trailing_position.trails = this.point_filter(trailing_position.trails);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import type { Component } from "./component";
|
||||
|
||||
export enum EntityType {
|
||||
LASER = "LASER",
|
||||
}
|
||||
|
||||
export interface Entity {
|
||||
entity_type: EntityType;
|
||||
id: string;
|
||||
components: Record<string, Component>;
|
||||
}
|
|
@ -1,95 +1,16 @@
|
|||
import $ from "jquery";
|
||||
import { Vec2 } from "./vector";
|
||||
import { MouseController } from "./mouse_controller";
|
||||
import {
|
||||
EntityPositionUpdateEvent,
|
||||
EventPublisher,
|
||||
EventQueue,
|
||||
EventType,
|
||||
SetControllableEvent,
|
||||
WebsocketEventPublisher,
|
||||
WebSocketEventQueue,
|
||||
} from "./network";
|
||||
|
||||
class KennelClient {
|
||||
private running: boolean;
|
||||
private last_update: number;
|
||||
|
||||
private controllable_entities: Set<string> = new Set();
|
||||
private mouse_controller: MouseController;
|
||||
|
||||
constructor(
|
||||
private readonly client_id: string,
|
||||
private readonly event_queue: EventQueue,
|
||||
private readonly event_publisher: EventPublisher,
|
||||
) {
|
||||
this.last_update = 0;
|
||||
this.running = false;
|
||||
|
||||
this.mouse_controller = new MouseController((position: Vec2) =>
|
||||
this.on_mouse_move(position),
|
||||
);
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.running = true;
|
||||
this.last_update = performance.now();
|
||||
this.mouse_controller.start();
|
||||
|
||||
const loop = (timestamp: number) => {
|
||||
if (!this.running) return;
|
||||
const dt = timestamp - this.last_update;
|
||||
this.propogate_state_after(dt);
|
||||
requestAnimationFrame(loop); // tail call recursion! /s
|
||||
};
|
||||
requestAnimationFrame(loop);
|
||||
|
||||
$(document).on("mousemove", (event) => {
|
||||
this.mouse_controller.move(event.clientX, event.clientY);
|
||||
});
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.running = false;
|
||||
this.mouse_controller.stop();
|
||||
this.controllable_entities.clear();
|
||||
$(document).off("mousemove");
|
||||
}
|
||||
|
||||
private propogate_state_after(dt: number) {
|
||||
const events = this.event_queue.peek();
|
||||
for (const event of events) {
|
||||
switch (event.event_type) {
|
||||
case EventType.SET_CONTROLLABLE:
|
||||
this.process_set_controllable_event(event as SetControllableEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (events.length > 0) {
|
||||
console.log(events, dt);
|
||||
}
|
||||
this.event_queue.clear();
|
||||
this.event_publisher.publish();
|
||||
}
|
||||
|
||||
private process_set_controllable_event(event: SetControllableEvent) {
|
||||
if (event.data.client_id !== this.client_id) {
|
||||
console.warn("got controllable event for client that is not us");
|
||||
return;
|
||||
}
|
||||
this.controllable_entities.add(event.data.id);
|
||||
}
|
||||
|
||||
private on_mouse_move(position: Vec2) {
|
||||
for (const id of this.controllable_entities) {
|
||||
const event: EntityPositionUpdateEvent = {
|
||||
event_type: EventType.ENTITY_POSITION_UPDATE,
|
||||
data: { id, position: { x: position.x, y: position.y } },
|
||||
};
|
||||
this.event_publisher.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
} from "./engine/events";
|
||||
import { Game } from "./engine/game";
|
||||
import { NetworkSystem } from "./engine/network";
|
||||
import { RenderSystem } from "./engine/render";
|
||||
import { InputSystem } from "./engine/input";
|
||||
import { TrailingPositionSystem } from "./engine/trailing_position";
|
||||
import { drainPoints, setDelay } from "laser-pen";
|
||||
|
||||
$(async () => {
|
||||
const client_id = await fetch("/assign", {
|
||||
|
@ -105,8 +26,25 @@ $(async () => {
|
|||
|
||||
const queue: EventQueue = new WebSocketEventQueue(ws);
|
||||
const publisher: EventPublisher = new WebsocketEventPublisher(ws);
|
||||
const kennel_client = new KennelClient(client_id, queue, publisher);
|
||||
ws.onclose = () => kennel_client.close();
|
||||
const network_system = new NetworkSystem(queue, publisher);
|
||||
|
||||
kennel_client.start();
|
||||
const gamecanvas = $("#gamecanvas").get(0)!;
|
||||
const input_system = new InputSystem(publisher, gamecanvas);
|
||||
|
||||
setDelay(1_000);
|
||||
const render_system = new RenderSystem(gamecanvas as HTMLCanvasElement);
|
||||
|
||||
const trailing_position = new TrailingPositionSystem(drainPoints);
|
||||
|
||||
const systems = [
|
||||
network_system,
|
||||
trailing_position,
|
||||
input_system,
|
||||
render_system,
|
||||
];
|
||||
|
||||
const game = new Game(client_id, systems);
|
||||
ws.onclose = () => game.stop();
|
||||
|
||||
game.start();
|
||||
});
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
export class Vec2 {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {}
|
||||
|
||||
public distance_to(that: Vec2): number {
|
||||
return Math.sqrt((this.x - that.x) ** 2 + (this.y - that.y) ** 2);
|
||||
}
|
||||
|
||||
public copy(): Vec2 {
|
||||
return new Vec2(this.x, this.y);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue