import { ContextI } from "../ContextI" import { segments } from "../shared/segments" import { audio, playSong, disableSound, enableSound } from '../shared/audio' import { StateI } from "./StateI" import * as PIXI from 'pixi.js' import * as planck from 'planck' import { DecorationInstance } from "../shared/decors" import { SpriteInstance, sprites } from "../shared/sprites" import { ShapeCircle, ShapePoints } from "../data/sprite" import { SegmentZone } from "../data/segment" import { Zone } from "../live/Zone" import { Entity } from "../live/Entity" import { isPlayerEntity, PlayerEntity } from "../live/PlayerEntity" import { Action } from "../live/Action" import { WorldContext } from "../live/World" export interface PIXIMissingColorMatrix extends PIXI.Filter { night(intensity: number, multiply: boolean): void contrast(amount: number, multiply: boolean): void brightness(amount: number, multiply: boolean): void saturate(amount: number, multiply: boolean): void hue(rotation: number, multiply: boolean): void colorTone(desaturation: number, toned: number, lightColor: number, darkColor: number, multiple: boolean): void reset(): void } export interface Layer { title: string container: PIXI.Container decorations: DecorationInstance[] colorMatrix: PIXIMissingColorMatrix } export function GameState(ctx: ContextI): StateI { //disableSound() let isNight = false let modeTimer = 0 let nightTime = 10 * 1000 let dayTime = 10 * 1000 let lastTime: number = performance.now() let world: planck.World = planck.World({ gravity: planck.Vec2(0, 0), }) let worldBody: planck.Body = world.createBody({ type: 'static', }) world.on('begin-contact', (contact: planck.Contact) => { let a = contact.getFixtureA().getUserData() let b = contact.getFixtureB().getUserData() if (a instanceof Zone) { if (b instanceof Entity) { b.addZoneContact(a) } } else if (a instanceof Entity) { if (b instanceof Entity) { a.addEntityContact(b) b.addEntityContact(a) } } }) world.on('end-contact', (contact: planck.Contact) => { let a = contact.getFixtureA().getUserData() let b = contact.getFixtureB().getUserData() if (a instanceof Zone) { if (b instanceof Entity) { b.removeZoneContact(a) } } else if (a instanceof Entity) { if (b instanceof Entity) { a.removeEntityContact(b) b.removeEntityContact(a) } } }) let rootContainer = new PIXI.Container() let playLayer: Layer let layers: Layer[] = [] let entities: Entity[] = [] let zones: Zone[] = [] let enter = () => { lastTime = performance.now() hookKeyboard() // Load the world segment. let w = segments.world if (!w) return ctx.pop() rootContainer.width = w.width rootContainer.height = w.height rootContainer.scale.set(2, 2) for (let l of w.layers) { let layer: Layer = { title: l.title, container: new PIXI.Container(), decorations: [], colorMatrix: new PIXI.filters.ColorMatrixFilter() } layer.container.filters = [layer.colorMatrix] layer.container.width = w.width layer.container.height = w.height if (l.title === 'objects') { playLayer = layer layer.container.sortableChildren = true } layers.push(layer) for (let d of l.decorations) { let di = new DecorationInstance(d.decor, d.decoration) di.elapsed = d.timeOffset di.container.x = d.x di.container.y = d.y if (d.rotation !== 0) { di.container.angle = d.rotation } if (d.flip) { di.container.pivot.y = 1 di.container.scale.y *= -1 if (d.rotation === 0) { di.container.position.y-- } } if (d.mirror) { di.container.pivot.x = 1 di.container.scale.x *= -1 di.container.position.x-- if (d.rotation !== 0) { di.container.angle = -d.rotation di.container.position.x++ di.container.position.y-- } } if (l.title === 'objects') { di.container.zIndex = di.container.position.y + di.container.height/4 } layer.container.addChild(di.container) layer.decorations.push(di) } rootContainer.addChild(layer.container) } for (let z of w.zones) { addZone(new Zone(z)) } // Add bogus entity addEntity(new PlayerEntity('deer.animal.stand.west.0'), 300, 100) ctx.app.stage.addChild(rootContainer) } let leave = () => { unhookKeyboard() for (let entity of entities) { removeEntity(entity) } ctx.app.stage.removeChild(rootContainer) } let elapsed: number = 0 let update = (delta: number) => { let time = performance.now() let realDelta = time - lastTime lastTime = time checkGamepads() elapsed += delta modeTimer += realDelta if (isNight && modeTimer >= nightTime) { modeTimer = 0 nightfall(false) } else if (!isNight && modeTimer >= dayTime) { modeTimer = 0 nightfall(true) } // Run world sim. while (elapsed >= 1/60) { world.step(1/60) elapsed -= 1/60 } // Update/render. for (let layer of layers) { for (let decoration of layer.decorations) { decoration.update(realDelta) } } for (let entity of entities) { if (isPlayerEntity(entity)) { entity.act(desiredActions) // FIXME: This isn't the right place for this. rootContainer.position.set(Math.round(ctx.app.renderer.width/2), Math.round(ctx.app.renderer.height/2)) rootContainer.pivot.set(Math.max(rootContainer.width/6, Math.min(entity.x, rootContainer.width/3)), Math.max(rootContainer.height/6, Math.min(entity.y, rootContainer.height/3))) } entity.update(realDelta, worldContext) // I guess... entity.sprite.container.x = entity.x entity.sprite.container.y = entity.y entity.sprite.container.zIndex = entity.y + (entity.sprite.frame?entity.sprite.frame.originY:0) } } let addEntity = (entity: Entity, x: number, y: number) => { if (entities.find(v=>v===entity)) return console.log('add somethin', entity) entities.push(entity) playLayer.container.addChild(entity.sprite.container) // I guess this is a fair enough place to create physics and add it to the entity. let spriteShape = entity.sprite.getBodyShape() if (spriteShape) { let shape: planck.Shape|undefined if (spriteShape instanceof ShapeCircle) { shape = planck.Circle(planck.Vec2(spriteShape.x, spriteShape.y), spriteShape.radius) } else if (spriteShape instanceof ShapePoints) { shape = planck.Polygon(spriteShape.points.map(v=>planck.Vec2(v[0], v[1]))) } if (shape !== undefined) { let body = world.createDynamicBody({ position: planck.Vec2(entity.x, entity.y), fixedRotation: true, }) let fixture = body.createFixture({ shape, density: 1, friction: 0.9, restitution: 0.05, }) fixture.setUserData(entity) entity.body = body body.setUserData(entity) } } entity.x = x entity.y = y } let removeEntity = (entity: Entity) => { entities = entities.filter(v=>v!==entity) playLayer.container.removeChild(entity.sprite.container) if (entity.body) { world.destroyBody(entity.body) entity.body = undefined } } // Zonage let addZone = (zone: Zone) => { if (zones.find(v=>v===zone)) return let shape = planck.Chain(zone.points.map(v=>planck.Vec2(v[0],v[1]))) zone.fixture = worldBody.createFixture({ shape: shape, }) if (zone.type === 'fluid') { zone.fixture.setSensor(true) } zone.fixture.setUserData(zone) } let removeZone = (zone: Zone) => { zones = zones.filter(v=>v!==zone) if (zone.fixture) { worldBody.destroyFixture(zone.fixture) } } let worldContext: WorldContext = { addEntity, removeEntity, } let desiredActions: Action[] = [] let adjustAction = (type: string, v: number, inc?: boolean) => { let action = desiredActions.find(v=>v.type===type) if (!action) { if (v === 0) return desiredActions.push({ type: type, priority: v, }) } else { action.priority = inc?action.priority+v:v if (action.priority <= 0) { desiredActions = desiredActions.filter(v=>v!==action) } } } let keyup = (e: KeyboardEvent) => { if (e.key === 'ArrowLeft') { adjustAction('west', 0) } else if (e.key === 'ArrowRight') { adjustAction('east', 0) } else if (e.key === 'ArrowUp') { adjustAction('north', 0) } else if (e.key === 'ArrowDown') { adjustAction('south', 0) } } let keydown = (e: KeyboardEvent) => { if (e.repeat) return if (e.key === 'ArrowLeft') { adjustAction('west', 1) } else if (e.key === 'ArrowRight') { adjustAction('east', 1) } else if (e.key === 'ArrowUp') { adjustAction('north', 1) } else if (e.key === 'ArrowDown') { adjustAction('south', 1) } } let hookKeyboard = () => { window.addEventListener('keyup', keyup) window.addEventListener('keydown', keydown) } let unhookKeyboard = () => { window.removeEventListener('keyup', keyup) window.removeEventListener('keydown', keydown) } let gamepads: Record = {} window.addEventListener('gamepadconnected', (ev: GamepadEvent) => { gamepads[ev.gamepad.id] = ev.gamepad }) window.removeEventListener('gamepaddisconnected', (ev: GamepadEvent) => { delete gamepads[ev.gamepad.id] }) function checkGamepads() { //for (let gp of Object.values(gamepads)) { for (let gp of navigator.getGamepads()) { if (!gp || !gp.connected) break if (gp.axes[0] < 0) { adjustAction('west', Math.abs(gp.axes[0])) } else if (gp.axes[0] > 0) { adjustAction('east', gp.axes[0]) } else { adjustAction('east', 0) adjustAction('west', 0) } if (gp.axes[1] < 0) { adjustAction('north', Math.abs(gp.axes[1])) } else if (gp.axes[1] > 0) { adjustAction('south', gp.axes[1]) } else { adjustAction('north', 0) adjustAction('south', 0) } //console.log(gp.axes, gp.buttons) let attackHeld = false for (let i = 0; i < 4; i++) { let btn = gp.buttons[i] if (btn.pressed) { attackHeld = true } } if (attackHeld) { adjustAction('attack', 2) } else if (desiredActions.find(v=>v.type==='attack')) { adjustAction('attack', 0) } } } let nightfall = (b: boolean) => { if (b) { playSong('GGJ-ScaryMusic') for (let l of layers) { //l.colorMatrix.brightness(0.25, false) l.colorMatrix.night(0.2, true) l.colorMatrix.saturate(-0.75, true) //l.colorMatrix.hue(160, true) } for (let e of entities) { } } else { playSong('GGJ-HappyMusic') for (let l of layers) { l.colorMatrix.reset() } for (let e of entities) { } } isNight = b } nightfall(false) return { enter, leave, update, } }