PageRenderTime 682ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Assets/Scripts/BMS/NoteDetector.cs

https://gitlab.com/banana-beats/banana-beats
C# | 245 lines | 227 code | 18 blank | 0 comment | 61 complexity | 760ee8f1716de1d0be533d41696ff552 MD5 | raw file
  1. using UnityEngine;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. namespace BMS {
  7. public class NoteDetector : MonoBehaviour {
  8. enum NoteType {
  9. Normal,
  10. LongStart,
  11. LongEnd
  12. }
  13. struct KeyFrame {
  14. public TimeSpan timePosition;
  15. public int channelId;
  16. public int dataId;
  17. public NoteType type;
  18. public int longNoteId;
  19. public TimeSpan lnEndPosition;
  20. public TimeSpan sliceStart;
  21. public TimeSpan sliceEnd;
  22. }
  23. struct QueuedLongNoteState {
  24. public bool isNoteDown;
  25. public int longNoteId;
  26. }
  27. struct LongNoteState {
  28. public bool isDown;
  29. public bool isMissed;
  30. public int longNoteId;
  31. public int longNoteDataId;
  32. }
  33. [SerializeField]
  34. BMSManager bmsManager;
  35. [SerializeField]
  36. float startTimeOffsetSeconds;
  37. [SerializeField]
  38. float endTimeOffsetSeconds;
  39. readonly Dictionary<int, Queue<KeyFrame>> queuedFrames = new Dictionary<int, Queue<KeyFrame>>();
  40. readonly Dictionary<int, QueuedLongNoteState> queuedLongNoteState = new Dictionary<int, QueuedLongNoteState>();
  41. readonly Dictionary<int, LongNoteState> longNoteStates = new Dictionary<int, LongNoteState>();
  42. readonly Dictionary<int, bool> longNoteAutoFlag = new Dictionary<int, bool>();
  43. Chart.EventDispatcher preQueueMapper, postQueueMapper;
  44. IList<BMSEvent> bmsEvents;
  45. TimeSpan startTimeOffset, endTimeOffset;
  46. public TimeSpan EndTimeOffset {
  47. get { return endTimeOffset; }
  48. }
  49. public event Action<TimeSpan, int, int, int> OnNoteClicked;
  50. public event Action<int> OnLongNoteMissed;
  51. public bool autoMode;
  52. void Awake() {
  53. startTimeOffset = TimeSpan.FromSeconds(startTimeOffsetSeconds);
  54. endTimeOffset = TimeSpan.FromSeconds(-endTimeOffsetSeconds);
  55. bmsManager.OnBMSLoaded += OnBMSLoaded;
  56. bmsManager.OnGameEnded += OnEnded;
  57. bmsManager.OnNoteEvent += NoteEvent;
  58. }
  59. void OnDestroy() {
  60. if(bmsManager != null) {
  61. bmsManager.OnBMSLoaded -= OnBMSLoaded;
  62. bmsManager.OnGameEnded -= OnEnded;
  63. bmsManager.OnNoteEvent -= NoteEvent;
  64. }
  65. }
  66. void Update() {
  67. if(bmsManager.IsStarted && !bmsManager.IsPaused) {
  68. TimeSpan timePosition = bmsManager.TimePosition;
  69. preQueueMapper.Seek(timePosition + startTimeOffset);
  70. postQueueMapper.Seek(timePosition + endTimeOffset);
  71. if(Input.GetAxis("Cancel") > 0)
  72. bmsManager.IsPaused = true;
  73. }
  74. }
  75. void OnApplicationFocus(bool state) {
  76. if(!autoMode && bmsManager != null && bmsManager.IsStarted)
  77. bmsManager.IsPaused = true;
  78. }
  79. void OnApplicationPause(bool state) {
  80. if(bmsManager != null && bmsManager.IsStarted)
  81. bmsManager.IsPaused = true;
  82. }
  83. void OnBMSLoaded() {
  84. bmsEvents = bmsManager.LoadedChart.Events;
  85. if(preQueueMapper != null)
  86. preQueueMapper.BMSEvent -= OnHasKeyFrame;
  87. preQueueMapper = bmsManager.LoadedChart.GetEventDispatcher();
  88. preQueueMapper.BMSEvent += OnHasKeyFrame;
  89. preQueueMapper.Seek(TimeSpan.MinValue, false);
  90. if(postQueueMapper != null)
  91. postQueueMapper.BMSEvent -= OnPostMap;
  92. postQueueMapper = bmsManager.LoadedChart.GetEventDispatcher();
  93. postQueueMapper.BMSEvent += OnPostMap;
  94. postQueueMapper.Seek(TimeSpan.MinValue, false);
  95. foreach(var channel in bmsManager.GetAllChannelIds()) {
  96. if(!bmsManager.GetAllAdoptedChannels().Contains(channel)) continue;
  97. GetChannelQueue(channel).Clear();
  98. }
  99. }
  100. void OnEnded() {
  101. foreach(var queue in queuedFrames.Values)
  102. queue.Clear();
  103. longNoteStates.Clear();
  104. queuedLongNoteState.Clear();
  105. longNoteAutoFlag.Clear();
  106. preQueueMapper.Seek(TimeSpan.MinValue, false);
  107. postQueueMapper.Seek(TimeSpan.MinValue, false);
  108. }
  109. void NoteEvent(BMSEvent bmsEvent) {
  110. if(autoMode) {
  111. if(!bmsManager.GetAllAdoptedChannels().Contains(bmsEvent.data1))
  112. return;
  113. HandleNoteEvent(bmsEvent.data1, true, bmsEvent.type == BMSEventType.LongNoteStart || bmsEvent.type == BMSEventType.Note);
  114. }
  115. }
  116. void OnHasKeyFrame(BMSEvent bmsEvent) {
  117. if(!bmsEvent.IsNote || !bmsManager.GetAllAdoptedChannels().Contains(bmsEvent.data1))
  118. return;
  119. bool isLongNote = bmsEvent.type == BMSEventType.LongNoteEnd || bmsEvent.type == BMSEventType.LongNoteStart, lnDown = false;
  120. TimeSpan lnEndpos = TimeSpan.Zero;
  121. QueuedLongNoteState queuedLNState = new QueuedLongNoteState();
  122. if(isLongNote) {
  123. queuedLongNoteState.TryGetValue(bmsEvent.data1, out queuedLNState);
  124. lnDown = queuedLNState.isNoteDown;
  125. queuedLNState.isNoteDown = !lnDown;
  126. if(!lnDown) {
  127. queuedLNState.longNoteId++;
  128. lnEndpos = bmsEvent.time2;
  129. }
  130. queuedLongNoteState[bmsEvent.data1] = queuedLNState;
  131. }
  132. GetChannelQueue(bmsEvent.data1).Enqueue(new KeyFrame {
  133. timePosition = bmsEvent.time,
  134. channelId = bmsEvent.data1,
  135. dataId = (int)bmsEvent.data2,
  136. type = isLongNote ? (lnDown ? NoteType.LongEnd : NoteType.LongStart) : NoteType.Normal,
  137. longNoteId = queuedLNState.longNoteId,
  138. lnEndPosition = lnEndpos,
  139. sliceStart = bmsEvent.sliceStart,
  140. sliceEnd = bmsEvent.sliceEnd
  141. });
  142. }
  143. void OnPostMap(BMSEvent bmsEvent) {
  144. if(!bmsEvent.IsNote || !bmsManager.GetAllAdoptedChannels().Contains(bmsEvent.data1))
  145. return;
  146. HandleNoteEvent(bmsEvent.data1, false, false);
  147. }
  148. Queue<KeyFrame> GetChannelQueue(int channelId) {
  149. Queue<KeyFrame> result;
  150. if(!queuedFrames.TryGetValue(channelId, out result))
  151. queuedFrames[channelId] = result = new Queue<KeyFrame>();
  152. return result;
  153. }
  154. LongNoteState GetLongNoteState(int channelId) {
  155. LongNoteState result;
  156. if(!longNoteStates.TryGetValue(channelId, out result))
  157. longNoteStates[channelId] = result = new LongNoteState();
  158. return result;
  159. }
  160. public void OnClick(int channel, bool isDown) {
  161. if(autoMode || !bmsManager.GetAllAdoptedChannels().Contains(channel)) return;
  162. if(channel >= 50) channel -= 40;
  163. HandleNoteEvent(channel, true, isDown);
  164. }
  165. void HandleNoteEvent(int channel, bool isClicking, bool isDown) {
  166. var ch = GetChannelQueue(channel);
  167. var lns = GetLongNoteState(channel);
  168. TimeSpan offsetPosition = bmsManager.RealTimePosition + endTimeOffset;
  169. KeyFrame keyFrame;
  170. int flag = -1;
  171. while(ch.Count > 0) {
  172. keyFrame = ch.Peek();
  173. if(keyFrame.timePosition > offsetPosition) break;
  174. ch.Dequeue();
  175. if(keyFrame.type != NoteType.LongEnd || !lns.isMissed)
  176. flag = bmsManager.NoteClicked(keyFrame.timePosition, channel, keyFrame.dataId, true, keyFrame.sliceStart, keyFrame.sliceEnd);
  177. else
  178. flag = -1;
  179. if(OnNoteClicked != null)
  180. OnNoteClicked.Invoke(keyFrame.timePosition, channel, keyFrame.dataId, flag);
  181. }
  182. if(isClicking) {
  183. if(ch.Count > 0) {
  184. keyFrame = ch.Peek();
  185. bool handle = true, skip = false, hasSound = true;
  186. TimeSpan? endNote = null;
  187. switch(keyFrame.type) {
  188. case NoteType.Normal:
  189. if(!isDown) handle = false;
  190. lns.longNoteId = -1;
  191. break;
  192. case NoteType.LongStart:
  193. if(!isDown) handle = false;
  194. lns.longNoteId = keyFrame.longNoteId;
  195. lns.longNoteDataId = keyFrame.dataId;
  196. endNote = keyFrame.lnEndPosition;
  197. break;
  198. case NoteType.LongEnd:
  199. if(isDown) handle = false;
  200. if(lns.longNoteId != keyFrame.longNoteId) skip = true;
  201. lns.longNoteId = -1;
  202. hasSound = lns.longNoteDataId != keyFrame.dataId;
  203. break;
  204. }
  205. lns.isDown = keyFrame.type == NoteType.Normal ? false : isDown;
  206. longNoteStates[channel] = lns;
  207. if(handle || skip) ch.Dequeue();
  208. if(handle) {
  209. flag = bmsManager.NoteClicked(keyFrame.timePosition, channel, keyFrame.dataId, false, keyFrame.sliceStart, keyFrame.sliceEnd, hasSound, endNote);
  210. if(OnNoteClicked != null)
  211. OnNoteClicked.Invoke(keyFrame.timePosition, channel, keyFrame.dataId, flag);
  212. lns.isMissed = flag < 0;
  213. if(flag < 0) lns.longNoteId = -1;
  214. longNoteStates[channel] = lns;
  215. }
  216. } else if(lns.isDown) {
  217. lns.isDown = false;
  218. lns.isMissed = true;
  219. bmsManager.NoteClicked(TimeSpan.Zero, channel, 0, true, TimeSpan.Zero, TimeSpan.MaxValue, false, null);
  220. if(OnLongNoteMissed != null)
  221. OnLongNoteMissed.Invoke(channel);
  222. longNoteStates[channel] = lns;
  223. }
  224. }
  225. }
  226. }
  227. }