From 768ae85c271fab85b0c677d4c3c8b8628adc362f Mon Sep 17 00:00:00 2001
From: Narrik Synthfox <80410683+NarrikSynthfox@users.noreply.github.com>
Date: Fri, 21 Apr 2023 15:50:39 -0400
Subject: [PATCH] Taps (#144)

* Taps work kinda

* tap logic, ignore force strum/hopo if sysex tap

* Merge branch 'EliteAsian123:master' into taps

* Start implementing tap model

* Fully implement tap note model.
---
 Assets/Prefabs/Note.prefab                    | 11 ++++-
 Assets/Script/Data/NoteInfo.cs                |  5 ++-
 .../DiffDownsample/FiveFretDownsample.cs      |  3 ++
 Assets/Script/PlayMode/FiveFretTrack.cs       | 19 ++++----
 Assets/Script/Pools/NoteComponent.cs          |  8 ++--
 .../Parser/MidiParser.FiveFret.cs             | 45 ++++++++++++++-----
 6 files changed, 65 insertions(+), 26 deletions(-)

diff --git a/Assets/Prefabs/Note.prefab b/Assets/Prefabs/Note.prefab
index 7b86319b1..5877e36e0 100644
--- a/Assets/Prefabs/Note.prefab
+++ b/Assets/Prefabs/Note.prefab
@@ -54,8 +54,9 @@ MonoBehaviour:
   meshRenderers:
   - {fileID: 4571971630462908758}
   - {fileID: 6846950644507852135}
+  - {fileID: 6376229487116774476}
   - {fileID: 7147253800328892818}
-  meshRendererMiddleIndices: 020000000100000001000000
+  meshRendererMiddleIndices: 02000000010000000200000001000000
   noteGroup: {fileID: 1397812567785855241}
   hopoGroup: {fileID: 1294466957868725440}
   tapGroup: {fileID: 3624981743589644049}
@@ -141,7 +142,7 @@ GameObject:
   m_Icon: {fileID: 0}
   m_NavMeshLayer: 0
   m_StaticEditorFlags: 0
-  m_IsActive: 0
+  m_IsActive: 1
 --- !u!4 &2078363896503798061
 Transform:
   m_ObjectHideFlags: 0
@@ -588,6 +589,12 @@ Transform:
     type: 3}
   m_PrefabInstance: {fileID: 6392612060457181869}
   m_PrefabAsset: {fileID: 0}
+--- !u!23 &6376229487116774476 stripped
+MeshRenderer:
+  m_CorrespondingSourceObject: {fileID: 57407852433297121, guid: 0f0950362ca41164899b1391a673053d,
+    type: 3}
+  m_PrefabInstance: {fileID: 6392612060457181869}
+  m_PrefabAsset: {fileID: 0}
 --- !u!1001 &7866531513016969808
 PrefabInstance:
   m_ObjectHideFlags: 0
diff --git a/Assets/Script/Data/NoteInfo.cs b/Assets/Script/Data/NoteInfo.cs
index 00ab78108..022af9e84 100644
--- a/Assets/Script/Data/NoteInfo.cs
+++ b/Assets/Script/Data/NoteInfo.cs
@@ -8,7 +8,10 @@ public class NoteInfo : AbstractInfo {
 		/// Hammer-on/pull-off, or Cymbal for drums.
 		/// </summary>
 		public bool hopo;
-
+		/// <summary>
+		/// Tap note. Only used for guitar, coop guitar, rhythm, and bass.
+		/// </summary>
+		public bool tap;
 		/// <summary>
 		/// Whether or not this HOPO is automatic.<br/>
 		/// Used for difficulty downsampling.
diff --git a/Assets/Script/DiffDownsample/FiveFretDownsample.cs b/Assets/Script/DiffDownsample/FiveFretDownsample.cs
index 2b4350f41..a0d4f1118 100644
--- a/Assets/Script/DiffDownsample/FiveFretDownsample.cs
+++ b/Assets/Script/DiffDownsample/FiveFretDownsample.cs
@@ -129,6 +129,7 @@ private class ChordedNoteInfo {
 
 			public FretFlag frets;
 			public bool hopo;
+			public bool tap;
 			public bool autoHopo;
 		}
 
@@ -155,6 +156,7 @@ private static List<ChordedNoteInfo> ConsolidateToChords(List<NoteInfo> input) {
 						length = new float[5],
 						frets = (FretFlag) (1 << note.fret),
 						hopo = note.hopo,
+						tap = note.tap,
 						autoHopo = note.autoHopo
 					};
 
@@ -187,6 +189,7 @@ private static List<NoteInfo> SplitToNotes(List<ChordedNoteInfo> chords) {
 						length = chord.length[i],
 						fret = i,
 						hopo = chord.hopo,
+						tap = chord.tap,
 						autoHopo = chord.autoHopo
 					});
 				}
