fix auto-attack targeting

This commit is contained in:
2025-01-13 11:45:26 +09:00
parent 49a4d3e924
commit 03bbea4862
8 changed files with 32 additions and 134 deletions
-7
View File
@@ -10,7 +10,6 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@tweenjs/tween.js": "^25.0.0", "@tweenjs/tween.js": "^25.0.0",
"quickhull": "^1.0.3",
"sat": "^0.9.0", "sat": "^0.9.0",
"three": "^0.171.0", "three": "^0.171.0",
"websocket-express": "^3.1.2" "websocket-express": "^3.1.2"
@@ -753,12 +752,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/quickhull": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/quickhull/-/quickhull-1.0.3.tgz",
"integrity": "sha512-AQbLaXdzGDJdO9Mu3qY/NY5JWlDqIutCLW8vJbsQTq+/bydIZeltnMVRKCElp81Y5/uRm4Yw/RsMdcltFYsS6w==",
"license": "MIT"
},
"node_modules/range-parser": { "node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
-1
View File
@@ -11,7 +11,6 @@
"description": "", "description": "",
"dependencies": { "dependencies": {
"@tweenjs/tween.js": "^25.0.0", "@tweenjs/tween.js": "^25.0.0",
"quickhull": "^1.0.3",
"sat": "^0.9.0", "sat": "^0.9.0",
"three": "^0.171.0", "three": "^0.171.0",
"websocket-express": "^3.1.2" "websocket-express": "^3.1.2"
-9
View File
@@ -184,8 +184,6 @@ function connectWebSocket() {
entity.position.set(e.position.x / 100, e.position.y / 100, e.height / 100) entity.position.set(e.position.x / 100, e.position.y / 100, e.height / 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.height / 100) + hpMargin, 0) maxHp.position.set(0, (e.height / 100) + hpMargin, 0)
@@ -386,13 +384,6 @@ window.addEventListener('load', () => {
if (event.code == 'KeyE') { if (event.code == 'KeyE') {
websocket.send(JSON.stringify({ action: 'cast', slot: 3, id: playerId, x, y })) websocket.send(JSON.stringify({ action: 'cast', slot: 3, id: playerId, x, y }))
} }
if (event.code == 'KeyD') {
websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y }))
}
if (event.code == 'KeyF') {
websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y }))
}
} }
}) })
+1 -1
View File
@@ -68,7 +68,7 @@ export default class Ability {
let closest = null let closest = null
let distance = Infinity let distance = Infinity
caster.game?.entities.filter((e) => e.team != caster.team && e.position.clone().sub(caster.position).length() < ability.range).forEach((e) => { 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 const newDistance = e.position.clone().sub(cursor).length()
if (newDistance < distance) { if (newDistance < distance) {
closest = e closest = e
distance = newDistance distance = newDistance
+13 -18
View File
@@ -27,6 +27,7 @@ export default class Entity {
#dest = null #dest = null
#game = null #game = null
#logic = null #logic = null
#move = false
#path = [] #path = []
#position = new Vector2() #position = new Vector2()
#scheduledPathfinding = null #scheduledPathfinding = null
@@ -87,7 +88,7 @@ export default class Entity {
this.moveAction(x, y, true) this.moveAction(x, y, true)
} }
castAction(slot, x, y, clearDestination = true) { castAction(slot, x, y, halt = true) {
const ability = this.abilities[slot] const ability = this.abilities[slot]
if (this.casting != null) { if (this.casting != null) {
@@ -99,8 +100,8 @@ export default class Entity {
return false return false
} }
if (clearDestination) { if (halt) {
this.#dest = null this.#move = false
} }
const cursor = new Vector2(x, y) const cursor = new Vector2(x, y)
@@ -117,21 +118,22 @@ export default class Entity {
} }
haltAction() { haltAction() {
this.#dest = null this.#move = false
} }
moveAction(x, y, attack = false) { moveAction(x, y, attack = false) {
this.#attack = attack
if (this.casting != null && (!this.#attack || this.casting.ability.id != this.abilities[0].id)) { if (this.casting != null && (!this.#attack || this.casting.ability.id != this.abilities[0].id)) {
this.casting = null this.casting = null
} }
this.#attack = attack
this.#move = true
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height) this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
} }
stopAction() { stopAction() {
this.casting = null this.casting = null
this.#dest = null this.#move = true
this.#attack = false this.#attack = false
} }
@@ -160,14 +162,6 @@ export default class Entity {
return entityColliders.concat(terrainColliders) return entityColliders.concat(terrainColliders)
} }
// DEPRECATED: hulls were a failed concept for position fixing
collidableHulls() {
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id).map((e) => e.collider)
const terrainColliders = (this.game?.terrains ?? []).map((t) => t.hull)
return entityColliders.concat(terrainColliders)
}
cooldown(id) { cooldown(id) {
this.cooldowns[id] = this.game?.currentTick ?? 0 this.cooldowns[id] = this.game?.currentTick ?? 0
} }
@@ -207,7 +201,7 @@ export default class Entity {
this.fixPosition() this.fixPosition()
} }
takeStep(distanceTraveled = 0) { move(distanceTraveled = 0) {
if (this.casting != null) { return false } 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)) { if (this.#attack && this.game?.entities.some((e) => e.team != this.team && e.position.clone().sub(this.position).length() < this.abilities[0].range)) {
@@ -221,7 +215,7 @@ export default class Entity {
return true return true
} }
if (this.#dest == null) { return false } if (!this.#move || this.#dest == null) { return false }
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)
@@ -268,10 +262,11 @@ export default class Entity {
if (this.position.equals(destination)) { if (this.position.equals(destination)) {
this.#path = this.#path.slice(1) this.#path = this.#path.slice(1)
if (this.#path.length > 0) { if (this.#path.length > 0) {
this.takeStep(distance) this.move(distance)
} }
else { else {
this.#dest = null this.#dest = null
this.#move = false
} }
} }
} }
@@ -279,7 +274,7 @@ export default class Entity {
update() { update() {
this.cast() this.cast()
this.takeStep() this.move()
this.fixPosition() this.fixPosition()
if (this.#logic != null) { if (this.#logic != null) {
this.#logic() this.#logic()
+3 -78
View File
@@ -35,11 +35,6 @@ app.ws('/ws', async (req, res) => {
} }
console.log(message) console.log(message)
// DEPRECATED: teleporting is now directly possible via Ability...
if (message.action == 'teleport') {
entity.teleport(new Vector2(message.x, message.y))
}
if (message.action == 'attack') { if (message.action == 'attack') {
entity.attackAction(message.x, message.y) entity.attackAction(message.x, message.y)
} }
@@ -62,79 +57,6 @@ app.ws('/ws', async (req, res) => {
}) })
}) })
// function testScenario() {
// const entity1 = new Entity()
// entity1.id = '1'
// entity1.teleport(new Vector2(200, 500))
// entity1.radius = 50
// entity1.maxHealth = 100
// entity1.health = 80
// game.spawnEntity(entity1)
// const entity2 = new Entity()
// entity2.id = '2'
// entity2.teleport(new Vector2(110, 110))
// entity2.radius = 50
// entity2.maxHealth = 50
// entity2.health = 50
// game.spawnEntity(entity2)
// const horseshoe = new Terrain([
// { x: 400, y: 200 },
// { x: 600, y: 200 },
// { x: 700, y: 300 },
// { x: 650, y: 600 },
// { x: 400, y: 600 },
// { x: 400, y: 450 },
// { x: 600, y: 500 },
// { x: 600, y: 300 },
// { x: 400, y: 300 },
// ])
// horseshoe.id = 'horseshoe'
// game.addTerrain(horseshoe)
// const stopsign = new Terrain([
// { x: 800, y: 800 },
// { x: 900, y: 900 },
// { x: 900, y: 1000 },
// { x: 800, y: 1100 },
// { x: 800, y: 1100 },
// { x: 700, y: 1100 },
// { x: 600, y: 1000 },
// { x: 600, y: 900 },
// { x: 700, y: 800 },
// ])
// stopsign.id = 'stopsign'
// game.addTerrain(stopsign)
// const box = new Terrain([
// { x: 1200, y: 700 },
// { x: 1200, y: 800 },
// { x: 1300, y: 800 },
// { x: 1300, y: 700 },
// ])
// box.id = 'box'
// game.addTerrain(box)
// const diamond = new Terrain([
// { x: 1000, y: 300 },
// { x: 1100, y: 400 },
// { x: 1000, y: 500 },
// { x: 900, y: 400 },
// ])
// diamond.id = 'diamond'
// game.addTerrain(diamond)
// const pole = new Terrain([
// { x: 400, y: 1000 },
// { x: 410, y: 1000 },
// { x: 410, y: 1010 },
// { x: 400, y: 1010 },
// ])
// pole.id = 'pole'
// game.addTerrain(pole)
// }
function laneScenario() { function laneScenario() {
const entity1 = new Entity({ const entity1 = new Entity({
id: '1', id: '1',
@@ -197,6 +119,9 @@ function laneScenario() {
if (entity.id == '2') { if (entity.id == '2') {
entity.teleport(new Vector2(1600, 1800)) entity.teleport(new Vector2(1600, 1800))
} }
if (entity.id == '3') {
entity.teleport(new Vector2(1800, 1600))
}
} }
} }
} }
+14 -7
View File
@@ -4,19 +4,26 @@ import PriorityQueue from './priority-queue.js'
import SATX from './satx.js' import SATX from './satx.js'
export default class Pathfind { export default class Pathfind {
static precision = 0.001 static precision = 0.01
static multiplier = 1000 // (1 / this.precision) static multiplier = 1000000 // (1 / this.precision) * 10^expected_digit_count / 10
static key2(a, b) { static key2(a, b) {
return `${a},${b}` return `${a},${b}`
} }
// TODO: Value exceeds safe integer limit: collisions cause waypointing anomalies // Fowler-Noll-Vo hash prime and offset basis for small keyspaces
static floatKey4(a, b, c, d) { static floatKey4(a, b, c, d) {
return Math.floor(a * Pathfind.multiplier) + const prime = 16777619
Math.floor(b * Pathfind.multiplier) * Pathfind.multiplier + let result = 2166136261
Math.floor(c * Pathfind.multiplier) * Pathfind.multiplier ** 2 + result ^= Math.floor(a * Pathfind.multiplier)
Math.floor(d * Pathfind.multiplier) * Pathfind.multiplier ** 3 result *= prime
result ^= Math.floor(b * Pathfind.multiplier)
result *= prime
result ^= Math.floor(c * Pathfind.multiplier)
result *= prime
result ^= Math.floor(d * Pathfind.multiplier)
result *= prime
return result
} }
static uniqueWaypoints(waypoints) { static uniqueWaypoints(waypoints) {
-12
View File
@@ -1,4 +1,3 @@
import QuickHull from 'quickhull' // DEPRECATED: hulls were a failed concept for position fixing
import SAT from 'sat' import SAT from 'sat'
import { Shape, ShapeUtils, Vector2 } from 'three' import { Shape, ShapeUtils, Vector2 } from 'three'
@@ -26,7 +25,6 @@ export default class Terrain {
} }
get colliders() { return this.#colliders } get colliders() { return this.#colliders }
get hull() { return this.#hull } // DEPRECATED: hulls were a failed concept for position fixing
get unadjustedWaypoints() { return this.#unadjustedWaypoints } get unadjustedWaypoints() { return this.#unadjustedWaypoints }
get vertices() { return this.#vertices } get vertices() { return this.#vertices }
@@ -76,16 +74,6 @@ export default class Terrain {
} }
this.#colliders = ShapeUtils.triangulateShape(points.shape, points.holes).map(indicesToPolygon) this.#colliders = ShapeUtils.triangulateShape(points.shape, points.holes).map(indicesToPolygon)
this.#calculateHull()
}
// DEPRECATED: hulls were a failed concept for position fixing
#calculateHull() {
const vertices = QuickHull(this.#vertices.map((v) => ({ x: v.x, y: v.y }))).map((v) => new Vector2(v.x, v.y))
const first = vertices.at(0)
const satPoints = [new SAT.Vector(...first.toArray()), ...vertices.slice(1).map((v) => new SAT.Vector(...v.clone(first).sub(first).toArray()))]
this.#hull = new SAT.Polygon(satPoints[0], [new SAT.Vector(), ...satPoints.slice(1)])
} }
#calculatePosition() { #calculatePosition() {