Add skill system, level-ups, and new enemy types
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user