mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-15 03:20:27 +00:00
Reconcile 和解通用mod
This commit is contained in:
@@ -118,6 +118,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
return new Mod[]
|
||||
{
|
||||
new ModNiceBPM(),
|
||||
new ModReconcile(),
|
||||
new UniversalLoopPlayClip(),
|
||||
};
|
||||
|
||||
|
||||
@@ -262,6 +262,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModEz2Settings(),
|
||||
new ManiaModCleanColumn(),
|
||||
new ManiaModNiceBPM(),
|
||||
new ManiaModReconcile(),
|
||||
new ManiaModSpaceBody(),
|
||||
new ManiaModLoopPlayClip(),
|
||||
new ManiaModSRAdjust(),
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Mods.LAsMods
|
||||
|
||||
// 基类中未包含的额外信息
|
||||
// Seed 可能为 null
|
||||
yield return ((LocalisableString)"Seed", Seed.Value.HasValue ? Seed.Value.Value.ToString() : "None");
|
||||
yield return ((LocalisableString)"Seed", Seed.Value?.ToString() ?? "None");
|
||||
yield return ((LocalisableString)"Randomize Columns", (Rand.Value ? "On" : "Off"));
|
||||
yield return ((LocalisableString)"Mirror", (Mirror.Value ? "On" : "Off"));
|
||||
}
|
||||
|
||||
11
osu.Game.Rulesets.Mania/Mods/LAsMods/ManiaModReconcile.cs
Normal file
11
osu.Game.Rulesets.Mania/Mods/LAsMods/ManiaModReconcile.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// 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 osu.Game.LAsEzExtensions.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods.LAsMods
|
||||
{
|
||||
public class ManiaModReconcile : ModReconcile
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
return new Mod[]
|
||||
{
|
||||
new ModNiceBPM(),
|
||||
new ModReconcile(),
|
||||
new UniversalLoopPlayClip(),
|
||||
};
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
return new Mod[]
|
||||
{
|
||||
new ModNiceBPM(),
|
||||
new ModReconcile(),
|
||||
new UniversalLoopPlayClip(),
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,42 @@ namespace osu.Game.LAsEzExtensions.Mods
|
||||
public static readonly LocalisableString RateChangeOnMiss_Label = new EzLocalisableString("Miss时的速率变化", "Rate Change On Miss");
|
||||
public static readonly LocalisableString RateChangeOnMiss_Description = new EzLocalisableString("达到Miss阈值时应用的速率倍数", "Rate multiplier applied when miss threshold is reached");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reconcile
|
||||
|
||||
public static readonly LocalisableString Reconcile_Description = new EzLocalisableString("满足条件时暂停,可选回溯到上一个目标位置。",
|
||||
"Pause when conditions are met, optionally rewinding to the previous target position.");
|
||||
|
||||
public static readonly LocalisableString Reconcile_EnableMiss_Label = new EzLocalisableString("启用判定计数", "Enable judgement count");
|
||||
public static readonly LocalisableString Reconcile_EnableMiss_Description = new EzLocalisableString("当指定判定累计到阈值时触发", "Trigger when the selected judgement reaches the threshold.");
|
||||
public static readonly LocalisableString Reconcile_MissJudgement_Label = new EzLocalisableString("判定类型", "Judgement Type");
|
||||
public static readonly LocalisableString Reconcile_MissJudgement_Description = new EzLocalisableString("选择要计数的判定类型", "Select the judgement to count.");
|
||||
public static readonly LocalisableString Reconcile_MissCount_Label = new EzLocalisableString("判定计数阈值", "Judgement Count Threshold");
|
||||
public static readonly LocalisableString Reconcile_MissCount_Description = new EzLocalisableString("达到该数量时触发暂停", "Trigger pause when this count is reached.");
|
||||
|
||||
public static readonly LocalisableString Reconcile_EnableAcc_Label = new EzLocalisableString("启用Acc条件", "Enable accuracy condition");
|
||||
public static readonly LocalisableString Reconcile_EnableAcc_Description = new EzLocalisableString("当Acc低于阈值时触发", "Trigger when accuracy falls below the threshold.");
|
||||
public static readonly LocalisableString Reconcile_AccThreshold_Label = new EzLocalisableString("Acc阈值(%)", "Accuracy Threshold (%)");
|
||||
public static readonly LocalisableString Reconcile_AccThreshold_Description = new EzLocalisableString("低于此Acc触发暂停", "Trigger pause when accuracy is below this value.");
|
||||
|
||||
public static readonly LocalisableString Reconcile_EnableHealth_Label = new EzLocalisableString("启用血量条件", "Enable health condition");
|
||||
public static readonly LocalisableString Reconcile_EnableHealth_Description = new EzLocalisableString("当血量低于阈值时触发", "Trigger when health falls below the threshold.");
|
||||
public static readonly LocalisableString Reconcile_HealthThreshold_Label = new EzLocalisableString("血量阈值(%)", "Health Threshold (%)");
|
||||
public static readonly LocalisableString Reconcile_HealthThreshold_Description = new EzLocalisableString("低于此血量触发暂停", "Trigger pause when health is below this value.");
|
||||
|
||||
public static readonly LocalisableString Reconcile_RewindEnabled_Label = new EzLocalisableString("启用回溯", "Enable rewind");
|
||||
|
||||
public static readonly LocalisableString Reconcile_RewindEnabled_Description = new EzLocalisableString(
|
||||
"触发后回溯到目标位置再暂停。规则:"
|
||||
+ "\n判定回溯到阈值的2/3处;"
|
||||
+ "\nAcc回溯到阈值+(100-阈值)/3;"
|
||||
+ "\n血量回溯到阈值+(100-阈值)*0.8。",
|
||||
"Rewind to the target position before pausing. Rules: "
|
||||
+ "\nJudgement rewinds to 2/3 of the threshold; "
|
||||
+ "\nAcc rewinds to threshold+(100-threshold)/3; "
|
||||
+ "\nHealth rewinds to threshold+(100-threshold)*0.8.");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
257
osu.Game/LAsEzExtensions/Mods/ModReconcile.cs
Normal file
257
osu.Game/LAsEzExtensions/Mods/ModReconcile.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.LAsEzExtensions.Mods
|
||||
{
|
||||
public class ModReconcile : Mod, IApplicableToPlayer, IApplicableToScoreProcessor, IApplicableToHealthProcessor, IUpdatableByPlayfield
|
||||
{
|
||||
public override string Name => "Reconcile";
|
||||
public override string Acronym => "RC";
|
||||
public override LocalisableString Description => EzModStrings.Reconcile_Description;
|
||||
public override ModType Type => ModType.LA_Mod;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => false;
|
||||
public override bool ValidForMultiplayer => true;
|
||||
public override bool ValidForFreestyleAsRequiredMod => false;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Handshake;
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_EnableMiss_Label), nameof(EzModStrings.Reconcile_EnableMiss_Description))]
|
||||
public BindableBool EnableMissCondition { get; } = new BindableBool(false);
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_MissJudgement_Label), nameof(EzModStrings.Reconcile_MissJudgement_Description), SettingControlType = typeof(SettingsEnumDropdown<HitResult>))]
|
||||
public Bindable<HitResult> MissJudgement { get; } = new Bindable<HitResult>(HitResult.Miss);
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_MissCount_Label), nameof(EzModStrings.Reconcile_MissCount_Description))]
|
||||
public BindableNumber<int> MissCountThreshold { get; } = new BindableInt(3)
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 20,
|
||||
Precision = 1
|
||||
};
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_EnableAcc_Label), nameof(EzModStrings.Reconcile_EnableAcc_Description))]
|
||||
public BindableBool EnableAccCondition { get; } = new BindableBool(false);
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_AccThreshold_Label), nameof(EzModStrings.Reconcile_AccThreshold_Description))]
|
||||
public BindableNumber<double> AccThresholdPercent { get; } = new BindableDouble(94)
|
||||
{
|
||||
MinValue = 50,
|
||||
MaxValue = 100,
|
||||
Precision = 0.1
|
||||
};
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_EnableHealth_Label), nameof(EzModStrings.Reconcile_EnableHealth_Description))]
|
||||
public BindableBool EnableHealthCondition { get; } = new BindableBool(true);
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_HealthThreshold_Label), nameof(EzModStrings.Reconcile_HealthThreshold_Description))]
|
||||
public BindableNumber<int> HealthThresholdPercent { get; } = new BindableInt(30)
|
||||
{
|
||||
MinValue = 10,
|
||||
MaxValue = 90,
|
||||
};
|
||||
|
||||
[SettingSource(typeof(EzModStrings), nameof(EzModStrings.Reconcile_RewindEnabled_Label), nameof(EzModStrings.Reconcile_RewindEnabled_Description))]
|
||||
public BindableBool RewindEnabled { get; } = new BindableBool(false);
|
||||
|
||||
private Player? player;
|
||||
private ScoreProcessor? scoreProcessor;
|
||||
private HealthProcessor? healthProcessor;
|
||||
|
||||
private int currentJudgementCount;
|
||||
private double? lastJudgementTargetTime;
|
||||
private double? lastAccTargetTime;
|
||||
private double? lastHealthTargetTime;
|
||||
private double? cooldownUntilTime;
|
||||
|
||||
private const double pause_cooldown_ms = 5000;
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
this.player = player;
|
||||
|
||||
player.GameplayState.PlayingState.BindValueChanged(_ =>
|
||||
{
|
||||
if (player.GameplayState.PlayingState.Value == LocalUserPlayingState.NotPlaying)
|
||||
resetCounts();
|
||||
});
|
||||
}
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
this.scoreProcessor = scoreProcessor;
|
||||
|
||||
scoreProcessor.NewJudgement += onNewJudgement;
|
||||
scoreProcessor.JudgementReverted += onJudgementReverted;
|
||||
scoreProcessor.OnResetFromReplayFrame += resetCounts;
|
||||
|
||||
MissJudgement.BindValueChanged(_ => resetJudgementTracking(), true);
|
||||
AccThresholdPercent.BindValueChanged(_ => lastAccTargetTime = null);
|
||||
}
|
||||
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
|
||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||
{
|
||||
this.healthProcessor = healthProcessor;
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
if (player == null || scoreProcessor == null || healthProcessor == null)
|
||||
return;
|
||||
|
||||
if (player.GameplayState.HasCompleted)
|
||||
return;
|
||||
|
||||
if (player.GameplayState.PlayingState.Value != LocalUserPlayingState.Playing)
|
||||
return;
|
||||
|
||||
if (!playfield.Clock.IsRunning)
|
||||
return;
|
||||
|
||||
double currentTime = playfield.Clock.CurrentTime;
|
||||
|
||||
if (cooldownUntilTime is { } cooldownUntil && currentTime < cooldownUntil)
|
||||
return;
|
||||
|
||||
var triggerState = getTriggerState(scoreProcessor, healthProcessor);
|
||||
|
||||
if (triggerState.ShouldTrigger)
|
||||
handleTrigger(playfield, currentTime, triggerState);
|
||||
}
|
||||
|
||||
private TriggerState getTriggerState(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor)
|
||||
{
|
||||
bool missTriggered = EnableMissCondition.Value && MissCountThreshold.Value > 0 && currentJudgementCount >= MissCountThreshold.Value;
|
||||
bool accTriggered = EnableAccCondition.Value && scoreProcessor.Accuracy.Value * 100 < AccThresholdPercent.Value;
|
||||
bool healthTriggered = EnableHealthCondition.Value && healthProcessor.Health.Value * 100 < HealthThresholdPercent.Value;
|
||||
|
||||
return new TriggerState(missTriggered, accTriggered, healthTriggered);
|
||||
}
|
||||
|
||||
private void handleTrigger(Playfield playfield, double currentTime, TriggerState triggerState)
|
||||
{
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
if (RewindEnabled.Value)
|
||||
{
|
||||
double? targetTime = getRewindTargetTime(triggerState);
|
||||
|
||||
if (targetTime is { } target && target <= currentTime)
|
||||
player.Seek(target);
|
||||
}
|
||||
|
||||
if (!player.Pause())
|
||||
return;
|
||||
|
||||
resetCounts();
|
||||
cooldownUntilTime = currentTime + pause_cooldown_ms;
|
||||
}
|
||||
|
||||
private void onNewJudgement(JudgementResult result)
|
||||
{
|
||||
if (!result.IsFinal)
|
||||
return;
|
||||
|
||||
if (result.Type != HitResult.None && result.Type == MissJudgement.Value)
|
||||
{
|
||||
currentJudgementCount++;
|
||||
|
||||
int relaxedCount = getRelaxedMissCount();
|
||||
if (currentJudgementCount == relaxedCount)
|
||||
lastJudgementTargetTime = result.TimeAbsolute;
|
||||
}
|
||||
|
||||
if (scoreProcessor != null)
|
||||
{
|
||||
if (scoreProcessor.Accuracy.Value * 100 >= getRelaxedAccThreshold())
|
||||
lastAccTargetTime = result.TimeAbsolute;
|
||||
}
|
||||
|
||||
if (healthProcessor != null)
|
||||
{
|
||||
if (healthProcessor.Health.Value * 100 >= getRelaxedHealthThreshold())
|
||||
lastHealthTargetTime = result.TimeAbsolute;
|
||||
}
|
||||
}
|
||||
|
||||
private void onJudgementReverted(JudgementResult result)
|
||||
{
|
||||
if (!result.IsFinal)
|
||||
return;
|
||||
|
||||
if (result.Type != HitResult.None && result.Type == MissJudgement.Value)
|
||||
{
|
||||
currentJudgementCount = currentJudgementCount > 0 ? currentJudgementCount - 1 : 0;
|
||||
|
||||
if (currentJudgementCount < getRelaxedMissCount())
|
||||
lastJudgementTargetTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetCounts()
|
||||
{
|
||||
currentJudgementCount = 0;
|
||||
}
|
||||
|
||||
private void resetJudgementTracking()
|
||||
{
|
||||
currentJudgementCount = 0;
|
||||
lastJudgementTargetTime = null;
|
||||
}
|
||||
|
||||
private int getRelaxedMissCount()
|
||||
{
|
||||
int threshold = MissCountThreshold.Value;
|
||||
return (int)System.Math.Ceiling(threshold * (2d / 3d));
|
||||
}
|
||||
|
||||
private double getRelaxedAccThreshold()
|
||||
{
|
||||
double threshold = AccThresholdPercent.Value;
|
||||
return threshold + (100 - threshold) / 3d;
|
||||
}
|
||||
|
||||
private double getRelaxedHealthThreshold()
|
||||
{
|
||||
double threshold = HealthThresholdPercent.Value;
|
||||
return threshold + (100 - threshold) * 0.8d;
|
||||
}
|
||||
|
||||
private double? getRewindTargetTime(TriggerState triggerState)
|
||||
{
|
||||
double? targetTime = null;
|
||||
|
||||
if (triggerState.MissTriggered && lastJudgementTargetTime is { } missTarget)
|
||||
targetTime = selectEarlier(targetTime, missTarget);
|
||||
|
||||
if (triggerState.AccTriggered && lastAccTargetTime is { } accTarget)
|
||||
targetTime = selectEarlier(targetTime, accTarget);
|
||||
|
||||
if (triggerState.HealthTriggered && lastHealthTargetTime is { } healthTarget)
|
||||
targetTime = selectEarlier(targetTime, healthTarget);
|
||||
|
||||
return targetTime;
|
||||
|
||||
static double selectEarlier(double? current, double candidate) => current.HasValue ? System.Math.Min(current.Value, candidate) : candidate;
|
||||
}
|
||||
|
||||
private readonly record struct TriggerState(bool MissTriggered, bool AccTriggered, bool HealthTriggered)
|
||||
{
|
||||
public bool ShouldTrigger => MissTriggered || AccTriggered || HealthTriggered;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user