mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 10:33:22 +08:00
Created Scoring (markdown)
parent
0539d9c002
commit
cbfb489b40
248
Scoring.md
Normal file
248
Scoring.md
Normal file
@ -0,0 +1,248 @@
|
||||
# Total score
|
||||
|
||||
Score is divided into three portions: accuracy, combo, and bonus. The accuracy and combo portions together comprise the base 1000000 score, and then the bonus is added on top to determine the total score.
|
||||
|
||||
```
|
||||
accuracy_portion = 0.3
|
||||
combo_portion = 0.7
|
||||
total_score = 1000000 * ((accuracy * accuracy_portion) + (combo / max_achievable_combo * combo_portion)) + bonus_portion
|
||||
```
|
||||
|
||||
These portions can be adjusted by deriving `ScoreProcessor`:
|
||||
|
||||
```csharp
|
||||
public class MyScoreProcessor : ScoreProcessor
|
||||
{
|
||||
// Accuracy accounts for up to 950000 of the base score.
|
||||
protected override double DefaultAccuracyPortion => 0.95;
|
||||
|
||||
// Combo accounts for up to 50000 of the base score.
|
||||
protected override double DefaultComboPortion => 0.05;
|
||||
}
|
||||
```
|
||||
|
||||
# Hit results
|
||||
|
||||
The `HitResult` class determines which portion(s) of the total score a judgement contributes to, and the amount of health gained or lost. This is defined on a per-hitobject/per-judgement level.
|
||||
|
||||
### Score
|
||||
|
||||
| Type | Score | Accuracy portion? | Combo portion?¹ | Bonus portion? | Examples | MinResult² |
|
||||
| ------------- | :-----: | :---------------: | :-------------: | :------------: | -------------------------------------------------- | ------------- |
|
||||
| IgnoreMiss³ | 0 | ❌ | ❌ | ❌ | Slider, SwellTick, StrongHit, Banana, SpinnerBonus | - |
|
||||
| IgnoreHit³ | 0 | ❌ | ❌ | ❌ | Slider, SwellTick | IgnoreMiss |
|
||||
| Miss | 0 | ✅ | ✅ | ❌ | HitCircle | - |
|
||||
| Meh | 50 | ✅ | ✅ | ❌ | HitCircle | Miss |
|
||||
| Ok | 100 | ✅ | ✅ | ❌ | HitCircle | Miss |
|
||||
| Good | 200 | ✅ | ✅ | ❌ | HitCircle | Miss |
|
||||
| Great | 300 | ✅ | ✅ | ❌ | HitCircle | Miss |
|
||||
| Perfect | 350 | ✅ | ✅ | ❌ | HitCircle | Miss |
|
||||
| SmallTickMiss | 0 | ✅ | ❌ | ❌ | TinyDroplet, SpinnerTick | - |
|
||||
| SmallTickHit | 10 | ✅ | ❌ | ❌ | TinyDroplet, SpinnerTick | SmallTickMiss |
|
||||
| LargeTickMiss | 0 | ✅ | ✅ | ❌ | Droplet, SliderTick | - |
|
||||
| LargeTickHit | 30 | ✅ | ✅ | ❌ | Droplet, SliderTick | LargeTickMiss |
|
||||
| SmallBonus | 10 | ❌ | ❌ | ✅ | StrongHit | IgnoreMiss |
|
||||
| LargeBonus | 50 | ❌ | ❌ | ✅ | Banana, SpinnerBonus | IgnoreMiss |
|
||||
|
||||
¹ Contribution to the combo portion also implies the combo is increased or decreased for a hit or miss respectively.
|
||||
² The minimum result is provided for reference to be used in later sections that describe hit result application.
|
||||
³ All hitobjects must provide a hit result, but "Ignore" results are to be provided when the score should not be affected.
|
||||
|
||||
### Health
|
||||
|
||||
The amount of health increase or decrease is defined relative to a "Pefect" hit result. By default, a "Perfect" hit result increases HP by 5%.
|
||||
|
||||
| Type | Relative addition |
|
||||
| ------------- | :---------------: |
|
||||
| IgnoreMiss | 0% |
|
||||
| IgnoreHit | 0% |
|
||||
| Miss | -100% |
|
||||
| Meh | -50% |
|
||||
| Ok | -30% |
|
||||
| Good | -10% |
|
||||
| Great | 80% |
|
||||
| Perfect | 100% |
|
||||
| SmallTickMiss | -5% |
|
||||
| SmallTickHit | 5% |
|
||||
| LargeTickMiss | -10% |
|
||||
| LargeTickHit | 10% |
|
||||
| SmallBonus | 10% |
|
||||
| LargeBonus | 20% |
|
||||
|
||||
These values can be adjusted by deriving `Judgement`:
|
||||
|
||||
```csharp
|
||||
public class MyJudgement : Judgement
|
||||
{
|
||||
protected override double HealthIncreaseFor(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Good:
|
||||
// Make Goods not reduce HP.
|
||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||
|
||||
default:
|
||||
return base.HealthIncreaseFor(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Hitobjects
|
||||
|
||||
By default, all `HitObject` types affect the accuracy and combo portions of the score. When judged, they accept any hit result that is not a tick, bonus, or ignore result.
|
||||
|
||||
To change this, derive `HitObject` and `Judgement` to set the appropriate `Judgement.MaxResult`:
|
||||
|
||||
```csharp
|
||||
public class MySliderTick : HitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new MySliderTickJudgement();
|
||||
}
|
||||
|
||||
public class MySliderTickJudgement : Judgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.SmallTickHit;
|
||||
}
|
||||
```
|
||||
This will in-turn change the value of `Judgement.MinResult` as described by the table above.
|
||||
|
||||
# Judging
|
||||
|
||||
To judge a `DrawableHitObject`, invoke `DrawableHitObject.ApplyResult(Action<JudgementResult> application)` and set the appropriate result within the range of `Judgement.MinResult` and `Judgement.MaxResult` for the hitobject:
|
||||
|
||||
| MaxResult | Accepted judgement result types |
|
||||
| ------------ | ----------------------------------- |
|
||||
| IgnoreHit | IgnoreHit, IgnoreMiss |
|
||||
| Meh | Meh, Miss |
|
||||
| Ok | Ok, Meh, Miss |
|
||||
| Good | Good, Ok, Meh, Miss |
|
||||
| Great | Great, Good, Ok, Meh, Miss |
|
||||
| Perfect | Perfect, Great, Good, Ok, Meh, Miss |
|
||||
| SmallTickHit | SmallTickHit, SmallTickMiss |
|
||||
| LargeTickHit | LargeTickHit, LargeTickMiss |
|
||||
| SmallBonus | SmallBonus, IgnoreMiss |
|
||||
| LargeBonus | LargeBonus, IgnoreMiss |
|
||||
|
||||
```csharp
|
||||
public class MyDrawableHitObject : DrawableHitObject<MyHitObject>
|
||||
{
|
||||
public MyDrawableHitObject(MyHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset > HitObject.StartTime)
|
||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||
}
|
||||
else
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
UpdateResult(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Weighting hitobjects
|
||||
|
||||
Nested hitobjects can be used to increase the weighting of hitobjects that are more important than others and should contribute more towards the score:
|
||||
|
||||
```csharp
|
||||
public class MyHitObject : HitObject
|
||||
{
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
// When applying a result, the same result will also be applied to the padding object (see below).
|
||||
// This results in a 2x weighting for the "MyHitObject" type.
|
||||
AddNested(new ScorePaddingObject());
|
||||
|
||||
// For a 3x weighting, add another one!
|
||||
// AddNested(new ScorePaddingObject());
|
||||
}
|
||||
}
|
||||
|
||||
public class ScorePaddingObject : HitObject
|
||||
{
|
||||
}
|
||||
|
||||
public class MyDrawableHitObject : DrawableHitObject<MyHitObject>
|
||||
{
|
||||
private readonly List<DrawableScorePaddingObject> paddingObjects = new List<DrawableScorePaddingObject>();
|
||||
|
||||
public MyDrawableHitObject(MyHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
paddingObjects.Clear();
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
|
||||
if (hitObject is DrawableScorePaddingObject pad)
|
||||
paddingObjects.Add(pad);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case ScorePaddingObject pad:
|
||||
return new DrawableScorePaddingObject(pad);
|
||||
|
||||
default:
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset > HitObject.StartTime)
|
||||
applyResult(false);
|
||||
}
|
||||
else
|
||||
applyResult(true);
|
||||
}
|
||||
|
||||
private void applyResult(bool hit)
|
||||
{
|
||||
ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
foreach (var nested in paddingObjects)
|
||||
nested.ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
UpdateResult(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class DrawableScorePaddingObject : DrawableHitObject<ScorePaddingObject>
|
||||
{
|
||||
public DrawableScorePaddingObject(ScorePaddingObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
public new void ApplyResult(Action<JudgementResult> application) => base.ApplyResult(application);
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue
Block a user