rely on stringification instead of state reports
This commit is contained in:
+73
-6
@@ -11,8 +11,9 @@ scene.background = backgroundColor
|
|||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
renderer.setAnimationLoop(render)
|
renderer.setAnimationLoop(render)
|
||||||
camera.position.set(5, -12, 20)
|
camera.position.set(5, -12, 20)
|
||||||
// camera.position.set(5, -12, 10)
|
|
||||||
camera.rotation.set((56 / 180) * Math.PI, 0, 0)
|
camera.rotation.set((56 / 180) * Math.PI, 0, 0)
|
||||||
|
camera.zoom += 0.4
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
camera.layers.enable(1)
|
camera.layers.enable(1)
|
||||||
camera.layers.enable(2)
|
camera.layers.enable(2)
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ const entities = {}
|
|||||||
const projectiles = {}
|
const projectiles = {}
|
||||||
const positionTweens = {}
|
const positionTweens = {}
|
||||||
const terrains = {}
|
const terrains = {}
|
||||||
|
let state = { abilities: [], entities: [], terrains: [], projectiles: [] }
|
||||||
|
|
||||||
const geometry = new THREE.PlaneGeometry(0, 0)
|
const geometry = new THREE.PlaneGeometry(0, 0)
|
||||||
const material = new THREE.MeshToonMaterial({ color: 0x115011 })
|
const material = new THREE.MeshToonMaterial({ color: 0x115011 })
|
||||||
@@ -164,7 +166,71 @@ function connectWebSocket() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
websocket.onmessage = (event) => {
|
websocket.onmessage = (event) => {
|
||||||
let state = JSON.parse(event.data)
|
state.byteSize = new Blob([event.data]).size
|
||||||
|
const stateUpdates = JSON.parse(event.data)
|
||||||
|
for (const [key, value] of Object.entries(stateUpdates)) {
|
||||||
|
if (!['abilities', 'terrains', 'entities', 'projectiles'].includes(key)) {
|
||||||
|
state[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateUpdates.abilities != null) {
|
||||||
|
const ids = stateUpdates.abilities.map((it) => it.id)
|
||||||
|
state.abilities = state.abilities.filter((it) => ids.includes(it.id))
|
||||||
|
for (const ability of stateUpdates.abilities ?? []) {
|
||||||
|
const index = state?.abilities?.findIndex((it) => it.id == ability.id)
|
||||||
|
if (index > -1) {
|
||||||
|
state.abilities[index] = {...state.abilities[index], ...ability}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state.abilities.push(ability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateUpdates.entities != null) {
|
||||||
|
const ids = stateUpdates.entities.map((it) => it.id)
|
||||||
|
state.entities = state.entities.filter((it) => ids.includes(it.id))
|
||||||
|
for (const entity of stateUpdates.entities ?? []) {
|
||||||
|
const index = state?.entities?.findIndex((it) => it.id == entity.id)
|
||||||
|
if (index > -1) {
|
||||||
|
state.entities[index] = {...state.entities[index], ...entity}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state.entities.push(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateUpdates.terrains != null) {
|
||||||
|
const ids = stateUpdates.terrains.map((it) => it.id)
|
||||||
|
state.terrains = state.terrains.filter((it) => ids.includes(it.id))
|
||||||
|
for (const terrain of stateUpdates.terrains ?? []) {
|
||||||
|
const index = state?.terrains?.findIndex((it) => it.id == terrain.id)
|
||||||
|
if (index > -1) {
|
||||||
|
state.terrains[index] = {...state.terrains[index], ...terrain}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state.terrains.push(terrain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateUpdates.projectiles != null) {
|
||||||
|
const ids = stateUpdates.projectiles.map((it) => it.id)
|
||||||
|
state.projectiles = state.projectiles.filter((it) => ids.includes(it.id))
|
||||||
|
for (const projectile of stateUpdates.projectiles) {
|
||||||
|
const index = state?.projectiles?.findIndex((it) => it.id == projectile.id)
|
||||||
|
if (index > -1) {
|
||||||
|
state.projectiles[index] = {...state.projectiles[index], ...projectile}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state.projectiles.push(projectile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(state)
|
||||||
|
|
||||||
if (state.width != null && state.height != null && (ground.geometry.attributes.width != state.width || ground.geometry.attributes.height != state.height)) {
|
if (state.width != null && state.height != null && (ground.geometry.attributes.width != state.width || ground.geometry.attributes.height != state.height)) {
|
||||||
ground.geometry = new THREE.PlaneGeometry(state.width / 100, state.height / 100)
|
ground.geometry = new THREE.PlaneGeometry(state.width / 100, state.height / 100)
|
||||||
@@ -246,13 +312,13 @@ function connectWebSocket() {
|
|||||||
projectile.layers.set(2)
|
projectile.layers.set(2)
|
||||||
scene.add(projectile)
|
scene.add(projectile)
|
||||||
|
|
||||||
|
projectile.rotation.x = Math.PI / 2 // needed for the team marker...
|
||||||
const teamMaterial = teamMaterials['projectile']
|
const teamMaterial = teamMaterials['projectile']
|
||||||
const teamMarker = new THREE.Mesh(new THREE.CylinderGeometry((p.radius) / 100, (p.radius) / 100, 1), teamMaterial)
|
const teamMarker = new THREE.Mesh(new THREE.CylinderGeometry((p.radius) / 100, (p.radius) / 100, 1), teamMaterial)
|
||||||
const teamMarkerSize = 4000
|
const teamMarkerSize = 4000
|
||||||
teamMarker.scale.y = p.height / teamMarkerSize
|
teamMarker.scale.y = p.height / teamMarkerSize
|
||||||
teamMarker.position.y = (p.height / (teamMarkerSize * 2)) - (p.height / 100)
|
teamMarker.position.y = (p.height / (teamMarkerSize * 2)) - (p.height / 100)
|
||||||
projectile.rotation.x = Math.PI / 2
|
teamMarker.layers.set(2)
|
||||||
projectile.layers.set(2)
|
|
||||||
projectile.add(teamMarker)
|
projectile.add(teamMarker)
|
||||||
|
|
||||||
projectiles[p.id] = projectile
|
projectiles[p.id] = projectile
|
||||||
@@ -295,7 +361,8 @@ function connectWebSocket() {
|
|||||||
if (player != null) {
|
if (player != null) {
|
||||||
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 abilityId = player.abilities[abilityIndex]
|
||||||
|
const ability = state.abilities.find((it) => it.id == abilityId)
|
||||||
const lastCast = player.cooldowns[ability.id] ?? -Infinity
|
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
|
||||||
@@ -342,7 +409,7 @@ function connectWebSocket() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('state').innerHTML = JSON.stringify(state, null, 2)
|
// document.getElementById('state').innerHTML = JSON.stringify(stateUpdates, null, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.debug-panel {
|
.debug-panel {
|
||||||
/* display: none; */
|
display: none;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export default class Ability {
|
|||||||
effect: function straightShotEffect(caster, cursor) {
|
effect: function straightShotEffect(caster, cursor) {
|
||||||
const ability = this
|
const ability = this
|
||||||
const straightShotCollision = function straightShotCollision(projectile, collidingEntity) {
|
const straightShotCollision = function straightShotCollision(projectile, collidingEntity) {
|
||||||
|
if (collidingEntity == null) { return }
|
||||||
|
if (collidingEntity.team == (projectile.owner?.team ?? 'unknown')) { return }
|
||||||
|
|
||||||
collidingEntity.damage(ability.damage)
|
collidingEntity.damage(ability.damage)
|
||||||
projectile.despawn()
|
projectile.despawn()
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-17
@@ -79,21 +79,12 @@ export default class Entity {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustWaypoint(waypoint, direction) {
|
|
||||||
return SATX.clamp(
|
|
||||||
waypoint.clone().add(direction.clone().multiplyScalar(this.radius)),
|
|
||||||
this.game?.width,
|
|
||||||
this.game?.height,
|
|
||||||
this.radius,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
attackAction(cursor) {
|
attackAction(cursor) {
|
||||||
this.moveAction(cursor, true)
|
this.moveAction(cursor, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
castAction(slot, cursor, halt = false) {
|
castAction(slot, cursor, halt = false) {
|
||||||
const ability = this.abilities[slot]
|
const ability = this.ability(slot)
|
||||||
if (ability == null) { return }
|
if (ability == null) { return }
|
||||||
|
|
||||||
if (this.casting != null) {
|
if (this.casting != null) {
|
||||||
@@ -143,6 +134,21 @@ export default class Entity {
|
|||||||
|
|
||||||
// --- Actions above --- //
|
// --- Actions above --- //
|
||||||
|
|
||||||
|
ability(slot) {
|
||||||
|
if (this.abilities[slot] != null) {
|
||||||
|
return this.game?.abilities.find((it) => it.id == this.abilities[slot])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustWaypoint(waypoint, direction) {
|
||||||
|
return SATX.clamp(
|
||||||
|
waypoint.clone().add(direction.clone().multiplyScalar(this.radius)),
|
||||||
|
this.game?.width,
|
||||||
|
this.game?.height,
|
||||||
|
this.radius,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
collidables() {
|
collidables() {
|
||||||
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id).map((e) => e.collider())
|
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id).map((e) => e.collider())
|
||||||
const terrainColliders = (this.game?.terrains ?? []).map((t) => t.colliders()).flat()
|
const terrainColliders = (this.game?.terrains ?? []).map((t) => t.colliders()).flat()
|
||||||
@@ -200,12 +206,6 @@ export default class Entity {
|
|||||||
this.dead = false
|
this.dead = false
|
||||||
}
|
}
|
||||||
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
teleport(cursor) {
|
teleport(cursor) {
|
||||||
this.position = cursor.clone()
|
this.position = cursor.clone()
|
||||||
this.fixPosition()
|
this.fixPosition()
|
||||||
@@ -264,7 +264,8 @@ export default class Entity {
|
|||||||
|
|
||||||
if (this.#attacking) {
|
if (this.#attacking) {
|
||||||
const cursor = this.#dest ?? this.position
|
const cursor = this.#dest ?? this.position
|
||||||
const basicAttack = this.abilities[0]
|
const basicAttack = this.ability(0)
|
||||||
|
if (basicAttack != null) {
|
||||||
const target = this.closestTargetTo(cursor, basicAttack.range)
|
const target = this.closestTargetTo(cursor, basicAttack.range)
|
||||||
if (target != null && this.distanceTo(target.position) < basicAttack.range) {
|
if (target != null && this.distanceTo(target.position) < basicAttack.range) {
|
||||||
const cooldown = this.game?.secToTick(basicAttack.cooldown) ?? 0
|
const cooldown = this.game?.secToTick(basicAttack.cooldown) ?? 0
|
||||||
@@ -276,6 +277,7 @@ export default class Entity {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.#moving || this.#dest == null) { return false }
|
if (!this.#moving || this.#dest == null) { return false }
|
||||||
|
|
||||||
|
|||||||
+13
-23
@@ -1,31 +1,30 @@
|
|||||||
import { EventEmitter } from 'node:events'
|
import { EventEmitter } from 'node:events'
|
||||||
|
import Ability from './ability.js'
|
||||||
import Entity from './entity.js'
|
import Entity from './entity.js'
|
||||||
import Projectile from './projectile.js'
|
import Projectile from './projectile.js'
|
||||||
import Terrain from './terrain.js'
|
import Terrain from './terrain.js'
|
||||||
|
|
||||||
export default class Game {
|
export default class Game {
|
||||||
|
abilities = Object.values({...Ability})
|
||||||
averageTick = 0
|
averageTick = 0
|
||||||
currentTick = 0
|
currentTick = 0
|
||||||
|
entities = []
|
||||||
height = 2000
|
height = 2000
|
||||||
|
projectiles = []
|
||||||
secondToSlowestTick = 0
|
secondToSlowestTick = 0
|
||||||
|
terrains = []
|
||||||
tickRate = 30
|
tickRate = 30
|
||||||
width = 2000
|
width = 2000
|
||||||
|
|
||||||
#currentTiming = 0
|
#currentTiming = 0
|
||||||
#entities = []
|
|
||||||
#eventEmitter = new EventEmitter()
|
#eventEmitter = new EventEmitter()
|
||||||
#logic = null
|
#logic = null
|
||||||
#nextTickAt = 0
|
#nextTickAt = 0
|
||||||
#projectiles = []
|
|
||||||
#terrains = []
|
|
||||||
#tickBudget = 1000 / this.tickRate
|
#tickBudget = 1000 / this.tickRate
|
||||||
#timings = new Float32Array(this.tickRate)
|
#timings = new Float32Array(this.tickRate)
|
||||||
|
|
||||||
get logic() { return this.#logic }
|
get logic() { return this.#logic }
|
||||||
get entities() { return this.#entities }
|
|
||||||
get eventEmitter() { return this.#eventEmitter }
|
get eventEmitter() { return this.#eventEmitter }
|
||||||
get projectiles() { return this.#projectiles }
|
|
||||||
get terrains() { return this.#terrains }
|
|
||||||
get tickBudget() { return this.#tickBudget }
|
get tickBudget() { return this.#tickBudget }
|
||||||
set logic(value) { this.#logic = value }
|
set logic(value) { this.#logic = value }
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ export default class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addTerrain(terrain) {
|
addTerrain(terrain) {
|
||||||
this.#terrains.push(terrain)
|
this.terrains.push(terrain)
|
||||||
}
|
}
|
||||||
|
|
||||||
despawn(object) {
|
despawn(object) {
|
||||||
@@ -45,17 +44,17 @@ export default class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
despawnEntity(entity) {
|
despawnEntity(entity) {
|
||||||
this.#entities = this.#entities.filter((e) => e.id != entity.id)
|
this.entities = this.entities.filter((e) => e.id != entity.id)
|
||||||
entity.game = null
|
entity.game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
despawnProjectile(projectile) {
|
despawnProjectile(projectile) {
|
||||||
this.#projectiles = this.#projectiles.filter((p) => p.id != projectile.id)
|
this.projectiles = this.projectiles.filter((p) => p.id != projectile.id)
|
||||||
projectile.game = null
|
projectile.game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTerrain(terrain) {
|
removeTerrain(terrain) {
|
||||||
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
|
this.terrains = this.terrains.filter((t) => t.id != terrain.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
secToTick(sec) {
|
secToTick(sec) {
|
||||||
@@ -70,31 +69,22 @@ export default class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spawnEntity(entity) {
|
spawnEntity(entity) {
|
||||||
this.#entities.push(entity)
|
this.entities.push(entity)
|
||||||
entity.game = this
|
entity.game = this
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnProjectile(projectile) {
|
spawnProjectile(projectile) {
|
||||||
this.#projectiles.push(projectile)
|
this.projectiles.push(projectile)
|
||||||
projectile.game = this
|
projectile.game = this
|
||||||
}
|
}
|
||||||
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
entities: this.#entities.map((e) => e.state()),
|
|
||||||
terrains: this.#terrains.map((t) => t.state()),
|
|
||||||
projectiles: this.#projectiles.map((p) => p.state()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
setInterval(() => this.#gameLoop(), 1)
|
setInterval(() => this.#gameLoop(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.#entities.forEach((e) => e.update())
|
this.entities.forEach((e) => e.update())
|
||||||
this.#projectiles.forEach((p) => p.update())
|
this.projectiles.forEach((p) => p.update())
|
||||||
if (this.#logic != null) {
|
if (this.#logic != null) {
|
||||||
this.#logic()
|
this.#logic()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ app.use(express.urlencoded({ extended: true }))
|
|||||||
app.ws('/ws', async (req, res) => {
|
app.ws('/ws', async (req, res) => {
|
||||||
const websocket = await res.accept()
|
const websocket = await res.accept()
|
||||||
|
|
||||||
const subscription = () => websocket.send(JSON.stringify(game.state()))
|
const subscription = () => websocket.send(JSON.stringify(game))
|
||||||
game.eventEmitter.on('tick', subscription)
|
game.eventEmitter.on('tick', subscription)
|
||||||
|
|
||||||
websocket.on('close', () => {
|
websocket.on('close', () => {
|
||||||
|
|||||||
@@ -52,12 +52,6 @@ export default class Projectile {
|
|||||||
this.game?.despawn(this)
|
this.game?.despawn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.#move()
|
this.#move()
|
||||||
if (this.onCollide != null) { this.checkCollisions(this.collider()) }
|
if (this.onCollide != null) { this.checkCollisions(this.collider()) }
|
||||||
|
|||||||
+5
-5
@@ -5,7 +5,7 @@ import Team from './team.js'
|
|||||||
export default class Template {
|
export default class Template {
|
||||||
static minion(team, options = {}) {
|
static minion(team, options = {}) {
|
||||||
return {
|
return {
|
||||||
abilities: [options.ranged ? Ability.rangedAttack : Ability.meleeAttack, null, null, null],
|
abilities: [options.ranged ? Ability.rangedAttack.id : Ability.meleeAttack.id, null, null, null],
|
||||||
height: options.ranged ? 40 : 38,
|
height: options.ranged ? 40 : 38,
|
||||||
logic: this.#minionLogic(options.route),
|
logic: this.#minionLogic(options.route),
|
||||||
maxHealth: options.ranged ? 300 : 450,
|
maxHealth: options.ranged ? 300 : 450,
|
||||||
@@ -20,10 +20,10 @@ export default class Template {
|
|||||||
static player(overrides) {
|
static player(overrides) {
|
||||||
return {
|
return {
|
||||||
abilities: [
|
abilities: [
|
||||||
Ability.rangedAttack,
|
Ability.rangedAttack.id,
|
||||||
Ability.straightShot,
|
Ability.straightShot.id,
|
||||||
Ability.shieldThrow,
|
Ability.shieldThrow.id,
|
||||||
Ability.blink,
|
Ability.blink.id,
|
||||||
],
|
],
|
||||||
height: 80,
|
height: 80,
|
||||||
logic: this.#playerLogic,
|
logic: this.#playerLogic,
|
||||||
|
|||||||
@@ -45,12 +45,6 @@ export default class Terrain {
|
|||||||
|
|
||||||
colliders() { return this.#colliders }
|
colliders() { return this.#colliders }
|
||||||
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#shape() {
|
#shape() {
|
||||||
const complexShape = new Shape()
|
const complexShape = new Shape()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user