Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModCenteredCursor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;

namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModCenteredCursor : OsuModTestScene
{
[Test]
public void TestOsuModCenteredCursor() => CreateModTest(new ModTestData
{
Mod = new OsuModCenteredCursor(),
Autoplay = true,
PassCondition = () => true
});
}
}
3 changes: 2 additions & 1 deletion osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class OsuModAutopilot : Mod, IUpdatableByPlayfield, IApplicableToDrawable
typeof(ModAutoplay),
typeof(OsuModMagnetised),
typeof(OsuModRepel),
typeof(ModTouchDevice)
typeof(ModTouchDevice),
typeof(OsuModCenteredCursor)
};

private OsuInputManager inputManager = null!;
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public partial class OsuModBubbles : Mod, IApplicableToDrawableRuleset<OsuHitObj
public override ModType Type => ModType.Fun;

// Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect
public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModCenteredCursor) };

private PlayfieldAdjustmentContainer bubbleContainer = null!;

Expand Down
104 changes: 104 additions & 0 deletions osu.Game.Rulesets.Osu/Mods/OsuModCenteredCursor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;

namespace osu.Game.Rulesets.Osu.Mods
{
public partial class OsuModCenteredCursor : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
{
public override string Name => "Centered Cursor";
public override LocalisableString Description => "Cursor stays in the middle!";
public override double ScoreMultiplier => 1;
public override string Acronym => "CC";
public override ModType Type => ModType.Fun;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModFlashlight), typeof(OsuModAutopilot), typeof(OsuModRelax), typeof(OsuModBubbles), typeof(ModTouchDevice) }).ToArray();

private OsuInputManager osuInputManager = null!;
private bool hasReplay;
private ExternalMousePosGetter externalMousePosGetter = null!;

public void Update(Playfield playfield)
{
externalMousePosGetter.Enable = !hasReplay;

// The coords of the cursor in playfield local space
Vector2 osuPos;

// If it's a replay we don't need to do mouse conversion
if (hasReplay)
{
osuPos = playfield.Cursor!.ActiveCursor.Position;
}
else
{
var mousePos = externalMousePosGetter.MousePos;

// We convert the coords using the playfield parent because the playfield is moving so the values would be wrong
osuPos = playfield.Parent!.ToLocalSpace(mousePos);

new ConvertedMousePositionAbsoluteInput { Position = playfield.ToScreenSpace(osuPos) }.Apply(osuInputManager.CurrentState, osuInputManager);
}

playfield.Position = playfield.LayoutSize / 2 - osuPos;
}

public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
// Grab the input manager for future use
osuInputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager;

// Added this way, ExternalMousePosGetter receives OnMouseMove before the playfield drawables, so it can block propagation to the playfield
drawableRuleset.PlayfieldAdjustmentContainer.Add(externalMousePosGetter = new ExternalMousePosGetter { RelativeSizeAxes = Axes.Both });

// Reset playfield position while paused so the resume overlay reads the real cursor position correctly.
// This avoids the resume overlay forcing the user to move the mouse to the center, which would cause a cursor jump/teleportation when resuming.
drawableRuleset.IsPaused.BindValueChanged(p =>
{
if (p.NewValue) drawableRuleset.Playfield.Position = Vector2.Zero;
});
}

public void ApplyToPlayer(Player player)
{
if (osuInputManager.ReplayInputHandler != null) hasReplay = true;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could have been done in ApplyToDrawableRuleset() but I just done the same thing that OsuModRelax :

https://github.com/ppy/osu/blob/master/osu.Game.Rulesets.Osu%2FMods%2FOsuModRelax.cs#L55-L71

}

private class ConvertedMousePositionAbsoluteInput : MousePositionAbsoluteInput;

private partial class ExternalMousePosGetter : Drawable, IRequireHighFrequencyMousePosition
{
public bool Enable = true;
public Vector2 MousePos { get; private set; } = Vector2.Zero;

public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;

protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!Enable)
return base.OnMouseMove(e);

// We skip our own added mouse position
if (e.CurrentState.Mouse.LastSource is ConvertedMousePositionAbsoluteInput)
return base.OnMouseMove(e);

MousePos = e.ScreenSpaceMousePosition;

// We block real mouse position propagation to the playfield
return true;
}
}
}
}
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public partial class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObject
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModBloom), typeof(OsuModBlinds) }).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModBloom), typeof(OsuModBlinds), typeof(OsuModCenteredCursor) }).ToArray();

private const double default_follow_delay = 120;

Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawabl
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";

public override Type[] IncompatibleMods =>
base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap), typeof(OsuModCenteredCursor) }).ToArray();

/// <summary>
/// How early before a hitobject's start time to trigger a hit.
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModTouchDevice : ModTouchDevice
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModBloom) }).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModBloom), typeof(OsuModCenteredCursor) }).ToArray();
public override bool Ranked => UsesDefaultConfiguration;
}
}
3 changes: 2 additions & 1 deletion osu.Game.Rulesets.Osu/OsuRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new OsuModBubbles(),
new OsuModSynesthesia(),
new OsuModDepth(),
new OsuModBloom()
new OsuModBloom(),
new OsuModCenteredCursor()
};

case ModType.System:
Expand Down
Loading