1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 05:02:55 +08:00

Rework settings; Add seed to ScorePanel; Apply requested changes from @bdach

This commit is contained in:
Pasi4K5 2021-05-12 18:11:50 +02:00
parent 6bed268bd8
commit 946abfbb83
5 changed files with 152 additions and 122 deletions

View File

@ -35,68 +35,46 @@ namespace osu.Game.Rulesets.Osu.Mods
private const byte border_distance_x = 192;
private const byte border_distance_y = 144;
private static readonly Bindable<int> seed = new Bindable<int>
[SettingSource("Custom seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))]
public Bindable<int?> CustomSeed { get; } = new Bindable<int?>
{
Default = -1
Default = null,
Value = null
};
private static readonly BindableBool random_seed = new BindableBool
public void ApplyToBeatmap(IBeatmap beatmap)
{
Value = true,
Default = true
};
[SettingSource("Random seed", "Generate a random seed for the beatmap generation")]
public BindableBool RandomSeed => random_seed;
[SettingSource("Seed", "Seed for the random beatmap generation", SettingControlType = typeof(OsuModRandomSettingsControl))]
public Bindable<int> Seed => seed;
internal static bool CustomSeedDisabled => random_seed.Value;
public OsuModRandom()
{
if (seed.Default != -1)
if (!(beatmap is OsuBeatmap osuBeatmap))
return;
var random = RNG.Next();
seed.Value = random;
seed.Default = random;
seed.BindValueChanged(e => seed.Default = e.NewValue);
}
var seed = RNG.Next();
public void ApplyToBeatmap(IBeatmap iBeatmap)
{
if (!(iBeatmap is OsuBeatmap beatmap))
return;
if (CustomSeed.Value != null)
seed = (int)CustomSeed.Value;
if (RandomSeed.Value)
seed.Value = RNG.Next();
Seed = seed;
var rng = new Random(seed.Value);
var rng = new Random(seed);
// Absolute angle
float prevAngleRad = 0;
// Absolute positions
Vector2 prevPosUnchanged = beatmap.HitObjects[0].Position;
Vector2 prevPosChanged = beatmap.HitObjects[0].Position;
var prevObjectInfo = new HitObjectInfo
{
AngleRad = 0,
PosUnchanged = osuBeatmap.HitObjects[0].Position,
PosChanged = osuBeatmap.HitObjects[0].Position
};
// rateOfChangeMultiplier changes every i iterations to prevent shaky-line-shaped streams
byte i = 3;
float rateOfChangeMultiplier = 0;
foreach (var beatmapHitObject in beatmap.HitObjects)
foreach (var currentHitObject in osuBeatmap.HitObjects)
{
if (!(beatmapHitObject is OsuHitObject hitObject))
return;
// posUnchanged: position from the original beatmap (not randomised)
var posUnchanged = hitObject.EndPosition;
var posChanged = Vector2.Zero;
// Angle of the vector pointing from the last to the current hit object
float angleRad = 0;
var currentObjectInfo = new HitObjectInfo
{
AngleRad = 0,
PosUnchanged = currentHitObject.EndPosition,
PosChanged = Vector2.Zero
};
if (i >= 3)
{
@ -104,24 +82,23 @@ namespace osu.Game.Rulesets.Osu.Mods
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
}
if (hitObject is HitCircle circle)
if (currentHitObject is HitCircle circle)
{
var distanceToPrev = Vector2.Distance(posUnchanged, prevPosUnchanged);
var distanceToPrev = Vector2.Distance(currentObjectInfo.PosUnchanged, prevObjectInfo.PosUnchanged);
circle.Position = posChanged = getRandomisedPosition(
getObjectInfo(
rateOfChangeMultiplier,
prevPosChanged,
prevAngleRad,
prevObjectInfo,
distanceToPrev,
out angleRad
ref currentObjectInfo
);
circle.Position = currentObjectInfo.PosChanged;
}
// TODO: Implement slider position randomisation
prevAngleRad = angleRad;
prevPosUnchanged = posUnchanged;
prevPosChanged = posChanged;
prevObjectInfo = currentObjectInfo;
i++;
}
}
@ -130,12 +107,11 @@ namespace osu.Game.Rulesets.Osu.Mods
/// Returns the final position of the hit object
/// </summary>
/// <returns>Final position of the hit object</returns>
private Vector2 getRandomisedPosition(
private void getObjectInfo(
float rateOfChangeMultiplier,
Vector2 prevPosChanged,
float prevAngleRad,
HitObjectInfo prevObjectInfo,
float distanceToPrev,
out float newAngle)
ref HitObjectInfo currentObjectInfo)
{
// The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
// is proportional to the distance between the last and the current hit object
@ -143,19 +119,19 @@ namespace osu.Game.Rulesets.Osu.Mods
var maxDistance = OsuPlayfield.BASE_SIZE.LengthFast;
var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / maxDistance;
newAngle = (float)randomAngleRad + prevAngleRad;
if (newAngle < 0)
newAngle += 2 * (float)Math.PI;
currentObjectInfo.AngleRad = (float)randomAngleRad + prevObjectInfo.AngleRad;
if (currentObjectInfo.AngleRad < 0)
currentObjectInfo.AngleRad += 2 * (float)Math.PI;
var posRelativeToPrev = new Vector2(
distanceToPrev * (float)Math.Cos(newAngle),
distanceToPrev * (float)Math.Sin(newAngle)
distanceToPrev * (float)Math.Cos(currentObjectInfo.AngleRad),
distanceToPrev * (float)Math.Sin(currentObjectInfo.AngleRad)
);
posRelativeToPrev = getRotatedVector(prevPosChanged, posRelativeToPrev);
posRelativeToPrev = getRotatedVector(prevObjectInfo.PosChanged, posRelativeToPrev);
newAngle = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
var position = Vector2.Add(prevPosChanged, posRelativeToPrev);
currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
var position = Vector2.Add(prevObjectInfo.PosChanged, posRelativeToPrev);
// Move hit objects back into the playfield if they are outside of it,
// which would sometimes happen during big jumps otherwise.
@ -169,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Mods
else if (position.Y > OsuPlayfield.BASE_SIZE.Y)
position.Y = OsuPlayfield.BASE_SIZE.Y;
return position;
currentObjectInfo.PosChanged = position;
}
/// <summary>
@ -214,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return rotateVectorTowardsVector(
posRelativeToPrev,
Vector2.Subtract(playfieldMiddle, prevPosChanged),
relativeRotationDistance
relativeRotationDistance / 2
);
}
@ -230,10 +206,6 @@ namespace osu.Game.Rulesets.Osu.Mods
var initialAngleRad = Math.Atan2(initial.Y, initial.X);
var destAngleRad = Math.Atan2(destination.Y, destination.X);
// Divide by 2 to limit the max. angle to 90°
// (90° is enough to prevent the hit objects from leaving the playfield)
relativeDistance /= 2;
var diff = destAngleRad - initialAngleRad;
while (diff < -Math.PI)
@ -246,46 +218,45 @@ namespace osu.Game.Rulesets.Osu.Mods
diff -= 2 * Math.PI;
}
var finalAngle = 0d;
if (diff > 0)
{
finalAngle = initialAngleRad + relativeDistance * diff;
}
else if (diff < 0)
{
finalAngle = initialAngleRad + relativeDistance * diff;
}
var finalAngleRad = initialAngleRad + relativeDistance * diff;
return new Vector2(
initial.Length * (float)Math.Cos(finalAngle),
initial.Length * (float)Math.Sin(finalAngle)
initial.Length * (float)Math.Cos(finalAngleRad),
initial.Length * (float)Math.Sin(finalAngleRad)
);
}
private struct HitObjectInfo
{
internal float AngleRad { get; set; }
internal Vector2 PosUnchanged { get; set; }
internal Vector2 PosChanged { get; set; }
}
}
public class OsuModRandomSettingsControl : SettingsItem<int>
public class OsuModRandomSettingsControl : SettingsItem<int?>
{
[Resolved]
private static GameHost host { get; set; }
[BackgroundDependencyLoader]
private void load(GameHost gameHost) => host = gameHost;
protected override Drawable CreateControl() => new SeedControl
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = 5 }
};
private sealed class SeedControl : CompositeDrawable, IHasCurrentValue<int>
private sealed class SeedControl : CompositeDrawable, IHasCurrentValue<int?>
{
private readonly BindableWithCurrent<int> current = new BindableWithCurrent<int>();
[Resolved]
private GameHost host { get; set; }
public Bindable<int> Current
private readonly BindableWithCurrent<int?> current = new BindableWithCurrent<int?>();
public Bindable<int?> Current
{
get => current;
set => Scheduler.Add(() => current.Current = value);
set
{
current.Current = value;
seedNumberBox.Text = value.Value.ToString();
}
}
private readonly OsuNumberBox seedNumberBox;
@ -324,40 +295,29 @@ namespace osu.Game.Rulesets.Osu.Mods
{
RelativeSizeAxes = Axes.Both,
Height = 1,
Text = "Copy",
Action = copySeedToClipboard
Text = "Paste",
Action = () => seedNumberBox.Text = host.GetClipboard().GetText()
}
}
}
}
};
seedNumberBox.Current.BindValueChanged(onTextBoxValueChanged);
seedNumberBox.Current.BindValueChanged(e =>
{
int? value = null;
if (int.TryParse(e.NewValue, out var intVal))
value = intVal;
current.Value = value;
});
}
private void onTextBoxValueChanged(ValueChangedEvent<string> e)
{
string seed = e.NewValue;
while (!string.IsNullOrEmpty(seed) && !int.TryParse(seed, out _))
seed = seed[..^1];
if (!int.TryParse(seed, out var intVal))
intVal = 0;
current.Value = intVal;
}
private void copySeedToClipboard() => host.GetClipboard().SetText(seedNumberBox.Text);
protected override void Update()
{
seedNumberBox.ReadOnly = OsuModRandom.CustomSeedDisabled;
if (seedNumberBox.HasFocus)
return;
seedNumberBox.Text = current.Current.Value.ToString();
if (current.Value == null)
seedNumberBox.Text = current.Current.Value.ToString();
}
}
}

