import { KeyName } from "../Applet/Keyboard"; import { DrawSet, Layer } from "../Applet/Render"; import { ComponentSchema, Data as EcsData } from "../Ecs/Components"; import { Component, copySparse, Join, StateForSchema, Store } from "../Ecs/Data"; import { DumbMotion } from "../Ecs/Location"; import { INPUT_FREQUENCY, LockstepProcessor } from "../Ecs/Lockstep"; import { Buttons } from "./Input"; export enum GamePhase { TITLE, PLAYING, LOST, WON } export type RGB = [number, number, number]; export class World { width = 500; height = 400; /* * Core Game Status */ phase = GamePhase.TITLE; score = 0; constructor() {} /* * Drawing Layers */ groundLayer = new Layer(0); debugLayer = new Layer(2); bulletLayer = new Layer(10); playerLayer = new Layer(15); smokeLayer = new Layer(16); hudLayer = new Layer(20); bgColor: RGB = [255, 255, 255]; /** * Catch-all debug tool */ debug: Record = {}; drawHud(drawSet: DrawSet) { drawSet.queue(this.hudLayer.toRender((cx, dt) => { cx.font = "16px monospace"; cx.textAlign = "left"; cx.textBaseline = "middle"; const score = `Score: ${this.score}`; cx.fillStyle = "#000"; cx.fillText(score, this.width/3 + 1, this.height - 18 + 1, this.width/4); cx.fillStyle = "#0ff"; cx.fillText(score, this.width/3, this.height - 18, this.width/4); })); } } interface GameSchema extends ComponentSchema { boss: Boss; bullet: Bullet; hp: Hp; lifetime: Lifetime; message: Message; } export class Data extends EcsData implements StateForSchema { boss: Store; bullet: Store; hp: Store; lifetime: Store; message: Store; // globals debugLayer = new Layer(2); constructor(from: Partial) { super(from); this.boss = copySparse(from.boss); this.bullet = copySparse(from.bullet); this.hp = copySparse(from.hp); this.lifetime = copySparse(from.lifetime); this.message = copySparse(from.message); } clone() { return new Data(this); } } export enum Teams { PLAYER, ENEMY } export class Bullet extends Component { hit: boolean; team: Teams; attack: number; constructor(from: Partial) { super(from); this.hit = from.hit ?? false; this.team = from.team ?? Teams.ENEMY; this.attack = from.attack ?? 1; } clone(): Bullet { return new Bullet(this); } } export class Hp extends Component { receivedDamage: number; team: Teams; hp: number; constructor(from: Partial) { super(from); this.receivedDamage = from.receivedDamage ?? 0; this.team = from.team ?? Teams.ENEMY; this.hp = from.hp ?? 10; } clone(): Hp { return new Hp(this); } } export class Lifetime extends Component { time: number; constructor(from: Partial & {time: number}) { super(from); this.time = from.time; } clone(): Lifetime { return new Lifetime(this); } } export class Boss extends Component { name: string; constructor(from: Partial) { super(from); this.name = from.name ?? ""; } clone(): Boss { return new Boss(this); } } export class Message extends Component { targetY = 0; layer: number; color: string; message: string; timeout = 3; constructor(from: Partial) { super(from); this.targetY = from.targetY ?? 0; this.layer = from.layer ?? 1; this.color = from.color ?? "#000"; this.message = from.message ?? ""; this.timeout = from.timeout ?? 3; } clone(): Message { return new Message(this); } } export class Engine implements LockstepProcessor { cloneState(old: Data) { return new Data(old); } compareInput(a: KeyName[], b: KeyName[]): boolean { if (a.length != b.length) return false; let matches = true; a.forEach((keyA, i) => { if (keyA != b[i]) { matches = false; } }); return matches; } advanceState(state: Data, input: KeyName[]) { DumbMotion(state, INPUT_FREQUENCY); Join(state, "location").forEach(([location]) => { let dir = 0; if(input.indexOf("left") != -1) { dir -= 1; } if(input.indexOf("right") != -1) { dir += 1; } location.VAngle = dir * 0.01; }); } }