mirror of
https://github.com/ppy/osu.git
synced 2024-11-06 09:07:25 +08:00
Merge remote-tracking branch 'HollyHacker/ctb-pp' into sorcerer-catch-changes
This commit is contained in:
commit
7d102e9e8f
@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
@ -118,6 +119,8 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
public override int? LegacyID => 2;
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
||||
|
104
osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
Normal file
104
osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
Normal file
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
private int fruitsHit;
|
||||
private int ticksHit;
|
||||
private int tinyTicksHit;
|
||||
private int tinyTicksMissed;
|
||||
private int misses;
|
||||
|
||||
public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
||||
: base(ruleset, beatmap, score)
|
||||
{
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
|
||||
var legacyScore = Score as LegacyScoreInfo;
|
||||
|
||||
fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect];
|
||||
ticksHit = legacyScore?.Count100 ?? 0;
|
||||
tinyTicksHit = legacyScore?.Count50 ?? 0;
|
||||
tinyTicksMissed = legacyScore?.CountKatu ?? 0;
|
||||
misses = Score.Statistics[HitResult.Miss];
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
// We are heavily relying on aim in catch the beat
|
||||
double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f;
|
||||
|
||||
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
|
||||
int numTotalHits = totalComboHits();
|
||||
|
||||
// Longer maps are worth more
|
||||
float lengthBonus =
|
||||
0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) +
|
||||
(numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f);
|
||||
|
||||
// Longer maps are worth more
|
||||
value *= lengthBonus;
|
||||
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
value *= Math.Pow(0.97f, misses);
|
||||
|
||||
// Combo scaling
|
||||
float beatmapMaxCombo = Attributes.MaxCombo;
|
||||
if (beatmapMaxCombo > 0)
|
||||
value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
|
||||
|
||||
float approachRate = (float)Attributes.ApproachRate;
|
||||
float approachRateFactor = 1.0f;
|
||||
if (approachRate > 9.0f)
|
||||
approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
|
||||
else if (approachRate < 8.0f)
|
||||
approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
|
||||
|
||||
value *= approachRateFactor;
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
// Hiddens gives nothing on max approach rate, and more the lower it is
|
||||
value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight))
|
||||
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||
value *= 1.35f * lengthBonus;
|
||||
|
||||
// Scale the aim value with accuracy _slightly_
|
||||
value *= Math.Pow(accuracy(), 5.5f);
|
||||
|
||||
// Custom multipliers for NoFail. SpunOut is not applicable.
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
value *= 0.90f;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f);
|
||||
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
|
||||
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
|
||||
private int totalComboHits() => misses + ticksHit + fruitsHit;
|
||||
}
|
||||
}
|
118
osu.Game/Scoring/Legacy/LegacyScoreInfo.cs
Normal file
118
osu.Game/Scoring/Legacy/LegacyScoreInfo.cs
Normal file
@ -0,0 +1,118 @@
|
||||
// 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.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Scoring.Legacy
|
||||
{
|
||||
public class LegacyScoreInfo : ScoreInfo
|
||||
{
|
||||
private int countGeki;
|
||||
|
||||
public int CountGeki
|
||||
{
|
||||
get => countGeki;
|
||||
set
|
||||
{
|
||||
countGeki = value;
|
||||
|
||||
switch (Ruleset?.ID ?? RulesetID)
|
||||
{
|
||||
case 3:
|
||||
Statistics[HitResult.Perfect] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int count300;
|
||||
|
||||
public int Count300
|
||||
{
|
||||
get => count300;
|
||||
set
|
||||
{
|
||||
count300 = value;
|
||||
|
||||
switch (Ruleset?.ID ?? RulesetID)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 3:
|
||||
Statistics[HitResult.Great] = value;
|
||||
break;
|
||||
case 2:
|
||||
Statistics[HitResult.Perfect] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int countKatu;
|
||||
|
||||
public int CountKatu
|
||||
{
|
||||
get => countKatu;
|
||||
set
|
||||
{
|
||||
countKatu = value;
|
||||
|
||||
switch (Ruleset?.ID ?? RulesetID)
|
||||
{
|
||||
case 3:
|
||||
Statistics[HitResult.Good] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int count100;
|
||||
|
||||
public int Count100
|
||||
{
|
||||
get => count100;
|
||||
set
|
||||
{
|
||||
count100 = value;
|
||||
|
||||
switch (Ruleset?.ID ?? RulesetID)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
Statistics[HitResult.Good] = value;
|
||||
break;
|
||||
case 3:
|
||||
Statistics[HitResult.Ok] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int count50;
|
||||
|
||||
public int Count50
|
||||
{
|
||||
get => count50;
|
||||
set
|
||||
{
|
||||
count50 = value;
|
||||
|
||||
switch (Ruleset?.ID ?? RulesetID)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
case 3:
|
||||
Statistics[HitResult.Meh] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int CountMiss
|
||||
{
|
||||
get => Statistics[HitResult.Miss];
|
||||
set => Statistics[HitResult.Miss] = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,7 +34,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
using (SerializationReader sr = new SerializationReader(stream))
|
||||
{
|
||||
currentRuleset = GetRuleset(sr.ReadByte());
|
||||
score.ScoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo };
|
||||
var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo };
|
||||
|
||||
score.ScoreInfo = scoreInfo;
|
||||
|
||||
var version = sr.ReadInt32();
|
||||
|
||||
@ -43,66 +45,39 @@ namespace osu.Game.Scoring.Legacy
|
||||
throw new BeatmapNotFoundException();
|
||||
|
||||
currentBeatmap = workingBeatmap.Beatmap;
|
||||
score.ScoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
|
||||
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
|
||||
|
||||
score.ScoreInfo.User = new User { Username = sr.ReadString() };
|
||||
scoreInfo.User = new User { Username = sr.ReadString() };
|
||||
|
||||
// MD5Hash
|
||||
sr.ReadString();
|
||||
|
||||
var count300 = (int)sr.ReadUInt16();
|
||||
var count100 = (int)sr.ReadUInt16();
|
||||
var count50 = (int)sr.ReadUInt16();
|
||||
var countGeki = (int)sr.ReadUInt16();
|
||||
var countKatu = (int)sr.ReadUInt16();
|
||||
var countMiss = (int)sr.ReadUInt16();
|
||||
scoreInfo.Count300 = sr.ReadUInt16();
|
||||
scoreInfo.Count100 = sr.ReadUInt16();
|
||||
scoreInfo.Count50 = sr.ReadUInt16();
|
||||
scoreInfo.CountGeki = sr.ReadUInt16();
|
||||
scoreInfo.CountKatu = sr.ReadUInt16();
|
||||
scoreInfo.CountMiss = sr.ReadUInt16();
|
||||
|
||||
switch (currentRuleset.LegacyID)
|
||||
{
|
||||
case 0:
|
||||
score.ScoreInfo.Statistics[HitResult.Great] = count300;
|
||||
score.ScoreInfo.Statistics[HitResult.Good] = count100;
|
||||
score.ScoreInfo.Statistics[HitResult.Meh] = count50;
|
||||
score.ScoreInfo.Statistics[HitResult.Miss] = countMiss;
|
||||
break;
|
||||
case 1:
|
||||
score.ScoreInfo.Statistics[HitResult.Great] = count300;
|
||||
score.ScoreInfo.Statistics[HitResult.Good] = count100;
|
||||
score.ScoreInfo.Statistics[HitResult.Miss] = countMiss;
|
||||
break;
|
||||
case 2:
|
||||
score.ScoreInfo.Statistics[HitResult.Perfect] = count300;
|
||||
score.ScoreInfo.Statistics[HitResult.Miss] = countMiss;
|
||||
break;
|
||||
case 3:
|
||||
score.ScoreInfo.Statistics[HitResult.Perfect] = countGeki;
|
||||
score.ScoreInfo.Statistics[HitResult.Great] = count300;
|
||||
score.ScoreInfo.Statistics[HitResult.Good] = countKatu;
|
||||
score.ScoreInfo.Statistics[HitResult.Ok] = count100;
|
||||
score.ScoreInfo.Statistics[HitResult.Meh] = count50;
|
||||
score.ScoreInfo.Statistics[HitResult.Miss] = countMiss;
|
||||
break;
|
||||
}
|
||||
|
||||
score.ScoreInfo.TotalScore = sr.ReadInt32();
|
||||
score.ScoreInfo.MaxCombo = sr.ReadUInt16();
|
||||
scoreInfo.TotalScore = sr.ReadInt32();
|
||||
scoreInfo.MaxCombo = sr.ReadUInt16();
|
||||
|
||||
/* score.Perfect = */
|
||||
sr.ReadBoolean();
|
||||
|
||||
score.ScoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
|
||||
scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
|
||||
|
||||
/* score.HpGraphString = */
|
||||
sr.ReadString();
|
||||
|
||||
score.ScoreInfo.Date = sr.ReadDateTime();
|
||||
scoreInfo.Date = sr.ReadDateTime();
|
||||
|
||||
var compressedReplay = sr.ReadByteArray();
|
||||
|
||||
if (version >= 20140721)
|
||||
score.ScoreInfo.OnlineScoreID = sr.ReadInt64();
|
||||
scoreInfo.OnlineScoreID = sr.ReadInt64();
|
||||
else if (version >= 20121008)
|
||||
score.ScoreInfo.OnlineScoreID = sr.ReadInt32();
|
||||
scoreInfo.OnlineScoreID = sr.ReadInt32();
|
||||
|
||||
if (compressedReplay?.Length > 0)
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
@ -68,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
public override bool Seek(double seek)
|
||||
{
|
||||
offset = MathHelper.Clamp(seek, 0, Length);
|
||||
offset = Math.Min(seek, Length);
|
||||
lastReferenceTime = null;
|
||||
return true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user