b506f0bcc3
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
- Add ActiveEffect, EffectType types to game.ts; activeEffects + effectiveArmor on EnemyState - Add SpellOnHitEffect + onHitEffect field to SpellDefinition - Wire onHitEffect to fire (burn), death (curse), lightning (armor_corrode), frost (freeze), soul (bypassArmor burn) - Add applyOnHitEffect() — applies on-hit effect on successful spell hit (spec §6.2) - Add processDoTPhase() — ticks all active effects after weapon/golem attacks (spec §6.3) - Add bypassArmor/bypassBarrier support in applyEnemyDefenses() (AC-13) - Export standalone applyEnemyDefenses from combat-tick.ts for DoT pipeline - Split DoT runtime into separate dot-runtime.ts (135 lines) to keep combat-actions.ts under 400 lines - Update all enemy generation sites with activeEffects/effectiveArmor defaults - Fix test helpers for new required fields All 921 tests pass (45 test files)
74 lines
3.7 KiB
TypeScript
74 lines
3.7 KiB
TypeScript
// ─── Enemy Naming & Swarm Generation ──────────────────────────────────
|
|
// Moved from store-modules/enemy-utils.ts to eliminate legacy dependencies
|
|
|
|
import type { EnemyState } from '../types';
|
|
import { SWARM_CONFIG } from '../constants';
|
|
import { getFloorMaxHP, getFloorElement } from './floor-utils';
|
|
|
|
// Barrier elements more likely to have barrier
|
|
const BARRIER_ELEMENTS = ['light', 'water', 'earth'];
|
|
|
|
// Get barrier for an enemy (0-1 as percentage of max HP)
|
|
function getEnemyBarrier(floor: number, element: string): number {
|
|
if (floor < 20) return 0;
|
|
const baseChance = BARRIER_ELEMENTS.includes(element) ? 0.15 : 0.08;
|
|
const floorBonus = Math.min(0.25, (floor - 20) * 0.003);
|
|
const barrierChance = Math.min(0.4, baseChance + floorBonus);
|
|
if (Math.random() > barrierChance) return 0;
|
|
const floorProgress = Math.min(1, (floor - 20) / 80);
|
|
return 0.1 + floorProgress * 0.2;
|
|
}
|
|
|
|
// Enemy names by element and floor tier
|
|
const ENEMY_NAMES_BY_ELEMENT: Record<string, string[]> = {
|
|
fire: ['Fire Imp', 'Flame Sprite', 'Emberling', 'Scorchling', 'Inferno Whelp'],
|
|
water: ['Water Elemental', 'Tidal Wraith', 'Aqua Sprite', 'Drowned One', 'Tsunami Spawn'],
|
|
air: ['Wind Sylph', 'Gale Rider', 'Storm Spirit', 'Zephyr Darter', 'Cyclone Wisp'],
|
|
earth: ['Stone Golem', 'Earth Elemental', 'Graveling', 'Mountain Giant', 'Terra Brute'],
|
|
light: ['Light Saint', 'Radiant Angel', 'Luminous Spirit', 'Divine Warden', 'Holy Sentinel'],
|
|
dark: ['Shadow Assassin', 'Dark Cultist', 'Umbral Fiend', 'Void Walker', 'Night Stalker'],
|
|
death: ['Skeleton Warrior', 'Zombie Lord', 'Lichling', 'Bone Reaper', 'Necrotic Wraith'],
|
|
// Special element names
|
|
lightning: ['Storm Elemental', 'Thunder Hawk', 'Lightning Eel', 'Shock Sprite', 'Voltaic Wisp'],
|
|
metal: ['Iron Golem', 'Steel Guardian', 'Rust Monster', 'Chrome Beetle', 'Mercury Spirit'],
|
|
sand: ['Sand Wraith', 'Dune Stalker', 'Desert Spirit', 'Cactus Thrasher', 'Mirage Runner'],
|
|
crystal: ['Crystal Guardian', 'Prism Sprite', 'Gem Hound', 'Diamond Golem', 'Shardling'],
|
|
stellar: ['Star Spawn', 'Cosmic Entity', 'Nova Spirit', 'Astral Watcher', 'Supernova Seed'],
|
|
void: ['Void Lord', 'Abyssal Horror', 'Entropy Spawn', 'Chaos Elemental', 'Nether Beast'],
|
|
};
|
|
|
|
// Get enemy name based on element and floor tier (1-100)
|
|
export function getEnemyName(element: string, floor: number): string {
|
|
const names = ENEMY_NAMES_BY_ELEMENT[element] || ['Unknown Entity'];
|
|
// Higher floors get "stronger" sounding names (pick from later in the list)
|
|
const tierIndex = Math.min(names.length -1, Math.floor(floor / 20));
|
|
const randomIndex = (tierIndex + Math.floor(Math.random() * (names.length - tierIndex))) % names.length;
|
|
return names[randomIndex!];
|
|
}
|
|
|
|
// Generate enemies for a swarm room
|
|
export function generateSwarmEnemies(floor: number): EnemyState[] {
|
|
const baseHP = getFloorMaxHP(floor);
|
|
const element = getFloorElement(floor);
|
|
const numEnemies = SWARM_CONFIG.minEnemies +
|
|
Math.floor(Math.random() * (SWARM_CONFIG.maxEnemies - SWARM_CONFIG.minEnemies + 1));
|
|
|
|
const enemies: EnemyState[] = [];
|
|
for (let i = 0; i < numEnemies; i++) {
|
|
const enemyName = getEnemyName(element, floor);
|
|
enemies.push({
|
|
id: `enemy_${i}`,
|
|
name: enemyName,
|
|
hp: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
|
|
maxHP: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
|
|
armor: SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor,
|
|
dodgeChance: 0,
|
|
barrier: getEnemyBarrier(floor, element),
|
|
element,
|
|
activeEffects: [],
|
|
effectiveArmor: SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor,
|
|
});
|
|
}
|
|
return enemies;
|
|
}
|