add cast times and cooldowns
This commit is contained in:
+2
-2
@@ -172,6 +172,8 @@ function connectWebSocket() {
|
|||||||
entity.position.set(e.position.x / 100, e.position.y / 100, e.radius / 100)
|
entity.position.set(e.position.x / 100, e.position.y / 100, e.radius / 100)
|
||||||
scene.add(entity)
|
scene.add(entity)
|
||||||
|
|
||||||
|
// TODO: player model out of basic geometries
|
||||||
|
|
||||||
const hpMargin = 0.4
|
const hpMargin = 0.4
|
||||||
const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 }))
|
const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 }))
|
||||||
maxHp.position.set(0, (e.radius / 100) + hpMargin, 0)
|
maxHp.position.set(0, (e.radius / 100) + hpMargin, 0)
|
||||||
@@ -188,7 +190,6 @@ function connectWebSocket() {
|
|||||||
entities[e.id] = entity
|
entities[e.id] = entity
|
||||||
}
|
}
|
||||||
|
|
||||||
// entity.position.set(e.position.x / 100, e.position.y / 100, e.radius / 100)
|
|
||||||
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z: e.radius / 100 }, tweenDuration).start()
|
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z: e.radius / 100 }, tweenDuration).start()
|
||||||
|
|
||||||
const hp = entity.children.at(0).children.at(0)
|
const hp = entity.children.at(0).children.at(0)
|
||||||
@@ -217,7 +218,6 @@ function connectWebSocket() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
projectile.userData.flaggedForRemoval = false
|
projectile.userData.flaggedForRemoval = false
|
||||||
// projectile.position.set(p.position.x / 100, p.position.y / 100, p.visualHeight / 100)
|
|
||||||
positionTweens[projectile.id] = new Tween(projectile.position).to({ x: p.position.x / 100, y: p.position.y / 100, z: p.visualHeight / 100 }, tweenDuration).start()
|
positionTweens[projectile.id] = new Tween(projectile.position).to({ x: p.position.x / 100, y: p.position.y / 100, z: p.visualHeight / 100 }, tweenDuration).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
<p>Connection: <span id="connection"></span></p>
|
<p>Connection: <span id="connection"></span></p>
|
||||||
<pre id="state"></pre>
|
<pre id="state"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- TODO: HUD -->
|
||||||
|
<!-- TODO: cast indicator -->
|
||||||
<script type="module" src="client.js"></script>
|
<script type="module" src="client.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+36
-38
@@ -1,46 +1,44 @@
|
|||||||
import { Vector2 } from 'three'
|
import Effect from './effect.js'
|
||||||
import Projectile from './projectile.js'
|
|
||||||
|
|
||||||
export default class Ability {
|
export default class Ability {
|
||||||
static skillshot({ range, radius, speed, onCollide, after }) {
|
id = crypto.randomUUID()
|
||||||
return function(x, y) {
|
name = 'Ability'
|
||||||
const projectile = new Projectile()
|
cooldown = 0
|
||||||
const destination = this.position.clone().add(new Vector2(x, y).sub(this.position).normalize().multiplyScalar(range))
|
effect = () => {}
|
||||||
projectile.owner = this.id
|
castTime = 0
|
||||||
projectile.position.copy(this.position)
|
|
||||||
projectile.destination = destination
|
constructor(options) {
|
||||||
projectile.radius = radius
|
if (options.name != null) { this.name = options.name }
|
||||||
projectile.speed = speed
|
if (options.effect != null) { this.effect = options.effect }
|
||||||
projectile.after = after
|
if (options.cooldown != null) { this.cooldown = options.cooldown }
|
||||||
projectile.onCollide = onCollide
|
if (options.castTime != null) { this.castTime = options.castTime }
|
||||||
this.game?.spawnProjectile(projectile)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static homingProjectile({ range, radius, speed, onCollide, after }) {
|
static basicAttack(range, radius, speed) {
|
||||||
return function(x, y) {
|
return new this({
|
||||||
const cursor = new Vector2(x, y)
|
name: 'Basic Attack',
|
||||||
let closest = null
|
castTime: 0.25,
|
||||||
let distance = Infinity
|
cooldown: 1.25,
|
||||||
this.game?.entities.filter((e) => e.id != this.id && e.position.clone().sub(this.position).length() < range).forEach((e) => {
|
effect: Effect.homingProjectile({
|
||||||
const newDistance = e.position.clone().sub(cursor).length() < distance
|
range,
|
||||||
if (newDistance < distance) {
|
radius,
|
||||||
closest = e
|
speed,
|
||||||
distance = newDistance
|
after: Effect.damage({ despawn: true }).bind(this),
|
||||||
}
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (closest == null) { return } // TODO: refund
|
|
||||||
|
|
||||||
const projectile = new Projectile()
|
|
||||||
projectile.owner = this.id
|
|
||||||
projectile.position.copy(this.position)
|
|
||||||
projectile.homingTarget = closest
|
|
||||||
projectile.radius = radius
|
|
||||||
projectile.speed = speed
|
|
||||||
projectile.after = after
|
|
||||||
projectile.onCollide = onCollide
|
|
||||||
this.game?.spawnProjectile(projectile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static straightShot(range, radius, speed) {
|
||||||
|
return new this({
|
||||||
|
name: 'Straight Shot',
|
||||||
|
castTime: 0.1,
|
||||||
|
cooldown: 7,
|
||||||
|
effect: Effect.skillshot({
|
||||||
|
range,
|
||||||
|
radius,
|
||||||
|
speed,
|
||||||
|
onCollide: Effect.damage({ despawn: true }).bind(this)
|
||||||
|
}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-1
@@ -1,10 +1,54 @@
|
|||||||
|
import { Vector2 } from 'three'
|
||||||
|
import Projectile from './projectile.js'
|
||||||
|
|
||||||
export default class Effect {
|
export default class Effect {
|
||||||
static damage({ despawn }) {
|
static damage({ despawn }) {
|
||||||
return function(projectile, entity) {
|
return function(projectile, entity) {
|
||||||
entity.health -= 10
|
entity.damage(10, this)
|
||||||
if (despawn) {
|
if (despawn) {
|
||||||
projectile.despawn()
|
projectile.despawn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static skillshot({ range, radius, speed, onCollide, after }) {
|
||||||
|
return function(cursor) {
|
||||||
|
const projectile = new Projectile()
|
||||||
|
const destination = this.position.clone().add(cursor.clone().sub(this.position).normalize().multiplyScalar(range))
|
||||||
|
projectile.owner = this.id
|
||||||
|
projectile.position.copy(this.position)
|
||||||
|
projectile.destination = destination
|
||||||
|
projectile.radius = radius
|
||||||
|
projectile.speed = speed
|
||||||
|
projectile.after = after
|
||||||
|
projectile.onCollide = onCollide
|
||||||
|
this.game?.spawnProjectile(projectile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static homingProjectile({ range, radius, speed, onCollide, after }) {
|
||||||
|
return function(cursor) {
|
||||||
|
let closest = null
|
||||||
|
let distance = Infinity
|
||||||
|
this.game?.entities.filter((e) => e.id != this.id && e.position.clone().sub(this.position).length() < range).forEach((e) => {
|
||||||
|
const newDistance = e.position.clone().sub(cursor).length() < distance
|
||||||
|
if (newDistance < distance) {
|
||||||
|
closest = e
|
||||||
|
distance = newDistance
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (closest == null) { return } // TODO: refund
|
||||||
|
|
||||||
|
const projectile = new Projectile()
|
||||||
|
projectile.owner = this.id
|
||||||
|
projectile.position.copy(this.position)
|
||||||
|
projectile.homingTarget = closest
|
||||||
|
projectile.radius = radius
|
||||||
|
projectile.speed = speed
|
||||||
|
projectile.after = after
|
||||||
|
projectile.onCollide = onCollide
|
||||||
|
this.game?.spawnProjectile(projectile)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+51
-4
@@ -9,15 +9,19 @@ export default class Entity {
|
|||||||
id = crypto.randomUUID()
|
id = crypto.randomUUID()
|
||||||
speed = 400
|
speed = 400
|
||||||
radius = 0
|
radius = 0
|
||||||
health = 1
|
health = 1 // TODO: health can go into negatives and can go over maxHealth
|
||||||
maxHealth = 1
|
maxHealth = 1
|
||||||
abilities = [
|
abilities = [
|
||||||
Ability.homingProjectile({ range: 600, radius: 3, speed: 500, onCollide: Effect.damage({ despawn: true }) }),
|
Ability.basicAttack(600, 5, 600),
|
||||||
Ability.skillshot({ range: 800, radius: 5, speed: 3000, onCollide: Effect.damage({ despawn: true }) }),
|
Ability.straightShot(800, 7, 3000),
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
]
|
]
|
||||||
|
casting = null
|
||||||
|
// TODO: teams
|
||||||
|
|
||||||
|
cooldowns = {}
|
||||||
|
|
||||||
#position = new Vector2()
|
#position = new Vector2()
|
||||||
#dest = null
|
#dest = null
|
||||||
@@ -68,8 +72,42 @@ export default class Entity {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.bind(this)(this.casting.cursor)
|
||||||
|
|
||||||
|
if (this.casting.ability.cooldown != null) {
|
||||||
|
this.cooldowns[this.casting.ability.id] = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
this.casting = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
castAction(slot, x, y) {
|
castAction(slot, x, y) {
|
||||||
this.abilities[slot].bind(this)(x, y)
|
const ability = this.abilities[slot]
|
||||||
|
const cursor = new Vector2(x, y)
|
||||||
|
const cooldown = this.game?.secToTick(ability.cooldown) ?? 0
|
||||||
|
const lastCast = this.cooldowns[ability.id]
|
||||||
|
const timestamp = this.game?.currentTick ?? 0
|
||||||
|
if (lastCast != null && lastCast + cooldown > timestamp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#dest = null
|
||||||
|
this.casting = { ability, cursor, timestamp }
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
collidables() {
|
collidables() {
|
||||||
@@ -87,10 +125,18 @@ export default class Entity {
|
|||||||
return entityColliders.concat(terrainColliders)
|
return entityColliders.concat(terrainColliders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
damage(amount, source = null) {
|
||||||
|
this.health = Math.min(Math.max(0, this.health - amount), this.maxHealth)
|
||||||
|
}
|
||||||
|
|
||||||
despawn() {
|
despawn() {
|
||||||
this.game?.despawn(this)
|
this.game?.despawn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
heal(amount, source = null) {
|
||||||
|
this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth)
|
||||||
|
}
|
||||||
|
|
||||||
fixPosition() {
|
fixPosition() {
|
||||||
this.#position = SATX.fixCollisions(this.#position, this.collidables(), this.radius, this.game?.width, this.game?.height)
|
this.#position = SATX.fixCollisions(this.#position, this.collidables(), this.radius, this.game?.width, this.game?.height)
|
||||||
}
|
}
|
||||||
@@ -166,6 +212,7 @@ export default class Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
this.cast()
|
||||||
this.takeStep()
|
this.takeStep()
|
||||||
this.fixPosition()
|
this.fixPosition()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ export default class Game {
|
|||||||
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
|
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secToTick(sec) {
|
||||||
|
return Math.floor(this.tickRate * sec)
|
||||||
|
}
|
||||||
|
|
||||||
spawn(object) {
|
spawn(object) {
|
||||||
if (object instanceof Entity) { this.spawnEntity(object) }
|
if (object instanceof Entity) { this.spawnEntity(object) }
|
||||||
else if (object instanceof Terrain) { this.addTerrain(object) }
|
else if (object instanceof Terrain) { this.addTerrain(object) }
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ export default class Projectile {
|
|||||||
if (!this.#position.equals(this.destination)) { return }
|
if (!this.#position.equals(this.destination)) { return }
|
||||||
|
|
||||||
if (this.after != null) {
|
if (this.after != null) {
|
||||||
this.after(this)
|
this.after(this, this.#homingTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.despawn()
|
this.despawn()
|
||||||
|
|||||||
Reference in New Issue
Block a user