move entity definitions to templates

This commit is contained in:
2025-01-13 16:53:12 +09:00
parent ffbc4d9803
commit 9d3fbda494
8 changed files with 93 additions and 137 deletions
+3 -8
View File
@@ -1,18 +1,12 @@
import { Vector2 } from 'three' import { Vector2 } from 'three'
import Pathfind from './pathfind.js'
import SAT from 'sat' import SAT from 'sat'
import SATX from './satx.js' import SATX from './satx.js'
import Pathfind from './pathfind.js'
import Ability from './ability.js'
import Team from './team.js' import Team from './team.js'
export default class Entity { export default class Entity {
id = crypto.randomUUID() id = crypto.randomUUID()
abilities = [ abilities = [null, null, null, null] // TODO: do something about this being an array...
Ability.rangedAttack,
Ability.straightShot,
Ability.shieldThrow,
Ability.blink,
]
casting = null casting = null
cooldowns = {} cooldowns = {}
dead = false dead = false
@@ -22,6 +16,7 @@ export default class Entity {
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
+1 -1
View File
@@ -1,7 +1,7 @@
import { EventEmitter } from 'node:events' import { EventEmitter } from 'node:events'
import Entity from './entity.js' import Entity from './entity.js'
import Terrain from './terrain.js'
import Projectile from './projectile.js' import Projectile from './projectile.js'
import Terrain from './terrain.js'
export default class Game { export default class Game {
tickRate = 30 tickRate = 30
+21 -124
View File
@@ -1,11 +1,12 @@
import express from 'express'
import { WebSocketExpress } from 'websocket-express'
import Game from './game.js'
import Entity from './entity.js'
import Terrain from './terrain.js'
import { Vector2 } from 'three' import { Vector2 } from 'three'
import Team from './team.js' import { WebSocketExpress } from 'websocket-express'
import Ability from './ability.js' import Ability from './ability.js'
import Entity from './entity.js'
import express from 'express'
import Game from './game.js'
import Team from './team.js'
import Template from './template.js'
import Terrain from './terrain.js'
const app = new WebSocketExpress() const app = new WebSocketExpress()
const port = 1280 const port = 1280
@@ -58,39 +59,22 @@ app.ws('/ws', async (req, res) => {
}) })
function laneScenario() { function laneScenario() {
// TODO: proper respawn const player1 = new Entity(Template.player({
const playerLogic = function playerLogic() {
const entity = this
if (entity.dead) {
entity.respawn()
}
}
const playerTemplate = {
height: 80,
logic: playerLogic,
maxHealth: 600,
spawnPosition: new Vector2(500, 150),
radius: 65,
}
const entity1 = new Entity({
...playerTemplate,
id: '1', id: '1',
spawnPosition: new Vector2(500, 150), spawnPosition: new Vector2(500, 150),
team: Team.blue, team: Team.blue,
}) }))
game.spawnEntity(player1)
game.spawnEntity(entity1) const player2 = new Entity(Template.player({
const entity2 = new Entity({
...playerTemplate,
id: '2', id: '2',
spawnPosition: new Vector2(1600, 1800), spawnPosition: new Vector2(1600, 1800),
team: Team.red, team: Team.red,
}) }))
game.spawnEntity(player2)
game.spawnEntity(entity2) player1.attackAction(500, 150)
player2.attackAction(1600, 1800)
const midWallStart = new Vector2(400, 400) const midWallStart = new Vector2(400, 400)
const midWallEnd = new Vector2(1600, 1600) const midWallEnd = new Vector2(1600, 1600)
@@ -117,106 +101,19 @@ function laneScenario() {
midSouthWall.id = 'midSouthWall' midSouthWall.id = 'midSouthWall'
game.addTerrain(midSouthWall) game.addTerrain(midSouthWall)
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 = finalGoal
if (subGoalCheck(entity)) {
goal = subGoal
}
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(100)
const fakeDestination = entity.position.clone().add(direction)
entity.attackAction(fakeDestination.x, fakeDestination.y)
}
}
const minionTemplate = {
height: 40,
maxHealth: 300,
radius: 48,
speed: 325,
}
const meleeMinionTemplate = {
...minionTemplate,
height: 38,
radius: 46,
maxHealth: 450,
}
const gameLogic = function gameLogic() { const gameLogic = function gameLogic() {
const game = this const game = this
const blueMinion = new Entity({ if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
...minionTemplate, game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false })))
logic: minionLogic(Team.blue), game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false })))
team: Team.blue,
position: new Vector2(200, 200),
})
const blueMeleeMinion = new Entity({
...meleeMinionTemplate,
logic: minionLogic(Team.blue),
team: Team.blue,
position: new Vector2(200, 200),
})
blueMeleeMinion.abilities[0] = Ability.meleeAttack
const redMinion = new Entity({
...minionTemplate,
logic: minionLogic(Team.red),
team: Team.red,
position: new Vector2(1800, 1800),
})
const redMeleeMinion = new Entity({
...meleeMinionTemplate,
logic: minionLogic(Team.red),
team: Team.red,
position: new Vector2(1800, 1800),
})
redMeleeMinion.abilities[0] = Ability.meleeAttack
if (game.currentTick % (30 * game.tickRate) == (0 * game.tickRate)) {
game.spawnEntity(blueMeleeMinion)
game.spawnEntity(redMeleeMinion)
} }
if (game.currentTick % (30 * game.tickRate) == (1 * game.tickRate)) { if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
game.spawnEntity(blueMeleeMinion) game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true })))
game.spawnEntity(redMeleeMinion) game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: true })))
}
if (game.currentTick % (30 * game.tickRate) == (2 * game.tickRate)) {
game.spawnEntity(blueMeleeMinion)
game.spawnEntity(redMeleeMinion)
}
if (game.currentTick % (30 * game.tickRate) == (3 * game.tickRate)) {
game.spawnEntity(blueMinion)
game.spawnEntity(redMinion)
}
if (game.currentTick % (30 * game.tickRate) == (4 * game.tickRate)) {
game.spawnEntity(blueMinion)
game.spawnEntity(redMinion)
}
if (game.currentTick % (30 * game.tickRate) == (5 * game.tickRate)) {
game.spawnEntity(blueMinion)
game.spawnEntity(redMinion)
} }
} }
entity2.attackAction(1600, 1800)
entity1.attackAction(500, 150)
game.logic = gameLogic game.logic = gameLogic
} }
-1
View File
@@ -1,4 +1,3 @@
import { Path, Vector2 } from 'three'
import Entity from './entity.js' import Entity from './entity.js'
import PriorityQueue from './priority-queue.js' import PriorityQueue from './priority-queue.js'
import SATX from './satx.js' import SATX from './satx.js'
+1 -1
View File
@@ -1,6 +1,6 @@
import { Vector2 } from 'three'
import SAT from 'sat' import SAT from 'sat'
import SATX from './satx.js' import SATX from './satx.js'
import { Vector2 } from 'three'
export default class Projectile { export default class Projectile {
id = crypto.randomUUID() id = crypto.randomUUID()
+1 -1
View File
@@ -1,6 +1,6 @@
import SAT from 'sat'
import { Vector2 } from 'three' import { Vector2 } from 'three'
import Entity from './entity.js' import Entity from './entity.js'
import SAT from 'sat'
export default class SATX { export default class SATX {
static clamp(vectorOrObject, maxX = Infinity, maxY = Infinity, radius = 0) { static clamp(vectorOrObject, maxX = Infinity, maxY = Infinity, radius = 0) {
+65
View File
@@ -0,0 +1,65 @@
import { Vector2 } from 'three'
import Ability from './ability.js'
import Team from './team.js'
export default class Template {
static minion(team, options = {}) {
return {
abilities: [options.ranged ? Ability.rangedAttack : Ability.meleeAttack, null, null, null],
height: options.ranged ? 40 : 38,
logic: this.#minionLogic(team),
maxHealth: options.ranged ? 300 : 450,
position: team == Team.blue ? new Vector2(200, 200) : new Vector2(1800, 1800),
radius: options.ranged ? 46 : 48,
speed: 325,
team,
}
}
static player(overrides) {
return {
abilities: [
Ability.rangedAttack,
Ability.straightShot,
Ability.shieldThrow,
Ability.blink,
],
height: 80,
logic: this.#playerLogic,
maxHealth: 600,
spawnPosition: new Vector2(500, 150),
radius: 65,
...overrides,
}
}
static #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 = finalGoal
if (subGoalCheck(entity)) {
goal = subGoal
}
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(100)
const fakeDestination = entity.position.clone().add(direction)
entity.attackAction(fakeDestination.x, fakeDestination.y)
}
}
// TODO: proper respawn
static #playerLogic() {
return function playerLogic() {
const entity = this
if (entity.dead) {
entity.respawn()
}
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import SAT from 'sat'
import { Shape, ShapeUtils, Vector2 } from 'three' import { Shape, ShapeUtils, Vector2 } from 'three'
import SAT from 'sat'
export default class Terrain { export default class Terrain {
id = crypto.randomUUID() id = crypto.randomUUID()