forked from ppy/osu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOsuPlayfield.cs
More file actions
254 lines (207 loc) · 10.4 KB
/
Copy pathOsuPlayfield.cs
File metadata and controls
254 lines (207 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using System.Numerics;
namespace osu.Game.Rulesets.Osu.UI
{
[Cached]
public partial class OsuPlayfield : Playfield
{
private readonly Container borderContainer;
private readonly PlayfieldBorder playfieldBorder;
private readonly ProxyContainer approachCircles;
private readonly ProxyContainer spinnerProxies;
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
private readonly JudgementPooler<DrawableOsuJudgement> judgementPooler;
// For osu! gameplay, everything is always on screen.
// Skipping masking calculations improves performance in intense beatmaps (ie. https://osu.ppy.sh/beatmapsets/150945#osu/372245)
public override bool UpdateSubTreeMasking() => false;
public SmokeContainer Smoke { get; }
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
protected override GameplayCursorContainer? CreateCursor() => new OsuCursorContainer();
public override Quad SkinnableComponentScreenSpaceDrawQuad => playfieldBorder.ScreenSpaceDrawQuad;
private readonly Container judgementAboveHitObjectLayer;
public OsuPlayfield()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
borderContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
},
Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both },
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
};
HitPolicy = new StartTimeOrderedHitPolicy();
AddInternal(judgementPooler = new JudgementPooler<DrawableOsuJudgement>(new[]
{
HitResult.Great,
HitResult.Ok,
HitResult.Meh,
HitResult.Miss,
HitResult.LargeTickHit,
HitResult.SliderTailHit,
HitResult.LargeTickMiss,
HitResult.IgnoreMiss,
}, onJudgementLoaded));
NewResult += onNewResult;
}
private IHitPolicy hitPolicy;
public IHitPolicy HitPolicy
{
get => hitPolicy;
[MemberNotNull(nameof(hitPolicy))]
set
{
hitPolicy = value ?? throw new ArgumentNullException(nameof(value));
hitPolicy.HitObjectContainer = HitObjectContainer;
}
}
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
{
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.CheckHittable;
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
}
private void onDrawableHitObjectLoaded(Drawable drawable)
{
// note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded.
switch (drawable)
{
case DrawableSpinner:
spinnerProxies.Add(drawable.CreateProxy());
break;
case DrawableHitCircle hitCircle:
approachCircles.Add(hitCircle.ProxiedLayer.CreateProxy());
break;
}
}
private void onJudgementLoaded(DrawableOsuJudgement judgement)
{
judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent);
}
[BackgroundDependencyLoader]
private void load(OsuRulesetConfigManager? config, IBeatmap? beatmap)
{
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
var osuBeatmap = (OsuBeatmap?)beatmap;
RegisterPool<HitCircle, DrawableHitCircle>(20, 100);
// handle edge cases where a beatmap has a slider with many repeats.
int maxRepeatsOnOneSlider;
int maxTicksOnOneSlider;
// Use persisted metadata when available to avoid a full beatmap scan.
// Fall back to scanning the loaded beatmap if metadata has not yet been computed.
int persistedMaxRepeats = beatmap?.BeatmapInfo.MaxSliderRepeats ?? -1;
int persistedMaxTicks = beatmap?.BeatmapInfo.MaxSliderTicks ?? -1;
if (persistedMaxRepeats >= 0 && persistedMaxTicks >= 0)
{
maxRepeatsOnOneSlider = persistedMaxRepeats;
maxTicksOnOneSlider = persistedMaxTicks;
}
else
{
// Fallback: scan all sliders when persisted metadata is not available.
maxRepeatsOnOneSlider = 0;
maxTicksOnOneSlider = 0;
if (osuBeatmap != null)
{
foreach (var slider in osuBeatmap.HitObjects.OfType<Slider>())
{
maxRepeatsOnOneSlider = Math.Max(maxRepeatsOnOneSlider, slider.RepeatCount);
maxTicksOnOneSlider = Math.Max(maxTicksOnOneSlider, slider.NestedHitObjects.OfType<SliderTick>().Count());
}
}
}
RegisterPool<Slider, DrawableSlider>(20, 100);
RegisterPool<SliderHeadCircle, DrawableSliderHead>(20, 100);
RegisterPool<SliderTailCircle, DrawableSliderTail>(20, 100);
RegisterPool<SliderTick, DrawableSliderTick>(Math.Max(maxTicksOnOneSlider, 20), Math.Max(maxTicksOnOneSlider, 200));
RegisterPool<SliderRepeat, DrawableSliderRepeat>(Math.Max(maxRepeatsOnOneSlider, 20), Math.Max(maxRepeatsOnOneSlider, 200));
RegisterPool<Spinner, DrawableSpinner>(2, 20);
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 200);
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 200);
if (beatmap != null)
ApplyCircleSizeToPlayfieldBorder(beatmap);
}
protected void ApplyCircleSizeToPlayfieldBorder(IBeatmap beatmap)
{
borderContainer.Padding = new MarginPadding(OsuHitObject.OBJECT_RADIUS * -LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Difficulty.CircleSize, true));
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
protected override void OnHitObjectAdded(HitObject hitObject)
{
base.OnHitObjectAdded(hitObject);
FollowPoints.AddFollowPoints((OsuHitObject)hitObject);
}
protected override void OnHitObjectRemoved(HitObject hitObject)
{
base.OnHitObjectRemoved(hitObject);
FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject);
}
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
hitPolicy.HandleHit(judgedObject);
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
var explosion = judgementPooler.Get(result.Type, doj => doj.Apply(result, judgedObject));
if (explosion == null)
return;
judgementLayer.Add(explosion);
// the proxied content is added to judgementAboveHitObjectLayer once, on first load, and never removed from it.
// ensure that ordering is consistent with expectations (latest judgement should be front-most).
judgementAboveHitObjectLayer.ChangeChildDepth(explosion.ProxiedAboveHitObjectsContent, (float)-result.TimeAbsolute);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
private OsuResumeOverlay.OsuResumeOverlayInputBlocker? resumeInputBlocker;
public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker)
{
Debug.Assert(this.resumeInputBlocker == null);
this.resumeInputBlocker = resumeInputBlocker;
AddInternal(resumeInputBlocker);
}
private partial class ProxyContainer : LifetimeManagementContainer
{
public void Add(Drawable proxy) => AddInternal(proxy);
}
private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry
{
public OsuHitObjectLifetimeEntry(HitObject hitObject)
: base(hitObject)
{
// Prevent past objects in idles states from remaining alive as their end times are skipped in non-frame-stable contexts.
LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss);
}
protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt;
}
}
}