optimize reporting and serialization for clients
This commit is contained in:
+39
-32
@@ -12,20 +12,14 @@ export default class Entity {
|
|||||||
static #nextUniqueId = 0
|
static #nextUniqueId = 0
|
||||||
|
|
||||||
abilities = {}
|
abilities = {}
|
||||||
bbox = new Float32Array(4)
|
|
||||||
buffs = []
|
buffs = []
|
||||||
casting = null
|
casting = null
|
||||||
collision = true
|
|
||||||
cooldowns = {}
|
cooldowns = {}
|
||||||
dead = false
|
dead = false
|
||||||
ghostable = true
|
|
||||||
ghosting = false
|
ghosting = false
|
||||||
health = null
|
health = null
|
||||||
height = 40
|
height = 40
|
||||||
maxHealth = 1
|
maxHealth = 1
|
||||||
memory = {}
|
|
||||||
pathfindingCooldown = 0
|
|
||||||
pathfindingObstacleLimit = null
|
|
||||||
position = null
|
position = null
|
||||||
radius = 0
|
radius = 0
|
||||||
rotation = 0
|
rotation = 0
|
||||||
@@ -34,8 +28,15 @@ export default class Entity {
|
|||||||
visionRange = 900
|
visionRange = 900
|
||||||
visualRadius = null
|
visualRadius = null
|
||||||
|
|
||||||
|
#collision = true
|
||||||
|
#ghostable = true
|
||||||
#attacking = false
|
#attacking = false
|
||||||
|
#bbox = new Float32Array(4)
|
||||||
#colliders = []
|
#colliders = []
|
||||||
|
#entitiesInVision = []
|
||||||
|
#projectilesInVision = []
|
||||||
|
#pathfindingCooldown = 0
|
||||||
|
#pathfindingObstacleLimit = null
|
||||||
#dest = null
|
#dest = null
|
||||||
#game = null
|
#game = null
|
||||||
#logic = null
|
#logic = null
|
||||||
@@ -44,7 +45,6 @@ export default class Entity {
|
|||||||
#noPathfindingUntil = 0
|
#noPathfindingUntil = 0
|
||||||
#spawnPosition = new Vector2()
|
#spawnPosition = new Vector2()
|
||||||
|
|
||||||
|
|
||||||
static bbox(x, y, radius) {
|
static bbox(x, y, radius) {
|
||||||
return new Float32Array([y + radius, x + radius, y - radius, x - radius])
|
return new Float32Array([y + radius, x + radius, y - radius, x - radius])
|
||||||
}
|
}
|
||||||
@@ -145,16 +145,28 @@ export default class Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get attacking() { return this.#attacking }
|
get attacking() { return this.#attacking }
|
||||||
|
get bbox() { return this.#bbox }
|
||||||
|
get collision() { return this.#collision }
|
||||||
get destination() { return this.#dest }
|
get destination() { return this.#dest }
|
||||||
get logic() { return this.#logic }
|
get entitiesInVision() { return this.#entitiesInVision }
|
||||||
get game() { return this.#game }
|
get game() { return this.#game }
|
||||||
|
get ghostable() { return this.#ghostable }
|
||||||
|
get logic() { return this.#logic }
|
||||||
|
get pathfindingCooldown() { return this.#pathfindingCooldown }
|
||||||
|
get pathfindingObstacleLimit() { return this.#pathfindingObstacleLimit }
|
||||||
|
get projectilesInVision() { return this.#projectilesInVision }
|
||||||
get spawnPosition() { return this.#spawnPosition }
|
get spawnPosition() { return this.#spawnPosition }
|
||||||
get x() { return this.position.x }
|
get x() { return this.position.x }
|
||||||
get y() { return this.position.y }
|
get y() { return this.position.y }
|
||||||
|
|
||||||
|
set bbox(value) { this.#bbox = value }
|
||||||
|
set collision(value) { this.#collision = value }
|
||||||
set destination(value) { this.#dest = value }
|
set destination(value) { this.#dest = value }
|
||||||
set logic(value) { this.#logic = value }
|
|
||||||
set game(value) { this.#game = value }
|
set game(value) { this.#game = value }
|
||||||
|
set ghostable(value) { this.#ghostable = value }
|
||||||
|
set logic(value) { this.#logic = value }
|
||||||
|
set pathfindingCooldown(value) { this.#pathfindingCooldown = value }
|
||||||
|
set pathfindingObstacleLimit(value) { this.#pathfindingObstacleLimit = value }
|
||||||
set spawnPosition(value) { this.#spawnPosition = value }
|
set spawnPosition(value) { this.#spawnPosition = value }
|
||||||
set x(value) { this.position.x = value }
|
set x(value) { this.position.x = value }
|
||||||
set y(value) { this.position.y = value }
|
set y(value) { this.position.y = value }
|
||||||
@@ -273,7 +285,7 @@ export default class Entity {
|
|||||||
if (targetsInRange.length < 1) { return }
|
if (targetsInRange.length < 1) { return }
|
||||||
|
|
||||||
const absoluteClosestTarget = targetsInRange.reduce((e1, e2) => (e1?.distanceTo(cursor) ?? Infinity) < e2.distanceTo(cursor) ? e1 : e2, null)
|
const absoluteClosestTarget = targetsInRange.reduce((e1, e2) => (e1?.distanceTo(cursor) ?? Infinity) < e2.distanceTo(cursor) ? e1 : e2, null)
|
||||||
const entityIdsInDirectVision = this.entitiesInVision()
|
const entityIdsInDirectVision = this.#entitiesInVision
|
||||||
if (entityIdsInDirectVision.includes(absoluteClosestTarget.id)) {
|
if (entityIdsInDirectVision.includes(absoluteClosestTarget.id)) {
|
||||||
return absoluteClosestTarget
|
return absoluteClosestTarget
|
||||||
}
|
}
|
||||||
@@ -313,17 +325,6 @@ export default class Entity {
|
|||||||
return this.position.distanceTo(cursor)
|
return this.position.distanceTo(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: cache vision because linear per player connected is better than exponential
|
|
||||||
entitiesInVision() {
|
|
||||||
const entities = this.game?.entities
|
|
||||||
if (entities == null) { return }
|
|
||||||
|
|
||||||
const entitiesInVisionRange = entities.filter((it) => it.id != this.id && it.distanceTo(this.position) <= this.visionRange + it.radius)
|
|
||||||
const entitiesInLineOfSight = entitiesInVisionRange.filter((it) => this.isInLineOfVision(it.position))
|
|
||||||
|
|
||||||
return entitiesInLineOfSight.concat([this]).map((it) => it.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
futureCollidables(futurePosition) {
|
futureCollidables(futurePosition) {
|
||||||
return this.customBboxCollidables(new Float32Array([
|
return this.customBboxCollidables(new Float32Array([
|
||||||
futurePosition.y + this.radius,
|
futurePosition.y + this.radius,
|
||||||
@@ -435,16 +436,6 @@ export default class Entity {
|
|||||||
return bboxCheckedObstacles.filter((obstacle) => obstacle.colliders().some((it) => SATX.collideObject(collider, it)))
|
return bboxCheckedObstacles.filter((obstacle) => obstacle.colliders().some((it) => SATX.collideObject(collider, it)))
|
||||||
}
|
}
|
||||||
|
|
||||||
projectilesInVision() {
|
|
||||||
const projectiles = this.game?.projectiles
|
|
||||||
if (projectiles == null) { return }
|
|
||||||
|
|
||||||
const projectilesInVisionRange = projectiles.filter((it) => this.distanceTo(it.position) <= this.visionRange + it.radius)
|
|
||||||
const projectilesInLineOfSight = projectilesInVisionRange.filter((it) => it.visibleThroughTerrain || this.isInLineOfVision(it.position))
|
|
||||||
|
|
||||||
return projectilesInLineOfSight.map((it) => it.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeBuff(id) {
|
removeBuff(id) {
|
||||||
this.buffs = this.buffs.filter((it) => it.id != id)
|
this.buffs = this.buffs.filter((it) => it.id != id)
|
||||||
}
|
}
|
||||||
@@ -484,11 +475,13 @@ export default class Entity {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make non-race-condition calculations multi-threaded
|
||||||
update() {
|
update() {
|
||||||
if (this.dead) {
|
if (this.dead) {
|
||||||
// TODO: do something while the entity is dead (and disallow casting, etc)
|
// TODO: do something while the entity is dead (and disallow casting, vision, etc)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
this.#calculateVision()
|
||||||
this.#cast()
|
this.#cast()
|
||||||
this.#checkHealth()
|
this.#checkHealth()
|
||||||
this.#move()
|
this.#move()
|
||||||
@@ -529,6 +522,20 @@ export default class Entity {
|
|||||||
this.#colliders = [Entity.collider(this.position.x, this.position.y, this.radius)]
|
this.#colliders = [Entity.collider(this.position.x, this.position.y, this.radius)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#calculateVision() {
|
||||||
|
const entities = this.game?.entities ?? []
|
||||||
|
const projectiles = this.game?.projectiles ?? []
|
||||||
|
|
||||||
|
const entitiesInVisionRange = entities.filter((it) => it.id != this.id && it.distanceTo(this.position) <= this.visionRange + it.radius)
|
||||||
|
const entitiesInLineOfSight = entitiesInVisionRange.filter((it) => this.isInLineOfVision(it.position))
|
||||||
|
|
||||||
|
const projectilesInVisionRange = projectiles.filter((it) => this.position.distanceTo(it.position) <= this.visionRange + it.radius)
|
||||||
|
const projectilesInLineOfSight = projectilesInVisionRange.filter((it) => it.visibleThroughTerrain || this.isInLineOfVision(it.position))
|
||||||
|
|
||||||
|
this.#entitiesInVision = entitiesInLineOfSight.concat([this]).map((it) => it.id)
|
||||||
|
this.#projectilesInVision = projectilesInLineOfSight.map((it) => it.id)
|
||||||
|
}
|
||||||
|
|
||||||
#cast() {
|
#cast() {
|
||||||
if (this.casting == null) {
|
if (this.casting == null) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
+36
-33
@@ -5,31 +5,27 @@ import Buff from './buff.js'
|
|||||||
import Entity from './entity.js'
|
import Entity from './entity.js'
|
||||||
import Projectile from './projectile.js'
|
import Projectile from './projectile.js'
|
||||||
import Terrain from './terrain.js'
|
import Terrain from './terrain.js'
|
||||||
|
import { Worker } from 'node:worker_threads'
|
||||||
|
|
||||||
export default class Game {
|
export default class Game {
|
||||||
id = crypto.randomUUID()
|
id = crypto.randomUUID()
|
||||||
|
|
||||||
abilities = Object.values({...Ability})
|
abilities = Object.values({...Ability})
|
||||||
buffs = Object.values({...Buff})
|
buffs = Object.values({...Buff})
|
||||||
averageTick = 0
|
|
||||||
currentTick = 0
|
currentTick = 0
|
||||||
entities = []
|
entities = []
|
||||||
gameLoopIntervalId = null
|
|
||||||
height = 0
|
height = 0
|
||||||
projectiles = []
|
projectiles = []
|
||||||
secondToSlowestTick = 0
|
|
||||||
startTimestamp = 0
|
|
||||||
terrains = []
|
terrains = []
|
||||||
tickRate = 30
|
tickRate = 30
|
||||||
width = 0
|
width = 0
|
||||||
|
|
||||||
#behindMs = 0
|
|
||||||
#currentTiming = 0
|
|
||||||
#eventEmitter = new EventEmitter()
|
#eventEmitter = new EventEmitter()
|
||||||
|
#gameLoopIntervalId = null
|
||||||
#logic = null
|
#logic = null
|
||||||
#nextTickAt = 0
|
#nextTickAt = 0
|
||||||
|
#startTimestamp = 0
|
||||||
#tickBudget = 1000 / this.tickRate
|
#tickBudget = 1000 / this.tickRate
|
||||||
#timings = new Float32Array(this.tickRate)
|
|
||||||
|
|
||||||
get logic() { return this.#logic }
|
get logic() { return this.#logic }
|
||||||
get eventEmitter() { return this.#eventEmitter }
|
get eventEmitter() { return this.#eventEmitter }
|
||||||
@@ -71,6 +67,19 @@ export default class Game {
|
|||||||
projectile.game = null
|
projectile.game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
joinReport() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
height: this.height,
|
||||||
|
width: this.width,
|
||||||
|
currentTick: this.currentTick,
|
||||||
|
abilities: this.abilities,
|
||||||
|
buffs: this.buffs,
|
||||||
|
terrains: this.terrains,
|
||||||
|
tickRate: this.tickRate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removeTerrain(terrain) {
|
removeTerrain(terrain) {
|
||||||
this.terrains = this.terrains.filter((t) => t.id != terrain.id)
|
this.terrains = this.terrains.filter((t) => t.id != terrain.id)
|
||||||
}
|
}
|
||||||
@@ -97,30 +106,37 @@ export default class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (this.gameLoopIntervalId != null) { return }
|
if (this.#gameLoopIntervalId != null) { return }
|
||||||
|
|
||||||
this.startTimestamp = performance.now() + (this.currentTick * this.tickBudget)
|
this.#startTimestamp = performance.now() + (this.currentTick * this.tickBudget)
|
||||||
console.info(`Started game ${this.id} with ${this.tickRate} tps. Starting on tick ${this.currentTick}.`)
|
console.info(`Started game ${this.id} with ${this.tickRate} tps. Starting on tick ${this.currentTick}.`)
|
||||||
this.gameLoopIntervalId = setInterval(this.#gameLoopCall.bind(this), this.tickBudget / 5)
|
this.#gameLoopIntervalId = setInterval(this.#gameLoopCall.bind(this), this.tickBudget / 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (this.gameLoopIntervalId == null) { return }
|
if (this.#gameLoopIntervalId == null) { return }
|
||||||
|
|
||||||
clearInterval(this.gameLoopIntervalId)
|
clearInterval(this.#gameLoopIntervalId)
|
||||||
this.gameLoopIntervalId = null
|
this.#gameLoopIntervalId = null
|
||||||
console.info(`Stopped game ${this.id}. Stopped on tick ${this.currentTick}.`)
|
console.info(`Stopped game ${this.id}. Stopped on tick ${this.currentTick}.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription(websocket, id) {
|
subscription(websocket, id) {
|
||||||
|
const worker = new Worker('./src/worker/object-to-json.js')
|
||||||
|
const sendToWebSocket = function sendToWebSocket(message) { websocket.send(message) } // TODO: latency because workers wait for the main thread's next tick which is ~33ms
|
||||||
|
worker.on('message', sendToWebSocket)
|
||||||
|
|
||||||
return function builtSubscription() {
|
return function builtSubscription() {
|
||||||
const game = this
|
const game = this
|
||||||
|
|
||||||
const entity = game.entities.find((it) => it.id == id)
|
const entity = game.entities.find((it) => it.id == id)
|
||||||
if (entity == null) { return }
|
if (entity == null) { return }
|
||||||
|
|
||||||
const team = entity.team
|
const team = entity.team
|
||||||
const message = game.visionByTeam(team)
|
const state = game.visionByTeam(team)
|
||||||
websocket.send(JSON.stringify(message))
|
state.currentTick = game.currentTick
|
||||||
|
|
||||||
|
worker.postMessage(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +148,6 @@ export default class Game {
|
|||||||
this.#logic()
|
this.#logic()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#calculateTickMetrics()
|
|
||||||
this.eventEmitter.emit('tick')
|
this.eventEmitter.emit('tick')
|
||||||
|
|
||||||
this.currentTick++
|
this.currentTick++
|
||||||
@@ -140,12 +155,12 @@ export default class Game {
|
|||||||
|
|
||||||
visibleEntities(team) {
|
visibleEntities(team) {
|
||||||
const visionSources = this.visionSources(team)
|
const visionSources = this.visionSources(team)
|
||||||
return Array.from(new Set(visionSources.map((it) => it.entitiesInVision()).flat()))
|
return Array.from(new Set(visionSources.map((it) => it.entitiesInVision).flat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
visibleProjectiles(team) {
|
visibleProjectiles(team) {
|
||||||
const visionSources = this.visionSources(team)
|
const visionSources = this.visionSources(team)
|
||||||
return Array.from(new Set(visionSources.map((it) => it.projectilesInVision()).flat()))
|
return Array.from(new Set(visionSources.map((it) => it.projectilesInVision).flat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
visionSources(team) {
|
visionSources(team) {
|
||||||
@@ -156,20 +171,14 @@ export default class Game {
|
|||||||
|
|
||||||
visionByTeam(team) {
|
visionByTeam(team) {
|
||||||
const visionSources = this.visionSources(team)
|
const visionSources = this.visionSources(team)
|
||||||
const visibleEntities = new Set(visionSources.map((it) => it.entitiesInVision()).flat())
|
const visibleEntities = new Set(visionSources.map((it) => it.entitiesInVision).flat())
|
||||||
const visibleProjectiles = new Set(visionSources.map((it) => it.projectilesInVision()).flat())
|
const visibleProjectiles = new Set(visionSources.map((it) => it.projectilesInVision).flat())
|
||||||
return {
|
return {
|
||||||
...this,
|
|
||||||
entities: this.entities.filter((it) => visibleEntities.has(it.id)),
|
entities: this.entities.filter((it) => visibleEntities.has(it.id)),
|
||||||
projectiles: this.projectiles.filter((it) => visibleProjectiles.has(it.id)),
|
projectiles: this.projectiles.filter((it) => visibleProjectiles.has(it.id)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#calculateTickMetrics() {
|
|
||||||
this.averageTick = Math.floor(10 * this.#timings.reduce((sum, t) => sum += t, 0) / this.#timings.length) / 10
|
|
||||||
this.secondToSlowestTick = Math.floor(10 * this.#timings.toSorted().at(-2)) / 10
|
|
||||||
}
|
|
||||||
|
|
||||||
#gameLoop() {
|
#gameLoop() {
|
||||||
if (this.#nextTickAt != null) {
|
if (this.#nextTickAt != null) {
|
||||||
const tickBudget = this.#tickBudget
|
const tickBudget = this.#tickBudget
|
||||||
@@ -185,15 +194,9 @@ export default class Game {
|
|||||||
const taken = (after - before)
|
const taken = (after - before)
|
||||||
|
|
||||||
const useAbsoluteBehind = true
|
const useAbsoluteBehind = true
|
||||||
const absoluteBehind = Math.max(0, (start - this.startTimestamp) - ((this.currentTick) * tickBudget))
|
const absoluteBehind = Math.max(0, (start - this.#startTimestamp) - ((this.currentTick) * tickBudget))
|
||||||
this.#behindMs = absoluteBehind
|
|
||||||
this.#nextTickAt = start + tickBudget - (useAbsoluteBehind ? absoluteBehind : prevBehind)
|
this.#nextTickAt = start + tickBudget - (useAbsoluteBehind ? absoluteBehind : prevBehind)
|
||||||
|
|
||||||
this.#timings[this.#currentTiming] = taken
|
|
||||||
if (this.#currentTiming++ > this.#timings.length) {
|
|
||||||
this.#currentTiming = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after - before > tickBudget) {
|
if (after - before > tickBudget) {
|
||||||
const behindNotice = absoluteBehind > 0.1 ? `(Was already behind ${absoluteBehind.toFixed(1)} ms)` : ``
|
const behindNotice = absoluteBehind > 0.1 ? `(Was already behind ${absoluteBehind.toFixed(1)} ms)` : ``
|
||||||
console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms / ${tickBudget.toFixed(1)} ms. ${behindNotice}`)
|
console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms / ${tickBudget.toFixed(1)} ms. ${behindNotice}`)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ app.ws('/ws', async (req, res) => {
|
|||||||
const message = JSON.parse(rawData)
|
const message = JSON.parse(rawData)
|
||||||
console.log(message)
|
console.log(message)
|
||||||
if (message.action == 'join') {
|
if (message.action == 'join') {
|
||||||
|
websocket.send(JSON.stringify(game.joinReport()))
|
||||||
const subscription = game.subscription(websocket, message.id).bind(game)
|
const subscription = game.subscription(websocket, message.id).bind(game)
|
||||||
game.eventEmitter.on('tick', subscription)
|
game.eventEmitter.on('tick', subscription)
|
||||||
|
|
||||||
|
|||||||
+23
-23
@@ -8,9 +8,7 @@ export default class Projectile {
|
|||||||
static nextId() { return this.#nextUniqueId++ }
|
static nextId() { return this.#nextUniqueId++ }
|
||||||
static #nextUniqueId = 0
|
static #nextUniqueId = 0
|
||||||
|
|
||||||
bbox = new Float32Array(4)
|
|
||||||
height = 50
|
height = 50
|
||||||
memory = {}
|
|
||||||
owner = null
|
owner = null
|
||||||
position = new Vector2()
|
position = new Vector2()
|
||||||
radius = 0
|
radius = 0
|
||||||
@@ -21,19 +19,26 @@ export default class Projectile {
|
|||||||
visualRadius = null
|
visualRadius = null
|
||||||
|
|
||||||
#after = null
|
#after = null
|
||||||
|
#bbox = new Float32Array(4)
|
||||||
#dest = null
|
#dest = null
|
||||||
|
#entitiesInVision = []
|
||||||
|
#game = null
|
||||||
#homingTarget = null
|
#homingTarget = null
|
||||||
#logic = null
|
#logic = null
|
||||||
#onCollide = null
|
#onCollide = null
|
||||||
#game = null
|
#projectilesInVision = []
|
||||||
|
|
||||||
get after() { return this.#after }
|
get after() { return this.#after }
|
||||||
|
get bbox() { return this.#bbox }
|
||||||
|
get entitiesInVision() { return this.#entitiesInVision }
|
||||||
get game() { return this.#game }
|
get game() { return this.#game }
|
||||||
get homingTarget() { return this.#homingTarget }
|
get homingTarget() { return this.#homingTarget }
|
||||||
get logic() { return this.#logic }
|
get logic() { return this.#logic }
|
||||||
get onCollide() { return this.#onCollide }
|
get onCollide() { return this.#onCollide }
|
||||||
|
get projectilesInVision() { return this.#projectilesInVision }
|
||||||
|
|
||||||
set after(value) { this.#after = value }
|
set after(value) { this.#after = value }
|
||||||
|
set bbox(value) { this.#bbox = value }
|
||||||
set destination(value) { this.#dest = value }
|
set destination(value) { this.#dest = value }
|
||||||
set game(value) { this.#game = value }
|
set game(value) { this.#game = value }
|
||||||
set homingTarget(value) { this.#homingTarget = value }
|
set homingTarget(value) { this.#homingTarget = value }
|
||||||
@@ -59,16 +64,6 @@ export default class Projectile {
|
|||||||
this.game?.despawn(this)
|
this.game?.despawn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
entitiesInVision() {
|
|
||||||
const entities = this.game?.entities
|
|
||||||
if (entities == null) { return }
|
|
||||||
|
|
||||||
const entitiesInVisionRange = entities.filter((it) => it.id != this.id && it.distanceTo(this.position) <= this.visionRange + it.radius)
|
|
||||||
const entitiesInLineOfSight = entitiesInVisionRange.filter((it) => this.isInLineOfVision(it.position))
|
|
||||||
|
|
||||||
return entitiesInLineOfSight.concat([this]).map((it) => it.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
isInLineOfVision(destination) {
|
isInLineOfVision(destination) {
|
||||||
const bbox = Entity.tunnelBbox(this.position.x, this.position.y, destination.x, destination.y, 0)
|
const bbox = Entity.tunnelBbox(this.position.x, this.position.y, destination.x, destination.y, 0)
|
||||||
const terrains = this.game?.terrains ?? []
|
const terrains = this.game?.terrains ?? []
|
||||||
@@ -90,6 +85,7 @@ export default class Projectile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
this.#calculateVision()
|
||||||
this.#move()
|
this.#move()
|
||||||
this.#checkStationaryCollisions()
|
this.#checkStationaryCollisions()
|
||||||
this.#checkIfArrived()
|
this.#checkIfArrived()
|
||||||
@@ -98,16 +94,6 @@ export default class Projectile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
projectilesInVision() {
|
|
||||||
const projectiles = this.game?.projectiles
|
|
||||||
if (projectiles == null) { return }
|
|
||||||
|
|
||||||
const projectilesInVisionRange = projectiles.filter((it) => this.position.distanceTo(it.position) <= this.visionRange + it.radius)
|
|
||||||
const projectilesInLineOfSight = projectilesInVisionRange.filter((it) => it.visibleThroughTerrain || this.isInLineOfVision(it.position))
|
|
||||||
|
|
||||||
return projectilesInLineOfSight.concat([this]).map((it) => it.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#calculateBbox() {
|
#calculateBbox() {
|
||||||
this.bbox[0] = this.position.y + this.radius
|
this.bbox[0] = this.position.y + this.radius
|
||||||
this.bbox[1] = this.position.x + this.radius
|
this.bbox[1] = this.position.x + this.radius
|
||||||
@@ -115,6 +101,20 @@ export default class Projectile {
|
|||||||
this.bbox[3] = this.position.x - this.radius
|
this.bbox[3] = this.position.x - this.radius
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#calculateVision() {
|
||||||
|
const entities = this.game?.entities ?? []
|
||||||
|
const projectiles = this.game?.projectiles ?? []
|
||||||
|
|
||||||
|
const entitiesInVisionRange = entities.filter((it) => it.id != this.id && it.distanceTo(this.position) <= this.visionRange + it.radius)
|
||||||
|
const entitiesInLineOfSight = entitiesInVisionRange.filter((it) => this.isInLineOfVision(it.position))
|
||||||
|
|
||||||
|
const projectilesInVisionRange = projectiles.filter((it) => this.position.distanceTo(it.position) <= this.visionRange + it.radius)
|
||||||
|
const projectilesInLineOfSight = projectilesInVisionRange.filter((it) => it.visibleThroughTerrain || this.isInLineOfVision(it.position))
|
||||||
|
|
||||||
|
this.#entitiesInVision = entitiesInLineOfSight.concat([this]).map((it) => it.id)
|
||||||
|
this.#projectilesInVision = projectilesInLineOfSight.map((it) => it.id)
|
||||||
|
}
|
||||||
|
|
||||||
#checkIfArrived() {
|
#checkIfArrived() {
|
||||||
if (this.destination == null) { return }
|
if (this.destination == null) { return }
|
||||||
if (!this.position.equals(this.destination)) { return }
|
if (!this.position.equals(this.destination)) { return }
|
||||||
|
|||||||
+3
-2
@@ -44,6 +44,7 @@ export default class Template {
|
|||||||
const checkpointSize = 300
|
const checkpointSize = 300
|
||||||
const recalculateDestRadius = 50
|
const recalculateDestRadius = 50
|
||||||
const aggroRadius = 500
|
const aggroRadius = 500
|
||||||
|
const memory = {}
|
||||||
|
|
||||||
return function builtMinionLogic() {
|
return function builtMinionLogic() {
|
||||||
const entity = this
|
const entity = this
|
||||||
@@ -62,12 +63,12 @@ export default class Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((route.length > 0 || entity.attacking) && target == null) {
|
if ((route.length > 0 || entity.attacking) && target == null) {
|
||||||
const routeIndex = entity.memory.routeCheckpoint ?? 0
|
const routeIndex = memory.routeCheckpoint ?? 0
|
||||||
const goal = route[routeIndex].clone()
|
const goal = route[routeIndex].clone()
|
||||||
if (goal instanceof Vector2) {
|
if (goal instanceof Vector2) {
|
||||||
if (entity.distanceTo(goal) < checkpointSize) {
|
if (entity.distanceTo(goal) < checkpointSize) {
|
||||||
if (routeIndex + 1 < route.length) {
|
if (routeIndex + 1 < route.length) {
|
||||||
entity.memory.routeCheckpoint = routeIndex + 1
|
memory.routeCheckpoint = routeIndex + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { parentPort } from 'node:worker_threads'
|
||||||
|
|
||||||
|
parentPort.on('message', (message) => {
|
||||||
|
parentPort.postMessage(JSON.stringify(message))
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user