Skip to content

Commit fb54200

Browse files
committed
proper hit detection and tick notes
1 parent db046e2 commit fb54200

9 files changed

Lines changed: 189 additions & 26 deletions

File tree

‎fluXis/Modes/Gameplay/Input/GameModeKeybindContainer.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public abstract partial class GameModeKeybindContainer<T> : KeyBindingContainer<
2222
private readonly Dictionary<int, T> intToActionMap;
2323
private readonly Dictionary<T, int> actionToIntMap;
2424

25-
protected GameModeKeybindContainer()
25+
protected GameModeKeybindContainer(SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.Unique, KeyCombinationMatchingMode matchingMode = KeyCombinationMatchingMode.Any)
26+
: base(simultaneousMode, matchingMode)
2627
{
2728
intToActionMap = Enum.GetValues<T>().ToDictionary(x => Convert.ToInt32(x), x => x);
2829
actionToIntMap = Enum.GetValues<T>().ToDictionary(x => x, x => Convert.ToInt32(x));

‎fluXis/Modes/Gameplay/Objects/DrawableHitObject.cs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public abstract partial class DrawableHitObject : CompositeDrawable
2424
protected double TimeDelta => Object.Time - Time.Current;
2525
public abstract bool CanBeRemoved { get; }
2626

27-
public bool Judged { get; private set; }
27+
public bool Judged { get; protected set; }
2828
public HitWindows HitWindows => hitWindowLazy.Value;
2929
public Action<DrawableHitObject, double>? OnHit { get; set; }
3030

@@ -35,6 +35,25 @@ protected DrawableHitObject(HitObject o)
3535
Object = o;
3636
hitWindowLazy = new Lazy<HitWindows>(() => Ruleset.PlayableMode.CreateHitWindowFor(Object));
3737
}
38+
39+
protected bool UpdateJudgement(bool byUser)
40+
{
41+
if (Judged)
42+
return false;
43+
44+
CheckJudgement(byUser, TimeDelta);
45+
return Judged;
46+
}
47+
48+
protected virtual void CheckJudgement(bool byUser, double offset) { }
49+
50+
protected void ApplyResult(double diff)
51+
{
52+
if (Judged) throw new InvalidOperationException("Can not apply judgement to already judged HitObject.");
53+
54+
Judged = true;
55+
OnHit?.Invoke(this, diff);
56+
}
3857
}
3958

4059
public abstract partial class DrawableHitObject<T> : DrawableHitObject

‎fluXis/Modes/Gameplay/Objects/GameModeHitObjectManager.cs‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,15 @@ protected override void Update()
6666

6767
while (FutureObjects is { Count: > 0 } && (ShouldBeRendered(FutureObjects[0]) || ActiveObjects.Count < MinimumLoadedHitObject))
6868
{
69-
var hit = createObject(FutureObjects[0]);
69+
var hit = FutureObjects[0];
70+
var draw = createObject(hit);
7071

7172
FutureObjects.RemoveAt(0);
7273

73-
if (hit is null) // this will break stuff 100%, but it won't freeze the game
74-
PastObjects.Push(FutureObjects[0]);
74+
if (draw is null) // this will break stuff 100%, but it won't freeze the game
75+
PastObjects.Push(hit);
7576
else
76-
ActiveObjects.Add(hit);
77+
ActiveObjects.Add(draw);
7778
}
7879

7980
while (ActiveObjects.Count > 0 && !ShouldBeRendered(ActiveObjects[0].Object) && ActiveObjects.Count > MinimumLoadedHitObject)
@@ -82,7 +83,7 @@ protected override void Update()
8283
removeObject(hit, true);
8384
}
8485

85-
foreach (var hitObject in ActiveObjects.Where(h => h.CanBeRemoved).Reverse().ToList())
86+
foreach (var hitObject in ActiveObjects.Where(h => h.CanBeRemoved).ToList())
8687
removeObject(hitObject);
8788

8889
while (Ruleset.AllowReverting && PastObjects.Count > 0)

