mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge branch 'rewrite/master' into blazin-rain
This commit is contained in:
commit
ab74cc2d5a
72 changed files with 961 additions and 338 deletions
0
.github/hooks/post-checkout
vendored
Normal file → Executable file
0
.github/hooks/post-checkout
vendored
Normal file → Executable file
0
.github/hooks/post-merge
vendored
Normal file → Executable file
0
.github/hooks/post-merge
vendored
Normal file → Executable file
0
.github/hooks/pre-push
vendored
Normal file → Executable file
0
.github/hooks/pre-push
vendored
Normal file → Executable file
|
@ -124,7 +124,7 @@
|
|||
<haxelib name="polymod" /> <!-- Modding framework -->
|
||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
||||
<haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback -->
|
||||
<haxelib name="funkVis"/>
|
||||
<haxelib name="funkin.vis"/>
|
||||
|
||||
|
||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
||||
|
|
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit 00463685fa570f0c853d08e250b46ef80f30bc48
|
||||
Subproject commit f72947b65fe0555821f827dccd562f01d308486d
|
|
@ -16,5 +16,5 @@
|
|||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||
- HTML5: Compiles without any extra setup
|
||||
6. If you are targeting for native, you likely need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug`
|
||||
7. `lime test PLATFORM` !
|
||||
6. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug`
|
||||
7. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State).
|
||||
|
|
186
docs/FNFC-SPEC.md
Normal file
186
docs/FNFC-SPEC.md
Normal file
|
@ -0,0 +1,186 @@
|
|||
# .fnfc File Specification
|
||||
|
||||
*Updated 2024-04-29*
|
||||
|
||||
- Manifest version: `1.0.0`
|
||||
- Metadata version: `2.2.2`
|
||||
- Chart data version: `2.0.0`
|
||||
|
||||
## Introduction
|
||||
|
||||
This document describes the structure of the FNFC file format used for saving and loading charts in the Chart Editor for Friday Night Funkin'. It is designed to refactor how the game stores levels, as the original format was clunky and poorly extensible.
|
||||
|
||||
FNFC files are a store of all the required files for crediting and editing charts. This includes any relevant audio files, which is perfect for collaboration between charters.
|
||||
|
||||
## Overview
|
||||
|
||||
Friday Night Funkin' charts utilize a concept called "variations"; these are groups of difficulties which share gameplay data. This is done to prevent significant redundancy; if each chart
|
||||
|
||||
If a difficulty for a song should have the same events as another difficulty, those difficulties should be in the same variation (for example, Normal and Hard). If a particular difficulty for a song should have different song events, characters, stages, or music (including instruments or vocals) from the base variation, they should use a different variation. Difficulties for a song start in the `default` variation.
|
||||
|
||||
An example of this is the Erect and Nightmare difficulties. These are defined in an alternative variation, which allows for those difficulties to utilize different events and music from the base variation.
|
||||
|
||||
The Chart Editor is made aware of which variations are available in the current chart file by querying the `playData.songVariations` key in the `default` variation's metadata file.
|
||||
|
||||
The chart data itself is split into two files. The `<songid>-metadata.json` file should contain all the information the game needs to display a song in menus such as Freeplay, including but not limited to the song name, artist, and BPM. The `<songid>-chart.json` should only contain the note data for each difficulty of that variation, and the song event data for the variation. This allows the game to, when it first loads, parse and cache the metadata for all available songs for use in menus, while keeping the bulky chart file data unloaded until that chart specifically is played.
|
||||
|
||||
Note that the game itself does not store its songs as FNFC files; rather, it stores the `metadata.json`, `chart.json`, and `.ogg` files separately.
|
||||
|
||||
Note also that the files may include some values whose functionality are not yet fully implemented into the game itself.
|
||||
|
||||
## File Contents
|
||||
|
||||
FNFC files are standard ZIP files containing the following:
|
||||
|
||||
- `manifest.json`: This file contains minimal information to minimize work in the parsing of chart files.
|
||||
- `Inst.ogg`: Song instrumental for the default variation.
|
||||
- `Inst-<instid>.ogg`: *(optional)* An alternative instrumental which can be used.
|
||||
- `Voices-<charid>.ogg`: *(optional)* Song vocals for a specific character, for the default variation.
|
||||
- `Voices-<charid>-<variation>.ogg`: *(optional)* Song vocals for a specific character, for an alternate variation.
|
||||
- `<songid>-metadata.json`: Song metadata for the `default` variation.
|
||||
- `<songid>-metadata-<variation>.json`: *(optional)* Song metadata for alternate variations.
|
||||
- `<songid>-chart.json`: Song chart data for the `default` variation.
|
||||
- `<songid>-chart-<variation>.json`: *(optional)* Song chart data for alternate variations.
|
||||
|
||||
## Files
|
||||
|
||||
Note that each component file contains its own separate [Semantic Version](https://semver.org/) number which the game adheres to when parsing. New functionality (with backwards compatibility) should be represented by a `1.x.0` change and breaking changes should be kept to a minimum, and represented by a `x.0.0` change.
|
||||
|
||||
### manifest.json
|
||||
|
||||
`manifest.json` is a JSON-formatted text file, containing a single object with the following keys:
|
||||
|
||||
- `version`: The Semantic Version string for the manifest file.
|
||||
- `songId`: The song ID associated with this chart. Used to allow the Chart Editor to easily determine the proper filenames for the `metadata` and `chart` files.
|
||||
|
||||
#### manifest.json Example
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": "1.0.0", // The Semantic Version string.
|
||||
"songId": "dadbattle" // The song ID.
|
||||
}
|
||||
```
|
||||
|
||||
### Inst.ogg
|
||||
|
||||
This is an audio file in the OGG container format with the Vorbis audio codec.
|
||||
|
||||
This file is mandatory. A chart file without an instrumental track is considered invalid.
|
||||
|
||||
This file is used as the default backing track for the song.
|
||||
|
||||
### Inst-<instid>.ogg
|
||||
|
||||
This is an audio file in the OGG container format with the Vorbis audio codec.
|
||||
|
||||
This file is optional if no alternative instrumental is specified in any variation.
|
||||
|
||||
This file is used as an alternate backing track for the song. It is specified using the `playData.characters.instrumental` key in the current variation's metadata file.
|
||||
|
||||
### Voices-<charid>.ogg
|
||||
|
||||
This is an audio file in the OGG container format with the Vorbis audio codec.
|
||||
|
||||
This file is optional. The game will look for the specified file but will ignore if it is missing.
|
||||
|
||||
This file is used for the character vocal track for the song. The game will look for and play the vocal track for the player (using the ID specified by the `playData.characters.player` key in the current variation's metadata file), and the opponent (using the ID specified by the `playData.characters.opponent` key in the current variation's metadata file).
|
||||
|
||||
### Voices-<charid>-<variation>.ogg
|
||||
|
||||
This is an audio file in the OGG container format with the Vorbis audio codec.
|
||||
|
||||
This file is optional. The game will look for the specified file but will ignore if it is missing.
|
||||
|
||||
This file is used for the character vocal track for the song, for the given variation. The game will look for and play the vocal track for the player and the opponent using the same JSON keys as the `Voices-<charid>.ogg` files for the default variation, while also applying the current variation ID.
|
||||
|
||||
### <songid>-metadata.json
|
||||
|
||||
`<songid>-metadata.json` is a JSON-formatted text file, specifying metadata about the default variation for this song. It has the following keys:
|
||||
|
||||
- `version`: The Semantic Version string for the metadata file.
|
||||
- `songName`: The human readable name for the song, as a string.
|
||||
- `artist`: The human readable artist(s) for the song, as a string.
|
||||
- `timeFormat`: The time format. In the future, this will allow chart files to define the timestamps for BPM changes, note data, or event data, in fractional beats and steps, but for now the only supported value is the string `"ms"`.
|
||||
- `timeChanges`: An array of Song Time Change objects. Note that at least one Song Time Change object must be specified, with a timestamp of `0`.
|
||||
- `playData`: A Song Play Data object.
|
||||
- `offsets`: A Song Offset Data object.
|
||||
- `generatedBy`: A string specified when creating a chart. Should only be used for debugging purposes, and not read or used by the game. Custom engines should modify `Constants.hx` to ensure unique values in case of issues with the metadata or chart data.
|
||||
|
||||
The Song Time Change objects have the following keys:
|
||||
- `t`: The timestamp for the BPM change, in milliseconds, as a float.
|
||||
- `bpm`: The new song timing, in beats per minute, as a float.
|
||||
- `n`: *optional* Time signature numerator, as an integer. Defaults to 4. (int). Optional, defaults to `4`.
|
||||
- `d`: *optional* Time signature denominator, as an integer. Should only ever be a power of two. Defaults to `4`.
|
||||
- `bt`: *optional* Beat tuplets. This defines how many steps each beat is divided into. Defaults to `[4, 4, 4, 4]`
|
||||
|
||||
The Song Play Data objects have the following keys:
|
||||
- `album`: The album ID to display in the Freeplay menu, as a string ID.
|
||||
- `previewStart`: The timestamp to begin the audio preview for this track in the Freeplay Menu, in milliseconds, as a float.
|
||||
- `previewEnd`: The timestamp to end the audio preview for this track in the Freeplay Menu, in milliseconds, as a float.
|
||||
- `ratings`: An map object; each key is a difficulty ID and each value is an integer difficulty rating value for display in Freeplay.
|
||||
- `songVariations`: An array of string variation IDs this song has. The game will attempt to read `<songid>-metadata-<variationid>.json` and `<songid>-chart-<variationid>.json` files for each variation ID included in this list.
|
||||
- `difficulties`: An array of string difficulties this song has available to play. Any difficulties in this list will be made available to players in-game, and any difficulties not in this list will be ignored.
|
||||
- `characters`: A Song Character Data object.
|
||||
- `stage`: The stage to use for this chart, as a string ID.
|
||||
- `noteStyle`: The note style to use for this chart, as a string ID.
|
||||
|
||||
The Song Character Data objects have the following keys:
|
||||
- `player`: The player character to use, as a string ID.
|
||||
- `girlfriend`: The girlfriend character to use, as a string ID.
|
||||
- `opponent`: The opponent character to use, as a string ID.
|
||||
- `instrumental`: The instrumental ID to use. Defaults to a blank string to use `Inst.ogg`
|
||||
- `altInstrumentals`:
|
||||
|
||||
The Song Offset Data objects have the following keys:
|
||||
|
||||
#### <songid>-metadata.json Example
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": "2.2.2", // Semantic Version string
|
||||
"songName": "DadBattle", // Readable song name
|
||||
"artist": "Kawai Sprite", // Song artist(s)
|
||||
"timeFormat": "ms", // Time format to use
|
||||
"timeChanges": [{ // List of BPM changes. Must have at least one.
|
||||
"t": 0, // BPM change timestamp in milliseconds.
|
||||
"bpm": 180 // The target BPM.
|
||||
}],
|
||||
"playData": {
|
||||
"album": "volume1", // The album to display in Freeplay.
|
||||
"previewStart": 0, // Time (ms) to start preview at
|
||||
"previewEnd": 15000, // Time (ms) to end preview at
|
||||
"ratings": { // Rating data for each difficulty
|
||||
"easy": 1, // Rating for Easy difficulty
|
||||
"normal": 3, // Rating for Normal difficulty
|
||||
"hard": 5 // Rating for Hard difficulty
|
||||
},
|
||||
"songVariations": [ // Available variation files.
|
||||
"erect" // This says dadbattle-metadata-erect.json exists.
|
||||
],
|
||||
"difficulties": [ // Available difficulties.
|
||||
"easy",
|
||||
"normal",
|
||||
"hard"
|
||||
],
|
||||
"characters": { // Characters to use for this variation.
|
||||
"player": "bf", // Boyfriend
|
||||
"girlfriend": "gf", // Girlfriend
|
||||
"opponent": "dad" // Daddy Dearest
|
||||
},
|
||||
"stage": "mainStage", // Week 1 stage.
|
||||
"noteStyle": "funkin" // Default note style.
|
||||
},
|
||||
"generatedBy": "EliteMasterEric (by hand)" // Unique string.
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### <songid>-chart.json
|
||||
Song chart data for the `default` variation.
|
||||
|
||||
### <songid>-metadata-<variation>.json
|
||||
*(optional)* Song metadata for alternate variations.
|
||||
|
||||
### <songid>-chart-<variation>.json
|
||||
*(optional)* Song chart data for alternate variations.
|
6
hmm.json
6
hmm.json
|
@ -46,10 +46,10 @@
|
|||
"version": "3.5.0"
|
||||
},
|
||||
{
|
||||
"name": "funkVis",
|
||||
"name": "funkin.vis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7fc9901553fbe9b8fcf6e2e84bc86eabeaf29701",
|
||||
"ref": "98c9db09f0bbfedfe67a84538a5814aaef80bdea",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
|
@ -80,7 +80,7 @@
|
|||
"name": "hxCodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "387e1665d6feb5762358134f168e6ebfe46acec8",
|
||||
"ref": "c0c7f2680cc190c932a549c2e2fdd9b0ba2bd10e",
|
||||
"url": "https://github.com/FunkinCrew/hxCodec"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -113,7 +113,9 @@ class Main extends Sprite
|
|||
|
||||
addChild(game);
|
||||
|
||||
#if debug
|
||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
addChild(fpsCounter);
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
@ -74,6 +74,7 @@ class InitState extends FlxState
|
|||
//
|
||||
|
||||
// Setup window events (like callbacks for onWindowClose)
|
||||
// and fullscreen keybind setup
|
||||
WindowUtil.initWindowEvents();
|
||||
// Disable the thing on Windows where it tries to send a bug report to Microsoft because why do they care?
|
||||
WindowUtil.disableCrashHandler();
|
||||
|
@ -304,7 +305,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)
|
||||
{
|
||||
|
|
|
@ -53,7 +53,7 @@ class Preferences
|
|||
|
||||
static function get_flashingLights():Bool
|
||||
{
|
||||
return Save.instance.options.flashingLights;
|
||||
return Save?.instance?.options?.flashingLights ?? true;
|
||||
}
|
||||
|
||||
static function set_flashingLights(value:Bool):Bool
|
||||
|
@ -115,7 +115,7 @@ class Preferences
|
|||
|
||||
static function get_autoPause():Bool
|
||||
{
|
||||
return Save.instance.options.autoPause;
|
||||
return Save?.instance?.options?.autoPause ?? true;
|
||||
}
|
||||
|
||||
static function set_autoPause(value:Bool):Bool
|
||||
|
|
|
@ -8,8 +8,8 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|||
import flixel.math.FlxMath;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.util.MathUtil;
|
||||
import funkVis.dsp.SpectralAnalyzer;
|
||||
import funkVis.audioclip.frontends.LimeAudioClip;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
import funkin.vis.audioclip.frontends.LimeAudioClip;
|
||||
|
||||
using Lambda;
|
||||
|
||||
|
@ -90,7 +90,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
}
|
||||
|
||||
/**
|
||||
* TJW funkVis based visualizer! updateFFT() is the old nasty shit that dont worky!
|
||||
* TJW funkin.vis based visualizer! updateFFT() is the old nasty shit that dont worky!
|
||||
*/
|
||||
function drawFFT():Void
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
9
source/funkin/data/dialogue/conversation/CHANGELOG.md
Normal file
9
source/funkin/data/dialogue/conversation/CHANGELOG.md
Normal 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.
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.data.dialogue;
|
||||
package funkin.data.dialogue.conversation;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
|
@ -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>
|
13
source/funkin/data/dialogue/dialoguebox/CHANGELOG.md
Normal file
13
source/funkin/data/dialogue/dialoguebox/CHANGELOG.md
Normal 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.
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.data.dialogue;
|
||||
package funkin.data.dialogue.dialoguebox;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
|
@ -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>
|
9
source/funkin/data/dialogue/speaker/CHANGELOG.md
Normal file
9
source/funkin/data/dialogue/speaker/CHANGELOG.md
Normal 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.
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.data.dialogue;
|
||||
package funkin.data.dialogue.speaker;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
|
@ -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>
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.data.freeplay;
|
||||
package funkin.data.freeplay.album;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
|
@ -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>
|
9
source/funkin/data/freeplay/album/CHANGELOG.md
Normal file
9
source/funkin/data/freeplay/album/CHANGELOG.md
Normal 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.
|
36
source/funkin/data/song/CHANGELOG.md
Normal file
36
source/funkin/data/song/CHANGELOG.md
Normal 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.
|
14
source/funkin/data/stage/CHANGELOG.md
Normal file
14
source/funkin/data/stage/CHANGELOG.md
Normal 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.
|
|
@ -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";
|
||||
|
||||
|
|
9
source/funkin/data/story/level/CHANGELOG.md
Normal file
9
source/funkin/data/story/level/CHANGELOG.md
Normal 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.
|
|
@ -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;
|
||||
|
||||
/**
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -66,6 +66,7 @@ class Controls extends FlxActionSet
|
|||
var _volume_up = new FunkinAction(Action.VOLUME_UP);
|
||||
var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
|
||||
var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
|
||||
var _fullscreen = new FunkinAction(Action.FULLSCREEN);
|
||||
|
||||
var byName:Map<String, FunkinAction> = new Map<String, FunkinAction>();
|
||||
|
||||
|
@ -272,6 +273,11 @@ class Controls extends FlxActionSet
|
|||
inline function get_VOLUME_MUTE()
|
||||
return _volume_mute.check();
|
||||
|
||||
public var FULLSCREEN(get, never):Bool;
|
||||
|
||||
inline function get_FULLSCREEN()
|
||||
return _fullscreen.check();
|
||||
|
||||
public function new(name, scheme:KeyboardScheme = null)
|
||||
{
|
||||
super(name);
|
||||
|
@ -296,6 +302,7 @@ class Controls extends FlxActionSet
|
|||
add(_volume_up);
|
||||
add(_volume_down);
|
||||
add(_volume_mute);
|
||||
add(_fullscreen);
|
||||
|
||||
for (action in digitalActions) {
|
||||
if (Std.isOfType(action, FunkinAction)) {
|
||||
|
@ -399,6 +406,7 @@ class Controls extends FlxActionSet
|
|||
case VOLUME_UP: _volume_up;
|
||||
case VOLUME_DOWN: _volume_down;
|
||||
case VOLUME_MUTE: _volume_mute;
|
||||
case FULLSCREEN: _fullscreen;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,6 +482,8 @@ class Controls extends FlxActionSet
|
|||
func(_volume_down, JUST_PRESSED);
|
||||
case VOLUME_MUTE:
|
||||
func(_volume_mute, JUST_PRESSED);
|
||||
case FULLSCREEN:
|
||||
func(_fullscreen, JUST_PRESSED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -668,6 +678,7 @@ class Controls extends FlxActionSet
|
|||
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
|
||||
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
|
||||
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
|
||||
bindKeys(Control.FULLSCREEN, getDefaultKeybinds(scheme, Control.FULLSCREEN));
|
||||
|
||||
bindMobileLol();
|
||||
}
|
||||
|
@ -696,6 +707,8 @@ class Controls extends FlxActionSet
|
|||
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
|
||||
case Control.FULLSCREEN: return [FlxKey.F];
|
||||
|
||||
}
|
||||
case Duo(true):
|
||||
switch (control) {
|
||||
|
@ -719,6 +732,8 @@ class Controls extends FlxActionSet
|
|||
case Control.VOLUME_UP: return [PLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO];
|
||||
case Control.FULLSCREEN: return [FlxKey.F];
|
||||
|
||||
}
|
||||
case Duo(false):
|
||||
switch (control) {
|
||||
|
@ -742,6 +757,8 @@ class Controls extends FlxActionSet
|
|||
case Control.VOLUME_UP: return [NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [NUMPADZERO];
|
||||
case Control.FULLSCREEN: return [];
|
||||
|
||||
}
|
||||
default:
|
||||
// Fallthrough.
|
||||
|
@ -876,6 +893,7 @@ class Controls extends FlxActionSet
|
|||
case Control.CUTSCENE_ADVANCE: return [A];
|
||||
case Control.DEBUG_MENU: return [];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.FULLSCREEN: return [];
|
||||
default:
|
||||
// Fallthrough.
|
||||
}
|
||||
|
@ -1398,6 +1416,7 @@ enum Control
|
|||
ACCEPT;
|
||||
BACK;
|
||||
PAUSE;
|
||||
FULLSCREEN;
|
||||
// CUTSCENE
|
||||
CUTSCENE_ADVANCE;
|
||||
// SCREENSHOT
|
||||
|
@ -1443,6 +1462,7 @@ enum abstract Action(String) to String from String
|
|||
var BACK = "back";
|
||||
var PAUSE = "pause";
|
||||
var RESET = "reset";
|
||||
var FULLSCREEN = "fullscreen";
|
||||
// SCREENSHOT
|
||||
var SCREENSHOT = "screenshot";
|
||||
// CUTSCENE
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -568,7 +568,7 @@ class PlayState extends MusicBeatSubState
|
|||
var generatedMusic:Bool = false;
|
||||
var perfectMode:Bool = false;
|
||||
|
||||
static final BACKGROUND_COLOR:FlxColor = FlxColor.MAGENTA;
|
||||
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
|
||||
|
||||
/**
|
||||
* Instantiate a new PlayState.
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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:
|
||||
|
|
12
source/funkin/save/changelog.md
Normal file
12
source/funkin/save/changelog.md
Normal 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`
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.anyJustPressed([LEFT, RIGHT]))
|
||||
{
|
||||
if (FlxG.keys.justPressed.RIGHT)
|
||||
{
|
||||
Conductor.instance.inputOffset += 1.0 * multiply;
|
||||
localConductor.inputOffset += 1 * multiply;
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
if (controls.BACK)
|
||||
{
|
||||
daNote.alpha = 1;
|
||||
// daNote.data.strumTime += Conductor.instance.beatLengthMs * 8;
|
||||
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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -336,13 +336,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
exitMovers.set([blackOverlayBullshitLOLXD, bgDad],
|
||||
{
|
||||
x: FlxG.width * 1.5,
|
||||
y: bgDad.height,
|
||||
speed: 0.4,
|
||||
wait: 0
|
||||
});
|
||||
|
||||
add(bgDad);
|
||||
FlxTween.tween(blackOverlayBullshitLOLXD, {x: pinkBack.width * 0.75}, 1, {ease: FlxEase.quintOut});
|
||||
FlxTween.tween(blackOverlayBullshitLOLXD, {x: pinkBack.width * 0.75}, 0.7, {ease: FlxEase.quintOut});
|
||||
|
||||
blackOverlayBullshitLOLXD.shader = bgDad.shader;
|
||||
|
||||
|
@ -409,6 +408,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var sillyStroke:StrokeShader = new StrokeShader(0xFFFFFFFF, 2, 2);
|
||||
fnfFreeplay.shader = sillyStroke;
|
||||
ostName.shader = sillyStroke;
|
||||
add(fnfFreeplay);
|
||||
add(ostName);
|
||||
|
||||
|
@ -430,6 +430,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
add(fp);
|
||||
|
||||
var clearBoxSprite:FlxSprite = new FlxSprite(1165, 65).loadGraphic(Paths.image('freeplay/clearBox'));
|
||||
clearBoxSprite.visible = false;
|
||||
add(clearBoxSprite);
|
||||
|
||||
txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR);
|
||||
|
@ -456,9 +457,17 @@ class FreeplayState extends MusicBeatSubState
|
|||
default:
|
||||
generateSongList({filterType: REGEXP, filterData: str}, true);
|
||||
}
|
||||
|
||||
// We want to land on the first song of the group, rather than random song when changing letter sorts
|
||||
// that is, only if there's more than one song in the group!
|
||||
if (grpCapsules.members.length > 0)
|
||||
{
|
||||
curSelected = 1;
|
||||
changeSelection();
|
||||
}
|
||||
};
|
||||
|
||||
exitMovers.set([fp, txtCompletion, fnfHighscoreSpr],
|
||||
exitMovers.set([fp, txtCompletion, fnfHighscoreSpr, txtCompletion, clearBoxSprite],
|
||||
{
|
||||
x: FlxG.width,
|
||||
speed: 0.3
|
||||
|
@ -501,6 +510,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
fp.visible = true;
|
||||
fp.updateScore(0);
|
||||
|
||||
clearBoxSprite.visible = true;
|
||||
txtCompletion.visible = true;
|
||||
intendedCompletion = 0;
|
||||
|
||||
|
@ -744,13 +754,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
switch (txtCompletion.text.length)
|
||||
{
|
||||
case 3:
|
||||
txtCompletion.x = 1185 - 10;
|
||||
txtCompletion.offset.x = 10;
|
||||
case 2:
|
||||
txtCompletion.x = 1185;
|
||||
txtCompletion.offset.x = 0;
|
||||
case 1:
|
||||
txtCompletion.x = 1185 + 24;
|
||||
txtCompletion.offset.x = -24;
|
||||
default:
|
||||
txtCompletion.x = 1185;
|
||||
txtCompletion.offset.x = 0;
|
||||
}
|
||||
|
||||
handleInputs(elapsed);
|
||||
|
|
|
@ -54,7 +54,7 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
playMenuMusic();
|
||||
|
||||
persistentUpdate = false;
|
||||
persistentUpdate = true;
|
||||
persistentDraw = true;
|
||||
|
||||
var bg:FlxSprite = new FlxSprite(Paths.image('menuBG'));
|
||||
|
@ -103,6 +103,7 @@ class MainMenuState extends MusicBeatState
|
|||
// Freeplay has its own custom transition
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
openSubState(new FreeplayState());
|
||||
});
|
||||
|
||||
|
@ -274,6 +275,8 @@ class MainMenuState extends MusicBeatState
|
|||
public function openPrompt(prompt:Prompt, onClose:Void->Void):Void
|
||||
{
|
||||
menuItems.enabled = false;
|
||||
persistentUpdate = false;
|
||||
|
||||
prompt.closeCallback = function() {
|
||||
menuItems.enabled = true;
|
||||
if (onClose != null) onClose();
|
||||
|
@ -326,6 +329,8 @@ class MainMenuState extends MusicBeatState
|
|||
#if CHART_EDITOR_SUPPORTED
|
||||
if (controls.DEBUG_MENU)
|
||||
{
|
||||
persistentUpdate = false;
|
||||
|
||||
FlxG.state.openSubState(new DebugMenuSubState());
|
||||
}
|
||||
#end
|
||||
|
|
|
@ -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,19 @@ 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)
|
||||
else if (y >= -height)
|
||||
{
|
||||
lerpYPos = -height - 10;
|
||||
alphaTarget = 0;
|
||||
}
|
||||
|
||||
if (y <= -height)
|
||||
{
|
||||
|
@ -103,7 +108,6 @@ class FunkinSoundTray extends FlxSoundTray
|
|||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the little volume tray slide out.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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").
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,12 +12,12 @@ import funkin.ui.MusicBeatState;
|
|||
* After about 2 minutes of inactivity on the title screen,
|
||||
* the game will enter the Attract state, as a reference to physical arcade machines.
|
||||
*
|
||||
* In the current version, this just plays the Kickstarter trailer, but this can be changed to
|
||||
* In the current version, this just plays the ~~Kickstarter trailer~~ Erect teaser, but this can be changed to
|
||||
* gameplay footage, a generic game trailer, or something more elaborate.
|
||||
*/
|
||||
class AttractState extends MusicBeatState
|
||||
{
|
||||
static final ATTRACT_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('kickstarterTrailer', 'shared'));
|
||||
static final ATTRACT_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('erectTeaser'));
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
|
|
|
@ -251,7 +251,7 @@ class TitleState extends MusicBeatState
|
|||
|
||||
var transitioning:Bool = false;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
FlxG.bitmapLog.add(FlxG.camera.buffer);
|
||||
|
||||
|
@ -286,7 +286,6 @@ class TitleState extends MusicBeatState
|
|||
}
|
||||
|
||||
if (FlxG.sound.music != null) Conductor.instance.update(FlxG.sound.music.time);
|
||||
if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen;
|
||||
|
||||
// do controls.PAUSE | controls.ACCEPT instead?
|
||||
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER;
|
||||
|
|
|
@ -326,8 +326,8 @@ class LoadingState extends MusicBeatSubState
|
|||
// I will fix this properly later I swear -eric
|
||||
if (!path.endsWith('.png')) continue;
|
||||
|
||||
new Future<String>(function() {
|
||||
FunkinSprite.cacheTexture(path);
|
||||
|
||||
// Another dumb hack: FlxAnimate fetches from OpenFL's BitmapData cache directly and skips the FlxGraphic cache.
|
||||
// Since FlxGraphic tells OpenFL to not cache it, we have to do it manually.
|
||||
if (path.endsWith('spritemap1.png'))
|
||||
|
@ -335,6 +335,11 @@ class LoadingState extends MusicBeatSubState
|
|||
trace('Preloading FlxAnimate asset: ${path}');
|
||||
openfl.Assets.getBitmapData(path, true);
|
||||
}
|
||||
return 'Done precaching ${path}';
|
||||
}, true);
|
||||
|
||||
trace("Queued ${path} for precaching");
|
||||
// FunkinSprite.cacheTexture(path);
|
||||
}
|
||||
|
||||
// FunkinSprite.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above!
|
||||
|
|
|
@ -112,6 +112,7 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
var logo:Bitmap;
|
||||
#if TOUCH_HERE_TO_PLAY
|
||||
var touchHereToPlay:Bitmap;
|
||||
var touchHereSprite:Sprite;
|
||||
#end
|
||||
var progressBarPieces:Array<Sprite>;
|
||||
var progressBar:Bitmap;
|
||||
|
@ -119,10 +120,12 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
var progressRightText:TextField;
|
||||
|
||||
var dspText:TextField;
|
||||
var fnfText:TextField;
|
||||
var enhancedText:TextField;
|
||||
var stereoText:TextField;
|
||||
|
||||
var vfdShader:VFDOverlay;
|
||||
var vfdBitmap:Bitmap;
|
||||
var box:Sprite;
|
||||
var progressLines:Sprite;
|
||||
|
||||
|
@ -162,18 +165,6 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
});
|
||||
// addChild(logo);
|
||||
|
||||
#if TOUCH_HERE_TO_PLAY
|
||||
touchHereToPlay = createBitmap(TouchHereToPlayImage, function(bmp:Bitmap) {
|
||||
// Scale and center the touch to start image.
|
||||
// We have to do this inside the async call, after the image size is known.
|
||||
bmp.scaleX = bmp.scaleY = ratio;
|
||||
bmp.x = (this._width - bmp.width) / 2;
|
||||
bmp.y = (this._height - bmp.height) / 2;
|
||||
});
|
||||
touchHereToPlay.alpha = 0.0;
|
||||
addChild(touchHereToPlay);
|
||||
#end
|
||||
|
||||
var amountOfPieces:Int = 16;
|
||||
progressBarPieces = [];
|
||||
var maxBarWidth = this._width - BAR_PADDING * 2;
|
||||
|
@ -212,6 +203,7 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
// Create the progress message.
|
||||
progressLeftText = new TextField();
|
||||
dspText = new TextField();
|
||||
fnfText = new TextField();
|
||||
enhancedText = new TextField();
|
||||
stereoText = new TextField();
|
||||
|
||||
|
@ -262,6 +254,15 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
dspText.y = -5;
|
||||
box.addChild(dspText);
|
||||
|
||||
fnfText.selectable = false;
|
||||
fnfText.textColor = 0x000000;
|
||||
fnfText.width = this._width;
|
||||
fnfText.height = 20;
|
||||
fnfText.x = 75;
|
||||
fnfText.y = -5;
|
||||
fnfText.text = 'FNF';
|
||||
box.addChild(fnfText);
|
||||
|
||||
enhancedText.selectable = false;
|
||||
enhancedText.textColor = Constants.COLOR_PRELOADER_BAR;
|
||||
enhancedText.width = this._width;
|
||||
|
@ -289,11 +290,27 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
// gradient.graphics.endFill();
|
||||
// addChild(gradient);
|
||||
|
||||
var vfdBitmap:Bitmap = new Bitmap(new BitmapData(this._width, this._height, true, 0xFFFFFFFF));
|
||||
vfdBitmap = new Bitmap(new BitmapData(this._width, this._height, true, 0xFFFFFFFF));
|
||||
addChild(vfdBitmap);
|
||||
|
||||
vfdShader = new VFDOverlay();
|
||||
vfdBitmap.shader = vfdShader;
|
||||
|
||||
#if TOUCH_HERE_TO_PLAY
|
||||
touchHereToPlay = createBitmap(TouchHereToPlayImage, function(bmp:Bitmap) {
|
||||
// Scale and center the touch to start image.
|
||||
// We have to do this inside the async call, after the image size is known.
|
||||
bmp.scaleX = bmp.scaleY = ratio;
|
||||
bmp.x = (this._width - bmp.width) / 2;
|
||||
bmp.y = (this._height - bmp.height) / 2;
|
||||
});
|
||||
touchHereToPlay.alpha = 0.0;
|
||||
|
||||
touchHereSprite = new Sprite();
|
||||
touchHereSprite.buttonMode = false;
|
||||
touchHereSprite.addChild(touchHereToPlay);
|
||||
addChild(touchHereSprite);
|
||||
#end
|
||||
}
|
||||
|
||||
var lastElapsed:Float = 0.0;
|
||||
|
@ -802,9 +819,14 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
|
||||
if (touchHereToPlay.alpha < 1.0)
|
||||
{
|
||||
touchHereSprite.buttonMode = true;
|
||||
touchHereToPlay.alpha = 1.0;
|
||||
removeChild(vfdBitmap);
|
||||
|
||||
addEventListener(MouseEvent.CLICK, onTouchHereToPlay);
|
||||
touchHereSprite.addEventListener(MouseEvent.MOUSE_OVER, overTouchHereToPlay);
|
||||
touchHereSprite.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownTouchHereToPlay);
|
||||
touchHereSprite.addEventListener(MouseEvent.MOUSE_OUT, outTouchHereToPlay);
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
|
@ -818,9 +840,34 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
}
|
||||
|
||||
#if TOUCH_HERE_TO_PLAY
|
||||
function overTouchHereToPlay(e:MouseEvent):Void
|
||||
{
|
||||
touchHereToPlay.scaleX = touchHereToPlay.scaleY = ratio * 1.1;
|
||||
touchHereToPlay.x = (this._width - touchHereToPlay.width) / 2;
|
||||
touchHereToPlay.y = (this._height - touchHereToPlay.height) / 2;
|
||||
}
|
||||
|
||||
function outTouchHereToPlay(e:MouseEvent):Void
|
||||
{
|
||||
touchHereToPlay.scaleX = touchHereToPlay.scaleY = ratio * 1;
|
||||
touchHereToPlay.x = (this._width - touchHereToPlay.width) / 2;
|
||||
touchHereToPlay.y = (this._height - touchHereToPlay.height) / 2;
|
||||
}
|
||||
|
||||
function mouseDownTouchHereToPlay(e:MouseEvent):Void
|
||||
{
|
||||
touchHereToPlay.y += 10;
|
||||
}
|
||||
|
||||
function onTouchHereToPlay(e:MouseEvent):Void
|
||||
{
|
||||
touchHereToPlay.x = (this._width - touchHereToPlay.width) / 2;
|
||||
touchHereToPlay.y = (this._height - touchHereToPlay.height) / 2;
|
||||
|
||||
removeEventListener(MouseEvent.CLICK, onTouchHereToPlay);
|
||||
touchHereSprite.removeEventListener(MouseEvent.MOUSE_OVER, overTouchHereToPlay);
|
||||
touchHereSprite.removeEventListener(MouseEvent.MOUSE_OUT, outTouchHereToPlay);
|
||||
touchHereSprite.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownTouchHereToPlay);
|
||||
|
||||
// This is the actual thing that makes the game load.
|
||||
immediatelyStartGame();
|
||||
|
@ -931,9 +978,13 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
progressLeftText.text = text;
|
||||
|
||||
dspText.defaultTextFormat = new TextFormat("Quantico", 20, 0x000000, false);
|
||||
dspText.text = 'DSP\t\t\t\t\tFNF'; // fukin dum....
|
||||
dspText.text = 'DSP'; // fukin dum....
|
||||
dspText.textColor = 0x000000;
|
||||
|
||||
fnfText.defaultTextFormat = new TextFormat("Quantico", 20, 0x000000, false);
|
||||
fnfText.text = 'FNF';
|
||||
fnfText.textColor = 0x000000;
|
||||
|
||||
enhancedText.defaultTextFormat = new TextFormat("Inconsolata Black", 16, Constants.COLOR_PRELOADER_BAR, false);
|
||||
enhancedText.text = 'ENHANCED';
|
||||
enhancedText.textColor = Constants.COLOR_PRELOADER_BAR;
|
||||
|
@ -971,6 +1022,7 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
progressRightText.alpha = logo.alpha;
|
||||
box.alpha = logo.alpha;
|
||||
dspText.alpha = logo.alpha;
|
||||
fnfText.alpha = logo.alpha;
|
||||
enhancedText.alpha = logo.alpha;
|
||||
stereoText.alpha = logo.alpha;
|
||||
progressLines.alpha = logo.alpha;
|
||||
|
@ -999,9 +1051,9 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
*/
|
||||
override function createSiteLockFailureScreen():Void
|
||||
{
|
||||
addChild(createSiteLockFailureBackground(Constants.COLOR_PRELOADER_LOCK_BG, Constants.COLOR_PRELOADER_LOCK_BG));
|
||||
addChild(createSiteLockFailureIcon(Constants.COLOR_PRELOADER_LOCK_FG, 0.9));
|
||||
addChild(createSiteLockFailureText(30));
|
||||
// addChild(createSiteLockFailureBackground(Constants.COLOR_PRELOADER_LOCK_BG, Constants.COLOR_PRELOADER_LOCK_BG));
|
||||
// addChild(createSiteLockFailureIcon(Constants.COLOR_PRELOADER_LOCK_FG, 0.9));
|
||||
// addChild(createSiteLockFailureText(30));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -90,6 +90,16 @@ class WindowUtil
|
|||
openfl.Lib.current.stage.application.onExit.add(function(exitCode:Int) {
|
||||
windowExit.dispatch(exitCode);
|
||||
});
|
||||
|
||||
openfl.Lib.current.stage.addEventListener(openfl.events.KeyboardEvent.KEY_DOWN, (e:openfl.events.KeyboardEvent) -> {
|
||||
for (key in PlayerSettings.player1.controls.getKeysForAction(FULLSCREEN))
|
||||
{
|
||||
if (e.keyCode == key)
|
||||
{
|
||||
openfl.Lib.application.window.fullscreen = !openfl.Lib.application.window.fullscreen;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue