Files
HEIHUAaandGitHub 9d9640071c NoteGroup optimize (#881)
* NoteGroup optimize

* CoolUtil.bound faster
2026-01-21 07:26:55 +07:00

336 lines
9.5 KiB
Haxe

package funkin.game;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import funkin.backend.chart.ChartData;
import funkin.backend.scripting.events.note.NoteCreationEvent;
import funkin.backend.system.Conductor;
using StringTools;
@:allow(funkin.game.PlayState)
class Note extends FlxSprite
{
public var extra:Map<String, Dynamic> = [];
public var strumTime:Float = 0;
public var mustPress(get, never):Bool;
public var strumLine(default, set):StrumLine;
private function set_strumLine(strLine:StrumLine) {
if (this.strumLine != null) {
if (this.strumLine.notes != null)
this.strumLine.notes.remove(this, true);
strLine.notes.add(this);
strLine.notes.sortNotes();
}
return strumLine = strLine;
}
private inline function get_mustPress():Bool {
return false;
}
public var noteData:Int = 0;
public var canBeHit:Bool = false;
public var tooLate:Bool = false;
public var wasGoodHit:Bool = false;
/**
* Whenever that note should be avoided by Botplay.
*/
public var avoid:Bool = false;
/**
* The note that comes before this one (sustain and not)
*/
public var prevNote:Note;
/**
* The note that comes after this one (sustain and not)
*/
public var nextNote:Note;
/**
* The next sustain after this one
*/
public var nextSustain:Note;
/**
* The parent of the sustain.
*
* If this note is not sustain, this will be null.
*/
public var sustainParent:Null<Note>;
/**
* Number of active sustain pieces attached to this note
*
* Increases by 1 every time a hold piece is initialized.
*
* Decreases by 1 every time a hold piece gets destroyed.
*/
public var tailCount:Int = 0;
/**
* Name of the splash.
*/
public var splash:String = "default";
public var strumID(get, never):Int;
private function get_strumID() {
var id = noteData % strumLine.members.length;
if (id < 0) id = 0;
return id;
}
public var sustainLength:Float = 0;
public var isSustainNote:Bool = false;
public var noSustainClip:Bool = false;
public var flipSustain:Bool = true;
public var noteTypeID:Int = 0;
// TO APPLY THOSE ON A SINGLE NOTE
public var scrollSpeed:Null<Float> = null;
public var noteAngle:Null<Float> = null;
public var copyStrumAngle:Bool = true;
public var updateNotesPosX:Bool = true;
public var updateNotesPosY:Bool = true;
public var updateFlipY:Bool = true;
public var noteType(get, never):String;
@:dox(hide) public var __strumCameras:Array<FlxCamera> = null;
@:dox(hide) public var __strum:Strum = null;
@:dox(hide) public var __noteAngle:Float = 0;
private function get_noteType() {
if (PlayState.instance == null) return null;
return PlayState.instance.getNoteType(noteTypeID);
}
public static var swagWidth:Float = 160 * 0.7; // TODO: remove this
private static var __customNoteTypeExists:Map<String, Bool> = [];
public var animSuffix:String = null;
// Deprecated?
@:dox(hide) public var tripTimer:Float = 0; // ranges from 0 to 1
private static function customTypePathExists(path:String) {
if (__customNoteTypeExists.exists(path))
return __customNoteTypeExists[path];
return __customNoteTypeExists[path] = Assets.exists(path);
}
static var DEFAULT_FIELDS:Array<String> = ["time", "id", "type", "sLen"];
public function new(strumLine:StrumLine, noteData:ChartNote, sustain:Bool = false, sustainLength:Float = 0, sustainOffset:Float = 0, ?prev:Note)
{
super();
moves = false;
if(prev != null)
this.prevNote = prev;
else
this.prevNote = strumLine.notes.members.last();
if (this.prevNote != null) this.prevNote.nextNote = this;
this.noteTypeID = noteData.type.getDefault(0);
this.isSustainNote = sustain;
this.sustainLength = sustainLength;
this.strumLine = strumLine;
for(field in Reflect.fields(noteData)) if(!DEFAULT_FIELDS.contains(field))
this.extra.set(field, Reflect.field(noteData, field));
// work around to set the `sustainParent`
if (isSustainNote)
sustainParent = prevNote.isSustainNote ? prevNote.sustainParent : prevNote;
x += 50;
// MAKE SURE ITS DEFINITELY OFF SCREEN?
y -= 2000;
this.strumTime = noteData.time.getDefault(0) + sustainOffset;
this.noteData = noteData.id.getDefault(0);
var customType = Paths.image('game/notes/${this.noteType}');
var event = EventManager.get(NoteCreationEvent).recycle(this, strumID, this.noteType, noteTypeID, PlayState.instance.strumLines.members.indexOf(strumLine), mustPress,
(this.noteType != null && customTypePathExists(customType)) ? 'game/notes/${this.noteType}' : 'game/notes/default', @:privateAccess strumLine.strumScale * Flags.DEFAULT_NOTE_SCALE, animSuffix);
if (PlayState.instance != null)
event = PlayState.instance.gameAndCharsEvent("onNoteCreation", event);
this.animSuffix = event.animSuffix;
if (!event.cancelled) {
switch (event.noteType)
{
// case "My Custom Note Type": // hardcoding note types
default:
frames = Paths.getFrames(event.noteSprite);
switch(event.strumID % 4) {
case 0:
animation.addByPrefix('scroll', 'purple0');
animation.addByPrefix('hold', 'purple hold piece');
animation.addByPrefix("holdend", "pruple end hold");
if (animation.exists("holdend") != true) // null or false
animation.addByPrefix('holdend', 'purple hold end');
case 1:
animation.addByPrefix('scroll', 'blue0');
animation.addByPrefix('hold', 'blue hold piece');
animation.addByPrefix('holdend', 'blue hold end');
case 2:
animation.addByPrefix('scroll', 'green0');
animation.addByPrefix('hold', 'green hold piece');
animation.addByPrefix('holdend', 'green hold end');
case 3:
animation.addByPrefix('scroll', 'red0');
animation.addByPrefix('hold', 'red hold piece');
animation.addByPrefix('holdend', 'red hold end');
}
scale.set(event.noteScale, event.noteScale);
antialiasing = true;
}
}
updateHitbox();
if (isSustainNote && prevNote != null)
{
alpha = 0.6;
animation.play('holdend');
updateHitbox();
if (prevNote.isSustainNote)
{
prevNote.nextSustain = this;
prevNote.animation.play('hold');
}
} else {
animation.play("scroll");
}
if (PlayState.instance != null) {
PlayState.instance.splashHandler.getSplashGroup(splash);
PlayState.instance.gameAndCharsEvent("onPostNoteCreation", event);
}
}
public var lastScrollSpeed:Null<Float> = null;
public var gapFix:SingleOrFloat = 0;
public var useAntialiasingFix(get, set):Bool;
inline function set_useAntialiasingFix(v:Bool) {
if(v != useAntialiasingFix) {
gapFix = v ? 1 : 0;
}
return v;
}
inline function get_useAntialiasingFix() {
return gapFix>0;
}
/**
* Whenever the position of the note should be relative to the strum position or not.
* For example, if this is true, a note at the position 0; 0 will be on the strum, instead of at the top left of the screen.
*/
public var strumRelativePos:Bool = true;
override function drawComplex(camera:FlxCamera) {
var downscrollCam = (camera is HudCamera ? ({var _:HudCamera=cast camera;_;}).downscroll : false);
if (updateFlipY) flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
if (downscrollCam) {
frameOffset.y += __notePosFrameOffset.y * 2;
super.drawComplex(camera);
frameOffset.y -= __notePosFrameOffset.y * 2;
} else
super.drawComplex(camera);
}
static var __notePosFrameOffset:FlxPoint = new FlxPoint();
static var __posPoint:FlxPoint = new FlxPoint();
override function draw() {
@:privateAccess var oldDefaultCameras = FlxCamera._defaultCameras;
@:privateAccess if (__strumCameras != null) FlxCamera._defaultCameras = __strumCameras;
var negativeScroll = isSustainNote && nextSustain != null && lastScrollSpeed < 0;
if (negativeScroll) offset.y *= -1;
if (__strum != null && strumRelativePos) {
var pos = __posPoint.set(x, y);
setPosition(__strum.x, __strum.y);
__notePosFrameOffset.set(pos.x / scale.x, pos.y / scale.y);
frameOffset.x -= __notePosFrameOffset.x;
frameOffset.y -= __notePosFrameOffset.y;
this.frameOffsetAngle = __noteAngle;
super.draw();
this.frameOffsetAngle = 0;
frameOffset.x += __notePosFrameOffset.x;
frameOffset.y += __notePosFrameOffset.y;
setPosition(pos.x, pos.y);
//pos.put();
} else {
__notePosFrameOffset.set(0, 0);
super.draw();
}
if (negativeScroll) offset.y *= -1;
@:privateAccess FlxCamera._defaultCameras = oldDefaultCameras;
}
// The * 0.5 is so that it's easier to hit them too late, instead of too early
public var earlyPressWindow:Float = 0.5;
public var latePressWindow:Float = 1;
public function updateSustain(strum:Strum) {
var scrollSpeed = strum.getScrollSpeed(this);
if (lastScrollSpeed != scrollSpeed) {
lastScrollSpeed = scrollSpeed;
if (nextSustain != null) {
scale.y = (sustainLength * 0.45 * scrollSpeed) / frameHeight;
updateHitbox();
scale.y += gapFix / frameHeight;
}
}
updateSustainClip();
}
public function updateSustainClip() if (wasGoodHit && !noSustainClip) {
var t = CoolUtil.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1);
var rect = clipRect == null ? FlxRect.get() : clipRect;
clipRect = rect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t));
}
@:noCompletion
override function set_clipRect(rect:FlxRect):FlxRect
{
clipRect = rect;
if (frames != null)
frame = frames.frames[animation.frameIndex];
return rect;
}
public override function destroy() {
super.destroy();
clipRect = FlxDestroyUtil.put(clipRect);
}
}