Files
CodenameEngine/source/funkin/game/scoring/RatingManager.hx
T
SrtHero278 80ca43b46e shits break combo because read desc
this engine is spam and get away with it central like i struggled to hit a shit no joke

and i did get owner approval to do this
2026-05-11 19:44:59 -07:00

159 lines
3.9 KiB
Haxe

package funkin.game.scoring;
import funkin.game.scoring.*;
import funkin.game.scoring.HitWindowData.WindowPreset;
import flixel.util.FlxSignal;
import haxe.ds.StringMap;
/**
* Judges note hits and returns a rating.
*/
class RatingManager
{
public var onRatingAdded:FlxTypedSignal<Rating->Void> = new FlxTypedSignal();
public var onRatingRemoved:FlxTypedSignal<Rating->Void> = new FlxTypedSignal();
public var hitWindows:StringMap<Float>;
public var ratingData:Array<Rating> = [];
public var lastHitWindow:Float = -1;
public function new(?preset:WindowPreset):Void
{
var usedPreset = preset != null ? preset : WindowPreset.DEFAULT;
hitWindows = HitWindowData.getWindows(usedPreset);
initDefaultData(hitWindows);
}
/**
* Returns a rating based on a wimdow of time.
* @param time The timing window to judge.
*/
public function judgeNote(time:Float):Rating
{
for (i => rating in ratingData)
{
if (rating.hittable && rating.window > -1 && time <= rating.window)
{
return rating;
}
}
return ratingData.last();
}
/**
* Initializes the default rating data containing the four judgements.
*
* "Sick", "Good", "Bad", "Shit"
*/
public function initDefaultData(windows:StringMap<Float>)
{
inline function getWindow(name:String):Float
{
return windows.exists(name) ? windows.get(name) : -1;
}
addRating({name: "sick", window: getWindow("sick"), accuracy: 1, score: 300, splash: true});
addRating({name: "good", window: getWindow("good"), accuracy: 0.75, score: 200, health: 0.015});
addRating({name: "bad", window: getWindow("bad"), accuracy: 0.45, score: 100, health: 0});
addRating({name: "shit", window: getWindow("shit"), accuracy: 0.25, score: 50, health: -0.05, breaksCombo: true});
}
public function addRating(data:Dynamic)
{
if (data == null || data.name == null) return;
var name = data.name.toLowerCase();
var window = data.window != null
? data.window
: (hitWindows.exists(name) ? hitWindows.get(name) : -1);
if (window > lastHitWindow) lastHitWindow = window;
var newRating:Rating = {
name: name,
window: window,
accuracy: data.accuracy != null ? data.accuracy : 1,
score: data.score != null ? data.score : 0,
health: data.health != null ? data.health : 0.023,
splash: data.splash == true,
breaksCombo: data.breaksCombo == true,
hittable: data.hittable != null ? data.hittable : true
};
var existingIndex = -1;
for (i in 0...ratingData.length)
if (ratingData[i].name == name)
existingIndex = i;
if (existingIndex >= 0)
ratingData[existingIndex] = newRating;
else
ratingData.push(newRating);
ratingData.sort((a, b) -> Reflect.compare(a.window, b.window));
onRatingAdded.dispatch(newRating);
}
public function removeRating(name:String):Void
{
if (name == null) return;
name = name.toLowerCase();
var toRemove = ratingData.filter(r -> r.name == name);
for (rating in toRemove) {
ratingData.remove(rating);
onRatingRemoved.dispatch(rating);
}
}
public function getHitWindow(name:String):Float
{
return hitWindows.exists(name) ? hitWindows.get(name) : -1;
}
}
@:structInit
final class Rating
{
/**
* Name of rating.
*
* Also used for the image file name of the rating.
*/
public var name:String = "unknown";
/**
* Amount of accuracy given when earning this rating.
*/
public var accuracy:Float = 0.0;
/**
* MS Timing Window to hit the rating.
*/
public var window:Float = -1;
/**
* Amount of score given when earning this rating.
*/
public var score:Int = 0;
/**
* Amount of health given when earning this rating.
*/
public var health:Float = 0.023;
/**
* If this rating was hit, a note splash will appear.
*/
@:optional public var splash:Bool = false;
/**
* Whether the rating will break your combo or not.
*/
@:optional public var breaksCombo:Bool = false;
/**
* Whether the rating is hittable or not.
*/
@:optional public var hittable:Bool = true;
}