inflate ranges by entity radii
This commit is contained in:
+13
-30
@@ -49,7 +49,7 @@ export default class Ability {
|
||||
speed: ability.speed,
|
||||
})
|
||||
|
||||
projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range))
|
||||
projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range + caster.radius))
|
||||
caster.game?.spawnProjectile(projectile)
|
||||
caster.cooldown(ability.id)
|
||||
},
|
||||
@@ -60,31 +60,22 @@ export default class Ability {
|
||||
name: 'Ranged Attack',
|
||||
castTime: 0.25,
|
||||
cooldown: 1.25,
|
||||
damage: 5,
|
||||
damage: 25,
|
||||
radius: 5,
|
||||
range: 500,
|
||||
speed: 600,
|
||||
effect: function rangedAttackEffect(caster, cursor) {
|
||||
const ability = this
|
||||
let closest = null
|
||||
let distance = Infinity
|
||||
caster.game?.entities.filter((e) => e.team != caster.team && e.position.clone().sub(caster.position).length() < ability.range).forEach((e) => {
|
||||
const newDistance = e.position.clone().sub(cursor).length()
|
||||
if (newDistance < distance) {
|
||||
closest = e
|
||||
distance = newDistance
|
||||
}
|
||||
})
|
||||
|
||||
if (closest == null) { return }
|
||||
const target = caster.closestTargetTo(cursor, ability.range)
|
||||
if (target == null) { return }
|
||||
|
||||
const rangedAttackAfter = function rangedAttackAfter() {
|
||||
closest.damage(ability.damage)
|
||||
target.damage(ability.damage)
|
||||
}
|
||||
|
||||
const projectile = new Projectile({
|
||||
after: rangedAttackAfter,
|
||||
homingTarget: closest,
|
||||
homingTarget: target,
|
||||
owner: caster,
|
||||
position: caster.position.clone(),
|
||||
radius: ability.radius,
|
||||
@@ -106,19 +97,10 @@ export default class Ability {
|
||||
range: 100,
|
||||
effect: function meleeAttackEffect(caster, cursor) {
|
||||
const ability = this
|
||||
let closest = null
|
||||
let distance = Infinity
|
||||
caster.game?.entities.filter((e) => e.team != caster.team && 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
|
||||
}
|
||||
})
|
||||
const target = caster.closestTargetTo(cursor, ability.range)
|
||||
if (target == null) { return }
|
||||
|
||||
if (closest == null) { return }
|
||||
|
||||
closest.damage(ability.damage)
|
||||
target.damage(ability.damage)
|
||||
caster.cooldown(ability.id)
|
||||
},
|
||||
})
|
||||
@@ -153,7 +135,7 @@ export default class Ability {
|
||||
speed: ability.speed,
|
||||
})
|
||||
|
||||
projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range))
|
||||
projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range + caster.radius))
|
||||
caster.game?.spawnProjectile(projectile)
|
||||
caster.cooldown(ability.id)
|
||||
},
|
||||
@@ -168,8 +150,9 @@ export default class Ability {
|
||||
effect: function blinkEffect(caster, cursor) {
|
||||
const ability = this
|
||||
const direction = cursor.clone().sub(caster.position)
|
||||
if (direction.length() > ability.range) {
|
||||
direction.normalize().multiplyScalar(ability.range)
|
||||
const realRange = ability.range + caster.radius
|
||||
if (direction.length() > realRange) {
|
||||
direction.normalize().multiplyScalar(realRange)
|
||||
}
|
||||
|
||||
const destination = caster.position.clone().add(direction)
|
||||
|
||||
+19
-15
@@ -159,6 +159,14 @@ export default class Entity {
|
||||
this.cooldowns[id] = this.game?.currentTick ?? 0
|
||||
}
|
||||
|
||||
closestTargetTo(cursor, range) {
|
||||
return this
|
||||
.game
|
||||
?.entities
|
||||
.filter((e) => this.team != e.team && e.distanceTo(cursor) <= range + this.radius + e.radius)
|
||||
.reduce((e1, e2) => (e1?.distanceTo(cursor) ?? Infinity) < e2.distanceTo(cursor) ? e1 : e2, null)
|
||||
}
|
||||
|
||||
damage(amount) {
|
||||
this.health = Math.min(Math.max(0, this.health - amount), this.maxHealth)
|
||||
}
|
||||
@@ -167,6 +175,10 @@ export default class Entity {
|
||||
this.game?.despawn(this)
|
||||
}
|
||||
|
||||
distanceTo(vector) {
|
||||
return this.position.distanceTo(vector)
|
||||
}
|
||||
|
||||
heal(amount) {
|
||||
this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth)
|
||||
}
|
||||
@@ -260,24 +272,16 @@ export default class Entity {
|
||||
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 cursor = this.#dest ?? this.position
|
||||
const basicAttack = this.abilities[0]
|
||||
const target = this.closestTargetTo(cursor, basicAttack.range)
|
||||
if (target != null && this.distanceTo(target.position) < basicAttack.range) {
|
||||
const cooldown = this.game?.secToTick(basicAttack.cooldown) ?? 0
|
||||
const lastCast = this.cooldowns[basicAttack.id]
|
||||
const timestamp = this.game?.currentTick ?? 0
|
||||
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
|
||||
|
||||
this.castAction(0, attackCursor.x, attackCursor.y, false)
|
||||
this.castAction(0, cursor.x, cursor.y, false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+39
-38
@@ -66,25 +66,27 @@ function laneScenario() {
|
||||
}
|
||||
}
|
||||
|
||||
const entity1 = new Entity({
|
||||
id: '1',
|
||||
const playerTemplate = {
|
||||
height: 80,
|
||||
logic: playerLogic,
|
||||
maxHealth: 100,
|
||||
maxHealth: 600,
|
||||
spawnPosition: new Vector2(500, 150),
|
||||
radius: 65,
|
||||
}
|
||||
|
||||
const entity1 = new Entity({
|
||||
...playerTemplate,
|
||||
id: '1',
|
||||
spawnPosition: new Vector2(500, 150),
|
||||
radius: 50,
|
||||
team: Team.blue,
|
||||
})
|
||||
|
||||
game.spawnEntity(entity1)
|
||||
|
||||
const entity2 = new Entity({
|
||||
...playerTemplate,
|
||||
id: '2',
|
||||
height: 80,
|
||||
logic: playerLogic,
|
||||
maxHealth: 100,
|
||||
spawnPosition: new Vector2(1600, 1800),
|
||||
radius: 50,
|
||||
team: Team.red,
|
||||
})
|
||||
|
||||
@@ -115,39 +117,38 @@ function laneScenario() {
|
||||
midSouthWall.id = 'midSouthWall'
|
||||
game.addTerrain(midSouthWall)
|
||||
|
||||
const blueMinionLogic = function minionLogic() {
|
||||
const minionLogic = (team) => {
|
||||
const finalGoal = team == Team.blue ? new Vector2(1900, 1900) : new Vector2(100, 100)
|
||||
const subGoal = new Vector2(850, 1150)
|
||||
const subGoalCheck = team == Team.blue ? ((entity) => entity.position.x < 800 || entity.position.y < 1100) : ((entity) => entity.position.x > 900 || entity.position.y > 1200)
|
||||
|
||||
return function builtMinionLogic() {
|
||||
const entity = this
|
||||
if (entity.dead) { entity.despawn() }
|
||||
|
||||
let goal = new Vector2(1900, 1900)
|
||||
if (entity.position.x < 800 || entity.position.y < 1100) {
|
||||
goal = new Vector2(850, 1150)
|
||||
let goal = finalGoal
|
||||
if (subGoalCheck(entity)) {
|
||||
goal = subGoal
|
||||
}
|
||||
|
||||
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(75)
|
||||
const subGoal = entity.position.clone().add(direction)
|
||||
entity.attackAction(subGoal.x, subGoal.y)
|
||||
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(100)
|
||||
const fakeDestination = entity.position.clone().add(direction)
|
||||
entity.attackAction(fakeDestination.x, fakeDestination.y)
|
||||
}
|
||||
|
||||
const redMinionLogic = function minionLogic() {
|
||||
const entity = this
|
||||
if (entity.dead) { entity.despawn() }
|
||||
|
||||
let goal = new Vector2(100, 100)
|
||||
if (entity.position.x > 900 || entity.position.y > 1200) {
|
||||
goal = new Vector2(850, 1150)
|
||||
}
|
||||
|
||||
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(75)
|
||||
const subGoal = entity.position.clone().add(direction)
|
||||
entity.attackAction(subGoal.x, subGoal.y)
|
||||
}
|
||||
|
||||
const minionTemplate = {
|
||||
health: 20,
|
||||
maxHealth: 20,
|
||||
radius: 30,
|
||||
speed: 300,
|
||||
height: 40,
|
||||
maxHealth: 300,
|
||||
radius: 48,
|
||||
speed: 325,
|
||||
}
|
||||
|
||||
const meleeMinionTemplate = {
|
||||
...minionTemplate,
|
||||
height: 38,
|
||||
radius: 46,
|
||||
maxHealth: 450,
|
||||
}
|
||||
|
||||
const gameLogic = function gameLogic() {
|
||||
@@ -155,14 +156,14 @@ function laneScenario() {
|
||||
|
||||
const blueMinion = new Entity({
|
||||
...minionTemplate,
|
||||
logic: blueMinionLogic,
|
||||
logic: minionLogic(Team.blue),
|
||||
team: Team.blue,
|
||||
position: new Vector2(200, 200),
|
||||
})
|
||||
|
||||
const blueMeleeMinion = new Entity({
|
||||
...minionTemplate,
|
||||
logic: blueMinionLogic,
|
||||
...meleeMinionTemplate,
|
||||
logic: minionLogic(Team.blue),
|
||||
team: Team.blue,
|
||||
position: new Vector2(200, 200),
|
||||
})
|
||||
@@ -170,14 +171,14 @@ function laneScenario() {
|
||||
|
||||
const redMinion = new Entity({
|
||||
...minionTemplate,
|
||||
logic: redMinionLogic,
|
||||
logic: minionLogic(Team.red),
|
||||
team: Team.red,
|
||||
position: new Vector2(1800, 1800),
|
||||
})
|
||||
|
||||
const redMeleeMinion = new Entity({
|
||||
...minionTemplate,
|
||||
logic: redMinionLogic,
|
||||
...meleeMinionTemplate,
|
||||
logic: minionLogic(Team.red),
|
||||
team: Team.red,
|
||||
position: new Vector2(1800, 1800),
|
||||
})
|
||||
|
||||
+34
-53
@@ -5,65 +5,42 @@ import { Vector2 } from 'three'
|
||||
export default class Projectile {
|
||||
id = crypto.randomUUID()
|
||||
after = null
|
||||
speed = 1000
|
||||
radius = 5
|
||||
owner = null
|
||||
onCollide = null
|
||||
height = 50
|
||||
onCollide = null
|
||||
owner = null
|
||||
position = new Vector2()
|
||||
radius = 5
|
||||
speed = 1000
|
||||
|
||||
#position = new Vector2()
|
||||
#dest = null
|
||||
#homingTarget = null
|
||||
#game = null
|
||||
|
||||
get collider() {
|
||||
return new SAT.Circle(new SAT.Vector(this.x, this.y), this.radius)
|
||||
}
|
||||
|
||||
get homing() {
|
||||
return !!this.#homingTarget
|
||||
}
|
||||
get game() { return this.#game }
|
||||
set game(value) { this.#game = value }
|
||||
set destination(value) { this.#dest = value }
|
||||
set homingTarget(value) { this.#homingTarget = value }
|
||||
|
||||
get destination() {
|
||||
return this.#dest ?? this.#homingTarget?.position
|
||||
}
|
||||
|
||||
set homingTarget(value) {
|
||||
this.#homingTarget = value
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
Object.entries(options).forEach(([key, value]) => this[key] = value)
|
||||
}
|
||||
|
||||
get game() { return this.#game }
|
||||
get position() { return this.#position }
|
||||
get x() { return this.position.x }
|
||||
get y() { return this.position.y }
|
||||
set game(value) { this.#game = value }
|
||||
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?.id) { return }
|
||||
|
||||
if (SATX.collideObject(this.collider, e.collider)) {
|
||||
if (SATX.collideObject(this.collider(), e.collider)) {
|
||||
this.onCollide(this, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
checkIfArrived() {
|
||||
if (!this.#position.equals(this.destination)) { return }
|
||||
|
||||
if (this.after != null) {
|
||||
this.after(this, this.#homingTarget)
|
||||
}
|
||||
|
||||
this.despawn()
|
||||
collider() {
|
||||
return new SAT.Circle(new SAT.Vector(this.x, this.y), this.radius)
|
||||
}
|
||||
|
||||
despawn() {
|
||||
@@ -73,28 +50,32 @@ export default class Projectile {
|
||||
state() {
|
||||
return {
|
||||
...this,
|
||||
position: {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
takeStep() {
|
||||
const speed = (this.speed / (this.game?.tickBudget ?? 1000))
|
||||
const destination = this.destination
|
||||
const difference = destination.clone().sub(this.position)
|
||||
const distance = difference.length()
|
||||
const direction = difference.clone().normalize()
|
||||
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
|
||||
const position = distance <= speed ? destination : stepTaken
|
||||
|
||||
this.position.copy(position)
|
||||
}
|
||||
|
||||
update() {
|
||||
this.takeStep()
|
||||
this.#move()
|
||||
if (this.onCollide != null) { this.checkCollisions() }
|
||||
this.checkIfArrived()
|
||||
this.#checkIfArrived()
|
||||
}
|
||||
|
||||
#checkIfArrived() {
|
||||
if (!this.position.equals(this.destination)) { return }
|
||||
|
||||
if (this.after != null) {
|
||||
this.after(this, this.#homingTarget)
|
||||
}
|
||||
|
||||
this.despawn()
|
||||
}
|
||||
|
||||
#move() {
|
||||
const speed = (this.speed / (this.game?.tickBudget ?? 1000))
|
||||
if (this.position.distanceTo(this.destination) < speed) {
|
||||
this.position.copy(this.destination)
|
||||
}
|
||||
|
||||
const step = this.destination.clone().sub(this.position).normalize().multiplyScalar(speed)
|
||||
this.position.add(step)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user