diff --git a/Assets/Script/PlayMode/FiveFretTrack.cs b/Assets/Script/PlayMode/FiveFretTrack.cs
index 3daea2cc7..1f0cc7aa6 100644
--- a/Assets/Script/PlayMode/FiveFretTrack.cs
+++ b/Assets/Script/PlayMode/FiveFretTrack.cs
@@ -215,14 +215,15 @@ private void UpdateInput() {
 			// Handle hits (one per frame so no double hits)
 			var chord = expectedHits.Peek();
 
-			// If the note is not a HOPO and the player has not strummed, nothing happens.
-			if (!chord[0].hopo && !strummed && strumLeniency == 0f) {
+			// If the note is not a HOPO or tap and the player has not strummed, nothing happens.
+			if (!chord[0].hopo && !chord[0].tap && !strummed && strumLeniency == 0f) {
 				return;
 			}
 
+
 			// If the note is a HOPO, the player has not strummed, and the HOPO can't be hit, nothing happens.
-			if (chord[0].hopo && !strummed && strumLeniency == 0f) {
-				if (Combo <= 0) {
+			if ((chord[0].hopo || chord[0].tap) && !strummed && strumLeniency == 0f) {
+				if (Combo <= 0 && chord[0].hopo) {
 					return;
 				}
 
@@ -277,7 +278,7 @@ private void UpdateInput() {
 			}
 
 			// Avoid multi-hits
-			if (chord[0].hopo) {
+			if (chord[0].hopo || chord[0].tap) {
 				// If latest input is cleared, it was already used
 				if (latestInput == null) {
 					return;
@@ -342,10 +343,10 @@ private void UpdateInput() {
 			// add it to the allowed overstrums. This is so the player
 			// doesn't lose their combo when they strum AFTER they hit
 			// the tap note.
-			if (chord[0].hopo && !strummedCurrentNote) {
+			if ((chord[0].hopo||chord[0].tap) && !strummedCurrentNote) {
 				allowedOverstrums.Clear(); // Only allow overstrumming latest HO/PO
 				allowedOverstrums.Add(chord);
-			} else if (allowedOverstrums.Count > 0 && !chord[0].hopo) {
+			} else if (allowedOverstrums.Count > 0 && !chord[0].hopo && !chord[0].tap) {
 				for (int i = 0; i < allowedOverstrums.Count; i++) {
 					if (!ChordEquals(chord, allowedOverstrums[i])) {
 						allowedOverstrums.Clear(); // If latest strum is different from latest HO/PO, disallow overstrumming
@@ -565,13 +566,14 @@ private void SpawnNote(NoteInfo noteInfo, float time) {
 			float lagCompensation = CalcLagCompensation(time, noteInfo.time);
 			float x = noteInfo.fret == 5 ? 0f : frets[noteInfo.fret].transform.localPosition.x;
 			var pos = new Vector3(x, 0f, TRACK_SPAWN_OFFSET - lagCompensation);
-
 			// Get model type
 			var model = NoteComponent.ModelType.NOTE;
 			if (noteInfo.fret == 5) {
 				model = NoteComponent.ModelType.FULL;
 			} else if (noteInfo.hopo) {
 				model = NoteComponent.ModelType.HOPO;
+			} else if (noteInfo.tap){
+				model = NoteComponent.ModelType.TAP;
 			}
 
 
@@ -584,6 +586,7 @@ private void SpawnNote(NoteInfo noteInfo, float time) {
 				noteInfo.length,
 				model
 			);
+			
 		}
 
 		private string PrintFrets() { // Debug function; remove later?
diff --git a/Assets/Script/Pools/NoteComponent.cs b/Assets/Script/Pools/NoteComponent.cs
index dc88ad140..3be4009da 100644
--- a/Assets/Script/Pools/NoteComponent.cs
+++ b/Assets/Script/Pools/NoteComponent.cs
@@ -7,6 +7,7 @@ public class NoteComponent : Poolable {
 		public enum ModelType {
 			NOTE,
 			HOPO,
+			TAP,
 			FULL
 		}
 
@@ -81,8 +82,8 @@ private void OnDisable() {
 		public void SetInfo(Color notes, Color sustains, float length, ModelType hopo) {
 			noteGroup.SetActive(hopo == ModelType.NOTE);
 			hopoGroup.SetActive(hopo == ModelType.HOPO);
+			tapGroup.SetActive( hopo == ModelType.TAP);
 			fullGroup.SetActive(hopo == ModelType.FULL);
-
 			state = State.WAITING;
 
 			SetLength(length);
@@ -160,7 +161,8 @@ public void HitNote() {
 			noteGroup.SetActive(false);
 			hopoGroup.SetActive(false);
 			fullGroup.SetActive(false);
-
+			tapGroup.SetActive(false);
+			
 			if (fretNumber != null) {
 				fretNumber.gameObject.SetActive(false);
 			}
@@ -173,7 +175,7 @@ public void MissNote() {
 			if (fretNumber != null) {
 				fretNumber.gameObject.SetActive(false);
 			}
-
+			
 			state = State.MISSED;
 			UpdateLineColor();
 		}
diff --git a/Assets/Script/Serialization/Parser/MidiParser.FiveFret.cs b/Assets/Script/Serialization/Parser/MidiParser.FiveFret.cs
index 933483f57..c60baab7d 100644
--- a/Assets/Script/Serialization/Parser/MidiParser.FiveFret.cs
+++ b/Assets/Script/Serialization/Parser/MidiParser.FiveFret.cs
@@ -20,9 +20,10 @@ private enum ForceState {
 			NONE,
 			HOPO,
 			STRUM,
-			OPEN
+			OPEN,
+			TAP
 		}
-
+		private bool isCurrentlyTap=false;
 		private class FiveFretIR {
 			public long startTick;
 			// This is an array due to extended sustains
@@ -30,7 +31,7 @@ private class FiveFretIR {
 
 			public FretFlag fretFlag;
 			public bool hopo;
-
+			public bool tap;
 			// Used for difficulty downsampling
 			public bool autoHopo;
 		}
@@ -63,7 +64,7 @@ private List<ForceStateIR> FiveFretGetForceState(TrackChunk trackChunk, int diff
 			// we must store the ON events and wait until the
 			// OFF event to actually add the state. This stores
 			// the ON event timings.
-			long?[] forceStateArray = new long?[4];
+			long?[] forceStateArray = new long?[5];
 
 			// Convert track events into intermediate representation
 			foreach (var trackEvent in trackChunk.Events) {
@@ -87,7 +88,7 @@ private List<ForceStateIR> FiveFretGetForceState(TrackChunk trackChunk, int diff
 						forceState = ForceState.OPEN;
 					} else if (header.SequenceEqual(SYSEX_TAP_NOTE)) {
 						i = 3;
-						forceState = ForceState.HOPO;
+						forceState = ForceState.TAP;
 					} else {
 						continue;
 					}
@@ -96,11 +97,16 @@ private List<ForceStateIR> FiveFretGetForceState(TrackChunk trackChunk, int diff
 						// If it is a flag on, wait until we get the flag
 						// off so we can get the length of the flag period.
 						forceStateArray[i] = totalDelta;
+						if(forceState==ForceState.TAP){
+							isCurrentlyTap=true;
+						}
 					} else {
 						if (forceStateArray[i] == null) {
 							continue;
 						}
-
+						if(forceState==ForceState.TAP){
+							isCurrentlyTap=false;
+						}
 						forceIR.Add(new ForceStateIR {
 							startTick = forceStateArray[i].Value,
 							endTick = totalDelta,
@@ -109,16 +115,19 @@ private List<ForceStateIR> FiveFretGetForceState(TrackChunk trackChunk, int diff
 
 						forceStateArray[i] = null;
 					}
-				} else if (trackEvent is NoteEvent noteEvent) {
+				} else if (trackEvent is NoteEvent noteEvent && !isCurrentlyTap) {
 					// Note based flags
 
 					// Look for correct octave
 					if (noteEvent.GetNoteOctave() != 4 + difficulty) {
 						continue;
 					}
-
+					ForceState forceState = ForceState.NONE;
+					if(noteEvent.GetNoteOctave() == 7 && noteEvent.GetNoteName()==NoteName.GSharp){
+						forceState = ForceState.TAP;
+					}else{
 					// Convert note to force state
-					ForceState forceState = noteEvent.GetNoteName() switch {
+						forceState = noteEvent.GetNoteName() switch {
 						// Force HOPO
 						NoteName.F => ForceState.HOPO,
 						// Force strum
@@ -126,6 +135,9 @@ private List<ForceStateIR> FiveFretGetForceState(TrackChunk trackChunk, int diff
 						// Default
 						_ => ForceState.NONE
 					};
+					}
+
+					
 
 					// Skip if not an actual state
 					if (forceState == ForceState.NONE) {
@@ -197,7 +209,7 @@ private List<FiveFretIR> FiveFretNotePass(TrackChunk trackChunk, int difficulty)
 					// Default
 					_ => -1
 				};
-
+				
 				// Skip if not an actual note
 				if (fret == -1) {
 					continue;
@@ -288,11 +300,15 @@ private void FiveFretNoteStatePass(List<FiveFretIR> noteIR, List<ForceStateIR> f
 					// Set as open if requested
 					note.fretFlag = FretFlag.OPEN;
 					note.hopo = false;
-				} else {
+				} else{
 					// Otherwise, just set as a HOPO if requested
 					note.hopo = force == ForceState.HOPO;
 				}
-
+				if (force == ForceState.TAP) {
+					note.tap=true;
+					note.hopo=false;
+					note.autoHopo=false;
+				}
 				lastTime = note.startTick;
 				lastFret = note.fretFlag;
 			}
@@ -319,6 +335,10 @@ private List<NoteInfo> FiveFretIrToRealPass(List<FiveFretIR> noteIR, TempoMap te
 
 					int fret = i - 1;
 
+					if(noteInfo.tap){
+						noteInfo.hopo=false;
+						noteInfo.autoHopo=false;
+					}
 					// Get the end tick (different for open notes)
 					long endTick;
 					if (fret == 5) {
@@ -336,6 +356,7 @@ private List<NoteInfo> FiveFretIrToRealPass(List<FiveFretIR> noteIR, TempoMap te
 						time = startTime,
 						length = endTime - startTime,
 						fret = fret,
+						tap = noteInfo.tap,
 						hopo = noteInfo.hopo,
 						autoHopo = noteInfo.autoHopo
 					});