using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dreamteck.Splines;
using Sirenix.OdinInspector;
using UniRx;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni.RhythmGame
{
public abstract partial class NoteBase : GameElement, IHaveTimeDurationSubmodule
{
[Title("Basic Info")]
public float exactJudgeTime;
public NoteJudgeIntervals judgeIntervals;
[Title("Track Info")]
public bool isOnTrack;
public Track track;
public SplinePositioner trackPositioner;
[Title("Audio")]
public AudioContainer audioContainer;
[Title("NoteVisual")]
public NoteVisualBase noteVisual;
[Title("Submodules")]
public TimeDurationSubmodule timeDurationSubmodule { get; set; }
public NoteJudgeSubmodule noteJudgeSubmodule { get; set; }
public NoteAudioSubmodule noteAudioSubmodule { get; set; }
[Title("In-Game Info")]
public Vector2 noteScreenPosition;
public bool isFirstJudged;
public bool isFinalJudged;
public override int HierarchyPriority => -10;
///
/// 在MovableTrack上更新Note的位置,注意HoldNote需要重写这个方法
///
public virtual void UpdateNoteInMovableTrack()
{
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
trackPositioner.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(exactJudgeTime));
}
///
/// 在StaticTrack上更新Note的位置,注意HoldNote需要重写这个方法
///
public virtual void UpdateNoteInStaticTrack()
{
float songTime = GameManager.instance.songTime;
TrackTimeSubmoduleStatic trackTimeSubmoduleStatic = track.trackTimeSubmodule as TrackTimeSubmoduleStatic;
float startMove = exactJudgeTime - trackTimeSubmoduleStatic.trackTotalTime;
float percent = AnimationCurveEvaluator.Evaluate(trackTimeSubmoduleStatic.animationCurveType, (songTime - startMove) / trackTimeSubmoduleStatic.trackTotalTime);
percent = Mathf.Max(percent, 0);
percent = Mathf.Min(percent, 1);
trackPositioner.SetPercent(1 - percent);
}
public override void SetDefaultSubmodules()
{
timeDurationSubmodule = new TimeDurationSubmodule(this);
noteJudgeSubmodule = new NoteJudgeSubmodule(this);
}
public override void AfterInitialize()
{
noteVisual.effectSubmodule.effectCollection["Generate"].ForEach(e => e.Recover());
}
protected virtual void Update()
{
if (!GameManager.instance.audioManager.isUpdating)
{
return;
}
if (isOnTrack)
{
if (track.trackTimeSubmodule is TrackTimeSubmoduleStatic)
{
UpdateNoteInStaticTrack();
}
}
if (noteJudgeSubmodule != null)
{
if (GameManager.instance.songTime > exactJudgeTime - 0.25f && !isFirstJudged)
{
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => !unit.isShowingJudge))
{
unit.SetShowingJudge(true);
}
}
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.UpdateJudge();
}
if (GameManager.instance.songTime > exactJudgeTime + 0.25f)
{
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.SetShowingJudge(false);
}
}
}
noteScreenPosition = GameManager.instance.cameraManager.gameCamera.gameCamera
.WorldToScreenPoint(noteVisual.transform.position);
if (noteVisual != null)
{
noteVisual.effectSubmodule.effectCollection["Generate"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}
if (!isFirstJudged && GameManager.instance.songTime > exactJudgeTime + 0.25f)
{
Debug.Log("Note judged too late, marking as Miss.");
Miss(exactJudgeTime + 0.25f);
isFirstJudged = true;
isFinalJudged = true;
if (this is Tap t)
{
GameManager.instance.inputManager.checkingTapList.Remove(t);
}
}
}
public virtual NoteJudgeType GetStartJudgeType(float timeDifference)
{
return judgeIntervals.GetNoteJudgeType(timeDifference);
}
public virtual void ExecuteStartJudge()
{
Debug.Log("ExecuteStartJudge");
float triggerTime = GameManager.instance.songTime;
float timeDifference = triggerTime - exactJudgeTime;
NoteJudgeType startJudgeType = GetStartJudgeType(timeDifference);
if (startJudgeType == NoteJudgeType.Perfect)
{
Perfect(triggerTime);
}
else if (startJudgeType == NoteJudgeType.Good)
{
Good(triggerTime);
}
else if (startJudgeType == NoteJudgeType.Bad)
{
Bad(triggerTime);
}
else if (startJudgeType == NoteJudgeType.Miss)
{
Miss(triggerTime);
}
if (startJudgeType != NoteJudgeType.Miss)
{
noteAudioSubmodule.PlayGeneralJudgeAudios();
}
isFirstJudged = true;
}
public void UpdateNoteInTrack()
{
if (isOnTrack && track.trackTimeSubmodule != null)
{
if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable)
{
UpdateNoteInMovableTrack();
}
else if (track.trackTimeSubmodule is TrackTimeSubmoduleStatic)
{
UpdateNoteInStaticTrack();
}
}
}
public virtual void Perfect(float triggerTime)
{
GameManager.instance.playingRecorder.AddPerfect();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Perfect);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["Perfect"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
}
public virtual void Good(float triggerTime)
{
GameManager.instance.playingRecorder.AddGood();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Good);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["Good"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
}
public virtual void Bad(float triggerTime){
{
GameManager.instance.playingRecorder.AddBad();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Bad);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["Bad"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
}}
public virtual void Miss(float triggerTime)
{
GameManager.instance.playingRecorder.AddMiss();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Miss);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["Miss"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
}
}
public abstract partial class NoteBase
{
public enum NoteJudgeType
{
Perfect,
Good,
Bad,
Miss,
NotJudged
}
public class NoteJudgeIntervals
{
public TimeInterval beforeMiss;
public TimeInterval beforeBad;
public TimeInterval beforeGood;
public TimeInterval perfect;
public TimeInterval afterGood;
public TimeInterval afterBad;
public float afterMiss;
public NoteJudgeIntervals(TimeInterval beforeMiss, TimeInterval beforeBad, TimeInterval beforeGood,
TimeInterval perfect, TimeInterval afterGood, TimeInterval afterBad, float afterMiss)
{
this.beforeMiss = beforeMiss;
this.beforeBad = beforeBad;
this.beforeGood = beforeGood;
this.perfect = perfect;
this.afterGood = afterGood;
this.afterBad = afterBad;
this.afterMiss = afterMiss;
}
public NoteJudgeType GetNoteJudgeType(float timeDifference)
{
if (beforeMiss.IsInInterval(timeDifference))
{
return NoteJudgeType.Miss;
}
else if (beforeBad.IsInInterval(timeDifference))
{
return NoteJudgeType.Bad;
}
else if (beforeGood.IsInInterval(timeDifference))
{
return NoteJudgeType.Good;
}
else if (perfect.IsInInterval(timeDifference))
{
return NoteJudgeType.Perfect;
}
else if (afterGood.IsInInterval(timeDifference))
{
return NoteJudgeType.Good;
}
else if (afterBad.IsInInterval(timeDifference))
{
return NoteJudgeType.Bad;
}
return NoteJudgeType.Miss; // Default to Miss if no other condition is met
}
}
public class TimeInterval
{
public float intervalStart;
public float intervalEnd;
public TimeInterval(float start, float end)
{
intervalStart = start;
intervalEnd = end;
}
public bool IsInInterval(float time)
{
if (Mathf.Approximately(intervalStart, intervalEnd)) return false;
return time >= intervalStart && time <= intervalEnd;
}
}
public static string GetNoteTypeName(NoteBase note)
{
return note switch
{
Tap => "Tap",
Stay => "Stay",
Hold => "Hold",
Flick => "Flick",
_ => throw new NotImplementedException("Note type not recognized")
};
}
}
namespace Beatmap
{
public abstract class NoteBase_BM : GameElement_BM
{
public float exactJudgeTime;
public NoteBase_BM()
{
}
public NoteBase_BM(string elementName, Guid elementGuid, List tags, GameElement_BM attachedElement, float exactJudgeTime)
: base(elementName, elementGuid, tags, attachedElement)
{
this.exactJudgeTime = exactJudgeTime;
}
public override void ExecuteBM()
{
throw new NotImplementedException();
}
public override GameElement DuplicateBM(GameElement parent)
{
throw new NotImplementedException();
}
}
}
}