‎fluXis/Modes/Keys/Gameplay/KeysKeybindContainer.cs‎

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,22 @@ public partial class KeysKeybindContainer : GameModeKeybindContainer<FluXisGamep
1313
{
1414
public override IEnumerable<IKeyBinding> DefaultKeyBindings { get; }
1515

16-
public bool[] Pressed { get; }
1716
public double[] PressTimes { get; }
1817
public FluXisGameplayKeybind[] Keys { get; }
1918

2019
public KeysKeybindContainer(int mode, bool dual)
2120
{
22-
DefaultKeyBindings = GetKeys(mode, dual);
23-
Keys = [.. DefaultKeyBindings.Select(x => (FluXisGameplayKeybind)x.Action)];
24-
25-
if (dual)
26-
mode *= 2;
27-
28-
Pressed = new bool[mode];
29-
PressTimes = new double[mode];
21+
var binds = DefaultKeyBindings = GetKeys(mode, dual);
22+
Keys = [.. binds.Select(x => (FluXisGameplayKeybind)x.Action)];
23+
PressTimes = new double[Keys.Length];
3024
}
3125

3226
protected override Drawable PropagatePressed(IEnumerable<Drawable> drawables, InputState state, FluXisGameplayKeybind pressed, float scrollAmount = 0, bool isPrecise = false, bool repeat = false)
3327
{
3428
var idx = Array.IndexOf(Keys, pressed);
3529
if (idx == -1) return null;
3630

37-
Pressed[idx] = true;
3831
PressTimes[idx] = Time.Current;
39-
4032
return base.PropagatePressed(drawables, state, pressed, scrollAmount, isPrecise, repeat);
4133
}
4234

@@ -45,9 +37,7 @@ protected override void PropagateReleased(IEnumerable<Drawable> drawables, Input
4537
var idx = Array.IndexOf(Keys, released);
4638
if (idx == -1) return;
4739

48-
Pressed[idx] = false;
4940
PressTimes[idx] = 0;
50-
5141
base.PropagateReleased(drawables, state, released);
5242
}
5343

@@ -73,7 +63,7 @@ public static IEnumerable<KeyBinding> GetKeys(int mode, bool dual)
7363
}
7464
}
7565
else
76-
binds = Enum.GetValues<FluXisGameplayKeybind>().ToList();
66+
binds = [.. Enum.GetValues<FluXisGameplayKeybind>()];
7767

7868
return binds.Select(b => new KeyBinding(GetDefaultFor(b), b));
7969
}

‎fluXis/Modes/Keys/Gameplay/Objects/KeysHitObjectManager.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace fluXis.Modes.Keys.Gameplay.Objects;
1616

