using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class BeatmapManager : MonoBehaviour
{
// static holder to accept SongData passed from previous scene before this manager exists
public static SongData pendingSongData = null;
public static int pendingDifficulty = -1;
///
/// Called by previous scene to hand over a SongData SO and difficulty to this manager after scene load.
/// This stores the pair in static fields so BeatmapManager.Start can pick them up.
///
public static void SetPendingSong(SongData song, int difficulty)
{
pendingSongData = song;
pendingDifficulty = difficulty;
Debug.LogWarning($"BeatmapManager.SetPendingSong called: song={(song==null?"NULL":song.songName)}, difficulty={difficulty}");
}
[Header("Assigned SongData (set at runtime from previous scene or via Inspector)")]
public SongData assignedSongData; // visible in Inspector so you can see the SO passed from previous scene
[Header("Assigned difficulty (for inspector visibility)")]
public int assignedDifficulty = -1;
///
/// Public API to accept a SongData at runtime (can be called by other scripts after scene load)
/// Also sets inspector-exposed fields so you can visually confirm in Editor during play.
///
public void AcceptSongData(SongData song, int difficulty)
{
assignedSongData = song;
assignedDifficulty = difficulty;
Debug.LogWarning($"BeatmapManager.AcceptSongData: accepted song={(song==null?"NULL":song.songName)}, difficulty={difficulty}");
}
public Beatmap beatmap; // 当前谱面数据
// 音符生成器引用
public NoteSpawner noteSpawner;
// teamUIController 引用,直接拖拽赋值
public teamUIController uiController;
// Extra fields from beatmap JSON (stored temporarily)
[HideInInspector] public string parsedTitle;
[HideInInspector] public string parsedComposer;
[HideInInspector] public string parsedIllustrator;
[HideInInspector] public string parsedCharter;
[HideInInspector] public string parsedBeatmapId;
[HideInInspector] public string parsedDifficultyName;
[HideInInspector] public string parsedCreatedDate;
[HideInInspector] public string parsedLastSavedTime;
[HideInInspector] public string parsedMusicFile;
[HideInInspector] public string parsedBackgroundFile;
[HideInInspector] public int parsedDuration; // as int per request
[HideInInspector] public int parsedBpm; // as int per request
[HideInInspector] public int parsedDifficulty; // duplicate of beatmap.difficulty
[HideInInspector] public int parsedNoteAmount;
[HideInInspector] public float globalDelaySeconds = 0f;
// New parsed fields
[HideInInspector] public bool parsedEnemyListIsEmpty;
[HideInInspector] public ColorSegment[] parsedColorSegments;
[HideInInspector] public NoteStatistic[] parsedNoteStatistics;
[HideInInspector] public TrackStates parsedTrackStates;
// Multipliers for difficulty levels
[Header("Difficulty Multipliers")]
public float ezMultiplier = 1f;
public float hdMultiplier = 1.5f;
public float inMultiplier = 2f;
public float imMultiplier = 3f;
[Header("HP Calculation Settings")]
[Tooltip("Scales the note amount to enemy HP. Example: NoteAmount * MaxMultiplier * BaseScale ≈ TotalHP")]
public float baseHpUnitScale = 1f;
// Total chart score and per-note score fields
public int totalChartScore = 1000000; // Default total score for a chart
public int perNoteScore;
public int leftoverScore;
// 从 JSON 文件加载谱面数据
public void LoadBeatmap(string fileName)
{
string path = Application.streamingAssetsPath + "/" + fileName;
if (File.Exists(path))
{
string json = File.ReadAllText(path);
Debug.Log("谱面 JSON 已读取: " + path);
ProcessJsonAndLoad(json);
}
else
{
Debug.LogError("文件不存在:" + path);
}
}
// 从原始 JSON 字符串加载谱面(允许任意磁盘路径先读取后传入)
public void LoadBeatmapFromJsonString(string json)
{
if (string.IsNullOrEmpty(json))
{
Debug.LogError("LoadBeatmapFromJsonString: json is null or empty");
return;
}
ProcessJsonAndLoad(json);
}
// 解析 JSON 但不启动 NoteSpawner(用于 test mode 的延迟开始)
public bool ParseJsonOnly(string json)
{
if (string.IsNullOrEmpty(json))
{
Debug.LogError("ParseJsonOnly: json is empty");
return false;
}
// Parse main beatmap
Beatmap parsed = null;
try
{
parsed = JsonUtility.FromJson(json);
}
catch (System.Exception ex)
{
Debug.LogError($"ParseJsonOnly 解析 Beatmap 失败: {ex}");
return false;
}
if (parsed == null)
{
Debug.LogError("ParseJsonOnly: 解析 Beatmap 返回 null");
return false;
}
// Parse extras
BeatmapExtra extra = null;
try
{
extra = JsonUtility.FromJson(json);
}
catch { extra = null; }
if (extra != null)
{
parsedTitle = extra.title;
parsedComposer = extra.composer;
parsedIllustrator = extra.illustrator;
parsedCharter = extra.charter;
parsedBeatmapId = extra.beatmapId;
parsedDifficultyName = extra.difficultyName;
parsedCreatedDate = extra.createdDate;
parsedLastSavedTime = extra.lastSavedTime;
parsedMusicFile = extra.musicFile;
parsedBackgroundFile = extra.backgroundFile;
parsedDuration = extra.duration;
parsedBpm = extra.bpm;
parsedDifficulty = extra.difficulty;
parsedNoteAmount = extra.noteAmount;
globalDelaySeconds = extra.globalDelaySeconds;
if (!string.IsNullOrEmpty(extra.musicFile)) parsed.musicFile = extra.musicFile;
if (!string.IsNullOrEmpty(extra.backgroundFile)) parsed.backgroundFile = extra.backgroundFile;
// New fields
parsedEnemyListIsEmpty = extra.enemyList_isEmpty;
if (!parsedEnemyListIsEmpty)
{
parsedColorSegments = extra.colorSegments;
}
parsedNoteStatistics = extra.noteStatistics;
parsedTrackStates = extra.trackStates;
}
else
{
parsedTitle = parsed.title;
parsedComposer = parsed.composer;
parsedIllustrator = parsed.illustrator;
parsedCharter = parsed.charter;
parsedBeatmapId = parsed.beatmapId;
parsedDifficultyName = parsed.difficultyName;
parsedCreatedDate = parsed.createdDate;
parsedMusicFile = parsed.musicFile;
parsedBackgroundFile = parsed.backgroundFile;
parsedDuration = Mathf.RoundToInt(parsed.duration);
parsedBpm = Mathf.RoundToInt(parsed.bpm);
parsedDifficulty = parsed.difficulty;
}
// Calculate per-note and leftover scores
CalculateNoteScores();
// NOTE: Do NOT modify parsed.notes here. globalDelaySeconds will be applied to audio playback instead.
beatmap = parsed;
Debug.Log("ParseJsonOnly: parsed beatmap " + beatmap.title);
// 调用统一的敌人处理逻辑(包含 HP 计算和设置)
SetupEnemiesAndHP();
return true;
}
// 直接传入已经解析的 Beatmap(兼容旧调用)
public void LoadBeatmap(Beatmap loadedBeatmap)
{
beatmap = loadedBeatmap;
Debug.Log("谱面已加载(来自 Beatmap 对象):" + (beatmap != null ? beatmap.title : "null"));
// No globalDelaySeconds available in this path. Call NoteSpawner directly.
noteSpawner.LoadBeatmap(beatmap);
}
// 读取并加载当前谱面到音符生成器(备用)
public void LoadBeatmapFromFile()
{
string path = Application.streamingAssetsPath + "/Emilia_demo.json";
if (File.Exists(path))
{
string json = File.ReadAllText(path);
ProcessJsonAndLoad(json);
}
else
{
Debug.LogError("谱面文件未找到!");
}
}
// 新增:从 TextAsset 解析并根据 parseOnly 决定是否只是解析或直接加载
public bool LoadBeatmapFromTextAsset(TextAsset chartAsset, bool parseOnly = false)
{
if (chartAsset == null)
{
Debug.LogWarning("LoadBeatmapFromTextAsset: chartAsset is null");
return false;
}
if (string.IsNullOrEmpty(chartAsset.text))
{
Debug.LogWarning("LoadBeatmapFromTextAsset: chartAsset.text is null or empty");
return false;
}
if (parseOnly)
{
return ParseJsonOnly(chartAsset.text);
}
else
{
ProcessJsonAndLoad(chartAsset.text);
return true;
}
}
// 新增:从 SongData 的 chart TextAsset 加载谱面
public bool LoadBeatmapFromSongData(SongData song, int difficulty, bool parseOnly = false)
{
if (song == null)
{
Debug.LogWarning("LoadBeatmapFromSongData: song is null");
return false;
}
TextAsset ta = song.GetChartFile(difficulty);
if (ta == null)
{
Debug.LogWarning($"LoadBeatmapFromSongData: chart TextAsset for difficulty {difficulty} is null on song {song.songName}");
return false;
}
Debug.LogWarning($"LoadBeatmapFromSongData: loading chart for song {song.songName}, difficulty {difficulty}, parseOnly={parseOnly}, chartSize={(ta.text != null ? ta.text.Length : 0)}");
return LoadBeatmapFromTextAsset(ta, parseOnly);
}
// 在 Start() 方法中初始化
void Start()
{
// 被动接收上一个场景传递过来的 SongData(通过 BeatmapManager.pendingSongData)
if (pendingSongData != null)
{
Debug.LogWarning($"BeatmapManager.Start: pendingSongData detected: {pendingSongData.songName}, difficulty={pendingDifficulty}");
// 将 pending 显示到 Inspector 字段,便于检查
assignedSongData = pendingSongData;
assignedDifficulty = pendingDifficulty;
// 尝试解析(只解析,不立即生成音符)
bool ok = LoadBeatmapFromSongData(assignedSongData, assignedDifficulty, true);
if (!ok) Debug.LogWarning("BeatmapManager.Start: LoadBeatmapFromSongData failed");
else
{
Debug.LogWarning("BeatmapManager.Start: chart parsed from SongData, attempting to assign audio and pause system");
// try to assign audio to GameManager.musicSource
var gm = FindObjectOfType();
if (gm != null && gm.musicSource != null)
{
if (assignedSongData != null && assignedSongData.audioFile != null)
{
gm.musicSource.clip = assignedSongData.audioFile;
gm.musicSource.loop = false;
Debug.LogWarning("Assigned SongData.audioFile to GameManager.musicSource.clip (from SO)");
}
else if (!string.IsNullOrEmpty(parsedMusicFile))
{
Debug.LogWarning($"Attempting Resources.Load for audio: {parsedMusicFile}");
var ac = Resources.Load(parsedMusicFile);
if (ac != null)
{
gm.musicSource.clip = ac;
gm.musicSource.loop = false;
Debug.LogWarning("Assigned audio via Resources.Load(parsedMusicFile)");
}
else
{
Debug.LogWarning($"Resources.Load failed for '{parsedMusicFile}'");
}
}
else
{
Debug.LogWarning("No audio available on SongData and parsedMusicFile empty");
}
}
else
{
Debug.LogWarning("GameManager or its musicSource not found; audio not assigned");
}
// Pause the system to show pause overlay (PauseManager will enable overlayRoot)
var pauseMgr = PauseManager.Instance ?? FindObjectOfType();
if (pauseMgr != null)
{
pauseMgr.Pause(true);
Debug.LogWarning("System paused after loading chart and audio to allow player to start");
}
else
{
Debug.LogWarning("PauseManager not found in scene; cannot pause system automatically");
}
}
// clear pending so it doesn't run again
pendingSongData = null;
pendingDifficulty = -1;
}
// 如果处于测试模式,则跳过自动加载谱面
if (GameConfig.testMode)
{
Debug.Log("Test mode active: skipping automatic beatmap load in BeatmapManager.Start");
}
else
{
// 原先默认加载的调试谱面已注释,改为仅在需要时手动恢复
// LoadBeatmap("Emilia_demo.json"); // 加载并实例化音符
Debug.LogWarning("Default automatic demo beatmap load is commented out to prioritize SO-provided charts.");
}
}
// 获取当前难度乘数
private float GetCurrentDifficultyMultiplier()
{
if (parsedDifficulty == 1) return ezMultiplier;
if (parsedDifficulty == 2) return hdMultiplier;
if (parsedDifficulty == 3) return inMultiplier;
if (parsedDifficulty == 4) return imMultiplier;
return 1f;
}
// 统一处理敌人 ID 提取、发送给 UI 以及 HP 计算的逻辑
private void SetupEnemiesAndHP()
{
if (uiController == null)
{
Debug.LogError("teamUIController not assigned in BeatmapManager Inspector");
return;
}
if (!parsedEnemyListIsEmpty && parsedColorSegments != null && parsedColorSegments.Length > 0)
{
List enemyIds = new List();
foreach (var segment in parsedColorSegments)
{
Debug.Log($"Processing colorSegment: enemyID='{segment.enemyID}', percentage={segment.percentage}");
int enemyId;
if (!int.TryParse(segment.enemyID, out enemyId))
{
Debug.LogError($"Failed to parse enemyID '{segment.enemyID}' to int");
continue;
}
enemyIds.Add(enemyId);
}
while (enemyIds.Count < 5) enemyIds.Add(0);
Debug.Log($"Assigning enemySlotIds to teamUIController: {string.Join(",", enemyIds)}");
uiController.enemySlotIds = enemyIds;
uiController.PopulateEnemySOsFromIds();
if (parsedNoteAmount > 0)
{
float currentMultiplier = GetCurrentDifficultyMultiplier();
float totalCalculatedHP = parsedNoteAmount * currentMultiplier * baseHpUnitScale;
int activeEnemyCount = enemyIds.FindAll(id => id != 0).Count;
if (activeEnemyCount == 0) activeEnemyCount = 1;
int individualMaxHP = Mathf.RoundToInt(totalCalculatedHP / activeEnemyCount);
Debug.Log($"[BeatmapManager] HP Calc -> Notes: {parsedNoteAmount}, Multiplier: {currentMultiplier}, Total: {totalCalculatedHP:F0}, ActiveEnemies: {activeEnemyCount}, Per Enemy: {individualMaxHP}");
uiController.ApplyCalculatedEnemyHP(individualMaxHP);
}
}
else
{
Debug.Log("Parsed enemy list is empty, clearing UI.");
uiController.enemySlotIds = new List { 0, 0, 0, 0, 0 };
uiController.PopulateEnemySOsFromIds();
}
}
// Parse JSON string to Beatmap and extras, then pass to NoteSpawner
private void ProcessJsonAndLoad(string json)
{
if (string.IsNullOrEmpty(json))
{
Debug.LogError("ProcessJsonAndLoad: json is empty");
return;
}
Beatmap parsed = null;
try
{
parsed = JsonUtility.FromJson(json);
}
catch (System.Exception ex)
{
Debug.LogError($"解析 Beatmap 失败: {ex}");
return;
}
if (parsed == null)
{
Debug.LogError("解析 Beatmap 返回 null");
return;
}
BeatmapExtra extra = null;
try { extra = JsonUtility.FromJson(json); } catch { extra = null; }
if (extra != null)
{
parsedTitle = extra.title;
parsedComposer = extra.composer;
parsedIllustrator = extra.illustrator;
parsedCharter = extra.charter;
parsedBeatmapId = extra.beatmapId;
parsedDifficultyName = extra.difficultyName;
parsedCreatedDate = extra.createdDate;
parsedLastSavedTime = extra.lastSavedTime;
parsedMusicFile = extra.musicFile;
parsedBackgroundFile = extra.backgroundFile;
parsedDuration = extra.duration;
parsedBpm = extra.bpm;
parsedDifficulty = extra.difficulty;
parsedNoteAmount = extra.noteAmount;
globalDelaySeconds = extra.globalDelaySeconds;
if (!string.IsNullOrEmpty(extra.musicFile)) parsed.musicFile = extra.musicFile;
if (!string.IsNullOrEmpty(extra.backgroundFile)) parsed.backgroundFile = extra.backgroundFile;
parsedEnemyListIsEmpty = extra.enemyList_isEmpty;
if (!parsedEnemyListIsEmpty) parsedColorSegments = extra.colorSegments;
parsedNoteStatistics = extra.noteStatistics;
parsedTrackStates = extra.trackStates;
}
else
{
parsedTitle = parsed.title;
parsedComposer = parsed.composer;
parsedIllustrator = parsed.illustrator;
parsedCharter = parsed.charter;
parsedBeatmapId = parsed.beatmapId;
parsedDifficultyName = parsed.difficultyName;
parsedCreatedDate = parsed.createdDate;
parsedMusicFile = parsed.musicFile;
parsedBackgroundFile = parsed.backgroundFile;
parsedDuration = Mathf.RoundToInt(parsed.duration);
parsedBpm = Mathf.RoundToInt(parsed.bpm);
parsedDifficulty = parsed.difficulty;
Debug.LogWarning("BeatmapExtra not found in JSON; populated limited fields from Beatmap.");
}
CalculateNoteScores();
beatmap = parsed;
Debug.Log("谱面已加载:" + beatmap.title);
if (noteSpawner != null) noteSpawner.LoadBeatmap(beatmap);
else Debug.LogError("NoteSpawner is null in BeatmapManager.");
SetupEnemiesAndHP();
}
private void CalculateNoteScores()
{
if (parsedNoteAmount > 0)
{
perNoteScore = totalChartScore / parsedNoteAmount;
leftoverScore = totalChartScore % parsedNoteAmount;
Debug.Log($"Calculated perNoteScore: {perNoteScore}, leftoverScore: {leftoverScore}");
}
else
{
perNoteScore = 0;
leftoverScore = 0;
Debug.LogWarning("parsedNoteAmount is zero or less; perNoteScore and leftoverScore set to 0.");
}
}
[System.Serializable]
private class BeatmapExtra
{
public string title;
public string composer;
public string illustrator;
public string charter;
public string beatmapId;
public int duration; // as int per request
public int bpm; // as int per request
public int difficulty;
public string difficultyName;
public string createdDate;
public string lastSavedTime;
public string musicFile;
public string backgroundFile;
public int noteAmount;
public float globalDelaySeconds;
public bool enemyList_isEmpty;
public ColorSegment[] colorSegments;
public NoteStatistic[] noteStatistics;
public TrackStates trackStates;
}
[System.Serializable]
public class ColorSegment { public string enemyID; public float percentage; }
[System.Serializable]
public class NoteStatistic { public string colorType; public int count; }
[System.Serializable]
public class TrackStates { public bool trackFading_active; public TrackState red; public TrackState green; public TrackState yellow; public TrackState purple; public TrackState blue; }
[System.Serializable]
public class TrackState { public bool isOn; public float fadeTime; }
}