/// <summary> /// Creates a new stage with start position and columns defined by the skin. /// </summary> /// <param name="skin">The skin to use.</param> /// <param name="posY">The vertical position of the stage.</param> /// <param name="height">The height of the stage.</param> /// <param name="minimal">Should we draw sprites?</param> /// <param name="columnOffset">The skin column offset for this stage (used for coop)</param> internal StageMania(SkinMania skin, bool minimal = false, float posY = 0f, float height = 480f, float posX = float.NaN, float widthScale = 1f, int stageIndex = 0, int columnOffset = 0) { Minimal = minimal; Height = height; Skin = skin; this.stageIndex = stageIndex; this.ColumnOffset = columnOffset; if (!minimal) { SpriteManager = new SpriteManager(true); SpriteManagerBelow = new SpriteManager(true); SpriteManagerAbove = new SpriteManager(true); SpriteManagerNotes = new SpriteManager(true); stageHint = new pSprite(Skin.Load(@"StageHint", @"mania-stage-hint", SkinSource), Fields.TopLeft, Origins.CentreLeft, Clocks.Audio, new Vector2(0, HitPosition), 0.62f, true, Color.White); stageHint.Alpha = 0.9f; stageHint.VectorScale = new Vector2(0, 0.9f); stageHint.Scale = 1.6026f; stageHint.FlipVertical = UpsideDown; SpriteManager.Add(stageHint); pSprite p = new pSprite(Skin.Load(@"StageLeft", @"mania-stage-left", SkinSource), Fields.TopLeft, Origins.TopRight, Clocks.Audio, new Vector2(0.05f, Top), 0.8f, true, Color.White); p.VectorScale = new Vector2(HeightRatio, Height / (float)p.Height * 1.6f); SpriteManagerBelow.Add(p); stageRight = new pSprite(Skin.Load(@"StageRight", @"mania-stage-right", SkinSource), Fields.TopLeft, Origins.TopLeft, Clocks.Audio, new Vector2(0.05f, Top), 0.8f, true, Color.White); stageRight.VectorScale = new Vector2(HeightRatio, Height / (float)stageRight.Height * 1.6f); SpriteManagerBelow.Add(stageRight); pTexture[] pt = Skin.LoadAll(@"StageBottom", @"mania-stage-bottom", SkinSource); if (pt != null && pt.Length > 0) { stageBottom = new pAnimation(pt, Fields.TopLeft, FlipOrigin(Origins.BottomCentre), Clocks.Audio, new Vector2(0, ActualBottom), 0.94f, true, Color.White); stageBottom.FrameDelay = 1000 / 60f; stageBottom.Scale = 1.6f; SpriteManagerAbove.Add(stageBottom); } if (Skin.JudgementLine) { judgementLine = new pSprite(GameBase.WhitePixel, Fields.TopLeft, FlipOrigin(Origins.TopLeft), Clocks.Audio, new Vector2(0, HitPosition), 0.62f, true, Skin.GetColour(@"JudgementLine")); judgementLine.Alpha = 0.9f; judgementLine.VectorScale = new Vector2(0, 0.7f); judgementLine.Scale = 1.6026f; SpriteManager.Add(judgementLine); } p = new pSprite(TextureManager.Load("mania-stage-light", SkinSource.Osu), Fields.TopLeft, Origins.BottomRight, Clocks.Audio, new Vector2(0, Top), 0.5f, true, Color.TransparentWhite); p.Rotation = -(float)(Math.PI / 2); p.VectorScale = new Vector2(Height / (float)p.Width, 0.27f) * 1.6f; SpriteManagerBelow.Add(p); waveRight = new pSprite(TextureManager.Load("mania-stage-light", SkinSource.Osu), Fields.TopLeft, Origins.BottomLeft, Clocks.Audio, new Vector2(0, Top), 0.5f, true, Color.TransparentWhite); waveRight.Rotation = (float)(Math.PI / 2); waveRight.VectorScale = new Vector2(Height / (float)waveRight.Width, 0.27f) * 1.6f; SpriteManagerBelow.Add(waveRight); hitScore = new pAnimation(null, Fields.TopLeft, Origins.Centre, Clocks.Audio, new Vector2(0, ScaleFlipPosition(Skin.ScorePosition)), 0.998f, true, Color.White); hitScore.FrameDelay = 1000 / 20f; SpriteManagerAbove.Add(hitScore); foreach (string hitType in new[] { @"0", @"50", @"100", @"200", @"300", @"300g" }) { hitTextures.Add(hitType, Skin.LoadAll($@"Hit{hitType}", $@"mania-hit{hitType}", SkinSource)); } if (hasHiddenSprites) { bool fromKeyLayer = ModManager.CheckActive(Player.currentScore.EnabledMods, Mods.Hidden); pTexture hiddenSprite = TextureManager.Load(@"mania-stage-hidden", SkinSource.Osu); if (hiddenSprite == null) { GameBase.ChangeMode(OsuModes.Menu); } if (UpsideDown) { hiddenMask = new pSprite(GameBase.WhitePixel, Fields.TopLeft, fromKeyLayer ? Origins.TopLeft : Origins.BottomLeft, Clocks.Audio, new Vector2(0, fromKeyLayer ? HitPosition : 480), 0.81f, true, Color.Black); hiddenFade = new pSprite(hiddenSprite, Fields.TopLeft, fromKeyLayer ? Origins.TopLeft : Origins.BottomLeft, Clocks.Audio, new Vector2(0, fromKeyLayer ? (HitPosition + 160 / 1.6f) : (320 / 1.6f)), 0.81f, true, Color.TransparentWhite); } else { hiddenMask = new pSprite(GameBase.WhitePixel, Fields.TopLeft, fromKeyLayer ? Origins.BottomLeft : Origins.TopLeft, Clocks.Audio, new Vector2(0, fromKeyLayer ? HitPosition : 0), 0.81f, true, Color.Black); hiddenFade = new pSprite(hiddenSprite, Fields.TopLeft, fromKeyLayer ? Origins.BottomLeft : Origins.TopLeft, Clocks.Audio, new Vector2(0, fromKeyLayer ? (HitPosition - 160 / 1.6f) : (160 / 1.6f)), 0.81f, true, Color.TransparentWhite); } SpriteManagerAbove.Add(hiddenMask); SpriteManagerAbove.Add(hiddenFade); } } //We must set these later as they position the spritemanagers Left = float.IsNaN(posX) ? Skin.ColumnStart : posX; Top = posY; //Scale the width of columns, column spacing, and stage separation to fit everything on the screen //NOTE: This logic assumes there are a maximim of two stages if (!minimal && columnOffset == 0) { //Flip left and right offsets if they overlap float left = Math.Min(Left, GameBase.WindowManager.WidthScaled - Skin.ColumnRight); float right = Math.Min(Skin.ColumnRight, GameBase.WindowManager.WidthScaled - Left); Left = left; //resizeWidth is the width of everything we're scaling that is to the right of "Left" float resizeWidth = 0; for (int i = 0; i < Skin.Columns; i++) { resizeWidth += Skin.ColumnWidth[i] + (i > 0 ? Skin.ColumnSpacing[i - 1] : 0); } resizeWidth += Skin.SplitStages && SecondaryStageColumns != -1 ? Skin.StageSeparation : 0; float totalWidth = Left + resizeWidth + right; float screenOverflow = Math.Max(0, totalWidth - GameBase.WindowManager.WidthScaled); widthScale = (resizeWidth - screenOverflow) / resizeWidth; } this.widthScale = widthScale; for (int i = columnOffset; i < columnOffset + PrimaryStageColumns; i++) { Columns.Add(new ColumnMania(this, i, widthScale)); } if (!minimal) { //Set the visible area of the notes now that we know the width of the columns SpriteManagerNotes.SetVisibleArea(new RectangleF(Left, Top, Width, Height)); SpriteManagerNotes.ForwardPlayOptimisations = true; } if (!minimal) { stageHint.VectorScale.X = Width / stageHint.Width; stageRight.Position.X += Width; if (stageBottom != null) { stageBottom.Position.X += Width / 2f; } if (judgementLine != null) { judgementLine.VectorScale.X = Width; } waveRight.Position.X += Width; hitScore.Position.X += Width / 2f; //Scale is used for sprite transformations, so we use vectorscale instead if (SkinManager.Current.Version >= 2.4 || HeightRatio < 1f) { hitScore.VectorScale = Vector2.One * MinimumRatio; } //Hidden masks if (hasHiddenSprites) { bool fromKeyLayer = ModManager.CheckActive(Player.currentScore.EnabledMods, Mods.Hidden); hiddenMask.VectorScale = new Vector2(Width * 1.6f, 160); hiddenMask.Alpha = 0; hiddenFade.VectorScale = new Vector2(Width * 1.6f, 180 / 250f); if ((!fromKeyLayer && !UpsideDown) || (fromKeyLayer && UpsideDown)) { hiddenFade.FlipVertical = true; } hiddenFade.Alpha = 0; } if (SecondaryStageColumns == -1) { HPBar = new HpBarMania(this); } } if (SecondaryStageColumns != -1) { posX = Left + Width + Skin.StageSeparation * widthScale; SecondaryStage = new StageMania(Skin, minimal, posY, height, posX, widthScale, stageIndex + 1, columnOffset + PrimaryStageColumns); Columns.AddRange(SecondaryStage.Columns); HPBar = SecondaryStage.HPBar; } bindColumnKeys(); }
internal void LoadKeys(bool setDefaultSplitStages = false) { currentBindingColumn = 0; spriteManager.SpriteList.FindAll(s => s.Tag == this).ForEach(s => { s.FadeOut(50); s.AlwaysDraw = false; spriteManager.Remove(s); }); disposeManiaStage(); SkinMania skin = SkinManager.LoadManiaSkin(currentKeyConfig); cSplitLayout.Enabled = currentKeyConfig > 1 && skin.SplitStagesFromSkin == null; if (!setDefaultSplitStages) { skin.SplitStages = cSplitLayout.Checked; } cSplitLayout.SetStatusQuietly(skin.SplitStages); dKeyConfig.SpriteMainBox.Text = getKeyOptionText(currentKeyConfig, skin.SplitStages); stage = new StageMania(skin, true); //Validate the layout by recopying it to itself stage.CopyBindingToLayout(stage.LayoutList.Layout); dSpecialStyle.Enabled = stage.AllowSpecialStyle; switch (stage.SpecialStyle) { case ManiaSpecialStyle.Left: dSpecialStyle.SetSelected(1, true); break; case ManiaSpecialStyle.Right: dSpecialStyle.SetSelected(2, true); break; default: dSpecialStyle.SetSelected(0, true); break; } cBindAlternateKeys.Enabled = stage.SpecialStyle != ManiaSpecialStyle.None; if (stage.SpecialStyle == ManiaSpecialStyle.None) { cBindAlternateKeys.SetStatusQuietly(false); } cJudgementLine.SetStatusQuietly(skin.JudgementLine); cUpsideDown.SetStatusQuietly(skin.UpsideDown); float delta = 40f; float separation = skin.SplitStages ? 1f : 0f; float totalWidth = (currentKeyConfig + separation) * delta; //Scale down if the keys are too wide to fit on the screen float scale = Math.Min(1f, GameBase.WindowManager.WidthScaled / totalWidth); totalWidth *= scale; delta *= scale; float start = (GameBase.WindowManager.WidthScaled - totalWidth) / 2; arrow = new pSprite(TextureManager.LoadFirstAvailable(new[] { @"arrow-generic", @"play-warningarrow" }, SkinSource.Osu), Fields.TopLeft, Origins.CentreLeft, Clocks.Game, new Vector2(0, 100), 0.92f, true, GameBase.NewGraphicsAvailable ? Color.SkyBlue : Color.White); arrow.Tag = this; arrow.Rotation = OsuMathHelper.PiOver2; spriteManager.Add(arrow); keys = new pText[currentKeyConfig]; int layoutIndex = 0; for (int i = 0; i < currentKeyConfig; i++) { bool skip = false; if (cBindAlternateKeys.Checked) { skip = true; foreach (StageMania s in stage) { if (i == s.Columns[s.SpecialColumn]) { skip = false; } } } if (!skip) { pSprite p = new pSprite(TextureManager.Load(@"mania-key1D", SkinSource.Osu), Fields.TopLeft, Origins.TopLeft, Clocks.Game, new Vector2(start, 192), 0.92f, true, stage.Columns[i].Colour); p.VectorScale = 1.3f * new Vector2(scale, 1f); p.Tag = this; spriteManager.Add(p); keys[i] = new pText(getKeyText(i), Math.Max(1, (int)Math.Round(14 * scale)), new Vector2(start + delta / 2f, 192 + 40), 0.93f, true, Color.White); keys[i].TextBorder = true; keys[i].Origin = Origins.Centre; keys[i].Tag = this; keys[i].TagNumeric = layoutIndex; spriteManager.Add(keys[i]); //Found the first non-skipped key if (layoutIndex++ == 0) { currentBindingColumn = i; arrow.Position.X = start + delta / 2; } } start += delta + delta * (i == stage.PrimaryStageColumns - 1 ? separation : 0); } }