1717
public partial class KeysHitObjectManager : GameModeHitObjectManager
1818
{
19-
protected ScrollGroup DefaultScrollGroup { get; init; }
19+
public ScrollGroup DefaultScrollGroup { get; init; }
2020

2121
public float ScrollSpeed
2222
{
@@ -67,6 +67,7 @@ protected override bool ShouldBeRendered(HitObject obj)
6767
{
6868
TimingLine line => new KeysDrawableTimingLine(line),
6969
{ Type: HitObjectType.Normal, LongNote: false } => new DrawableNote(obj),
70+
{ Type: HitObjectType.Tick } => new DrawableTick(obj),
7071
_ => null
7172
};
7273

‎fluXis/Modes/Keys/HitObjects/DrawableLandmine.cs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using fluXis.Input;
32
using fluXis.Map.Structures;
43
using fluXis.Scoring;
@@ -30,7 +29,7 @@ private bool didNotGetHit
3029
[Resolved]
3130
private GameplayInput input { get; set; }
3231

33-
private bool isBeingHeld => Keybinds.Pressed[Array.IndexOf(Keybinds.Keys, Keybind)];
32+
private bool isBeingHeld => Keybinds.PressedActions.Contains(Keybind);
3433

3534
// next non-landmine HitObject on the column. Only set if the landmine is in the hit window of the next note, null otherwise.
3635
private HitObject nextNote;

‎fluXis/Modes/Keys/Map/Objects/Drawable/DrawableKeysHitObject.cs‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
using System;
2+
using fluXis.Input;
13
using fluXis.Map.Structures;
24
using fluXis.Modes.Gameplay.Objects;
5+
using fluXis.Modes.Keys.Gameplay;
36
using fluXis.Modes.Keys.Gameplay.Objects;
7+
using osu.Framework.Allocation;
48
using osu.Framework.Graphics;
59

610
namespace fluXis.Modes.Keys.Map.Objects.Drawable;
@@ -12,6 +16,9 @@ public abstract partial class DrawableKeysHitObject<T> : DrawableHitObject<T>
1216
{
1317
protected new KeysHitObjectManager Manager => (KeysHitObjectManager)base.Manager;
1418

19+
[Resolved]
20+
protected KeysKeybindContainer Keybinds { get; private set; } = null!;
21+
1522
protected int PlayerLane
1623
{
1724
get
@@ -25,6 +32,10 @@ protected int PlayerLane
2532
}
2633
}
2734

35+
public FluXisGameplayKeybind Action { get; set; }
36+
37+
protected double ScrollVelocityTime { get; private set; }
38+
2839
protected DrawableKeysHitObject(T o)
2940
: base(o)
3041
{
@@ -34,10 +45,19 @@ protected DrawableKeysHitObject(T o)
3445
Masking = true;
3546
}
3647

48+
[BackgroundDependencyLoader]
49+
private void load()
50+
{
51+
Action = Keybinds.Keys[Math.Clamp(Object.Lane - 1, 0, Keybinds.Keys.Length - 1)];
52+
53+
var group = Object.ScrollGroup ?? Manager.DefaultScrollGroup;
54+
ScrollVelocityTime = group.PositionFromTime(Object.Time);
55+
}
56+
3757
protected override void Update()
3858
{
3959
base.Update();
4060

41-
Y = Manager.PositionAtTime(Object.Time);
61+
Y = Manager.PositionAtTime(ScrollVelocityTime, Object.ScrollGroup);
4262
}
4363
}

‎fluXis/Modes/Keys/Map/Objects/Drawable/DrawableNote.cs‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
using fluXis.Input;
12
using fluXis.Map.Structures;
23
using osu.Framework.Allocation;
34
using osu.Framework.Graphics;
5+
using osu.Framework.Input.Bindings;
6+
using osu.Framework.Input.Events;
47

58
namespace fluXis.Modes.Keys.Map.Objects.Drawable;
69

710
#nullable enable
811

9-
public partial class DrawableNote : DrawableKeysHitObject<HitObject>
12+
public partial class DrawableNote : DrawableKeysHitObject<HitObject>, IKeyBindingHandler<FluXisGameplayKeybind>
1013
{
1114
public override bool CanBeRemoved => Judged || Time.Current - Object.Time > HitWindows.TimingFor(HitWindows.LowestHitable);
1215

@@ -20,4 +23,23 @@ private void load()
2023
{
2124
InternalChild = Skin.GetHitObject(PlayerLane, Manager.KeyCount).With(d => d.RelativeSizeAxes = Axes.X);
2225
}
26+
27+
protected override void CheckJudgement(bool byUser, double offset)
28+
{
29+
if (!byUser)
30+
{
31+
ApplyResult(HitWindows.TimingFor(HitWindows.Lowest));
32+
return;
33+
}
34+
35+
if (!HitWindows.CanBeHit(offset))
36+
return;
37+
38+
ApplyResult(offset);
39+
}
40+
41+
public bool OnPressed(KeyBindingPressEvent<FluXisGameplayKeybind> e)
42+
=> e.Action == Action && UpdateJudgement(true);
43+
44+
public void OnReleased(KeyBindingReleaseEvent<FluXisGameplayKeybind> e) { }
2345
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using fluXis.Input;
3+
using fluXis.Map.Structures;
4+
using fluXis.Scoring.Enums;
5+
using fluXis.Utils.Extensions;
6+
using osu.Framework.Allocation;
7+
using osu.Framework.Graphics;
8+
using osu.Framework.Input.Bindings;
9+
using osu.Framework.Input.Events;
10+
11+
namespace fluXis.Modes.Keys.Map.Objects.Drawable;
12+
13+
#nullable enable
14+
15+
public partial class DrawableTick : DrawableKeysHitObject<HitObject>, IKeyBindingHandler<FluXisGameplayKeybind>
16+
{
17+
public override bool CanBeRemoved => Judged || wouldMiss;
18+
private bool wouldMiss => Time.Current - Object.Time > HitWindows.TimingFor(HitWindows.LowestHitable);
19+
20+
private int actionIndex;
21+
22+
private bool heldDown;
23+
private bool directHit = false;
24+
private double? holdStartTime;
25+
26+
public DrawableTick(HitObject o)
27+
: base(o)
28+
{
29+
}
30+
31+
[BackgroundDependencyLoader]
32+
private void load()
33+
{
34+
InternalChild = Skin.GetTickNote(PlayerLane, Manager.KeyCount, Object.HoldTime > 0).WithRelativeSize(Axes.X);
35+
actionIndex = Array.IndexOf(Keybinds.Keys, Action);
36+
}
37+
38+
protected override void Update()
39+
{
40+
base.Update();
41+
42+
heldDown = Keybinds.PressedActions.Contains(Action);
43+
holdStartTime = heldDown ? Keybinds.PressTimes[actionIndex] : null;
44+
45+
if (heldDown) UpdateJudgement(true);
46+
}
47+
48+
protected override void CheckJudgement(bool byUser, double offset)
49+
{
50+
if (!byUser)
51+
{
52+
ApplyResult(lagCompensation() ?? HitWindows.TimingFor(HitWindows.Lowest));
53+
return;
54+
}
55+
56+
if (offset >= 0 && !directHit)
57+
return;
58+
59+
if (wouldMiss)
60+
{
61+
var off = lagCompensation();
62+
63+
if (off != null)
64+
{
65+
ApplyResult(off.Value);
66+
return;
67+
}
68+
}
69+
70+
// ObjectManager.PlayHitSound(Data, false);
71+
ApplyResult(lagCompensation() ?? offset);
72+
return;
73+
74+
double? lagCompensation()
75+
{
76+
if (heldDown && holdStartTime != null)
77+
{
78+
var delta = holdStartTime.Value - Object.Time;
79+
return delta < 0 ? 0 : delta;
80+
}
81+
82+
return null;
83+
}
84+
}
85+
86+
public bool OnPressed(KeyBindingPressEvent<FluXisGameplayKeybind> e)
87+
{
88+
if (e.Action != Action)
89+
return false;
90+
91+
var flWindow = HitWindows.TimingFor(Judgement.Flawless);
92+
93+
if (Math.Abs(TimeDelta) < flWindow)
94+
{
95+
directHit = true;
96+
UpdateJudgement(true);
97+
return true;
98+
}
99+
100+
return false;
101+
}
102+
103+
public void OnReleased(KeyBindingReleaseEvent<FluXisGameplayKeybind> e)
104+
{
105+
if (e.Action != Action)
106+
return;
107+
108+
UpdateJudgement(true);
109+
}
110+
}

0 commit comments

Comments
 (0)