fix projectiles colliding with dead entities

This commit is contained in:
2025-01-23 10:36:28 +09:00
parent afa419e77a
commit 4acd7a2881
4 changed files with 52 additions and 29 deletions
+34 -19
View File
@@ -217,47 +217,62 @@ export default class Ability {
name: 'Shield Throw', name: 'Shield Throw',
castTime: 0.25, castTime: 0.25,
cooldown: 5, cooldown: 5,
damage: 90,
deceleratePerTick: 90, deceleratePerTick: 90,
radius: 110, radius: 110,
range: 1025, range: 1025,
speed: 2400, speed: 2400,
effect: function shieldThrowEffect(caster, cursor) { effect: function shieldThrowEffect(caster, cursor) {
const ability = this const ability = this
const amount = ability.damage
let onTheWayBack = false
let collided = new Set()
const accelerateLogic = function accelerateLogic() { const shieldThrowCollision = function shieldThrowCollision(projectile, collidingEntity) {
const projectile = this if (collidingEntity == null) { return }
projectile.speed += ability.deceleratePerTick if (collidingEntity.id == caster.id) { return }
if (caster.team == null || collidingEntity.team == null || collidingEntity.team != caster.team) { return }
const entityId = collidingEntity.id
if (!collided.has(entityId)) {
collidingEntity.heal(amount, caster)
collided.add(entityId)
}
} }
const decelerateLogic = function decelerateLogic() { const accelerateLogic = function accelerateLogic(projectile) {
const projectile = this if (onTheWayBack) {
projectile.speed += ability.deceleratePerTick
}
else {
if (projectile.speed - ability.deceleratePerTick >= ability.deceleratePerTick) { if (projectile.speed - ability.deceleratePerTick >= ability.deceleratePerTick) {
projectile.speed = projectile.speed - ability.deceleratePerTick projectile.speed = projectile.speed - ability.deceleratePerTick
} }
} }
}
const shieldThrowReturn = function shieldThrowReturn(projectile, homingTarget) { const shieldThrowSecondAfter = function shieldThrowSecondAfter(projectile, homingTarget) {
const returnProjectile = new Projectile({ caster.heal(amount, caster)
homingTarget: caster, caster.heal(amount, caster) // NOTE: duplicated on purpose
logic: accelerateLogic, }
owner: caster.id,
position: projectile.position.clone(),
radius: ability.radius,
speed: ability.deceleratePerTick,
visionRange: ability.radius,
})
caster.game?.spawnProjectile(returnProjectile) const shieldThrowFirstAfter = function shieldThrowFirstAfter(projectile, homingTarget) {
projectile.destination = null
projectile.homingTarget = caster
onTheWayBack = true
collided.clear()
projectile.after = shieldThrowSecondAfter
} }
const projectile = new Projectile({ const projectile = new Projectile({
after: shieldThrowReturn, after: shieldThrowFirstAfter,
logic: decelerateLogic, logic: accelerateLogic,
onCollide: shieldThrowCollision,
owner: caster.id, owner: caster.id,
position: caster.position.clone(), position: caster.position.clone(),
radius: ability.radius, radius: ability.radius,
speed: ability.speed, speed: ability.speed,
visionRange: ability.radius, visionRange: ability.radius * 1.5,
}) })
projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range + caster.radius)) projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range + caster.radius))
+8 -3
View File
@@ -177,6 +177,7 @@ export default class Entity {
this.moveAction(cursor, true) this.moveAction(cursor, true)
} }
// TODO: buffer skill inputs
castAction(slot, cursor, halt = false) { castAction(slot, cursor, halt = false) {
if (this.dead) { return } if (this.dead) { return }
@@ -295,10 +296,13 @@ export default class Entity {
this.cooldowns[id] = this.game?.currentTick ?? 0 this.cooldowns[id] = this.game?.currentTick ?? 0
} }
closestTargetTo(cursor, range) { closestTargetTo(cursor, range, targetAllies = false) {
const entities = this.game?.entities const entities = this.game?.entities
if (entities == null) { return } if (entities == null) { return }
const targetsInRange = entities.filter((it) => this.team != it.team && it.distanceTo(cursor) <= range + this.radius + it.radius) const targetsInRange = targetAllies
? entities.filter((it) => this.team == it.team && it.distanceTo(cursor) <= range + this.radius + it.radius)
: entities.filter((it) => this.team != it.team && it.distanceTo(cursor) <= range + this.radius + it.radius)
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)
@@ -321,6 +325,7 @@ export default class Entity {
return entitiesAndTerrains.filter((it) => !it.dead && it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox)) return entitiesAndTerrains.filter((it) => !it.dead && it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
} }
// TODO: add shielding logic
damage(amount, source = null) { damage(amount, source = null) {
if (this.dead) { return } if (this.dead) { return }
@@ -399,7 +404,7 @@ export default class Entity {
return this.buffs.some((it) => it.id == id) && this.game?.buffs.some((it) => it.id == id) return this.buffs.some((it) => it.id == id) && this.game?.buffs.some((it) => it.id == id)
} }
heal(amount) { heal(amount, _source) {
if (this.dead) { return } if (this.dead) { return }
this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth) this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth)
+2 -2
View File
@@ -22,8 +22,8 @@ export class Dungeon {
game.addTerrain(new Terrain([new Vector2(2000, 1500), new Vector2(2500, 1000), new Vector2(2500, 1500)], false)) game.addTerrain(new Terrain([new Vector2(2000, 1500), new Vector2(2500, 1000), new Vector2(2500, 1500)], false))
game.spawnEntity(new Entity(Template.player({ id: '6', spawnPosition: new Vector2(2400, 1400), team: enemy, logic: castQ }))) game.spawnEntity(new Entity(Template.player({ id: '6', spawnPosition: new Vector2(2400, 1400), team: enemy, logic: castQ })))
game.spawnEntity(new Entity(Template.player({ id: '1', spawnPosition: new Vector2(1500, 700), team, dead: true }))) game.spawnEntity(new Entity(Template.player({ id: '1', spawnPosition: new Vector2(200, 1300), team, health: 10 })))
game.spawnEntity(new Entity(Template.player({ id: '2', spawnPosition: new Vector2(200, 1300), team, health: 10 }))) game.spawnEntity(new Entity(Template.player({ id: '2', spawnPosition: new Vector2(1500, 700), team, dead: true })))
game.spawnEntity(new Entity(Template.basilisk({ id: 'boss', spawnPosition: new Vector2(2200, 750), team: Team.neutral }))) game.spawnEntity(new Entity(Template.basilisk({ id: 'boss', spawnPosition: new Vector2(2200, 750), team: Team.neutral })))
+5 -2
View File
@@ -123,6 +123,9 @@ export default class Projectile {
this.#after(this, this.#homingTarget) this.#after(this, this.#homingTarget)
} }
if (this.destination == null) { return }
if (!this.position.equals(this.destination)) { return }
this.despawn() this.despawn()
} }
@@ -131,7 +134,7 @@ export default class Projectile {
const bbox = this.bbox const bbox = this.bbox
const entitiesAndTerrains = this.game?.entities ?? [] const entitiesAndTerrains = this.game?.entities ?? []
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => SATX.bboxCheck(bbox, it.bbox)) const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => !it.dead && SATX.bboxCheck(bbox, it.bbox))
if (bboxCheckedObstacles.length > 0) { if (bboxCheckedObstacles.length > 0) {
const collider = this.collider() const collider = this.collider()
const colliding = bboxCheckedObstacles.filter((it) => it.colliders().some((c) => SATX.collideObject(collider, c))) const colliding = bboxCheckedObstacles.filter((it) => it.colliders().some((c) => SATX.collideObject(collider, c)))
@@ -155,7 +158,7 @@ export default class Projectile {
if (this.#onCollide != null) { if (this.#onCollide != null) {
const bbox = Entity.tunnelBbox(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius) const bbox = Entity.tunnelBbox(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius)
const entitiesAndTerrains = this.game?.entities ?? [] const entitiesAndTerrains = this.game?.entities ?? []
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => SATX.bboxCheck(bbox, it.bbox)) const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => !it.dead && SATX.bboxCheck(bbox, it.bbox))
if (bboxCheckedObstacles.length > 0) { if (bboxCheckedObstacles.length > 0) {
const collider = Entity.tunnelCollider(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius) const collider = Entity.tunnelCollider(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius)
const colliding = bboxCheckedObstacles.filter((it) => it.colliders().some((c) => SATX.collideObject(collider, c))) const colliding = bboxCheckedObstacles.filter((it) => it.colliders().some((c) => SATX.collideObject(collider, c)))