public void UpdateSustainLength(QNT_Duration newLength) { sustainLength = newLength; if (endMarker != null) { endMarker.transform.position = new Vector3(endMarker.transform.position.x, endMarker.transform.position.y, gameObject.transform.position.z + sustainLength.ToBeatTime()); } }
public bool IsInValidTime(QNT_Timestamp time) { QNT_Duration loadedDuration = Constants.QuarterNoteDuration + Constants.EighthNoteDuration; if (location == TargetIconLocation.Grid && Math.Abs((time - target.data.time).tick) > (long)loadedDuration.tick) { return(false); } return(true); }
private void OnBeatLengthChanged(QNT_Duration newBeatLength) { if (!data.supportsBeatLength) { return; } if (data.behavior == TargetBehavior.NR_Pathbuilder) { ChainBuilder.GenerateChainNotes(data); } }
public TargetData(Cue cue) { ID = GetNextId(); Vector2 pos = NotePosCalc.PitchToPos(cue); x = pos.x; y = pos.y; time = new QNT_Timestamp((UInt64)cue.tick); beatLength = new QNT_Duration((UInt64)cue.tickLength); velocity = cue.velocity; handType = cue.handType; behavior = cue.behavior; }
public override void DoAction(Timeline timeline) { pathBuilderData.behavior = data.behavior; pathBuilderData.velocity = data.velocity; pathBuilderData.handType = data.handType; data.pathBuilderData = pathBuilderData; //Ensure the path builder always starts with a quarter note of build time oldBeatLength = data.beatLength; if (data.beatLength < Constants.QuarterNoteDuration) { data.beatLength = Constants.QuarterNoteDuration; } data.behavior = TargetBehavior.NR_Pathbuilder; ChainBuilder.ChainBuilder.GenerateChainNotes(data); }
public void UpdateTimelineSustainLength() { if (!data.supportsBeatLength) { return; } float scale = 20.0f / Timeline.scale; QNT_Duration beatLength = data.beatLength; var lineRenderers = gameObject.GetComponentsInChildren <LineRenderer>(true); foreach (LineRenderer l in lineRenderers) { if (l.positionCount < 3) { continue; } l.SetPosition(0, new Vector3(0.0f, 0.0f, 0.0f)); l.SetPosition(1, new Vector3(0.0f, sustainDirection, 0.0f)); l.SetPosition(2, new Vector3((beatLength.ToBeatTime() / 0.7f) * scale, sustainDirection, 0.0f)); } }
private QNT_Duration SetRhythmLimit(int difficulty) { QNT_Duration limit = Constants.SixteenthNoteDuration; switch (difficulty) { case 1: limit = Constants.SixteenthNoteDuration; break; case 2: limit = Constants.QuarterNoteDuration / 2; break; case 3: limit = Constants.QuarterNoteDuration; break; default: limit = Constants.SixteenthNoteDuration; break; } return(limit); }
private void OnSustainLengthChanged(QNT_Duration beatLength) { UpdateTimelineSustainLength(); }
private bool InsufficientBreakAfterPreviousTarget(TargetData prevTarget, Target curTarget, QNT_Duration leadTime) { return(curTarget.data.time.tick - prevTarget.time.tick < leadTime.tick); }
private bool InsufficientBreakAfterSustain(TargetData prevTarget, Target curTarget, QNT_Duration leadTime) { return(curTarget.data.time.tick - (prevTarget.time.tick + prevTarget.beatLength.tick) < leadTime.tick); }
private List <ErrorLogEntry> ParseCues(List <Target> targetCues, string output, int difficulty, string label) { //error log List <ErrorLogEntry> errorLog = new List <ErrorLogEntry>(); //references to previous targets to help with parsing TargetData prevTarget = new TargetData(); //dual purpose reference. This is the previous target regardless if it's RH or LH; also used in the RH/LH backtrack checks so I don't have to copy paste code. TargetData prevRHTarget = new TargetData(); TargetData prevLHTarget = new TargetData(); TargetData prevMeleeTarget = new TargetData(); TargetData lastLastTarget = new TargetData(); int consecutiveCounter = 0; //Check for a preview point: if (Timeline.audicaFile.desc.previewStartSeconds == 0) { errorLog.Add(new ErrorLogEntry(new QNT_Timestamp(0), "No preview start point has been added. Go to a point in the song and press P to set it.")); } //////////////////////// // main parsing block // //////////////////////// foreach (Target curTarget in targetCues) { /////////////////////// // standalone checks // /////////////////////// //cues without hitsounds if (!HasHitSound(curTarget)) { var error = new ErrorLogEntry(curTarget.data.time, "ERROR, target has an invalid hitsound."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } ////////////////////////////// // general backtrack checks // ////////////////////////////// // consecutive rhythm check // if lower difficulties and not chain node if (difficulty != 0 && (!prevTarget.behavior.Equals(TargetBehavior.Chain) && !curTarget.data.behavior.Equals(TargetBehavior.Chain))) { QNT_Duration rhythmLimit = SetRhythmLimit(difficulty); int countLimit = SetCountLimit(difficulty); QNT_Duration beatTimeDiff = new QNT_Duration(curTarget.data.time.tick - prevTarget.time.tick); if (beatTimeDiff.tick > rhythmLimit.tick) { //reset counter consecutiveCounter = 0; } else if (beatTimeDiff != 0 && beatTimeDiff < rhythmLimit) { //straight up too fast var error = new ErrorLogEntry(curTarget.data.time, "WARNING, for " + label + ", this target happens too soon after the previous target."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } else if (beatTimeDiff == rhythmLimit) { // consecutive 8th notes on one hand if (difficulty == 2 && prevTarget.handType.Equals(curTarget.data.handType)) { Debug.Log("consecutive 8t notes on one hand"); var error = new ErrorLogEntry(curTarget.data.time, "WARNING, in " + label + ", consecutive 8th notes on one hand are not recommended."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } //increment counter, if it gets above the countLimit, log an error consecutiveCounter++; if (consecutiveCounter >= countLimit) { //TODO convert rhythmLimit to quarter note, eigth note, etc. var error = new ErrorLogEntry(curTarget.data.time, "WARNING, in " + label + ", having more than " + countLimit + " consecutive " + rhythmLimit + " targets is not recommended."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } } } //simultaneous target checks //chains, melees, and pathbuilder notes don't count if ( prevTarget.time == curTarget.data.time && (!prevTarget.behavior.Equals(TargetBehavior.Chain) && !curTarget.data.behavior.Equals(TargetBehavior.Chain)) && (!prevTarget.behavior.Equals(TargetBehavior.Melee) && !curTarget.data.behavior.Equals(TargetBehavior.Melee)) && (!prevTarget.behavior.Equals(TargetBehavior.NR_Pathbuilder) && !curTarget.data.behavior.Equals(TargetBehavior.NR_Pathbuilder)) ) { // same pitch if (prevTarget.position == curTarget.data.position) { var error = new ErrorLogEntry(prevTarget.time, "ERROR, there are multiple targets occupying the same position."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } // same color if (prevTarget.handType.Equals(curTarget.data.handType) && !prevTarget.handType.Equals(TargetHandType.Either)) { var error = new ErrorLogEntry(prevTarget.time, "ERROR, the " + prevTarget.handType + " hand has multiple targets at the same time."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } // ADVANCED and lower if (difficulty > 0) { //simultaneous shot and melee if (IsSimultaneousShotAndMelee(prevTarget, curTarget)) { var error = new ErrorLogEntry(prevTarget.time, "WARNING, in " + label + ", simultaneous melee and shot targets are not recommended."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } //simultaneous targets must be within 4 spaces apart for Advanced, 3 for Standard/Beginner else { float distance = (difficulty == 1 ? 4 : 3); if (!IsCloseEnough(prevTarget, curTarget, distance)) { var error = new ErrorLogEntry(prevTarget.time, "WARNING, in " + label + ", simultaneous targets more than " + distance + " spaces apart are not recommended."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } } } } //////////////////////////////////// // lower difficulty lead-in times // //////////////////////////////////// // slotted notes if (IsSlottedNote(curTarget.data) && difficulty != 0) { //ADVANCED if (difficulty == 1) { if (!IsSlottedNote(prevTarget) && InsufficientBreakAfterPreviousTarget(prevTarget, curTarget, new QNT_Duration(Constants.PulsesPerQuarterNote * 2))) { var error = new ErrorLogEntry(prevTarget.time, "WARNING, in ADVANCED, it is recommended to have at least 2 beats of lead-in time before introducing a horizontal/vertical slotted note."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } } else // no slotted notes for STANDARD or BEGINNER { var error = new ErrorLogEntry(prevTarget.time, "WARNING, in " + label + ", use of horizontal/vertical slotted notes is not recommended."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } } ////////////////// // melee checks // ////////////////// //prev low solo melee check if (prevTarget.behavior.Equals(TargetBehavior.Melee) && IsLowMelee(prevTarget)) { if (IsLowSoloMelee(lastLastTarget, prevTarget, curTarget.data)) { var error = new ErrorLogEntry(prevTarget.time, "WARNING, this Melee Target is by itself in the lower slot. Single melees should be in the higher slot. Only use the lower slot for making simultaneous melees stacked on top of each other."); error.affectedTargets.Add(timeline.FindNote(prevTarget)); errorLog.Add(error); } } if (curTarget.data.behavior.Equals(TargetBehavior.Melee)) { //non melee hitsound if (!IsMeleeHitSound(curTarget)) { var error = new ErrorLogEntry(curTarget.data.time, "WARNING, Melee Target doesn't have a Melee hitsound."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } //low melee if (IsLowMelee(curTarget.data)) { // won't log an ERROR, yet. // will check on the next cycle of the parser if we have a low solo melee. // for now, save an extra reference to prevTarget. lastLastTarget = prevTarget; } //update previous melee reference prevMeleeTarget = curTarget.data; } //////////////////////////// // LH/RH backtrack checks // //////////////////////////// if (curTarget.data.handType.Equals(TargetHandType.Right)) { prevTarget = prevRHTarget; } else { prevTarget = prevLHTarget; } if (!curTarget.data.behavior.Equals(TargetBehavior.Melee)) { //short break after sustain if (prevTarget.behavior.Equals(TargetBehavior.Hold)) { if (InsufficientBreakAfterSustain(prevTarget, curTarget, sustainLeadTime)) { var error = new ErrorLogEntry(prevTarget.time, "WARNING, the time between the end of this sustain target and the next target on the same hand is very short; at least " + sustainLeadTime + " is recommended."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } } //short break after chain node if (prevTarget.behavior.Equals(TargetBehavior.Chain) && !curTarget.data.behavior.Equals(TargetBehavior.Chain)) { if (InsufficientBreakAfterPreviousTarget(prevTarget, curTarget, chainLeadTime)) { var error = new ErrorLogEntry(prevTarget.time, "WARNING, the time between the end of this chain and the next target on the same hand is very short; at least " + chainLeadTime + " is recommended."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } } //headless chains if (curTarget.data.behavior.Equals(TargetBehavior.Chain) && !(prevTarget.behavior.Equals(TargetBehavior.Chain) || prevTarget.behavior.Equals(TargetBehavior.ChainStart))) { var error = new ErrorLogEntry(curTarget.data.time, "ERROR, this chain node does not have a proper Chain Start."); error.affectedTargets.Add(curTarget); errorLog.Add(error); } // update prev target if (curTarget.data.handType.Equals(TargetHandType.Right)) { prevRHTarget = curTarget.data; } else { prevLHTarget = curTarget.data; } } //Update previous target reference prevTarget = curTarget.data; } // string output Debug.Log("Error checker has detected " + errorLog.Count + " errors/warnings."); // foreach(ErrorLogEntry item in errorLog) // { //output = output + "[" + (item.beatTime*TickBeatConst) + "] " + item.errorDesc + System.Environment.NewLine; //} return(errorLog); }
public static void CalculateChainNotes(TargetData data) { if (data.behavior != TargetBehavior.NR_Pathbuilder) { return; } if (data.pathBuilderData.createdNotes) { data.pathBuilderData.generatedNotes.ForEach(t => { timeline.DeleteTargetFromAction(t); }); data.pathBuilderData.createdNotes = false; } //No notes can be generated if (data.beatLength.tick == 0) { return; } data.pathBuilderData.generatedNotes = new List <TargetData>(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////// WARNING! ///////// ///////// Chainging this calculation breaks backwards compatibility with saves of older NotReaper versions! ///////// ///////// Make sure to update NRCueData.Version, and handle an upgrade path! ///////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Generate first note at the start TargetData firstData = new TargetData(); firstData.behavior = data.pathBuilderData.behavior; firstData.velocity = data.pathBuilderData.velocity; firstData.handType = data.pathBuilderData.handType; firstData.time = data.time; firstData.position = data.position; data.pathBuilderData.generatedNotes.Add(firstData); //We increment as if all these values were for 1/4 notes over 4 beats, makes the ui much better float quarterIncrConvert = (4.0f / data.pathBuilderData.interval) * (Constants.PulsesPerQuarterNote * 4.0f / data.beatLength.tick); //Generate new notes Vector2 currentPos = data.position; Vector2 currentDir = new Vector2(Mathf.Sin(data.pathBuilderData.initialAngle * Mathf.Deg2Rad), Mathf.Cos(data.pathBuilderData.initialAngle * Mathf.Deg2Rad)); float currentAngle = (data.pathBuilderData.angle / 4) * quarterIncrConvert; float currentStep = data.pathBuilderData.stepDistance * quarterIncrConvert; TargetBehavior generatedBehavior = data.pathBuilderData.behavior; if (generatedBehavior == TargetBehavior.ChainStart) { generatedBehavior = TargetBehavior.Chain; } TargetVelocity generatedVelocity = data.pathBuilderData.velocity; if (generatedVelocity == TargetVelocity.ChainStart) { generatedVelocity = TargetVelocity.Chain; } for (int i = 1; i <= (data.beatLength.tick / (float)Constants.PulsesPerQuarterNote) * (data.pathBuilderData.interval / 4.0f); ++i) { currentPos += currentDir * currentStep; currentDir = currentDir.Rotate(currentAngle); currentAngle += (data.pathBuilderData.angleIncrement / 4) * quarterIncrConvert; currentStep += data.pathBuilderData.stepIncrement * quarterIncrConvert; TargetData newData = new TargetData(); newData.behavior = generatedBehavior; newData.velocity = generatedVelocity; newData.handType = data.pathBuilderData.handType; newData.time = data.time + QNT_Duration.FromBeatTime(i * (4.0f / data.pathBuilderData.interval)); newData.position = currentPos; data.pathBuilderData.generatedNotes.Add(newData); } data.pathBuilderData.OnFinishRecalculate(); }