fix dead state
This commit is contained in:
+38
-8
@@ -197,11 +197,18 @@ function connectWebSocket() {
|
||||
state.height = stateUpdates.height
|
||||
|
||||
minimapCamera.top = state.height / 200
|
||||
minimapCamera.right = state.height / 200
|
||||
minimapCamera.right = state.width / 200
|
||||
minimapCamera.bottom = -state.height / 200
|
||||
minimapCamera.left = -state.height / 200
|
||||
minimapCamera.left = -state.width / 200
|
||||
minimapCamera.updateProjectionMatrix()
|
||||
minimapCamera.position.set(state.width / 200, state.height / 200, minimapCameraZ)
|
||||
|
||||
const size = 300
|
||||
const wide = state.width > state.height
|
||||
minimapRenderer.setSize(
|
||||
wide ? size : (state.width / state.height) * size,
|
||||
wide ? (state.height / state.width) * size : size,
|
||||
)
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(stateUpdates)) {
|
||||
@@ -333,8 +340,8 @@ function connectWebSocket() {
|
||||
rotationBase.add(castingMarker)
|
||||
|
||||
const rangeMaterial = teamMaterials['range']
|
||||
const rangeSize = e.visionRange ?? 0
|
||||
// const rangeSize = (state.abilities.find((it) => it.id == e.abilities?.a)?.range ?? 0) + e.radius
|
||||
// const rangeSize = e.visionRange ?? 0
|
||||
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 = 5000
|
||||
rangeMarker.scale.y = e.height / rangeMarkerSize
|
||||
@@ -346,18 +353,32 @@ function connectWebSocket() {
|
||||
entities[e.id] = entity
|
||||
}
|
||||
|
||||
entity.children.at(0).visible = !e.dead
|
||||
entity.children.at(1).visible = !e.dead
|
||||
entity.children.at(2).visible = e.buffs.some((it) => it.id == 'exposed') // TODO: only works for Exposed now
|
||||
|
||||
let z = e.height / 100
|
||||
|
||||
if (e.dead) {
|
||||
entity.rotation.x = 0
|
||||
entity.position.z = 0
|
||||
z = 0
|
||||
}
|
||||
else {
|
||||
entity.rotation.x = Math.PI / 2
|
||||
entity.position.z = e.height / 100
|
||||
}
|
||||
|
||||
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()
|
||||
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z }, 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(4).visible = e.id == playerId
|
||||
entity.children.at(3).children.at(0).visible = e.casting != null
|
||||
}
|
||||
|
||||
@@ -446,8 +467,8 @@ function connectWebSocket() {
|
||||
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]
|
||||
for (let abilityIndex = 0; abilityIndex < 7; abilityIndex++) {
|
||||
const abilityKey = ['a', 'q', 'w', 'e', 'r', 'd', 'f'][abilityIndex]
|
||||
if (player.abilities[abilityKey] != null) {
|
||||
const abilityId = player.abilities[abilityKey]
|
||||
const ability = state.abilities.find((it) => it.id == abilityId)
|
||||
@@ -565,6 +586,15 @@ window.addEventListener('load', () => {
|
||||
if (event.code == 'KeyE') {
|
||||
websocket.send(JSON.stringify({ action: 'cast', slot: 'e', id: playerId, x, y }))
|
||||
}
|
||||
if (event.code == 'KeyR') {
|
||||
websocket.send(JSON.stringify({ action: 'cast', slot: 'r', id: playerId, x, y }))
|
||||
}
|
||||
if (event.code == 'KeyD') {
|
||||
websocket.send(JSON.stringify({ action: 'cast', slot: 'd', id: playerId, x, y }))
|
||||
}
|
||||
if (event.code == 'KeyF') {
|
||||
websocket.send(JSON.stringify({ action: 'cast', slot: 'f', id: playerId, x, y }))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -199,6 +199,21 @@
|
||||
<div id="ability-3-cooldown" class="cooldown"></div>
|
||||
<div id="ability-3-cooldown-text" class="cooldown-text"></div>
|
||||
</div>
|
||||
<div id="ability-4" class="ability">
|
||||
R
|
||||
<div id="ability-4-cooldown" class="cooldown"></div>
|
||||
<div id="ability-4-cooldown-text" class="cooldown-text"></div>
|
||||
</div>
|
||||
<div id="ability-5" class="ability">
|
||||
D
|
||||
<div id="ability-5-cooldown" class="cooldown"></div>
|
||||
<div id="ability-5-cooldown-text" class="cooldown-text"></div>
|
||||
</div>
|
||||
<div id="ability-6" class="ability">
|
||||
F
|
||||
<div id="ability-6-cooldown" class="cooldown"></div>
|
||||
<div id="ability-6-cooldown-text" class="cooldown-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="buffs" class="buffs"></div>
|
||||
<script type="module" src="client.js"></script>
|
||||
|
||||
+71
-5
@@ -10,7 +10,7 @@ export default class Ability {
|
||||
|
||||
name = 'Ability'
|
||||
|
||||
castTime = 0
|
||||
castTime = null
|
||||
cooldown = 0
|
||||
damage = 0
|
||||
moveCancelable = false
|
||||
@@ -18,15 +18,17 @@ export default class Ability {
|
||||
range = 0
|
||||
speed = 1000
|
||||
|
||||
#effect = () => {}
|
||||
#effect = null
|
||||
|
||||
get effect() { return this.#effect }
|
||||
get effect() { return this.#effect ?? Ability.noEffect }
|
||||
set effect(value) { this.#effect = value }
|
||||
|
||||
constructor(options = {}) {
|
||||
Object.entries(options).forEach(([key, value]) => this[key] = value)
|
||||
}
|
||||
|
||||
static get noEffect() { return function noEffect() {} }
|
||||
|
||||
static straightShot = new Ability({
|
||||
id: 'straight_shot',
|
||||
name: 'Straight Shot',
|
||||
@@ -155,8 +157,7 @@ export default class Ability {
|
||||
static blink = new Ability({
|
||||
id: 'blink',
|
||||
name: 'Blink',
|
||||
castTime: 0.25,
|
||||
cooldown: 2,
|
||||
cooldown: 10,
|
||||
range: 475,
|
||||
effect: function blinkEffect(caster, cursor) {
|
||||
const ability = this
|
||||
@@ -246,4 +247,69 @@ export default class Ability {
|
||||
caster.game?.spawnProjectile(projectile)
|
||||
},
|
||||
})
|
||||
|
||||
static circleOfResurrectionChannel = new Ability({
|
||||
id: 'channel:circle_of_resurrection',
|
||||
name: 'Channeling: Circle of Resurrection',
|
||||
castTime: 3,
|
||||
})
|
||||
|
||||
static circleOfResurrection = new Ability({
|
||||
id: 'circle_of_resurrection',
|
||||
name: 'Circle of Resurrection',
|
||||
castTime: 0.5,
|
||||
cooldown: 100,
|
||||
duration: 3,
|
||||
radius: 300,
|
||||
range: 300,
|
||||
effect: function circleOfResurrectionEffect(caster, cursor) {
|
||||
const ability = this
|
||||
caster.haltAction()
|
||||
|
||||
const direction = cursor.clone().sub(caster.position)
|
||||
if (direction.length() > ability.range) {
|
||||
direction.normalize().multiplyScalar(ability.range)
|
||||
}
|
||||
|
||||
const destination = caster.position.clone().add(direction)
|
||||
|
||||
const team = caster.team
|
||||
const currentTick = caster.game?.currentTick ?? 0
|
||||
const duration = caster.game?.secToTick(ability.duration) ?? 0
|
||||
const despawnAfter = currentTick + duration
|
||||
const casterPosition = caster.position.clone()
|
||||
|
||||
const circleOfResurrectionLogic = function castingVisionLogic(projectile) {
|
||||
const currentTick = projectile.game?.currentTick ?? 0
|
||||
if (casterPosition.distanceTo(caster.position) > 1) {
|
||||
projectile.despawn()
|
||||
}
|
||||
|
||||
if (currentTick > despawnAfter) {
|
||||
const entities = projectile.game?.entities ?? []
|
||||
const pos = projectile.position
|
||||
projectile.despawn()
|
||||
const nearbyDeadTeammates = entities.filter((it) => it.dead && it.team == team)
|
||||
const closestDeadTeammate = nearbyDeadTeammates.reduce((e1, e2) => (e1?.distanceTo(pos) ?? Infinity) < e2.distanceTo(pos) ? e1 : e2, null)
|
||||
if (closestDeadTeammate != null) {
|
||||
closestDeadTeammate.revive(closestDeadTeammate.maxHealth / 4)
|
||||
caster.cooldown(ability.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const projectile = new Projectile({
|
||||
logic: circleOfResurrectionLogic,
|
||||
owner: caster.id,
|
||||
position: destination,
|
||||
radius: ability.radius,
|
||||
visualRadius: 0,
|
||||
})
|
||||
|
||||
caster.game?.spawnProjectile(projectile)
|
||||
if (caster.casting != null) {
|
||||
caster.forceCast(Ability.circleOfResurrectionChannel.id, destination)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+4
-2
@@ -7,11 +7,13 @@ export default class Buff {
|
||||
|
||||
duration = 0
|
||||
|
||||
#effect = () => {}
|
||||
#effect = null
|
||||
|
||||
get effect() { return this.#effect }
|
||||
get effect() { return this.#effect ?? Buff.noEffect }
|
||||
set effect(value) { this.#effect = value }
|
||||
|
||||
static get noEffect() { return function noEffect() {} }
|
||||
|
||||
constructor(options = {}) {
|
||||
Object.entries(options).forEach(([key, value]) => this[key] = value)
|
||||
}
|
||||
|
||||
+75
-7
@@ -172,10 +172,14 @@ export default class Entity {
|
||||
set y(value) { this.position.y = value }
|
||||
|
||||
attackAction(cursor) {
|
||||
if (this.dead) { return }
|
||||
|
||||
this.moveAction(cursor, true)
|
||||
}
|
||||
|
||||
castAction(slot, cursor, halt = false) {
|
||||
if (this.dead) { return }
|
||||
|
||||
const ability = this.ability(slot)
|
||||
if (ability == null) { return }
|
||||
|
||||
@@ -197,6 +201,12 @@ export default class Entity {
|
||||
this.rotation = targetPosition.clone().sub(this.position).angle()
|
||||
}
|
||||
|
||||
if (ability.castTime == null) {
|
||||
ability.effect(this, cursor)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const cooldown = this.game?.secToTick(ability.cooldown) ?? 0
|
||||
const lastCast = this.cooldowns[ability.id]
|
||||
const timestamp = this.game?.currentTick ?? 0
|
||||
@@ -210,10 +220,14 @@ export default class Entity {
|
||||
}
|
||||
|
||||
haltAction() {
|
||||
if (this.dead) { return }
|
||||
|
||||
this.#moving = false
|
||||
}
|
||||
|
||||
moveAction(cursor, attack = false) {
|
||||
if (this.dead) { return }
|
||||
|
||||
if (this.casting != null && this.game?.abilities.filter((it) => it.id == this.casting.ability)?.moveCancelable) {
|
||||
if (!attack && !(this.casting != null && this.casting.ability == this.abilities[0])) {
|
||||
this.casting = null
|
||||
@@ -226,6 +240,8 @@ export default class Entity {
|
||||
}
|
||||
|
||||
stopAction() {
|
||||
if (this.dead) { return }
|
||||
|
||||
this.casting = null
|
||||
this.#moving = true
|
||||
this.#attacking = false
|
||||
@@ -301,10 +317,12 @@ export default class Entity {
|
||||
|
||||
customBboxCollidables(bbox) {
|
||||
const entitiesAndTerrains = (this.game?.entities ?? []).filter((it) => it.id != this.id).concat(this.game?.terrains ?? [])
|
||||
return entitiesAndTerrains.filter((it) => it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
|
||||
return entitiesAndTerrains.filter((it) => !it.dead && it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
|
||||
}
|
||||
|
||||
damage(amount, source = null) {
|
||||
if (this.dead) { return }
|
||||
|
||||
let damage = amount
|
||||
if (this.hasBuff(Buff.exposed.id)) {
|
||||
const buff = this.getBuff(Buff.exposed.id)
|
||||
@@ -325,6 +343,27 @@ export default class Entity {
|
||||
return this.position.distanceTo(cursor)
|
||||
}
|
||||
|
||||
forceCast(abilityId, cursor) {
|
||||
if (this.dead) { return }
|
||||
|
||||
const ability = this.game?.abilities.find((it) => it.id == abilityId)
|
||||
if (ability == null) { return }
|
||||
|
||||
const targetPosition = (cursor instanceof Vector2) ? cursor : this.game?.entities.find((it) => it.id == cursor)?.position
|
||||
if (targetPosition instanceof Vector2) {
|
||||
this.rotation = targetPosition.clone().sub(this.position).angle()
|
||||
}
|
||||
|
||||
const timestamp = this.game?.currentTick ?? 0
|
||||
|
||||
if (ability.castTime == null) {
|
||||
ability.effect(this, cursor)
|
||||
}
|
||||
else {
|
||||
this.casting = { ability: ability.id, cursor, timestamp }
|
||||
}
|
||||
}
|
||||
|
||||
futureCollidables(futurePosition) {
|
||||
return this.customBboxCollidables(new Float32Array([
|
||||
futurePosition.y + this.radius,
|
||||
@@ -335,6 +374,8 @@ export default class Entity {
|
||||
}
|
||||
|
||||
getBuff(id) {
|
||||
if (this.dead) { return }
|
||||
|
||||
const entityBuff = this.buffs.find((it) => it.id == id)
|
||||
if (entityBuff == null) { return }
|
||||
|
||||
@@ -345,10 +386,14 @@ export default class Entity {
|
||||
}
|
||||
|
||||
hasBuff(id) {
|
||||
if (this.dead) { return false }
|
||||
|
||||
return this.buffs.some((it) => it.id == id) && this.game?.buffs.some((it) => it.id == id)
|
||||
}
|
||||
|
||||
heal(amount) {
|
||||
if (this.dead) { return }
|
||||
|
||||
this.health = Math.min(Math.max(0, this.health + amount), this.maxHealth)
|
||||
}
|
||||
|
||||
@@ -403,7 +448,7 @@ export default class Entity {
|
||||
isInLineOfSight(destination, position = this.position) {
|
||||
const bbox = Entity.tunnelBbox(position.x, position.y, destination.x, destination.y, this.radius)
|
||||
const entitiesAndTerrains = (this.game?.entities ?? []).filter((it) => it.id != this.id).concat(this.game?.terrains ?? [])
|
||||
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
|
||||
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => !it.dead && it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
|
||||
if (bboxCheckedObstacles.length < 1) { return true }
|
||||
|
||||
const colliders = bboxCheckedObstacles.map((it) => it.colliders()).flat()
|
||||
@@ -429,7 +474,7 @@ export default class Entity {
|
||||
obstaclesInStraightPath(destination, position = this.position) {
|
||||
const bbox = Entity.tunnelBbox(position.x, position.y, destination.x, destination.y, this.radius)
|
||||
const entitiesAndTerrains = (this.game?.entities ?? []).filter((it) => it.id != this.id).concat(this.game?.terrains ?? [])
|
||||
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
|
||||
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => !it.dead && it.collision && !((this.ghosting && it.ghostable) || (this.ghostable && it.ghosting)) && SATX.bboxCheck(bbox, it.bbox))
|
||||
if (bboxCheckedObstacles.length < 1) { return [] }
|
||||
|
||||
const collider = Entity.tunnelCollider(position.x, position.y, destination.x, destination.y, this.radius)
|
||||
@@ -437,6 +482,8 @@ export default class Entity {
|
||||
}
|
||||
|
||||
removeBuff(id) {
|
||||
if (this.dead) { return }
|
||||
|
||||
this.buffs = this.buffs.filter((it) => it.id != id)
|
||||
}
|
||||
|
||||
@@ -446,6 +493,15 @@ export default class Entity {
|
||||
this.dead = false
|
||||
}
|
||||
|
||||
revive(startingHealth = null) {
|
||||
this.dead = false
|
||||
const health = (startingHealth ?? this.maxHealth)
|
||||
this.health = Math.max(0, Math.min(health, this.maxHealth))
|
||||
|
||||
this.#calculateCollider()
|
||||
this.#calculateVision()
|
||||
}
|
||||
|
||||
setPosition(vector) {
|
||||
this.position.copy(vector)
|
||||
this.#calculateCollider()
|
||||
@@ -475,15 +531,14 @@ export default class Entity {
|
||||
])
|
||||
}
|
||||
|
||||
// TODO: make non-race-condition calculations multi-threaded
|
||||
update() {
|
||||
this.#calculateVision()
|
||||
this.#checkHealth()
|
||||
if (this.dead) {
|
||||
// TODO: do something while the entity is dead (and disallow casting, vision, etc)
|
||||
}
|
||||
else {
|
||||
this.#calculateVision()
|
||||
this.#cast()
|
||||
this.#checkHealth()
|
||||
this.#move()
|
||||
this.#tickBuffs()
|
||||
this.fixPosition()
|
||||
@@ -523,6 +578,12 @@ export default class Entity {
|
||||
}
|
||||
|
||||
#calculateVision() {
|
||||
if (this.dead) {
|
||||
this.#entitiesInVision = [this.id]
|
||||
this.#projectilesInVision = []
|
||||
return
|
||||
}
|
||||
|
||||
const entities = this.game?.entities ?? []
|
||||
const projectiles = this.game?.projectiles ?? []
|
||||
|
||||
@@ -555,15 +616,22 @@ export default class Entity {
|
||||
|
||||
ability.effect(this, this.casting.cursor)
|
||||
|
||||
if (this.casting.ability == ability.id) {
|
||||
this.casting = null
|
||||
}
|
||||
|
||||
// TODO: only spawn castingVision if slightly outside regular vision (or obstructed)
|
||||
Ability.castingVision.effect(this, this.position)
|
||||
return true
|
||||
}
|
||||
|
||||
#checkHealth() {
|
||||
if (this.health <= 0) {
|
||||
if (!this.dead && this.health <= 0) {
|
||||
this.dead = true
|
||||
this.buffs = []
|
||||
}
|
||||
else if (this.dead && this.health > 0) {
|
||||
this.health = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-8
@@ -19,21 +19,18 @@ export default class Game {
|
||||
tickRate = 30
|
||||
width = 0
|
||||
|
||||
#eventEmitter = new EventEmitter()
|
||||
#gameLoopIntervalId = null
|
||||
#logic = null
|
||||
#nextTickAt = 0
|
||||
#startTimestamp = 0
|
||||
#subscriptions = new Map()
|
||||
#tickBudget = 1000 / this.tickRate
|
||||
|
||||
get logic() { return this.#logic }
|
||||
get eventEmitter() { return this.#eventEmitter }
|
||||
get tickBudget() { return this.#tickBudget }
|
||||
set logic(value) { this.#logic = value }
|
||||
get subscriptions() { return this.#subscriptions }
|
||||
|
||||
constructor() {
|
||||
this.#eventEmitter.setMaxListeners(20)
|
||||
}
|
||||
set logic(value) { this.#logic = value }
|
||||
|
||||
action(id, options) {
|
||||
const entity = this.entities.find((it) => it.id == id)
|
||||
@@ -140,6 +137,10 @@ export default class Game {
|
||||
}
|
||||
|
||||
update() {
|
||||
for (const subscription of this.#subscriptions.values()) {
|
||||
subscription()
|
||||
}
|
||||
|
||||
const callUpdate = function callUpdate(object) { object.update() }
|
||||
this.entities.forEach(callUpdate) // TODO: entity with lower ID has unfair collision advantage (regular loop + until it fully loops around with an offset?)
|
||||
this.projectiles.forEach(callUpdate)
|
||||
@@ -147,8 +148,6 @@ export default class Game {
|
||||
this.#logic()
|
||||
}
|
||||
|
||||
this.eventEmitter.emit('tick')
|
||||
|
||||
this.currentTick++
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
import { Dungeon, Ravine } from './level.js'
|
||||
import { Dungeon } from './level.js'
|
||||
import { WebSocketExpress } from 'websocket-express'
|
||||
import express from 'express'
|
||||
import Game from './game.js'
|
||||
@@ -33,13 +33,14 @@ app.ws('/ws', async (req, res) => {
|
||||
console.log(message)
|
||||
if (message.action == 'join') {
|
||||
const id = message.id
|
||||
const connectionId = crypto.randomUUID()
|
||||
websocket.send(JSON.stringify(game.joinReport()))
|
||||
const subscription = game.subscription(websocket, id).bind(game)
|
||||
game.eventEmitter.on('tick', subscription)
|
||||
game.subscriptions.set(connectionId, subscription)
|
||||
|
||||
websocket.on('close', () => {
|
||||
console.log({ event: 'disconnected', id })
|
||||
game.eventEmitter.removeListener('tick', subscription)
|
||||
game.subscriptions.delete(connectionId)
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -51,6 +52,5 @@ app.ws('/ws', async (req, res) => {
|
||||
app.listen(port, () => {
|
||||
console.info(`Server started! Visit http://localhost:${port}`)
|
||||
|
||||
// Dungeon.scenario(game)
|
||||
Ravine.scenario(game)
|
||||
Dungeon.scenario(game)
|
||||
})
|
||||
|
||||
+20
-3
@@ -6,11 +6,28 @@ import Terrain from './terrain.js'
|
||||
|
||||
export class Dungeon {
|
||||
static scenario(game) {
|
||||
game.width = 3000
|
||||
game.height = 3000
|
||||
game.width = 2500
|
||||
game.height = 1500
|
||||
|
||||
const from = new Vector2(100, 100)
|
||||
const team = Team.blue
|
||||
const enemy = Team.neutral
|
||||
|
||||
game.spawnEntity(new Entity(Template.player({ id: '1', spawnPosition: new Vector2(1500, 700), team })))
|
||||
game.spawnEntity(new Entity(Template.player({ id: '2', spawnPosition: new Vector2(200, 1300), team, health: 10 })))
|
||||
|
||||
game.spawnEntity(new Entity(Template.basilisk({ id: 'boss', spawnPosition: new Vector2(2200, 750), team: enemy })))
|
||||
|
||||
setTimeout(() => game.entities.find((it) => it.id == '1').damage(9999), 10)
|
||||
|
||||
game.start()
|
||||
}
|
||||
}
|
||||
|
||||
export class Zigzag {
|
||||
static scenario(game) {
|
||||
game.width = 3000
|
||||
game.height = 2000
|
||||
const from = new Vector2(100, 100)
|
||||
game.spawnEntity(new Entity(Template.player({ id: '1', spawnPosition: from, pathfindingObstacleLimit: 1, pathfindingCooldown: 0 })))
|
||||
for (let i = 100; i < game.width; i += 300) {
|
||||
const highest = ((i - 100) % 600) == 0 ? 0 : 500
|
||||
|
||||
+25
-4
@@ -3,6 +3,19 @@ import Ability from './ability.js'
|
||||
import Team from './team.js'
|
||||
|
||||
export default class Template {
|
||||
static basilisk(overrides) {
|
||||
return {
|
||||
abilities: {},
|
||||
height: 100,
|
||||
logic: this.#basiliskLogic,
|
||||
radius: 180,
|
||||
speed: 230,
|
||||
visualRadius: 170,
|
||||
maxHealth: 3000,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
static minion(team, options = {}) {
|
||||
return {
|
||||
abilities: { a: options.ranged ? Ability.rangedAttack.id : Ability.meleeAttack.id },
|
||||
@@ -26,7 +39,9 @@ export default class Template {
|
||||
a: Ability.rangedAttack.id,
|
||||
q: Ability.straightShot.id,
|
||||
w: Ability.expose.id,
|
||||
e: Ability.blink.id,
|
||||
e: Ability.control.id,
|
||||
d: Ability.circleOfResurrection.id,
|
||||
f: Ability.blink.id,
|
||||
},
|
||||
height: 80,
|
||||
logic: this.#playerLogic,
|
||||
@@ -40,6 +55,12 @@ export default class Template {
|
||||
}
|
||||
}
|
||||
|
||||
static #basiliskLogic() {
|
||||
const entity = this
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
static #minionLogic(route = [], odd = false) {
|
||||
const checkpointSize = 300
|
||||
const recalculateDestRadius = 50
|
||||
@@ -87,8 +108,8 @@ export default class Template {
|
||||
|
||||
static #playerLogic() {
|
||||
const entity = this
|
||||
if (entity.dead) {
|
||||
entity.respawn()
|
||||
}
|
||||
// if (entity.dead) {
|
||||
// entity.respawn()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ export default class Terrain {
|
||||
this.#calculateUnadjustedWaypoints()
|
||||
this.#calculateBbox()
|
||||
}
|
||||
|
||||
get vertices() { return this.#vertices }
|
||||
get dead() { return false }
|
||||
|
||||
static waypointsForSide(fromVertex, toVertex, isClockwise = false) {
|
||||
const from = isClockwise ? toVertex : fromVertex
|
||||
|
||||
Reference in New Issue
Block a user