add dead state

This commit is contained in:
2025-01-13 14:08:10 +09:00
parent 03bbea4862
commit 16429a6e1b
4 changed files with 139 additions and 116 deletions
+116 -73
View File
@@ -7,11 +7,6 @@ import Team from './team.js'
export default class Entity {
id = crypto.randomUUID()
speed = 400
radius = 0
health = 1
maxHealth = 1
height = 40
abilities = [
Ability.rangedAttack,
Ability.straightShot,
@@ -19,18 +14,24 @@ export default class Entity {
Ability.blink,
]
casting = null
cooldowns = {}
dead = false
health = null
height = 40
maxHealth = 1
radius = 0
speed = 400
team = Team.neutral
cooldowns = {}
#attack = false
#attacking = false
#dest = null
#game = null
#logic = null
#move = false
#moving = false
#path = []
#position = new Vector2()
#position = null
#scheduledPathfinding = null
#spawnPosition = new Vector2()
static collider(x, y, radius) {
return new SAT.Circle(new SAT.Vector(x, y), radius)
@@ -38,6 +39,12 @@ export default class Entity {
constructor(options = {}) {
Object.entries(options).forEach(([key, value]) => this[key] = value)
if (this.#position == null) {
this.#position = this.#spawnPosition.clone()
}
if (this.health == null) {
this.health = this.maxHealth
}
}
get destination() { return this.#dest }
@@ -45,6 +52,7 @@ export default class Entity {
get game() { return this.#game }
get position() { return this.#position }
get scheduledPathfinding() { return this.#scheduledPathfinding }
get spawnPosition() { return this.#spawnPosition }
get x() { return this.position.x }
get y() { return this.position.y }
@@ -53,6 +61,7 @@ export default class Entity {
set game(value) { this.#game = value }
set position(value) { this.#position = value }
set scheduledPathfinding(value) { this.#scheduledPathfinding = value }
set spawnPosition(value) { this.#spawnPosition = value }
set x(value) { this.position.x = value }
set y(value) { this.position.y = value }
@@ -101,7 +110,7 @@ export default class Entity {
}
if (halt) {
this.#move = false
this.#moving = false
}
const cursor = new Vector2(x, y)
@@ -118,42 +127,26 @@ export default class Entity {
}
haltAction() {
this.#move = false
this.#moving = false
}
moveAction(x, y, attack = false) {
if (this.casting != null && (!this.#attack || this.casting.ability.id != this.abilities[0].id)) {
if (this.casting != null && (!this.#attacking || this.casting.ability.id != this.abilities[0].id)) {
this.casting = null
}
this.#attack = attack
this.#move = true
this.#attacking = attack
this.#moving = true
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
}
stopAction() {
this.casting = null
this.#move = true
this.#attack = false
this.#moving = true
this.#attacking = false
}
cast() {
if (this.casting == null) {
return false
}
const castTime = this.game?.secToTick(this.casting.ability.castTime) ?? 0
const castStart = this.casting.timestamp
const timestamp = this.game?.currentTick ?? 0
if (castStart + castTime > timestamp) {
return false
}
this.casting.ability.effect(this, this.casting.cursor)
this.casting = null
return true
}
// --- Actions above --- //
collidables() {
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id).map((e) => e.collider)
@@ -186,6 +179,12 @@ export default class Entity {
return SATX.collideObjects(this.collider, colliders)
}
respawn() {
this.#position = this.#spawnPosition.clone()
this.health = this.maxHealth
this.dead = false
}
state() {
return {
...this,
@@ -201,21 +200,89 @@ export default class Entity {
this.fixPosition()
}
move(distanceTraveled = 0) {
if (this.casting != null) { return false }
if (this.#attack && this.game?.entities.some((e) => e.team != this.team && e.position.clone().sub(this.position).length() < this.abilities[0].range)) {
const cooldown = this.game?.secToTick(this.abilities[0].cooldown) ?? 0
const lastCast = this.cooldowns[this.abilities[0].id]
const timestamp = this.game?.currentTick ?? 0
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
const target = this.#dest ?? this.position
this.castAction(0, target.x, target.y, false)
return true
update() {
if (this.dead) {
// TODO: do something while the entity is dead
}
else {
this.#cast()
this.#checkHealth()
this.#move()
this.fixPosition()
}
if (!this.#move || this.#dest == null) { return false }
if (this.#logic != null) {
this.#logic()
}
}
waypoints() {
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id)
const terrainColliders = (this.game?.terrains ?? [])
const unadjustedWaypoints = entityColliders.concat(terrainColliders).map((e) => e.unadjustedWaypoints).flat()
return unadjustedWaypoints.map(([waypoint, direction]) => {
return SATX.clamp(
waypoint.clone().add(direction.clone().multiplyScalar(this.radius)),
this.game?.width,
this.game?.height,
this.radius,
)
}) ?? []
}
#cast() {
if (this.casting == null) {
return false
}
const castTime = this.game?.secToTick(this.casting.ability.castTime) ?? 0
const castStart = this.casting.timestamp
const timestamp = this.game?.currentTick ?? 0
if (castStart + castTime > timestamp) {
return false
}
this.casting.ability.effect(this, this.casting.cursor)
this.casting = null
return true
}
#checkHealth() {
if (this.health <= 0) {
this.dead = true
}
}
// TODO: make scheduled pathfinding continue until collision to make the entity more "alive"
#move(distanceTraveled = 0) {
if (this.casting != null) { return false }
if (this.#attacking) {
const attackCursor = this.#dest ?? this.position
const targets = this.game?.entities.filter((e) => e.team != this.team && e.position.clone().sub(attackCursor).length() < this.abilities[0].range)
const target = targets.reduce((prev, e) => {
if (prev == null || e.position.clone().sub(attackCursor).length() > prev.position.clone().sub(attackCursor).length()) {
return e
}
else {
return prev
}
}, null)
if (target != null && target.position.clone().sub(this.position).length() < this.abilities[0].range) {
const cooldown = this.game?.secToTick(this.abilities[0].cooldown) ?? 0
const lastCast = this.cooldowns[this.abilities[0].id]
const timestamp = this.game?.currentTick ?? 0
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
this.castAction(0, attackCursor.x, attackCursor.y, false)
return true
}
}
if (!this.#moving || this.#dest == null) { return false }
const collidables = this.collidables()
const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, collidables, this.radius), this.game?.width, this.game?.height, this.radius)
@@ -262,37 +329,13 @@ export default class Entity {
if (this.position.equals(destination)) {
this.#path = this.#path.slice(1)
if (this.#path.length > 0) {
this.move(distance)
this.#move(distance)
}
else {
this.#dest = null
this.#move = false
this.#moving = false
}
}
}
}
update() {
this.cast()
this.move()
this.fixPosition()
if (this.#logic != null) {
this.#logic()
}
}
waypoints() {
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id)
const terrainColliders = (this.game?.terrains ?? [])
const unadjustedWaypoints = entityColliders.concat(terrainColliders).map((e) => e.unadjustedWaypoints).flat()
return unadjustedWaypoints.map(([waypoint, direction]) => {
return SATX.clamp(
waypoint.clone().add(direction.clone().multiplyScalar(this.radius)),
this.game?.width,
this.game?.height,
this.radius,
)
}) ?? []
}
}