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

Merge pull request #1083 from peppy/more-mods

Implement NoFail, HardRock, Easy mods
This commit is contained in:
Dan Balasescu 2017-08-07 10:07:06 +09:00 committed by GitHub
commit c7b59646c0
16 changed files with 125 additions and 29 deletions

@ -1 +1 @@
Subproject commit 9ae8979ef7ef0968c90cdbce40a04969d43633c7
Subproject commit 107c5517670ca88dbe8c83a97e37e99ac5742ee6

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
namespace osu.Game.Beatmaps
{
@ -45,7 +46,7 @@ namespace osu.Game.Beatmaps
/// <param name="original">The original beatmap to use the parameters of.</param>
public Beatmap(Beatmap original = null)
{
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors;

View File

@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps
// Editor
// This bookmarks stuff is necessary because DB doesn't know how to store int[]
public string StoredBookmarks { get; internal set; }
public string StoredBookmarks { get; set; }
[Ignore]
[JsonIgnore]

View File

@ -52,7 +52,14 @@ namespace osu.Game.Beatmaps
{
lock (beatmapLock)
{
return beatmap ?? (beatmap = GetBeatmap());
if (beatmap != null) return beatmap;
beatmap = GetBeatmap();
// use the database-backed info.
beatmap.BeatmapInfo = BeatmapInfo;
return beatmap;
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@ -16,19 +17,27 @@ namespace osu.Game.Graphics
private readonly Sprite spriteShadow;
private readonly Sprite spriteMain;
private readonly Container shadowVisibility;
public SpriteIcon()
{
InternalChildren = new[]
InternalChildren = new Drawable[]
{
spriteShadow = new Sprite
shadowVisibility = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Position = new Vector2(0, 0.06f),
Colour = new Color4(0f, 0f, 0f, 0.2f),
Alpha = 0
Child = spriteShadow = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Y = 2,
Colour = new Color4(0f, 0f, 0f, 0.2f),
},
Alpha = 0,
},
spriteMain = new Sprite
{
@ -60,10 +69,27 @@ namespace osu.Game.Graphics
Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
if ((invalidation & Invalidation.Colour) > 0)
{
//adjust shadow alpha based on highest component intensity to avoid muddy display of darker text.
//squared result for quadratic fall-off seems to give the best result.
var avgColour = (Color4)DrawInfo.Colour.AverageColour;
spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2);
}
return base.Invalidate(invalidation, source, shallPropagate);
}
public bool Shadow
{
get { return spriteShadow.IsPresent; }
set { spriteShadow.Alpha = value ? 1 : 0; }
set
{
shadowVisibility.Alpha = value ? 1 : 0;
}
}
private FontAwesome icon;

View File

@ -13,7 +13,7 @@ namespace osu.Game.IO.Serialization
{
public static string Serialize(this IJsonSerializable obj)
{
return JsonConvert.SerializeObject(obj);
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
}
public static T Deserialize<T>(this string objString)

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// An interface for mods that make general adjustments to difficulty.
/// </summary>
public interface IApplicableToDifficulty
{
void ApplyToDifficulty(BeatmapDifficulty difficulty);
}
}

View File

@ -45,5 +45,10 @@ namespace osu.Game.Rulesets.Mods
/// The mods this mod cannot be enabled with.
/// </summary>
public virtual Type[] IncompatibleMods => new Type[] { };
/// <summary>
/// Whether we should allow failing at the current point in time.
/// </summary>
public virtual bool AllowFail => true;
}
}

View File

@ -2,11 +2,12 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModEasy : Mod
public abstract class ModEasy : Mod, IApplicableToDifficulty
{
public override string Name => "Easy";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy;
@ -15,5 +16,14 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 0.5f;
difficulty.CircleSize *= ratio;
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
}
}
}

View File

@ -2,16 +2,26 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModHardRock : Mod
public abstract class ModHardRock : Mod, IApplicableToDifficulty
{
public override string Name => "Hard Rock";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock;
public override ModType Type => ModType.DifficultyIncrease;
public override string Description => "Everything just got a bit harder...";
public override Type[] IncompatibleMods => new[] { typeof(ModEasy) };
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 1.4f;
difficulty.CircleSize *= ratio;
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
}
}
}

View File

@ -15,5 +15,10 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) };
/// <summary>
/// We never fail, 'yo.
/// </summary>
public override bool AllowFail => false;
}
}

View File

