diff --git a/src/ability.js b/src/ability.js index c264324..d8828f2 100644 --- a/src/ability.js +++ b/src/ability.js @@ -38,7 +38,8 @@ export default class Ability { const ability = this const straightShotCollision = function straightShotCollision(projectile, collidingEntity) { if (collidingEntity == null) { return } - if (collidingEntity.team == (projectile.owner?.team ?? 'unknown')) { return } + if (collidingEntity.id == caster.id) { return } + if (collidingEntity.team == (caster.team ?? 'unknown')) { return } collidingEntity.damage(ability.damage, caster) projectile.despawn() @@ -181,7 +182,8 @@ export default class Ability { const ability = this const exposeCollision = function exposeCollision(projectile, collidingEntity) { if (collidingEntity == null) { return } - if (collidingEntity.team == (projectile.owner?.team ?? 'unknown')) { return } + if (collidingEntity.team == caster.id) { return } + if (collidingEntity.team == (caster.team ?? 'unknown')) { return } collidingEntity.applyBuff(Buff.exposed.id, caster.id) projectile.despawn() diff --git a/src/entity.js b/src/entity.js index b4fb90b..d636bc4 100644 --- a/src/entity.js +++ b/src/entity.js @@ -25,6 +25,7 @@ export default class Entity { visualRadius = null #attacking = false + #colliders = [] #dest = null #game = null #logic = null @@ -123,6 +124,8 @@ export default class Entity { if (this.visualRadius == null) { this.visualRadius = this.radius } + + this.#calculateCollider() } get attacking() { return this.#attacking } @@ -256,11 +259,11 @@ export default class Entity { } collider() { - return Entity.collider(this.position.x, this.position.y, this.radius) + return this.#colliders.at(0) } colliders() { - return [this.collider()] + return this.#colliders } cooldown(id) { @@ -329,7 +332,10 @@ export default class Entity { } fixPosition() { - this.position = this.fixFuturePosition(this.position.clone()).clone() + const fixedPosition = this.fixFuturePosition(this.position) + if (this.position.equals(fixedPosition)) { return } + + this.setPosition(fixedPosition) } fixFuturePosition(futurePosition) { @@ -400,14 +406,18 @@ export default class Entity { } respawn() { - this.position = this.#spawnPosition.clone() + this.setPosition(this.#spawnPosition) this.health = this.maxHealth this.dead = false } + setPosition(vector) { + this.position.copy(vector) + this.#calculateCollider() + } + teleport(cursor) { - this.position = cursor.clone() - this.fixPosition() + this.setPosition(this.fixFuturePosition(cursor)) } update() { @@ -425,8 +435,6 @@ export default class Entity { if (this.#logic != null) { this.#logic() } - - this.#calculateBbox() } waypoints() { @@ -456,6 +464,11 @@ export default class Entity { this.bbox[3] = this.position.x - this.radius } + #calculateCollider() { + this.#calculateBbox() + this.#colliders = [Entity.collider(this.position.x, this.position.y, this.radius)] + } + #cast() { if (this.casting == null) { return false @@ -574,7 +587,7 @@ export default class Entity { this.rotation = rotation if (!this.willCollide(position)) { - this.position.copy(position) + this.setPosition(position) } if (this.position.equals(destination)) { diff --git a/src/game.js b/src/game.js index 6227ab7..6e22c2b 100644 --- a/src/game.js +++ b/src/game.js @@ -124,7 +124,7 @@ export default class Game { } if (after - before > tickBudget) { - console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms (Budget: ${tickBudget.toFixed(1)} ms)`) + console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms. Entity count: ${this.entities.length}`) } } } diff --git a/src/index.js b/src/index.js index 3c8037e..acbd9e0 100644 --- a/src/index.js +++ b/src/index.js @@ -125,25 +125,28 @@ function laneScenario() { new Vector2(9136, 8248), ].map((p) => p.multiplyScalar(1.6)) - 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 }))) + if (game.entities.length < 100) { + if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (6 * 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 }))) - game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: topRoute.toReversed() }))) - game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: midRoute.toReversed() }))) - game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: botRoute.toReversed() }))) + game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: topRoute.toReversed() }))) + game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: midRoute.toReversed() }))) + 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))) { + 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 }))) + + game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: topRoute.toReversed() }))) + game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: midRoute.toReversed() }))) + game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: botRoute.toReversed() }))) + } } - 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 }))) - - game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: topRoute.toReversed() }))) - game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: midRoute.toReversed() }))) - game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: botRoute.toReversed() }))) - } } game.logic = gameLogic } diff --git a/src/projectile.js b/src/projectile.js index 191d877..ae16ce9 100644 --- a/src/projectile.js +++ b/src/projectile.js @@ -36,17 +36,6 @@ export default class Projectile { } } - checkCollisions(collider) { - (this.game?.entities ?? []).filter((e) => e.id != this.id).forEach((e) => { - if (this.game == null) { return } - if (e.id == this.owner?.id) { return } - - if (SATX.collideObject(collider, e.collider())) { - if (this.onCollide != null) { this.onCollide(this, e) } - } - }) - } - collider() { return new SAT.Circle(new SAT.Vector(this.position.x, this.position.y), this.radius) } @@ -55,10 +44,14 @@ export default class Projectile { this.game?.despawn(this) } + setPosition(vector) { + this.position.copy(vector) + this.#calculateBbox() + } + update() { this.#move() - this.#calculateBbox() - if (this.onCollide != null) { this.checkCollisions(this.collider()) } + this.#checkStationaryCollisions() this.#checkIfArrived() } @@ -80,21 +73,41 @@ export default class Projectile { this.despawn() } + #checkStationaryCollisions() { + if (this.onCollide == null) { return } + + const bbox = this.bbox + 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 = this.collider() + colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.onCollide(this, it)) + } + } + #move() { if (this.destination == null) { return } const speed = (this.speed / (this.game?.tickBudget ?? 1000)) const prevPos = this.position.clone() if (this.position.distanceTo(this.destination) < speed) { - this.position.copy(this.destination) + this.setPosition(this.destination) } else { const step = this.destination.clone().sub(this.position).normalize().multiplyScalar(speed) this.position.add(step) } - // TODO: decouple from entity's tunnel collider - const tunnel = Entity.tunnelCollider(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius) - if (this.onCollide != null) { this.checkCollisions(tunnel) } + 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)) + } + } } }