599 lines
22 KiB
JavaScript
599 lines
22 KiB
JavaScript
import * as THREE from 'three'
|
|
import { Tween } from '@tweenjs/tween.js'
|
|
import Stats from 'stats.js'
|
|
|
|
const global = (0,eval)('this')
|
|
const scene = new THREE.Scene()
|
|
const raycaster = new THREE.Raycaster()
|
|
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1000)
|
|
const renderer = new THREE.WebGLRenderer()
|
|
const backgroundColor = new THREE.Color().setHex(0x112233)
|
|
scene.background = backgroundColor
|
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
renderer.setAnimationLoop(render)
|
|
const cameraOffsetX = 0
|
|
const cameraOffsetY = -13.5
|
|
const cameraOffsetZ = 20
|
|
camera.position.set(cameraOffsetX, cameraOffsetY, cameraOffsetZ)
|
|
camera.rotation.set((34 / 180) * Math.PI, 0, 0)
|
|
camera.zoom += 0.2
|
|
camera.updateProjectionMatrix()
|
|
camera.layers.enable(1)
|
|
camera.layers.enable(2)
|
|
|
|
const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xcccccc })
|
|
const terrainMaterial = new THREE.MeshToonMaterial({ color: 0x5c4033 })
|
|
const bboxMaterial = new THREE.MeshToonMaterial({ color: 0xffd700, transparent: true, opacity: 0.2 })
|
|
const opacity = 0.3
|
|
const teamMaterials = {
|
|
blue: new THREE.MeshToonMaterial({ color: 0x4444ff }),
|
|
blueTransparent: new THREE.MeshToonMaterial({ color: 0x4444ff, transparent: true, opacity }),
|
|
neutral: new THREE.MeshToonMaterial({ color: 0x22dd22, transparent: true, opacity }),
|
|
red: new THREE.MeshToonMaterial({ color: 0xff4444 }),
|
|
redTransparent: new THREE.MeshToonMaterial({ color: 0xff4444, transparent: true, opacity }),
|
|
projectile: new THREE.MeshToonMaterial({ color: 0xff00ff, transparent: true, opacity }),
|
|
range: new THREE.MeshToonMaterial({ color: 0x00ffff, transparent: true, opacity: opacity / 2 }),
|
|
}
|
|
|
|
const minimapCameraZ = 10
|
|
const minimapCamera = new THREE.OrthographicCamera(-10, 10, 10, -10)
|
|
const minimapRenderer = new THREE.WebGLRenderer()
|
|
|
|
minimapRenderer.setSize(300, 300)
|
|
minimapRenderer.setAnimationLoop(minimapRender)
|
|
minimapCamera.position.set(10, 10, 10)
|
|
|
|
const entities = {}
|
|
const projectiles = {}
|
|
const positionTweens = {}
|
|
const terrains = {}
|
|
let state = { abilities: [], entities: [], terrains: [], projectiles: [] }
|
|
|
|
const geometry = new THREE.PlaneGeometry(0, 0)
|
|
const material = new THREE.MeshToonMaterial({ color: 0x115011 })
|
|
const ground = new THREE.Mesh(geometry, material)
|
|
scene.add(ground)
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x404040, 10)
|
|
scene.add(ambientLight)
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5)
|
|
directionalLight.position.set(-0.5, -0.05, 1)
|
|
directionalLight.power = 3000
|
|
scene.add(directionalLight)
|
|
|
|
global.THREE = THREE
|
|
global.renderer = renderer
|
|
global.camera = camera
|
|
global.scene = scene
|
|
|
|
const tweenDuration = 33
|
|
const keysDown = {}
|
|
const mouse = {}
|
|
|
|
var stats = new Stats()
|
|
stats.showPanel(0)
|
|
|
|
function render() {
|
|
stats.begin()
|
|
cameraMovement()
|
|
Object.values(positionTweens).forEach((tween) => tween.update()) // TODO: clean up tweens
|
|
renderer.render(scene, camera)
|
|
stats.end()
|
|
}
|
|
|
|
function minimapRender() {
|
|
minimapRenderer.render(scene, minimapCamera)
|
|
}
|
|
|
|
var cameraLocked = true
|
|
function followCamera() {
|
|
const entity = entities[playerId]
|
|
if (entity == null) { return }
|
|
|
|
const distanceX = Math.abs((entity.position.x + cameraOffsetX) - camera.position.x)
|
|
const distanceY = Math.abs((entity.position.y + cameraOffsetY) - camera.position.y)
|
|
|
|
camera.position.z = cameraOffsetZ
|
|
if (distanceX > 0.01) {
|
|
if (entity.position.x + cameraOffsetX > camera.position.x) {
|
|
camera.position.x += cameraSpeed * distanceX
|
|
}
|
|
if (entity.position.x + cameraOffsetX < camera.position.x) {
|
|
camera.position.x -= cameraSpeed * distanceX
|
|
}
|
|
}
|
|
else if (distanceX != 0) {
|
|
camera.position.x = entity.position.x + cameraOffsetX
|
|
}
|
|
|
|
if (distanceY > 0.01) {
|
|
if (entity.position.y + cameraOffsetY > camera.position.y) {
|
|
camera.position.y += cameraSpeed * distanceY
|
|
}
|
|
if (entity.position.y + cameraOffsetY < camera.position.y) {
|
|
camera.position.y -= cameraSpeed * distanceY
|
|
}
|
|
}
|
|
else if (distanceY != 0) {
|
|
camera.position.y = entity.position.y + cameraOffsetY
|
|
}
|
|
}
|
|
|
|
const cameraSpeed = 0.03
|
|
function cameraMovement() {
|
|
if (cameraLocked) {
|
|
followCamera()
|
|
return
|
|
}
|
|
|
|
if (keysDown.ArrowLeft) { camera.position.x -= cameraSpeed }
|
|
else if (keysDown.ArrowRight) { camera.position.x += cameraSpeed }
|
|
|
|
if (keysDown.ArrowUp) { camera.position.y += cameraSpeed }
|
|
else if (keysDown.ArrowDown) { camera.position.y -= cameraSpeed }
|
|
|
|
if (keysDown.Space) {
|
|
camera.position.set(entities[playerId].position.x + cameraOffsetX, entities[playerId].position.y + cameraOffsetY, cameraOffsetZ)
|
|
}
|
|
}
|
|
|
|
function raycastToGround() {
|
|
const canvas = renderer.domElement
|
|
raycaster.setFromCamera(new THREE.Vector2((mouse.x / canvas.clientWidth) * 2 - 1, (mouse.y / canvas.clientHeight) * -2 + 1), camera)
|
|
const intersect = raycaster.intersectObject(ground).at(0)?.point
|
|
if (intersect != null) {
|
|
return {
|
|
x: Math.round(intersect.x * 100),
|
|
y: Math.round(intersect.y * 100),
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
var websocket = null
|
|
global.websocket = null
|
|
var timerId = null
|
|
var playerId = null
|
|
|
|
function connectWebSocket() {
|
|
websocket = new WebSocket(`ws://${window.location.hostname}:1280/ws`)
|
|
global.websocket = websocket
|
|
websocket.onerror = () => websocket.close()
|
|
websocket.onopen = () => {
|
|
document.getElementById('connection').innerHTML = 'open'
|
|
clearInterval(timerId)
|
|
}
|
|
websocket.onclose = () => {
|
|
websocket = null
|
|
document.getElementById('connection').innerHTML = 'closed'
|
|
timerId = setInterval(() => {
|
|
if (websocket == null) {
|
|
connectWebSocket()
|
|
}
|
|
}, 2000)
|
|
}
|
|
|
|
websocket.onmessage = (event) => {
|
|
state.byteSize = new Blob([event.data]).size
|
|
const stateUpdates = JSON.parse(event.data)
|
|
|
|
if (stateUpdates.width != null && stateUpdates.height != null) {
|
|
state.width = stateUpdates.width
|
|
state.height = stateUpdates.height
|
|
|
|
minimapCamera.top = state.height / 200
|
|
minimapCamera.right = state.height / 200
|
|
minimapCamera.bottom = -state.height / 200
|
|
minimapCamera.left = -state.height / 200
|
|
minimapCamera.updateProjectionMatrix()
|
|
minimapCamera.position.set(state.width / 200, state.height / 200, minimapCameraZ)
|
|
}
|
|
|
|
for (const [key, value] of Object.entries(stateUpdates)) {
|
|
if (!['abilities', 'terrains', 'entities', 'projectiles', 'width', 'height'].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)) {
|
|
ground.geometry = new THREE.PlaneGeometry(state.width / 100, state.height / 100)
|
|
ground.position.set(state.width / 200, state.height / 200, 0)
|
|
}
|
|
|
|
for (const e of Object.values(entities)) {
|
|
e.userData.flaggedForRemoval = true
|
|
}
|
|
|
|
for (const e of state.entities ?? []) {
|
|
let entity
|
|
if (e.id in entities) {
|
|
entity = entities[e.id]
|
|
}
|
|
else {
|
|
const entityMaterial = teamMaterials[e.team]
|
|
entity = new THREE.Mesh(new THREE.CylinderGeometry(e.visualRadius / 100, e.visualRadius / 100, e.height / 50), entityMaterial)
|
|
entity.rotation.x = Math.PI / 2
|
|
entity.userData.type = 'entity'
|
|
entity.userData.id = e.id
|
|
entity.position.set(e.position.x / 100, e.position.y / 100, e.height / 100)
|
|
scene.add(entity)
|
|
|
|
const hpMargin = 0.4
|
|
const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 }))
|
|
maxHp.position.set(0, (e.height / 100) + hpMargin, 0)
|
|
maxHp.scale.set(1.5, 0.2, 1)
|
|
maxHp.layers.set(1)
|
|
entity.add(maxHp)
|
|
|
|
const hp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0x77ff77 }))
|
|
hp.position.set(0, 0, 0)
|
|
hp.scale.set(1, 1, 1)
|
|
hp.layers.set(1)
|
|
maxHp.add(hp)
|
|
|
|
const teamMaterial = teamMaterials[`${e.team}Transparent`]
|
|
const teamMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.radius) / 100, (e.radius) / 100, 1), teamMaterial)
|
|
const teamMarkerSize = 4000
|
|
teamMarker.scale.y = e.height / teamMarkerSize
|
|
teamMarker.position.y = (e.height / (teamMarkerSize * 2)) - (e.height / 100)
|
|
teamMarker.position.y += 0.01
|
|
teamMarker.layers.set(1)
|
|
entity.add(teamMarker)
|
|
|
|
const buffMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, transparent: true, opacity: 0.4 })
|
|
const buffMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.visualRadius + 10) / 100, (e.visualRadius + 10) / 100, 1), buffMaterial)
|
|
const buffMarkerSize = 400
|
|
buffMarker.scale.y = e.height / buffMarkerSize
|
|
buffMarker.layers.set(1)
|
|
buffMarker.visible = false
|
|
entity.add(buffMarker)
|
|
|
|
const rotationBase = new THREE.Object3D()
|
|
entity.add(rotationBase)
|
|
|
|
const castingMaterial = new THREE.MeshToonMaterial({ color: 0x10dde0, transparent: true, opacity: 0.4 })
|
|
const castingMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.height * 0.9) / 100, (e.height * 0.9) / 100, 1), castingMaterial)
|
|
const castingMarkerSize = 800
|
|
castingMarker.rotation.z = Math.PI / 2
|
|
castingMarker.position.x = (e.radius) / 100
|
|
castingMarker.scale.y = e.height / castingMarkerSize
|
|
castingMarker.layers.set(1)
|
|
buffMarker.visible = false
|
|
rotationBase.add(castingMarker)
|
|
|
|
const rangeMaterial = teamMaterials['range']
|
|
const rangeSize = (state.abilities.find((it) => it.id == e.abilities?.a)?.range ?? 0) + e.radius
|
|
const rangeMarker = new THREE.Mesh(new THREE.CylinderGeometry((rangeSize) / 100, (rangeSize) / 100, 1), rangeMaterial)
|
|
const rangeMarkerSize = 4000
|
|
rangeMarker.scale.y = e.height / rangeMarkerSize
|
|
rangeMarker.position.y = (e.height / (rangeMarkerSize * 2)) - (e.height / 100)
|
|
rangeMarker.layers.set(1)
|
|
rangeMarker.visible = false
|
|
entity.add(rangeMarker)
|
|
|
|
entities[e.id] = entity
|
|
}
|
|
|
|
entity.children.at(2).visible = e.buffs.some((it) => it.id == 'exposed') // TODO: only works for Exposed now
|
|
|
|
entity.userData.flaggedForRemoval = false
|
|
entity.children.at(3).rotation.y = e.rotation
|
|
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z: e.height / 100 }, tweenDuration).start()
|
|
|
|
const hp = entity.children.at(0).children.at(0)
|
|
const percentageHp = e.health / e.maxHealth
|
|
hp.scale.x = percentageHp
|
|
hp.position.x = -(1 - percentageHp) / 2
|
|
|
|
entity.children.at(4).visible = e.id == playerId // TODO: undo, just for clarity
|
|
entity.children.at(3).children.at(0).visible = e.casting != null
|
|
}
|
|
|
|
for (const e of Object.values(entities)) {
|
|
if (e.userData.flaggedForRemoval) {
|
|
scene.remove(e)
|
|
delete entities[e.userData.id]
|
|
delete positionTweens[e.userData.id]
|
|
}
|
|
}
|
|
|
|
for (const p of Object.values(projectiles)) {
|
|
p.userData.flaggedForRemoval = true
|
|
}
|
|
|
|
for (const p of state.projectiles ?? []) {
|
|
let projectile
|
|
if (p.id in projectiles) {
|
|
projectile = projectiles[p.id]
|
|
}
|
|
else {
|
|
projectile = new THREE.Mesh(new THREE.SphereGeometry(p.visualRadius / 100), projectileMaterial)
|
|
projectile.userData.type = 'projectile'
|
|
projectile.userData.id = p.id
|
|
projectile.position.set(p.position.x / 100, p.position.y / 100, p.height / 100)
|
|
projectile.layers.set(2)
|
|
scene.add(projectile)
|
|
|
|
projectile.rotation.x = Math.PI / 2 // needed for the team marker...
|
|
const teamMaterial = teamMaterials['projectile']
|
|
const teamMarker = new THREE.Mesh(new THREE.CylinderGeometry((p.radius) / 100, (p.radius) / 100, 1), teamMaterial)
|
|
const teamMarkerSize = 4000
|
|
teamMarker.scale.y = p.height / teamMarkerSize
|
|
teamMarker.position.y = (p.height / (teamMarkerSize * 2)) - (p.height / 100)
|
|
teamMarker.position.y += 0.01
|
|
teamMarker.layers.set(2)
|
|
projectile.add(teamMarker)
|
|
|
|
projectiles[p.id] = projectile
|
|
}
|
|
|
|
projectile.userData.flaggedForRemoval = false
|
|
positionTweens[projectile.id] = new Tween(projectile.position).to({ x: p.position.x / 100, y: p.position.y / 100, z: p.height / 100 }, tweenDuration).start()
|
|
}
|
|
|
|
for (const p of Object.values(projectiles)) {
|
|
if (p.userData.flaggedForRemoval) {
|
|
scene.remove(p)
|
|
delete projectiles[p.userData.id]
|
|
delete positionTweens[p.userData.id]
|
|
}
|
|
}
|
|
|
|
for (const t of state.terrains ?? []) {
|
|
let terrain
|
|
if (t.id in terrains) {
|
|
terrain = terrains[t.id]
|
|
}
|
|
else {
|
|
const vertices = t.relativeVertices
|
|
const shape = new THREE.Shape()
|
|
shape.moveTo(vertices.at(0).x / 100, vertices.at(0).y / 100)
|
|
vertices.slice(1).forEach((v) => shape.lineTo(v.x / 100, v.y / 100))
|
|
terrain = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { bevelEnabled: false, depth: 0.5 }), terrainMaterial)
|
|
terrain.userData.type = 'terrain'
|
|
terrain.userData.id = t.id
|
|
scene.add(terrain)
|
|
terrains[t.id] = terrain
|
|
|
|
// TODO: bboxes aren't tracked and can leak memory
|
|
const bboxValues = Object.values(t.bbox)
|
|
if (bboxValues.length >= 4) {
|
|
const width = (bboxValues[1] - bboxValues[3]) / 100
|
|
const height = (bboxValues[0] - bboxValues[2]) / 100
|
|
|
|
const bbox = new THREE.Mesh(new THREE.BoxGeometry(width, height, 0.2), bboxMaterial)
|
|
bbox.position.set((bboxValues[3] / 100) + (width / 2), (bboxValues[2] / 100) + (height / 2), 0)
|
|
scene.add(bbox)
|
|
}
|
|
}
|
|
|
|
terrain.position.set(t.position.x / 100, t.position.y / 100, 0)
|
|
}
|
|
|
|
if (playerId != null) {
|
|
const player = state.entities.find((e) => e.id == playerId)
|
|
if (player != null) {
|
|
for (let abilityIndex = 0; abilityIndex < 4; abilityIndex++) {
|
|
const abilityKey = ['a', 'q', 'w', 'e'][abilityIndex]
|
|
if (player.abilities[abilityKey] != null) {
|
|
const abilityId = player.abilities[abilityKey]
|
|
const ability = state.abilities.find((it) => it.id == abilityId)
|
|
const lastCast = player.cooldowns[ability.id] ?? -Infinity
|
|
const cooldownDuration = (ability.cooldown * state.tickRate) ?? 0
|
|
const remainingCooldown = (lastCast + cooldownDuration) - state.currentTick
|
|
let cssPercentage = '100%'
|
|
let text = ''
|
|
if (remainingCooldown > 0) {
|
|
const cooldownPercentage = 1 - (remainingCooldown / cooldownDuration)
|
|
cssPercentage = `${Math.round(100 * cooldownPercentage)}%`
|
|
if (remainingCooldown / state.tickRate <= 5) {
|
|
text = `${(Math.round(10 * remainingCooldown / state.tickRate) / 10).toFixed(1)}`
|
|
}
|
|
else {
|
|
text = `${Math.round(remainingCooldown / state.tickRate)}`
|
|
}
|
|
}
|
|
|
|
if (player.casting?.ability?.id == ability.id) {
|
|
document.getElementById(`ability-${abilityIndex}-cooldown`).style.clipPath = `polygon(50% 0%, 0% 100%, 100% 100%)` // triangle
|
|
}
|
|
else {
|
|
document.getElementById(`ability-${abilityIndex}-cooldown`).style.clipPath = `polygon(0 ${cssPercentage}, 100% ${cssPercentage}, 100% 100%, 0 100%)`
|
|
}
|
|
|
|
document.getElementById(`ability-${abilityIndex}-cooldown-text`).innerHTML = text
|
|
}
|
|
}
|
|
|
|
let buffs = ``
|
|
player.buffs.forEach((b) => {
|
|
buffs += `<div class="buff"><div class="buff-body">${state.buffs.find((it) => it.id == b.id).name}</div></div>`
|
|
})
|
|
|
|
if (document.getElementById('buffs').innerHTML != buffs) {
|
|
document.getElementById('buffs').innerHTML = buffs
|
|
}
|
|
|
|
let castIndicatorDisplay = 'none'
|
|
if (player.casting != null) {
|
|
castIndicatorDisplay = 'block'
|
|
const castDuration = (player.casting.ability.castTime * state.tickRate) ?? 0
|
|
const remainingCastTime = (player.casting.timestamp + castDuration) - state.currentTick
|
|
let cssPercentage = '100%'
|
|
if (remainingCastTime > 0) {
|
|
const castPercentage = 1 - (remainingCastTime / castDuration)
|
|
cssPercentage = `${Math.round(100 * castPercentage)}%`
|
|
}
|
|
|
|
document.getElementById('cast_indicator_progress').style.clipPath = `polygon(0 0, ${cssPercentage} 0, ${cssPercentage} 100%, 0% 100%)`
|
|
document.getElementById('cast_indicator_name').innerHTML = player.casting.ability?.name ?? ''
|
|
}
|
|
|
|
document.getElementById('cast_indicator').style.display = castIndicatorDisplay
|
|
}
|
|
}
|
|
|
|
document.getElementById('state').innerHTML = JSON.stringify(stateUpdates, null, 2)
|
|
}
|
|
}
|
|
|
|
window.addEventListener('load', () => {
|
|
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries())
|
|
playerId = params.id
|
|
if (playerId == null) {
|
|
playerId = prompt('Player ID:')
|
|
}
|
|
|
|
connectWebSocket()
|
|
|
|
const canvas = renderer.domElement
|
|
canvas.classList.add('canvas')
|
|
|
|
window.addEventListener('mousedown', (event) => {
|
|
const intersect = raycastToGround()
|
|
if (intersect != null) {
|
|
const { x, y } = intersect
|
|
if (event.button == 0) {
|
|
websocket.send(JSON.stringify({ action: 'attack', id: playerId, x, y }))
|
|
}
|
|
|
|
if (event.button == 2) {
|
|
websocket.send(JSON.stringify({ action: 'move', id: playerId, x, y }))
|
|
}
|
|
}
|
|
})
|
|
window.addEventListener('keydown', (event) => {
|
|
const intersect = raycastToGround()
|
|
if (intersect != null) {
|
|
const { x, y } = intersect
|
|
if (event.code == 'KeyA') {
|
|
websocket.send(JSON.stringify({ action: 'attack', id: playerId, x, y }))
|
|
}
|
|
if (event.code == 'KeyX') {
|
|
websocket.send(JSON.stringify({ action: 'cast', slot: 'a', id: playerId, x, y }))
|
|
}
|
|
|
|
if (event.code == 'KeyS') {
|
|
websocket.send(JSON.stringify({ action: 'stop', id: playerId }))
|
|
}
|
|
if (event.code == 'KeyH') {
|
|
websocket.send(JSON.stringify({ action: 'halt', id: playerId }))
|
|
}
|
|
|
|
if (event.code == 'KeyQ') {
|
|
websocket.send(JSON.stringify({ action: 'cast', slot: 'q', id: playerId, x, y }))
|
|
}
|
|
if (event.code == 'KeyW') {
|
|
websocket.send(JSON.stringify({ action: 'cast', slot: 'w', id: playerId, x, y }))
|
|
}
|
|
if (event.code == 'KeyE') {
|
|
websocket.send(JSON.stringify({ action: 'cast', slot: 'e', id: playerId, x, y }))
|
|
}
|
|
}
|
|
})
|
|
|
|
window.addEventListener('wheel', (event) => {
|
|
if (event.deltaY < 0) {
|
|
camera.zoom += 0.2
|
|
if (camera.zoom > 3) {
|
|
camera.zoom = 3
|
|
}
|
|
camera.updateProjectionMatrix()
|
|
}
|
|
if (event.deltaY > 0) {
|
|
camera.zoom -= 0.2
|
|
if (camera.zoom < 1) {
|
|
camera.zoom = 1
|
|
}
|
|
camera.updateProjectionMatrix()
|
|
}
|
|
})
|
|
|
|
window.addEventListener('resize', (event) => {
|
|
camera.aspect = window.innerWidth / window.innerHeight
|
|
camera.updateProjectionMatrix()
|
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
})
|
|
|
|
window.addEventListener('contextmenu', (event) => event.preventDefault())
|
|
window.addEventListener('keydown', (event) => keysDown[event.code] = true)
|
|
window.addEventListener('keyup', (event) => keysDown[event.code] = false)
|
|
window.addEventListener('keydown', (event) => {
|
|
if (event.code == 'Space') {
|
|
cameraLocked = !cameraLocked
|
|
}
|
|
})
|
|
window.addEventListener('mousemove', (event) => {
|
|
mouse.x = event.clientX
|
|
mouse.y = event.clientY
|
|
})
|
|
|
|
document.body.appendChild(canvas)
|
|
|
|
const minimap = minimapRenderer.domElement
|
|
minimap.classList.add('minimap')
|
|
document.body.appendChild(minimap)
|
|
|
|
document.body.appendChild(stats.dom)
|
|
})
|