// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class OsuLegacySkinTransformer : LegacySkinTransformer { public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasHitCircle.Value; private readonly Lazy hasHitCircle; /// /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. /// Their hittable area is 128px, but the actual circle portion is 118px. /// We must account for some gameplay elements such as slider bodies, where this padding is not present. /// public const float LEGACY_CIRCLE_RADIUS = 64 - 5; public OsuLegacySkinTransformer(ISkin skin) : base(skin) { hasHitCircle = new Lazy(() => GetTexture("hitcircle") != null); } public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { if (lookup is OsuSkinComponentLookup osuComponent) { switch (osuComponent.Component) { case OsuSkinComponents.FollowPoint: return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false); case OsuSkinComponents.SliderScorePoint: return this.GetAnimation("sliderscorepoint", false, false); case OsuSkinComponents.SliderFollowCircle: var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true); if (followCircleContent != null) return new LegacyFollowCircle(followCircleContent); return null; case OsuSkinComponents.SliderBall: var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); // todo: slider ball has a custom frame delay based on velocity // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); if (sliderBallContent != null) return new LegacySliderBall(sliderBallContent, this); return null; case OsuSkinComponents.SliderBody: if (hasHitCircle.Value) return new LegacySliderBody(); return null; case OsuSkinComponents.SliderTailHitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece("sliderendcircle", false); return null; case OsuSkinComponents.SliderHeadHitCircle: if (hasHitCircle.Value) return new LegacySliderHeadHitCircle(); return null; case OsuSkinComponents.ReverseArrow: if (hasHitCircle.Value) return new LegacyReverseArrow(); return null; case OsuSkinComponents.HitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece(); return null; case OsuSkinComponents.Cursor: if (GetTexture("cursor") != null) return new LegacyCursor(this); return null; case OsuSkinComponents.CursorTrail: if (GetTexture("cursortrail") != null) return new LegacyCursorTrail(this); return null; case OsuSkinComponents.CursorRipple: if (GetTexture("cursor-ripple") != null) { var ripple = this.GetAnimation("cursor-ripple", false, false); // In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible. // If anyone complains about these not being applied, this can be uncommented. // // But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size, // so we might be okay. // // if (ripple != null) // { // ripple.Scale = new Vector2(0.5f); // ripple.Alpha = 0.2f; // } return ripple; } return null; case OsuSkinComponents.CursorParticles: if (GetTexture("star2") != null) return new LegacyCursorParticles(); return null; case OsuSkinComponents.CursorSmoke: if (GetTexture("cursor-smoke") != null) return new LegacySmokeSegment(); return null; case OsuSkinComponents.HitCircleText: if (!this.HasFont(LegacyFont.HitCircle)) return null; return new LegacySpriteText(LegacyFont.HitCircle) { // stable applies a blanket 0.8x scale to hitcircle fonts Scale = new Vector2(0.8f), }; case OsuSkinComponents.SpinnerBody: bool hasBackground = GetTexture("spinner-background") != null; if (GetTexture("spinner-top") != null && !hasBackground) return new LegacyNewStyleSpinner(); else if (hasBackground) return new LegacyOldStyleSpinner(); return null; case OsuSkinComponents.ApproachCircle: return new LegacyApproachCircle(); default: throw new UnsupportedSkinComponentException(lookup); } } return base.GetDrawableComponent(lookup); } public override IBindable? GetConfig(TLookup lookup) { switch (lookup) { case OsuSkinColour colour: return base.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) { case OsuSkinConfiguration.SliderPathRadius: if (hasHitCircle.Value) return SkinUtils.As(new BindableFloat(LEGACY_CIRCLE_RADIUS)); break; case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // HitCircleOverlayAboveNumer (with typo) should still be supported for now. return base.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? base.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; } return base.GetConfig(lookup); } } }