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",
"request": "launch"
},
{
"name": "Debug",
"type": "lime",
"request": "launch",
"preLaunchTask": null
},
{
// Launch in browser
"name": "HTML5 Debug",

22
.vscode/tasks.json vendored
View file

@ -1,13 +1,13 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "lime",
"command": "test",
"group": {
"kind": "build",
"isDefault": true
}
}
]
"version": "2.0.0",
"tasks": [
{
"type": "lime",
"command": "test",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View file

@ -5,6 +5,9 @@ import flixel.util.FlxSignal;
import flixel.math.FlxMath;
import funkin.data.song.SongData.SongTimeChange;
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,
@ -89,6 +92,9 @@ class Conductor
*/
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.
*/
@ -233,8 +239,41 @@ class Conductor
/**
* 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.
@ -353,16 +392,19 @@ class Conductor
* BPM, current step, etc. will be re-calculated based on the song position.
*
* @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)
{
// Take into account instrumental and file format song offsets.
songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
songPos = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
}
// Take into account instrumental and file format song offsets.
songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0;
var oldMeasure:Float = this.currentMeasure;
var oldBeat:Float = this.currentBeat;
var oldStep:Float = this.currentStep;
@ -421,6 +463,35 @@ class Conductor
{
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
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
{

View file

@ -16,14 +16,14 @@ import funkin.util.macro.MacroUtil;
import funkin.util.WindowUtil;
import funkin.play.PlayStatePlaylist;
import openfl.display.BitmapData;
import funkin.data.level.LevelRegistry;
import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.freeplay.AlbumRegistry;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.data.song.SongRegistry;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.modding.module.ModuleHandler;
@ -304,7 +304,7 @@ class InitState extends FlxState
*/
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)
{

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)
{
@ -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)
{

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;

View file

@ -1,7 +1,7 @@
package funkin.data.dialogue;
package funkin.data.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;
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;

View file

@ -1,7 +1,7 @@
package funkin.data.dialogue;
package funkin.data.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;
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;

View file

@ -1,7 +1,7 @@
package funkin.data.dialogue;
package funkin.data.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;
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;

View file

@ -1,7 +1,7 @@
package funkin.data.freeplay;
package funkin.data.freeplay.album;
import funkin.ui.freeplay.Album;
import funkin.data.freeplay.AlbumData;
import funkin.data.freeplay.album.AlbumData;
import funkin.ui.freeplay.ScriptedAlbum;
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
* 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";

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;
@ -13,7 +13,7 @@ typedef LevelData =
* 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.
*/
@:default(funkin.data.level.LevelRegistry.LEVEL_DATA_VERSION)
@:default(funkin.data.story.level.LevelRegistry.LEVEL_DATA_VERSION)
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.data.level.LevelData;
import funkin.data.story.level.LevelData;
import funkin.ui.story.ScriptedLevel;
class LevelRegistry extends BaseRegistry<Level, LevelData>

View file

@ -29,7 +29,7 @@ class HSVShader extends FlxRuntimeShader
function set_saturation(value:Float):Float
{
this.setFloat('sat', value);
this.setFloat('_sat', value);
this.saturation = value;
return this.saturation;
@ -37,7 +37,7 @@ class HSVShader extends FlxRuntimeShader
function set_value(value:Float):Float
{
this.setFloat('val', value);
this.setFloat('_val', value);
this.value = 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.
// 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);
if (getInputByKey(key)?.justPressed ?? false)

View file

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

View file

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

View file

@ -19,7 +19,7 @@ import flixel.util.FlxTimer;
import funkin.api.newgrounds.NGio;
import funkin.audio.FunkinSound;
import funkin.audio.VoicesGroup;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.notestyle.NoteStyleData;
import funkin.data.notestyle.NoteStyleRegistry;
@ -890,7 +890,8 @@ class PlayState extends MusicBeatSubState
{
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();
}
}
@ -2049,10 +2050,10 @@ class PlayState extends MusicBeatSubState
{
if (note == null) continue;
// TODO: Does this properly account for offsets?
var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS;
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
// TODO: Are offsets being accounted for in the correct direction?
var hitWindowStart = note.strumTime + Conductor.instance.inputOffset - Constants.HIT_WINDOW_MS;
var hitWindowCenter = note.strumTime + Conductor.instance.inputOffset;
var hitWindowEnd = note.strumTime + Conductor.instance.inputOffset + Constants.HIT_WINDOW_MS;
if (Conductor.instance.songPosition > hitWindowEnd)
{

View file

@ -42,7 +42,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
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;
}
else
@ -133,7 +133,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
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;
}
else

View file

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

View file

@ -11,8 +11,8 @@ import funkin.modding.events.ScriptEvent;
import funkin.audio.FunkinSound;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import flixel.util.FlxColor;
import funkin.data.dialogue.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
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 funkin.util.assets.FlxAnimationUtil;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import funkin.data.dialogue.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.dialogue.speaker.SpeakerData;
import funkin.data.dialogue.speaker.SpeakerRegistry;
/**
* The character sprite which displays during dialogue.

View file

@ -127,7 +127,7 @@ class FocusCameraSongEvent extends SongEvent
switch (ease)
{
case 'CLASSIC': // Old-school. No ease. Just set follow point.
PlayState.instance.resetCamera();
PlayState.instance.resetCamera(false, true);
PlayState.instance.cameraFollowPoint.setPosition(targetX, targetY);
case 'INSTANT': // Instant ease. Duration is automatically 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.ui.options.PreferencesMenu;
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.
@ -45,6 +46,25 @@ class Strumline extends FlxSpriteGroup
*/
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.
* This group iterates over this every frame to update note positions.
@ -151,24 +171,10 @@ class Strumline extends FlxSpriteGroup
updateNotes();
}
var frameMax:Int;
var animFinishedEver:Bool;
/**
* Get a list of notes within + or - the given strumtime.
* @param strumTime The current time.
* @param hitWindow The hit window to check.
* Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
* @return An array of `NoteSprite` objects.
*/
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>
{
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>
{
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
{
if (noteData == null) return null;
@ -280,7 +277,7 @@ class Strumline extends FlxSpriteGroup
* @param strumTime
* @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.
// 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 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
@ -301,8 +299,8 @@ class Strumline extends FlxSpriteGroup
// if (conductorInUse.currentStep == 0) nextNoteIndex = 0;
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
var hitWindowStart:Float = conductorInUse.songPosition - Constants.HIT_WINDOW_MS;
var renderWindowStart:Float = conductorInUse.songPosition + RENDER_DISTANCE_MS;
for (noteIndex in nextNoteIndex...noteData.length)
{
@ -351,7 +349,7 @@ class Strumline extends FlxSpriteGroup
{
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))
{
@ -365,7 +363,7 @@ class Strumline extends FlxSpriteGroup
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.
holdNote.visible = false;
@ -422,13 +420,13 @@ class Strumline extends FlxSpriteGroup
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.
holdConfirm(holdNote.noteDirection);
holdNote.visible = true;
holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.instance.songPosition;
holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - conductorInUse.songPosition;
if (holdNote.sustainLength <= 10)
{

View file

@ -87,6 +87,8 @@ class Save
zoomCamera: true,
debugDisplay: false,
autoPause: true,
inputOffset: 0,
audioVisualOffset: 0,
controls:
{
@ -866,6 +868,18 @@ typedef SaveDataOptions =
*/
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 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 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()
{
super();
@ -111,7 +126,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
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);
@ -122,7 +137,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
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);

View file

@ -21,6 +21,21 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
public var leftWatermarkText: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)
{
super();
@ -51,7 +66,6 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
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);
// Emergency exit button.
@ -62,11 +76,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
// Display Conductor info in the watch window.
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));
}
@ -94,7 +105,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
*/
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);
@ -110,7 +121,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
*/
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);

View file

@ -23,7 +23,9 @@ class DebugMenuSubState extends MusicBeatSubState
override function create():Void
{
FlxTransitionableState.skipNextTransIn = true;
super.create();
bgColor = 0x00000000;
// Create an object for the camera to track.
@ -48,9 +50,12 @@ class DebugMenuSubState extends MusicBeatSubState
items.onChange.add(onMenuChange);
add(items);
FlxTransitionableState.skipNextTransIn = true;
// Create each menu item.
// Call onMenuChange when the first item is created to move the camera .
onMenuChange(createItem("CHART EDITOR", openChartEditor));
createItem("Input Offset Testing", openInputOffsetTesting);
createItem("ANIMATION EDITOR", openAnimationEditor);
createItem("STAGE EDITOR", openStageEditor);
createItem("TEST STICKERS", testStickers);
@ -92,6 +97,12 @@ class DebugMenuSubState extends MusicBeatSubState
FlxG.switchState(() -> new ChartEditorState());
}
function openInputOffsetTesting()
{
openSubState(new funkin.ui.debug.latency.LatencyState());
trace('Input Offset Testing');
}
function openAnimationEditor()
{
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 flixel.util.FlxColor;
import funkin.ui.MusicBeatState;
import funkin.data.dialogue.ConversationData;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.freeplay.AlbumRegistry;
import funkin.data.dialogue.conversation.ConversationData;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.speaker.SpeakerData;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.play.cutscene.dialogue.Speaker;

View file

@ -7,7 +7,6 @@ import flash.text.TextField;
import flash.text.TextFormatAlign;
import flixel.math.FlxMath;
import flixel.system.debug.DebuggerUtil;
import flixel.system.debug.stats.Stats;
import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil;
@ -16,13 +15,31 @@ import flixel.util.FlxDestroyUtil;
* SHAMELESSLY STOLEN FROM FLIXEL
* https://github.com/HaxeFlixel/flixel/blob/master/flixel/system/debug/stats/StatsGraph.hx
*/
#if FLX_DEBUG
class CoolStatsGraph extends Sprite
{
static inline var AXIS_COLOR:FlxColor = 0xffffff;
static inline var AXIS_ALPHA:Float = 0.5;
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 curLabel: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)
{
super();
x = X;
y = Y;
_width = Width - LabelWidth;
@ -57,11 +75,11 @@ class CoolStatsGraph extends Sprite
_axis = new Shape();
_axis.x = _labelWidth + 10;
maxLabel = DebuggerUtil.createTextField(0, 0, Stats.LABEL_COLOR, Stats.TEXT_SIZE);
curLabel = DebuggerUtil.createTextField(0, (_height / 2) - (Stats.TEXT_SIZE / 2), graphColor, Stats.TEXT_SIZE);
minLabel = DebuggerUtil.createTextField(0, _height - Stats.TEXT_SIZE, Stats.LABEL_COLOR, Stats.TEXT_SIZE);
maxLabel = DebuggerUtil.createTextField(0, 0, LABEL_COLOR, TEXT_SIZE);
curLabel = DebuggerUtil.createTextField(0, (_height / 2) - (TEXT_SIZE / 2), graphColor, 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.defaultTextFormat.align = TextFormatAlign.CENTER;
avgLabel.alpha = 0.5;
@ -136,7 +154,7 @@ class CoolStatsGraph extends Sprite
function formatValue(value:Float):String
{
return FlxMath.roundDecimal(value, Stats.DECIMALS) + " " + _unit;
return FlxMath.roundDecimal(value, DECIMALS) + " " + _unit;
}
public function average():Float
@ -157,4 +175,3 @@ class CoolStatsGraph extends Sprite
history = null;
}
}
#end

View file

@ -8,20 +8,27 @@ import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxMath;
import funkin.ui.MusicBeatSubState;
import flixel.sound.FlxSound;
import flixel.system.debug.stats.StatsGraph;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.audio.visualize.PolygonSpectogram;
import funkin.play.notes.NoteSprite;
import funkin.ui.debug.latency.CoolStatsGraph;
import haxe.Timer;
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
{
var visualOffsetText:FlxText;
var offsetText:FlxText;
var noteGrp:FlxTypedGroup<NoteSprite>;
var strumLine:FlxSprite;
var noteGrp:Array<SongNoteData> = [];
var strumLine:Strumline;
var blocks:FlxTypedGroup<FlxSprite>;
@ -31,76 +38,81 @@ class LatencyState extends MusicBeatSubState
var beatTrail:FlxSprite;
var diffGrp:FlxTypedGroup<FlxText>;
var offsetsPerBeat:Array<Int> = [];
var offsetsPerBeat:Array<Null<Int>> = [];
var swagSong:HomemadeMusic;
#if FLX_DEBUG
var funnyStatsGraph:CoolStatsGraph;
var realStats:CoolStatsGraph;
#end
var previousVolume:Float;
var stateCamera:FlxCamera;
/**
* 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()
{
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.loadEmbedded(Paths.sound('soundTest'), true);
swagSong.looped = true;
swagSong.play();
FlxG.sound.list.add(swagSong);
FlxG.sound.music = swagSong;
FlxG.sound.music.play();
PreciseInputManager.instance.onInputPressed.add(preciseInputPressed);
#if FLX_DEBUG
funnyStatsGraph = new CoolStatsGraph(0, Std.int(FlxG.height / 2), FlxG.width, Std.int(FlxG.height / 2), FlxColor.PINK, "time");
FlxG.addChildBelowMouse(funnyStatsGraph);
PreciseInputManager.instance.onInputReleased.add(preciseInputReleased);
realStats = new CoolStatsGraph(0, Std.int(FlxG.height / 2), FlxG.width, Std.int(FlxG.height / 2), FlxColor.YELLOW, "REAL");
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
localConductor.forceBPM(60);
Conductor.instance.forceBPM(60);
noteGrp = new FlxTypedGroup<NoteSprite>();
add(noteGrp);
diffGrp = new FlxTypedGroup<FlxText>();
add(diffGrp);
// var musSpec:PolygonSpectogram = new PolygonSpectogram(FlxG.sound.music, FlxColor.RED, FlxG.height, Math.floor(FlxG.height / 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))
for (beat in 0...Math.floor(swagSong.length / (localConductor.stepLengthMs * 2)))
{
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.alpha = 0.3;
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;
diffGrp.add(offsetTxt);
offsetsPerBeat.push(0);
offsetsPerBeat.push(null);
}
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)
{
var block = new FlxSprite(2, 50 * i).makeGraphic(48, 48);
block.alpha = 0;
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.1;
blocks.add(block);
}
for (i in 0...32)
{
var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
noteGrp.add(note);
}
var strumlineBG:FlxSprite = new FlxSprite();
add(strumlineBG);
offsetText = new FlxText();
offsetText.screenCenter();
add(offsetText);
strumLine = new FlxSprite(FlxG.width / 2, 100).makeGraphic(FlxG.width, 5);
strumLine = new Strumline(NoteStyleRegistry.instance.fetchDefault(), true);
strumLine.conductorInUse = localConductor;
strumLine.screenCenter();
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
{
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();
@ -154,11 +226,11 @@ class LatencyState extends MusicBeatSubState
override function beatHit():Bool
{
if (Conductor.instance.currentBeat % 8 == 0) blocks.forEach(blok -> {
blok.alpha = 0;
if (localConductor.currentBeat % 8 == 0) blocks.forEach(blok -> {
blok.alpha = 0.1;
});
blocks.members[Conductor.instance.currentBeat % 8].alpha = 1;
blocks.members[localConductor.currentBeat % 8].alpha = 1;
// block.visible = !block.visible;
return super.beatHit();
@ -171,117 +243,114 @@ class LatencyState extends MusicBeatSubState
trace(FlxG.sound.music._channel.position);
*/
// localConductor.update(swagSong.time, false);
localConductor.update(swagSong.time, false);
if (FlxG.keys.justPressed.S)
{
trace("\tUPDATE PRESS: \t" + FlxG.sound.music.time + " " + Timer.stamp());
}
// localConductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
if (FlxG.keys.justPressed.SPACE)
{
if (FlxG.sound.music.playing) FlxG.sound.music.pause();
else
FlxG.sound.music.resume();
}
songPosVis.x = songPosToX(localConductor.songPosition);
songVisFollowAudio.x = songPosToX(localConductor.songPosition - localConductor.audioVisualOffset);
songVisFollowVideo.x = songPosToX(localConductor.songPosition - localConductor.inputOffset);
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);
// Conductor.instance.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
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;
offsetText.text = "INPUT Offset (Left/Right to change): " + localConductor.inputOffset + "ms";
offsetText.text += "\n\nYou can hold SHIFT to step 1ms at a time";
var avgOffsetInput:Float = 0;
var loopInd:Int = 0;
for (offsetThing in offsetsPerBeat)
{
if (offsetThing == null) continue;
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.CONTROL)
if (FlxG.keys.pressed.CONTROL || FlxG.keys.pressed.SPACE)
{
if (FlxG.keys.justPressed.RIGHT)
{
Conductor.instance.instrumentalOffset += 1.0 * multiply;
localConductor.audioVisualOffset += 1 * multiply;
}
if (FlxG.keys.justPressed.LEFT)
{
Conductor.instance.instrumentalOffset -= 1.0 * multiply;
localConductor.audioVisualOffset -= 1 * multiply;
}
}
else
{
if (FlxG.keys.justPressed.RIGHT)
if (FlxG.keys.anyJustPressed([LEFT, RIGHT]))
{
Conductor.instance.inputOffset += 1.0 * multiply;
}
if (FlxG.keys.justPressed.RIGHT)
{
localConductor.inputOffset += 1 * multiply;
}
if (FlxG.keys.justPressed.LEFT)
{
Conductor.instance.inputOffset -= 1.0 * multiply;
if (FlxG.keys.justPressed.LEFT)
{
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) {
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;
// daNote.data.strumTime += Conductor.instance.beatLengthMs * 8;
}
});
if (controls.BACK)
{
close();
}
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 getDiff:Float = Conductor.instance.songPosition - (closestBeat * Conductor.instance.beatLengthMs);
getDiff -= Conductor.instance.inputOffset;
var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - event.timestamp) / 1000.0 / 1000.0;
// trace("input latency: " + inputLatencyMs + "ms");
// 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
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;
diffGrp.members[closestBeat].text = getDiff + "ms";
offsetsPerBeat[closestBeat] = Std.int(getDiff);
offsetsPerBeat[closestBeat] = Math.round(getDiff);
}
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
{
public var prevTimestamp:Int = 0;
public var timeWithDiff:Float = 0;
public function new()
{

View file

@ -1,8 +1,8 @@
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.freeplay.AlbumRegistry;
import funkin.data.IRegistryEntry;
import flixel.graphics.FlxGraphic;

View file

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

View file

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

View file

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

View file

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

View file

@ -6,8 +6,8 @@ import flixel.util.FlxColor;
import funkin.play.song.Song;
import funkin.data.IRegistryEntry;
import funkin.data.song.SongRegistry;
import funkin.data.level.LevelRegistry;
import funkin.data.level.LevelData;
import funkin.data.story.level.LevelRegistry;
import funkin.data.story.level.LevelData;
/**
* 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.util.assets.FlxAnimationUtil;
import funkin.data.level.LevelData;
import funkin.data.story.level.LevelData;
class LevelProp extends Bopper
{

View file

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

View file

@ -295,6 +295,8 @@ class Constants
/**
* Constant for the number of seconds in a minute.
*
* sex per min
*/
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 massive.munit.Assert;
import massive.munit.async.AsyncFactory;
@ -8,7 +8,7 @@ import massive.munit.util.Timer;
@:nullSafety
@:access(funkin.ui.story.Level)
@:access(funkin.data.level.LevelRegistry)
@:access(funkin.data.story.level.LevelRegistry)
class LevelRegistryTest extends FunkinTest
{
public function new()