diff --git a/src/ability.js b/src/ability.js index 58bb0ad..6fdcaca 100644 --- a/src/ability.js +++ b/src/ability.js @@ -1,48 +1,113 @@ -import Effect from './effect.js' +import Projectile from './projectile.js' + +// Q: damage -> self sustain / crowd control +// W: control -> mobility +// E: support -> vision / selfless support / creative export default class Ability { id = crypto.randomUUID() name = 'Ability' - cooldown = 0 + castTime = 0 + cooldown = 0 + damage = 0 + radius = 1 + range = 0 + speed = 1000 #effect = () => {} get effect() { return this.#effect } set effect(value) { this.#effect = value } - constructor(options) { - if (options.name != null) { this.name = options.name } - if (options.effect != null) { this.#effect = options.effect } - if (options.cooldown != null) { this.cooldown = options.cooldown } - if (options.castTime != null) { this.castTime = options.castTime } + constructor(options = {}) { + Object.entries(options).forEach(([key, value]) => this[key] = value) } - static basicAttack(range, radius, speed) { - return new this({ - name: 'Basic Attack', - castTime: 0.25, - cooldown: 1.25, - effect: Effect.homingProjectile({ - range, - radius, - speed, - after: Effect.damage({ despawn: true }).bind(this), - }), - }) - } + static straightShot = new Ability({ + id: 'straight_shot', + name: 'Straight Shot', + castTime: 0.1, + cooldown: 7, + damage: 10, + radius: 7, + range: 800, + speed: 3000, + effect: function straightShotEffect(caster, cursor) { + const ability = this + const straightShotCollision = function straightShotCollision(projectile, collidingEntity) { + collidingEntity.damage(ability.damage) + projectile.despawn() + } - 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) - }), - }) - } + const projectile = new Projectile({ + onCollide: straightShotCollision, + owner: caster, + position: caster.position.clone(), + radius: ability.radius, + speed: ability.speed, + }) + + projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range)) + caster.game?.spawnProjectile(projectile) + caster.cooldown(ability.id) + }, + }) + + static basicAttack = new Ability({ + id: 'basic_attack', + name: 'Basic Attack', + castTime: 0.25, + cooldown: 1.25, + damage: 5, + radius: 5, + range: 600, + speed: 600, + effect: function basicAttackEffect(caster, cursor) { + const ability = this + let closest = null + let distance = Infinity + caster.game?.entities.filter((e) => e.id != caster.id && e.position.clone().sub(caster.position).length() < ability.range).forEach((e) => { + const newDistance = e.position.clone().sub(cursor).length() < distance + if (newDistance < distance) { + closest = e + distance = newDistance + } + }) + + if (closest == null) { return } + + const basicAttackAfter = function basicAttackAfter() { + closest.damage(ability.damage) + } + + const projectile = new Projectile({ + after: basicAttackAfter, + homingTarget: closest, + owner: caster, + position: caster.position.clone(), + radius: ability.radius, + speed: ability.speed, + }) + + caster.game?.spawnProjectile(projectile) + caster.cooldown(ability.id) + }, + }) + + static control = new Ability({ + id: 'control', + name: 'Control', + castTime: 1, + cooldown: 5, + effect: function controlEffect() { console.log('Control is still work in progress.') }, + }) + + static shieldThrow = new Ability({ + id: 'shield_throw', + name: 'Shield Throw', + castTime: 0.1, + cooldown: 7, + effect: function shieldThrowEffect() { console.log('Shield throw is still work in progress.') }, + }) } diff --git a/src/effect.js b/src/effect.js deleted file mode 100644 index 52335e1..0000000 --- a/src/effect.js +++ /dev/null @@ -1,54 +0,0 @@ -import { Vector2 } from 'three' -import Projectile from './projectile.js' - -export default class Effect { - static damage({ despawn }) { - return function(projectile, entity) { - entity.damage(10, this) - if (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) - } - } -} \ No newline at end of file diff --git a/src/entity.js b/src/entity.js index 8af3650..81bf797 100644 --- a/src/entity.js +++ b/src/entity.js @@ -11,10 +11,10 @@ export default class Entity { health = 1 // TODO: health can go into negatives and can go over maxHealth maxHealth = 1 abilities = [ - Ability.basicAttack(600, 5, 600), - Ability.straightShot(800, 7, 3000), - () => {}, - () => {}, + Ability.basicAttack, + Ability.straightShot, + Ability.control, + Ability.shieldThrow, ] casting = null // TODO: teams @@ -30,8 +30,8 @@ export default class Entity { return new SAT.Circle(new SAT.Vector(x, y), radius) } - constructor(...options) { - Object.entries(options).forEach((value, key) => this[key] = value) + constructor(options = {}) { + Object.entries(options).forEach(([key, value]) => this[key] = value) } get game() { return this.#game } @@ -82,11 +82,7 @@ export default class Entity { 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.ability.effect(this, this.casting.cursor) this.casting = null return true @@ -123,6 +119,10 @@ export default class Entity { return entityColliders.concat(terrainColliders) } + cooldown(id) { + this.cooldowns[id] = this.game?.currentTick ?? 0 + } + damage(amount, source = null) { this.health = Math.min(Math.max(0, this.health - amount), this.maxHealth) } @@ -213,6 +213,17 @@ export default class Entity { this.cast() this.takeStep() this.fixPosition() + if (this.health <= 0) { + if (this.id == '1' || this.id == '2') { + this.health = this.maxHealth + if (this.id == '1') { + this.teleport(200, 200) + } + if (this.id == '2') { + this.teleport(1800, 1800) + } + } + } } waypoints() { diff --git a/src/projectile.js b/src/projectile.js index dc12a3f..da24790 100644 --- a/src/projectile.js +++ b/src/projectile.js @@ -32,8 +32,8 @@ export default class Projectile { this.#homingTarget = value } - constructor(...options) { - Object.entries(options).forEach((value, key) => this[key] = value) + constructor(options = {}) { + Object.entries(options).forEach(([key, value]) => this[key] = value) } get game() { return this.#game } @@ -44,10 +44,11 @@ export default class Projectile { set x(value) { this.position.x = value } set y(value) { this.position.y = value } set destination(value) { this.#dest = value } + set position(value) { this.#position = value } checkCollisions() { (this.game?.entities ?? []).filter((e) => e.id != this.id).forEach((e) => { - if (e.id == this.owner) { return } + if (e.id == this.owner?.id) { return } if (SATX.collideObject(this.collider, e.collider)) { this.onCollide(this, e)