@ -16,8 +16,9 @@ namespace osu.Game.Rulesets.Scoring
{
/// <summary>
/// Invoked when the ScoreProcessor is in a failed state.
/// Return true if the fail was permitted.
/// </summary>
public event Action Failed;
public event Func<bool> Failed;
/// <summary>
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the <see cref="ScoreProcessor"/>.
@ -106,8 +107,8 @@ namespace osu.Game.Rulesets.Scoring
if (alreadyFailed || !HasFailed)
return;
alreadyFailed = true;
Failed?.Invoke();
if (Failed?.Invoke() != false)
alreadyFailed = true;
}
/// <summary>

View File

@ -153,6 +153,10 @@ namespace osu.Game.Rulesets.UI
// Convert the beatmap
Beatmap = converter.Convert(beatmap.Beatmap, isForCurrentRuleset);
// Apply difficulty adjustments from mods before using Difficulty.
foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(Beatmap.BeatmapInfo.Difficulty);
// Apply defaults
foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
@ -163,7 +167,7 @@ namespace osu.Game.Rulesets.UI
ApplyBeatmap();
// Add mods, should always be the last thing applied to give full control to mods
applyMods(beatmap.Mods.Value);
applyMods(Mods);
}
/// <summary>

View File

@ -45,10 +45,11 @@ namespace osu.Game.Rulesets.UI
},
modIcon = new SpriteIcon
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Colour = OsuColour.Gray(84),
Size = new Vector2(icon_size - 35),
Y = 25,
Icon = mod.Icon
},
};

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
using osu.Framework.Audio.Sample;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Play
{
@ -77,23 +78,28 @@ namespace osu.Game.Screens.Play
Ruleset rulesetInstance;
WorkingBeatmap working = Beatmap.Value;
Beatmap beatmap;
try
{
if (Beatmap.Value.Beatmap == null)
beatmap = working.Beatmap;
if (beatmap == null)
throw new InvalidOperationException("Beatmap was not loaded");
ruleset = osu?.Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset;
ruleset = osu?.Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
try
{
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, ruleset.ID == Beatmap.Value.BeatmapInfo.Ruleset.ID);
HitRenderer = rulesetInstance.CreateHitRendererWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
}
catch (BeatmapInvalidForRulesetException)
{
// we may fail to create a HitRenderer if the beatmap cannot be loaded with the user's preferred ruleset
// let's try again forcing the beatmap's ruleset.
ruleset = Beatmap.Value.BeatmapInfo.Ruleset;
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, true);
}
@ -110,11 +116,11 @@ namespace osu.Game.Screens.Play
return;
}
adjustableSourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = HitRenderer.Objects.First().StartTime;
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.Value.BeatmapInfo.AudioLeadIn)));
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.ProcessFrame();
offsetClock = new FramedOffsetClock(decoupledClock);
@ -127,7 +133,7 @@ namespace osu.Game.Screens.Play
{
adjustableSourceClock.Reset();
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
foreach (var mod in working.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(adjustableSourceClock);
decoupledClock.ChangeSource(adjustableSourceClock);
@ -195,7 +201,7 @@ namespace osu.Game.Screens.Play
hudOverlay.Progress.AllowSeeking = HitRenderer.HasReplayLoaded;
hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos);
hudOverlay.ModDisplay.Current.BindTo(Beatmap.Value.Mods);
hudOverlay.ModDisplay.Current.BindTo(working.Mods);
//bind HitRenderer to ScoreProcessor and ourselves (for a pass situation)
HitRenderer.OnAllJudged += onCompletion;
@ -238,13 +244,17 @@ namespace osu.Game.Screens.Play
}
}
private void onFail()
private bool onFail()
{
if (Beatmap.Value.Mods.Value.Any(m => !m.AllowFail))
return false;
decoupledClock.Stop();
HasFailed = true;
failOverlay.Retries = RestartCount;
failOverlay.Show();
return true;
}
protected override void OnEntering(Screen last)

View File

@ -117,6 +117,7 @@
<Compile Include="Overlays\Profile\Sections\RanksSection.cs" />
<Compile Include="Overlays\Profile\Sections\RecentSection.cs" />
<Compile Include="Graphics\Containers\ConstrainedIconContainer.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDifficulty.cs" />
<Compile Include="Users\UserCoverBackground.cs" />
<Compile Include="Overlays\UserProfileOverlay.cs" />
<Compile Include="Overlays\Profile\ProfileHeader.cs" />