assets submod

This commit is contained in:
Cameron Taylor 2024-04-29 01:20:52 -04:00
commit c5c22b245d
55 changed files with 612 additions and 273 deletions

6
.vscode/launch.json vendored
View file

@ -7,6 +7,12 @@
"type": "lime", "type": "lime",
"request": "launch" "request": "launch"
}, },
{
"name": "Debug",
"type": "lime",
"request": "launch",
"preLaunchTask": null
},
{ {
// Launch in browser // Launch in browser
"name": "HTML5 Debug", "name": "HTML5 Debug",

View file

@ -5,6 +5,9 @@ import flixel.util.FlxSignal;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.SongDataUtils; import funkin.data.song.SongDataUtils;
import funkin.save.Save;
import haxe.Timer;
import flixel.sound.FlxSound;
/** /**
* A core class which handles musical timing throughout the game, * A core class which handles musical timing throughout the game,
@ -89,6 +92,9 @@ class Conductor
*/ */
public var songPosition(default, null):Float = 0; public var songPosition(default, null):Float = 0;
var prevTimestamp:Float = 0;
var prevTime:Float = 0;
/** /**
* Beats per minute of the current song at the current time. * Beats per minute of the current song at the current time.
*/ */
@ -233,8 +239,41 @@ class Conductor
/** /**
* An offset set by the user to compensate for input lag. * An offset set by the user to compensate for input lag.
* No matter if you're using a local conductor or not, this always loads
* to/from the save file
*/ */
public var inputOffset:Float = 0; public var inputOffset(get, set):Int;
/**
* An offset set by the user to compensate for audio/visual lag
* No matter if you're using a local conductor or not, this always loads
* to/from the save file
*/
public var audioVisualOffset(get, set):Int;
function get_inputOffset():Int
{
return Save.instance.options.inputOffset;
}
function set_inputOffset(value:Int):Int
{
Save.instance.options.inputOffset = value;
Save.instance.flush();
return Save.instance.options.inputOffset;
}
function get_audioVisualOffset():Int
{
return Save.instance.options.audioVisualOffset;
}
function set_audioVisualOffset(value:Int):Int
{
Save.instance.options.audioVisualOffset = value;
Save.instance.flush();
return Save.instance.options.audioVisualOffset;
}
/** /**
* The number of beats in a measure. May be fractional depending on the time signature. * The number of beats in a measure. May be fractional depending on the time signature.
@ -353,16 +392,19 @@ class Conductor
* BPM, current step, etc. will be re-calculated based on the song position. * BPM, current step, etc. will be re-calculated based on the song position.
* *
* @param songPosition The current position in the song in milliseconds. * @param songPosition The current position in the song in milliseconds.
* Leave blank to use the `FlxG.sound.music` position. * Leave blank to use the FlxG.sound.music position.
* @param applyOffsets If it should apply the instrumentalOffset + formatOffset + audioVisualOffset
*/ */
public function update(?songPos:Float):Void public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false)
{ {
if (songPos == null) if (songPos == null)
{ {
// Take into account instrumental and file format song offsets. songPos = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
} }
// Take into account instrumental and file format song offsets.
songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0;
var oldMeasure:Float = this.currentMeasure; var oldMeasure:Float = this.currentMeasure;
var oldBeat:Float = this.currentBeat; var oldBeat:Float = this.currentBeat;
var oldStep:Float = this.currentStep; var oldStep:Float = this.currentStep;
@ -421,6 +463,35 @@ class Conductor
{ {
this.onMeasureHit.dispatch(); this.onMeasureHit.dispatch();
} }
// only update the timestamp if songPosition actually changed
// which it doesn't do every frame!
if (prevTime != this.songPosition)
{
// Update the timestamp for use in-between frames
prevTime = this.songPosition;
prevTimestamp = Std.int(Timer.stamp() * 1000);
}
}
/**
* Can be called in-between frames, usually for input related things
* that can potentially get processed on exact milliseconds/timestmaps.
* If you need song position, use `Conductor.instance.songPosition` instead
* for use in update() related functions.
* @param soundToCheck Which FlxSound object to check, defaults to FlxG.sound.music if no input
* @return Float
*/
public function getTimeWithDiff(?soundToCheck:FlxSound):Float
{
if (soundToCheck == null) soundToCheck = FlxG.sound.music;
// trace(this.songPosition);
@:privateAccess
this.songPosition = soundToCheck._channel.position;
// return this.songPosition + (Std.int(Timer.stamp() * 1000) - prevTimestamp);
// trace("\t--> " + this.songPosition);
return this.songPosition;
} }
/** /**
@ -468,7 +539,7 @@ class Conductor
} }
// Update currentStepTime // Update currentStepTime
this.update(Conductor.instance.songPosition); this.update(this.songPosition, false);
} }
/** /**
@ -587,7 +658,8 @@ class Conductor
} }
/** /**
* Add variables of the current Conductor instance to the Flixel debugger. * Adds Conductor fields to the Flixel debugger variable display.
* @param conductorToUse The conductor to use. Defaults to `Conductor.instance`.
*/ */
public static function watchQuick(?target:Conductor):Void public static function watchQuick(?target:Conductor):Void
{ {

View file

@ -16,14 +16,14 @@ import funkin.util.macro.MacroUtil;
import funkin.util.WindowUtil; import funkin.util.WindowUtil;
import funkin.play.PlayStatePlaylist; import funkin.play.PlayStatePlaylist;
import openfl.display.BitmapData; import openfl.display.BitmapData;
import funkin.data.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.event.SongEventRegistry; import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry; import funkin.data.stage.StageRegistry;
import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerRegistry; import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.AlbumRegistry; import funkin.data.freeplay.album.AlbumRegistry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.modding.module.ModuleHandler; import funkin.modding.module.ModuleHandler;
@ -304,7 +304,7 @@ class InitState extends FlxState
*/ */
function startLevel(levelId:String, difficultyId:String = 'normal'):Void function startLevel(levelId:String, difficultyId:String = 'normal'):Void
{ {
var currentLevel:funkin.ui.story.Level = funkin.data.level.LevelRegistry.instance.fetchEntry(levelId); var currentLevel:funkin.ui.story.Level = funkin.data.story.level.LevelRegistry.instance.fetchEntry(levelId);
if (currentLevel == null) if (currentLevel == null)
{ {

View file

@ -120,7 +120,7 @@ class DataParse
} }
} }
public static function backdropData(json:Json, name:String):funkin.data.dialogue.ConversationData.BackdropData public static function backdropData(json:Json, name:String):funkin.data.dialogue.conversation.ConversationData.BackdropData
{ {
switch (json.value) switch (json.value)
{ {
@ -152,7 +152,7 @@ class DataParse
} }
} }
public static function outroData(json:Json, name:String):Null<funkin.data.dialogue.ConversationData.OutroData> public static function outroData(json:Json, name:String):Null<funkin.data.dialogue.conversation.ConversationData.OutroData>
{ {
switch (json.value) switch (json.value)
{ {

View file

@ -0,0 +1,9 @@
# Dialogue Conversation Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue; package funkin.data.dialogue.conversation;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;

View file

@ -1,7 +1,7 @@
package funkin.data.dialogue; package funkin.data.dialogue.conversation;
import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.dialogue.Conversation;
import funkin.data.dialogue.ConversationData; import funkin.data.dialogue.conversation.ConversationData;
import funkin.play.cutscene.dialogue.ScriptedConversation; import funkin.play.cutscene.dialogue.ScriptedConversation;
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>

View file

@ -0,0 +1,13 @@
# Dialogue Box Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0]
### Added
- Added an option to specify the font used by the dialogue box. Defaults to `Arial` if unspecified.
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue; package funkin.data.dialogue.dialoguebox;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;

View file

@ -1,7 +1,7 @@
package funkin.data.dialogue; package funkin.data.dialogue.dialoguebox;
import funkin.play.cutscene.dialogue.DialogueBox; import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.data.dialogue.DialogueBoxData; import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.play.cutscene.dialogue.ScriptedDialogueBox; import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>

View file

@ -0,0 +1,9 @@
# Dialogue Speaker Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue; package funkin.data.dialogue.speaker;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;

View file

@ -1,7 +1,7 @@
package funkin.data.dialogue; package funkin.data.dialogue.speaker;
import funkin.play.cutscene.dialogue.Speaker; import funkin.play.cutscene.dialogue.Speaker;
import funkin.data.dialogue.SpeakerData; import funkin.data.dialogue.speaker.SpeakerData;
import funkin.play.cutscene.dialogue.ScriptedSpeaker; import funkin.play.cutscene.dialogue.ScriptedSpeaker;
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>

View file

@ -1,4 +1,4 @@
package funkin.data.freeplay; package funkin.data.freeplay.album;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;

View file

@ -1,7 +1,7 @@
package funkin.data.freeplay; package funkin.data.freeplay.album;
import funkin.ui.freeplay.Album; import funkin.ui.freeplay.Album;
import funkin.data.freeplay.AlbumData; import funkin.data.freeplay.album.AlbumData;
import funkin.ui.freeplay.ScriptedAlbum; import funkin.ui.freeplay.ScriptedAlbum;
class AlbumRegistry extends BaseRegistry<Album, AlbumData> class AlbumRegistry extends BaseRegistry<Album, AlbumData>

View file

@ -0,0 +1,9 @@
# Freeplay Album Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -0,0 +1,36 @@
# Song Chart Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.2]
### Added
- Added `playData.previewStart` and `playData.previewEnd` fields to specify when in the song should the song's audio should be played as a preview in Freeplay.
## [2.2.1]
### Added
- Added `playData.offsets` field to specify instrumental and vocal offsets.
## [2.2.0]
### Added
- Added `playData.album` to specify the album art to display in Freeplay.
- Added `playData.ratings` for difficulty ratings displayed in Freeplay.
### Changed
- Renamed `playData.noteSkin` to `playData.noteStyle`.
## [2.1.0]
### Changed
- Rearranged the `playData` field.
- Refactored the `playableChars`
### Removed
- Removed the `variation` field.
## [2.0.0]
Full refactor of the chart format for improved structure.
### Added
- Added a semantic version field for migration tracking.
## [1.0.0]
Initial version from 2020.

View file

@ -0,0 +1,14 @@
# Story Mode Level Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.1]
### Added
- Added the ability to specify a hexadecimal color in the `assetPath` field instead of a texture key.
- In this case, the `scale` property will be used to determine the size of the rectangle in pixels.
## [1.0.0]
Initial release.

View file

@ -11,7 +11,7 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
* Handle breaking changes by incrementing this value * Handle breaking changes by incrementing this value
* and adding migration to the `migrateStageData()` function. * and adding migration to the `migrateStageData()` function.
*/ */
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.1"; public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.0";
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x"; public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";

View file

@ -0,0 +1,9 @@
# Story Mode Level Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.level; package funkin.data.story.level;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;
@ -13,7 +13,7 @@ typedef LevelData =
* When making changes to the level data format, this should be incremented, * When making changes to the level data format, this should be incremented,
* and a migration function should be added to LevelDataParser to handle old versions. * and a migration function should be added to LevelDataParser to handle old versions.
*/ */
@:default(funkin.data.level.LevelRegistry.LEVEL_DATA_VERSION) @:default(funkin.data.story.level.LevelRegistry.LEVEL_DATA_VERSION)
var version:String; var version:String;
/** /**

View file

@ -1,7 +1,7 @@
package funkin.data.level; package funkin.data.story.level;
import funkin.ui.story.Level; import funkin.ui.story.Level;
import funkin.data.level.LevelData; import funkin.data.story.level.LevelData;
import funkin.ui.story.ScriptedLevel; import funkin.ui.story.ScriptedLevel;
class LevelRegistry extends BaseRegistry<Level, LevelData> class LevelRegistry extends BaseRegistry<Level, LevelData>

View file

@ -29,7 +29,7 @@ class HSVShader extends FlxRuntimeShader
function set_saturation(value:Float):Float function set_saturation(value:Float):Float
{ {
this.setFloat('sat', value); this.setFloat('_sat', value);
this.saturation = value; this.saturation = value;
return this.saturation; return this.saturation;
@ -37,7 +37,7 @@ class HSVShader extends FlxRuntimeShader
function set_value(value:Float):Float function set_value(value:Float):Float
{ {
this.setFloat('val', value); this.setFloat('_val', value);
this.value = value; this.value = value;
return this.value; return this.value;

View file

@ -293,8 +293,9 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
// TODO: Remove this line with SDL3 when timestamps change meaning. // TODO: Remove this line with SDL3 when timestamps change meaning.
// This is because SDL3's timestamps are measured in nanoseconds, not milliseconds. // This is because SDL3's timestamps are measured in nanoseconds, not milliseconds.
timestamp *= Constants.NS_PER_MS; timestamp *= Constants.NS_PER_MS; // 18126000000 38367000000
timestamp -= Conductor.instance.inputOffset * Constants.NS_PER_MS;
// trace(timestamp);
updateKeyStates(key, true); updateKeyStates(key, true);
if (getInputByKey(key)?.justPressed ?? false) if (getInputByKey(key)?.justPressed ?? false)

View file

@ -1,14 +1,14 @@
package funkin.modding; package funkin.modding;
import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerRegistry; import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.event.SongEventRegistry; import funkin.data.event.SongEventRegistry;
import funkin.data.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.data.stage.StageRegistry; import funkin.data.stage.StageRegistry;
import funkin.data.freeplay.AlbumRegistry; import funkin.data.freeplay.album.AlbumRegistry;
import funkin.modding.module.ModuleHandler; import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.save.Save; import funkin.save.Save;

View file

@ -2,14 +2,16 @@ package funkin.play;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
import flixel.FlxG; import flixel.FlxG;
import flixel.util.FlxTimer;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup; import flixel.group.FlxSpriteGroup;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import flixel.sound.FlxSound;
import flixel.text.FlxText; import flixel.text.FlxText;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.ui.freeplay.FreeplayState; import funkin.ui.freeplay.FreeplayState;
@ -17,6 +19,7 @@ import funkin.graphics.FunkinSprite;
import funkin.play.cutscene.VideoCutscene; import funkin.play.cutscene.VideoCutscene;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.ui.AtlasText; import funkin.ui.AtlasText;
import funkin.ui.debug.latency.LatencyState;
import funkin.ui.MusicBeatSubState; import funkin.ui.MusicBeatSubState;
import funkin.ui.transition.StickerSubState; import funkin.ui.transition.StickerSubState;

View file

@ -19,7 +19,7 @@ import flixel.util.FlxTimer;
import funkin.api.newgrounds.NGio; import funkin.api.newgrounds.NGio;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.audio.VoicesGroup; import funkin.audio.VoicesGroup;
import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.event.SongEventRegistry; import funkin.data.event.SongEventRegistry;
import funkin.data.notestyle.NoteStyleData; import funkin.data.notestyle.NoteStyleData;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
@ -890,7 +890,8 @@ class PlayState extends MusicBeatSubState
{ {
if (isInCountdown) if (isInCountdown)
{ {
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000); // Do NOT apply offsets at this point, because they already got applied the previous frame!
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000, false);
if (Conductor.instance.songPosition >= (startTimestamp)) startSong(); if (Conductor.instance.songPosition >= (startTimestamp)) startSong();
} }
} }
@ -2049,10 +2050,10 @@ class PlayState extends MusicBeatSubState
{ {
if (note == null) continue; if (note == null) continue;
// TODO: Does this properly account for offsets? // TODO: Are offsets being accounted for in the correct direction?
var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS; var hitWindowStart = note.strumTime + Conductor.instance.inputOffset - Constants.HIT_WINDOW_MS;
var hitWindowCenter = note.strumTime; var hitWindowCenter = note.strumTime + Conductor.instance.inputOffset;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; var hitWindowEnd = note.strumTime + Conductor.instance.inputOffset + Constants.HIT_WINDOW_MS;
if (Conductor.instance.songPosition > hitWindowEnd) if (Conductor.instance.songPosition > hitWindowEnd)
{ {

View file

@ -42,7 +42,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
if (PlayState.instance.currentStageId.startsWith('school')) if (PlayState.instance.currentStageId.startsWith('school'))
{ {
rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.65)); rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
rating.antialiasing = false; rating.antialiasing = false;
} }
else else
@ -133,7 +133,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
if (PlayState.instance.currentStageId.startsWith('school')) if (PlayState.instance.currentStageId.startsWith('school'))
{ {
numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE)); numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 0.7));
numScore.antialiasing = false; numScore.antialiasing = false;
} }
else else

View file

@ -8,13 +8,13 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxSort; import flixel.util.FlxSort;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.data.dialogue.ConversationData; import funkin.data.dialogue.conversation.ConversationData;
import funkin.data.dialogue.ConversationData.DialogueEntryData; import funkin.data.dialogue.conversation.ConversationData.DialogueEntryData;
import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxData; import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerData; import funkin.data.dialogue.speaker.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry; import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.IRegistryEntry; import funkin.data.IRegistryEntry;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;

View file

@ -11,8 +11,8 @@ import funkin.modding.events.ScriptEvent;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.modding.IScriptedClass.IDialogueScriptedClass; import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import funkin.data.dialogue.DialogueBoxData; import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData> class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData>
{ {

View file

@ -6,8 +6,8 @@ import funkin.modding.events.ScriptEvent;
import flixel.graphics.frames.FlxFramesCollection; import flixel.graphics.frames.FlxFramesCollection;
import funkin.util.assets.FlxAnimationUtil; import funkin.util.assets.FlxAnimationUtil;
import funkin.modding.IScriptedClass.IDialogueScriptedClass; import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import funkin.data.dialogue.SpeakerData; import funkin.data.dialogue.speaker.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry; import funkin.data.dialogue.speaker.SpeakerRegistry;
/** /**
* The character sprite which displays during dialogue. * The character sprite which displays during dialogue.

View file

@ -127,7 +127,7 @@ class FocusCameraSongEvent extends SongEvent
switch (ease) switch (ease)
{ {
case 'CLASSIC': // Old-school. No ease. Just set follow point. case 'CLASSIC': // Old-school. No ease. Just set follow point.
PlayState.instance.resetCamera(); PlayState.instance.resetCamera(false, true);
PlayState.instance.cameraFollowPoint.setPosition(targetX, targetY); PlayState.instance.cameraFollowPoint.setPosition(targetX, targetY);
case 'INSTANT': // Instant ease. Duration is automatically 0. case 'INSTANT': // Instant ease. Duration is automatically 0.
PlayState.instance.tweenCameraToPosition(targetX, targetY, 0); PlayState.instance.tweenCameraToPosition(targetX, targetY, 0);

View file

@ -15,6 +15,7 @@ import funkin.play.notes.SustainTrail;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.ui.options.PreferencesMenu; import funkin.ui.options.PreferencesMenu;
import funkin.util.SortUtil; import funkin.util.SortUtil;
import funkin.modding.events.ScriptEvent;
/** /**
* A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player. * A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player.
@ -45,6 +46,25 @@ class Strumline extends FlxSpriteGroup
*/ */
public var isPlayer:Bool; public var isPlayer:Bool;
/**
* Usually you want to keep this as is, but if you are using a Strumline and
* playing a sound that has it's own conductor, set this (LatencyState for example)
*/
public var conductorInUse(get, set):Conductor;
var _conductorInUse:Null<Conductor>;
function get_conductorInUse():Conductor
{
if (_conductorInUse == null) return Conductor.instance;
return _conductorInUse;
}
function set_conductorInUse(value:Conductor):Conductor
{
return _conductorInUse = value;
}
/** /**
* The notes currently being rendered on the strumline. * The notes currently being rendered on the strumline.
* This group iterates over this every frame to update note positions. * This group iterates over this every frame to update note positions.
@ -151,24 +171,10 @@ class Strumline extends FlxSpriteGroup
updateNotes(); updateNotes();
} }
var frameMax:Int;
var animFinishedEver:Bool;
/** /**
* Get a list of notes within + or - the given strumtime. * Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
* @param strumTime The current time. * @return An array of `NoteSprite` objects.
* @param hitWindow The hit window to check.
*/ */
public function getNotesInRange(strumTime:Float, hitWindow:Float):Array<NoteSprite>
{
var hitWindowStart:Float = strumTime - hitWindow;
var hitWindowEnd:Float = strumTime + hitWindow;
return notes.members.filter(function(note:NoteSprite) {
return note != null && note.alive && !note.hasBeenHit && note.strumTime >= hitWindowStart && note.strumTime <= hitWindowEnd;
});
}
public function getNotesMayHit():Array<NoteSprite> public function getNotesMayHit():Array<NoteSprite>
{ {
return notes.members.filter(function(note:NoteSprite) { return notes.members.filter(function(note:NoteSprite) {
@ -176,6 +182,10 @@ class Strumline extends FlxSpriteGroup
}); });
} }
/**
* Return hold notes that are within `Constants.HIT_WINDOW` ms of the strumline.
* @return An array of `SustainTrail` objects.
*/
public function getHoldNotesHitOrMissed():Array<SustainTrail> public function getHoldNotesHitOrMissed():Array<SustainTrail>
{ {
return holdNotes.members.filter(function(holdNote:SustainTrail) { return holdNotes.members.filter(function(holdNote:SustainTrail) {
@ -183,19 +193,6 @@ class Strumline extends FlxSpriteGroup
}); });
} }
public function getHoldNotesInRange(strumTime:Float, hitWindow:Float):Array<SustainTrail>
{
var hitWindowStart:Float = strumTime - hitWindow;
var hitWindowEnd:Float = strumTime + hitWindow;
return holdNotes.members.filter(function(note:SustainTrail) {
return note != null
&& note.alive
&& note.strumTime >= hitWindowStart
&& (note.strumTime + note.fullSustainLength) <= hitWindowEnd;
});
}
public function getNoteSprite(noteData:SongNoteData):NoteSprite public function getNoteSprite(noteData:SongNoteData):NoteSprite
{ {
if (noteData == null) return null; if (noteData == null) return null;
@ -280,7 +277,7 @@ class Strumline extends FlxSpriteGroup
* @param strumTime * @param strumTime
* @return Float * @return Float
*/ */
static function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float public function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float
{ {
// Make the note move faster visually as it moves offscreen. // Make the note move faster visually as it moves offscreen.
// var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0; // var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
@ -288,7 +285,8 @@ class Strumline extends FlxSpriteGroup
var vwoosh:Float = 1.0; var vwoosh:Float = 1.0;
var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
return Constants.PIXELS_PER_MS * (Conductor.instance.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1); return
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
} }
function updateNotes():Void function updateNotes():Void
@ -301,8 +299,8 @@ class Strumline extends FlxSpriteGroup
// if (conductorInUse.currentStep == 0) nextNoteIndex = 0; // if (conductorInUse.currentStep == 0) nextNoteIndex = 0;
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0; var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS; var hitWindowStart:Float = conductorInUse.songPosition - Constants.HIT_WINDOW_MS;
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS; var renderWindowStart:Float = conductorInUse.songPosition + RENDER_DISTANCE_MS;
for (noteIndex in nextNoteIndex...noteData.length) for (noteIndex in nextNoteIndex...noteData.length)
{ {
@ -351,7 +349,7 @@ class Strumline extends FlxSpriteGroup
{ {
if (holdNote == null || !holdNote.alive) continue; if (holdNote == null || !holdNote.alive) continue;
if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote) if (conductorInUse.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote)
{ {
if (isPlayer && !isKeyHeld(holdNote.noteDirection)) if (isPlayer && !isKeyHeld(holdNote.noteDirection))
{ {
@ -365,7 +363,7 @@ class Strumline extends FlxSpriteGroup
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8; var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
if (holdNote.missedNote && Conductor.instance.songPosition >= renderWindowEnd) if (holdNote.missedNote && conductorInUse.songPosition >= renderWindowEnd)
{ {
// Hold note is offscreen, kill it. // Hold note is offscreen, kill it.
holdNote.visible = false; holdNote.visible = false;
@ -422,13 +420,13 @@ class Strumline extends FlxSpriteGroup
holdNote.cover.kill(); holdNote.cover.kill();
} }
} }
else if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote) else if (conductorInUse.songPosition > holdNote.strumTime && holdNote.hitNote)
{ {
// Hold note is currently being hit, clip it off. // Hold note is currently being hit, clip it off.
holdConfirm(holdNote.noteDirection); holdConfirm(holdNote.noteDirection);
holdNote.visible = true; holdNote.visible = true;
holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.instance.songPosition; holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - conductorInUse.songPosition;
if (holdNote.sustainLength <= 10) if (holdNote.sustainLength <= 10)
{ {

View file

@ -87,6 +87,8 @@ class Save
zoomCamera: true, zoomCamera: true,
debugDisplay: false, debugDisplay: false,
autoPause: true, autoPause: true,
inputOffset: 0,
audioVisualOffset: 0,
controls: controls:
{ {
@ -866,6 +868,18 @@ typedef SaveDataOptions =
*/ */
var autoPause:Bool; var autoPause:Bool;
/**
* Offset the users inputs by this many ms.
* @default `0`
*/
var inputOffset:Int;
/**
* Affects the delay between the audio and the visuals during gameplay
* @default `0`
*/
var audioVisualOffset:Int;
var controls: var controls:
{ {
var p1: var p1:

View file

@ -0,0 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.3] - 2024-01-09
### Added
- `inputOffset:Float` to `SongDataOptions`
- `audioVisualOffset:Float` to `SongDataOptions`

View file

@ -29,6 +29,21 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public var leftWatermarkText:FlxText = null; public var leftWatermarkText:FlxText = null;
public var rightWatermarkText:FlxText = null; public var rightWatermarkText:FlxText = null;
public var conductorInUse(get, set):Conductor;
var _conductorInUse:Null<Conductor>;
function get_conductorInUse():Conductor
{
if (_conductorInUse == null) return Conductor.instance;
return _conductorInUse;
}
function set_conductorInUse(value:Conductor):Conductor
{
return _conductorInUse = value;
}
public function new() public function new()
{ {
super(); super();
@ -111,7 +126,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function stepHit():Bool public function stepHit():Bool
{ {
var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); var event = new SongTimeScriptEvent(SONG_STEP_HIT, conductorInUse.currentBeat, conductorInUse.currentStep);
dispatchEvent(event); dispatchEvent(event);
@ -122,7 +137,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function beatHit():Bool public function beatHit():Bool
{ {
var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); var event = new SongTimeScriptEvent(SONG_BEAT_HIT, conductorInUse.currentBeat, conductorInUse.currentStep);
dispatchEvent(event); dispatchEvent(event);

View file

@ -21,6 +21,21 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
public var leftWatermarkText:FlxText = null; public var leftWatermarkText:FlxText = null;
public var rightWatermarkText:FlxText = null; public var rightWatermarkText:FlxText = null;
public var conductorInUse(get, set):Conductor;
var _conductorInUse:Null<Conductor>;
function get_conductorInUse():Conductor
{
if (_conductorInUse == null) return Conductor.instance;
return _conductorInUse;
}
function set_conductorInUse(value:Conductor):Conductor
{
return _conductorInUse = value;
}
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT) public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
{ {
super(); super();
@ -51,7 +66,6 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
override function update(elapsed:Float):Void override function update(elapsed:Float):Void
{ {
// 3.59% CPU Usage (100% is FlxTypedGroup#update() and most of that is updating each member.)
super.update(elapsed); super.update(elapsed);
// Emergency exit button. // Emergency exit button.
@ -62,11 +76,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
// Display Conductor info in the watch window. // Display Conductor info in the watch window.
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
Conductor.watchQuick(conductorInUse);
// 0.09% CPU Usage?
Conductor.watchQuick();
// 4.31% CPU Usage
dispatchEvent(new UpdateScriptEvent(elapsed)); dispatchEvent(new UpdateScriptEvent(elapsed));
} }
@ -94,7 +105,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
*/ */
public function stepHit():Bool public function stepHit():Bool
{ {
var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, conductorInUse.currentBeat, conductorInUse.currentStep);
dispatchEvent(event); dispatchEvent(event);
@ -110,7 +121,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
*/ */
public function beatHit():Bool public function beatHit():Bool
{ {
var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, conductorInUse.currentBeat, conductorInUse.currentStep);
dispatchEvent(event); dispatchEvent(event);

View file

@ -23,7 +23,9 @@ class DebugMenuSubState extends MusicBeatSubState
override function create():Void override function create():Void
{ {
FlxTransitionableState.skipNextTransIn = true;
super.create(); super.create();
bgColor = 0x00000000; bgColor = 0x00000000;
// Create an object for the camera to track. // Create an object for the camera to track.
@ -48,9 +50,12 @@ class DebugMenuSubState extends MusicBeatSubState
items.onChange.add(onMenuChange); items.onChange.add(onMenuChange);
add(items); add(items);
FlxTransitionableState.skipNextTransIn = true;
// Create each menu item. // Create each menu item.
// Call onMenuChange when the first item is created to move the camera . // Call onMenuChange when the first item is created to move the camera .
onMenuChange(createItem("CHART EDITOR", openChartEditor)); onMenuChange(createItem("CHART EDITOR", openChartEditor));
createItem("Input Offset Testing", openInputOffsetTesting);
createItem("ANIMATION EDITOR", openAnimationEditor); createItem("ANIMATION EDITOR", openAnimationEditor);
createItem("STAGE EDITOR", openStageEditor); createItem("STAGE EDITOR", openStageEditor);
createItem("TEST STICKERS", testStickers); createItem("TEST STICKERS", testStickers);
@ -92,6 +97,12 @@ class DebugMenuSubState extends MusicBeatSubState
FlxG.switchState(() -> new ChartEditorState()); FlxG.switchState(() -> new ChartEditorState());
} }
function openInputOffsetTesting()
{
openSubState(new funkin.ui.debug.latency.LatencyState());
trace('Input Offset Testing');
}
function openAnimationEditor() function openAnimationEditor()
{ {
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState()); FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());

View file

@ -5,13 +5,13 @@ import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatState;
import funkin.data.dialogue.ConversationData; import funkin.data.dialogue.conversation.ConversationData;
import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxData; import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerData; import funkin.data.dialogue.speaker.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry; import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.AlbumRegistry; import funkin.data.freeplay.album.AlbumRegistry;
import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.DialogueBox; import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.play.cutscene.dialogue.Speaker; import funkin.play.cutscene.dialogue.Speaker;

View file

@ -7,7 +7,6 @@ import flash.text.TextField;
import flash.text.TextFormatAlign; import flash.text.TextFormatAlign;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import flixel.system.debug.DebuggerUtil; import flixel.system.debug.DebuggerUtil;
import flixel.system.debug.stats.Stats;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil; import flixel.util.FlxDestroyUtil;
@ -16,13 +15,31 @@ import flixel.util.FlxDestroyUtil;
* SHAMELESSLY STOLEN FROM FLIXEL * SHAMELESSLY STOLEN FROM FLIXEL
* https://github.com/HaxeFlixel/flixel/blob/master/flixel/system/debug/stats/StatsGraph.hx * https://github.com/HaxeFlixel/flixel/blob/master/flixel/system/debug/stats/StatsGraph.hx
*/ */
#if FLX_DEBUG
class CoolStatsGraph extends Sprite class CoolStatsGraph extends Sprite
{ {
static inline var AXIS_COLOR:FlxColor = 0xffffff; static inline var AXIS_COLOR:FlxColor = 0xffffff;
static inline var AXIS_ALPHA:Float = 0.5; static inline var AXIS_ALPHA:Float = 0.5;
static inline var HISTORY_MAX:Int = 500; static inline var HISTORY_MAX:Int = 500;
/**
* How often to update the stats, in ms. The lower, the more performance-intense!
*/
static inline var UPDATE_DELAY:Int = 250;
/**
* The initial width of the stats window.
*/
static inline var INITIAL_WIDTH:Int = 160;
static inline var FPS_COLOR:FlxColor = 0xff96ff00;
static inline var MEMORY_COLOR:FlxColor = 0xff009cff;
static inline var DRAW_TIME_COLOR:FlxColor = 0xffA60004;
static inline var UPDATE_TIME_COLOR:FlxColor = 0xffdcd400;
public static inline var LABEL_COLOR:FlxColor = 0xaaffffff;
public static inline var TEXT_SIZE:Int = 11;
public static inline var DECIMALS:Int = 1;
public var minLabel:TextField; public var minLabel:TextField;
public var curLabel:TextField; public var curLabel:TextField;
public var maxLabel:TextField; public var maxLabel:TextField;
@ -45,6 +62,7 @@ class CoolStatsGraph extends Sprite
public function new(X:Int, Y:Int, Width:Int, Height:Int, GraphColor:FlxColor, Unit:String, LabelWidth:Int = 45, ?Label:String) public function new(X:Int, Y:Int, Width:Int, Height:Int, GraphColor:FlxColor, Unit:String, LabelWidth:Int = 45, ?Label:String)
{ {
super(); super();
x = X; x = X;
y = Y; y = Y;
_width = Width - LabelWidth; _width = Width - LabelWidth;
@ -57,11 +75,11 @@ class CoolStatsGraph extends Sprite
_axis = new Shape(); _axis = new Shape();
_axis.x = _labelWidth + 10; _axis.x = _labelWidth + 10;
maxLabel = DebuggerUtil.createTextField(0, 0, Stats.LABEL_COLOR, Stats.TEXT_SIZE); maxLabel = DebuggerUtil.createTextField(0, 0, LABEL_COLOR, TEXT_SIZE);
curLabel = DebuggerUtil.createTextField(0, (_height / 2) - (Stats.TEXT_SIZE / 2), graphColor, Stats.TEXT_SIZE); curLabel = DebuggerUtil.createTextField(0, (_height / 2) - (TEXT_SIZE / 2), graphColor, TEXT_SIZE);
minLabel = DebuggerUtil.createTextField(0, _height - Stats.TEXT_SIZE, Stats.LABEL_COLOR, Stats.TEXT_SIZE); minLabel = DebuggerUtil.createTextField(0, _height - TEXT_SIZE, LABEL_COLOR, TEXT_SIZE);
avgLabel = DebuggerUtil.createTextField(_labelWidth + 20, (_height / 2) - (Stats.TEXT_SIZE / 2) - 10, Stats.LABEL_COLOR, Stats.TEXT_SIZE); avgLabel = DebuggerUtil.createTextField(_labelWidth + 20, (_height / 2) - (TEXT_SIZE / 2) - 10, LABEL_COLOR, TEXT_SIZE);
avgLabel.width = _width; avgLabel.width = _width;
avgLabel.defaultTextFormat.align = TextFormatAlign.CENTER; avgLabel.defaultTextFormat.align = TextFormatAlign.CENTER;
avgLabel.alpha = 0.5; avgLabel.alpha = 0.5;
@ -136,7 +154,7 @@ class CoolStatsGraph extends Sprite
function formatValue(value:Float):String function formatValue(value:Float):String
{ {
return FlxMath.roundDecimal(value, Stats.DECIMALS) + " " + _unit; return FlxMath.roundDecimal(value, DECIMALS) + " " + _unit;
} }
public function average():Float public function average():Float
@ -157,4 +175,3 @@ class CoolStatsGraph extends Sprite
history = null; history = null;
} }
} }
#end

View file

@ -8,20 +8,27 @@ import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import funkin.ui.MusicBeatSubState; import funkin.ui.MusicBeatSubState;
import flixel.sound.FlxSound; import flixel.sound.FlxSound;
import flixel.system.debug.stats.StatsGraph;
import flixel.text.FlxText; import flixel.text.FlxText;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import funkin.audio.visualize.PolygonSpectogram; import funkin.audio.visualize.PolygonSpectogram;
import funkin.play.notes.NoteSprite; import funkin.play.notes.NoteSprite;
import funkin.ui.debug.latency.CoolStatsGraph; import funkin.ui.debug.latency.CoolStatsGraph;
import haxe.Timer;
import openfl.events.KeyboardEvent; import openfl.events.KeyboardEvent;
import funkin.input.PreciseInputManager;
import funkin.play.notes.Strumline;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.data.notestyle.NoteStyleData;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongData.SongNoteData;
import haxe.Timer;
import flixel.FlxCamera;
class LatencyState extends MusicBeatSubState class LatencyState extends MusicBeatSubState
{ {
var visualOffsetText:FlxText;
var offsetText:FlxText; var offsetText:FlxText;
var noteGrp:FlxTypedGroup<NoteSprite>; var noteGrp:Array<SongNoteData> = [];
var strumLine:FlxSprite; var strumLine:Strumline;
var blocks:FlxTypedGroup<FlxSprite>; var blocks:FlxTypedGroup<FlxSprite>;
@ -31,76 +38,81 @@ class LatencyState extends MusicBeatSubState
var beatTrail:FlxSprite; var beatTrail:FlxSprite;
var diffGrp:FlxTypedGroup<FlxText>; var diffGrp:FlxTypedGroup<FlxText>;
var offsetsPerBeat:Array<Int> = []; var offsetsPerBeat:Array<Null<Int>> = [];
var swagSong:HomemadeMusic; var swagSong:HomemadeMusic;
#if FLX_DEBUG var previousVolume:Float;
var funnyStatsGraph:CoolStatsGraph;
var realStats:CoolStatsGraph; var stateCamera:FlxCamera;
#end
/**
* A local conductor instance for this testing class, in-case we are in a PlayState
* because I'm too lazy to set the old variables for conductor stuff !
*/
var localConductor:Conductor;
// stores values of what the previous persistent draw/update stuff was, example if opened
// from pause menu, we want to NOT draw persistently, but then resume drawing once closed
var prevPersistentDraw:Bool;
var prevPersistentUpdate:Bool;
override function create() override function create()
{ {
super.create();
prevPersistentDraw = FlxG.state.persistentDraw;
prevPersistentUpdate = FlxG.state.persistentUpdate;
FlxG.state.persistentDraw = false;
FlxG.state.persistentUpdate = false;
localConductor = new Conductor();
conductorInUse = localConductor;
stateCamera = new FlxCamera(0, 0, FlxG.width, FlxG.height);
stateCamera.bgColor = FlxColor.BLACK;
FlxG.cameras.add(stateCamera);
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
add(bg);
if (FlxG.sound.music != null)
{
previousVolume = FlxG.sound.music.volume;
FlxG.sound.music.volume = 0; // only want to mute the volume, incase we are coming from pause menu
}
else
previousVolume = 1; // defaults to 1 if no music is playing 🤔 also fuck it, emoji in code comment
swagSong = new HomemadeMusic(); swagSong = new HomemadeMusic();
swagSong.loadEmbedded(Paths.sound('soundTest'), true); swagSong.loadEmbedded(Paths.sound('soundTest'), true);
swagSong.looped = true;
swagSong.play();
FlxG.sound.list.add(swagSong);
FlxG.sound.music = swagSong; PreciseInputManager.instance.onInputPressed.add(preciseInputPressed);
FlxG.sound.music.play();
#if FLX_DEBUG PreciseInputManager.instance.onInputReleased.add(preciseInputReleased);
funnyStatsGraph = new CoolStatsGraph(0, Std.int(FlxG.height / 2), FlxG.width, Std.int(FlxG.height / 2), FlxColor.PINK, "time");
FlxG.addChildBelowMouse(funnyStatsGraph);
realStats = new CoolStatsGraph(0, Std.int(FlxG.height / 2), FlxG.width, Std.int(FlxG.height / 2), FlxColor.YELLOW, "REAL"); localConductor.forceBPM(60);
FlxG.addChildBelowMouse(realStats);
#end
FlxG.stage.addEventListener(KeyboardEvent.KEY_DOWN, key -> {
trace(key.charCode);
if (key.charCode == 120) generateBeatStuff();
trace("\tEVENT PRESS: \t" + FlxG.sound.music.time + " " + Timer.stamp());
// trace(FlxG.sound.music.prevTimestamp);
trace(FlxG.sound.music.time);
trace("\tFR FR PRESS: \t" + swagSong.getTimeWithDiff());
// trace("\tREDDIT: \t" + swagSong.frfrTime + " " + Timer.stamp());
@:privateAccess
trace("\tREDDIT: \t" + FlxG.sound.music._channel.position + " " + Timer.stamp());
// trace("EVENT LISTENER: " + key);
});
// funnyStatsGraph.hi
Conductor.instance.forceBPM(60); Conductor.instance.forceBPM(60);
noteGrp = new FlxTypedGroup<NoteSprite>();
add(noteGrp);
diffGrp = new FlxTypedGroup<FlxText>(); diffGrp = new FlxTypedGroup<FlxText>();
add(diffGrp); add(diffGrp);
// var musSpec:PolygonSpectogram = new PolygonSpectogram(FlxG.sound.music, FlxColor.RED, FlxG.height, Math.floor(FlxG.height / 2)); for (beat in 0...Math.floor(swagSong.length / (localConductor.stepLengthMs * 2)))
// musSpec.x += 170;
// musSpec.scrollFactor.set();
// musSpec.waveAmplitude = 100;
// musSpec.realtimeVisLenght = 0.45;
// // musSpec.visType = FREQUENCIES;
// add(musSpec);
for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.instance.beatLengthMs))
{ {
var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.instance.beatLengthMs), FlxG.height - 15); var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * (localConductor.stepLengthMs * 2)), FlxG.height - 15);
beatTick.makeGraphic(2, 15); beatTick.makeGraphic(2, 15);
beatTick.alpha = 0.3; beatTick.alpha = 0.3;
add(beatTick); add(beatTick);
var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.instance.beatLengthMs), FlxG.height - 26, 0, "swag"); var offsetTxt:FlxText = new FlxText(songPosToX(beat * (localConductor.stepLengthMs * 2)), FlxG.height - 26, 0, "");
offsetTxt.alpha = 0.5; offsetTxt.alpha = 0.5;
diffGrp.add(offsetTxt); diffGrp.add(offsetTxt);
offsetsPerBeat.push(0); offsetsPerBeat.push(null);
} }
songVisFollowAudio = new FlxSprite(0, FlxG.height - 20).makeGraphic(2, 20, FlxColor.YELLOW); songVisFollowAudio = new FlxSprite(0, FlxG.height - 20).makeGraphic(2, 20, FlxColor.YELLOW);
@ -121,32 +133,92 @@ class LatencyState extends MusicBeatSubState
for (i in 0...8) for (i in 0...8)
{ {
var block = new FlxSprite(2, 50 * i).makeGraphic(48, 48); var block = new FlxSprite(2, ((FlxG.height / 8) + 2) * i).makeGraphic(Std.int(FlxG.height / 8), Std.int((FlxG.height / 8) - 4));
block.alpha = 0; block.alpha = 0.1;
blocks.add(block); blocks.add(block);
} }
for (i in 0...32) var strumlineBG:FlxSprite = new FlxSprite();
{ add(strumlineBG);
var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
noteGrp.add(note);
}
offsetText = new FlxText(); strumLine = new Strumline(NoteStyleRegistry.instance.fetchDefault(), true);
offsetText.screenCenter(); strumLine.conductorInUse = localConductor;
add(offsetText); strumLine.screenCenter();
strumLine = new FlxSprite(FlxG.width / 2, 100).makeGraphic(FlxG.width, 5);
add(strumLine); add(strumLine);
super.create(); strumlineBG.x = strumLine.x;
strumlineBG.makeGraphic(Std.int(strumLine.width), FlxG.height, 0xFFFFFFFF);
strumlineBG.alpha = 0.1;
visualOffsetText = new FlxText();
visualOffsetText.setFormat(Paths.font("vcr.ttf"), 20);
visualOffsetText.x = (FlxG.height / 8) + 10;
visualOffsetText.y = 10;
visualOffsetText.fieldWidth = strumLine.x - visualOffsetText.x - 10;
add(visualOffsetText);
offsetText = new FlxText();
offsetText.setFormat(Paths.font("vcr.ttf"), 20);
offsetText.x = strumLine.x + strumLine.width + 10;
offsetText.y = 10;
offsetText.fieldWidth = FlxG.width - offsetText.x - 10;
add(offsetText);
var helpText:FlxText = new FlxText();
helpText.setFormat(Paths.font("vcr.ttf"), 20);
helpText.text = "Press BACK to return to main menu";
helpText.x = FlxG.width - helpText.width;
helpText.y = FlxG.height - (helpText.height * 2) - 2;
add(helpText);
regenNoteData();
}
function preciseInputPressed(event:PreciseInputEvent)
{
generateBeatStuff(event);
strumLine.pressKey(event.noteDirection);
strumLine.playPress(event.noteDirection);
}
function preciseInputReleased(event:PreciseInputEvent)
{
strumLine.playStatic(event.noteDirection);
strumLine.releaseKey(event.noteDirection);
}
override public function close():Void
{
PreciseInputManager.instance.onInputPressed.remove(preciseInputPressed);
PreciseInputManager.instance.onInputReleased.remove(preciseInputReleased);
FlxG.sound.music.volume = previousVolume;
swagSong.stop();
FlxG.sound.list.remove(swagSong);
FlxG.cameras.remove(stateCamera);
FlxG.state.persistentDraw = prevPersistentDraw;
FlxG.state.persistentUpdate = prevPersistentUpdate;
super.close();
}
function regenNoteData()
{
for (i in 0...32)
{
var note:SongNoteData = new SongNoteData((localConductor.stepLengthMs * 2) * i, 1);
noteGrp.push(note);
}
strumLine.applyNoteData(noteGrp);
} }
override function stepHit():Bool override function stepHit():Bool
{ {
if (Conductor.instance.currentStep % 4 == 2) if (localConductor.currentStep % 4 == 2)
{ {
blocks.members[((Conductor.instance.currentBeat % 8) + 1) % 8].alpha = 0.5; blocks.members[((localConductor.currentBeat % 8) + 1) % 8].alpha = 0.5;
} }
return super.stepHit(); return super.stepHit();
@ -154,11 +226,11 @@ class LatencyState extends MusicBeatSubState
override function beatHit():Bool override function beatHit():Bool
{ {
if (Conductor.instance.currentBeat % 8 == 0) blocks.forEach(blok -> { if (localConductor.currentBeat % 8 == 0) blocks.forEach(blok -> {
blok.alpha = 0; blok.alpha = 0.1;
}); });
blocks.members[Conductor.instance.currentBeat % 8].alpha = 1; blocks.members[localConductor.currentBeat % 8].alpha = 1;
// block.visible = !block.visible; // block.visible = !block.visible;
return super.beatHit(); return super.beatHit();
@ -171,117 +243,114 @@ class LatencyState extends MusicBeatSubState
trace(FlxG.sound.music._channel.position); trace(FlxG.sound.music._channel.position);
*/ */
// localConductor.update(swagSong.time, false); localConductor.update(swagSong.time, false);
if (FlxG.keys.justPressed.S) // localConductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
{
trace("\tUPDATE PRESS: \t" + FlxG.sound.music.time + " " + Timer.stamp());
}
if (FlxG.keys.justPressed.SPACE) songPosVis.x = songPosToX(localConductor.songPosition);
{ songVisFollowAudio.x = songPosToX(localConductor.songPosition - localConductor.audioVisualOffset);
if (FlxG.sound.music.playing) FlxG.sound.music.pause(); songVisFollowVideo.x = songPosToX(localConductor.songPosition - localConductor.inputOffset);
else
FlxG.sound.music.resume();
}
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed; visualOffsetText.text = "Visual Offset: " + localConductor.audioVisualOffset + "ms";
visualOffsetText.text += "\n\nYou can press SPACE+Left/Right to change this value.";
visualOffsetText.text += "\n\nYou can hold SHIFT to step 1ms at a time";
Conductor.instance.update(swagSong.getTimeWithDiff() - Conductor.instance.inputOffset); offsetText.text = "INPUT Offset (Left/Right to change): " + localConductor.inputOffset + "ms";
// Conductor.instance.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp; offsetText.text += "\n\nYou can hold SHIFT to step 1ms at a time";
songPosVis.x = songPosToX(Conductor.instance.songPosition);
songVisFollowAudio.x = songPosToX(Conductor.instance.songPosition - Conductor.instance.instrumentalOffset);
songVisFollowVideo.x = songPosToX(Conductor.instance.songPosition - Conductor.instance.inputOffset);
offsetText.text = "INST Offset: " + Conductor.instance.instrumentalOffset + "ms";
offsetText.text += "\nINPUT Offset: " + Conductor.instance.inputOffset + "ms";
offsetText.text += "\ncurrentStep: " + Conductor.instance.currentStep;
offsetText.text += "\ncurrentBeat: " + Conductor.instance.currentBeat;
var avgOffsetInput:Float = 0; var avgOffsetInput:Float = 0;
var loopInd:Int = 0;
for (offsetThing in offsetsPerBeat) for (offsetThing in offsetsPerBeat)
{
if (offsetThing == null) continue;
avgOffsetInput += offsetThing; avgOffsetInput += offsetThing;
loopInd++;
}
avgOffsetInput /= offsetsPerBeat.length; avgOffsetInput /= loopInd;
offsetText.text += "\naverage input offset needed: " + avgOffsetInput; offsetText.text += "\n\nEstimated average input offset needed: " + avgOffsetInput;
var multiply:Float = 10; var multiply:Int = 10;
if (FlxG.keys.pressed.SHIFT) multiply = 1; if (FlxG.keys.pressed.SHIFT) multiply = 1;
if (FlxG.keys.pressed.CONTROL) if (FlxG.keys.pressed.CONTROL || FlxG.keys.pressed.SPACE)
{ {
if (FlxG.keys.justPressed.RIGHT) if (FlxG.keys.justPressed.RIGHT)
{ {
Conductor.instance.instrumentalOffset += 1.0 * multiply; localConductor.audioVisualOffset += 1 * multiply;
} }
if (FlxG.keys.justPressed.LEFT) if (FlxG.keys.justPressed.LEFT)
{ {
Conductor.instance.instrumentalOffset -= 1.0 * multiply; localConductor.audioVisualOffset -= 1 * multiply;
} }
} }
else else
{
if (FlxG.keys.anyJustPressed([LEFT, RIGHT]))
{ {
if (FlxG.keys.justPressed.RIGHT) if (FlxG.keys.justPressed.RIGHT)
{ {
Conductor.instance.inputOffset += 1.0 * multiply; localConductor.inputOffset += 1 * multiply;
} }
if (FlxG.keys.justPressed.LEFT) if (FlxG.keys.justPressed.LEFT)
{ {
Conductor.instance.inputOffset -= 1.0 * multiply; localConductor.inputOffset -= 1 * multiply;
}
// reset the average, so you don't need to wait a full loop to start getting averages
// also reset each text member
offsetsPerBeat = [];
diffGrp.forEach(memb -> memb.text = "");
} }
} }
noteGrp.forEach(function(daNote:NoteSprite) { if (controls.BACK)
daNote.y = (strumLine.y - ((Conductor.instance.songPosition - Conductor.instance.instrumentalOffset) - daNote.noteData.time) * 0.45);
daNote.x = strumLine.x + 30;
if (daNote.y < strumLine.y) daNote.alpha = 0.5;
if (daNote.y < 0 - daNote.height)
{ {
daNote.alpha = 1; close();
// daNote.data.strumTime += Conductor.instance.beatLengthMs * 8;
} }
});
super.update(elapsed); super.update(elapsed);
} }
function generateBeatStuff() function generateBeatStuff(event:PreciseInputEvent)
{ {
Conductor.instance.update(swagSong.getTimeWithDiff()); // localConductor.update(swagSong.getTimeWithDiff());
var closestBeat:Int = Math.round(Conductor.instance.songPosition / Conductor.instance.beatLengthMs) % diffGrp.members.length; var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - event.timestamp) / 1000.0 / 1000.0;
var getDiff:Float = Conductor.instance.songPosition - (closestBeat * Conductor.instance.beatLengthMs); // trace("input latency: " + inputLatencyMs + "ms");
getDiff -= Conductor.instance.inputOffset; // trace("cur timestamp: " + PreciseInputManager.getCurrentTimestamp() + "ns");
// trace("event timestamp: " + event.timestamp + "ns");
// trace("songtime: " + localConductor.getTimeWithDiff(swagSong) + "ms");
var closestBeat:Int = Math.round(localConductor.getTimeWithDiff(swagSong) / (localConductor.stepLengthMs * 2)) % diffGrp.members.length;
var getDiff:Float = localConductor.getTimeWithDiff(swagSong) - (closestBeat * (localConductor.stepLengthMs * 2));
// getDiff -= localConductor.inputOffset;
getDiff -= inputLatencyMs;
getDiff -= localConductor.audioVisualOffset;
// lil fix for end of song // lil fix for end of song
if (closestBeat == 0 && getDiff >= Conductor.instance.beatLengthMs * 2) getDiff -= FlxG.sound.music.length; if (closestBeat == 0 && getDiff >= localConductor.stepLengthMs * 2) getDiff -= swagSong.length;
trace("\tDISTANCE TO CLOSEST BEAT: " + getDiff + "ms");
trace("\tCLOSEST BEAT: " + closestBeat);
beatTrail.x = songPosVis.x; beatTrail.x = songPosVis.x;
diffGrp.members[closestBeat].text = getDiff + "ms"; diffGrp.members[closestBeat].text = getDiff + "ms";
offsetsPerBeat[closestBeat] = Std.int(getDiff); offsetsPerBeat[closestBeat] = Math.round(getDiff);
} }
function songPosToX(pos:Float):Float function songPosToX(pos:Float):Float
{ {
return FlxMath.remapToRange(pos, 0, FlxG.sound.music.length, 0, FlxG.width); return FlxMath.remapToRange(pos, 0, swagSong.length, 0, FlxG.width);
} }
} }
class HomemadeMusic extends FlxSound class HomemadeMusic extends FlxSound
{ {
public var prevTimestamp:Int = 0; public var prevTimestamp:Int = 0;
public var timeWithDiff:Float = 0;
public function new() public function new()
{ {

View file

@ -1,8 +1,8 @@
package funkin.ui.freeplay; package funkin.ui.freeplay;
import funkin.data.freeplay.AlbumData; import funkin.data.freeplay.album.AlbumData;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;
import funkin.data.freeplay.AlbumRegistry;
import funkin.data.IRegistryEntry; import funkin.data.IRegistryEntry;
import flixel.graphics.FlxGraphic; import flixel.graphics.FlxGraphic;

View file

@ -7,7 +7,7 @@ import flixel.util.FlxSort;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import funkin.data.freeplay.AlbumRegistry; import funkin.data.freeplay.album.AlbumRegistry;
import funkin.util.assets.FlxAnimationUtil; import funkin.util.assets.FlxAnimationUtil;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.util.SortUtil; import funkin.util.SortUtil;

View file

@ -18,7 +18,7 @@ import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil; import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.data.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.graphics.FunkinCamera; import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;

View file

@ -20,6 +20,7 @@ class FunkinSoundTray extends FlxSoundTray
{ {
var graphicScale:Float = 0.30; var graphicScale:Float = 0.30;
var lerpYPos:Float = 0; var lerpYPos:Float = 0;
var alphaTarget:Float = 0;
var volumeMaxSound:String; var volumeMaxSound:String;
@ -40,7 +41,7 @@ class FunkinSoundTray extends FlxSoundTray
// makes an alpha'd version of all the bars (bar_10.png) // makes an alpha'd version of all the bars (bar_10.png)
var backingBar:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/bars_10"))); var backingBar:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/bars_10")));
backingBar.x = 10; backingBar.x = 9;
backingBar.y = 5; backingBar.y = 5;
backingBar.scaleX = graphicScale; backingBar.scaleX = graphicScale;
backingBar.scaleY = graphicScale; backingBar.scaleY = graphicScale;
@ -56,7 +57,7 @@ class FunkinSoundTray extends FlxSoundTray
for (i in 1...11) for (i in 1...11)
{ {
var bar:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/bars_" + i))); var bar:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/bars_" + i)));
bar.x = 10; bar.x = 9;
bar.y = 5; bar.y = 5;
bar.scaleX = graphicScale; bar.scaleX = graphicScale;
bar.scaleY = graphicScale; bar.scaleY = graphicScale;
@ -77,15 +78,18 @@ class FunkinSoundTray extends FlxSoundTray
override public function update(MS:Float):Void override public function update(MS:Float):Void
{ {
y = MathUtil.coolLerp(y, lerpYPos, 0.1); y = MathUtil.coolLerp(y, lerpYPos, 0.1);
alpha = MathUtil.coolLerp(alpha, alphaTarget, 0.25);
// Animate sound tray thing // Animate sound tray thing
if (_timer > 0) if (_timer > 0)
{ {
_timer -= (MS / 1000); _timer -= (MS / 1000);
alphaTarget = 1;
} }
else if (y > -height) else if (y > -height)
{ {
lerpYPos = -height - 10; lerpYPos = -height - 10;
alphaTarget = 0;
if (y <= -height) if (y <= -height)
{ {

View file

@ -1,5 +1,6 @@
package funkin.ui.options; package funkin.ui.options;
import funkin.ui.debug.latency.LatencyState;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.FlxSubState; import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
@ -190,6 +191,9 @@ class OptionsMenu extends Page
add(items = new TextMenuList()); add(items = new TextMenuList());
createItem("PREFERENCES", function() switchPage(Preferences)); createItem("PREFERENCES", function() switchPage(Preferences));
createItem("CONTROLS", function() switchPage(Controls)); createItem("CONTROLS", function() switchPage(Controls));
createItem("INPUT OFFSETS", function() {
FlxG.state.openSubState(new LatencyState());
});
#if newgrounds #if newgrounds
if (NGio.isLoggedIn) createItem("LOGOUT", selectLogout); if (NGio.isLoggedIn) createItem("LOGOUT", selectLogout);

View file

@ -6,8 +6,8 @@ import flixel.util.FlxColor;
import funkin.play.song.Song; import funkin.play.song.Song;
import funkin.data.IRegistryEntry; import funkin.data.IRegistryEntry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.data.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.data.level.LevelData; import funkin.data.story.level.LevelData;
/** /**
* An object used to retrieve data about a story mode level (also known as "weeks"). * An object used to retrieve data about a story mode level (also known as "weeks").

View file

@ -2,7 +2,7 @@ package funkin.ui.story;
import funkin.play.stage.Bopper; import funkin.play.stage.Bopper;
import funkin.util.assets.FlxAnimationUtil; import funkin.util.assets.FlxAnimationUtil;
import funkin.data.level.LevelData; import funkin.data.story.level.LevelData;
class LevelProp extends Bopper class LevelProp extends Bopper
{ {

View file

@ -9,7 +9,7 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.data.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;

View file

@ -295,6 +295,8 @@ class Constants
/** /**
* Constant for the number of seconds in a minute. * Constant for the number of seconds in a minute.
*
* sex per min
*/ */
public static final SECS_PER_MIN:Int = 60; public static final SECS_PER_MIN:Int = 60;

View file

@ -1,6 +1,6 @@
package funkin.data.level; package funkin.data.story.level;
import funkin.data.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.ui.story.Level; import funkin.ui.story.Level;
import massive.munit.Assert; import massive.munit.Assert;
import massive.munit.async.AsyncFactory; import massive.munit.async.AsyncFactory;
@ -8,7 +8,7 @@ import massive.munit.util.Timer;
@:nullSafety @:nullSafety
@:access(funkin.ui.story.Level) @:access(funkin.ui.story.Level)
@:access(funkin.data.level.LevelRegistry) @:access(funkin.data.story.level.LevelRegistry)
class LevelRegistryTest extends FunkinTest class LevelRegistryTest extends FunkinTest
{ {
public function new() public function new()