Пример #1
0
        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());
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
        private void OnBeatLengthChanged(QNT_Duration newBeatLength)
        {
            if (!data.supportsBeatLength)
            {
                return;
            }

            if (data.behavior == TargetBehavior.NR_Pathbuilder)
            {
                ChainBuilder.GenerateChainNotes(data);
            }
        }
Пример #4
0
        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;
        }
Пример #5
0
        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);
        }
Пример #6
0
        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));
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
 private void OnSustainLengthChanged(QNT_Duration beatLength)
 {
     UpdateTimelineSustainLength();
 }
Пример #9
0
 private bool InsufficientBreakAfterPreviousTarget(TargetData prevTarget, Target curTarget, QNT_Duration leadTime)
 {
     return(curTarget.data.time.tick - prevTarget.time.tick < leadTime.tick);
 }
Пример #10
0
 private bool InsufficientBreakAfterSustain(TargetData prevTarget, Target curTarget, QNT_Duration leadTime)
 {
     return(curTarget.data.time.tick - (prevTarget.time.tick + prevTarget.beatLength.tick) < leadTime.tick);
 }
Пример #11
0
        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);
        }
Пример #12
0
        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();
        }