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:FlxTypedSignalVoid> = new FlxTypedSignal(); public var onRatingRemoved:FlxTypedSignalVoid> = new FlxTypedSignal(); public var hitWindows:StringMap; public var ratingData:Array = []; 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) { 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: Flags.SHITS_BREAK_COMBO}); } 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; }