mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
Fix extra lives in Easy mod potentially getting reapplied during gameplay (#36678)
Closes https://github.com/ppy/osu/issues/36676.
This commit is contained in:
88
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModEasy.cs
Normal file
88
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModEasy.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModEasy : OsuModTestScene
|
||||
{
|
||||
protected override bool AllowFail => true;
|
||||
|
||||
[Test]
|
||||
public void TestMultipleApplication()
|
||||
{
|
||||
bool reapplied = false;
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mods = [new OsuModEasy { Retries = { Value = 1 } }],
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () =>
|
||||
{
|
||||
// do stuff to speed up fails
|
||||
var b = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
Difficulty = { DrainRate = 10 }
|
||||
};
|
||||
|
||||
foreach (var ho in b.HitObjects)
|
||||
ho.StartTime /= 4;
|
||||
|
||||
return b;
|
||||
},
|
||||
PassCondition = () =>
|
||||
{
|
||||
if (((ModEasyTestPlayer)Player).FailuresSuppressed > 0 && !reapplied)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(new BeatmapDifficulty());
|
||||
|
||||
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToPlayer>())
|
||||
mod.ApplyToPlayer(Player);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// don't care if this fails. in fact a failure here is probably better than the alternative.
|
||||
}
|
||||
finally
|
||||
{
|
||||
reapplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
return Player.GameplayState.HasFailed && ((ModEasyTestPlayer)Player).FailuresSuppressed <= 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModEasyTestPlayer(CurrentTestData, AllowFail);
|
||||
|
||||
private partial class ModEasyTestPlayer : ModTestPlayer
|
||||
{
|
||||
public int FailuresSuppressed { get; private set; }
|
||||
|
||||
public ModEasyTestPlayer(ModTestData data, bool allowFail)
|
||||
: base(data, allowFail)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool CheckModsAllowFailure()
|
||||
{
|
||||
bool failureAllowed = GameplayState.Mods.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
|
||||
|
||||
if (!failureAllowed)
|
||||
FailuresSuppressed++;
|
||||
|
||||
return failureAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,18 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModEasyWithExtraLives : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor
|
||||
public abstract class ModEasyWithExtraLives : ModEasy, IApplicableFailOverride, IApplicableToPlayer, IApplicableToHealthProcessor
|
||||
{
|
||||
[SettingSource("Extra Lives", "Number of extra lives")]
|
||||
public Bindable<int> Retries { get; } = new BindableInt(2)
|
||||
@@ -33,18 +34,26 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAccuracyChallenge)).ToArray();
|
||||
|
||||
private int retries;
|
||||
private int? retries;
|
||||
|
||||
private readonly BindableNumber<double> health = new BindableDouble();
|
||||
|
||||
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
base.ApplyToDifficulty(difficulty);
|
||||
// this throw works for two reasons:
|
||||
// - every time `Player` loads, it deep-clones mods into itself, and the deep clone copies *only* `[SettingsSource]` properties
|
||||
// - `Player` is the only consumer of `IApplicableToPlayer` and it calls `ApplyToPlayer()` exactly once per mod instance
|
||||
// if either of the above assumptions no longer holds true for any reason, this will need to be reconsidered
|
||||
if (retries != null)
|
||||
throw new InvalidOperationException(@"Cannot apply this mod instance to a player twice.");
|
||||
|
||||
retries = Retries.Value;
|
||||
}
|
||||
|
||||
public bool PerformFail()
|
||||
{
|
||||
Debug.Assert(retries != null);
|
||||
|
||||
if (retries == 0) return true;
|
||||
|
||||
health.Value = health.MaxValue;
|
||||
|
||||
Reference in New Issue
Block a user