package funkin; import flixel.FlxSprite; import flixel.math.FlxMath; import funkin.play.PlayState; import funkin.play.Strumline.StrumlineStyle; import funkin.shaderslmfao.ColorSwap; import funkin.ui.PreferencesMenu; import funkin.util.Constants; using StringTools; class Note extends FlxSprite { public var data = new NoteData(); /** * code colors for.... code.... * i think goes in order of left to right * * left 0 * down 1 * up 2 * right 3 */ public static var codeColors:Array = [0xFFFF22AA, 0xFF00EEFF, 0xFF00CC00, 0xFFCC1111]; public var mustPress:Bool = false; public var followsTime:Bool = true; // used if you want the note to follow the time shit! public var canBeHit:Bool = false; public var tooLate:Bool = false; public var wasGoodHit:Bool = false; public var prevNote:Note; private var willMiss:Bool = false; public var invisNote:Bool = false; public var isSustainNote:Bool = false; public var colorSwap:ColorSwap; /** the lowercase name of the note, for anim control, i.e. left right up down */ public var dirName(get, never):String; inline function get_dirName() return data.dirName; /** the uppercase name of the note, for anim control, i.e. left right up down */ public var dirNameUpper(get, never):String; inline function get_dirNameUpper() return data.dirNameUpper; /** the lowercase name of the note's color, for anim control, i.e. purple blue green red */ public var colorName(get, never):String; inline function get_colorName() return data.colorName; /** the lowercase name of the note's color, for anim control, i.e. purple blue green red */ public var colorNameUpper(get, never):String; inline function get_colorNameUpper() return data.colorNameUpper; public var highStakes(get, never):Bool; inline function get_highStakes() return data.highStakes; public var lowStakes(get, never):Bool; inline function get_lowStakes() return data.lowStakes; public static var swagWidth:Float = 160 * 0.7; public static var PURP_NOTE:Int = 0; public static var GREEN_NOTE:Int = 2; public static var BLUE_NOTE:Int = 1; public static var RED_NOTE:Int = 3; // SCORING STUFF public static var HIT_WINDOW:Float = (10 / 60) * 1000; // 166.67 ms hit window (10 frames at 60fps) // thresholds are fractions of HIT_WINDOW ^^ // anything above bad threshold is shit public static var BAD_THRESHOLD:Float = 0.8; // 125ms , 8 frames public static var GOOD_THRESHOLD:Float = 0.55; // 91.67ms , 5.5 frames public static var SICK_THRESHOLD:Float = 0.2; // 33.33ms , 2 frames public var noteSpeedMulti:Float = 1; public var pastHalfWay:Bool = false; // anything below sick threshold is sick public static var arrowColors:Array = [1, 1, 1, 1]; // Which note asset to load? public var style:StrumlineStyle = NORMAL; public function new(strumTime:Float = 0, noteData:NoteType, ?prevNote:Note, ?sustainNote:Bool = false, ?style:StrumlineStyle = NORMAL) { super(); if (prevNote == null) prevNote = this; this.prevNote = prevNote; isSustainNote = sustainNote; x += 50; // MAKE SURE ITS DEFINITELY OFF SCREEN? y -= 2000; data.strumTime = strumTime; data.noteData = noteData; this.style = style; if (this.style == null) this.style = StrumlineStyle.NORMAL; // TODO: Make this logic more generic switch (this.style) { case PIXEL: loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17); animation.add('greenScroll', [6]); animation.add('redScroll', [7]); animation.add('blueScroll', [5]); animation.add('purpleScroll', [4]); if (isSustainNote) { loadGraphic(Paths.image('weeb/pixelUI/arrowEnds'), true, 7, 6); animation.add('purpleholdend', [4]); animation.add('greenholdend', [6]); animation.add('redholdend', [7]); animation.add('blueholdend', [5]); animation.add('purplehold', [0]); animation.add('greenhold', [2]); animation.add('redhold', [3]); animation.add('bluehold', [1]); } setGraphicSize(Std.int(width * Constants.PIXEL_ART_SCALE)); updateHitbox(); default: frames = Paths.getSparrowAtlas('NOTE_assets'); animation.addByPrefix('greenScroll', 'green instance'); animation.addByPrefix('redScroll', 'red instance'); animation.addByPrefix('blueScroll', 'blue instance'); animation.addByPrefix('purpleScroll', 'purple instance'); animation.addByPrefix('purpleholdend', 'pruple end hold'); animation.addByPrefix('greenholdend', 'green hold end'); animation.addByPrefix('redholdend', 'red hold end'); animation.addByPrefix('blueholdend', 'blue hold end'); animation.addByPrefix('purplehold', 'purple hold piece'); animation.addByPrefix('greenhold', 'green hold piece'); animation.addByPrefix('redhold', 'red hold piece'); animation.addByPrefix('bluehold', 'blue hold piece'); setGraphicSize(Std.int(width * 0.7)); updateHitbox(); antialiasing = true; // colorSwap.colorToReplace = 0xFFF9393F; // colorSwap.newColor = 0xFF00FF00; // color = FlxG.random.color(); // color.saturation *= 4; // replaceColor(0xFFC1C1C1, FlxColor.RED); } colorSwap = new ColorSwap(); shader = colorSwap.shader; updateColors(); x += swagWidth * data.int; animation.play(data.colorName + 'Scroll'); // trace(prevNote); if (isSustainNote && prevNote != null) { alpha = 0.6; if (PreferencesMenu.getPref('downscroll')) angle = 180; x += width / 2; animation.play(data.colorName + 'holdend'); updateHitbox(); x -= width / 2; if (PlayState.instance.currentStageId.startsWith('school')) x += 30; if (prevNote.isSustainNote) { prevNote.animation.play(prevNote.colorName + 'hold'); prevNote.updateHitbox(); var scaleThing:Float = Math.round((Conductor.stepCrochet) * (0.45 * FlxMath.roundDecimal(SongLoad.getSpeed(), 2))); // get them a LIL closer together cuz the antialiasing blurs the edges if (antialiasing) scaleThing *= 1.0 + (1.0 / prevNote.frameHeight); prevNote.scale.y = scaleThing / prevNote.frameHeight; prevNote.updateHitbox(); } } } override function destroy() { prevNote = null; super.destroy(); } public function updateColors():Void { colorSwap.update(arrowColors[data.noteData]); } override function update(elapsed:Float) { super.update(elapsed); // mustPress indicates the player is the one pressing the key if (mustPress) { // miss on the NEXT frame so lag doesnt make u miss notes if (willMiss && !wasGoodHit) { tooLate = true; canBeHit = false; } else { if (!pastHalfWay && data.strumTime <= Conductor.songPosition) { pastHalfWay = true; noteSpeedMulti *= 2; } if (data.strumTime > Conductor.songPosition - HIT_WINDOW) { // * 0.5 if sustain note, so u have to keep holding it closer to all the way thru! if (data.strumTime < Conductor.songPosition + (HIT_WINDOW * (isSustainNote ? 0.5 : 1))) canBeHit = true; } else { canBeHit = true; willMiss = true; } } } else { canBeHit = false; if (data.strumTime <= Conductor.songPosition) wasGoodHit = true; } if (tooLate) { if (alpha > 0.3) alpha = 0.3; } } static public function fromData(data:NoteData, prevNote:Note, isSustainNote = false) { var result = new Note(data.strumTime, data.noteData, prevNote, isSustainNote); result.data = data; return result; } } typedef RawNoteData = { var strumTime:Float; var noteData:NoteType; var sustainLength:Float; var noteKind:NoteKind; } @:forward abstract NoteData(RawNoteData) { public function new(strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, noteKind = NORMAL) { this = { strumTime: strumTime, noteData: noteData, sustainLength: sustainLength, noteKind: noteKind } } public var note(get, never):NoteType; inline function get_note() return this.noteData.value; public var int(get, never):Int; inline function get_int() return this.noteData.int; public var dir(get, never):NoteDir; inline function get_dir() return this.noteData.value; public var dirName(get, never):String; inline function get_dirName() return dir.name; public var dirNameUpper(get, never):String; inline function get_dirNameUpper() return dir.nameUpper; public var color(get, never):NoteColor; inline function get_color() return this.noteData.value; public var colorName(get, never):String; inline function get_colorName() return color.name; public var colorNameUpper(get, never):String; inline function get_colorNameUpper() return color.nameUpper; public var highStakes(get, never):Bool; inline function get_highStakes() return this.noteData.highStakes; public var lowStakes(get, never):Bool; inline function get_lowStakes() return this.noteData.lowStakes; } enum abstract NoteType(Int) from Int to Int { // public var raw(get, never):Int; // inline function get_raw() return this; public var int(get, never):Int; inline function get_int() return this < 0 ? -this : this % 4; public var value(get, never):NoteType; inline function get_value() return int; public var highStakes(get, never):Bool; inline function get_highStakes() return this > 3; public var lowStakes(get, never):Bool; inline function get_lowStakes() return this < 0; } @:forward enum abstract NoteDir(NoteType) from Int to Int from NoteType { var LEFT = 0; var DOWN = 1; var UP = 2; var RIGHT = 3; var value(get, never):NoteDir; inline function get_value() return this.value; public var name(get, never):String; function get_name() { return switch (value) { case LEFT: "left"; case DOWN: "down"; case UP: "up"; case RIGHT: "right"; } } public var nameUpper(get, never):String; function get_nameUpper() { return switch (value) { case LEFT: "LEFT"; case DOWN: "DOWN"; case UP: "UP"; case RIGHT: "RIGHT"; } } } @:forward enum abstract NoteColor(NoteType) from Int to Int from NoteType { var PURPLE = 0; var BLUE = 1; var GREEN = 2; var RED = 3; var value(get, never):NoteColor; inline function get_value() return this.value; public var name(get, never):String; function get_name() { return switch (value) { case PURPLE: "purple"; case BLUE: "blue"; case GREEN: "green"; case RED: "red"; } } public var nameUpper(get, never):String; function get_nameUpper() { return switch (value) { case PURPLE: "PURPLE"; case BLUE: "BLUE"; case GREEN: "GREEN"; case RED: "RED"; } } } enum abstract NoteKind(String) from String to String { /** * The default note type. */ var NORMAL = "normal"; var ALT = "alt"; }