TRXTD/src/game/config.ts

303 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { DifficultyDef, DifficultyId, EnemyDef, EnemyKind, TowerDef, TowerKind, WaveGroup } from './types'
export const TILE = 48
export const COLS = 20
export const ROWS = 11
export const W = COLS * TILE // 960
export const H = ROWS * TILE // 528
export const TOTAL_WAVES = 20
export const INTERMISSION = 20 // seconds between waves
export const SELL_RATIO = 0.7
/** cost to remove blocking obstacles */
export const OBSTACLE_COST: Record<'tree' | 'rock', number> = {
tree: 60,
rock: 40,
}
/** ground path in tile coords; -1/20 are outside the grid (spawn/exit) */
export const MAP_WAYPOINTS: [number, number][] = [
[-1, 5],
[2, 5],
[2, 1],
[6, 1],
[6, 9],
[10, 9],
[10, 1],
[14, 1],
[14, 9],
[17, 9],
[17, 5],
[20, 5],
]
/** flying enemies fly straight across the middle */
export const FLY_WAYPOINTS: [number, number][] = [
[-1, 5],
[20, 5],
]
export const TOWER_ORDER: TowerKind[] = ['arrow', 'cannon', 'frost', 'tesla', 'laser']
export const TOWERS: Record<TowerKind, TowerDef> = {
arrow: {
kind: 'arrow',
name: 'Bogenturm',
icon: '🏹',
desc: 'Günstiger Standardturm mit schnellen Einzelschüssen.',
cost: 50,
upgradeCost: [60, 110],
levels: [
{ damage: 12, range: 110, rate: 1.7 },
{ damage: 22, range: 120, rate: 2.0 },
{ damage: 38, range: 132, rate: 2.4 },
],
hitsFlying: true,
projectileSpeed: 460,
color: '#9a6a33',
accent: '#e8c27a',
hotkey: '1',
},
cannon: {
kind: 'cannon',
name: 'Kanone',
icon: '🧨',
desc: 'Flächenschaden durch Explosion trifft aber keine Flieger.',
cost: 110,
upgradeCost: [100, 180],
levels: [
{ damage: 28, range: 118, rate: 0.55 },
{ damage: 50, range: 128, rate: 0.62 },
{ damage: 92, range: 140, rate: 0.7 },
],
hitsFlying: false,
projectileSpeed: 300,
splash: [42, 48, 56],
color: '#57616e',
accent: '#ff9c40',
hotkey: '2',
},
frost: {
kind: 'frost',
name: 'Eisturm',
icon: '❄️',
desc: 'Frostpuls verlangsamt ALLE Gegner in Reichweite (auch Flieger).',
cost: 80,
upgradeCost: [70, 130],
levels: [
{ damage: 5, range: 95, rate: 0.9 },
{ damage: 9, range: 105, rate: 1.0 },
{ damage: 15, range: 118, rate: 1.1 },
],
hitsFlying: true,
slowFactor: [0.55, 0.45, 0.35],
slowDuration: [1.5, 1.9, 2.3],
color: '#4d8fb5',
accent: '#aef0ff',
hotkey: '3',
},
tesla: {
kind: 'tesla',
name: 'Teslaturm',
icon: '⚡',
desc: 'Kettenblitz springt auf mehrere Gegner über.',
cost: 130,
upgradeCost: [120, 200],
levels: [
{ damage: 22, range: 105, rate: 1.0 },
{ damage: 38, range: 115, rate: 1.1 },
{ damage: 66, range: 128, rate: 1.2 },
],
hitsFlying: true,
chain: [3, 4, 5],
chainRange: 100,
color: '#6a5fb0',
accent: '#c8b4ff',
hotkey: '4',
},
laser: {
kind: 'laser',
name: 'Laserturm',
icon: '💫',
desc: 'Soforttreffer mit hoher Reichweite und hohem Schaden.',
cost: 160,
upgradeCost: [150, 260],
levels: [
{ damage: 60, range: 170, rate: 0.5 },
{ damage: 105, range: 185, rate: 0.55 },
{ damage: 185, range: 205, rate: 0.6 },
],
hitsFlying: true,
color: '#b0483f',
accent: '#ff8a7a',
hotkey: '5',
},
}
export const ENEMIES: Record<EnemyKind, EnemyDef> = {
normal: {
kind: 'normal',
name: 'Kriecher',
hp: 55,
speed: 55,
reward: 8,
radius: 11,
dmg: 1,
flying: false,
color: '#58c14b',
},
fast: {
kind: 'fast',
name: 'Läufer',
hp: 34,
speed: 105,
reward: 9,
radius: 9,
dmg: 1,
flying: false,
color: '#ffd23e',
},
tank: {
kind: 'tank',
name: 'Panzer',
hp: 210,
speed: 34,
reward: 20,
radius: 14,
dmg: 2,
flying: false,
color: '#7d8894',
},
flyer: {
kind: 'flyer',
name: 'Flieger',
hp: 48,
speed: 72,
reward: 10,
radius: 10,
dmg: 1,
flying: true,
color: '#b06ee8',
},
boss: {
kind: 'boss',
name: 'Boss',
hp: 1500,
speed: 42,
reward: 130,
radius: 20,
dmg: 5,
flying: false,
color: '#d84b3f',
},
}
/** hp multiplier for wave n */
export function waveHpScale(n: number): number {
const m = n - 1
return 1 + 0.18 * m + 0.02 * m * m
}
/** speed multiplier for wave n */
export function waveSpeedScale(n: number): number {
return Math.min(1.32, 1 + 0.008 * (n - 1))
}
/** reward multiplier for wave n */
export function waveRewardScale(n: number): number {
return 1 + 0.04 * (n - 1)
}
export function waveClearBonus(n: number): number {
return 35 + 8 * n
}
export function earlyCallBonus(remainingSeconds: number): number {
return Math.round(remainingSeconds * 2.5)
}
const G = (kind: EnemyKind, count: number, interval: number): WaveGroup => ({ kind, count, interval })
/** hand-tuned waves 1..20 */
export const WAVES: WaveGroup[][] = [
/* 1 */ [G('normal', 8, 1.1)],
/* 2 */ [G('normal', 12, 0.9)],
/* 3 */ [G('normal', 8, 0.9), G('fast', 5, 0.7)],
/* 4 */ [G('fast', 10, 0.55), G('normal', 8, 0.8)],
/* 5 */ [G('normal', 10, 0.75), G('tank', 3, 2.2)],
/* 6 */ [G('fast', 12, 0.5), G('tank', 4, 2.0)],
/* 7 */ [G('flyer', 8, 0.9), G('normal', 10, 0.7)],
/* 8 */ [G('fast', 10, 0.5), G('flyer', 10, 0.7)],
/* 9 */ [G('tank', 6, 1.6), G('fast', 12, 0.45), G('normal', 10, 0.6)],
/* 10 */ [G('boss', 1, 1), G('normal', 12, 0.8)],
/* 11 */ [G('flyer', 14, 0.55), G('fast', 12, 0.45)],
/* 12 */ [G('tank', 8, 1.3), G('normal', 16, 0.5)],
/* 13 */ [G('fast', 20, 0.35), G('flyer', 12, 0.5)],
/* 14 */ [G('tank', 10, 1.1), G('fast', 16, 0.4)],
/* 15 */ [G('boss', 2, 8), G('flyer', 10, 0.5)],
/* 16 */ [G('normal', 24, 0.35), G('tank', 8, 1.0)],
/* 17 */ [G('fast', 24, 0.3), G('flyer', 16, 0.45)],
/* 18 */ [G('tank', 12, 0.9), G('normal', 20, 0.4)],
/* 19 */ [G('fast', 20, 0.3), G('flyer', 20, 0.4), G('tank', 8, 0.9)],
/* 20 */ [G('tank', 8, 1.0), G('boss', 2, 8), G('fast', 16, 0.35)],
]
/** endless mode waves after 20 */
export function endlessWave(n: number): WaveGroup[] {
const m = n - 20
const groups: WaveGroup[] = []
if (n % 5 === 0) groups.push(G('boss', 1 + Math.floor(m / 5), 8))
groups.push(G('tank', 8 + m * 2, Math.max(0.5, 1.1 - m * 0.02)))
groups.push(G('fast', 18 + m * 3, Math.max(0.15, 0.32 - m * 0.01)))
groups.push(G('flyer', 14 + m * 2, Math.max(0.25, 0.45 - m * 0.01)))
groups.push(G('normal', 20 + m * 4, Math.max(0.18, 0.35 - m * 0.01)))
return groups
}
export function getWave(n: number): WaveGroup[] {
if (n <= WAVES.length) return WAVES[n - 1]
return endlessWave(n)
}
export const DIFFICULTIES: Record<DifficultyId, DifficultyDef> = {
easy: {
id: 'easy',
name: 'Leicht',
desc: '30 Leben, mehr Startgold, schwächere Gegner',
lives: 30,
money: 320,
hpMul: 0.85,
},
normal: {
id: 'normal',
name: 'Normal',
desc: '20 Leben, ausgewogenes Balancing',
lives: 20,
money: 260,
hpMul: 1,
},
hard: {
id: 'hard',
name: 'Schwer',
desc: '12 Leben, weniger Gold, zähere Gegner',
lives: 12,
money: 220,
hpMul: 1.15,
},
}
export const ENEMY_COLORS: Record<EnemyKind, string> = {
normal: '#58c14b',
fast: '#ffd23e',
tank: '#7d8894',
flyer: '#b06ee8',
boss: '#d84b3f',
}
export const ENEMY_ICONS: Record<EnemyKind, string> = {
normal: '🟢',
fast: '🟡',
tank: '⬜',
flyer: '🟣',
boss: '🔴',
}