Add skill system, level-ups, and new enemy types

This commit is contained in:
2026-08-17 16:38:58 +02:00
parent 4751d428a7
commit 6f207ba1a0
23 changed files with 2192 additions and 83 deletions
+157
View File
@@ -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();
}
}