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 Team from './team.js' import Ability from './ability.js' const app = new WebSocketExpress() const port = 1280 const game = new Game() app.use('/', express.static('public')) app.use('/three/', express.static('node_modules/three')) app.use('/@tweenjs/', express.static('node_modules/@tweenjs')) app.use(express.urlencoded({ extended: true })) app.ws('/ws', async (req, res) => { const websocket = await res.accept() const subscription = () => websocket.send(JSON.stringify(game.state())) game.eventEmitter.on('tick', subscription) websocket.on('close', () => { game.eventEmitter.removeListener('tick', subscription) }) websocket.on('message', (rawData) => { const message = JSON.parse(rawData) const entity = message.id != null ? game.entities.find((e) => e.id == message.id) : null if (entity == null) { console.log({ error: { reason: 'Invalid ID', message } }) return } 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') { entity.attackAction(message.x, message.y) } if (message.action == 'cast') { entity.castAction(message.slot, message.x, message.y) } if (message.action == 'halt') { entity.haltAction() } if (message.action == 'stop') { entity.stopAction() } if (message.action == 'move') { entity.moveAction(message.x, message.y) } }) }) // 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() { const entity1 = new Entity({ id: '1', health: 100, height: 100, maxHealth: 100, position: new Vector2(500, 150), radius: 50, team: Team.blue, }) game.spawnEntity(entity1) const entity2 = new Entity({ id: '2', health: 100, height: 100, maxHealth: 100, position: new Vector2(1600, 1800), radius: 50, team: Team.red, }) game.spawnEntity(entity2) const midWallStart = new Vector2(400, 400) const midWallEnd = new Vector2(1600, 1600) const midWallMiddle = new Vector2(800, 1200) const midWallThickness = midWallEnd.clone().sub(midWallStart).rotateAround(new Vector2(), -Math.PI / 2).normalize().multiplyScalar(50) const midWallPoints = [ midWallStart, midWallMiddle, midWallEnd, midWallEnd.clone().add(midWallThickness), midWallMiddle.clone().add(midWallThickness), midWallStart.clone().add(midWallThickness), ] const midNorthWallOffset = new Vector2(-200, 200) const midNorthWallPoints = midWallPoints.map((p) => p.clone().add(midNorthWallOffset)) const midNorthWall = new Terrain(midNorthWallPoints) midNorthWall.id = 'midNorthWall' game.addTerrain(midNorthWall) const midSouthWallOffset = new Vector2(200, -200) const midSouthWallPoints = midWallPoints.map((p) => p.clone().add(midSouthWallOffset)) const midSouthWall = new Terrain(midSouthWallPoints) midSouthWall.id = 'midSouthWall' game.addTerrain(midSouthWall) // TODO: proper death and respawn const playerLogic = function playerLogic() { const entity = this if (entity.health <= 0) { if (entity.id == '1' || entity.id == '2') { entity.health = entity.maxHealth if (entity.id == '1') { entity.teleport(new Vector2(500, 150)) } if (entity.id == '2') { entity.teleport(new Vector2(1600, 1800)) } } } } entity1.logic = playerLogic entity2.logic = playerLogic const blueMinionLogic = function minionLogic() { const entity = this let goal = new Vector2(1900, 1900) if (entity.position.x < 800 || entity.position.y < 1100) { 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) if (entity.health <= 0) { entity.despawn() } } const redMinionLogic = function minionLogic() { const entity = this 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) if (entity.health <= 0) { entity.despawn() } } const minionTemplate = { health: 20, maxHealth: 20, radius: 30, speed: 300, } const gameLogic = function gameLogic() { const game = this const blueMinion = new Entity({ ...minionTemplate, logic: blueMinionLogic, team: Team.blue, position: new Vector2(200, 200), }) // blueMinion.scheduledPathfinding = game.entities.length % game.tickRate const blueMeleeMinion = new Entity({ ...minionTemplate, logic: blueMinionLogic, team: Team.blue, position: new Vector2(200, 200), }) blueMeleeMinion.abilities[0] = Ability.meleeAttack const redMinion = new Entity({ ...minionTemplate, logic: redMinionLogic, team: Team.red, position: new Vector2(1800, 1800), }) // redMinion.scheduledPathfinding = game.entities.length % game.tickRate const redMeleeMinion = new Entity({ ...minionTemplate, logic: redMinionLogic, 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)) { game.spawnEntity(blueMeleeMinion) game.spawnEntity(redMeleeMinion) } 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 } app.listen(port, () => { console.log(`Server started! Visit http://localhost:${port}`) laneScenario() game.start() })