2022-01-29 01:37:01 -08:00
|
|
|
import { ContextI } from "../ContextI"
|
2022-01-29 03:44:34 -08:00
|
|
|
import { segments } from "../shared/segments"
|
2022-01-29 01:37:01 -08:00
|
|
|
import { StateI } from "./StateI"
|
2022-01-29 03:44:34 -08:00
|
|
|
import * as PIXI from 'pixi.js'
|
2022-01-29 16:31:40 -08:00
|
|
|
import * as planck from 'planck'
|
2022-01-29 03:44:34 -08:00
|
|
|
import { DecorationInstance } from "../shared/decors"
|
2022-01-29 16:31:40 -08:00
|
|
|
import { SpriteInstance, sprites } from "../shared/sprites"
|
|
|
|
import { ShapeCircle, ShapePoints } from "../data/sprite"
|
2022-01-29 17:54:53 -08:00
|
|
|
import { SegmentZone } from "../data/segment"
|
2022-01-29 04:57:01 -08:00
|
|
|
|
|
|
|
interface Action {
|
|
|
|
type: string
|
|
|
|
priority: number
|
|
|
|
}
|
|
|
|
|
2022-01-29 17:54:53 -08:00
|
|
|
class Zone {
|
|
|
|
fixture: planck.Fixture|undefined
|
|
|
|
type: 'solid'|'fluid'
|
|
|
|
points: [number, number][]
|
|
|
|
constructor(z: SegmentZone) {
|
|
|
|
this.type = z.type
|
|
|
|
this.points = z.points.map(v=>[v[0], v[1]])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-29 04:57:01 -08:00
|
|
|
class Entity {
|
|
|
|
sprite: SpriteInstance
|
2022-01-29 16:31:40 -08:00
|
|
|
body?: planck.Body
|
|
|
|
velocity: [number, number] = [0, 0]
|
|
|
|
acceleration: number = 0.5
|
|
|
|
maxSpeed: number = 4
|
|
|
|
direction: number = 0
|
2022-01-29 17:06:13 -08:00
|
|
|
turnRate: number = 10
|
2022-01-29 04:57:01 -08:00
|
|
|
|
|
|
|
constructor(ctor: string) {
|
|
|
|
this.sprite = new SpriteInstance(ctor)
|
|
|
|
}
|
|
|
|
|
|
|
|
update(delta: number) {
|
|
|
|
// TODO: Update sprite.
|
|
|
|
}
|
|
|
|
|
2022-01-29 16:31:40 -08:00
|
|
|
get position(): [number, number] {
|
|
|
|
if (this.body) {
|
|
|
|
let p = this.body.getPosition()
|
|
|
|
return [p.x, p.y]
|
|
|
|
}
|
|
|
|
return [this.sprite.container.x, this.sprite.container.y]
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
get x(): number {
|
2022-01-29 16:31:40 -08:00
|
|
|
if (this.body) {
|
|
|
|
let p = this.body.getPosition()
|
|
|
|
return p.x
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
return this.sprite.container.x
|
|
|
|
}
|
|
|
|
set x(v: number) {
|
2022-01-29 16:31:40 -08:00
|
|
|
if (this.body) {
|
|
|
|
let p = this.body.getPosition()
|
|
|
|
this.body.setPosition(
|
|
|
|
planck.Vec2(
|
|
|
|
v,
|
|
|
|
p.y,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
this.sprite.container.y = v
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
get y(): number {
|
2022-01-29 16:31:40 -08:00
|
|
|
if (this.body) {
|
|
|
|
let p = this.body.getPosition()
|
|
|
|
return p.y
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
return this.sprite.container.y
|
|
|
|
}
|
|
|
|
set y(v: number) {
|
2022-01-29 16:31:40 -08:00
|
|
|
if (this.body) {
|
|
|
|
let p = this.body.getPosition()
|
|
|
|
this.body.setPosition(
|
|
|
|
planck.Vec2(
|
|
|
|
p.x,
|
|
|
|
v,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
this.sprite.container.y = v
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PlayerEntity extends Entity {
|
|
|
|
action?: Action
|
|
|
|
constructor(ctor: string) {
|
|
|
|
super(ctor)
|
|
|
|
}
|
|
|
|
act(actions: Action[]) {
|
|
|
|
this.action = actions.sort((a, b) => {
|
|
|
|
if (a.priority < b.priority) {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if (a.priority > b.priority) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})[0]
|
|
|
|
}
|
|
|
|
update(delta: number) {
|
|
|
|
if (this.action) {
|
|
|
|
// FIXME: Use physics.
|
2022-01-29 17:06:13 -08:00
|
|
|
let shouldMove = false
|
2022-01-29 04:57:01 -08:00
|
|
|
switch(this.action.type) {
|
|
|
|
case 'west':
|
2022-01-29 17:06:13 -08:00
|
|
|
if (this.direction !== 0) {
|
|
|
|
if (this.direction < 180) {
|
|
|
|
this.direction -= this.turnRate
|
|
|
|
} else {
|
|
|
|
this.direction += this.turnRate
|
|
|
|
}
|
2022-01-29 16:31:40 -08:00
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
shouldMove = true
|
2022-01-29 04:57:01 -08:00
|
|
|
break
|
|
|
|
case 'east':
|
2022-01-29 17:06:13 -08:00
|
|
|
if (this.direction !== 180) {
|
|
|
|
if (this.direction < 180) {
|
|
|
|
this.direction += this.turnRate
|
|
|
|
} else {
|
|
|
|
this.direction -= this.turnRate
|
|
|
|
}
|
2022-01-29 16:31:40 -08:00
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
shouldMove = true
|
2022-01-29 04:57:01 -08:00
|
|
|
break
|
|
|
|
case 'north':
|
2022-01-29 17:06:13 -08:00
|
|
|
if (this.direction !== 90) {
|
|
|
|
if (this.direction < 90 || this.direction > 270) {
|
|
|
|
this.direction += this.turnRate
|
|
|
|
} else {
|
|
|
|
this.direction -= this.turnRate
|
|
|
|
}
|
2022-01-29 16:31:40 -08:00
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
shouldMove = true
|
2022-01-29 04:57:01 -08:00
|
|
|
break
|
|
|
|
case 'south':
|
2022-01-29 17:06:13 -08:00
|
|
|
if (this.direction !== 270) {
|
|
|
|
if (this.direction > 90 && this.direction < 270) {
|
|
|
|
this.direction += this.turnRate
|
|
|
|
} else {
|
|
|
|
this.direction -= this.turnRate
|
|
|
|
}
|
2022-01-29 16:31:40 -08:00
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
shouldMove = true
|
2022-01-29 04:57:01 -08:00
|
|
|
break
|
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
if (this.direction > 360) {
|
|
|
|
this.direction = 0
|
|
|
|
} else if (this.direction < 0) {
|
|
|
|
this.direction = 360
|
|
|
|
}
|
|
|
|
if (shouldMove) {
|
|
|
|
let r = this.direction * (Math.PI/180)
|
|
|
|
if (Math.abs(this.velocity[0]) < this.maxSpeed) {
|
|
|
|
this.velocity[0] -= Math.cos(r) * this.acceleration
|
|
|
|
}
|
|
|
|
if (Math.abs(this.velocity[1]) < this.maxSpeed) {
|
|
|
|
this.velocity[1] -= Math.sin(r) * this.acceleration
|
|
|
|
}
|
|
|
|
let cardinal = this.getCardinal()
|
|
|
|
if (this.sprite.subsetKey !== cardinal) {
|
|
|
|
this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${cardinal}.${this.sprite.frameIndex}`)
|
|
|
|
}
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
|
2022-01-29 16:31:40 -08:00
|
|
|
//
|
|
|
|
this.velocity[0] *= 0.5
|
|
|
|
this.velocity[1] *= 0.5
|
|
|
|
|
|
|
|
// Eh... let's manually handle velocity
|
|
|
|
this.body?.setLinearVelocity(planck.Vec2(this.velocity[0], this.velocity[1]))
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
2022-01-29 17:06:13 -08:00
|
|
|
getCardinal(): string {
|
|
|
|
const degreesPerDirection = 360 / 8
|
|
|
|
const angle = this.direction + degreesPerDirection / 2
|
|
|
|
|
|
|
|
if (angle >= 0 * degreesPerDirection && angle < 1 * degreesPerDirection) {
|
|
|
|
return 'w'
|
|
|
|
} else if (angle >= 1 * degreesPerDirection && angle < 2 * degreesPerDirection) {
|
|
|
|
return 'nw'
|
|
|
|
} else if (angle >= 2 * degreesPerDirection && angle < 3 * degreesPerDirection) {
|
|
|
|
return 'n'
|
|
|
|
} else if (angle >= 3 * degreesPerDirection && angle < 4 * degreesPerDirection) {
|
|
|
|
return 'ne'
|
|
|
|
} else if (angle >= 4 * degreesPerDirection && angle < 5 * degreesPerDirection) {
|
|
|
|
return 'e'
|
|
|
|
} else if (angle >= 5 * degreesPerDirection && angle < 6 * degreesPerDirection) {
|
|
|
|
return 'se'
|
|
|
|
} else if (angle >= 6 * degreesPerDirection && angle < 7 * degreesPerDirection) {
|
|
|
|
return 's'
|
|
|
|
}
|
|
|
|
return 'sw'
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
function isPlayerEntity(o: any): o is PlayerEntity {
|
|
|
|
return o.act
|
|
|
|
}
|
2022-01-29 01:37:01 -08:00
|
|
|
|
|
|
|
export function GameState(ctx: ContextI): StateI {
|
2022-01-29 16:31:40 -08:00
|
|
|
let world: planck.World = planck.World({
|
|
|
|
gravity: planck.Vec2(0, 0),
|
|
|
|
})
|
2022-01-29 17:54:53 -08:00
|
|
|
let worldBody: planck.Body = world.createBody({
|
|
|
|
type: 'static',
|
|
|
|
})
|
2022-01-29 01:37:01 -08:00
|
|
|
|
2022-01-29 03:44:34 -08:00
|
|
|
let rootContainer = new PIXI.Container()
|
|
|
|
let decorations: DecorationInstance[] = []
|
2022-01-29 04:57:01 -08:00
|
|
|
let entities: Entity[] = []
|
2022-01-29 17:54:53 -08:00
|
|
|
let zones: Zone[] = []
|
2022-01-29 03:44:34 -08:00
|
|
|
|
2022-01-29 01:37:01 -08:00
|
|
|
let enter = () => {
|
2022-01-29 04:57:01 -08:00
|
|
|
hookKeyboard()
|
2022-01-29 03:44:34 -08:00
|
|
|
// 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 container = new PIXI.Container()
|
|
|
|
container.width = w.width
|
|
|
|
container.height = w.height
|
|
|
|
|
|
|
|
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
|
2022-01-29 16:31:40 -08:00
|
|
|
if (d.rotation !== 0) {
|
|
|
|
di.container.angle = d.rotation
|
|
|
|
}
|
2022-01-29 03:44:34 -08:00
|
|
|
if (d.flip) {
|
|
|
|
di.container.pivot.y = 1
|
|
|
|
di.container.scale.y *= -1
|
2022-01-29 16:31:40 -08:00
|
|
|
if (d.rotation === 0) {
|
|
|
|
di.container.position.y--
|
|
|
|
}
|
2022-01-29 03:44:34 -08:00
|
|
|
}
|
|
|
|
if (d.mirror) {
|
|
|
|
di.container.pivot.x = 1
|
|
|
|
di.container.scale.x *= -1
|
|
|
|
di.container.position.x--
|
2022-01-29 16:31:40 -08:00
|
|
|
if (d.rotation !== 0) {
|
|
|
|
di.container.angle = -d.rotation
|
|
|
|
di.container.position.x++
|
|
|
|
di.container.position.y--
|
|
|
|
}
|
2022-01-29 03:44:34 -08:00
|
|
|
}
|
|
|
|
container.addChild(di.container)
|
|
|
|
decorations.push(di)
|
|
|
|
}
|
|
|
|
|
|
|
|
rootContainer.addChild(container)
|
|
|
|
}
|
|
|
|
|
2022-01-29 17:54:53 -08:00
|
|
|
for (let z of w.zones) {
|
|
|
|
addZone(new Zone(z))
|
|
|
|
}
|
|
|
|
|
2022-01-29 04:57:01 -08:00
|
|
|
// Add bogus entity
|
2022-01-29 16:31:40 -08:00
|
|
|
//addEntity(new PlayerEntity('animals.deer.animal.west.0'), 0, 0)
|
2022-01-29 17:06:13 -08:00
|
|
|
addEntity(new PlayerEntity('bogus-arrows.player.normal.w.0'), 0, 0)
|
2022-01-29 04:57:01 -08:00
|
|
|
|
2022-01-29 03:44:34 -08:00
|
|
|
ctx.app.stage.addChild(rootContainer)
|
2022-01-29 01:37:01 -08:00
|
|
|
}
|
|
|
|
let leave = () => {
|
2022-01-29 04:57:01 -08:00
|
|
|
unhookKeyboard()
|
|
|
|
for (let entity of entities) {
|
|
|
|
removeEntity(entity)
|
|
|
|
}
|
2022-01-29 03:44:34 -08:00
|
|
|
ctx.app.stage.removeChild(rootContainer)
|
2022-01-29 01:37:01 -08:00
|
|
|
}
|
2022-01-29 16:31:40 -08:00
|
|
|
let elapsed: number = 0
|
2022-01-29 01:37:01 -08:00
|
|
|
let update = (delta: number) => {
|
2022-01-29 16:31:40 -08:00
|
|
|
elapsed += delta
|
|
|
|
// Run world sim.
|
|
|
|
while (elapsed >= 1/60) {
|
|
|
|
world.step(1/60)
|
|
|
|
elapsed -= 1/60
|
|
|
|
}
|
|
|
|
// Update/render.
|
2022-01-29 03:44:34 -08:00
|
|
|
for (let decoration of decorations) {
|
|
|
|
decoration.update(delta)
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
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))
|
2022-01-29 05:18:51 -08:00
|
|
|
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)))
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
entity.update(delta)
|
2022-01-29 16:31:40 -08:00
|
|
|
// I guess...
|
|
|
|
entity.sprite.container.x = entity.x
|
|
|
|
entity.sprite.container.y = entity.y
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-29 16:31:40 -08:00
|
|
|
let addEntity = (entity: Entity, x: number, y: number) => {
|
2022-01-29 04:57:01 -08:00
|
|
|
if (entities.find(v=>v===entity)) return
|
|
|
|
entities.push(entity)
|
|
|
|
rootContainer.addChild(entity.sprite.container)
|
2022-01-29 16:31:40 -08:00
|
|
|
// 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,
|
|
|
|
})
|
|
|
|
entity.body = body
|
|
|
|
body.setUserData(entity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entity.x = x
|
|
|
|
entity.y = y
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
let removeEntity = (entity: Entity) => {
|
|
|
|
entities = entities.filter(v=>v!==entity)
|
|
|
|
rootContainer.removeChild(entity.sprite.container)
|
2022-01-29 16:31:40 -08:00
|
|
|
if (entity.body) {
|
|
|
|
world.destroyBody(entity.body)
|
|
|
|
entity.body = undefined
|
|
|
|
}
|
2022-01-29 04:57:01 -08:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:54:53 -08:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let removeZone = (zone: Zone) => {
|
|
|
|
zones = zones.filter(v=>v!==zone)
|
|
|
|
if (zone.fixture) {
|
|
|
|
worldBody.destroyFixture(zone.fixture)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let desiredActions: Action[] = []
|
2022-01-29 04:57:01 -08:00
|
|
|
let adjustAction = (type: string, v: number) => {
|
|
|
|
let action = desiredActions.find(v=>v.type===type)
|
|
|
|
if (!action) {
|
|
|
|
desiredActions.push({
|
|
|
|
type: type,
|
|
|
|
priority: v,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
action.priority += v
|
|
|
|
if (action.priority <= 0) {
|
|
|
|
desiredActions = desiredActions.filter(v=>v!==action)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let keyup = (e: KeyboardEvent) => {
|
|
|
|
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 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)
|
2022-01-29 01:37:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
enter,
|
|
|
|
leave,
|
|
|
|
update,
|
|
|
|
}
|
|
|
|
}
|