View File

@ -13,5 +13,6 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.Dice;
public override double ScoreMultiplier => 1;
public int? Seed { get; protected set; }
}
}

View File

@ -88,6 +88,24 @@ namespace osu.Game.Scoring
}
}
public bool ContainsModOfType<T>(out T mod)
{
if (mods != null)
{
foreach (var currentMod in mods)
{
if (!(currentMod is T modOfType))
continue;
mod = modOfType;
return true;
}
}
mod = default;
return false;
}
// Used for API serialisation/deserialisation.
[JsonProperty("mods")]
[NotMapped]

View File

@ -7,11 +7,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
@ -55,6 +57,9 @@ namespace osu.Game.Screens.Ranking.Expanded
Padding = new MarginPadding(padding);
}
[Resolved]
private GameHost host { get; set; }
[BackgroundDependencyLoader]
private void load(BeatmapDifficultyCache beatmapDifficultyCache)
{
@ -224,7 +229,47 @@ namespace osu.Game.Screens.Ranking.Expanded
}
}
}
}
}.With(t =>
{
if (!score.ContainsModOfType<ModRandom>(out var mod) || mod.Seed == null)
return;
t.Add(new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[]
{
new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding
{
Top = 3f,
Right = 5f
},
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Medium),
Text = $"Seed: {mod.Seed}"
},
new TriangleButton
{
RelativeSizeAxes = Axes.Both,
Height = 1.2f,
Width = 0.5f,
Text = "Copy",
Action = () => host.GetClipboard().SetText(mod.Seed.ToString())
}
}
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
});
})
}
},
new OsuSpriteText

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Contracted;
using osu.Game.Screens.Ranking.Expanded;
@ -206,7 +207,12 @@ namespace osu.Game.Screens.Ranking
switch (state)
{
case PanelState.Expanded:
Size = new Vector2(EXPANDED_WIDTH, expanded_height);
var height = expanded_height;
if (Score.ContainsModOfType<ModRandom>(out var mod) && mod.Seed != null)
height += 20f;
Size = new Vector2(EXPANDED_WIDTH, height);
topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint);
middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint);