Files
BlockSeasons/lib/game/models/season.dart
T
airkjw 8947221b27 feat(audio): looping per-season background music system
MusicService (looping audioplayers player, independent of SFX) driven by the
active season theme's new 'bgm' key; switches track on season change, pauses on
app background, all failures swallowed. Separate Music on/off toggle in Settings
(persisted, independent of SFX). Season packs carry bgm keys (menu/season_001/
season_002), manifest regenerated. Assets slot assets/audio/bgm/ ready — drop in
menu.mp3/season_001.mp3/season_002.mp3 (CC0) and it plays; silent until then.
180 tests green, analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 09:31:10 +09:00

123 lines
3.9 KiB
Dart

import 'stage.dart';
/// Visual identity of a season. Colors are int ARGB so this file stays
/// pure Dart (architecture guard forbids Flutter imports here).
class SeasonTheme {
const SeasonTheme({
this.tileSet = 'spring',
this.background = '',
this.backgroundGradient = defaultGradient,
this.accentColor = 0xFFFF7EB3,
this.particleType = 'petals',
this.tilePalette,
this.boardTint,
this.bgm = 'menu',
});
factory SeasonTheme.fromJson(Map<String, dynamic> json) => SeasonTheme(
tileSet: (json['tileSet'] as String?) ?? 'spring',
background: (json['background'] as String?) ?? '',
backgroundGradient: json['backgroundGradient'] != null
? [for (final c in json['backgroundGradient'] as List) (c as num).toInt()]
: defaultGradient,
accentColor: (json['accentColor'] as int?) ?? 0xFFFF7EB3,
particleType: (json['particleType'] as String?) ?? 'petals',
tilePalette: json['tilePalette'] != null
? [for (final c in json['tilePalette'] as List) (c as num).toInt()]
: null,
boardTint: json['boardTint'] as int?,
bgm: (json['bgm'] as String?) ?? 'menu',
);
/// Season 1 "First Bloom": deep navy dusk.
static const defaultGradient = [0xFF0E1430, 0xFF16204A, 0xFF2A2E5E];
static const fallback = SeasonTheme();
final String tileSet;
final String background;
/// Top-to-bottom screen gradient, int ARGB.
final List<int> backgroundGradient;
final int accentColor;
/// petals | snow | leaves | none
final String particleType;
/// Optional tile color override; null = built-in candy palette.
final List<int>? tilePalette;
/// Optional board background override.
final int? boardTint;
/// Looping background-music track key, resolved to `assets/audio/bgm/KEY.mp3`.
/// Remote seasons reuse bundled track keys (no remote audio download).
final String bgm;
Map<String, dynamic> toJson() => {
'tileSet': tileSet,
'background': background,
'backgroundGradient': backgroundGradient,
'accentColor': accentColor,
'particleType': particleType,
if (tilePalette != null) 'tilePalette': tilePalette,
if (boardTint != null) 'boardTint': boardTint,
'bgm': bgm,
};
}
/// A season's full content: metadata, theme, and its stages. The unit of
/// remote (or bundled) delivery.
class SeasonPack {
const SeasonPack({
required this.schemaVersion,
required this.seasonId,
required this.version,
required this.title,
required this.theme,
required this.stages,
});
/// Bump when the pack format changes incompatibly; older app builds skip
/// packs with a newer schema instead of crashing.
static const int supportedSchema = 1;
factory SeasonPack.fromJson(Map<String, dynamic> json) {
final schema = json['schemaVersion'] as int;
if (schema > supportedSchema) {
throw FormatException('Unsupported pack schema: $schema');
}
return SeasonPack(
schemaVersion: schema,
seasonId: json['seasonId'] as String,
version: json['version'] as int,
title: (json['title'] as Map<String, dynamic>)
.map((k, v) => MapEntry(k, v as String)),
theme: SeasonTheme.fromJson(json['theme'] as Map<String, dynamic>),
stages: [
for (final stage in json['stages'] as List)
StageConfig.fromJson(stage as Map<String, dynamic>),
],
);
}
final int schemaVersion;
final String seasonId;
final int version;
final Map<String, String> title;
final SeasonTheme theme;
final List<StageConfig> stages;
String titleFor(String languageCode) =>
title[languageCode] ?? title['en'] ?? seasonId;
Map<String, dynamic> toJson() => {
'schemaVersion': schemaVersion,
'seasonId': seasonId,
'version': version,
'title': title,
'theme': theme.toJson(),
'stages': [for (final stage in stages) stage.toJson()],
};
}