158 lines
4.7 KiB
TypeScript
158 lines
4.7 KiB
TypeScript
import * as THREE from 'three';
|
|
import { Weapon } from './Weapon';
|
|
import { Player } from './Player';
|
|
import { lightningDamage, lightningJumps, lightningCooldown } from './Skills';
|
|
|
|
const CHAIN_FIRST_RANGE = 14;
|
|
const CHAIN_JUMP_RANGE = 8;
|
|
const FLASH_TIME = 0.35;
|
|
|
|
export class ChainLightning extends Weapon {
|
|
constructor() {
|
|
super();
|
|
this.COOLDOWN1_TIME = lightningCooldown(1);
|
|
}
|
|
|
|
skill1(_groundPoint: THREE.Vector3, player: Player): void {
|
|
const skills = player.game.skillSystem;
|
|
const level = skills.getLevel('chainlightning');
|
|
if (level <= 0) return;
|
|
if (this.cooldown1 > 0) return;
|
|
|
|
this.COOLDOWN1_TIME = lightningCooldown(level);
|
|
this.cooldown1 = this.COOLDOWN1_TIME;
|
|
|
|
const game = player.game;
|
|
const damage = lightningDamage(level);
|
|
const maxJumps = lightningJumps(level);
|
|
|
|
// Chain through enemies, always the closest un-hit one
|
|
const start = player.body.position.clone();
|
|
start.y += 0.8;
|
|
const chainPoints: THREE.Vector3[] = [start];
|
|
const hit: Set<import('./Enemy').Enemy> = new Set();
|
|
let from = player.body.position.clone();
|
|
|
|
for (let i = 0; i < maxJumps; i++) {
|
|
let best: import('./Enemy').Enemy | null = null;
|
|
let bestDist = i === 0 ? CHAIN_FIRST_RANGE : CHAIN_JUMP_RANGE;
|
|
for (const enemy of game.enemies) {
|
|
if (enemy.dead || hit.has(enemy)) continue;
|
|
const dist = enemy.body.position.distanceTo(from);
|
|
if (dist < bestDist) {
|
|
bestDist = dist;
|
|
best = enemy;
|
|
}
|
|
}
|
|
if (!best) break;
|
|
|
|
hit.add(best);
|
|
const fromPoint = from.clone();
|
|
best.takeDamage(damage, game, true, fromPoint);
|
|
const point = best.body.position.clone();
|
|
point.y += 0.8;
|
|
chainPoints.push(point);
|
|
from = best.body.position;
|
|
}
|
|
|
|
if (chainPoints.length < 2) return;
|
|
|
|
this.spawnVisual(game.scene, chainPoints, hit);
|
|
}
|
|
|
|
skill2(): void {
|
|
// Not implemented
|
|
}
|
|
|
|
private spawnVisual(
|
|
scene: THREE.Scene,
|
|
points: THREE.Vector3[],
|
|
hit: Set<import('./Enemy').Enemy>
|
|
) {
|
|
const group = new THREE.Group();
|
|
scene.add(group);
|
|
|
|
// Segments between chain points
|
|
for (let i = 0; i < points.length - 1; i++) {
|
|
const lineGeom = new THREE.BufferGeometry().setFromPoints([
|
|
points[i],
|
|
points[i + 1],
|
|
]);
|
|
const lineMat = new THREE.LineBasicMaterial({
|
|
color: 0x88ccff,
|
|
transparent: true,
|
|
opacity: 0.9,
|
|
blending: THREE.AdditiveBlending,
|
|
depthWrite: false,
|
|
});
|
|
const line = new THREE.Line(lineGeom, lineMat);
|
|
group.add(line);
|
|
|
|
// Jagged inner bolt
|
|
const mid = new THREE.Vector3()
|
|
.addVectors(points[i], points[i + 1])
|
|
.multiplyScalar(0.5);
|
|
mid.x += (Math.random() - 0.5) * 1.2;
|
|
mid.y += (Math.random() - 0.5) * 1.2;
|
|
mid.z += (Math.random() - 0.5) * 1.2;
|
|
const boltGeom = new THREE.BufferGeometry().setFromPoints([
|
|
points[i], mid, points[i + 1],
|
|
]);
|
|
const boltMat = new THREE.LineBasicMaterial({
|
|
color: 0xffffff,
|
|
transparent: true,
|
|
opacity: 0.9,
|
|
blending: THREE.AdditiveBlending,
|
|
depthWrite: false,
|
|
});
|
|
const bolt = new THREE.Line(boltGeom, boltMat);
|
|
group.add(bolt);
|
|
}
|
|
|
|
// Flash spheres on hit enemies
|
|
const flashes: THREE.Mesh[] = [];
|
|
for (const enemy of hit) {
|
|
const flashGeom = new THREE.SphereGeometry(0.7, 12, 12);
|
|
const flashMat = new THREE.MeshBasicMaterial({
|
|
color: 0x88ccff,
|
|
transparent: true,
|
|
opacity: 0.7,
|
|
blending: THREE.AdditiveBlending,
|
|
depthWrite: false,
|
|
});
|
|
const flash = new THREE.Mesh(flashGeom, flashMat);
|
|
flash.position.copy(enemy.body.position);
|
|
flash.position.y += 0.8;
|
|
group.add(flash);
|
|
flashes.push(flash);
|
|
}
|
|
|
|
const startTime = performance.now();
|
|
const update = () => {
|
|
const elapsed = (performance.now() - startTime) / 1000;
|
|
if (elapsed > FLASH_TIME) {
|
|
group.removeFromParent();
|
|
for (const child of [...group.children]) {
|
|
if (child instanceof THREE.Line || child instanceof THREE.Mesh) {
|
|
child.geometry.dispose();
|
|
(child.material as THREE.Material).dispose();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
const fade = 1 - elapsed / FLASH_TIME;
|
|
for (const child of group.children) {
|
|
if (child instanceof THREE.Line || child instanceof THREE.Mesh) {
|
|
(child.material as THREE.Material & { opacity: number }).opacity = fade;
|
|
}
|
|
}
|
|
for (const flash of flashes) {
|
|
const s = 0.7 + elapsed * 3;
|
|
flash.scale.set(s, s, s);
|
|
}
|
|
requestAnimationFrame(update);
|
|
};
|
|
update();
|
|
}
|
|
}
|