private void Reload(bool loadNewSkin) { spriteManager.Clear(); if (loadNewSkin) { SkinManager.LoadSkin(true); AudioEngine.CurrentSampleSet = SampleSet.None; if (BeatmapManager.Current != null) { HitObjectManager.LoadFile(); } } pAnimation back = new pAnimation(SkinManager.LoadAll("menu-back"), FieldTypes.Window, OriginTypes.TopLeft, ClockTypes.Game, new Vector2(0, 480), 0.8F, true, new Color(255, 255, 255, (byte)(255 * 0.6))); back.SetFramerateFromSkin(); back.OriginPosition = new Vector2(0, 146); back.OnClick += back_OnClick; back.IsClickable = true; back.HoverEffect = new Transformation(TransformationType.Fade, 0.6F, 1, 0, 300); spriteManager.Add(back); pButton pbutton = new pButton("Get more skins", new Vector2(440, 400), new Vector2(200, 30), 1, Color.White, delegate { GameBase.ChangeMode(Modes.UpdateSkin); }); pbutton.Text.StartColour = Color.Black; spriteManager.AddRange(pbutton.SpriteCollection); pbutton = new pButton("Random Beatmap", new Vector2(440, 440), new Vector2(200, 30), 1, Color.White, newSong); pbutton.Text.StartColour = Color.Black; spriteManager.AddRange(pbutton.SpriteCollection); int verticalSpacing = 0; pText pt; pSprite pb; spriteManager.Add(new pText("Available Skins:", 20, new Vector2(460, 20), Vector2.Zero, 1, true, Color.White, true)); for (int i = page * skinsPerPage; i < (page + 1) * skinsPerPage; i++) { if (i < Skins.Count) { Skin skin = Skins[i]; if (skin.RawName == ConfigManager.sSkin) { pt = new pText(SkinManager.Current.SkinName, 15, new Vector2(444, 50 + verticalSpacing), new Vector2(200, 0), 0.9f, true, Color.White, false); pt.Tag = skin.RawName; pt.TextAlignment = Alignment.LeftFixed; pt.BackgroundColour = Color.Black; pt.BorderWidth = 2; pt.BorderColour = Color.LimeGreen; pt.OnClick += pt_OnClick; spriteManager.Add(pt); } else { pt = new pText(skin.SkinName, 15, new Vector2(480, 50 + verticalSpacing), new Vector2(200, 0), 0.9f, true, Color.White, false); pt.Tag = skin.RawName; pt.IsClickable = true; pt.TextAlignment = Alignment.LeftFixed; pt.BackgroundColour = Color.Black; pt.BorderWidth = 2; pt.BorderColour = Color.OrangeRed; pt.HoverEffect = new Transformation(pt.StartPosition, pt.StartPosition - new Vector2(36, 0), 0, 200); pt.HoverEffect.Easing = EasingTypes.In; pt.OnClick += pt_OnClick; spriteManager.Add(pt); } } verticalSpacing += 20; } if (page > 0) { pt = new pText("<< Previous Page", 15, new Vector2(480, 50 + verticalSpacing), new Vector2(200, 0), 0.9f, true, Color.White, false); pt.IsClickable = true; pt.TextAlignment = Alignment.LeftFixed; pt.BackgroundColour = Color.Black; pt.BorderWidth = 2; pt.BorderColour = Color.Orange; pt.HoverEffect = new Transformation(pt.StartPosition, pt.StartPosition - new Vector2(36, 0), 0, 200); pt.HoverEffect.Easing = EasingTypes.In; pt.OnClick += prevPage; spriteManager.Add(pt); } verticalSpacing += 20; if (Skins.Count > (page + 1) * skinsPerPage) { pt = new pText(">> Next Page", 15, new Vector2(480, 50 + verticalSpacing), new Vector2(200, 0), 0.9f, true, Color.White, false); pt.IsClickable = true; pt.TextAlignment = Alignment.LeftFixed; pt.BackgroundColour = Color.Black; pt.BorderWidth = 2; pt.BorderColour = Color.Orange; pt.HoverEffect = new Transformation(pt.StartPosition, pt.StartPosition - new Vector2(36, 0), 0, 200); pt.HoverEffect.Easing = EasingTypes.In; pt.OnClick += nextPage; verticalSpacing += 20; spriteManager.Add(pt); } verticalSpacing += 60; pCheckbox skinSamples = new pCheckbox("Use skin's sound samples", new Vector2(445, verticalSpacing), 0.9f, ConfigManager.sSkinSamples); skinSamples.Tooltip = "If this is selected, the default osu!sounds will be used for hit samples"; skinSamples.OnCheckChanged += skinSamples_OnCheckChanged; spriteManager.AddRange(skinSamples.SpriteCollection); verticalSpacing += 20; pCheckbox useTaikoSkin = new pCheckbox("Use Taiko skin for Taiko Mod", new Vector2(445, verticalSpacing), 0.9f, ConfigManager.sUseTaikoSkin); useTaikoSkin.Tooltip = "If this is selected and the Taiko skin is present,\nit will automatically be used when playing Taiko Mod."; useTaikoSkin.OnCheckChanged += useTaikoSkin_OnCheckChanged; spriteManager.AddRange(useTaikoSkin.SpriteCollection); pAnimation pa = new pAnimation(SkinManager.LoadAll("scorebar-colour"), FieldTypes.Window, OriginTypes.TopLeft, ClockTypes.Game, new Vector2(3, 10), 0.95F, true, Color.White, null); pa.frameSkip = 60 / pa.TextureCount; pa.DrawWidth = 460; spriteManager.Add(pa); pb = new pSprite(SkinManager.Load("scorebar-ki"), OriginTypes.Centre, new Vector2(287, 16), 0.97F, true, Color.White); spriteManager.Add(pb); pb = new pSprite(SkinManager.Load("scorebar-kidanger"), OriginTypes.Centre, new Vector2(160, 16), 0.98F, true, Color.White); spriteManager.Add(pb); pb = new pSprite(SkinManager.Load("scorebar-kidanger2"), OriginTypes.Centre, new Vector2(100, 16), 0.99F, true, Color.White); spriteManager.Add(pb); pb = new pSprite(SkinManager.Load("scorebar-bg"), Vector2.Zero, 0.9F, true, Color.White); spriteManager.Add(pb); pSprite detailsBack = new pSprite(GameBase.WhitePixel, new Vector2(440, 0), 0.2F, true, new Color(0, 0, 0, 160)); detailsBack.VectorScale = new Vector2(320, 768); detailsBack.UseVectorScale = true; spriteManager.Add(detailsBack); currentSkin = new pText(SkinManager.Current.SkinName + " by " + SkinManager.Current.SkinAuthor, 24, new Vector2(0, 30), Vector2.Zero, 1, true, Color.White, true); currentSkin.TextBold = true; currentSkin.FadeInFromZero(1500); spriteManager.Add(currentSkin); }
internal override void Update() { if (AudioEngine.Time < EndTime && IsHit) { Disarm(); } if (IsHit || AudioEngine.Time < StartTime) { return; } double decay = Math.Pow(0.9, InputManager.ElapsedAudioTime / GameBase.SIXTY_FRAME_TIME); rpm = rpm * decay + (1.0 - decay) * (Math.Abs(velocityCurrent) * 1000) / (Math.PI * 2) * 60; if (spriteRpmText != null) { spriteRpmText.Text = string.Format(@"{0:#,0}", rpm); } Player.Instance?.LogSpinSpeed(rpm); if (spriteMiddleTop != null) { spriteMiddleTop.InitialColour = ColourHelper.ColourLerp(Color.White, Color.Red, (float)(AudioEngine.Time - StartTime) / Length); } if (GameBase.Mode == OsuModes.Edit) { editorCircleRotation.EndFloat = (float)(rotationRequirement * Math.PI); floatRotationCount = (float)((AudioEngine.Time - StartTime) / 1000 * hitObjectManager.SpinnerRotationRatio); } else if (AudioEngine.Time < EndTime && AudioEngine.Time > StartTime && !Player.Recovering) { // Mod time is applied here to keep discrepancies between DT, HT and nomod to preserve integrity of older scores. :( double maxAccelThisFrame = HitObjectManager.ApplyModsToTime(maxAccel * InputManager.ElapsedAudioTime, hitObjectManager.ActiveMods); if ((GameBase.Mode == OsuModes.Play && ModManager.CheckActive(hitObjectManager.ActiveMods, Mods.SpunOut)) || Player.Relaxing2) { velocityCurrent = 0.03; } else if (velocityTheoretical > velocityCurrent) { velocityCurrent += Math.Min(velocityTheoretical - velocityCurrent, velocityCurrent < 0 && Player.Relaxing ? maxAccelThisFrame / RELAX_BONUS_ACCEL : maxAccelThisFrame); } else { velocityCurrent += Math.Max(velocityTheoretical - velocityCurrent, velocityCurrent > 0 && Player.Relaxing ? -maxAccelThisFrame / RELAX_BONUS_ACCEL : -maxAccelThisFrame); } velocityCurrent = Math.Max(-0.05, Math.Min(velocityCurrent, 0.05)); float rotationAddition = (float)(velocityCurrent * InputManager.ElapsedAudioTime); float turnRatio = spriteMiddleBottom != null && spriteMiddleBottom.Texture != null ? 0.5f : 1; // We don't want the spinner sprite to spin faster / slower when DT / HT are active. It should always spin proportionally to the cursor spinning rate. SpriteCircleTop.Rotation += (float)HitObjectManager.ApplyModsToTime(rotationAddition * turnRatio, hitObjectManager.ActiveMods); if (spriteMiddleBottom != null) { spriteMiddleBottom.Rotation += (float)HitObjectManager.ApplyModsToTime(rotationAddition, hitObjectManager.ActiveMods); spriteCircleBottom.Rotation = SpriteCircleTop.Rotation / 3f; } if (velocityCurrent != 0) { StartSound(); } else { StopSound(); } floatRotationCount += Math.Abs((float)(rotationAddition / Math.PI)); } updateCompletion(Math.Abs(floatRotationCount) / rotationRequirement * 100); switch (state) { case SpinningState.NotStarted: if (scoringRotationCount == 0 || AudioEngine.Time < StartTime + 500) { break; } spriteSpin?.FadeOut(300); state = SpinningState.Started; break; case SpinningState.Started: if (scoringRotationCount < rotationRequirement) { break; } if (spriteGlow != null) { spriteGlow.InitialColour = new Color(3, 151, 255); } if (spriteSpin != null) { spriteSpin.FadeOut(100); spriteClear.Transformations.Clear(); spriteClear.Transformations.Add(new Transformation(TransformationType.Fade, 0, 1, AudioEngine.Time, Math.Min(EndTime, AudioEngine.Time + 400), EasingTypes.Out)); spriteClear.Transformations.Add(new Transformation(TransformationType.Scale, 2, 0.8f, AudioEngine.Time, Math.Min(EndTime, AudioEngine.Time + 240), EasingTypes.Out)); spriteClear.Transformations.Add(new Transformation(TransformationType.Scale, 0.8f, 1, Math.Min(EndTime, AudioEngine.Time + 240), Math.Min(EndTime, AudioEngine.Time + 400), EasingTypes.None)); spriteClear.Transformations.Add(new Transformation(TransformationType.Fade, 1, 0, EndTime - 50, EndTime)); } state = SpinningState.Passed; break; } }
internal override IncreaseScoreType GetScorePoints(Vector2 currentMousePos) { if (!InputManager.ScorableFrame) { return(0); } //First update the spinner velocity... Vector2 mouseVector = currentMousePos - SpriteCircleTop.drawPosition; double mouseAngle = Math.Atan2(mouseVector.Y, mouseVector.X); double mouseAngleDiff = mouseAngle - lastMouseAngle; if (mouseAngle - lastMouseAngle < -Math.PI) { mouseAngleDiff = (2 * Math.PI) + mouseAngle - lastMouseAngle; } else if (lastMouseAngle - mouseAngle < -Math.PI) { mouseAngleDiff = (-2 * Math.PI) - lastMouseAngle + mouseAngle; } double timeDiff = InputManager.LastScorableFrameTime > 0 ? AudioEngine.Time - InputManager.LastScorableFrameTime : GameBase.SIXTY_FRAME_TIME; double decay = Math.Pow(0.999, timeDiff); totalScoreFrameVariance = decay * totalScoreFrameVariance + (1 - decay) * timeDiff; if (mouseAngleDiff == 0) { velocityTheoretical = zeroCount++ < 1 ? velocityTheoretical / 3 : 0; } else { zeroCount = 0; if (!Player.Relaxing && ((InputManager.leftButton == ButtonState.Released && InputManager.rightButton == ButtonState.Released) || AudioEngine.Time < StartTime || AudioEngine.Time > EndTime)) { mouseAngleDiff = 0; } if (Math.Abs(mouseAngleDiff) < Math.PI) { if (HitObjectManager.ApplyModsToTime(totalScoreFrameVariance, hitObjectManager.ActiveMods) > GameBase.SIXTY_FRAME_TIME * 1.04f) { //after a certain lenience we need to stop allowing for SIXTY_FRAMEs and take frames for their actual elapsed time. //this is to handle the case where users are running at sub-60fps. //in a simple world, we could always use this timeDiff calculation, but due to historical reasons, //we were always slightly in the user's favour when calculating velocity here. velocityTheoretical = mouseAngleDiff / HitObjectManager.ApplyModsToTime(timeDiff, hitObjectManager.ActiveMods); } else { velocityTheoretical = mouseAngleDiff / GameBase.SIXTY_FRAME_TIME; } } else { velocityTheoretical = 0; } } lastMouseAngle = mouseAngle; //If we have actually progressed, let's return some score... if (rotationCount == lastRotationCount) { return(IncreaseScoreType.Ignore); } scoringRotationCount++; IncreaseScoreType score = IncreaseScoreType.Ignore; if (SkinManager.Current.SpinnerFrequencyModulate) { AudioEngine.UpdateSpinSample((float)scoringRotationCount / rotationRequirement); } if (scoringRotationCount > rotationRequirement + 3 && (scoringRotationCount - (rotationRequirement + 3)) % 2 == 0) { if (spriteGlow != null) { spriteGlow.FlashColour(Color.White, 200); } score = IncreaseScoreType.SpinnerBonus; if (!ModManager.CheckActive(Mods.Cinema)) { AudioEngine.PlaySample(@"spinnerbonus", AudioEngine.VolumeSample, SkinSource.All); } SpriteBonusCounter.Text = (1000 * (scoringRotationCount - (rotationRequirement + 3)) / 2).ToString(); SpriteBonusCounter.Transformations.Clear(); SpriteBonusCounter.Transformations.Add(new Transformation(TransformationType.Fade, 1, 0, AudioEngine.Time, AudioEngine.Time + 800)); SpriteBonusCounter.Transformations.Add(new Transformation(TransformationType.Scale, 2f, 1.28f, AudioEngine.Time, AudioEngine.Time + 800)); SpriteBonusCounter.Transformations[0].Easing = EasingTypes.Out; SpriteBonusCounter.Transformations[1].Easing = EasingTypes.Out; //Ensure we don't recycle this too early. SpriteBonusCounter.Transformations.Add(new Transformation(TransformationType.Fade, 0, 0, EndTime + 800, EndTime + 800)); } else if (scoringRotationCount > 1 && scoringRotationCount % 2 == 0) { score = IncreaseScoreType.SpinnerSpinPoints; } else if (scoringRotationCount > 1) { score = IncreaseScoreType.SpinnerSpin; } lastRotationCount = rotationCount; return(score); }
/// <summary> /// Handles an individual key press during gameplay. /// </summary> /// <param name="manager"></param> /// <param name="gameplayHitObject"></param> /// <param name="objectIndex"></param> private void HandleKeyPress(HitObjectManagerKeys manager, GameplayHitObjectKeys gameplayHitObject) { // Play the HitSounds of closest hit object. HitObjectManager.PlayObjectHitSounds(gameplayHitObject.Info); // Get Judgement and references var time = (int)manager.CurrentAudioPosition; var hitDifference = gameplayHitObject.Info.StartTime - time; var judgement = ((ScoreProcessorKeys)Ruleset.ScoreProcessor).CalculateScore(hitDifference, KeyPressType.Press); var lane = gameplayHitObject.Info.Lane - 1; // Ignore Ghost Taps if (judgement == Judgement.Ghost) { return; } // Remove HitObject from Object Pool. Will be recycled/killed as necessary. gameplayHitObject = manager.ActiveNoteLanes[lane].Dequeue(); // Update stats Ruleset.ScoreProcessor.Stats.Add( new HitStat( HitStatType.Hit, KeyPressType.Press, gameplayHitObject.Info, time, judgement, hitDifference, Ruleset.ScoreProcessor.Accuracy, Ruleset.ScoreProcessor.Health )); // Update Scoreboard var view = (GameplayScreenView)Ruleset.Screen.View; view.UpdateScoreboardUsers(); view.UpdateScoreAndAccuracyDisplays(); // Update Playfield var playfield = (GameplayPlayfieldKeys)Ruleset.Playfield; playfield.Stage.ComboDisplay.MakeVisible(); playfield.Stage.HitError.AddJudgement(judgement, gameplayHitObject.Info.StartTime - manager.CurrentAudioPosition); playfield.Stage.JudgementHitBurst.PerformJudgementAnimation(judgement); // Update Object Pooling switch (judgement) { // Handle early miss cases here. case Judgement.Miss when gameplayHitObject.Info.IsLongNote: manager.KillPoolObject(gameplayHitObject); break; // Handle miss cases. case Judgement.Miss: manager.RecyclePoolObject(gameplayHitObject); break; // Handle non-miss cases. Perform Hit Lighting Animation and Handle Object pooling. default: playfield.Stage.HitLightingObjects[lane].PerformHitAnimation(gameplayHitObject.Info.IsLongNote); if (gameplayHitObject.Info.IsLongNote) { manager.ChangePoolObjectStatusToHeld(gameplayHitObject); gameplayHitObject.StartLongNoteAnimation(); } else { manager.RecyclePoolObject(gameplayHitObject); } break; } }