Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/BeatmapManager.cs
T

934 lines
36 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using TMPro;
public class BeatmapManager : MonoBehaviour
{
public static BeatmapManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
// 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}");
UpdateGameplayUI();
}
public Beatmap beatmap; // Documentation text normalized.
// Documentation text normalized.
public NoteSpawner noteSpawner;
// Documentation text normalized.
public teamUIController uiController;
[Header("Enemy Ball UI")]
public load_enemyBall_light enemyBallLoader;
[Header("UI Display")]
public Text gameplay_songname;
public TextMeshProUGUI gameplay_difficultyID;
public TextMeshProUGUI gameplay_difficultyName;
public Image bgMainImage; // 场景主背景图 (UI Image)
public Image startCanvas_image;
public SpriteRenderer bgSpriteRenderer; // 场景 3D 背景图 (SpriteRenderer)
// 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);
// Also setup enemies if loading this way
SetupEnemiesAndHP();
}
// 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 background images if available
if (assignedSongData != null && assignedSongData.fullscreen_songPicture != null)
{
Sprite bgSprite = assignedSongData.fullscreen_songPicture;
if (bgMainImage != null)
{
bgMainImage.sprite = bgSprite;
// ensure fully visible
bgMainImage.color = new Color(bgMainImage.color.r, bgMainImage.color.g, bgMainImage.color.b, 1f);
Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to bgMainImage for song {assignedSongData.songName}");
}
if (bgSpriteRenderer != null)
{
bgSpriteRenderer.sprite = bgSprite;
Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to bgSpriteRenderer for song {assignedSongData.songName}");
// 释放引用以节省内存(按要求:完毕后令 sprite renderer = null
bgSpriteRenderer = null;
}
if (startCanvas_image != null)
{
startCanvas_image.sprite = bgSprite;
Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to startCanvas_image for song {assignedSongData.songName}");
}
if (bgMainImage == null && startCanvas_image == null)
{
Debug.LogWarning("No background Image targets assigned in BeatmapManager; 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.");
}
UpdateGameplayUI();
}
private void UpdateGameplayUI()
{
if (assignedSongData == null) return;
if (gameplay_songname != null)
{
gameplay_songname.text = assignedSongData.songName;
}
if (gameplay_difficultyID != null)
{
// Find the ChartFileEntry matching the assignedDifficulty to get difficultyLEVEL (star rating)
float diffLevel = 0;
if (assignedSongData.chartFiles != null)
{
foreach (var entry in assignedSongData.chartFiles)
{
if (entry.difficulty == assignedDifficulty)
{
diffLevel = entry.difficultyLEVEL;
break;
}
}
}
gameplay_difficultyID.text = diffLevel.ToString();
}
if (gameplay_difficultyName != null)
{
string diffText = "Unknown";
switch (assignedDifficulty)
{
case 0: diffText = "Easy"; break;
case 1: diffText = "Hard"; break;
case 2: diffText = "Incredible"; break;
case 3: diffText = "Impossible"; break;
}
gameplay_difficultyName.text = diffText;
}
}
// 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>();
if (assignedSongData != null && assignedSongData.chartFiles != null)
{
foreach (var entry in assignedSongData.chartFiles)
{
if (entry.difficulty == assignedDifficulty)
{
if (entry.enemyConfigList != null && entry.enemyConfigList.Count > 0)
{
foreach (var config in entry.enemyConfigList)
{
enemyIds.Add(config.enemyID);
}
}
break;
}
}
}
while (enemyIds.Count < 5) enemyIds.Add(0);
if (enemyIds.Count > 5) enemyIds = enemyIds.GetRange(0, 5);
uiController.enemySlotIds = enemyIds;
uiController.PopulateEnemySOsFromIds();
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>();
List<float> activePercentages = new List<float>();
bool foundInDifficulty = false;
if (assignedSongData != null && assignedSongData.chartFiles != null)
{
foreach (var entry in assignedSongData.chartFiles)
{
if (entry.difficulty == assignedDifficulty)
{
if (entry.enemyConfigList != null && entry.enemyConfigList.Count > 0)
{
foreach (var config in entry.enemyConfigList)
{
enemyIds.Add(config.enemyID);
if (config.enemyID != 0) activePercentages.Add(config.hpPercentage);
}
foundInDifficulty = true;
Debug.Log($"[BeatmapManager] Using enemyConfigList from ChartFileEntry (difficulty {assignedDifficulty})");
}
break;
}
}
}
if (!foundInDifficulty && !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)}");
}
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();
if (assignedSongData != null && assignedSongData.chartFiles != null)
{
foreach (var entry in assignedSongData.chartFiles)
{
if (entry.difficulty == assignedDifficulty)
{
if (entry.enemyTotalHP_Multiplier > 0.01f)
{
currentMultiplier = entry.enemyTotalHP_Multiplier;
Debug.Log($"[BeatmapManager] Using override enemyTotalHP_Multiplier from SongData: {currentMultiplier}");
}
break;
}
}
}
float totalCalculatedHP = parsedNoteAmount * currentMultiplier * baseHpUnitScale;
int totalHP = Mathf.RoundToInt(totalCalculatedHP);
int activeEnemyCount = enemyIds.FindAll(id => id != 0).Count;
if (activeEnemyCount <= 0) activeEnemyCount = 1;
List<int> individualHPList = new List<int>();
float sumPercentage = 0f;
for (int i = 0; i < activePercentages.Count; i++) sumPercentage += activePercentages[i];
if (foundInDifficulty && activePercentages.Count == activeEnemyCount && sumPercentage > 0.1f)
{
float[] exact = new float[activeEnemyCount];
int[] baseHp = new int[activeEnemyCount];
float[] frac = new float[activeEnemyCount];
int allocated = 0;
for (int i = 0; i < activeEnemyCount; i++)
{
exact[i] = totalHP * (activePercentages[i] / sumPercentage);
baseHp[i] = Mathf.FloorToInt(exact[i]);
frac[i] = exact[i] - baseHp[i];
allocated += baseHp[i];
}
int remainder = totalHP - allocated;
while (remainder > 0)
{
int bestIdx = 0;
float bestFrac = -1f;
for (int i = 0; i < activeEnemyCount; i++)
{
if (frac[i] > bestFrac)
{
bestFrac = frac[i];
bestIdx = i;
}
}
baseHp[bestIdx] += 1;
frac[bestIdx] = -1f;
remainder--;
}
for (int i = 0; i < activeEnemyCount; i++) individualHPList.Add(baseHp[i]);
Debug.Log($"[BeatmapManager] HP Calc (Custom Percentages) -> Total: {totalHP}, HPs: {string.Join(",", individualHPList)}");
}
else
{
int basePer = totalHP / activeEnemyCount;
int remainder = totalHP - (basePer * activeEnemyCount);
for (int i = 0; i < activeEnemyCount; i++)
{
int hp = basePer + (i < remainder ? 1 : 0);
individualHPList.Add(hp);
}
Debug.Log($"[BeatmapManager] HP Calc (Equal Dist) -> Total: {totalHP}, Active: {activeEnemyCount}, HPs: {string.Join(",", individualHPList)}");
}
uiController.ApplyCalculatedEnemyHP(individualHPList);
}
// Initialize enemy ball list UI
if (enemyBallLoader != null)
{
List<EnemyData_SO.EnemyType> enemyTypes = new List<EnemyData_SO.EnemyType>();
// Use uiController's populated list if available, otherwise just use IDs to look them up if possible
// uiController.PopulateEnemySOsFromIds() fills uiController.recognizedEnemySOs
if (uiController.recognizedEnemySOs != null)
{
foreach (var so in uiController.recognizedEnemySOs)
{
if (so != null)
{
enemyTypes.Add(so.enemyType);
}
}
}
enemyBallLoader.InitializeEnemyBalls(enemyTypes);
}
}
// 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; }
}