feat: add prestige system and skill upgrades with comprehensive documentation
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 5m57s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 5m57s
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
# Context: Task5 (2a Floor Rendering & Identity)
|
||||
|
||||
## Floor Type Definitions
|
||||
|
||||
### Room Types (from `src/lib/game/constants/rooms.ts` and `src/lib/game/types/game.ts`)
|
||||
```typescript
|
||||
// Room types for spire floors
|
||||
export type RoomType = 'combat' | 'puzzle' | 'swarm' | 'speed' | 'guardian';
|
||||
|
||||
// Room generation rules:
|
||||
// - Guardian floors (10, 20, 30, etc.) are ALWAYS guardian type
|
||||
// - Every 5th floor (5, 15, 25, etc.) has a chance for special rooms
|
||||
// - Other floors are combat with chance for swarm/speed
|
||||
export const PUZZLE_ROOM_INTERVAL = 7; // Every 7 floors, chance for puzzle
|
||||
export const SWARM_ROOM_CHANCE = 0.15; // 15% chance for swarm room
|
||||
export const SPEED_ROOM_CHANCE = 0.10; // 10% chance for speed room
|
||||
export const PUZZLE_ROOM_CHANCE = 0.20; // 20% chance for puzzle room on puzzle floors
|
||||
```
|
||||
|
||||
### Swarm Room Configuration
|
||||
```typescript
|
||||
// Swarm room configuration
|
||||
export const SWARM_CONFIG = {
|
||||
minEnemies: 3,
|
||||
maxEnemies: 6,
|
||||
hpMultiplier: 0.4, // Each enemy has 40% of normal floor HP
|
||||
armorBase: 0, // Swarm enemies start with no armor
|
||||
armorPerFloor: 0.01, // Gain 1% armor per 10 floors
|
||||
};
|
||||
```
|
||||
|
||||
### Speed Room Configuration
|
||||
```typescript
|
||||
// Speed room configuration (dodging enemies)
|
||||
export const SPEED_ROOM_CONFIG = {
|
||||
baseDodgeChance: 0.25, // 25% base dodge chance
|
||||
dodgePerFloor: 0.005, // +0.5% dodge per floor
|
||||
maxDodge: 0.50, // Max 50% dodge
|
||||
speedBonus: 0.5, // 50% less time to complete if dodged
|
||||
};
|
||||
```
|
||||
|
||||
### Floor Armor Configuration
|
||||
```typescript
|
||||
// Armor scaling for normal floors
|
||||
export const FLOOR_ARMOR_CONFIG = {
|
||||
baseChance: 0, // No armor on floor 1-9
|
||||
chancePerFloor: 0.01, // +1% chance per floor after 10
|
||||
maxArmorChance: 0.5, // Max 50% of floors have armor
|
||||
minArmor: 0.05, // Min 5% armor
|
||||
maxArmor: 0.25, // Max 25% armor on non-guardians
|
||||
};
|
||||
```
|
||||
|
||||
### Puzzle Room Definitions
|
||||
```typescript
|
||||
// Puzzle room definitions - themed around attunements
|
||||
export const PUZZLE_ROOMS: Record<string, {
|
||||
name: string;
|
||||
attunements: string[];
|
||||
baseProgressPerTick: number;
|
||||
attunementBonus: number;
|
||||
description: string;
|
||||
}> = {
|
||||
enchanter_trial: {
|
||||
name: "Enchanter's Trial",
|
||||
attunements: ['enchanter'],
|
||||
baseProgressPerTick: 0.02,
|
||||
attunementBonus: 0.03,
|
||||
description: "Decipher ancient enchantment runes."
|
||||
},
|
||||
fabricator_trial: {
|
||||
name: "Fabricator's Trial",
|
||||
attunements: ['fabricator'],
|
||||
baseProgressPerTick: 0.02,
|
||||
attunementBonus: 0.03,
|
||||
description: "Construct a mana-powered mechanism."
|
||||
},
|
||||
invoker_trial: {
|
||||
name: "Invoker's Trial",
|
||||
attunements: ['invoker'],
|
||||
baseProgressPerTick: 0.02,
|
||||
attunementBonus: 0.03,
|
||||
description: "Commune with guardian spirits."
|
||||
},
|
||||
// ... hybrid rooms also defined
|
||||
};
|
||||
```
|
||||
|
||||
### Guardian Definitions (from `src/lib/game/constants/guardians.ts`)
|
||||
```typescript
|
||||
// All guardians have armor - damage reduction percentage
|
||||
export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
10: {
|
||||
name: "Ignis Prime", element: "fire", hp: 5000, pact: 1.5, color: "#FF6B35",
|
||||
armor: 0.10, // 10% damage reduction
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 5, desc: '+5% Fire damage' },
|
||||
{ type: 'maxMana', value: 50, desc: '+50 max mana' },
|
||||
],
|
||||
pactCost: 500,
|
||||
pactTime: 2,
|
||||
uniquePerk: "Fire spells cast 10% faster"
|
||||
},
|
||||
20: { name: "Aqua Regia", element: "water", hp: 15000, pact: 1.75, color: "#4ECDC4", armor: 0.15, ... },
|
||||
30: { name: "Ventus Rex", element: "air", hp: 30000, pact: 2.0, color: "#00D4FF", armor: 0.18, ... },
|
||||
40: { name: "Terra Firma", element: "earth", hp: 50000, pact: 2.25, color: "#F4A261", armor: 0.25, ... },
|
||||
50: { name: "Lux Aeterna", element: "light", hp: 80000, pact: 2.5, color: "#FFD700", armor: 0.20, ... },
|
||||
60: { name: "Umbra Mortis", element: "dark", hp: 120000, pact: 2.75, color: "#9B59B6", armor: 0.22, ... },
|
||||
80: { name: "Mors Ultima", element: "death", hp: 250000, pact: 3.25, color: "#778CA3", armor: 0.25, ... },
|
||||
90: { name: "Primordialis", element: "void", hp: 400000, pact: 4.0, color: "#4A235A", armor: 0.30, ... },
|
||||
100: { name: "The Awakened One", element: "stellar", hp: 1000000, pact: 5.0, color: "#F0E68C", armor: 0.35, ... },
|
||||
};
|
||||
```
|
||||
|
||||
### GuardianDef Type (from `src/lib/game/types/attunements.ts`)
|
||||
```typescript
|
||||
export interface GuardianDef {
|
||||
name: string;
|
||||
element: string;
|
||||
hp: number;
|
||||
pact: number; // Pact multiplier when signed
|
||||
color: string;
|
||||
boons: GuardianBoon[]; // Bonuses granted when pact is signed
|
||||
pactCost: number; // Mana cost to perform pact ritual
|
||||
pactTime: number; // Hours required for pact ritual
|
||||
uniquePerk: string; // Description of unique perk
|
||||
armor?: number; // Damage reduction (0-1, e.g., 0.2 = 20% reduction)
|
||||
}
|
||||
|
||||
export interface GuardianBoon {
|
||||
type: 'maxMana' | 'manaRegen' | 'castingSpeed' | 'elementalDamage' | 'rawDamage' |
|
||||
'critChance' | 'critDamage' | 'spellEfficiency' | 'manaGain' | 'insightGain' |
|
||||
'studySpeed' | 'prestigeInsight';
|
||||
value: number;
|
||||
desc: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Element Definitions (from `src/lib/game/constants/elements.ts`)
|
||||
```typescript
|
||||
export const ELEMENTS: Record<string, ElementDef> = {
|
||||
// Base Elements
|
||||
fire: { name: "Fire", sym: "🔥", color: "#FF6B35", glow: "#FF6B3540", cat: "base" },
|
||||
water: { name: "Water", sym: "💧", color: "#4ECDC4", glow: "#4ECDC440", cat: "base" },
|
||||
air: { name: "Air", sym: "🌬️", color: "#00D4FF", glow: "#00D4FF40", cat: "base" },
|
||||
earth: { name: "Earth", sym: "⛰️", color: "#F4A261", glow: "#F4A26140", cat: "base" },
|
||||
light: { name: "Light", sym: "☀️", color: "#FFD700", glow: "#FFD70040", cat: "base" },
|
||||
dark: { name: "Dark", sym: "🌑", color: "#9B59B6", glow: "#9B59B640", cat: "base" },
|
||||
death: { name: "Death", sym: "💀", color: "#778CA3", glow: "#778CA340", cat: "base" },
|
||||
// ... other elements
|
||||
};
|
||||
|
||||
export const FLOOR_ELEM_CYCLE = ["fire", "water", "air", "earth", "light", "dark", "death"];
|
||||
```
|
||||
|
||||
### Room Type Labels (from `src/lib/game/constants/index.ts`)
|
||||
```typescript
|
||||
export const ROOM_TYPE_LABELS: Record<string, { label: string; icon: string; color: string }> = {
|
||||
combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' },
|
||||
swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' },
|
||||
speed: { label: 'Speed', icon: '💨', color: '#3B82F6' },
|
||||
guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' },
|
||||
puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' },
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current Floor Rendering Code
|
||||
|
||||
### SpireTab.tsx (from `src/components/game/tabs/SpireTab.tsx`)
|
||||
|
||||
#### Room Type Display Configuration
|
||||
```typescript
|
||||
// Room type configurations for display
|
||||
const ROOM_TYPE_CONFIG: Record<string, { label: string; icon: string; color: string }> = {
|
||||
combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' },
|
||||
swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' },
|
||||
speed: { label: 'Speed', icon: '💨', color: '#3B82F6' },
|
||||
guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' },
|
||||
puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' },
|
||||
};
|
||||
```
|
||||
|
||||
#### Floor Type Badge Rendering
|
||||
```tsx
|
||||
<Badge
|
||||
className="ml-2"
|
||||
style={{
|
||||
backgroundColor: `${roomConfig.color}20`,
|
||||
color: roomConfig.color,
|
||||
borderColor: `${roomConfig.color}60`
|
||||
}}
|
||||
>
|
||||
{roomConfig.icon} {roomConfig.label}
|
||||
</Badge>
|
||||
```
|
||||
|
||||
#### Guardian Name Display
|
||||
```tsx
|
||||
{isGuardianFloor && currentGuardian && (
|
||||
<div className="text-sm font-semibold game-panel-title" style={{ color: floorElemDef?.color }}>
|
||||
⚔️ {currentGuardian.name}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
#### Single Enemy Display (Combat/Speed/Guardian)
|
||||
```tsx
|
||||
{!isGuardianFloor && primaryEnemy && roomType !== 'swarm' && (
|
||||
<div className="p-3 bg-gray-800/50 rounded border border-gray-700">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skull className="w-4 h-4 text-red-400" />
|
||||
<span className="text-sm font-semibold text-gray-200">
|
||||
{primaryEnemy.name || 'Unknown Enemy'}
|
||||
</span>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{ELEMENTS[primaryEnemy.element]?.sym} {ELEMENTS[primaryEnemy.element]?.name}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Enemy HP Bar */}
|
||||
<div className="space-y-1 mb-2">
|
||||
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${Math.max(0, (primaryEnemy.hp / primaryEnemy.maxHP) * 100)}%`,
|
||||
background: `linear-gradient(90deg, ${floorElemDef?.color}99, ${floorElemDef?.color})`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-gray-400 game-mono">
|
||||
<span>{fmt(primaryEnemy.hp)} / {fmt(primaryEnemy.maxHP)} HP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enemy Properties */}
|
||||
<div className="flex flex-wrap gap-2 text-xs">
|
||||
{primaryEnemy.armor > 0 && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="outline" className="text-xs py-0">
|
||||
<Shield className="w-3 h-3 mr-1" />
|
||||
{(primaryEnemy.armor * 100).toFixed(0)}% Armor
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Reduces incoming damage by {(primaryEnemy.armor * 100).toFixed(0)}%</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{primaryEnemy.dodgeChance > 0 && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="outline" className="text-xs py-0">
|
||||
<Wind className="w-3 h-3 mr-1" />
|
||||
{(primaryEnemy.dodgeChance * 100).toFixed(0)}% Dodge
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Chance to dodge attacks and reduce progress</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
#### Swarm Enemies Display
|
||||
```tsx
|
||||
{roomType === 'swarm' && swarmEnemies.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs text-gray-400 font-semibold">
|
||||
Swarm Enemies ({swarmEnemies.length})
|
||||
</div>
|
||||
{swarmEnemies.map((enemy, index) => (
|
||||
<div key={enemy.id || `swarm-${index}`} className="p-2 bg-gray-800/50 rounded border border-gray-700">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skull className="w-3 h-3 text-red-400" />
|
||||
<span className="text-xs font-semibold text-gray-300">
|
||||
{enemy.name || `Enemy ${index + 1}`}
|
||||
</span>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs py-0">
|
||||
{ELEMENTS[enemy.element]?.sym} {fmt(enemy.hp)}/{fmt(enemy.maxHP)} HP
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="h-1.5 bg-gray-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${Math.max(0, (enemy.hp / enemy.maxHP) * 100)}%`,
|
||||
background: `linear-gradient(90deg, ${ELEMENTS[enemy.element]?.color}99, ${ELEMENTS[enemy.element]?.color})`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
#### Puzzle Room Display
|
||||
```tsx
|
||||
{roomType === 'puzzle' && (
|
||||
<div className="p-3 bg-purple-900/20 rounded border border-purple-700">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-lg">🧩</span>
|
||||
<span className="text-sm font-semibold text-purple-300">
|
||||
{currentRoom.puzzleId ? currentRoom.puzzleId.replace(/_/g, ' ').toUpperCase() : 'Puzzle Room'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-xs text-gray-400">
|
||||
<span>Progress</span>
|
||||
<span>{((currentRoom.puzzleProgress || 0) * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={Math.min(100, (currentRoom.puzzleProgress || 0) * 100)}
|
||||
className="h-2 bg-gray-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enemy Naming Logic
|
||||
|
||||
### Enemy Name Generation (from `src/lib/game/store.ts`)
|
||||
|
||||
```typescript
|
||||
// Generate enemy names based on 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!];
|
||||
}
|
||||
```
|
||||
|
||||
### Enemy State Type (from `src/lib/game/types/game.ts`)
|
||||
```typescript
|
||||
export interface EnemyState {
|
||||
id: string;
|
||||
name: string; // Display name for the enemy
|
||||
hp: number;
|
||||
maxHP: number;
|
||||
armor: number; // Damage reduction (0-1)
|
||||
dodgeChance: number; // For speed rooms (0-1)
|
||||
element: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Floor State Type
|
||||
```typescript
|
||||
export interface FloorState {
|
||||
roomType: RoomType;
|
||||
enemies: EnemyState[]; // For swarm rooms, multiple enemies
|
||||
puzzleProgress?: number; // For puzzle rooms (0-1)
|
||||
puzzleRequired?: number; // Total progress needed
|
||||
puzzleId?: string; // Which puzzle type
|
||||
puzzleAttunements?: string[]; // Which attunements speed up this puzzle
|
||||
}
|
||||
```
|
||||
|
||||
### Floor Generation Functions (from `src/lib/game/store.ts`)
|
||||
|
||||
```typescript
|
||||
// Generate room type for a floor
|
||||
export function generateRoomType(floor: number): RoomType {
|
||||
// Guardian floors are always guardian type
|
||||
if (GUARDIANS[floor]) {
|
||||
return 'guardian';
|
||||
}
|
||||
|
||||
// Check for puzzle room (every PUZZLE_ROOM_INTERVAL floors)
|
||||
if (floor % PUZZLE_ROOM_INTERVAL === 0 && Math.random() < PUZZLE_ROOM_CHANCE) {
|
||||
return 'puzzle';
|
||||
}
|
||||
|
||||
// Check for swarm room
|
||||
if (Math.random() < SWARM_ROOM_CHANCE) {
|
||||
return 'swarm';
|
||||
}
|
||||
|
||||
// Check for speed room
|
||||
if (Math.random() < SPEED_ROOM_CHANCE) {
|
||||
return 'speed';
|
||||
}
|
||||
|
||||
// Default to combat
|
||||
return 'combat';
|
||||
}
|
||||
|
||||
// Get armor for a non-guardian floor
|
||||
export function getFloorArmor(floor: number): number {
|
||||
if (GUARDIANS[floor]) {
|
||||
return GUARDIANS[floor].armor || 0;
|
||||
}
|
||||
|
||||
// Armor becomes more common on higher floors
|
||||
if (floor < 10) return 0;
|
||||
|
||||
const armorChance = Math.min(FLOOR_ARMOR_CONFIG.maxArmorChance,
|
||||
FLOOR_ARMOR_CONFIG.baseChance + (floor - 10) * FLOOR_ARMOR_CONFIG.chancePerFloor);
|
||||
|
||||
if (Math.random() > armorChance) return 0;
|
||||
|
||||
// Scale armor with floor
|
||||
const armorRange = FLOOR_ARMOR_CONFIG.maxArmor - FLOOR_ARMOR_CONFIG.minArmor;
|
||||
const floorProgress = Math.min(1, (floor - 10) / 90);
|
||||
return FLOOR_ARMOR_CONFIG.minArmor + armorRange * floorProgress * Math.random();
|
||||
}
|
||||
|
||||
// Get dodge chance for a speed room
|
||||
export function getDodgeChance(floor: number): number {
|
||||
return Math.min(
|
||||
SPEED_ROOM_CONFIG.maxDodge,
|
||||
SPEED_ROOM_CONFIG.baseDodgeChance + floor * SPEED_ROOM_CONFIG.dodgePerFloor
|
||||
);
|
||||
}
|
||||
|
||||
// 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,
|
||||
element,
|
||||
});
|
||||
}
|
||||
return enemies;
|
||||
}
|
||||
|
||||
// Generate initial floor state
|
||||
export function generateFloorState(floor: number): FloorState {
|
||||
const roomType = generateRoomType(floor);
|
||||
const element = getFloorElement(floor);
|
||||
const baseHP = getFloorMaxHP(floor);
|
||||
const guardian = GUARDIANS[floor];
|
||||
|
||||
switch (roomType) {
|
||||
case 'guardian':
|
||||
return {
|
||||
roomType: 'guardian',
|
||||
enemies: [{
|
||||
id: 'guardian',
|
||||
name: guardian.name,
|
||||
hp: guardian.hp,
|
||||
maxHP: guardian.hp,
|
||||
armor: guardian.armor || 0,
|
||||
dodgeChance: 0,
|
||||
element: guardian.element,
|
||||
}],
|
||||
};
|
||||
|
||||
case 'swarm':
|
||||
return {
|
||||
roomType: 'swarm',
|
||||
enemies: generateSwarmEnemies(floor),
|
||||
};
|
||||
|
||||
case 'speed': {
|
||||
const speedEnemyName = getEnemyName(element, floor);
|
||||
return {
|
||||
roomType: 'speed',
|
||||
enemies: [{
|
||||
id: 'speed_enemy',
|
||||
name: speedEnemyName,
|
||||
hp: baseHP,
|
||||
maxHP: baseHP,
|
||||
armor: getFloorArmor(floor),
|
||||
dodgeChance: getDodgeChance(floor),
|
||||
element,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
case 'puzzle': {
|
||||
// Select a puzzle type based on player's attunements
|
||||
const puzzleKeys = Object.keys(PUZZLE_ROOMS);
|
||||
const selectedPuzzle = puzzleKeys[Math.floor(Math.random() * puzzleKeys.length)];
|
||||
const puzzle = PUZZLE_ROOMS[selectedPuzzle];
|
||||
return {
|
||||
roomType: 'puzzle',
|
||||
enemies: [],
|
||||
puzzleProgress: 0,
|
||||
puzzleRequired: 1,
|
||||
puzzleId: selectedPuzzle,
|
||||
puzzleAttunements: puzzle.attunements,
|
||||
};
|
||||
}
|
||||
|
||||
default: // combat
|
||||
const combatEnemyName = getEnemyName(element, floor);
|
||||
return {
|
||||
roomType: 'combat',
|
||||
enemies: [{
|
||||
id: 'enemy',
|
||||
name: combatEnemyName,
|
||||
hp: baseHP,
|
||||
maxHP: baseHP,
|
||||
armor: getFloorArmor(floor),
|
||||
dodgeChance: 0,
|
||||
element,
|
||||
}],
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Special Floor Properties
|
||||
|
||||
### Currently Implemented Properties
|
||||
|
||||
#### Armor (Damage Reduction)
|
||||
- **Guardian floors**: Defined in `GUARDIANS[floor].armor` (0.10 to 0.35)
|
||||
- **Non-guardian floors**: Randomly generated via `getFloorArmor(floor)` using `FLOOR_ARMOR_CONFIG`
|
||||
- **Swarm enemies**: `SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor`
|
||||
- Displayed in UI with shield icon and percentage
|
||||
|
||||
#### Dodge Chance
|
||||
- **Speed rooms only**: Generated via `getDodgeChance(floor)` using `SPEED_ROOM_CONFIG`
|
||||
- Base: 25%, scales +0.5% per floor, max 50%
|
||||
- Displayed in UI with wind icon and percentage
|
||||
|
||||
#### Health/HP
|
||||
- **Guardian floors**: `GUARDIANS[floor].hp` (5000 to 1000000)
|
||||
- **Normal floors**: `getFloorMaxHP(floor)` - scales with floor number
|
||||
- **Swarm enemies**: `baseHP * SWARM_CONFIG.hpMultiplier` (40% of normal)
|
||||
|
||||
### Properties Mentioned in Task But Not Currently in Floor Config
|
||||
- **healthRegen**: Not currently implemented as a floor/enemy property (only exists in guardian boons as `manaRegen` for player)
|
||||
- **barrier**: Not currently implemented as a floor/enemy property (only exists as attunement mana type)
|
||||
|
||||
Note: The task mentions displaying "Special floor properties (armor%, health regen, barrier, dodge)" but `healthRegen` and `barrier` are not currently implemented in the floor config. These may need to be added as part of this task.
|
||||
|
||||
---
|
||||
|
||||
## File Paths
|
||||
|
||||
### Key Files for Task 5 (2a Floor Rendering & Identity)
|
||||
|
||||
1. **Floor/Room Type Definitions**:
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/constants/rooms.ts` - Room types, swarm/speed config, armor config
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/constants/guardians.ts` - Guardian definitions with names, HP, armor
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/constants/elements.ts` - Element definitions with symbols and colors
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/constants/index.ts` - ROOM_TYPE_LABELS export
|
||||
|
||||
2. **Type Definitions**:
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/types/game.ts` - RoomType, EnemyState, FloorState interfaces
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/types/attunements.ts` - GuardianDef, GuardianBoon interfaces
|
||||
|
||||
3. **Floor Rendering UI**:
|
||||
- `/home/user/repos/Mana-Loop/src/components/game/tabs/SpireTab.tsx` - Main floor rendering component with enemy display, room type badges, armor/dodge tooltips
|
||||
|
||||
4. **Floor Generation Logic**:
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/store.ts` - `getEnemyName()`, `generateRoomType()`, `generateFloorState()`, `getFloorArmor()`, `getDodgeChance()`, `generateSwarmEnemies()`
|
||||
|
||||
5. **Element Cycle for Floors**:
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/store.ts` - `getFloorElement()`, `getFloorMaxHP()`
|
||||
- `/home/user/repos/Mana-Loop/src/lib/game/constants/elements.ts` - `FLOOR_ELEM_CYCLE`
|
||||
Reference in New Issue
Block a user