add minion routing
This commit is contained in:
+1
-1
@@ -282,7 +282,7 @@ function connectWebSocket() {
|
|||||||
for (let abilityIndex = 0; abilityIndex < 4; abilityIndex++) {
|
for (let abilityIndex = 0; abilityIndex < 4; abilityIndex++) {
|
||||||
if (player.abilities[abilityIndex] != null) {
|
if (player.abilities[abilityIndex] != null) {
|
||||||
const ability = player.abilities[abilityIndex]
|
const ability = player.abilities[abilityIndex]
|
||||||
const lastCast = player.cooldowns[ability.id] ?? -99999
|
const lastCast = player.cooldowns[ability.id] ?? -Infinity
|
||||||
const cooldownDuration = (ability.cooldown * state.tickRate) ?? 0
|
const cooldownDuration = (ability.cooldown * state.tickRate) ?? 0
|
||||||
const remainingCooldown = (lastCast + cooldownDuration) - state.currentTick
|
const remainingCooldown = (lastCast + cooldownDuration) - state.currentTick
|
||||||
let cssPercentage = '100%'
|
let cssPercentage = '100%'
|
||||||
|
|||||||
+17
-24
@@ -13,10 +13,11 @@ export default class Entity {
|
|||||||
health = null
|
health = null
|
||||||
height = 40
|
height = 40
|
||||||
maxHealth = 1
|
maxHealth = 1
|
||||||
|
memory = {} // TODO: WARNING: currently only used for minions (code smell?)
|
||||||
|
position = null
|
||||||
radius = 0
|
radius = 0
|
||||||
speed = 400
|
speed = 400
|
||||||
team = Team.neutral
|
team = Team.neutral
|
||||||
memory = {} // TODO: WARNING: currently only used for minions (code smell?)
|
|
||||||
|
|
||||||
#attacking = false
|
#attacking = false
|
||||||
#dest = null
|
#dest = null
|
||||||
@@ -24,7 +25,6 @@ export default class Entity {
|
|||||||
#logic = null
|
#logic = null
|
||||||
#moving = false
|
#moving = false
|
||||||
#path = []
|
#path = []
|
||||||
#position = null
|
|
||||||
#scheduledPathfinding = null
|
#scheduledPathfinding = null
|
||||||
#spawnPosition = new Vector2()
|
#spawnPosition = new Vector2()
|
||||||
|
|
||||||
@@ -34,8 +34,8 @@ export default class Entity {
|
|||||||
|
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
Object.entries(options).forEach(([key, value]) => this[key] = value)
|
Object.entries(options).forEach(([key, value]) => this[key] = value)
|
||||||
if (this.#position == null) {
|
if (this.position == null) {
|
||||||
this.#position = this.#spawnPosition.clone()
|
this.position = this.#spawnPosition.clone()
|
||||||
}
|
}
|
||||||
if (this.health == null) {
|
if (this.health == null) {
|
||||||
this.health = this.maxHealth
|
this.health = this.maxHealth
|
||||||
@@ -45,7 +45,6 @@ export default class Entity {
|
|||||||
get destination() { return this.#dest }
|
get destination() { return this.#dest }
|
||||||
get logic() { return this.#logic }
|
get logic() { return this.#logic }
|
||||||
get game() { return this.#game }
|
get game() { return this.#game }
|
||||||
get position() { return this.#position }
|
|
||||||
get scheduledPathfinding() { return this.#scheduledPathfinding }
|
get scheduledPathfinding() { return this.#scheduledPathfinding }
|
||||||
get spawnPosition() { return this.#spawnPosition }
|
get spawnPosition() { return this.#spawnPosition }
|
||||||
get x() { return this.position.x }
|
get x() { return this.position.x }
|
||||||
@@ -54,7 +53,6 @@ export default class Entity {
|
|||||||
set destination(value) { this.#dest = value }
|
set destination(value) { this.#dest = value }
|
||||||
set logic(value) { this.#logic = value }
|
set logic(value) { this.#logic = value }
|
||||||
set game(value) { this.#game = value }
|
set game(value) { this.#game = value }
|
||||||
set position(value) { this.#position = value }
|
|
||||||
set scheduledPathfinding(value) { this.#scheduledPathfinding = value }
|
set scheduledPathfinding(value) { this.#scheduledPathfinding = value }
|
||||||
set spawnPosition(value) { this.#spawnPosition = value }
|
set spawnPosition(value) { this.#spawnPosition = value }
|
||||||
set x(value) { this.position.x = value }
|
set x(value) { this.position.x = value }
|
||||||
@@ -88,11 +86,11 @@ export default class Entity {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
attackAction(x, y) {
|
attackAction(cursor) {
|
||||||
this.moveAction(x, y, true)
|
this.moveAction(cursor, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
castAction(slot, x, y, halt = true) {
|
castAction(slot, cursor, halt = true) {
|
||||||
const ability = this.abilities[slot]
|
const ability = this.abilities[slot]
|
||||||
|
|
||||||
if (this.casting != null) {
|
if (this.casting != null) {
|
||||||
@@ -108,7 +106,6 @@ export default class Entity {
|
|||||||
this.#moving = false
|
this.#moving = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const cursor = new Vector2(x, y)
|
|
||||||
const cooldown = this.game?.secToTick(ability.cooldown) ?? 0
|
const cooldown = this.game?.secToTick(ability.cooldown) ?? 0
|
||||||
const lastCast = this.cooldowns[ability.id]
|
const lastCast = this.cooldowns[ability.id]
|
||||||
const timestamp = this.game?.currentTick ?? 0
|
const timestamp = this.game?.currentTick ?? 0
|
||||||
@@ -125,14 +122,14 @@ export default class Entity {
|
|||||||
this.#moving = false
|
this.#moving = false
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAction(x, y, attack = false) {
|
moveAction(cursor, attack = false) {
|
||||||
if (this.casting != null && (!this.#attacking || 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.casting = null
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#attacking = attack
|
this.#attacking = attack
|
||||||
this.#moving = true
|
this.#moving = true
|
||||||
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
|
this.#dest = SATX.fixCollisions(cursor, this.collidables(), this.radius, this.game?.width, this.game?.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
stopAction() {
|
stopAction() {
|
||||||
@@ -170,8 +167,8 @@ export default class Entity {
|
|||||||
this.game?.despawn(this)
|
this.game?.despawn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
distanceTo(vector) {
|
distanceTo(cursor) {
|
||||||
return this.position.distanceTo(vector)
|
return this.position.distanceTo(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
heal(amount) {
|
heal(amount) {
|
||||||
@@ -179,7 +176,7 @@ export default class Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fixPosition() {
|
fixPosition() {
|
||||||
this.#position = SATX.fixCollisions(this.#position, this.collidables(), this.radius, this.game?.width, this.game?.height).clone()
|
this.position = SATX.fixCollisions(this.position, this.collidables(), this.radius, this.game?.width, this.game?.height).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
isColliding(...colliders) {
|
isColliding(...colliders) {
|
||||||
@@ -187,7 +184,7 @@ export default class Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
respawn() {
|
respawn() {
|
||||||
this.#position = this.#spawnPosition.clone()
|
this.position = this.#spawnPosition.clone()
|
||||||
this.health = this.maxHealth
|
this.health = this.maxHealth
|
||||||
this.dead = false
|
this.dead = false
|
||||||
}
|
}
|
||||||
@@ -195,15 +192,11 @@ export default class Entity {
|
|||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
...this,
|
...this,
|
||||||
position: {
|
|
||||||
x: this.x,
|
|
||||||
y: this.y,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
teleport(position) {
|
teleport(cursor) {
|
||||||
this.#position = position.clone()
|
this.position = cursor.clone()
|
||||||
this.fixPosition()
|
this.fixPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +269,7 @@ export default class Entity {
|
|||||||
const timestamp = this.game?.currentTick ?? 0
|
const timestamp = this.game?.currentTick ?? 0
|
||||||
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
|
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
|
||||||
|
|
||||||
this.castAction(0, cursor.x, cursor.y, false)
|
this.castAction(0, cursor, false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,7 +278,7 @@ export default class Entity {
|
|||||||
|
|
||||||
const collidables = this.collidables()
|
const collidables = this.collidables()
|
||||||
const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, collidables, this.radius), this.game?.width, this.game?.height, this.radius)
|
const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, collidables, this.radius), this.game?.width, this.game?.height, this.radius)
|
||||||
const tunnel = SATX.entityTunnel(this.#position.x, this.#position.y, fixedDest.x, fixedDest.y, this.radius)
|
const tunnel = SATX.entityTunnel(this.position.x, this.position.y, fixedDest.x, fixedDest.y, this.radius)
|
||||||
const destinationInLineOfSight = !SATX.collideObjects(tunnel, collidables)
|
const destinationInLineOfSight = !SATX.collideObjects(tunnel, collidables)
|
||||||
|
|
||||||
if (this.#path.length > 0) {
|
if (this.#path.length > 0) {
|
||||||
|
|||||||
+18
-15
@@ -31,17 +31,19 @@ app.ws('/ws', async (req, res) => {
|
|||||||
const message = JSON.parse(rawData)
|
const message = JSON.parse(rawData)
|
||||||
const entity = message.id != null ? game.entities.find((e) => e.id == message.id) : null
|
const entity = message.id != null ? game.entities.find((e) => e.id == message.id) : null
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
console.log({ error: { reason: 'Invalid ID', message } })
|
console.error({ error: { reason: 'Invalid ID', message } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
console.log(message)
|
console.log(message)
|
||||||
|
}
|
||||||
|
|
||||||
if (message.action == 'attack') {
|
if (message.action == 'attack') {
|
||||||
entity.attackAction(message.x, message.y)
|
entity.attackAction(new Vector2(message.x, message.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.action == 'cast') {
|
if (message.action == 'cast') {
|
||||||
entity.castAction(message.slot, message.x, message.y)
|
entity.castAction(message.slot, new Vector2(message.x, message.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.action == 'halt') {
|
if (message.action == 'halt') {
|
||||||
@@ -53,7 +55,7 @@ app.ws('/ws', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.action == 'move') {
|
if (message.action == 'move') {
|
||||||
entity.moveAction(message.x, message.y)
|
entity.moveAction(new Vector2(message.x, message.y))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -65,6 +67,7 @@ function laneScenario() {
|
|||||||
team: Team.blue,
|
team: Team.blue,
|
||||||
}))
|
}))
|
||||||
game.spawnEntity(player1)
|
game.spawnEntity(player1)
|
||||||
|
player1.attackAction(new Vector2(500, 150))
|
||||||
|
|
||||||
const player2 = new Entity(Template.player({
|
const player2 = new Entity(Template.player({
|
||||||
id: '2',
|
id: '2',
|
||||||
@@ -72,12 +75,10 @@ function laneScenario() {
|
|||||||
team: Team.red,
|
team: Team.red,
|
||||||
}))
|
}))
|
||||||
game.spawnEntity(player2)
|
game.spawnEntity(player2)
|
||||||
|
player2.attackAction(new Vector2(1600, 1800))
|
||||||
|
|
||||||
player1.attackAction(500, 150)
|
const midWallStart = new Vector2(600, 600)
|
||||||
player2.attackAction(1600, 1800)
|
const midWallEnd = new Vector2(1400, 1400)
|
||||||
|
|
||||||
const midWallStart = new Vector2(400, 400)
|
|
||||||
const midWallEnd = new Vector2(1600, 1600)
|
|
||||||
const midWallMiddle = new Vector2(800, 1200)
|
const midWallMiddle = new Vector2(800, 1200)
|
||||||
const midWallThickness = midWallEnd.clone().sub(midWallStart).rotateAround(new Vector2(), -Math.PI / 2).normalize().multiplyScalar(50)
|
const midWallThickness = midWallEnd.clone().sub(midWallStart).rotateAround(new Vector2(), -Math.PI / 2).normalize().multiplyScalar(50)
|
||||||
const midWallPoints = [
|
const midWallPoints = [
|
||||||
@@ -89,13 +90,13 @@ function laneScenario() {
|
|||||||
midWallStart.clone().add(midWallThickness),
|
midWallStart.clone().add(midWallThickness),
|
||||||
]
|
]
|
||||||
|
|
||||||
const midNorthWallOffset = new Vector2(-200, 200)
|
const midNorthWallOffset = new Vector2(-400, 400)
|
||||||
const midNorthWallPoints = midWallPoints.map((p) => p.clone().add(midNorthWallOffset))
|
const midNorthWallPoints = midWallPoints.map((p) => p.clone().add(midNorthWallOffset))
|
||||||
const midNorthWall = new Terrain(midNorthWallPoints)
|
const midNorthWall = new Terrain(midNorthWallPoints)
|
||||||
midNorthWall.id = 'midNorthWall'
|
midNorthWall.id = 'midNorthWall'
|
||||||
game.addTerrain(midNorthWall)
|
game.addTerrain(midNorthWall)
|
||||||
|
|
||||||
const midSouthWallOffset = new Vector2(200, -200)
|
const midSouthWallOffset = new Vector2(0, 0)
|
||||||
const midSouthWallPoints = midWallPoints.map((p) => p.clone().add(midSouthWallOffset))
|
const midSouthWallPoints = midWallPoints.map((p) => p.clone().add(midSouthWallOffset))
|
||||||
const midSouthWall = new Terrain(midSouthWallPoints)
|
const midSouthWall = new Terrain(midSouthWallPoints)
|
||||||
midSouthWall.id = 'midSouthWall'
|
midSouthWall.id = 'midSouthWall'
|
||||||
@@ -104,14 +105,16 @@ function laneScenario() {
|
|||||||
const gameLogic = function gameLogic() {
|
const gameLogic = function gameLogic() {
|
||||||
const game = this
|
const game = this
|
||||||
|
|
||||||
|
const blueRoute = [new Vector2(600, 1350), new Vector2(1900, 1900)]
|
||||||
|
const redRoute = [new Vector2(600, 1350), new Vector2(100, 100)]
|
||||||
if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
|
if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
|
||||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false })))
|
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: blueRoute })))
|
||||||
game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false })))
|
game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: redRoute })))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
|
if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
|
||||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true })))
|
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: blueRoute })))
|
||||||
game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true })))
|
game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true, route: redRoute })))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
game.logic = gameLogic
|
game.logic = gameLogic
|
||||||
|
|||||||
+35
-13
@@ -7,7 +7,7 @@ export default class Template {
|
|||||||
return {
|
return {
|
||||||
abilities: [options.ranged ? Ability.rangedAttack : Ability.meleeAttack, null, null, null],
|
abilities: [options.ranged ? Ability.rangedAttack : Ability.meleeAttack, null, null, null],
|
||||||
height: options.ranged ? 40 : 38,
|
height: options.ranged ? 40 : 38,
|
||||||
logic: this.#minionLogic(team),
|
logic: this.#minionLogic(options.route),
|
||||||
maxHealth: options.ranged ? 300 : 450,
|
maxHealth: options.ranged ? 300 : 450,
|
||||||
position: team == Team.blue ? new Vector2(200, 200) : new Vector2(1800, 1800),
|
position: team == Team.blue ? new Vector2(200, 200) : new Vector2(1800, 1800),
|
||||||
radius: options.ranged ? 46 : 48,
|
radius: options.ranged ? 46 : 48,
|
||||||
@@ -33,33 +33,55 @@ export default class Template {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static #minionLogic(team) {
|
// TODO: fix disabled incremental pathing causes lag spikes
|
||||||
const finalGoal = team == Team.blue ? new Vector2(1900, 1900) : new Vector2(100, 100)
|
// TODO: minion aggro
|
||||||
const subGoal = new Vector2(850, 1150)
|
static #minionLogic(route = []) {
|
||||||
const subGoalCheck = team == Team.blue ? ((entity) => entity.position.x < 800 || entity.position.y < 1100) : ((entity) => entity.position.x > 900 || entity.position.y > 1200)
|
const checkpointSize = 300
|
||||||
|
const incrementalPathing = 100
|
||||||
|
|
||||||
return function builtMinionLogic() {
|
return function builtMinionLogic() {
|
||||||
const entity = this
|
const entity = this
|
||||||
if (entity.dead) { entity.despawn() }
|
if (entity.dead) { entity.despawn() }
|
||||||
|
|
||||||
let goal = finalGoal
|
if (route.length > 0) {
|
||||||
if (subGoalCheck(entity)) {
|
const routeIndex = entity.memory.routeCheckpoint ?? 0
|
||||||
goal = subGoal
|
const goal = route[routeIndex].clone()
|
||||||
|
const currentTick = entity.game?.currentTick ?? 0
|
||||||
|
if (goal instanceof Vector2) {
|
||||||
|
if (entity.distanceTo(goal) < checkpointSize) {
|
||||||
|
if (routeIndex + 1 < route.length) {
|
||||||
|
entity.memory.routeCheckpoint = routeIndex + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(100)
|
if ((entity.memory.incrementalPathingTimeout ?? -Infinity) < currentTick) {
|
||||||
const fakeDestination = entity.position.clone().add(direction)
|
const distanceToGoal = entity.distanceTo(goal)
|
||||||
entity.attackAction(fakeDestination.x, fakeDestination.y)
|
if (distanceToGoal > entity.memory.distanceToGoal ?? -Infinity) {
|
||||||
|
entity.memory.incrementalPathingTimeout = currentTick + (1 * (entity.game.tickRate ?? 1))
|
||||||
|
}
|
||||||
|
else if (distanceToGoal > incrementalPathing) {
|
||||||
|
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(incrementalPathing)
|
||||||
|
goal.copy(entity.position.clone().add(direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.memory.distanceToGoal = distanceToGoal
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.attackAction(goal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.position.equals(route.at(-1))) {
|
||||||
|
entity.despawn()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: proper respawn
|
// TODO: proper respawn
|
||||||
static #playerLogic() {
|
static #playerLogic() {
|
||||||
return function playerLogic() {
|
|
||||||
const entity = this
|
const entity = this
|
||||||
if (entity.dead) {
|
if (entity.dead) {
|
||||||
entity.respawn()
|
entity.respawn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user