Ejemplo n.º 1
0
        private char GetSMCharForNote(LaneNote note)
        {
            if (note.DestType?.Length == 1 &&
                SMCommon.NoteChars.Contains(note.DestType[0]))
            {
                return(note.DestType[0]);
            }

            if (MatchesSourceFileFormatType() &&
                note.SourceType?.Length == 1 &&
                SMCommon.NoteChars.Contains(note.SourceType[0]))
            {
                return(note.SourceType[0]);
            }

            if (note is LaneTapNote)
            {
                return(SMCommon.NoteChars[(int)SMCommon.NoteType.Tap]);
            }
            if (note is LaneHoldEndNote)
            {
                return(SMCommon.NoteChars[(int)SMCommon.NoteType.HoldEnd]);
            }
            if (note is LaneHoldStartNote)
            {
                return(SMCommon.NoteChars[(int)SMCommon.NoteType.HoldStart]);
            }
            return(SMCommon.NoteChars[(int)SMCommon.NoteType.None]);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Creates an ExpressedChart MineEvent.
        /// If possible, this MineEvent will be an AfterArrow type.
        /// If no arrow precedes this mine then this MineEvent will be a BeforeArrow type.
        /// If no arrow exists in the same lane as the mine then this will be a NoArrow type.
        /// </summary>
        /// <param name="numArrows">
        /// Number of arrows in the chart being used to generate the ExpressedChart.
        /// This is used to ease the searches into neighboring events.
        /// </param>
        /// <param name="releases">
        /// List of all FootActionEvents representing release events for the ExpressedChart.
        /// See GetReleasesAndSteps for release FootActionEvent generation.
        /// </param>
        /// <param name="releaseIndex">
        /// The index into releases of the release event which precedes the given mine.
        /// This could be determined by this method, but requiring it as a parameter is a
        /// performance optimization as it allows the caller to call this method in loop
        /// and avoid another scan.
        /// </param>
        /// <param name="steps">
        /// List of all FootActionEvents representing step events for the ExpressedChart.
        /// See GetReleasesAndSteps for release FootActionEvent generation.
        /// </param>
        /// <param name="stepIndex">
        /// The index into steps of the step event which follows the given mine.
        /// This could be determined by this method, but requiring it as a parameter is a
        /// performance optimization as it allows the caller to call this method in loop
        /// and avoid another scan.
        /// </param>
        /// <param name="mine">The LaneNote representing a mine from the original chart.</param>
        /// <returns>New MineEvent.</returns>
        public static ExpressedChart.MineEvent CreateExpressedMineEvent(
            int numArrows,
            List <FootActionEvent> releases,
            int releaseIndex,
            List <FootActionEvent> steps,
            int stepIndex,
            LaneNote mine)
        {
            // Try first to create an AfterArrow type of mine by associating the mine
            // with a preceding arrow.
            var(n, f) = GetHowRecentIsNeighboringArrow(true, releaseIndex, numArrows, releases, mine.Lane);
            if (n >= 0)
            {
                return(new ExpressedChart.MineEvent(mine.Position, mine.TimeMicros, mine.Lane)
                {
                    Type = MineType.AfterArrow,
                    ArrowIsNthClosest = n,
                    FootAssociatedWithPairedNote = f
                });
            }

            // Next, try to create a BeforeArrow type of mine by associating the mine
            // with a following arrow.
            (n, f) = GetHowRecentIsNeighboringArrow(false, stepIndex, numArrows, steps, mine.Lane);
            if (n >= 0)
            {
                return(new ExpressedChart.MineEvent(mine.Position, mine.TimeMicros, mine.Lane)
                {
                    Type = MineType.BeforeArrow,
                    ArrowIsNthClosest = n,
                    FootAssociatedWithPairedNote = f
                });
            }

            // The mine could not be associated with an arrow, use the default NoArrow type.
            return(new ExpressedChart.MineEvent(mine.Position, mine.TimeMicros, mine.Lane));
        }
Ejemplo n.º 3
0
 public LaneNote(LaneNote other)
     : base(other)
 {
     Lane = other.Lane;
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Parses the sm/ssc string representation of measures and notes as
        /// events into the given Chart.
        /// </summary>
        /// <param name="chart">Chart to parse notes into.</param>
        /// <param name="notesStr">String representation of notes from an sm or ssc chart.</param>
        /// <returns>Whether the notes represent a valid chart or not.</returns>
        protected bool ParseNotes(Chart chart, string notesStr)
        {
            if (chart.NumInputs < 1)
            {
                Logger?.Warn(
                    $"Cannot parse notes for {chart.Type} {chart.DifficultyType} Chart. Unknown number of inputs. This Chart will be ignored.");
                return(false);
            }

            var validChart           = true;
            var player               = 0;
            var measure              = 0;
            var currentMeasureEvents = new List <Event>();

            notesStr = notesStr.Trim(SMCommon.SMAllWhiteSpace);
            var notesStrsPerPlayer = notesStr.Split('&');

            foreach (var notesStrForPlayer in notesStrsPerPlayer)
            {
                var holding = new bool[chart.NumInputs];
                var rolling = new bool[chart.NumInputs];

                // RemoveEmptyEntries seems wrong, but matches Stepmania parsing logic.
                var measures = notesStrForPlayer.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var measureStr in measures)
                {
                    var lines = measureStr.Trim(SMCommon.SMAllWhiteSpace)
                                .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                    var linesInMeasure = lines.Length;
                    var lineInMeasure  = 0;
                    foreach (var line in lines)
                    {
                        var trimmedLine = line.Trim(SMCommon.SMAllWhiteSpace);

                        // Parse this line as note data
                        for (int charIndex = 0, laneIndex = 0;
                             charIndex < trimmedLine.Length && laneIndex < chart.NumInputs;
                             charIndex++, laneIndex++)
                        {
                            // Get the note type.
                            var c          = trimmedLine[charIndex];
                            var noteType   = SMCommon.NoteType.None;
                            var noteString = SMCommon.NoteStrings[(int)noteType];
                            for (var i = 0; i < SMCommon.NoteChars.Length; i++)
                            {
                                if (c == SMCommon.NoteChars[i])
                                {
                                    noteType   = (SMCommon.NoteType)i;
                                    noteString = SMCommon.NoteStrings[i];
                                    break;
                                }
                            }

                            // Validation.
                            if (noteType == SMCommon.NoteType.Tap ||
                                noteType == SMCommon.NoteType.Mine ||
                                noteType == SMCommon.NoteType.Lift ||
                                noteType == SMCommon.NoteType.Fake ||
                                noteType == SMCommon.NoteType.KeySound ||
                                noteType == SMCommon.NoteType.HoldStart ||
                                noteType == SMCommon.NoteType.RollStart)
                            {
                                if (holding[laneIndex])
                                {
                                    Logger?.Error(
                                        $"Invalid {chart.Type} {chart.DifficultyType} Chart. {noteString} during hold on lane {laneIndex} during measure {measure}. This Chart will be ignored.");
                                    validChart = false;
                                }

                                if (rolling[laneIndex])
                                {
                                    Logger?.Error(
                                        $"Invalid {chart.Type} {chart.DifficultyType} Chart. {noteString} during roll on lane {laneIndex} during measure {measure}. This Chart will be ignored.");
                                    validChart = false;
                                }
                            }
                            else if (noteType == SMCommon.NoteType.HoldEnd)
                            {
                                if (!holding[laneIndex] && !rolling[laneIndex])
                                {
                                    Logger?.Error(
                                        $"Invalid {chart.Type} {chart.DifficultyType} Chart. {noteString} while neither holding nor rolling on lane {laneIndex} during measure {measure}. This Chart will be ignored.");
                                    validChart = false;
                                }
                            }
                            if (!validChart)
                            {
                                break;
                            }

                            // Create a LaneNote based on the note type.
                            LaneNote note = null;
                            if (noteType == SMCommon.NoteType.Tap ||
                                noteType == SMCommon.NoteType.Fake ||
                                noteType == SMCommon.NoteType.Lift)
                            {
                                note = new LaneTapNote {
                                    SourceType = c.ToString()
                                };
                            }
                            else if (noteType == SMCommon.NoteType.Mine ||
                                     noteType == SMCommon.NoteType.KeySound)
                            {
                                note = new LaneNote {
                                    SourceType = c.ToString()
                                };
                            }
                            else if (noteType == SMCommon.NoteType.HoldStart)
                            {
                                holding[laneIndex] = true;
                                note = new LaneHoldStartNote {
                                    SourceType = c.ToString()
                                };
                            }
                            else if (noteType == SMCommon.NoteType.RollStart)
                            {
                                rolling[laneIndex] = true;
                                note = new LaneHoldStartNote {
                                    SourceType = c.ToString()
                                };
                            }
                            else if (noteType == SMCommon.NoteType.HoldEnd)
                            {
                                note = new LaneHoldEndNote {
                                    SourceType = c.ToString()
                                };
                                holding[laneIndex] = false;
                                rolling[laneIndex] = false;
                            }

                            // Keysound parsing.
                            // TODO: Parse keysounds properly. For now, putting them in SourceExtras.
                            if (charIndex + 1 < trimmedLine.Length && trimmedLine[charIndex + 1] == '[')
                            {
                                var startIndex = charIndex + 1;
                                while (charIndex < trimmedLine.Length)
                                {
                                    if (trimmedLine[charIndex] == ']')
                                    {
                                        break;
                                    }
                                    charIndex++;
                                }

                                var endIndex = charIndex - 1;
                                if (endIndex > startIndex && note != null)
                                {
                                    if (int.TryParse(trimmedLine.Substring(startIndex, endIndex - startIndex),
                                                     out var keySoundIndex))
                                    {
                                        note.Extras.AddSourceExtra(SMCommon.TagFumenKeySoundIndex, keySoundIndex, true);
                                    }
                                }
                            }

                            // Deprecated Attack parsing
                            if (charIndex + 1 < trimmedLine.Length && trimmedLine[charIndex + 1] == '{')
                            {
                                while (charIndex < trimmedLine.Length)
                                {
                                    if (trimmedLine[charIndex] == '}')
                                    {
                                        break;
                                    }
                                    charIndex++;
                                }
                            }

                            // Deprecated Item parsing
                            if (charIndex + 1 < trimmedLine.Length && trimmedLine[charIndex + 1] == '<')
                            {
                                while (charIndex < trimmedLine.Length)
                                {
                                    if (trimmedLine[charIndex] == '>')
                                    {
                                        break;
                                    }
                                    charIndex++;
                                }
                            }

                            // No note at this position, continue.
                            if (null == note)
                            {
                                continue;
                            }

                            // Configure common parameters on the note and add it.
                            var beat = (lineInMeasure * SMCommon.NumBeatsPerMeasure) / linesInMeasure;
                            var subDivisionNumerator   = lineInMeasure * SMCommon.NumBeatsPerMeasure - linesInMeasure * beat;
                            var subDivisionDenominator = linesInMeasure;
                            note.Lane     = laneIndex;
                            note.Player   = player;
                            note.Position = new MetricPosition(measure, beat, subDivisionNumerator, subDivisionDenominator);
                            currentMeasureEvents.Add(note);
                        }

                        if (!validChart)
                        {
                            break;
                        }

                        // Advance line marker
                        lineInMeasure++;
                    }

                    if (!validChart)
                    {
                        break;
                    }

                    chart.Layers[0].Events.AddRange(currentMeasureEvents);
                    currentMeasureEvents.Clear();
                    measure++;
                }

                // Validation.
                for (var i = 0; i < chart.NumInputs; i++)
                {
                    if (holding[i])
                    {
                        Logger?.Error(
                            $"Invalid {chart.Type} {chart.DifficultyType} Chart. Incomplete hold on lane {i}. This Chart will be ignored.");
                        validChart = false;
                    }
                    if (rolling[i])
                    {
                        Logger?.Error(
                            $"Invalid {chart.Type} {chart.DifficultyType} Chart. Incomplete roll on lane {i}. This Chart will be ignored.");
                        validChart = false;
                    }
                }

                if (!validChart)
                {
                    break;
                }

                player++;
            }

            return(validChart);
        }