diff --git a/src/scenes/CradleScene.ts b/src/scenes/CradleScene.ts index 2d7a0f5..e23c8bc 100644 --- a/src/scenes/CradleScene.ts +++ b/src/scenes/CradleScene.ts @@ -15,7 +15,10 @@ import { isSchoolUnlocked } from '../run/meta'; import { GAME_WIDTH, GAME_HEIGHT } from '../config'; import { getAvailableBonuses, purchaseBonus, canAffordBonus, resetShopSession } from '../mycelium/shop'; import { getGraphStats } from '../mycelium/graph'; +import { getCycleSummary } from '../run/cycle'; +import { CYCLE_THEME_NAMES_RU } from '../run/types'; import type { BonusEffect } from '../mycelium/types'; +import narrativeData from '../data/cycle-narrative.json'; const schools = schoolsData as unknown as SchoolData[]; const biomes = biomeDataArray as BiomeData[]; @@ -107,6 +110,9 @@ export class CradleScene extends Phaser.Scene { // Meta info (top-right) const graphStats = getGraphStats(this.meta.mycelium); + const cycleSummary = getCycleSummary(this.meta.greatCycle); + const themeName = CYCLE_THEME_NAMES_RU[cycleSummary.theme]; + const metaInfo = [ `Раны: ${this.meta.totalRuns}`, `Кодекс: ${this.meta.codex.length}`, @@ -118,13 +124,40 @@ export class CradleScene extends Phaser.Scene { fontFamily: 'monospace', }).setOrigin(1, 0).setDepth(10); - // Spore count (top-right, below meta info, updates dynamically) + // Spore count (top-right, below meta info) this.sporeCountText = this.add.text(GAME_WIDTH - 12, 28, `🍄 Споры: ${this.meta.spores}`, { fontSize: '13px', color: '#44ff88', fontFamily: 'monospace', }); this.sporeCountText.setOrigin(1, 0).setDepth(10); + + // Cycle info (top-right, below spores) + const cycleInfo = `Великий Цикл ${cycleSummary.cycleNumber}: ${themeName} | Ран ${cycleSummary.runInCycle}/${cycleSummary.totalRuns}`; + this.add.text(GAME_WIDTH - 12, 46, cycleInfo, { + fontSize: '11px', + color: '#336644', + fontFamily: 'monospace', + }).setOrigin(1, 0).setDepth(10); + + // Cycle narrative quote (top-left, fades in slowly) + const themeNarrative = (narrativeData.themes as Record)[cycleSummary.theme]; + if (themeNarrative) { + const quoteText = this.add.text(12, 12, themeNarrative.cradleQuoteRu, { + fontSize: '12px', + color: '#223322', + fontFamily: 'monospace', + fontStyle: 'italic', + }); + quoteText.setAlpha(0); + quoteText.setDepth(10); + this.tweens.add({ + targets: quoteText, + alpha: 0.5, + duration: 3000, + delay: 1000, + }); + } } private showSchoolSelection(): void { diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index 0c47144..b1c21d0 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -72,6 +72,10 @@ import { createMycosisState, updateMycosis, getMycosisVisuals } from '../myceliu import type { FungalNodeInfo, MycosisState, MemoryFlash } from '../mycelium/types'; import { FUNGAL_NODE_CONFIG, MYCOSIS_CONFIG } from '../mycelium/types'; +// World traces (Great Cycle) +import { spawnWorldTraces, updateTraceGlow, type WorldTraceInfo } from '../world/traces'; +import { CYCLE_THEME_NAMES_RU } from '../run/types'; + export class GameScene extends Phaser.Scene { private gameWorld!: GameWorld; private bridge!: PhaserBridge; @@ -129,6 +133,10 @@ export class GameScene extends Phaser.Scene { private fungalNodeGlowGraphics!: Phaser.GameObjects.Graphics; private hasDepositedThisRun = false; + // World traces from past runs + private worldTraceData: WorldTraceInfo[] = []; + private worldTraceGraphics!: Phaser.GameObjects.Graphics; + constructor() { super({ key: 'GameScene' }); } @@ -144,14 +152,14 @@ export class GameScene extends Phaser.Scene { init(data: { meta: MetaState; schoolId: string; runId: number; purchasedEffects?: import('../mycelium/types').BonusEffect[]; biomeId?: string }): void { this.meta = data.meta; - this.runState = createRunState(data.runId, data.schoolId); + this.biomeId = data.biomeId ?? 'catalytic-wastes'; + this.runState = createRunState(data.runId, data.schoolId, this.biomeId); this.crisisState = null; this.playerDead = false; this.mycosisState = createMycosisState(); this.hasDepositedThisRun = false; this.memoryFlashTimer = 0; this.purchasedEffects = data.purchasedEffects ?? []; - this.biomeId = data.biomeId ?? 'catalytic-wastes'; this.schoolBonuses = getSchoolBonuses(data.schoolId); } @@ -164,6 +172,7 @@ export class GameScene extends Phaser.Scene { // 2. Generate world — use selected biome const biome = (biomeDataArray as BiomeData[]).find(b => b.id === this.biomeId) ?? biomeDataArray[0] as BiomeData; this.worldSeed = Date.now() % 1000000; + this.runState.worldSeed = this.worldSeed; const worldData = generateWorld(biome, this.worldSeed); // 3. Create tilemap @@ -184,6 +193,13 @@ export class GameScene extends Phaser.Scene { this.gameWorld.world, worldData.grid, biome, this.worldSeed, ); + // 5c. Spawn world traces from past runs (Great Cycle) + this.worldTraceData = spawnWorldTraces( + this.gameWorld.world, this.meta.greatCycle, this.biomeId, biome, + ); + this.worldTraceGraphics = this.add.graphics(); + this.worldTraceGraphics.setDepth(4); // above tiles, below entities + // 6. Initialize creature systems — filter by biome const allSpecies = speciesDataArray as SpeciesData[]; const biomeSpecies = allSpecies.filter(s => s.biome === biome.id); @@ -581,6 +597,12 @@ export class GameScene extends Phaser.Scene { } this.registry.set('invCounts', counts); + // 14. Push cycle info to registry for UIScene + this.registry.set('cycleNumber', this.meta.greatCycle.cycleNumber); + this.registry.set('runInCycle', this.meta.greatCycle.runInCycle); + this.registry.set('cycleThemeRu', CYCLE_THEME_NAMES_RU[this.meta.greatCycle.theme]); + this.registry.set('runPhaseRu', RUN_PHASE_NAMES_RU[this.runState.phase]); + // 15. Creature observation for UIScene const nearbyCreatures = getObservableCreatures(this.gameWorld.world); if (nearbyCreatures.length > 0) { @@ -698,6 +720,25 @@ export class GameScene extends Phaser.Scene { ); } + // Render world trace glows (past run markers) + this.worldTraceGraphics.clear(); + updateTraceGlow(this.worldTraceData, delta); + for (const trace of this.worldTraceData) { + const eid = trace.eid; + const phase = (this.worldTraceGraphics as unknown as { _phase?: number })._phase ?? 0; + const glowPhase = phase + Position.x[eid] * 0.01; // offset per position + const pulse = 0.3 + 0.7 * Math.sin(glowPhase + this.runState.elapsed * 0.001); + const color = trace.traceType === 'death_site' ? 0xaa3333 : 0x4488aa; + const alpha = 0.1 + 0.1 * pulse; + const radius = 8 * (0.7 + 0.3 * pulse); + + this.worldTraceGraphics.fillStyle(color, alpha); + this.worldTraceGraphics.fillCircle(Position.x[eid], Position.y[eid], radius); + // Inner core + this.worldTraceGraphics.fillStyle(color, alpha * 2); + this.worldTraceGraphics.fillCircle(Position.x[eid], Position.y[eid], radius * 0.4); + } + // Fade memory flash text if (this.memoryFlashTimer > 0) { this.memoryFlashTimer -= delta; @@ -797,6 +838,14 @@ export class GameScene extends Phaser.Scene { this.playerDead = true; this.runState.alive = false; + // Record death position for great cycle traces + const px = Position.x[this.playerEid] ?? 0; + const py = Position.y[this.playerEid] ?? 0; + this.runState.deathPosition = { + tileX: Math.floor(px / this.tileSize), + tileY: Math.floor(py / this.tileSize), + }; + // Auto-deposit all discoveries into Mycelium on death if (!this.hasDepositedThisRun) { depositKnowledge(this.meta.mycelium, this.runState); diff --git a/src/scenes/UIScene.ts b/src/scenes/UIScene.ts index a6201ff..0c1e6b8 100644 --- a/src/scenes/UIScene.ts +++ b/src/scenes/UIScene.ts @@ -51,6 +51,12 @@ export class UIScene extends Phaser.Scene { // Controls hint private controlsText!: Phaser.GameObjects.Text; + // Cycle info (top-left, below health) + private cycleText!: Phaser.GameObjects.Text; + + // Run phase (top-left, below cycle) + private phaseText!: Phaser.GameObjects.Text; + constructor() { super({ key: 'UIScene' }); } @@ -135,6 +141,20 @@ export class UIScene extends Phaser.Scene { }); this.invText.setOrigin(1, 1); + // === Cycle info (top-left, below health bar) === + this.cycleText = this.add.text(12, 32, '', { + fontSize: '10px', + color: '#446644', + fontFamily: 'monospace', + }); + + // === Run phase (below cycle) === + this.phaseText = this.add.text(12, 46, '', { + fontSize: '10px', + color: '#557755', + fontFamily: 'monospace', + }); + // === Controls hint (top-right) === this.controlsText = this.add.text(w - 12, 12, 'WASD move | E collect | F throw | 1-4 slots | scroll zoom', { fontSize: '10px', @@ -192,5 +212,15 @@ export class UIScene extends Phaser.Scene { // === Update inventory === this.invText.setText(`${Math.round(invWeight)}/${invMaxWeight} AMU | ${invSlots} items`); + + // === Update cycle info === + const cycleNumber = (this.registry.get('cycleNumber') as number) ?? 1; + const runInCycle = (this.registry.get('runInCycle') as number) ?? 1; + const cycleTheme = (this.registry.get('cycleThemeRu') as string) ?? ''; + this.cycleText.setText(`Цикл ${cycleNumber}: ${cycleTheme} | Ран ${runInCycle}/7`); + + // === Update run phase === + const runPhaseRu = (this.registry.get('runPhaseRu') as string) ?? ''; + this.phaseText.setText(runPhaseRu); } }