package funkin.game; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; import flixel.math.FlxMath; import flixel.util.FlxColor; import flixel.math.FlxRect; import flixel.math.FlxPoint; import funkin.system.Conductor; #if polymod import polymod.format.ParseRules.TargetSignatureElement; #end using StringTools; class Note extends FlxSprite { public var strumTime:Float = 0; public var mustPress:Bool = false; public var noteData:Int = 0; public var canBeHit:Bool = false; public var tooLate:Bool = false; public var wasGoodHit:Bool = false; public var prevNote:Note; public var strumID(get, never):Int; private function get_strumID() { var id = noteData % 4; if (id < 0) id = 0; return id; } public var sustainLength:Float = 0; public var isSustainNote:Bool = false; public var noteScore:Float = 1; public var noteTypeID:Int = 0; public var noteType(get, null):String; private function get_noteType() { return PlayState.instance.getNoteType(noteTypeID); } public static var swagWidth:Float = 160 * 0.7; public function new(strumTime:Float, noteData:Int, noteType:Int = 0, mustPress:Bool = true, ?prevNote:Note, ?sustainNote:Bool = false) { super(); if (prevNote == null) prevNote = this; this.prevNote = prevNote; this.mustPress = mustPress; this.noteTypeID = noteType; isSustainNote = sustainNote; x += 50; // MAKE SURE ITS DEFINITELY OFF SCREEN? y -= 2000; this.strumTime = strumTime; this.noteData = noteData; var daStage:String = PlayState.instance == null ? "stage" : PlayState.instance.curStage; switch (daStage) { case 'school' | 'schoolEvil': 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 * PlayState.daPixelZoom)); updateHitbox(); default: frames = Paths.getSparrowAtlas('NOTE_assets'); animation.addByPrefix('greenScroll', 'green0'); animation.addByPrefix('redScroll', 'red0'); animation.addByPrefix('blueScroll', 'blue0'); animation.addByPrefix('purpleScroll', 'purple0'); 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; } switch (noteData) { case 0: x += swagWidth * 0; animation.play('purpleScroll'); case 1: x += swagWidth * 1; animation.play('blueScroll'); case 2: x += swagWidth * 2; animation.play('greenScroll'); case 3: x += swagWidth * 3; animation.play('redScroll'); } // trace(prevNote); if (isSustainNote && prevNote != null) { noteScore * 0.2; alpha = 0.6; x += width / 2; switch (noteData) { case 2: animation.play('greenholdend'); case 3: animation.play('redholdend'); case 1: animation.play('blueholdend'); case 0: animation.play('purpleholdend'); } updateHitbox(); x -= width / 2; if (daStage.startsWith('school')) x += 30; if (prevNote.isSustainNote) { switch (prevNote.noteData) { case 0: prevNote.animation.play('purplehold'); case 1: prevNote.animation.play('bluehold'); case 2: prevNote.animation.play('greenhold'); case 3: prevNote.animation.play('redhold'); } prevNote.scale.y *= Conductor.stepCrochet / 100 * 1.5 * PlayState.instance.scrollSpeed; prevNote.updateHitbox(); // prevNote.setGraphicSize(); } } } public var angleOffsets:Bool = true; override function draw() { if (angleOffsets && Std.int(angle % 360) != 0) { // get default values var oldOffset = FlxPoint.get(offset.x, offset.y); var oldOrigin = FlxPoint.get(origin.x, origin.y); // get offset pos without origin var pos = FlxPoint.get(-offset.x, -offset.y); // updates sin and cos values for angle updateTrig(); var sin = _sinAngle; var cos = _cosAngle; var anglePos = FlxPoint.get( (-cos * pos.x) + (-sin * pos.y), (-sin * pos.x) + (-cos * pos.y)); // applies new values x -= anglePos.x; y -= anglePos.y; offset.set(); origin.set(oldOrigin.x - oldOffset.x, oldOrigin.y - oldOffset.y); // draw super.draw(); // reset values offset.set(oldOffset.x, oldOffset.y); origin.set(oldOrigin.x, oldOrigin.y); x += anglePos.x; y += anglePos.y; // put flxpoints back in the recycling pool oldOffset.put(); oldOrigin.put(); pos.put(); anglePos.put(); return; } super.draw(); } override function update(elapsed:Float) { super.update(elapsed); if (mustPress) { // The * 0.5 is so that it's easier to hit them too late, instead of too early if (strumTime > Conductor.songPosition - Conductor.safeZoneOffset && strumTime < Conductor.songPosition + (Conductor.safeZoneOffset * 0.5)) canBeHit = true; else canBeHit = false; if (strumTime < Conductor.songPosition - Conductor.safeZoneOffset && !wasGoodHit) tooLate = true; } else canBeHit = false; if (tooLate) { if (alpha > 0.3) alpha = 0.3; } } public function updateClipRect(strum:Strum) { var t = FlxMath.bound((Conductor.songPosition - strumTime) / (height / (0.45 * FlxMath.roundDecimal(strum.getScrollSpeed(), 2))), 0, 1); var swagRect = new FlxRect(0, t * frameHeight, frameWidth, frameHeight); setClipRect(swagRect); } public function setClipRect(rect:FlxRect) { // TODO: Shader ClipRect like in Yoshi Engine for smoother sustains this.clipRect = rect; } }