tilegame/src/entity.js

214 lines
5.0 KiB
JavaScript

import { ctx } from './canvas'
import { ItemRegistry, ItemStack } from './items'
class PhysicsEntity {
constructor (x, y, w, h) {
this.x = x
this.y = y
this.width = w
this.height = h
this.mX = 0
this.mY = 0
this.grounded = false
this.speed = 8
this.gravity = 1
this.jumpPower = 20
this.dead = false
}
moveAndSlide (collider) {
// y collision
if (this.mY !== 0) {
let oldY = this.y
this.y += this.mY
if (oldY !== this.y && collider.collide(this)) {
if (this.y > oldY) this.grounded = true
this.y = oldY
this.mY = 0
} else {
this.grounded = false
}
}
// x collision
if (this.mX !== 0) {
let oldX = this.x
this.x += this.mX
if (oldX !== this.x && collider.collide(this)) {
this.mX = this.mX < 0 ? -1 : 1
this.x = oldX + this.mX
if (collider.collide(this)) {
this.x = oldX
this.mX = 0
}
}
}
}
update (dt, vp, world) {
this.mY += this.gravity
this.moveAndSlide(world)
}
draw (vp) {
ctx.fillStyle = '#f00'
ctx.fillRect(this.x - vp.x, this.y - vp.y, this.width, this.height)
}
collide (ent) {
if (!(ent instanceof PhysicsEntity)) return null
// Intersection check
if (this.x > ent.x + ent.width || this.x + this.width < ent.x ||
this.y > ent.y + ent.height || this.y + this.height < ent.y) return false
return true
}
distance (ent) {
if (!(ent instanceof PhysicsEntity)) return null
let d1 = { x: ent.x + ent.width / 2, y: ent.y + ent.height / 2 }
let d2 = { x: this.x + this.width / 2, y: this.y + this.height / 2 }
let dist = Math.floor(Math.sqrt(Math.pow(d1.x - d2.x, 2) + Math.pow(d1.y - d2.y, 2)))
return dist
}
}
const ITEM_LIFE = 120
const ITEM_MERGE_DISTANCE = 60
const ITEM_BOB_FACTOR = 5
class ItemEntity extends PhysicsEntity {
constructor (istr, x, y) {
super(x, y, 16, 16)
this.istr = istr
this._mergeTick = 0
this._life = 0
}
get name () {
if (this.istr === '') return ''
return this.istr.split(' ')[0]
}
get count () {
if (this.istr === '') return ''
let c = parseInt(this.istr.split(' ')[1])
return isNaN(c) ? 0 : c
}
get item () {
let itm = ItemRegistry.get(this.name)
return itm
}
static new (itemStack, x, y) {
if (!(itemStack instanceof ItemStack)) return null
return new ItemEntity(itemStack.toString(), x, y)
}
mergeNearby (vp, world) {
let entLayer = world.getLayer('ents')
if (!entLayer) return
let active = entLayer.getActiveEntities(vp, world)
for (let i in active) {
let ent = active[i]
if (ent.dead) continue
if (ent === this) continue
if (!(ent instanceof ItemEntity)) continue
if (ent.name !== this.name) continue
let dist = Math.floor(Math.sqrt(Math.pow(ent.x - this.x, 2) + Math.pow(ent.y - this.y, 2)))
if (dist > ITEM_MERGE_DISTANCE) continue
this.istr = this.name + ' ' + (this.count + ent.count)
this._life = 0
ent.dead = true
}
}
update (dt, vp, world) {
if (this.dead) return
this._mergeTick++
this._life += 1 * dt
if (this._mergeTick >= 60) {
this._mergeTick = 0
this.mergeNearby(vp, world)
}
if (this._life > ITEM_LIFE) {
this.dead = true
return
}
super.update(dt, vp, world)
}
draw (vp, world) {
let i = this.item
let b = Math.sin((this._life / ITEM_BOB_FACTOR) / Math.PI * 180)
if (!i) return
ctx.drawImage(i.image, this.x - vp.x, this.y - vp.y + b, this.width, this.height)
}
}
class EntityLayer {
constructor (name) {
this.name = name
this.entities = []
}
getActiveEntities (vp, world, cleanup = false) {
let active = []
let alive = []
for (let i in this.entities) {
let ent = this.entities[i]
if (ent.dead) {
continue
} else if (cleanup) {
alive.push(ent)
}
let entInChunk = world.gridPosition(vp, ent)
if (!entInChunk || !entInChunk.chunk) continue
let entChunk
for (let i in world._active) {
let chunk = world._active[i]
if (chunk.x === entInChunk.chunk.x && chunk.y === entInChunk.chunk.y) {
entChunk = chunk
break
}
}
if (!entChunk) continue
ent._chunk = entChunk
active.push(ent)
}
if (cleanup) this.entities = alive
return active
}
// Update active entities and clean up dead ones
update (dt, vp, world) {
let active = this.getActiveEntities(vp, world, true)
for (let i in active) {
let ent = active[i]
ent.update(dt, vp, world, ent._chunk)
}
}
draw (vp, world) {
let active = this.getActiveEntities(vp, world)
for (let i in active) {
let ent = active[i]
if (ent.x > vp.x + vp.width + ent.width || ent.x + ent._chunk.fullSize < vp.x - ent.width ||
ent.y > vp.y + vp.height + ent.height || ent.y + ent._chunk.fullSize < vp.y - ent.height) continue
ent.draw(vp, world)
}
}
}
export { ItemEntity, PhysicsEntity, EntityLayer }