/// <summary> /// Finds the object end before and the object start after the break period. /// </summary> internal bool FindActualEnds(EventBreak b, out int actualStart, out int actualEnd) { HitObject prevObj, nextObj; int indexAfter = hitObjectManager.BreakObjectAfter(b); int indexBefore = indexAfter - 1; prevObj = hitObjectManager.GetObject(indexBefore); //Get any object that may be extruding past start time int tempIndex; if (prevObj != null) { tempIndex = hitObjectManager.FindMaxOverlappedObject(Math.Min(prevObj.EndTime, b.StartTime), indexAfter); } else { tempIndex = hitObjectManager.FindMaxOverlappedObject(b.StartTime, indexAfter); } if (tempIndex >= 0) { indexBefore = tempIndex; } prevObj = hitObjectManager.GetObject(indexBefore); nextObj = hitObjectManager.GetObject(indexAfter); actualStart = getActualStart(prevObj); actualEnd = getActualEnd(nextObj); return(actualStart >= 0 && actualEnd >= 0); }
/// <remarks>See EventBreak.Update() for params.</remarks> internal void UpdateBreaks(bool updateOrder, bool findCustom, bool useMinimum = false, bool backup = true) { if (!BreakDirty) { return; } if (backup) { changeManager.BeginAction(ChangeType.Event); changeManager.PushAction(ActionType.MoveTimeline, Modifier.MoveBoth | Modifier.VariableChanges); } if (hitObjectManager.hitObjectsCount > 1) { //List can be modified inside Update call. Keep as for loop. for (int i = 0; i < eventManager.eventBreaks.Count; i++) { EventBreak br = eventManager.eventBreaks[i]; if (!br.Update(updateOrder, findCustom, useMinimum, backup)) { i--; } } if (CheckInvalidBreaks) { RemoveInvalidBreaks(backup); } fillLargeGaps(backup); } else if (eventManager.eventBreaks.Count > 0) //There needs to be at least 2 hitcircles to have a break time. { if (backup) { changeManager.PushAction(ActionType.Remove); changeManager.BackupData(eventManager.eventBreaks); } eventManager.RemoveAllBreaks(); } //Set variables back to default BreakDirty = false; UseMinimumLength = false; CheckInvalidBreaks = false; if (!backup) { return; } if (FinishOnBreakUpdate) { FinishOnBreakUpdate = false; changeManager.FinishAction(); } }
internal void RemoveBreak(EventBreak b, bool backup = true) { if (backup) { changeManager.BeginAction(ChangeType.Event, ActionType.Remove, b); } eventManager.Remove(b); }
internal void InsertBreak(EventBreak b, bool backup = true) { if (b.Length < EventBreak.MIN_BREAK_LENGTH) { return; } if (backup) { changeManager.BeginAction(ChangeType.Event, ActionType.Add, b); } eventManager.Add(b); }
internal void InsertBreak(int time, bool alertOnFail, bool backup = true) { bool found; int index = hitObjectManager.FindObjectNear(time, out found); //Object does not occupy space and is surrounded by objects if (!found && index > 0 && index < hitObjectManager.hitObjectsCount) { int startTime = hitObjectManager.GetObject(index - 1).EndTime; int endTime = hitObjectManager.GetObject(index).StartTime; int tempIndex = hitObjectManager.FindMaxOverlappedObject(startTime, index); if (tempIndex >= 0) { startTime = hitObjectManager.GetObject(tempIndex).EndTime; } if (startTime < time && endTime > time) { bool breakFound = false; //Space is large enough to contain a break and doesn't contain a break. if (IsValidBreakLengthMax(startTime, endTime) && !(breakFound = FindBreakWithin(startTime, endTime) != null)) { EventBreak b = new EventBreak(startTime, endTime, false); if (b.Length >= EventBreak.MIN_BREAK_LENGTH) { InsertBreak(b, backup); return; } } if (breakFound) { return; } } } if (alertOnFail) { NotificationManager.ShowMessageMassive( LocalisationManager.GetString(OsuString.EditorModeCompose_NotEnoughRoomMessage), 3000); } }
/// <summary> /// Is the local user active at [time] /// </summary> private bool isLocalUserActiveAt(int time) { if (EventManager.BreakMode || player.Passed || Player.Failed || player.Status != PlayerStatus.Playing) { return(true); } if (AudioEngine.Time > player.hitObjectManager.hitObjects[player.hitObjectManager.hitObjectsCount - 1].EndTime) { return(true); } EventBreak br = localUserActiveTime.Find(b => b.StartTime <= time && b.EndTime >= time); if (br == null) { return(false); } return(player.hitObjectManager.hitObjectsMinimal.FindIndex( ho => (ho.TagNumeric == player.localPlayerMatchId || ho.TagNumeric == -2) && ho.EndTime <= br.EndTime + player.hitObjectManager.HitWindow50 && !ho.IsHit) >= 0); }
/// <summary> /// Checks that the break bounds can fit within the actual space surrounding the break including preempt time. /// </summary> /// <param name="useNoBreakLength">if true compares break length with maximum allowed non break time</param> /// <returns>Actual break bounds are valid and exceeds a maximum break limit</returns> internal bool IsValidBreakLength(EventBreak b, bool useNoBreakLength = false) { if (b == null) { return(false); } if (b.Length < EventBreak.MIN_BREAK_LENGTH) { return(false); } int bStart, bEnd; FindActualEnds(b, out bStart, out bEnd); //Actual ends don't exist or preempt space is too small. if (bStart < 0 || bEnd < 0 || b.StartTime - bStart < EventBreak.PREEMPT_START || bEnd - b.EndTime < hitObjectManager.PreEmpt) { return(false); } return(IsValidBreakLengthMax(bStart, bEnd, useNoBreakLength)); }
/// <summary> /// Handle splitting up and labelling the hitObjects into different player's scopes. /// </summary> internal override void InitializeHitObjectPostProcessing() { bMatch match = PlayerVs.Match; usedPlayerSlots = new List <int>(); int playerCount = 0; for (int i = 0; i < bMatch.MAX_PLAYERS; i++) { if ((match.slotStatus[i] & SlotStatus.Playing) > 0 && match.slotTeam[i] == match.slotTeam[player.localPlayerMatchId]) { usedPlayerSlots.Add(i); playerCount++; } } HitObjectManager hitObjectManager = player.hitObjectManager; int currentPlayer = -1; localUserActiveTime = new List <EventBreak>(); EventBreak currentBreak = null; bool firstCombo = true; bool customTagColor; if (customTagColor = MatchSetup.TagComboColour != Color.TransparentWhite) { GameBase.Scheduler.Add(delegate { SkinManager.SliderRenderer.Tag = MatchSetup.TagComboColour; }); } for (int i = 0; i < hitObjectManager.hitObjectsCount; i++) { HitObject h = hitObjectManager.hitObjects[i]; //HitObject hLast = i > 0 ? hitObjectManager.hitObjects[i-1] : hitObjectManager.hitObjects[i]; bool firstInCombo = h.NewCombo || firstCombo; bool spinner = h.IsType(HitObjectType.Spinner) || h is HitCircleFruitsSpin; if (firstInCombo) { if (!spinner) { currentPlayer = (currentPlayer + 1) % playerCount; firstCombo = false; } if (spinner || usedPlayerSlots[currentPlayer] == player.localPlayerMatchId) { //The local player starts playing at this point. int st = (i > 0 ? Math.Max(h.StartTime - hitObjectManager.HitWindow50, hitObjectManager.hitObjects[i - 1].EndTime + 1) : h.StartTime - hitObjectManager.HitWindow50); int ed = h.EndTime; currentBreak = new EventBreak(st, ed); localUserActiveTime.Add(currentBreak); } else { //Another play has taken over. currentBreak = null; } } if (spinner || usedPlayerSlots[currentPlayer] == player.localPlayerMatchId) { if (currentBreak != null) { //The local player finishes playing at this point (or further). currentBreak.SetEndTime(h.EndTime + hitObjectManager.HitWindow50); } if (customTagColor) { h.SetColour(MatchSetup.TagComboColour); } } else { h.IsScorable = false; h.SetColour(Color.Gray); } h.TagNumeric = spinner ? -2 : usedPlayerSlots[currentPlayer]; } InitializeWarningArrows(); }
/// <summary> /// Validates and handles break drag changes. /// </summary> /// <param name="dragBreakEnd">Determines whether breakEnd or breakStart is being dragged.</param> /// <param name="newTime">The new time to apply to the current break end (or start)</param> /// <returns>Whether break has been changed or removed.</returns> internal bool UpdateBreakDrag(EventBreak b, bool dragBreakEnd, int newTime) { if (!BreakDirty || b == null) { return(false); } int actualStart, actualEnd; BreakDirty = false; if (dragBreakEnd) { int newEnd = newTime; int oldEnd = b.EndTime; if (newEnd == oldEnd) { return(false); } if (newEnd < oldEnd) //Break is being dragged to the left { if (FindActualEnds(b, out actualStart, out actualEnd)) { int preEmptStart = actualStart + EventBreak.PREEMPT_START; newEnd = Math.Max(newEnd, preEmptStart + EventBreak.PREFERRED_BREAK_LENGTH); //Value exceeds allowed custom break distance. if (newEnd < MaxBreakDragDistance) { return(false); } b.SetEndTime(newEnd); } else //This should never happen { RemoveBreak(b); return(true); } } else { //We cannot drag past the preempt point if (!b.CustomEnd) { return(false); } if (FindActualEnds(b, out actualStart, out actualEnd)) { int preEmptEnd = actualEnd - hitObjectManager.PreEmpt; newEnd = Math.Min(newEnd, preEmptEnd); b.SetEndTime(newEnd); } else //This should never happen { RemoveBreak(b); return(true); } } } else { int oldStart = b.StartTime; int newStart = newTime; if (newStart == oldStart) { return(false); } if (newStart < oldStart) { //We cannot drag past the preempt point if (!b.CustomStart) { return(false); } if (FindActualEnds(b, out actualStart, out actualEnd)) { int preEmptStart = actualStart + EventBreak.PREEMPT_START; newStart = Math.Max(newStart, preEmptStart); b.SetStartTime(newStart); } else //This should never happen { RemoveBreak(b); return(true); } } else { if (FindActualEnds(b, out actualStart, out actualEnd)) { int preEmptEnd = actualEnd - hitObjectManager.PreEmpt; newStart = Math.Min(newStart, preEmptEnd - EventBreak.PREFERRED_BREAK_LENGTH); //Value exceeds allowed custom break distance. if (newStart > MaxBreakDragDistance) { return(false); } b.SetStartTime(newStart); } else //This should never happen { RemoveBreak(b); return(true); } } } b.Update(!dragBreakEnd, true, true, false); return(true); }
internal override void ChangeState(State undoState) { if (HasModifier(Modifier.VariableChanges)) { if (HasModifier(Modifier.MoveBoth)) { bool isBreak = ((Event)changedObjects[0]).Type == EventTypes.Break; int index = 0; foreach (Event e in changedObjects) { ListHelper.Swap(startOffsets, index, ref e.StartTime); ListHelper.Swap(endOffsets, index, ref e.EndTime); if (isBreak) { EventBreak br = (EventBreak)e; ListHelper.Swap(customStarts, index, ref br.CustomStart); ListHelper.Swap(customEnds, index, ref br.CustomEnd); } index++; } } else if (HasModifier(Modifier.MoveStart)) { bool isBreak = ((Event)changedObjects[0]).Type == EventTypes.Break; int index = 0; foreach (Event e in changedObjects) { ListHelper.Swap(startOffsets, index, ref e.StartTime); if (isBreak) { EventBreak br = (EventBreak)e; ListHelper.Swap(customStarts, index, ref br.CustomStart); } index++; } } else { bool isBreak = ((Event)changedObjects[0]).Type == EventTypes.Break; int index = 0; foreach (Event e in changedObjects) { ListHelper.Swap(endOffsets, index, ref e.EndTime); if (isBreak) { EventBreak br = (EventBreak)e; ListHelper.Swap(customEnds, index, ref br.CustomEnd); } index++; } } } else { if (HasModifier(Modifier.None) || HasModifier(Modifier.MoveStart)) { Event e = (Event)changedObjects[0]; int offsetChange = e.StartTime - startOffset; startOffset = e.StartTime; foreach (Event e1 in changedObjects) { e1.StartTime -= offsetChange; } } else if (HasModifier(Modifier.MoveBoth)) { Event e = (Event)changedObjects[0]; int offsetChange = e.StartTime - startOffset; startOffset = e.StartTime; endOffset = e.EndTime; foreach (Event e1 in changedObjects) { if (e1 is EventSample || e1 is EventSprite || e1 is EventAnimation || e1 is EventVideo) { e1.StartTime -= offsetChange; e1.EndTime -= offsetChange; if (e1.Sprite != null) { foreach (Transformation t in e1.Sprite.Transformations) { t.Time1 -= offsetChange; t.Time2 -= offsetChange; } if (e1.Sprite.Loops != null) { foreach (TransformationLoop tl in e1.Sprite.Loops) { tl.StartTime -= offsetChange; } } } foreach (List <TriggerLoop> ll in e1.EventLoopTriggers.Values) { foreach (TriggerLoop l in ll) { l.StartTime -= offsetChange; l.TriggerStartTime -= offsetChange; l.TriggerEndTime -= offsetChange; } } } } } } }
protected override void PostProcessing() { isPost = true; Beatmap.StackLeniency = 0; double t = firstObjectTime; int i = 0; Vector2 lastPosition = new Vector2(256, 197); double speed = 0; double angle = targetRandom.NextDouble() * Math.PI * 2; while (t < lastObjectTime) { double progress = (t - firstObjectTime) / (lastObjectTime - firstObjectTime); bool speedIncrease = i % 8 == 0; if (lastComboIndex < comboTimes.Count && t >= comboTimes[lastComboIndex]) { lastComboIndex++; } if (speedIncrease) { speed = HitObjectRadius + (int)(progress * 40) / 40d * 333; } Vector2 movement; ControlPoint cp = Beatmap.ControlPointAt(t); double multiplier = cp.KiaiMode ? 1.2 : 1; if (speedIncrease) { multiplier *= 1.5; } angle += (targetRandom.NextDouble() - 0.5) * 2; movement.X = lastPosition.X + (float)(speed * multiplier * Math.Cos(angle)); movement.Y = lastPosition.Y + (float)(speed * multiplier * Math.Sin(angle)); int tryCount = 0; while (movement.X < 0 || movement.Y < 0 || movement.X > 512 || movement.Y > 384 || (speed > HitObjectRadius && closeToRecent(movement, tryCount))) { angle = targetRandom.NextDouble() * Math.PI * 2; movement.X = lastPosition.X + (float)(speed * multiplier * Math.Cos(angle)); movement.Y = lastPosition.Y + (float)(speed * multiplier * Math.Sin(angle)); if (++tryCount > 10) { multiplier *= 0.9; } } lastPosition = movement; HitCircleOsuTarget h = new HitCircleOsuTarget(this, lastPosition + new Vector2(0, -10), (int)t, speedIncrease, HitObjectSoundType.Normal, 0); EventBreak b = eventManager.eventBreaks.Find(br => br.StartTime <= t && br.EndTime >= t); if (b == null) { int found = intendedObjects.BinarySearch(h); if (found < 0) { found = ~found - 1; } if (found >= 0 && found > lastUsedIntended && found < intendedObjects.Count) { lastUsedIntended = found; HitObject closest = intendedObjects[found]; h.SampleSet = closest.SampleSet; h.SampleSetAdditions = closest.SampleSetAdditions; h.SoundType = closest.SoundType; h.CustomSampleSet = closest.CustomSampleSet; } foreach (pSprite p in h.SpriteCollection) { //physics.Add(p, new Vector2((float)(progress * (GameBase.random.NextDouble() - 0.5)), 0), true).weight = -100; p.Transformations.Add(new Transformation(TransformationType.Scale, p.Scale * 0.5f, p.Scale, (int)t - PreEmpt, (int)t)); } AddCircle(h); } i++; t += Beatmap.BeatLengthAt(t, false); } Sort(false); base.PostProcessing(); }