public JudgeResult?UserPressed(time_t timeStamp) { if (AutoPlay) { return(null); } m_userHeld = true; m_userWhen = timeStamp; if (m_ticks.Count == 0) { return(null); } var tick = m_ticks[0]; // Don't ACTUALLY handle holds handled in here if (tick.IsHold) { OnHoldPressed?.Invoke(timeStamp, tick.AssociatedObject); m_lastPressedObject = tick.AssociatedObject; return(null); } else { OnChipPressed?.Invoke(timeStamp, tick.AssociatedObject); } m_ticks.RemoveAt(0); time_t diff = tick.Position + JudgementOffset - timeStamp; time_t absDiff = MathL.Abs(diff.Seconds); time_t offsetTime = timeStamp - JudgementOffset; JudgeResult result; if (absDiff <= PERF_RADIUS) { result = new JudgeResult(diff, JudgeKind.Perfect); } else if (absDiff <= CRIT_RADIUS) { result = new JudgeResult(diff, JudgeKind.Critical); } else if (absDiff <= NEAR_RADIUS) { result = new JudgeResult(diff, JudgeKind.Near); } // TODO(local): Is this how we want to handle misses? else { result = new JudgeResult(diff, JudgeKind.Bad); } OnTickProcessed?.Invoke(tick.AssociatedObject, offsetTime, result); return(result); }
public JudgeResult?UserPressed(time_t position) { // This check just makes sure that we can process ticks. // If there are no state ticks, there should never be score ticks left. if (HasStateTicks) { Debug.Assert(HasScoreTicks); } else { SpawnKeyBeam?.Invoke(Label, JudgeKind.Passive, false); return(null); } switch (m_state) { case JudgeState.Idle: { var nextStateTick = NextStateTick; time_t difference = position - (nextStateTick.Position + JudgementOffset); time_t absDifference = Math.Abs((double)difference); if (nextStateTick.State == JudgeState.ChipAwaitPress && absDifference <= m_chipMissRadius) { var scoreTick = NextScoreTick; Debug.Assert(scoreTick.Kind == TickKind.Chip); Debug.Assert(scoreTick.Position == nextStateTick.Position); // `difference` applies to both ticks, don't recalculate JudgeResult result; if (absDifference <= m_chipPerfectRadius) { result = new JudgeResult(difference, JudgeKind.Perfect); } else if (absDifference <= m_chipCriticalRadius) { result = new JudgeResult(difference, JudgeKind.Critical); } else if (absDifference <= m_chipNearRadius) { result = new JudgeResult(difference, JudgeKind.Near); } // TODO(local): Is this how we want to handle misses? else { result = new JudgeResult(difference, JudgeKind.Bad); } OnTickProcessed?.Invoke(scoreTick.Entity, position, result, difference < 0); OnChipPressed?.Invoke(position, scoreTick.Entity); SpawnKeyBeam?.Invoke(scoreTick.Entity.Lane, result.Kind, difference < 0); AdvanceStateTick(); AdvanceScoreTick(); // state stays idle after a chip press, chips are instantaneous IsBeingPlayed = true; return(result); } else if (nextStateTick.State == JudgeState.HoldAwaitPress && absDifference <= m_holdActivateRadius) { OnHoldPressed?.Invoke(position, nextStateTick.Entity); AdvanceStateTick(); // No need to advance a score tick, we haven't judged anything // state is `hold on` because ofc we pressed the hold! m_state = JudgeState.HoldOn; m_currentStateTick = nextStateTick; IsBeingPlayed = true; } // do nothing when pressed otherwise else { SpawnKeyBeam?.Invoke(Label, JudgeKind.Passive, false); } } break; case JudgeState.HoldOff: { OnHoldPressed?.Invoke(position, m_currentStateTick.Entity); m_state = JudgeState.HoldOn; IsBeingPlayed = true; } break; case JudgeState.HoldOn: throw new InvalidOperationException(); } return(null); }
protected override void AdvancePosition(time_t position) { // This check just makes sure that we can process ticks. // If there are no state ticks, there should never be score ticks left. if (HasStateTicks) { Debug.Assert(HasScoreTicks); } else { return; } // Now, if we have ticks we can continue switch (m_state) { case JudgeState.Idle: { var nextStateTick = NextStateTick; time_t difference = position - (nextStateTick.Position + JudgementOffset); // check missed chips if (nextStateTick.State == JudgeState.ChipAwaitPress && difference > m_chipMissRadius) { var scoreTick = NextScoreTick; Debug.Assert(scoreTick.Kind == TickKind.Chip); Debug.Assert(scoreTick.Position == nextStateTick.Position); OnTickProcessed?.Invoke(scoreTick.Entity, position, new JudgeResult(m_chipMissRadius, JudgeKind.Miss), false); AdvanceStateTick(); AdvanceScoreTick(); IsBeingPlayed = false; } else if (nextStateTick.State == JudgeState.HoldAwaitPress && difference > 0) { m_state = JudgeState.HoldOff; AdvanceStateTick(); m_currentStateTick = nextStateTick; IsBeingPlayed = false; } } break; case JudgeState.HoldOn: case JudgeState.HoldOff: { var nextScoreTick = NextScoreTick; Debug.Assert(nextScoreTick.Entity == m_currentStateTick.Entity); if (position - (nextScoreTick.Position + JudgementOffset) >= 0) { var resultKind = IsBeingPlayed ? JudgeKind.Passive : JudgeKind.Miss; OnTickProcessed?.Invoke(nextScoreTick.Entity, nextScoreTick.Position, new JudgeResult(0, resultKind), false); AdvanceScoreTick(); } var nextStateTick = NextStateTick; if (nextStateTick.State == JudgeState.HoldAwaitRelease && position - (nextStateTick.Position + JudgementOffset) >= 0) { AdvanceStateTick(); m_state = JudgeState.Idle; m_currentStateTick = null; } } break; } }
protected override void AdvancePosition(time_t position) { time_t timeDelta = position - m_lastUpdatePosition; m_lastUpdatePosition = position; if (!HasStateTicks && !HasScoreTicks) { return; } switch (m_state) { case JudgeState.Idle: { var nextStateTick = NextStateTick; while (position - (nextStateTick.Position + JudgementOffset) >= 0) { if (nextStateTick.State == JudgeState.CursorReset) { AdvanceStateTick(); OnShowCursor?.Invoke(); CursorPosition = m_desiredCursorPosition = nextStateTick.RootEntity.InitialValue; LaserRange = nextStateTick.RootEntity.RangeExtended ? 2 : 1; m_direction = 0; } else if (nextStateTick.State == JudgeState.LaserBegin) { AdvanceStateTick(); //LastLockTime = position; m_direction = 0; m_state = nextStateTick.RootEntity.IsInstant ? (IsBeingPlayed ? JudgeState.ActiveOn : JudgeState.ActiveOff) : JudgeState.ActiveOn; m_currentStateTick = nextStateTick; // The first score tick happens at the same time as the laser start event, // hop straight over to the other case explicitly and let it process the score tick. goto case JudgeState.ActiveOn; } else { break; } if (HasStateTicks) { nextStateTick = NextStateTick; } else { break; } } } break; case JudgeState.ActiveOn: case JudgeState.ActiveOff: { var segmentCheck = m_currentStateTick.SegmentEntity; while (segmentCheck != null && segmentCheck.AbsoluteEndPosition < position && segmentCheck.NextConnected is AnalogEntity next) { segmentCheck = next; } m_desiredCursorPosition = segmentCheck.SampleValue(position); if (m_direction != 0) { m_lockTimer -= timeDelta * m_lockTimerSpeed; } if (m_lockTimer < 0) { m_lockTimer = 0; } if (AutoPlay) { CursorPosition = m_desiredCursorPosition; } var nextStateTick = NextStateTick; while (position - (nextStateTick.Position + JudgementOffset) >= 0) { if (nextStateTick.State == JudgeState.LaserEnd) { AdvanceStateTick(); OnHideCursor?.Invoke(); if (AutoPlay) { CursorPosition = m_desiredCursorPosition = nextStateTick.SegmentEntity.FinalValue; } m_state = JudgeState.Idle; m_currentStateTick = null; } else if (nextStateTick.State == JudgeState.SwitchDirection) { if (position > (nextStateTick.Position + JudgementOffset)) { if (position - (nextStateTick.Position + JudgementOffset) >= m_directionChangeRadius) { AdvanceStateTick(); m_direction = nextStateTick.SegmentEntity.DirectionSign; m_currentStateTick = nextStateTick; //Logger.Log($"Direction Switch ({ (m_direction == 1 ? "->" : (m_direction == -1 ? "<-" : "|")) }) Missed (by { position - (nextStateTick.Position + JudgementOffset) }): { nextStateTick.SegmentEntity.Position } ({ nextStateTick.SegmentEntity.AbsolutePosition })"); m_lockTimer = 0.0; } else { m_canControlCursorMovement = false; //Logger.Log($"Prevented automatic cursor moving while locked: direction switch origin passed without being played ({ nextStateTick.SegmentEntity.Position })"); break; } } } else if (nextStateTick.State == JudgeState.SameDirectionSlam && position - (nextStateTick.Position + JudgementOffset) >= m_directionChangeRadius) { AdvanceStateTick(); } else { break; } if (HasStateTicks) { nextStateTick = NextStateTick; } else { break; } } if (IsLocked && m_canControlCursorMovement) { CursorPosition = m_desiredCursorPosition; } IsBeingPlayed = IsLocked || MathL.Abs(m_desiredCursorPosition - CursorPosition) <= m_cursorActiveRange; if (IsBeingPlayed && m_direction == 0) { m_lockTimer = m_lockDuration; // NOTE(local): don't SetLocked, keep the timer decay speed if (m_canControlCursorMovement) { CursorPosition = m_desiredCursorPosition; } } if (HasScoreTicks) { var nextScoreTick = NextScoreTick; if (position - (nextScoreTick.Position + JudgementOffset) >= 0) { var resultKind = IsBeingPlayed ? JudgeKind.Passive : JudgeKind.Miss; OnTickProcessed?.Invoke(nextScoreTick.Entity, nextScoreTick.Position, new JudgeResult(0, resultKind)); AdvanceScoreTick(); } } } break; } if (HasStateTicks && position - (NextStateTick.Position + JudgementOffset) >= 0) { //Logger.Log($"{ NextStateTick.State } :: { NextStateTick.SegmentEntity.Position } or { NextStateTick.Position }"); } }
protected override void AdvancePosition(time_t position) { // remove old ticks first while (!AutoPlay && m_ticks.Count > 0) { var tick = m_ticks[0]; time_t radius = tick.IsHold ? HOLD_RADIUS : MAX_RADIUS; if (tick.Position + JudgementOffset < position - radius) { m_ticks.RemoveAt(0); OnTickProcessed?.Invoke(tick.AssociatedObject, position, new JudgeResult(tick.Position + JudgementOffset - position, JudgeKind.Miss)); } else { break; } } while (m_ticks.Count > 0) { var tick = m_ticks[0]; if (AutoPlay && position >= tick.Position) { m_ticks.RemoveAt(0); if (tick.IsAutoTick) { if (tick.Position == tick.AssociatedObject.AbsolutePosition) { OnHoldPressed?.Invoke(position, tick.AssociatedObject); } //else OnHoldReleased?.Invoke(position, tick.AssociatedObject); } else if (tick.IsHold) { OnTickProcessed?.Invoke(tick.AssociatedObject, position - JudgementOffset, new JudgeResult(0, JudgeKind.Passive)); } else { OnChipPressed?.Invoke(position, tick.AssociatedObject); OnTickProcessed?.Invoke(tick.AssociatedObject, tick.Position, new JudgeResult(0, JudgeKind.Perfect)); } } else // ===== NO AUTO PLAY ===== { if (!tick.IsHold) { break; } time_t check = tick.AssociatedObject.AbsolutePosition + JudgementOffset - m_userWhen; if (check > MISS_RADIUS) { break; } time_t diff = tick.Position + JudgementOffset - position; time_t absDiff = MathL.Abs(diff.Seconds); if (m_userHeld && diff > 0 && absDiff <= HOLD_RADIUS) { m_ticks.RemoveAt(0); OnTickProcessed?.Invoke(tick.AssociatedObject, position - JudgementOffset, new JudgeResult(diff, JudgeKind.Passive)); } else { break; } } } }