755 lines
29 KiB
C#
755 lines
29 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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; // Documentation text normalized.
|
|
|
|
// Documentation text normalized.
|
|
public NoteSpawner noteSpawner;
|
|
|
|
// Documentation text normalized.
|
|
public teamUIController uiController;
|
|
|
|
public Image bgSpriteImage; // Documentation text normalized.
|
|
|
|
public GameObject bgSpriteObject;
|
|
public Image startCanvas_image;
|
|
|
|
// 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("Documentation text normalized.")]
|
|
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;
|
|
|
|
// Documentation text normalized.
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
public void LoadBeatmapFromJsonString(string json)
|
|
{
|
|
if (string.IsNullOrEmpty(json))
|
|
{
|
|
Debug.LogError("LoadBeatmapFromJsonString: json is null or empty");
|
|
return;
|
|
}
|
|
ProcessJsonAndLoad(json);
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
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<Beatmap>(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<BeatmapExtra>(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(parsed);
|
|
|
|
// NOTE: Do NOT modify parsed.notes here. globalDelaySeconds will be applied to audio playback instead.
|
|
|
|
beatmap = parsed;
|
|
Debug.Log("ParseJsonOnly: parsed beatmap " + beatmap.title);
|
|
|
|
// Documentation text normalized.
|
|
SetupEnemiesAndHP();
|
|
ApplyTrackScoreCaps(beatmap);
|
|
ApplyTrackScoreCaps(beatmap);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
public void LoadBeatmap(Beatmap loadedBeatmap)
|
|
{
|
|
beatmap = loadedBeatmap;
|
|
parsedNoteAmount = (beatmap != null && beatmap.notes != null) ? beatmap.notes.Length : 0;
|
|
CalculateNoteScores(beatmap);
|
|
Debug.Log("谱面加载完成,当前 Beatmap 标题: " + (beatmap != null ? beatmap.title : "null"));
|
|
// No globalDelaySeconds available in this path. Call NoteSpawner directly.
|
|
noteSpawner.LoadBeatmap(beatmap);
|
|
ApplyTrackScoreCaps(beatmap);
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
public void LoadBeatmapFromFile()
|
|
{
|
|
string path = Application.streamingAssetsPath + "/Emilia_demo.json";
|
|
if (File.Exists(path))
|
|
{
|
|
string json = File.ReadAllText(path);
|
|
ProcessJsonAndLoad(json);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("默认谱面文件未找到");
|
|
}
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
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);
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
void Start()
|
|
{
|
|
// 如果 assignedSongData 为空,尝试从 SongDataHolder 获取,作为最后的兜底
|
|
if (pendingSongData == null && assignedSongData == null)
|
|
{
|
|
assignedSongData = SongDataHolder.SelectedSongData;
|
|
Debug.Log($"[BeatmapManager] No pending song, fallback to SongDataHolder: {(assignedSongData != null ? assignedSongData.songName : "null")}");
|
|
}
|
|
|
|
// 无论如何,在加载谱面前先同步敌人编队到 teamUIController
|
|
SyncEnemyListToUI();
|
|
|
|
if (pendingSongData != null)
|
|
{
|
|
Debug.LogWarning($"BeatmapManager.Start: pendingSongData detected: {pendingSongData.songName}, difficulty={pendingDifficulty}");
|
|
// Documentation text normalized.
|
|
assignedSongData = pendingSongData;
|
|
assignedDifficulty = pendingDifficulty;
|
|
|
|
// Documentation text normalized.
|
|
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 = FindAnyObjectByType<GameManager>();
|
|
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<AudioClip>(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");
|
|
}
|
|
|
|
// Assign fullscreen image from SongData to bgSprite if available
|
|
if (assignedSongData != null && assignedSongData.fullscreen_songPicture != null)
|
|
{
|
|
// Prefer setting a Scene GameObject's SpriteRenderer if provided
|
|
if (bgSpriteObject != null)
|
|
{
|
|
var sr = bgSpriteObject.GetComponentInChildren<SpriteRenderer>();
|
|
if (sr != null)
|
|
{
|
|
sr.sprite = assignedSongData.fullscreen_songPicture;
|
|
// ensure fully visible
|
|
sr.color = new Color(sr.color.r, sr.color.g, sr.color.b, 1f);
|
|
if (startCanvas_image != null)
|
|
{
|
|
startCanvas_image.sprite = assignedSongData.fullscreen_songPicture;
|
|
}
|
|
sr = null;
|
|
Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to SpriteRenderer on bgSpriteObject for song {assignedSongData.songName}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("bgSpriteObject has no SpriteRenderer in children; cannot assign fullscreen sprite");
|
|
}
|
|
}
|
|
else if (bgSpriteImage != null)
|
|
{
|
|
bgSpriteImage.sprite = assignedSongData.fullscreen_songPicture;
|
|
Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to bgSpriteImage for song {assignedSongData.songName}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("No bg target (bgSpriteObject or bgSpriteImage) assigned; fullscreen sprite not applied");
|
|
}
|
|
}
|
|
|
|
// Pause the system to show pause overlay (PauseManager will enable overlayRoot)
|
|
var pauseMgr = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
|
|
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;
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
if (GameConfig.testMode)
|
|
{
|
|
Debug.Log("Test mode active: skipping automatic beatmap load in BeatmapManager.Start");
|
|
}
|
|
else
|
|
{
|
|
// Documentation text normalized.
|
|
// Documentation text normalized.
|
|
Debug.LogWarning("Default automatic demo beatmap load is commented out to prioritize SO-provided charts.");
|
|
}
|
|
}
|
|
|
|
// Documentation text normalized.
|
|
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;
|
|
}
|
|
|
|
private void SyncEnemyListToUI()
|
|
{
|
|
if (uiController == null) return;
|
|
|
|
List<int> enemyIds = new List<int>();
|
|
|
|
// 优先从 SO 读取
|
|
if (assignedSongData != null && assignedSongData.enemyList != null && assignedSongData.enemyList.Count > 0)
|
|
{
|
|
enemyIds = new List<int>(assignedSongData.enemyList);
|
|
}
|
|
|
|
// 确保 5 个槽位:截断或补 0
|
|
while (enemyIds.Count < 5) enemyIds.Add(0);
|
|
if (enemyIds.Count > 5) enemyIds = enemyIds.GetRange(0, 5);
|
|
|
|
uiController.enemySlotIds = enemyIds;
|
|
Debug.Log($"[BeatmapManager] SyncEnemyListToUI: {string.Join(",", enemyIds)}");
|
|
}
|
|
|
|
private void SetupEnemiesAndHP()
|
|
{
|
|
if (uiController == null)
|
|
{
|
|
Debug.LogError("teamUIController not assigned in BeatmapManager Inspector");
|
|
return;
|
|
}
|
|
|
|
List<int> enemyIds = new List<int>();
|
|
|
|
// 优先使用 SongData SO 中配置的敌人列表
|
|
if (assignedSongData != null && assignedSongData.enemyList != null && assignedSongData.enemyList.Count > 0)
|
|
{
|
|
enemyIds = new List<int>(assignedSongData.enemyList);
|
|
Debug.Log($"[BeatmapManager] Using enemyList from SongData SO: {string.Join(",", enemyIds)}");
|
|
}
|
|
// 如果 SO 中没有,则尝试从谱面 JSON 解析
|
|
else if (!parsedEnemyListIsEmpty && parsedColorSegments != null && parsedColorSegments.Length > 0)
|
|
{
|
|
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);
|
|
}
|
|
Debug.Log($"[BeatmapManager] Using enemyList from Beatmap JSON: {string.Join(",", enemyIds)}");
|
|
}
|
|
|
|
// 确保列表始终为 5 个槽位
|
|
while (enemyIds.Count < 5) enemyIds.Add(0);
|
|
if (enemyIds.Count > 5) enemyIds = enemyIds.GetRange(0, 5);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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<Beatmap>(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<BeatmapExtra>(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.");
|
|
}
|
|
|
|
// Ensure amount and color statistics are populated even if missing from JSON
|
|
ValidateAndPopulateStatistics(parsed);
|
|
|
|
CalculateNoteScores(parsed);
|
|
|
|
beatmap = parsed;
|
|
Debug.Log("谱面加载完成: " + beatmap.title);
|
|
|
|
if (noteSpawner != null) noteSpawner.LoadBeatmap(beatmap);
|
|
else Debug.LogError("NoteSpawner is null in BeatmapManager.");
|
|
|
|
SetupEnemiesAndHP();
|
|
}
|
|
|
|
private int GetEffectiveNoteCount(Beatmap sourceBeatmap = null)
|
|
{
|
|
int noteCount = 0;
|
|
|
|
var src = sourceBeatmap != null ? sourceBeatmap : beatmap;
|
|
if (src != null && src.notes != null)
|
|
noteCount = src.notes.Length;
|
|
|
|
if (noteCount <= 0)
|
|
noteCount = Mathf.Max(0, parsedNoteAmount);
|
|
|
|
if (noteCount > 0)
|
|
parsedNoteAmount = noteCount;
|
|
|
|
return noteCount;
|
|
}
|
|
|
|
private void CalculateNoteScores(Beatmap sourceBeatmap = null)
|
|
{
|
|
int noteCount = GetEffectiveNoteCount(sourceBeatmap);
|
|
if (noteCount > 0)
|
|
{
|
|
perNoteScore = totalChartScore / noteCount;
|
|
leftoverScore = totalChartScore % noteCount;
|
|
Debug.Log($"Calculated perNoteScore: {perNoteScore}, leftoverScore: {leftoverScore}, noteCount: {noteCount}");
|
|
}
|
|
else
|
|
{
|
|
perNoteScore = 0;
|
|
leftoverScore = 0;
|
|
Debug.LogWarning("parsedNoteAmount is zero or less; perNoteScore and leftoverScore set to 0.");
|
|
}
|
|
}
|
|
|
|
private void ValidateAndPopulateStatistics(Beatmap parsed)
|
|
{
|
|
if (parsed == null || parsed.notes == null) return;
|
|
|
|
// Ensure note amount is correct
|
|
if (parsedNoteAmount <= 0)
|
|
{
|
|
parsedNoteAmount = parsed.notes.Length;
|
|
if (JudgeManager.IsDebugEnabled) Debug.Log($"[BeatmapManager] Calculated missing noteAmount: {parsedNoteAmount}");
|
|
}
|
|
|
|
// Ensure note statistics (counts by color) are populated
|
|
if (parsedNoteStatistics == null || parsedNoteStatistics.Length == 0)
|
|
{
|
|
var counts = new System.Collections.Generic.Dictionary<string, int>();
|
|
foreach (var note in parsed.notes)
|
|
{
|
|
if (string.IsNullOrEmpty(note.color)) continue;
|
|
string c = note.color.ToLower();
|
|
if (!counts.ContainsKey(c)) counts[c] = 0;
|
|
counts[c]++;
|
|
}
|
|
|
|
parsedNoteStatistics = new NoteStatistic[counts.Count];
|
|
int i = 0;
|
|
foreach (var kvp in counts)
|
|
{
|
|
parsedNoteStatistics[i++] = new NoteStatistic { colorType = kvp.Key, count = kvp.Value };
|
|
}
|
|
if (JudgeManager.IsDebugEnabled) Debug.Log($"[BeatmapManager] Calculated missing noteStatistics for {counts.Count} colors");
|
|
}
|
|
}
|
|
|
|
// Compute per-track note counts and apply max score caps to allies
|
|
private void ApplyTrackScoreCaps(Beatmap parsed)
|
|
{
|
|
if (parsed == null || parsed.notes == null) return;
|
|
|
|
const int trackCount = 5;
|
|
var counts = new int[trackCount];
|
|
for (int i = 0; i < parsed.notes.Length; i++)
|
|
{
|
|
var n = parsed.notes[i];
|
|
int idx = n != null ? n.trackIndex : -1;
|
|
if (idx < 0 || idx >= trackCount) continue;
|
|
counts[idx]++;
|
|
}
|
|
|
|
int effectiveNoteCount = GetEffectiveNoteCount(parsed);
|
|
int per = perNoteScore;
|
|
if (per <= 0 && effectiveNoteCount > 0)
|
|
per = totalChartScore / effectiveNoteCount;
|
|
|
|
var ui = teamUIController.Instance;
|
|
for (int i = 0; i < trackCount; i++)
|
|
{
|
|
int maxScore = Mathf.Max(0, per * counts[i]);
|
|
var ally = ResolveAllyCombatant(i, ui);
|
|
if (ally != null)
|
|
{
|
|
ally.SetMaxTrackScore(maxScore, true);
|
|
continue;
|
|
}
|
|
|
|
// Fallback: update UI text directly if combatant not found
|
|
string value = $"0/{maxScore}";
|
|
if (ui != null)
|
|
{
|
|
switch (i)
|
|
{
|
|
case 0: if (ui.teammate01_current_scoreText != null) ui.teammate01_current_scoreText.text = value; break;
|
|
case 1: if (ui.teammate02_current_scoreText != null) ui.teammate02_current_scoreText.text = value; break;
|
|
case 2: if (ui.teammate03_current_scoreText != null) ui.teammate03_current_scoreText.text = value; break;
|
|
case 3: if (ui.teammate04_current_scoreText != null) ui.teammate04_current_scoreText.text = value; break;
|
|
case 4: if (ui.teammate05_current_scoreText != null) ui.teammate05_current_scoreText.text = value; break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private AllyCombatant ResolveAllyCombatant(int slotIndex, teamUIController ui)
|
|
{
|
|
GameObject go = null;
|
|
if (ui != null)
|
|
go = ui.GetAllyObjectBySlot(slotIndex);
|
|
if (go == null)
|
|
{
|
|
var byName = GameObject.Find($"ally_0{slotIndex + 1}");
|
|
if (byName != null) go = byName;
|
|
}
|
|
if (go == null) return null;
|
|
var ally = go.GetComponent<AllyCombatant>();
|
|
if (ally != null) return ally;
|
|
return go.GetComponentInChildren<AllyCombatant>(true);
|
|
}
|
|
|
|
[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; }
|
|
}
|