diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..e8e490865 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,12 @@ +# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder +Documentation: +- changed-files: + - any-glob-to-any-file: + - any-glob-to-any-file: + - docs/* + - '**/*.md' + +# Adds Haxe tag to PR's changing haxe code files +Haxe: +- changed-files: + - any-glob-to-any-file: '**/*.hx' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000..0bcc420d3 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,14 @@ +name: "Pull Request Labeler" +on: +- pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + sync-labels: true diff --git a/.gitmodules b/.gitmodules index ad8099e60..452c0089b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "assets"] path = assets - url = https://github.com/FunkinCrew/Funkin-assets-secret + url = https://github.com/FunkinCrew/Funkin-Assets-secret [submodule "art"] path = art - url = https://github.com/FunkinCrew/Funkin-art-secret + url = https://github.com/FunkinCrew/Funkin-Art-secret diff --git a/Project.xml b/Project.xml index f19a19e8c..fcd29a25e 100644 --- a/Project.xml +++ b/Project.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<project> +<project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd"> <!-- _________________________ Application Settings _________________________ --> <app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.3.3" company="ninjamuffin99" /> <!--Switch Export with Unique ApplicationID and Icon--> @@ -28,7 +29,7 @@ <set name="BUILD_DIR" value="export/debug" if="debug" /> <set name="BUILD_DIR" value="export/release" unless="debug" /> <set name="BUILD_DIR" value="export/32bit" if="32bit" /> - <classpath name="source" /> + <source path="source" /> <assets path="assets/preload" rename="assets" exclude="*.ogg|*.wav" if="web" /> <assets path="assets/preload" rename="assets" exclude="*.mp3|*.wav" unless="web" /> <define name="PRELOAD_ALL" unless="web" /> @@ -126,6 +127,7 @@ <haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback --> <haxelib name="funkin.vis"/> + <haxelib name="FlxPartialSound" /> <!-- Loading partial sound data --> <haxelib name="json2object" /> <!-- JSON parsing --> <haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" --> diff --git a/checkstyle.json b/checkstyle.json index dc89409da..41f0a7998 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -79,7 +79,7 @@ { "props": { "ignoreExtern": true, - "format": "^[a-z][A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", + "format": "^[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$", "tokens": ["INLINE", "NOTINLINE"] }, "type": "ConstantName" diff --git a/hmm.json b/hmm.json index 716754b59..fe4e56dd9 100644 --- a/hmm.json +++ b/hmm.json @@ -1,5 +1,12 @@ { "dependencies": [ + { + "name": "FlxPartialSound", + "type": "git", + "dir": null, + "ref": "44aa7eb", + "url": "https://github.com/FunkinCrew/FlxPartialSound.git" + }, { "name": "discord_rpc", "type": "git", diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 54a4b7acf..b0a97c4fa 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -123,9 +123,17 @@ class Paths return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}'; } - public static function inst(song:String, ?suffix:String = ''):String + /** + * Gets the path to an `Inst.mp3/ogg` song instrumental from songs:assets/songs/`song`/ + * @param song name of the song to get instrumental for + * @param suffix any suffix to add to end of song name, used for `-erect` variants usually + * @param withExtension if it should return with the audio file extension `.mp3` or `.ogg`. + * @return String + */ + public static function inst(song:String, ?suffix:String = '', ?withExtension:Bool = true):String { - return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}'; + var ext:String = withExtension ? '.${Constants.EXT_SOUND}' : ''; + return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix$ext'; } public static function image(key:String, ?library:String):String @@ -153,3 +161,11 @@ class Paths return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library)); } } + +enum abstract PathsFunction(String) +{ + var MUSIC; + var INST; + var VOICES; + var SOUND; +} diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 939b17f28..b94c6008c 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -11,6 +11,11 @@ import funkin.audio.waveform.WaveformDataParser; import funkin.data.song.SongData.SongMusicData; import funkin.data.song.SongRegistry; import funkin.util.tools.ICloneable; +import funkin.util.flixel.sound.FlxPartialSound; +import funkin.Paths.PathsFunction; +import openfl.Assets; +import lime.app.Future; +import lime.app.Promise; import openfl.media.SoundMixer; #if (openfl >= "8.0.0") @@ -341,23 +346,68 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound> FlxG.log.warn('Tried and failed to find music metadata for $key'); } } - - var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true); - if (music != null) + var pathsFunction = params.pathsFunction ?? MUSIC; + var suffix = params.suffix ?? ''; + var pathToUse = switch (pathsFunction) { - FlxG.sound.music = music; + case MUSIC: Paths.music('$key/$key'); + case INST: Paths.inst('$key', suffix); + default: Paths.music('$key/$key'); + } - // Prevent repeat update() and onFocus() calls. - FlxG.sound.list.remove(FlxG.sound.music); + var shouldLoadPartial = params.partialParams?.loadPartial ?? false; - return true; + if (shouldLoadPartial) + { + var music = FunkinSound.loadPartial(pathToUse, params.partialParams?.start ?? 0, params.partialParams?.end ?? 1, params?.startingVolume ?? 1.0, + params.loop ?? true, false, false, params.onComplete); + + if (music != null) + { + while (partialQueue.length > 0) + { + @:nullSafety(Off) + partialQueue.pop().error("Cancel loading partial sound"); + } + + partialQueue.push(music); + + @:nullSafety(Off) + music.future.onComplete(function(partialMusic:Null<FunkinSound>) { + FlxG.sound.music = partialMusic; + FlxG.sound.list.remove(FlxG.sound.music); + + if (params.onLoad != null) params.onLoad(); + }); + + return true; + } + else + { + return false; + } } else { - return false; + var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true); + if (music != null) + { + FlxG.sound.music = music; + + // Prevent repeat update() and onFocus() calls. + FlxG.sound.list.remove(FlxG.sound.music); + + return true; + } + else + { + return false; + } } } + static var partialQueue:Array<Promise<Null<FunkinSound>>> = []; + /** * Creates a new `FunkinSound` object synchronously. * @@ -414,6 +464,42 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound> return sound; } + /** + * Will load a section of a sound file, useful for Freeplay where we don't want to load all the bytes of a song + * @param path The path to the sound file + * @param start The start time of the sound file + * @param end The end time of the sound file + * @param volume Volume to start at + * @param looped Whether the sound file should loop + * @param autoDestroy Whether the sound file should be destroyed after it finishes playing + * @param autoPlay Whether the sound file should play immediately + * @param onComplete Callback when the sound finishes playing + * @param onLoad Callback when the sound finishes loading + * @return A FunkinSound object + */ + public static function loadPartial(path:String, start:Float = 0, end:Float = 1, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, + autoPlay:Bool = true, ?onComplete:Void->Void, ?onLoad:Void->Void):Promise<Null<FunkinSound>> + { + var promise:lime.app.Promise<Null<FunkinSound>> = new lime.app.Promise<Null<FunkinSound>>(); + + // split the path and get only after first : + // we are bypassing the openfl/lime asset library fuss + path = Paths.stripLibrary(path); + + var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end); + + promise.future.onError(function(e) { + soundRequest.error("Sound loading was errored or cancelled"); + }); + + soundRequest.future.onComplete(function(partialSound) { + var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, onComplete, onLoad); + promise.complete(snd); + }); + + return promise; + } + @:nullSafety(Off) public override function destroy():Void { @@ -474,6 +560,12 @@ typedef FunkinSoundPlayMusicParams = */ var ?startingVolume:Float; + /** + * The suffix of the music file to play. Usually for "-erect" tracks when loading an INST file + * @default `` + */ + var ?suffix:String; + /** * Whether to override music if a different track is already playing. * @default `false` @@ -497,4 +589,22 @@ typedef FunkinSoundPlayMusicParams = * @default `true` */ var ?mapTimeChanges:Bool; + + /** + * Which Paths function to use to load a song + * @default `MUSIC` + */ + var ?pathsFunction:PathsFunction; + + var ?partialParams:PartialSoundParams; + + var ?onComplete:Void->Void; + var ?onLoad:Void->Void; +} + +typedef PartialSoundParams = +{ + var loadPartial:Bool; + var start:Float; + var end:Float; } diff --git a/source/funkin/data/song/CHANGELOG.md b/source/funkin/data/song/CHANGELOG.md index 3cd3af070..4f1c66ade 100644 --- a/source/funkin/data/song/CHANGELOG.md +++ b/source/funkin/data/song/CHANGELOG.md @@ -5,6 +5,10 @@ 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.3] +### Added +- Added `charter` field to denote authorship of a chart. + ## [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. diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 26380947a..bd25139a7 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -30,6 +30,9 @@ class SongMetadata implements ICloneable<SongMetadata> @:default("Unknown") public var artist:String; + @:optional + public var charter:Null<String> = null; + @:optional @:default(96) public var divisions:Null<Int>; // Optional field diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 277dcd9e1..a3305c4ec 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -20,7 +20,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> * Handle breaking changes by incrementing this value * and adding migration to the `migrateStageData()` function. */ - public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.2"; + public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.3"; public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x"; diff --git a/source/funkin/data/song/importer/FNFLegacyImporter.hx b/source/funkin/data/song/importer/FNFLegacyImporter.hx index ab2abda8e..fdfac7f72 100644 --- a/source/funkin/data/song/importer/FNFLegacyImporter.hx +++ b/source/funkin/data/song/importer/FNFLegacyImporter.hx @@ -36,7 +36,7 @@ class FNFLegacyImporter { trace('Migrating song metadata from FNF Legacy.'); - var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default'); + var songMetadata:SongMetadata = new SongMetadata('Import', Constants.DEFAULT_ARTIST, 'default'); var hadError:Bool = false; diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index fb9d9b4e2..8c45fac65 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -101,6 +101,10 @@ class PauseSubState extends MusicBeatSubState */ static final MUSIC_FINAL_VOLUME:Float = 0.75; + static final CHARTER_FADE_DELAY:Float = 15.0; + + static final CHARTER_FADE_DURATION:Float = 0.75; + /** * Defines which pause music to use. */ @@ -163,6 +167,12 @@ class PauseSubState extends MusicBeatSubState */ var metadataDeaths:FlxText; + /** + * A text object which displays the current song's artist. + * Fades to the charter after a period before fading back. + */ + var metadataArtist:FlxText; + /** * The actual text objects for the menu entries. */ @@ -203,6 +213,8 @@ class PauseSubState extends MusicBeatSubState regenerateMenu(); transitionIn(); + + startCharterTimer(); } /** @@ -222,6 +234,8 @@ class PauseSubState extends MusicBeatSubState public override function destroy():Void { super.destroy(); + charterFadeTween.cancel(); + charterFadeTween = null; pauseMusic.stop(); } @@ -270,16 +284,25 @@ class PauseSubState extends MusicBeatSubState metadata.scrollFactor.set(0, 0); add(metadata); - var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name - Artist'); + var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name'); metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); if (PlayState.instance?.currentChart != null) { - metadataSong.text = '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; + metadataSong.text = '${PlayState.instance.currentChart.songName}'; } metadataSong.scrollFactor.set(0, 0); metadata.add(metadataSong); - var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, FlxG.width - 40, 'Difficulty: '); + metadataArtist = new FlxText(20, metadataSong.y + 32, FlxG.width - 40, 'Artist: ${Constants.DEFAULT_ARTIST}'); + metadataArtist.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + if (PlayState.instance?.currentChart != null) + { + metadataArtist.text = 'Artist: ${PlayState.instance.currentChart.songArtist}'; + } + metadataArtist.scrollFactor.set(0, 0); + metadata.add(metadataArtist); + + var metadataDifficulty:FlxText = new FlxText(20, metadataArtist.y + 32, FlxG.width - 40, 'Difficulty: '); metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); if (PlayState.instance?.currentDifficulty != null) { @@ -288,12 +311,12 @@ class PauseSubState extends MusicBeatSubState metadataDifficulty.scrollFactor.set(0, 0); metadata.add(metadataDifficulty); - metadataDeaths = new FlxText(20, 15 + 64, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls'); + metadataDeaths = new FlxText(20, metadataDifficulty.y + 32, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls'); metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); metadataDeaths.scrollFactor.set(0, 0); metadata.add(metadataDeaths); - metadataPractice = new FlxText(20, 15 + 96, FlxG.width - 40, 'PRACTICE MODE'); + metadataPractice = new FlxText(20, metadataDeaths.y + 32, FlxG.width - 40, 'PRACTICE MODE'); metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; metadataPractice.scrollFactor.set(0, 0); @@ -302,6 +325,62 @@ class PauseSubState extends MusicBeatSubState updateMetadataText(); } + var charterFadeTween:Null<FlxTween> = null; + + function startCharterTimer():Void + { + charterFadeTween = FlxTween.tween(metadataArtist, {alpha: 0.0}, CHARTER_FADE_DURATION, + { + startDelay: CHARTER_FADE_DELAY, + ease: FlxEase.quartOut, + onComplete: (_) -> { + if (PlayState.instance?.currentChart != null) + { + metadataArtist.text = 'Charter: ${PlayState.instance.currentChart.charter ?? 'Unknown'}'; + } + else + { + metadataArtist.text = 'Charter: ${Constants.DEFAULT_CHARTER}'; + } + + FlxTween.tween(metadataArtist, {alpha: 1.0}, CHARTER_FADE_DURATION, + { + ease: FlxEase.quartOut, + onComplete: (_) -> { + startArtistTimer(); + } + }); + } + }); + } + + function startArtistTimer():Void + { + charterFadeTween = FlxTween.tween(metadataArtist, {alpha: 0.0}, CHARTER_FADE_DURATION, + { + startDelay: CHARTER_FADE_DELAY, + ease: FlxEase.quartOut, + onComplete: (_) -> { + if (PlayState.instance?.currentChart != null) + { + metadataArtist.text = 'Artist: ${PlayState.instance.currentChart.songArtist}'; + } + else + { + metadataArtist.text = 'Artist: ${Constants.DEFAULT_ARTIST}'; + } + + FlxTween.tween(metadataArtist, {alpha: 1.0}, CHARTER_FADE_DURATION, + { + ease: FlxEase.quartOut, + onComplete: (_) -> { + startCharterTimer(); + } + }); + } + }); + } + /** * Perform additional animations to transition the pause menu in when it is first displayed. */ diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a95166e21..0781e59b6 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1730,12 +1730,7 @@ class PlayState extends MusicBeatSubState */ function initStrumlines():Void { - var noteStyleId:String = switch (currentStageId) - { - case 'school': 'pixel'; - case 'schoolEvil': 'pixel'; - default: Constants.DEFAULT_NOTE_STYLE; - } + var noteStyleId:String = currentChart.noteStyle; var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); @@ -2322,8 +2317,6 @@ class PlayState extends MusicBeatSubState var notesInRange:Array<NoteSprite> = playerStrumline.getNotesMayHit(); var holdNotesInRange:Array<SustainTrail> = playerStrumline.getHoldNotesHitOrMissed(); - // If there are notes in range, pressing a key will cause a ghost miss. - var notesByDirection:Array<Array<NoteSprite>> = [[], [], [], []]; for (note in notesInRange) @@ -2345,17 +2338,27 @@ class PlayState extends MusicBeatSubState // Play the strumline animation. playerStrumline.playPress(input.noteDirection); + trace('PENALTY Score: ${songScore}'); } - else if (Constants.GHOST_TAPPING && (holdNotesInRange.length + notesInRange.length > 0) && notesInDirection.length == 0) + else if (Constants.GHOST_TAPPING && (!playerStrumline.mayGhostTap()) && notesInDirection.length == 0) { - // Pressed a wrong key with no notes nearby AND with notes in a different direction available. + // Pressed a wrong key with notes visible on-screen. // Perform a ghost miss (anti-spam). ghostNoteMiss(input.noteDirection, notesInRange.length > 0); // Play the strumline animation. playerStrumline.playPress(input.noteDirection); + trace('PENALTY Score: ${songScore}'); } - else if (notesInDirection.length > 0) + else if (notesInDirection.length == 0) + { + // Press a key with no penalty. + + // Play the strumline animation. + playerStrumline.playPress(input.noteDirection); + trace('NO PENALTY Score: ${songScore}'); + } + else { // Choose the first note, deprioritizing low priority notes. var targetNote:Null<NoteSprite> = notesInDirection.find((note) -> !note.lowPriority); @@ -2365,17 +2368,13 @@ class PlayState extends MusicBeatSubState // Judge and hit the note. trace('Hit note! ${targetNote.noteData}'); goodNoteHit(targetNote, input); + trace('Score: ${songScore}'); notesInDirection.remove(targetNote); // Play the strumline animation. playerStrumline.playConfirm(input.noteDirection); } - else - { - // Play the strumline animation. - playerStrumline.playPress(input.noteDirection); - } } while (inputReleaseQueue.length > 0) diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index 2d7099e8a..a3204329a 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -373,6 +373,10 @@ class HealthIcon extends FunkinSprite // Don't flip BF's icon here! That's done later. this.animation.add(Idle, [0], 0, false, false); this.animation.add(Losing, [1], 0, false, false); + if (animation.numFrames >= 3) + { + this.animation.add(Winning, [2], 0, false, false); + } } function correctCharacterId(charId:Null<String>):String diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 07d4ab69b..472c38bba 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -171,6 +171,20 @@ class Strumline extends FlxSpriteGroup updateNotes(); } + /** + * Returns `true` if no notes are in range of the strumline and the player can spam without penalty. + */ + public function mayGhostTap():Bool + { + // TODO: Refine this. Only querying "can be hit" is too tight but "is being rendered" is too loose. + // Also, if you just hit a note, there should be a (short) period where this is off so you can't spam. + + // If there are any notes on screen, we can't ghost tap. + return notes.members.filter(function(note:NoteSprite) { + return note != null && note.alive && !note.hasBeenHit; + }).length == 0; + } + /** * Return notes that are within `Constants.HIT_WINDOW` ms of the strumline. * @return An array of `NoteSprite` objects. diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 53408fb34..fd006c0f3 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -120,6 +120,18 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta return DEFAULT_ARTIST; } + /** + * The artist of the song. + */ + public var charter(get, never):String; + + function get_charter():String + { + if (_data != null) return _data?.charter ?? 'Unknown'; + if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.charter ?? 'Unknown'; + return Constants.DEFAULT_CHARTER; + } + /** * @param id The ID of the song to load. * @param ignoreErrors If false, an exception will be thrown if the song data could not be loaded. @@ -270,6 +282,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta difficulty.songName = metadata.songName; difficulty.songArtist = metadata.artist; + difficulty.charter = metadata.charter ?? Constants.DEFAULT_CHARTER; difficulty.timeFormat = metadata.timeFormat; difficulty.divisions = metadata.divisions; difficulty.timeChanges = metadata.timeChanges; @@ -334,6 +347,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta { difficulty.songName = metadata.songName; difficulty.songArtist = metadata.artist; + difficulty.charter = metadata.charter ?? Constants.DEFAULT_CHARTER; difficulty.timeFormat = metadata.timeFormat; difficulty.divisions = metadata.divisions; difficulty.timeChanges = metadata.timeChanges; @@ -590,6 +604,7 @@ class SongDifficulty public var songName:String = Constants.DEFAULT_SONGNAME; public var songArtist:String = Constants.DEFAULT_ARTIST; + public var charter:String = Constants.DEFAULT_CHARTER; public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT; public var divisions:Null<Int> = null; public var looped:Bool = false; diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index eb9eb1810..a6a4293a0 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -852,6 +852,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements } } + public override function toString():String + { + return 'Stage($id)'; + } + static function _fetchData(id:String):Null<StageData> { return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id)); diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 6d6e73e80..f8b1be9d2 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -62,7 +62,6 @@ class DebugMenuSubState extends MusicBeatSubState #if sys createItem("OPEN CRASH LOG FOLDER", openLogFolder); #end - FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y)); FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y + 500)); } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index a313981f4..260393fac 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1270,7 +1270,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var result:Null<SongMetadata> = songMetadata.get(selectedVariation); if (result == null) { - result = new SongMetadata('DadBattle', 'Kawai Sprite', selectedVariation); + result = new SongMetadata('Default Song Name', Constants.DEFAULT_ARTIST, selectedVariation); songMetadata.set(selectedVariation, result); } return result; @@ -4566,8 +4566,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } gridGhostHoldNote.visible = true; - gridGhostHoldNote.noteData = gridGhostNote.noteData; - gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection(); + gridGhostHoldNote.noteData = currentPlaceNoteData; + gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection(); gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true); gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes); diff --git a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx index aeb6dd0e4..7c20358a4 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx @@ -36,6 +36,8 @@ class ChartEditorHoldNoteSprite extends SustainTrail zoom *= 0.7; zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE; + flipY = false; + setup(); } diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index f85307c64..c97e8142d 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -29,6 +29,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox { var inputSongName:TextField; var inputSongArtist:TextField; + var inputSongCharter:TextField; var inputStage:DropDown; var inputNoteStyle:DropDown; var buttonCharacterPlayer:Button; @@ -89,6 +90,20 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox } }; + inputSongCharter.onChange = function(event:UIEvent) { + var valid:Bool = event.target.text != null && event.target.text != ''; + + if (valid) + { + inputSongCharter.removeClass('invalid-value'); + chartEditorState.currentSongMetadata.charter = event.target.text; + } + else + { + chartEditorState.currentSongMetadata.charter = null; + } + }; + inputStage.onChange = function(event:UIEvent) { var valid:Bool = event.data != null && event.data.id != null; @@ -104,6 +119,8 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox if (event.data?.id == null) return; chartEditorState.currentSongNoteStyle = event.data.id; }; + var startingValueNoteStyle = ChartEditorDropdowns.populateDropdownWithNoteStyles(inputNoteStyle, chartEditorState.currentSongMetadata.playData.noteStyle); + inputNoteStyle.value = startingValueNoteStyle; inputBPM.onChange = function(event:UIEvent) { if (event.value == null || event.value <= 0) return; @@ -176,6 +193,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox inputSongName.value = chartEditorState.currentSongMetadata.songName; inputSongArtist.value = chartEditorState.currentSongMetadata.artist; + inputSongCharter.value = chartEditorState.currentSongMetadata.charter; inputStage.value = chartEditorState.currentSongMetadata.playData.stage; inputNoteStyle.value = chartEditorState.currentSongMetadata.playData.noteStyle; inputBPM.value = chartEditorState.currentSongMetadata.timeChanges[0].bpm; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 530f28c33..61c53c7d7 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -31,6 +31,7 @@ import funkin.graphics.shaders.StrokeShader; import funkin.input.Controls; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song; +import funkin.ui.story.Level; import funkin.save.Save; import funkin.save.Save.SaveScoreData; import funkin.ui.AtlasText; @@ -212,10 +213,24 @@ class FreeplayState extends MusicBeatSubState // programmatically adds the songs via LevelRegistry and SongRegistry for (levelId in LevelRegistry.instance.listSortedLevelIds()) { - for (songId in LevelRegistry.instance.parseEntryData(levelId).songs) + var level:Level = LevelRegistry.instance.fetchEntry(levelId); + + if (level == null) + { + trace('[WARN] Could not find level with id (${levelId})'); + continue; + } + + for (songId in level.getSongs()) { var song:Song = SongRegistry.instance.fetchEntry(songId); + if (song == null) + { + trace('[WARN] Could not find song with id (${songId})'); + continue; + } + // Only display songs which actually have available difficulties for the current character. var displayedVariations = song.getVariationsByCharId(currentCharacter); var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false); @@ -1619,23 +1634,25 @@ class FreeplayState extends MusicBeatSubState } else { + var potentiallyErect:String = (currentDifficulty == "erect") || (currentDifficulty == "nightmare") ? "-erect" : ""; // TODO: Stream the instrumental of the selected song? - var didReplace:Bool = FunkinSound.playMusic('freakyMenu', + FunkinSound.playMusic(daSongCapsule.songData.songId, { startingVolume: 0.0, overrideExisting: true, - restartTrack: false + restartTrack: false, + pathsFunction: INST, + suffix: potentiallyErect, + partialParams: + { + loadPartial: true, + start: 0, + end: 0.1 + }, + onLoad: function() { + FlxG.sound.music.fadeIn(2, 0, 0.4); + } }); - if (didReplace) - { - FunkinSound.playMusic('freakyMenu', - { - startingVolume: 0.0, - overrideExisting: true, - restartTrack: false - }); - FlxG.sound.music.fadeIn(2, 0, 0.8); - } } grpCapsules.members[curSelected].selected = true; } diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index fc2a8c7d7..22262006a 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -49,6 +49,8 @@ class MainMenuState extends MusicBeatState DiscordClient.changePresence("In the Menus", null); #end + FlxG.cameras.reset(new FunkinCamera('mainMenu')); + transIn = FlxTransitionableState.defaultTransIn; transOut = FlxTransitionableState.defaultTransOut; @@ -170,7 +172,6 @@ class MainMenuState extends MusicBeatState function resetCamStuff():Void { - FlxG.cameras.reset(new FunkinCamera('mainMenu')); FlxG.camera.follow(camFollow, null, 0.06); FlxG.camera.snapToTarget(); } @@ -329,6 +330,8 @@ class MainMenuState extends MusicBeatState persistentUpdate = false; FlxG.state.openSubState(new DebugMenuSubState()); + // reset camera when debug menu is closed + subStateClosed.addOnce(_ -> resetCamStuff()); } #end diff --git a/source/funkin/ui/story/LevelProp.hx b/source/funkin/ui/story/LevelProp.hx index 5a3efc36a..0547404a1 100644 --- a/source/funkin/ui/story/LevelProp.hx +++ b/source/funkin/ui/story/LevelProp.hx @@ -11,12 +11,15 @@ class LevelProp extends Bopper function set_propData(value:LevelPropData):LevelPropData { // Only reset the prop if the asset path has changed. - if (propData == null || value?.assetPath != propData?.assetPath) + if (propData == null || !(thx.Dynamics.equals(value, propData))) { + this.propData = value; + + this.visible = this.propData != null; + danceEvery = this.propData?.danceEvery ?? 0; + applyData(); } - this.visible = (value != null); - danceEvery = this.propData?.danceEvery ?? 0; return this.propData; } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 0c2214529..c1a001e5d 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -306,7 +306,7 @@ class StoryMenuState extends MusicBeatState { Conductor.instance.update(); - highScoreLerp = Std.int(MathUtil.smoothLerp(highScoreLerp, highScore, elapsed, 0.5)); + highScoreLerp = Std.int(MathUtil.smoothLerp(highScoreLerp, highScore, elapsed, 0.25)); scoreText.text = 'LEVEL SCORE: ${Math.round(highScoreLerp)}'; @@ -466,6 +466,9 @@ class StoryMenuState extends MusicBeatState // Disable the funny music thing for now. // funnyMusicThing(); } + + updateText(); + refresh(); } final FADE_OUT_TIME:Float = 1.5; diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 49bef5e4a..c9b3619e9 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -67,9 +67,11 @@ class TitleState extends MusicBeatState // DEBUG BULLSHIT // netConnection.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown); - new FlxTimer().start(1, function(tmr:FlxTimer) { + if (!initialized) new FlxTimer().start(1, function(tmr:FlxTimer) { startIntro(); }); + else + startIntro(); } function client_onMetaData(metaData:Dynamic) @@ -118,7 +120,7 @@ class TitleState extends MusicBeatState function startIntro():Void { - playMenuMusic(); + if (!initialized || FlxG.sound.music == null) playMenuMusic(); persistentUpdate = true; @@ -231,7 +233,7 @@ class TitleState extends MusicBeatState overrideExisting: true, restartTrack: true }); - // Fade from 0.0 to 0.7 over 4 seconds + // Fade from 0.0 to 1 over 4 seconds if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 1.0); } diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index 2f3b570b3..4e706c612 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -248,6 +248,11 @@ class Constants */ public static final DEFAULT_ARTIST:String = 'Unknown'; + /** + * The default charter for songs. + */ + public static final DEFAULT_CHARTER:String = 'Unknown'; + /** * The default note style for songs. */