extend moveset with attack, halt, stop
This commit is contained in:
+41
-4
@@ -271,10 +271,33 @@ function connectWebSocket() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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`).style.clipPath = `polygon(0 ${cssPercentage}, 100% ${cssPercentage}, 100% 100%, 0 100%)`
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById(`ability-${abilityIndex}-cooldown-text`).innerHTML = text
|
document.getElementById(`ability-${abilityIndex}-cooldown-text`).innerHTML = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,12 +317,12 @@ window.addEventListener('load', () => {
|
|||||||
const canvas = renderer.domElement
|
const canvas = renderer.domElement
|
||||||
canvas.classList.add('canvas')
|
canvas.classList.add('canvas')
|
||||||
|
|
||||||
canvas.addEventListener('mousedown', (event) => {
|
window.addEventListener('mousedown', (event) => {
|
||||||
const intersect = raycastToGround()
|
const intersect = raycastToGround()
|
||||||
if (intersect != null) {
|
if (intersect != null) {
|
||||||
const { x, y } = intersect
|
const { x, y } = intersect
|
||||||
if (event.button == 0) {
|
if (event.button == 0) {
|
||||||
websocket.send(JSON.stringify({ action: 'cast', slot: 0, id: playerId, x, y }))
|
websocket.send(JSON.stringify({ action: 'attack', id: playerId, x, y }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.button == 2) {
|
if (event.button == 2) {
|
||||||
@@ -311,6 +334,20 @@ window.addEventListener('load', () => {
|
|||||||
const intersect = raycastToGround()
|
const intersect = raycastToGround()
|
||||||
if (intersect != null) {
|
if (intersect != null) {
|
||||||
const { x, y } = intersect
|
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: 0, 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') {
|
if (event.code == 'KeyQ') {
|
||||||
websocket.send(JSON.stringify({ action: 'cast', slot: 1, id: playerId, x, y }))
|
websocket.send(JSON.stringify({ action: 'cast', slot: 1, id: playerId, x, y }))
|
||||||
}
|
}
|
||||||
@@ -330,7 +367,7 @@ window.addEventListener('load', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('wheel', (event) => {
|
window.addEventListener('wheel', (event) => {
|
||||||
if (event.deltaY < 0) {
|
if (event.deltaY < 0) {
|
||||||
camera.zoom += 0.2
|
camera.zoom += 0.2
|
||||||
if (camera.zoom > 3) {
|
if (camera.zoom > 3) {
|
||||||
@@ -353,7 +390,7 @@ window.addEventListener('load', () => {
|
|||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('contextmenu', (event) => event.preventDefault())
|
window.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||||
window.addEventListener('keydown', (event) => keysDown[event.code] = true)
|
window.addEventListener('keydown', (event) => keysDown[event.code] = true)
|
||||||
window.addEventListener('keyup', (event) => keysDown[event.code] = false)
|
window.addEventListener('keyup', (event) => keysDown[event.code] = false)
|
||||||
window.addEventListener('keydown', (event) => {
|
window.addEventListener('keydown', (event) => {
|
||||||
|
|||||||
@@ -103,6 +103,35 @@
|
|||||||
color: white;
|
color: white;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cast-indicator-wrapper {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: auto 0 30%;
|
||||||
|
width: 400px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cast-indicator-progress {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #edd9ff;
|
||||||
|
width: calc(100% - 4px);
|
||||||
|
height: calc(100% - 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cast-indicator-name {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 1px 1px 2px black, 0 0 1em dimgray, 0 0 0.2em dimgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cast-indicator-bar {
|
||||||
|
position: relative;
|
||||||
|
background-color: dimgray;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -110,6 +139,12 @@
|
|||||||
<p>Connection: <span id="connection"></span></p>
|
<p>Connection: <span id="connection"></span></p>
|
||||||
<pre id="state"></pre>
|
<pre id="state"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="cast_indicator" class="cast-indicator-wrapper">
|
||||||
|
<div id="cast_indicator_name" class="cast-indicator-name"></div>
|
||||||
|
<div class="cast-indicator-bar">
|
||||||
|
<div id="cast_indicator_progress" class="cast-indicator-progress"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="hud">
|
<div class="hud">
|
||||||
<div id="ability-0" class="ability">
|
<div id="ability-0" class="ability">
|
||||||
A
|
A
|
||||||
|
|||||||
+75
-23
@@ -8,7 +8,7 @@ export default class Entity {
|
|||||||
id = crypto.randomUUID()
|
id = crypto.randomUUID()
|
||||||
speed = 400
|
speed = 400
|
||||||
radius = 0
|
radius = 0
|
||||||
health = 1 // TODO: health can go into negatives and can go over maxHealth
|
health = 1
|
||||||
maxHealth = 1
|
maxHealth = 1
|
||||||
abilities = [
|
abilities = [
|
||||||
Ability.basicAttack,
|
Ability.basicAttack,
|
||||||
@@ -21,6 +21,7 @@ export default class Entity {
|
|||||||
|
|
||||||
cooldowns = {}
|
cooldowns = {}
|
||||||
|
|
||||||
|
#attack = false
|
||||||
#position = new Vector2()
|
#position = new Vector2()
|
||||||
#dest = null
|
#dest = null
|
||||||
#game = null
|
#game = null
|
||||||
@@ -70,6 +71,72 @@ export default class Entity {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attackAction(x, y) {
|
||||||
|
this.moveAction(x, y, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
castAction(slot, x, y, clearDestination = true) {
|
||||||
|
const ability = this.abilities[slot]
|
||||||
|
|
||||||
|
if (this.casting != null) {
|
||||||
|
const abilityBeingCasted = this.casting.ability
|
||||||
|
if (abilityBeingCasted.id == ability.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearDestination) {
|
||||||
|
this.#dest = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursor = new Vector2(x, y)
|
||||||
|
const cooldown = this.game?.secToTick(ability.cooldown) ?? 0
|
||||||
|
const lastCast = this.cooldowns[ability.id]
|
||||||
|
const timestamp = this.game?.currentTick ?? 0
|
||||||
|
if (lastCast != null && lastCast + cooldown > timestamp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.casting = { ability, cursor, timestamp }
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
haltAction() {
|
||||||
|
this.#dest = null
|
||||||
|
}
|
||||||
|
|
||||||
|
moveAction(x, y, attack = false) {
|
||||||
|
this.#attack = attack
|
||||||
|
if (this.casting != null && (!this.#attack || this.casting.ability.id != this.abilities[0].id)) {
|
||||||
|
this.casting = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAction() {
|
||||||
|
this.casting = null
|
||||||
|
this.#dest = null
|
||||||
|
this.#attack = false
|
||||||
|
}
|
||||||
|
|
||||||
|
autoAttack() {
|
||||||
|
if (!this.#attack) { return false }
|
||||||
|
|
||||||
|
if (this.game?.entities.some((e) => e.id != this.id && e.position.clone().sub(this.position).length() < this.abilities[0].range)) {
|
||||||
|
const cooldown = this.game?.secToTick(this.abilities[0].cooldown) ?? 0
|
||||||
|
const lastCast = this.cooldowns[this.abilities[0].id]
|
||||||
|
const timestamp = this.game?.currentTick ?? 0
|
||||||
|
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
|
||||||
|
|
||||||
|
const target = this.#dest ?? this.position
|
||||||
|
this.castAction(0, target.x, target.y, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast() {
|
cast() {
|
||||||
if (this.casting == null) {
|
if (this.casting == null) {
|
||||||
return false
|
return false
|
||||||
@@ -88,22 +155,6 @@ export default class Entity {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
castAction(slot, x, y) {
|
|
||||||
const ability = this.abilities[slot]
|
|
||||||
const cursor = new Vector2(x, y)
|
|
||||||
const cooldown = this.game?.secToTick(ability.cooldown) ?? 0
|
|
||||||
const lastCast = this.cooldowns[ability.id]
|
|
||||||
const timestamp = this.game?.currentTick ?? 0
|
|
||||||
if (lastCast != null && lastCast + cooldown > timestamp) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#dest = null
|
|
||||||
this.casting = { ability, cursor, timestamp }
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
@@ -123,7 +174,7 @@ export default class Entity {
|
|||||||
this.cooldowns[id] = this.game?.currentTick ?? 0
|
this.cooldowns[id] = this.game?.currentTick ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
damage(amount, source = null) {
|
damage(amount) {
|
||||||
this.health = Math.min(Math.max(0, this.health - amount), this.maxHealth)
|
this.health = Math.min(Math.max(0, this.health - amount), this.maxHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +182,7 @@ export default class Entity {
|
|||||||
this.game?.despawn(this)
|
this.game?.despawn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
heal(amount, source = null) {
|
heal(amount) {
|
||||||
this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth)
|
this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,10 +194,6 @@ export default class Entity {
|
|||||||
return SATX.collideObjects(this.collider, colliders)
|
return SATX.collideObjects(this.collider, colliders)
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAction(x, y) {
|
|
||||||
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
...this,
|
...this,
|
||||||
@@ -165,6 +212,8 @@ export default class Entity {
|
|||||||
// TODO: unset destination on teleports, etc.
|
// TODO: unset destination on teleports, etc.
|
||||||
// TODO: recalculate path on obstructions (currently next waypoint is used)
|
// TODO: recalculate path on obstructions (currently next waypoint is used)
|
||||||
takeStep(distanceTraveled = 0) {
|
takeStep(distanceTraveled = 0) {
|
||||||
|
if (this.casting != null) { return false }
|
||||||
|
|
||||||
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
|
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
|
||||||
const collidables = this.collidables()
|
const collidables = this.collidables()
|
||||||
if (this.#dest != null) {
|
if (this.#dest != null) {
|
||||||
@@ -213,6 +262,9 @@ export default class Entity {
|
|||||||
this.cast()
|
this.cast()
|
||||||
this.takeStep()
|
this.takeStep()
|
||||||
this.fixPosition()
|
this.fixPosition()
|
||||||
|
this.autoAttack()
|
||||||
|
|
||||||
|
// TODO: proper death and respawn
|
||||||
if (this.health <= 0) {
|
if (this.health <= 0) {
|
||||||
if (this.id == '1' || this.id == '2') {
|
if (this.id == '1' || this.id == '2') {
|
||||||
this.health = this.maxHealth
|
this.health = this.maxHealth
|
||||||
|
|||||||
+15
-2
@@ -33,17 +33,30 @@ app.ws('/ws', async (req, res) => {
|
|||||||
}
|
}
|
||||||
console.log(message)
|
console.log(message)
|
||||||
|
|
||||||
|
// DEPRECATED: teleporting is now directly possible via Ability...
|
||||||
if (message.action == 'teleport') {
|
if (message.action == 'teleport') {
|
||||||
entity.teleport(new Vector2(message.x, message.y))
|
entity.teleport(new Vector2(message.x, message.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.action == 'move') {
|
if (message.action == 'attack') {
|
||||||
entity.moveAction(message.x, message.y)
|
entity.attackAction(message.x, message.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.action == 'cast') {
|
if (message.action == 'cast') {
|
||||||
entity.castAction(message.slot, message.x, message.y)
|
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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user