base2020/src/Game/GameComponents.ts

198 lines
4.7 KiB
TypeScript

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<string, any> = {};
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<GameSchema> {
boss: Store<Boss>;
bullet: Store<Bullet>;
hp: Store<Hp>;
lifetime: Store<Lifetime>;
message: Store<Message>;
// globals
debugLayer = new Layer(2);
constructor(from: Partial<Data>) {
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<Bullet> {
hit: boolean;
team: Teams;
attack: number;
constructor(from: Partial<Bullet>) {
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<Hp> {
receivedDamage: number;
team: Teams;
hp: number;
constructor(from: Partial<Hp>) {
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<Lifetime> {
time: number;
constructor(from: Partial<Lifetime> & {time: number}) {
super(from);
this.time = from.time;
}
clone(): Lifetime {
return new Lifetime(this);
}
}
export class Boss extends Component<Boss> {
name: string;
constructor(from: Partial<Boss>) {
super(from);
this.name = from.name ?? "";
}
clone(): Boss {
return new Boss(this);
}
}
export class Message extends Component<Message> {
targetY = 0;
layer: number;
color: string;
message: string;
timeout = 3;
constructor(from: Partial<Message>) {
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<KeyName[], Data> {
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;
});
}
}