diff --git a/public/client.js b/public/client.js index eea3868..f528885 100644 --- a/public/client.js +++ b/public/client.js @@ -423,6 +423,7 @@ function connectWebSocket() { const bbox = new THREE.Mesh(new THREE.BoxGeometry(width, height, 0.2), bboxMaterial) bbox.position.set((bboxValues[3] / 100) + (width / 2), (bboxValues[2] / 100) + (height / 2), 0) + bbox.layers.set(1) scene.add(bbox) } } diff --git a/src/ability.js b/src/ability.js index d8828f2..894e973 100644 --- a/src/ability.js +++ b/src/ability.js @@ -47,7 +47,7 @@ export default class Ability { const projectile = new Projectile({ onCollide: straightShotCollision, - owner: caster, + owner: caster.id, position: caster.position.clone(), radius: ability.radius, speed: ability.speed, @@ -82,7 +82,7 @@ export default class Ability { const projectile = new Projectile({ after: rangedAttackAfter, homingTarget: target, - owner: caster, + owner: caster.id, position: caster.position.clone(), radius: ability.radius, speed: ability.speed, @@ -124,7 +124,7 @@ export default class Ability { const ability = this const shieldThrowReturn = function shieldThrowReturn(projectile, homingTarget) { const returnProjectile = new Projectile({ - owner: caster, + owner: caster.id, position: projectile.position.clone(), radius: ability.radius, speed: ability.speed, @@ -136,7 +136,7 @@ export default class Ability { const projectile = new Projectile({ after: shieldThrowReturn, - owner: caster, + owner: caster.id, position: caster.position.clone(), radius: ability.radius, speed: ability.speed, @@ -191,7 +191,7 @@ export default class Ability { const projectile = new Projectile({ onCollide: exposeCollision, - owner: caster, + owner: caster.id, position: caster.position.clone(), radius: ability.radius, speed: ability.speed, diff --git a/src/entity.js b/src/entity.js index d636bc4..20b2a65 100644 --- a/src/entity.js +++ b/src/entity.js @@ -16,7 +16,8 @@ export default class Entity { health = null height = 40 maxHealth = 1 - memory = {} // TODO: hide from reports but keep public + memory = {} + pathfindingCooldown = 0 position = null radius = 0 rotation = 0 @@ -31,6 +32,7 @@ export default class Entity { #logic = null #moving = false #path = [] + #noPathfindingUntil = 0 #spawnPosition = new Vector2() static collider(x, y, radius) { @@ -495,6 +497,7 @@ export default class Entity { #move(distanceTraveled = 0) { if (this.casting != null) { return false } + const currentTick = this.game?.currentTick ?? 0 if (this.#attacking) { const cursor = this.#dest ?? this.position @@ -504,8 +507,7 @@ export default class Entity { if (target != null && this.distanceTo(target.position) < basicAttack.range + this.radius + target.radius) { const cooldown = this.game?.secToTick(basicAttack.cooldown) ?? 0 const lastCast = this.cooldowns[basicAttack.id] - const timestamp = this.game?.currentTick ?? 0 - if (lastCast != null && lastCast + cooldown > timestamp) { return false } + if (lastCast != null && lastCast + cooldown > currentTick) { return false } this.castAction('a', target.id, false) return true @@ -516,8 +518,9 @@ export default class Entity { if (!this.#moving || this.#dest == null) { return false } const fixedDest = this.fixFuturePosition(this.#dest) + const pathfinding = this.#noPathfindingUntil <= currentTick - if (this.#path.length > 0) { + if (pathfinding && this.#path.length > 0) { const sectionDest = this.#path.at(0) const lineOfSight = this.isInLineOfSight(sectionDest) if (!lineOfSight) { @@ -532,7 +535,7 @@ export default class Entity { } } - if ((this.#path.length < 1 || (this.#path.at(-1)?.distanceTo(fixedDest) ?? 0) > 0.01)) { + if (pathfinding && (this.#path.length < 1 || (this.#path.at(-1)?.distanceTo(fixedDest) ?? 0) > 0.01)) { const start = SATX.vectorToFloat32Array(this.position) const goal = SATX.vectorToFloat32Array(fixedDest) const obstacles = [] @@ -574,6 +577,10 @@ export default class Entity { } } + if (pathfinding && this.pathfindingCooldown > 0) { + this.#noPathfindingUntil = currentTick + (this.game?.secToTick(this.pathfindingCooldown) ?? 0) + } + if (this.#path.length > 0) { const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled const destination = this.#path.at(0) diff --git a/src/game.js b/src/game.js index 6e22c2b..dba85ab 100644 --- a/src/game.js +++ b/src/game.js @@ -18,6 +18,7 @@ export default class Game { tickRate = 30 width = 10000 * 1.6 + #behindMs = 0 #currentTiming = 0 #eventEmitter = new EventEmitter() #logic = null @@ -103,9 +104,8 @@ export default class Game { } #gameLoop() { - const tickBudget = this.#tickBudget - if (this.#nextTickAt != null) { + const tickBudget = this.#tickBudget const nextTickAt = this.#nextTickAt this.#nextTickAt = null @@ -114,9 +114,11 @@ export default class Game { const before = performance.now() this.update() - this.#nextTickAt = start + tickBudget const after = performance.now() const taken = (after - before) + const prevBehind = this.#behindMs + this.#behindMs = Math.max(0, this.#behindMs + taken - tickBudget) + this.#nextTickAt = start + tickBudget - prevBehind this.#timings[this.#currentTiming] = taken if (this.#currentTiming++ > this.#timings.length) { @@ -124,7 +126,7 @@ export default class Game { } if (after - before > tickBudget) { - console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms. Entity count: ${this.entities.length}`) + console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms / ${tickBudget} ms. (Behind ${this.#behindMs} ms)`) } } } diff --git a/src/index.js b/src/index.js index acbd9e0..9f05838 100644 --- a/src/index.js +++ b/src/index.js @@ -126,7 +126,7 @@ function laneScenario() { ].map((p) => p.multiplyScalar(1.6)) if (game.entities.length < 100) { - if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (6 * game.tickRate))) { + if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) { game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: topRoute }))) game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: midRoute }))) game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: botRoute }))) @@ -136,7 +136,7 @@ function laneScenario() { game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: botRoute.toReversed() }))) } - if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (6 * game.tickRate))) { + if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) { game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: topRoute }))) game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: midRoute }))) game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: botRoute }))) diff --git a/src/projectile.js b/src/projectile.js index ae16ce9..8107c49 100644 --- a/src/projectile.js +++ b/src/projectile.js @@ -5,25 +5,35 @@ import Entity from './entity.js' export default class Projectile { id = crypto.randomUUID() - after = null // TODO: hide from reports but keep public + bbox = new Float32Array(4) height = 50 - memory = {} // TODO: hide from reports but keep public - onCollide = null // TODO: hide from reports but keep public - owner = null // TODO: only keep an ID + memory = {} + owner = null position = new Vector2() radius = 5 speed = 1000 visualRadius = null + #after = null #dest = null #homingTarget = null + #logic = null + #onCollide = null #game = null + get after() { return this.#after } get game() { return this.#game } - set game(value) { this.#game = value } + get homingTarget() { return this.#homingTarget } + get logic() { return this.#logic } + get onCollide() { return this.#onCollide } + + set after(value) { this.#after = value } set destination(value) { this.#dest = value } + set game(value) { this.#game = value } set homingTarget(value) { this.#homingTarget = value } + set logic(value) { this.#logic = value } + set onCollide(value) { this.#onCollide = value } get destination() { return this.#dest ?? this.#homingTarget?.position @@ -53,6 +63,9 @@ export default class Projectile { this.#move() this.#checkStationaryCollisions() this.#checkIfArrived() + if (this.#logic != null) { + this.#logic() + } } #calculateBbox() { @@ -66,15 +79,15 @@ export default class Projectile { if (this.destination == null) { return } if (!this.position.equals(this.destination)) { return } - if (this.after != null) { - this.after(this, this.#homingTarget) + if (this.#after != null) { + this.#after(this, this.#homingTarget) } this.despawn() } #checkStationaryCollisions() { - if (this.onCollide == null) { return } + if (this.#onCollide == null) { return } const bbox = this.bbox const entitiesAndTerrains = this.game?.entities ?? [] @@ -82,7 +95,7 @@ export default class Projectile { if (bboxCheckedObstacles.length > 0) { const colliders = bboxCheckedObstacles.map((it) => it.colliders()).flat() const collider = this.collider() - colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.onCollide(this, it)) + colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.#onCollide(this, it)) } } @@ -99,14 +112,14 @@ export default class Projectile { this.position.add(step) } - if (this.onCollide != null) { + if (this.#onCollide != null) { const bbox = Entity.tunnelBbox(position.x, position.y, destination.x, destination.y, this.radius) const entitiesAndTerrains = this.game?.entities ?? [] const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => SATX.bboxCheck(bbox, it.bbox)) if (bboxCheckedObstacles.length > 0) { const colliders = bboxCheckedObstacles.map((it) => it.colliders()).flat() const collider = Entity.tunnelCollider(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius) - colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.onCollide(this, it)) + colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.#onCollide(this, it)) } } } diff --git a/src/template.js b/src/template.js index ba344db..5e2926e 100644 --- a/src/template.js +++ b/src/template.js @@ -8,6 +8,7 @@ export default class Template { height: options.ranged ? 40 : 38, logic: this.#minionLogic(options.route), maxHealth: options.ranged ? 300 : 450, + pathfindingCooldown: 0.2, position: options.route?.at(0) ?? new Vector2(0, 0), radius: 48, speed: 325,